import { spawnSync } from "node:child_process"; import { mkdir, readFile, readdir, writeFile } from "node:fs/promises"; import path from "node:path"; import { assertCiBuildEnvironment } from "./lib/build-environment.ts"; import { supplyChainDigest } from "./lib/supply-chain.ts"; assertCiBuildEnvironment(process.env); async function filesWithin(directory: string): Promise { const entries = await readdir(directory, { withFileTypes: true }); const nested: string[][] = await Promise.all( entries.map((entry) => { const target = path.join(directory, entry.name); return entry.isDirectory() ? filesWithin(target) : [target]; }), ); return nested.flat().sort(); } async function distDigest(): Promise { const rows = await Promise.all( (await filesWithin("dist")).map(async (file) => ({ path: path.relative("dist", file).replaceAll("\\", "/"), bytes: (await readFile(file)).byteLength, content: supplyChainDigest(await readFile(file)), })), ); return supplyChainDigest(rows); } function build(environment: NodeJS.ProcessEnv = process.env) { return spawnSync("corepack", ["pnpm", "build"], { env: environment, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, }); } const deterministicEnvironment = { ...process.env, SOURCE_DATE_EPOCH: "946684800", }; const firstBuild = build(deterministicEnvironment); const firstDigest = firstBuild.status === 0 ? await distDigest() : "BUILD_FAILED"; const secondBuild = build(deterministicEnvironment); const secondDigest = secondBuild.status === 0 ? await distDigest() : "BUILD_FAILED"; const restoreBuild = build(); const passed = firstBuild.status === 0 && secondBuild.status === 0 && restoreBuild.status === 0 && firstDigest === secondDigest; await mkdir("artifacts/release", { recursive: true }); await writeFile( "artifacts/release/reproducible-build.json", `${JSON.stringify( { schemaVersion: 1, sourceDateEpoch: deterministicEnvironment.SOURCE_DATE_EPOCH, buildId: process.env.VITE_BUILD_ID ?? "local-build", commitSha: process.env.VITE_COMMIT_SHA ?? "local", releaseId: process.env.RELEASE_ID ?? "local-release", runnerImage: process.env.CI_RUNNER_IMAGE ?? `${process.platform}-${process.arch}`, firstDigest, secondDigest, restored: restoreBuild.status === 0, status: passed ? "PASS" : "FAIL", }, null, 2, )}\n`, ); if (!passed) { process.stderr.write( `Reproducible build failed: first=${firstDigest} second=${secondDigest}\n`, ); process.exit(1); } process.stdout.write(`Reproducible build: PASS (${firstDigest})\n`);