Files
2026-09-12 11:07:25 -04:00

67 lines
3.7 KiB
JavaScript

import { copyFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import {
BUNDLED_ANDROID_PACKS, DEFAULT_CONTENT_SOURCES, PROJECT_ROOT,
createContentManifest, loadCampaignDungeonPackDefinitions, scanContentSources,
} from "./publish-content.mjs";
import { prepareContentObjects } from "./content-objects.mjs";
import contentPolicy from "../../shared/content-policy.json" with { type: "json" };
export function selectBootstrapContent(files, packDefinitions) {
const manifest = createContentManifest(files, { packDefinitions });
const packs = new Map(manifest.packs.map((pack) => [pack.id, pack]));
const included = new Set();
function include(id) {
if (included.has(id)) return;
included.add(id);
for (const dependency of packs.get(id)?.dependencies ?? []) include(dependency);
}
for (const packId of BUNDLED_ANDROID_PACKS) include(packId);
return {
files: files.filter((file) => included.has(file.packId)),
packDefinitions: manifest.packs.filter((pack) => included.has(pack.id)).map((pack) => ({
...pack,
bundledOnAndroid: BUNDLED_ANDROID_PACKS.includes(pack.id),
})),
};
}
export async function prepareBootstrap(platform) {
if (!["web", "android"].includes(platform)) throw new Error(`Unknown bootstrap platform: ${platform}`);
const outputDirectory = path.resolve(PROJECT_ROOT, `.${platform}-public`);
if (path.dirname(outputDirectory) !== PROJECT_ROOT || path.basename(outputDirectory) !== `.${platform}-public`) {
throw new Error(`Refusing to replace unexpected staging directory: ${outputDirectory}`);
}
const files = await scanContentSources([
...DEFAULT_CONTENT_SOURCES,
{ directory: path.join(PROJECT_ROOT, "public/basis"), logicalPrefix: "basis" },
{ directory: path.join(PROJECT_ROOT, "public/data"), logicalPrefix: "data" },
]);
const selected = selectBootstrapContent(files, await loadCampaignDungeonPackDefinitions());
await rm(outputDirectory, { recursive: true, force: true });
const prepared = await prepareContentObjects(selected.files, {
directory: path.join(outputDirectory, "content/bundled"), baseUrl: "/content/bundled",
});
if (prepared.storedBytes > contentPolicy.maxBootstrapStoredBytes) {
throw new Error(
`Bootstrap content is ${prepared.storedBytes} bytes; the release limit is ${contentPolicy.maxBootstrapStoredBytes} bytes.`,
);
}
const manifest = createContentManifest(prepared.files, {
baseUrl: "/content/bundled", packDefinitions: selected.packDefinitions,
});
await writeFile(path.join(outputDirectory, "content/bootstrap-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
await copyFile(path.join(PROJECT_ROOT, "public/content-cache-sw.js"), path.join(outputDirectory, "content-cache-sw.js"));
const storage = {
logicalBytes: prepared.logicalBytes, uniqueBytes: prepared.uniqueBytes, storedBytes: prepared.storedBytes,
duplicateBytesRemoved: prepared.logicalBytes - prepared.uniqueBytes,
uniqueObjects: prepared.uniqueObjects, logicalFiles: selected.files.length,
bundledDungeons: manifest.packs.filter((pack) => pack.id.startsWith("dungeon:")).length,
bundledManastormPacks: manifest.packs.filter((pack) => pack.id.startsWith("manastorm:") && pack.bundledOnAndroid).length,
};
await writeFile(path.join(outputDirectory, "content/storage-report.json"), `${JSON.stringify(storage, null, 2)}\n`);
console.log(`Prepared ${platform} bootstrap: ${storage.bundledDungeons} dungeons, ${prepared.uniqueObjects} shared objects`);
console.log(`Original: ${storage.logicalBytes} bytes; duplicates removed: ${storage.duplicateBytesRemoved} bytes; shipped: ${storage.storedBytes} bytes`);
return { manifest, storage, outputDirectory };
}