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"; 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 pack of manifest.packs) { if (BUNDLED_ANDROID_PACKS.includes(pack.id) || pack.id.startsWith("dungeon:")) include(pack.id); } return { files: files.filter((file) => included.has(file.packId)), packDefinitions: manifest.packs.filter((pack) => included.has(pack.id)).map((pack) => ({ ...pack, // Shared maps can be physically bundled without installing Manastorm gameplay. bundledOnAndroid: !pack.id.startsWith("manastorm:"), })), }; } 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", }); 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 }; }