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>, entryScript: string, ): string[] { const failures: string[] = []; const visiting = new Set(); const visited = new Set(); 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)]; }