refactor: generate CI workflow from gate contracts
This commit is contained in:
@@ -10,6 +10,9 @@ import {
|
||||
} from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import { parseCiGateContract } from "./contracts/ci-gates.ts";
|
||||
import { generateCiWorkflow } from "./generate-ci-workflow.ts";
|
||||
|
||||
const fixtureRoot = path.resolve(
|
||||
".tmp/browser-file-storage-runtime-removal",
|
||||
);
|
||||
@@ -29,6 +32,16 @@ const runtimePaths = [
|
||||
const runtimeSourceRoots = runtimePaths.filter((entry) =>
|
||||
entry.startsWith("src/"),
|
||||
);
|
||||
const removedScripts = new Set([
|
||||
"test:browser-capabilities",
|
||||
"verify:browser-capability-evidence",
|
||||
"check:browser-file-storage-boundaries",
|
||||
"test:browser-file-storage-removal",
|
||||
]);
|
||||
const removedEvidencePathFragments = [
|
||||
"browser-capabilities",
|
||||
"browser-file-storage-runtime-removal",
|
||||
] as const;
|
||||
const copyTargets = [
|
||||
"src",
|
||||
"tests",
|
||||
@@ -281,12 +294,7 @@ const packagePath = path.join(fixtureRoot, "package.json");
|
||||
const packageDocument = JSON.parse(await readFile(packagePath, "utf8")) as {
|
||||
scripts: Record<string, string>;
|
||||
};
|
||||
for (const script of [
|
||||
"test:browser-capabilities",
|
||||
"verify:browser-capability-evidence",
|
||||
"check:browser-file-storage-boundaries",
|
||||
"test:browser-file-storage-removal",
|
||||
]) {
|
||||
for (const script of removedScripts) {
|
||||
delete packageDocument.scripts[script];
|
||||
}
|
||||
await writeFile(
|
||||
@@ -308,39 +316,80 @@ await rm(
|
||||
path.join(fixtureRoot, "scripts/test-browser-file-storage-runtime-removal.ts"),
|
||||
{ force: true },
|
||||
);
|
||||
// The root snapshot locks the full repository inventory. This removal fixture
|
||||
// validates its smaller registry through check:ci and its regenerated workflow.
|
||||
await rm(
|
||||
path.join(fixtureRoot, "tests/unit/ci-workflow-generation.test.ts"),
|
||||
{ force: true },
|
||||
);
|
||||
await rm(
|
||||
path.join(
|
||||
fixtureRoot,
|
||||
"tests/unit/__snapshots__/ci-workflow-generation.test.ts.snap",
|
||||
),
|
||||
{ force: true },
|
||||
);
|
||||
|
||||
const gatesPath = path.join(fixtureRoot, "config/ci/gates.json");
|
||||
const gatesDocument = JSON.parse(
|
||||
await readFile(gatesPath, "utf8"),
|
||||
) as {
|
||||
gates: Record<
|
||||
string,
|
||||
{
|
||||
steps: Array<{ script: string }>;
|
||||
evidence: string[];
|
||||
}
|
||||
>;
|
||||
};
|
||||
for (const gate of Object.values(gatesDocument.gates)) {
|
||||
gate.steps = gate.steps.filter(
|
||||
({ script }) =>
|
||||
![
|
||||
"test:browser-capabilities",
|
||||
"verify:browser-capability-evidence",
|
||||
"check:browser-file-storage-boundaries",
|
||||
"test:browser-file-storage-removal",
|
||||
].includes(script),
|
||||
const gatesDocument = structuredClone(
|
||||
parseCiGateContract(JSON.parse(await readFile(gatesPath, "utf8"))),
|
||||
);
|
||||
const removedCommandIds = new Set(
|
||||
gatesDocument.commands
|
||||
.filter(({ script }) => removedScripts.has(script))
|
||||
.map(({ id }) => id),
|
||||
);
|
||||
if (removedCommandIds.size !== removedScripts.size) {
|
||||
throw new Error("Browser file/storage CI command removal set is incomplete");
|
||||
}
|
||||
const removedArtifactIds = new Set(
|
||||
gatesDocument.artifacts
|
||||
.filter(({ path: artifactPath }) =>
|
||||
removedEvidencePathFragments.some((fragment) =>
|
||||
artifactPath.includes(fragment),
|
||||
),
|
||||
)
|
||||
.map(({ id }) => id),
|
||||
);
|
||||
for (const fragment of removedEvidencePathFragments) {
|
||||
if (
|
||||
!gatesDocument.artifacts.some(({ path: artifactPath }) =>
|
||||
artifactPath.includes(fragment),
|
||||
)
|
||||
) {
|
||||
throw new Error(`Browser file/storage CI evidence is missing: ${fragment}`);
|
||||
}
|
||||
}
|
||||
gatesDocument.commands = gatesDocument.commands.filter(
|
||||
({ id }) => !removedCommandIds.has(id),
|
||||
);
|
||||
gatesDocument.artifacts = gatesDocument.artifacts.filter(
|
||||
({ id }) => !removedArtifactIds.has(id),
|
||||
);
|
||||
for (const gate of gatesDocument.gates) {
|
||||
gate.commandIds = gate.commandIds.filter(
|
||||
(commandId) => !removedCommandIds.has(commandId),
|
||||
);
|
||||
gate.evidence = gate.evidence.filter(
|
||||
(evidence) =>
|
||||
!evidence.includes("browser-capabilities") &&
|
||||
!evidence.includes("browser-file-storage-runtime-removal"),
|
||||
gate.evidenceArtifactIds = gate.evidenceArtifactIds.filter(
|
||||
(artifactId) => !removedArtifactIds.has(artifactId),
|
||||
);
|
||||
}
|
||||
const referencedSchemaIds = new Set(
|
||||
gatesDocument.artifacts.map(({ schemaId }) => schemaId),
|
||||
);
|
||||
gatesDocument.artifactSchemas = gatesDocument.artifactSchemas.filter(
|
||||
({ id }) => referencedSchemaIds.has(id),
|
||||
);
|
||||
const validatedGates = parseCiGateContract(gatesDocument);
|
||||
await writeFile(
|
||||
gatesPath,
|
||||
`${JSON.stringify(gatesDocument, null, 2)}\n`,
|
||||
`${JSON.stringify(validatedGates, null, 2)}\n`,
|
||||
);
|
||||
await generateCiWorkflow({
|
||||
root: fixtureRoot,
|
||||
contract: validatedGates,
|
||||
check: false,
|
||||
});
|
||||
await assertNoRuntimeImports(fixtureRoot);
|
||||
|
||||
const checks: Array<readonly [string, boolean]> = [
|
||||
|
||||
Reference in New Issue
Block a user