fix: harden CI evidence and removal contracts

This commit is contained in:
DongHyeonka
2026-08-02 14:48:04 +09:00
parent 1bb2cc4a20
commit f49d147b01
20 changed files with 1175 additions and 767 deletions
+45 -35
View File
@@ -1,17 +1,23 @@
import { spawnSync } from "node:child_process";
import {
cp,
mkdir,
readFile,
readdir,
rm,
symlink,
writeFile,
} from "node:fs/promises";
import path from "node:path";
import {
filesBelow,
prepareRemovalFixture,
pruneRemovalFixtureCiContract,
pruneScriptOrchestration,
regenerateRemovalFixtureWorkflow,
requireRemovalFixtureEnvironment,
runRemovalFixturePnpm,
} from "./lib/removal-fixture.ts";
const fixtureRoot = path.resolve(".tmp/optional-recipe-removal");
const pnpmCli = requireEnvironment("npm_execpath");
const pnpmCli = requireRemovalFixtureEnvironment("npm_execpath");
const copyTargets = [
"src",
"tests",
@@ -44,51 +50,55 @@ const copyTargets = [
".nvmrc",
];
function requireEnvironment(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`${name} is required to run removal verification`);
}
return value;
}
function runPnpm(script: string): boolean {
return (
spawnSync(process.execPath, [pnpmCli, script], {
cwd: fixtureRoot,
stdio: "inherit",
}).status === 0
);
return runRemovalFixturePnpm(fixtureRoot, pnpmCli, script);
}
async function filesBelow(directory: string): Promise<string[]> {
const entries = await readdir(directory, { withFileTypes: true });
const groups = await Promise.all(
entries.map((entry) => {
const target = path.join(directory, entry.name);
return entry.isDirectory() ? filesBelow(target) : [target];
}),
);
return groups.flat();
await prepareRemovalFixture(fixtureRoot, copyTargets);
for (const rootOnlyTest of [
"tests/unit/ci-workflow-generation.test.ts",
"tests/unit/__snapshots__/ci-workflow-generation.test.ts.snap",
]) {
await rm(path.join(fixtureRoot, rootOnlyTest), { force: true });
}
await rm(fixtureRoot, { recursive: true, force: true });
await mkdir(fixtureRoot, { recursive: true });
for (const target of copyTargets) {
await cp(target, path.join(fixtureRoot, target), { recursive: true });
}
await symlink(path.resolve("node_modules"), path.join(fixtureRoot, "node_modules"), "dir");
await rm(path.join(fixtureRoot, "recipes"), { recursive: true, force: true });
await rm(path.join(fixtureRoot, "tests/recipes"), {
recursive: true,
force: true,
});
const removedCiScripts = new Set([
"check:types:recipes",
"test:recipes",
"check:optional-recipes",
"check:optional-recipes:source",
"check:optional-recipe-fixtures",
"test:optional-recipe-removal",
]);
const fixturePackagePath = path.join(fixtureRoot, "package.json");
const fixturePackage = JSON.parse(await readFile(fixturePackagePath, "utf8")) as {
scripts: Record<string, string>;
};
pruneScriptOrchestration(fixturePackage.scripts, "check:types", removedCiScripts);
pruneScriptOrchestration(fixturePackage.scripts, "test:all", removedCiScripts);
await writeFile(fixturePackagePath, `${JSON.stringify(fixturePackage, null, 2)}\n`);
await pruneRemovalFixtureCiContract({
root: fixtureRoot,
removedScripts: removedCiScripts,
removedEvidencePathFragments: [
"optional-recipes",
"optional-recipe-fixtures",
"optional-recipe-removal",
],
});
await regenerateRemovalFixtureWorkflow(fixtureRoot);
const checks: Array<[string, boolean]> = [
["typecheck", runPnpm("check:types")],
["architecture", runPnpm("check:architecture")],
["test", runPnpm("test:all")],
["build", runPnpm("build")],
["ci-contract", runPnpm("check:ci")],
];
const residue: string[] = [];
for (const file of await filesBelow(path.join(fixtureRoot, "dist"))) {