import { readFile, writeFile, mkdir, rm } from "node:fs/promises"; import { createHash } from "node:crypto"; import { resolve } from "node:path"; const development = process.argv.includes("--development-fixture"); const retained = JSON.parse(process.env.FLAREBOT_RETAINED_RELEASES ?? "[]"); if ( !Array.isArray(retained) || retained.some((path) => typeof path !== "string" || !path) ) throw new Error("Invalid retained release inputs"); const retainedArgs = process.argv .filter((arg) => arg.startsWith("--retain=")) .map((arg) => arg.slice("--retain=".length)); if (retainedArgs.some((path) => !path)) throw new Error("Invalid retained release input"); const directories = [...retained, ...retainedArgs, "dist/release"].map( (directory) => resolve(directory), ); const sha = (bytes) => createHash("sha256").update(bytes).digest("hex"); const bundles = []; const versions = new Map(); for (const directory of directories) { const manifestBytes = await readFile(`${directory}/manifest.json`); const manifest = JSON.parse(manifestBytes); if (manifest.sourceDirty && !development) throw new Error( "Refusing to publish a dirty customer artifact. Commit source and rebuild, or select --development-fixture.", ); if ( manifest.files.reduce((sum, f) => sum + f.size, 0) > 24 * 1024 * 1024 || manifest.files .filter((f) => f.path.startsWith("worker/")) .reduce((sum, f) => sum + f.size, 0) > 16 * 1024 * 1024 ) throw new Error("Release exceeds deployment memory budget"); const artifactDigest = sha(manifestBytes); if ( artifactDigest !== (await readFile(`${directory}/manifest.sha256`, "utf8")).trim() ) throw new Error("Release manifest integrity mismatch"); for (const file of manifest.files) { if ( !/^(manifest\.json|deployment\.json|(?:worker|assets)\/[A-Za-z0-9_./-]+)$/.test( file.path, ) || file.path.split("/").some((p) => p === ".." || !p) ) throw new Error("Unsafe release path"); const bytes = await readFile(`${directory}/${file.path}`); if (bytes.length !== file.size || sha(bytes) !== file.sha256) throw new Error("Release file integrity mismatch"); } const identity = { version: manifest.release, sourceRevision: manifest.sourceRevision, artifactDigest, }; const prior = versions.get(identity.version); if (prior && prior !== identity.artifactDigest) throw new Error("A human release version cannot name different artifacts"); if (prior) throw new Error("Duplicate retained release identity"); versions.set(identity.version, identity.artifactDigest); bundles.push({ identity, development, files: manifest.files }); } await rm("dist/catalog", { recursive: true, force: true }); await mkdir("dist/catalog", { recursive: true }); const source = "// Generated trusted publisher metadata. Customer bytes remain in private R2.\n" + `export default ${JSON.stringify(bundles)};\n`; await writeFile("dist/catalog/catalog.ts", source); console.log( `Pinned publisher catalog: ${bundles.length} immutable release metadata entries${development ? " (development fixture)" : ""}`, );