Files
msd-core/scripts/check-alias-drift.cjs
Tom Boucher f729101eec refactor(scripts): replace process.exit() with ExitError + runMain handler (#739) (#740)
Part 1 of 2 of the n/no-process-exit cleanup (umbrella #738): convert every
process.exit() call in standalone scripts/** CLIs to the rule-compliant pattern.

- New shared helper scripts/lib/cli-exit.cjs: ExitError(code,message) + runMain()
  which translates a thrown ExitError / returned number into process.exitCode
  (never process.exit()), flushing output and still firing process.on('exit').
- main()-based entrypoints: throw new ExitError(code) for errors, return <code>
  for verdicts; invoked via runMain(main). Child exit codes preserved via return.
- top-level-only scripts: imperative body extracted into main() so mid-flow
  aborts (throw ExitError) actually halt; pure consts/helpers stay at module scope.
- diff-touches-shipped-paths.cjs: stdin event handling restructured to an async
  read so the whole flow runs under runMain; uncaughtException/unhandledRejection
  nets replaced by an in-band catch that preserves EXIT_ERROR=2.

Exit codes verified unchanged for every converted script (success/error/help and
the 0/1/2 semantic codes in diff-touches). Rule stays warn here; flipped to error
in part 2 (#738) once gsd-core/bin/** is also clean.

Refs #739

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:13:13 -04:00

115 lines
3.6 KiB
JavaScript

'use strict';
const fs = require('node:fs');
const path = require('node:path');
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
const ROOT = path.resolve(__dirname, '..');
const aliasesPath = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'command-aliases.cjs');
function fail(message) {
process.stderr.write(`${message}\n`);
throw new ExitError(1);
}
function ensureArray(value, name) {
if (!Array.isArray(value)) {
fail(`check:alias-drift: expected ${name} to be an array`);
}
}
function assertNoDuplicates(values, label) {
const seen = new Set();
for (const value of values) {
if (seen.has(value)) {
fail(`check:alias-drift: duplicate ${label} value "${value}"`);
}
seen.add(value);
}
}
function main() {
if (!fs.existsSync(aliasesPath)) {
fail(`check:alias-drift: missing ${path.relative(ROOT, aliasesPath)}`);
}
const aliases = require(aliasesPath);
const families = [
{
commandAliases: 'STATE_COMMAND_ALIASES',
subcommands: 'STATE_SUBCOMMANDS',
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'state-command-router.cjs'),
},
{
commandAliases: 'VERIFY_COMMAND_ALIASES',
subcommands: 'VERIFY_SUBCOMMANDS',
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'verify-command-router.cjs'),
},
{
commandAliases: 'INIT_COMMAND_ALIASES',
subcommands: 'INIT_SUBCOMMANDS',
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'init-command-router.cjs'),
},
{
commandAliases: 'PHASE_COMMAND_ALIASES',
subcommands: 'PHASE_SUBCOMMANDS',
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'phase-command-router.cjs'),
},
{
commandAliases: 'PHASES_COMMAND_ALIASES',
subcommands: 'PHASES_SUBCOMMANDS',
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'phases-command-router.cjs'),
},
{
commandAliases: 'VALIDATE_COMMAND_ALIASES',
subcommands: 'VALIDATE_SUBCOMMANDS',
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'validate-command-router.cjs'),
},
{
commandAliases: 'ROADMAP_COMMAND_ALIASES',
subcommands: 'ROADMAP_SUBCOMMANDS',
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'roadmap-command-router.cjs'),
},
];
for (const family of families) {
const commandAliases = aliases[family.commandAliases];
const subcommands = aliases[family.subcommands];
ensureArray(commandAliases, family.commandAliases);
ensureArray(subcommands, family.subcommands);
const derivedSubcommands = commandAliases.map((entry) => entry && entry.subcommand);
assertNoDuplicates(derivedSubcommands, `${family.commandAliases}.subcommand`);
if (derivedSubcommands.length !== subcommands.length) {
fail(
`check:alias-drift: ${family.subcommands} length ${subcommands.length} does not match ` +
`${family.commandAliases} length ${derivedSubcommands.length}`,
);
}
for (let i = 0; i < derivedSubcommands.length; i++) {
if (derivedSubcommands[i] !== subcommands[i]) {
fail(
`check:alias-drift: ${family.subcommands}[${i}] = "${subcommands[i]}" ` +
`does not match ${family.commandAliases}[${i}].subcommand = "${derivedSubcommands[i]}"`,
);
}
}
const routerSource = fs.readFileSync(family.routerPath, 'utf8');
if (!routerSource.includes(family.subcommands)) {
fail(
`check:alias-drift: ${path.relative(ROOT, family.routerPath)} does not reference ${family.subcommands}`,
);
}
}
process.stdout.write('check:alias-drift ok\n');
}
runMain(main);