84 lines
2.6 KiB
TypeScript
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`);
|