Files
clean-architecture-frontend…/scripts/verify-reproducible-build.ts
T

84 lines
2.6 KiB
TypeScript

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<string[]> {
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<string> {
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`);