#!/usr/bin/env node import { spawn } from "node:child_process"; import { access, readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const sourceFile = path.join( projectRoot, "dungeon-pipeline", "epoch-five-player-instances.json", ); const exists = async (file) => { try { await access(file); return true; } catch { return false; } }; const readJson = async (file) => JSON.parse(await readFile(file, "utf8")); const slugPart = (value) => String(value ?? "") .normalize("NFKD") .replace(/[\u0300-\u036f]/g, "") .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, "") || "unknown"; function run(script, argumentsList) { return new Promise((resolve, reject) => { const child = spawn(process.execPath, [script, ...argumentsList], { cwd: projectRoot, env: process.env, stdio: "inherit", windowsHide: true, }); child.once("error", reject); child.once("exit", (code) => ( code === 0 ? resolve() : reject(new Error(`${path.basename(script)} ${argumentsList.join(" ")} exited ${code}.`)) )); }); } const source = await readJson(sourceFile); const requested = new Set(process.argv.slice(2).filter((argument) => argument !== "--force")); const force = process.argv.includes("--force"); const dungeons = source.dungeons.filter((dungeon) => ( !requested.size || requested.has(dungeon.slug) )); if (!dungeons.length) throw new Error("No requested Epoch dungeon slugs were found."); if (!process.env[source.source.rootEnvironmentVariable] && !process.env.EPOCH_CLIENT_ROOT) { throw new Error(`${source.source.rootEnvironmentVariable} must point to the installed Epoch client.`); } const dungeonCli = path.join(projectRoot, "scripts", "dungeon-pipeline", "cli.mjs"); const scaffoldCli = path.join( projectRoot, "scripts", "manastorm-assets", "scaffold-converted-import.mjs", ); const importCli = path.join( projectRoot, "scripts", "manastorm-assets", "import-existing-stage.mjs", ); const manastormCli = path.join( projectRoot, "scripts", "manastorm-pipeline", "cli.mjs", ); const reports = []; for (const dungeon of dungeons) { const manastormSlug = `${dungeon.mapId}-${slugPart(dungeon.mapDirectory)}`; const shippingManifest = path.join( projectRoot, "public", "assets", "game", "manastorm", manastormSlug, "stage-pack.json", ); if (!force && await exists(shippingManifest)) { reports.push({ slug: dungeon.slug, manastormSlug, status: "already-built" }); continue; } try { const dungeonWork = path.join(projectRoot, "..", "HealerMan-Storage", "pipeline-work", "dungeons", dungeon.slug); const resumableStages = [ { command: "extract", outputs: [path.join(dungeonWork, "source-export", "automation-result.json")], }, { command: "convert", outputs: [path.join(dungeonWork, "staging", "environment", "conversion-report.json")], }, { command: "optimize", outputs: [path.join(dungeonWork, "staging", "environment", "optimization-report.json")], }, { command: "navmesh-conversion", outputs: [ path.join(dungeonWork, "recast-report.json"), path.join(dungeonWork, "staging", `${dungeon.slug}-navigation.glb`), ], }, ]; for (const { command, outputs } of resumableStages) { if (!force && (await Promise.all(outputs.map(exists))).every(Boolean)) continue; await run(dungeonCli, [command, dungeon.slug]); } await run(scaffoldCli, [manastormSlug, dungeon.slug]); await run(importCli, [manastormSlug]); await run(manastormCli, ["optimize", manastormSlug]); await run(manastormCli, ["pack", manastormSlug]); reports.push({ slug: dungeon.slug, manastormSlug, status: "green" }); } catch (error) { reports.push({ slug: dungeon.slug, manastormSlug, status: "error", error: error instanceof Error ? error.message : String(error), }); } } const failed = reports.filter((report) => report.status === "error"); console.log(JSON.stringify({ status: failed.length ? "error" : "green", dungeons: reports.length, built: reports.filter((report) => report.status === "green").length, skipped: reports.filter((report) => report.status === "already-built").length, failed, reports, }, null, 2)); if (failed.length) process.exitCode = 1;