vite.config.ts
Rama main · réplica de lectura del repositorio original.
import {
cpSync,
mkdirSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig, loadEnv, type Plugin } from "vite";
import {
ART_PACKS,
PACK_IDS,
resolvePacks,
unknownPacks,
} from "./src/protocol/castPacks.js";
import {
CATALOG_FILE,
catalogFromManifests,
docAsset,
droppedAvatarAtlases,
SKILL_DOCS,
SKILL_REFERENCE,
SKILL_THUMBS,
shippedManifests,
} from "./src/studio/catalogJson.js";
import { artUrls, moveArt, rewriteArtUrls } from "./tools/artHash.js";
import {
type OperatorValues,
robotsTxt,
sitemapXml,
substituteOperator,
} from "./tools/operatorHtml.js";
const entry = (file: string): string =>
fileURLToPath(new URL(file, import.meta.url));
const AVATAR_MANIFEST = "assets/avatars/manifest.json";
const BACKDROP_MANIFEST = "assets/backgrounds/manifest.json";
// a copy of public/ holding only the shipped cast, handed to vite as publicDir
function shippedPublicDir(mode: string): string {
const value = loadEnv(mode, entry("."), "").CHARACTER_PACKS;
const unknown = unknownPacks(value);
if (unknown.length > 0)
throw new Error(
`CHARACTER_PACKS names no such pack: ${unknown.join(", ")}. Use ${PACK_IDS.join(", ")}, all, or none`,
);
const packs = resolvePacks(value);
const source = entry("public");
const read = (file: string) => readFileSync(join(source, file), "utf8");
const dropped = new Set([
...droppedAvatarAtlases(read(AVATAR_MANIFEST), packs).map((file) =>
resolve(source, "assets/avatars", file),
),
...ART_PACKS.filter((pack) => !packs.has(pack.id)).flatMap((pack) =>
pack.backdrops.map((name) =>
resolve(source, "assets/backgrounds", `${name}.png`),
),
),
]);
const staged = entry("node_modules/.cache/art-packs/public");
rmSync(staged, { recursive: true, force: true });
cpSync(source, staged, {
recursive: true,
filter: (file) => !dropped.has(resolve(file)),
});
const manifests = shippedManifests(
read(AVATAR_MANIFEST),
read(BACKDROP_MANIFEST),
packs,
);
const moved = moveArt(
staged,
artUrls(manifests.avatars, manifests.backgrounds),
);
writeFileSync(
join(staged, AVATAR_MANIFEST),
rewriteArtUrls(manifests.avatars, moved),
);
writeFileSync(
join(staged, BACKDROP_MANIFEST),
rewriteArtUrls(manifests.backgrounds, moved),
);
writeFileSync(
join(staged, CATALOG_FILE),
JSON.stringify(
catalogFromManifests(
manifests.avatars,
manifests.backgrounds,
readFileSync(entry(`${SKILL_REFERENCE}/cast.md`), "utf8"),
),
),
);
for (const doc of SKILL_DOCS)
writeFileSync(
join(staged, docAsset(doc)),
readFileSync(entry(`${SKILL_REFERENCE}/${doc}`), "utf8"),
);
for (const thumb of SKILL_THUMBS) {
const dest = join(staged, docAsset(thumb));
mkdirSync(join(dest, ".."), { recursive: true });
writeFileSync(dest, readFileSync(entry(`${SKILL_REFERENCE}/${thumb}`)));
}
return staged;
}
// the empty prefix takes plain names, so Workers Builds vars and a local .env read the same
function operatorPages(): Plugin {
let values: OperatorValues = {};
return {
name: "operator-pages",
configResolved(config) {
values = loadEnv(config.mode, config.root, "");
},
transformIndexHtml: {
order: "pre",
handler: (html) => substituteOperator(html, values),
},
generateBundle() {
this.emitFile({
type: "asset",
fileName: "robots.txt",
source: robotsTxt(values.SITE_ORIGIN),
});
const sitemap = sitemapXml(values.SITE_ORIGIN);
if (sitemap)
this.emitFile({
type: "asset",
fileName: "sitemap.xml",
source: sitemap,
});
},
};
}
export default defineConfig(({ mode }) => ({
publicDir: shippedPublicDir(mode),
plugins: [operatorPages()],
build: {
// art keeps /assets, so the immutable cache header covers these hashed bundles alone
assetsDir: "build",
rollupOptions: {
input: {
main: entry("index.html"),
studio: entry("studio.html"),
legal: entry("legal.html"),
report: entry("report.html"),
agentSkill: entry("agent-skill.html"),
},
},
},
// so `vite` (HMR) can borrow the worker's /api + room websockets from a `wrangler dev` on :8787
server: {
proxy: {
"/api": {
target: "http://127.0.0.1:8787",
changeOrigin: true,
ws: true,
},
},
},
}));