mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-24 06:14:01 +08:00
- Require the bootstrap seed while accepting scalar and list workflow dependencies. - Preserve stacked CI validation and instrumented seed safeguards.
93 lines
4.2 KiB
TypeScript
93 lines
4.2 KiB
TypeScript
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join } from "node:path";
|
|
import { load } from "js-yaml";
|
|
import { expect, test } from "vitest";
|
|
import { verifyBootstrapSeed } from "../../scripts/bootstrap-seed.mjs";
|
|
|
|
test("bootstrap artifacts require a complete seed from the same revision, lane, and path", () => {
|
|
const directory = mkdtempSync(join(tmpdir(), "scriptc-seed-contract-"));
|
|
try {
|
|
const metadata = { schema: 1, revision: "revision", sanitize: true, directory };
|
|
writeFileSync(join(directory, "bootstrap-seed.json"), JSON.stringify(metadata));
|
|
expect(() => verifyBootstrapSeed(directory, "revision", true)).toThrow("missing");
|
|
for (const path of [
|
|
"distribution/bin/scriptc",
|
|
"distribution/bin/scriptc.json",
|
|
".scriptc/distribution-seed/cli.ir.json",
|
|
".scriptc/distribution-seed/cli.ll",
|
|
".scriptc/distribution-seed/compiler.ffi.json",
|
|
]) {
|
|
const file = join(directory, path);
|
|
mkdirSync(dirname(file), { recursive: true });
|
|
writeFileSync(file, "fixture");
|
|
}
|
|
expect(() => verifyBootstrapSeed(directory, "revision", true)).not.toThrow();
|
|
expect(() => verifyBootstrapSeed(directory, "other", true)).toThrow("identity");
|
|
expect(() => verifyBootstrapSeed(directory, "revision", false)).toThrow("identity");
|
|
expect(() => verifyBootstrapSeed(directory, undefined, true)).toThrow("identity");
|
|
writeFileSync(
|
|
join(directory, "bootstrap-seed.json"),
|
|
JSON.stringify({ ...metadata, directory: "/different" }),
|
|
);
|
|
expect(() => verifyBootstrapSeed(directory, "revision", true)).toThrow("identity");
|
|
} finally {
|
|
rmSync(directory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("CI restores each seed only into its matching instrumented bootstrap lane", () => {
|
|
const workflow = load(
|
|
readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"),
|
|
) as {
|
|
jobs: Record<
|
|
string,
|
|
{
|
|
needs?: string | string[];
|
|
strategy: { matrix: { flavor?: string[]; include?: { flavor: string }[] } };
|
|
env: Record<string, string>;
|
|
steps: {
|
|
uses?: string;
|
|
if?: string;
|
|
with?: Record<string, unknown>;
|
|
env?: Record<string, string>;
|
|
run?: string;
|
|
}[];
|
|
}
|
|
>;
|
|
};
|
|
const seed = workflow.jobs.bootstrap_seed!;
|
|
const checks = workflow.jobs.bootstrap!;
|
|
const dependencies = Array.isArray(checks.needs) ? checks.needs : [checks.needs];
|
|
expect(dependencies).toContain("bootstrap_seed");
|
|
expect(
|
|
seed.steps
|
|
.filter((step) => step.uses === "./.github/actions/native-packages")
|
|
.map((step) => step.with?.target),
|
|
).toEqual(["linux-x64-gnu", "wasm32-wasi"]);
|
|
for (const step of checks.steps.filter(
|
|
(step) => step.uses === "./.github/actions/native-packages",
|
|
)) {
|
|
expect(step.if).toBe("matrix.phase == 'commands'");
|
|
}
|
|
// Each bootstrap job restores the seed of a flavor the seed job builds.
|
|
const checkFlavors = new Set(checks.strategy.matrix.include?.map((job) => job.flavor));
|
|
expect([...checkFlavors].toSorted()).toEqual(seed.strategy.matrix.flavor);
|
|
expect(seed.env.SCRIPTC_SAN).toBe(checks.env.SCRIPTC_SAN);
|
|
const prepare = seed.steps.find((step) => step.run === "node scripts/bootstrap-seed.mjs")!;
|
|
const execute = checks.steps.find((step) => step.run === "node scripts/ci-native-bootstrap.mjs")!;
|
|
expect(prepare.env?.SCRIPTC_BOOTSTRAP_SEED_DIRECTORY).toBe("${{ runner.temp }}/native-bootstrap");
|
|
expect(execute.env?.SCRIPTC_BOOTSTRAP_SEED_DIRECTORY).toBe(
|
|
prepare.env?.SCRIPTC_BOOTSTRAP_SEED_DIRECTORY,
|
|
);
|
|
// Runner paths are available in step contexts, not job-level env.
|
|
for (const job of [seed, checks]) {
|
|
expect(Object.values(job.env).some((value) => value.includes("runner."))).toBe(false);
|
|
}
|
|
const upload = seed.steps.find((step) => step.uses === "actions/upload-artifact@v4")!;
|
|
const download = checks.steps.find((step) => step.uses === "actions/download-artifact@v4")!;
|
|
expect(upload.with?.name).toBe("bootstrap-seed-${{ matrix.flavor }}-${{ github.sha }}");
|
|
expect(download.with?.name).toBe(upload.with?.name);
|
|
expect(upload.with?.["if-no-files-found"]).toBe("error");
|
|
});
|