import { readFile, stat } from "node:fs/promises"; import path from "node:path"; import process from "node:process"; const root = path.resolve(process.argv[2] || "web/admin/dist"); function resolveOutput(name) { const normalized = String(name || "").replaceAll("\\", "/"); if (!normalized || normalized.startsWith("/") || normalized.split("/").includes("..")) { throw new Error(`Invalid asset path in manifest: ${JSON.stringify(name)}`); } const resolved = path.resolve(root, ...normalized.split("/")); if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) { throw new Error(`Asset path escapes dist: ${JSON.stringify(name)}`); } return resolved; } async function requireFile(name) { const file = resolveOutput(name); const info = await stat(file); if (!info.isFile() || info.size === 0) { throw new Error(`Missing or empty admin asset: ${name}`); } } const manifest = JSON.parse(await readFile(path.join(root, "asset-manifest.json"), "utf8")); const build = JSON.parse(await readFile(path.join(root, "admin-build.json"), "utf8")); if (!build.buildId || typeof build.buildId !== "string") { throw new Error("admin-build.json does not contain a buildId"); } if (!manifest || typeof manifest !== "object" || Object.keys(manifest).length === 0) { throw new Error("asset-manifest.json is empty"); } await requireFile("index.html"); for (const [key, entry] of Object.entries(manifest)) { if (!entry?.file) throw new Error(`Manifest entry ${key} has no output file`); for (const dependency of [...(entry.imports || []), ...(entry.dynamicImports || [])]) { if (!manifest[dependency]) throw new Error(`Manifest entry ${key} references missing entry ${dependency}`); } for (const file of [entry.file, ...(entry.css || []), ...(entry.assets || [])]) { await requireFile(file); } } process.stdout.write(`Admin build ${build.buildId}: ${Object.keys(manifest).length} manifest entries verified.\n`);