refactor: generate CI workflow from gate contracts
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
const packageScriptInvocation = /\b(?:(?:corepack\s+)?pnpm(?:\s+--?[A-Za-z][A-Za-z-]*(?:=[^\s;&|]+)?)*(?:\s+run)?|npm\s+run)\s+([A-Za-z0-9:_-]+)/gu;
|
||||
const pnpmNonScriptCommands = new Set(["dlx", "exec", "install"]);
|
||||
|
||||
export function validatePackageScriptGraph(
|
||||
scripts: Readonly<Record<string, string>>,
|
||||
entryScript: string,
|
||||
): string[] {
|
||||
const failures: string[] = [];
|
||||
const visiting = new Set<string>();
|
||||
const visited = new Set<string>();
|
||||
const stack: string[] = [];
|
||||
|
||||
const visit = (scriptName: string): void => {
|
||||
if (visiting.has(scriptName)) {
|
||||
const start = stack.indexOf(scriptName);
|
||||
failures.push(`package script cycle: ${[...stack.slice(start), scriptName].join(" -> ")}`);
|
||||
return;
|
||||
}
|
||||
if (visited.has(scriptName)) return;
|
||||
const command = scripts[scriptName];
|
||||
if (command === undefined) {
|
||||
failures.push(`package script missing: ${scriptName}`);
|
||||
return;
|
||||
}
|
||||
visiting.add(scriptName);
|
||||
stack.push(scriptName);
|
||||
if (/\bscripts\/run-ci-gate(?:\.[cm]?[jt]s)?\b/u.test(command)) {
|
||||
failures.push(`${scriptName} must not invoke the CI gate runner`);
|
||||
}
|
||||
if (/\bci:gate\b/u.test(command)) {
|
||||
failures.push(`${scriptName} must not invoke ci:gate`);
|
||||
}
|
||||
packageScriptInvocation.lastIndex = 0;
|
||||
const dependencies = Array.from(
|
||||
command.matchAll(packageScriptInvocation),
|
||||
(match) => match[1]!,
|
||||
).filter((dependency) => !pnpmNonScriptCommands.has(dependency));
|
||||
for (const dependency of dependencies) {
|
||||
if (dependency !== "ci:gate") {
|
||||
if (!(dependency in scripts)) {
|
||||
failures.push(`package script missing: ${scriptName} -> ${dependency}`);
|
||||
} else {
|
||||
visit(dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.pop();
|
||||
visiting.delete(scriptName);
|
||||
visited.add(scriptName);
|
||||
};
|
||||
|
||||
visit(entryScript);
|
||||
return [...new Set(failures)];
|
||||
}
|
||||
Reference in New Issue
Block a user