Files
msd-core/scripts/gen-scripts-cli-exit.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

186 lines
6.7 KiB
JavaScript

#!/usr/bin/env node
/**
* gen-scripts-cli-exit.cjs — generates scripts/lib/cli-exit.cjs from a fresh
* compile of src/cli-exit.cts.
*
* ADR-3889 Phase 0 (#3904): scripts/lib/cli-exit.cjs used to be a hand-written
* fork of msd-core/bin/lib/cli-exit.cjs (the compiled artifact of
* src/cli-exit.cts). The two drifted — only the .cts copy routed a
* non-ExitError throw through getJsonErrorMode() to emit a structured
* { ok:false, reason, message } envelope. This script makes scripts/lib/cli-exit.cjs
* a generated artifact of the SAME source, so the two surfaces cannot diverge
* again.
*
* scripts/ runs straight from the repo checkout and must work on an unbuilt
* clone (scripts/check-env.cjs requires this file before any build runs), so
* the generated file is compiled to a THROWAWAY outDir rather than read from
* msd-core/bin/lib/ — reading the tracked build output would let a stale build
* produce a false green.
*
* Usage:
* node scripts/gen-scripts-cli-exit.cjs # same as --write
* node scripts/gen-scripts-cli-exit.cjs --write # write scripts/lib/cli-exit.cjs
* node scripts/gen-scripts-cli-exit.cjs --check # exit 1 if committed file is stale
*/
'use strict';
const { execFileSync } = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const REPO_ROOT = path.resolve(__dirname, '..');
const OUTPUT_PATH = path.join(REPO_ROOT, 'scripts', 'lib', 'cli-exit.cjs');
const COMPILE_TIMEOUT_MS = 60_000;
/** Frozen reason codes so tests assert on structure, not prose. */
const REASON = Object.freeze({
OK: 'ok_generated_sync',
DRIFTED: 'fail_generated_drifted',
BUILD_FAILED: 'fail_build_failed',
MISSING_EMIT: 'fail_missing_emit',
USAGE: 'fail_usage',
});
const USAGE_MESSAGE = [
'Usage: node scripts/gen-scripts-cli-exit.cjs [--write|--check]',
' (no flag) same as --write',
' --write write scripts/lib/cli-exit.cjs',
' --check exit 1 if the committed file is stale',
].join('\n');
const BANNER = [
'// GENERATED FILE — DO NOT EDIT BY HAND.',
'// Source of truth: src/cli-exit.cts. Regenerate with:',
'// node scripts/gen-scripts-cli-exit.cjs --write',
'// Byte-compared by `npm run lint:generated-sync` (#3904, ADR-3889 Phase 0).',
'//',
'// Why this copy exists: scripts/ runs straight from the repo checkout and must',
'// work on an unbuilt clone — 64+ scripts require this file, including',
'// check-env.cjs, which runs before any build. msd-core/bin/lib/cli-exit.cjs is',
'// gitignored tsc output and doubles as the build sentinel, so it cannot be',
'// required from here. Hence one source, two emitted locations.',
'',
'',
].join('\n');
/** Compile the whole project to a throwaway outDir so the work tree is untouched. */
function compileToTemp() {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-scripts-cli-exit-'));
try {
execFileSync(
process.execPath,
[
path.join(REPO_ROOT, 'node_modules', 'typescript', 'bin', 'tsc'),
'-p', path.join(REPO_ROOT, 'tsconfig.build.json'),
'--outDir', tmp,
// A throwaway outDir must not reuse the in-tree incremental state, or
// tsc skips emit for files it believes are already current.
'--incremental', 'false',
'--tsBuildInfoFile', 'null',
],
{ cwd: REPO_ROOT, encoding: 'utf8', stdio: 'pipe', timeout: COMPILE_TIMEOUT_MS },
);
return { ok: true, dir: tmp };
} catch (err) {
fs.rmSync(tmp, { recursive: true, force: true });
const detail = [err.stdout, err.stderr].filter(Boolean).join('\n').trim();
return { ok: false, detail };
}
}
/**
* Compile src/cli-exit.cts to a throwaway outDir and return the expected
* generated content (banner + compiled bytes), or a failure descriptor.
*
* @returns {{ ok: true, content: string } | { ok: false, reason: string, detail?: string }}
*/
function buildExpectedContent() {
const build = compileToTemp();
if (!build.ok) {
return { ok: false, reason: REASON.BUILD_FAILED, detail: build.detail };
}
try {
const emitted = path.join(build.dir, 'cli-exit.cjs');
if (!fs.existsSync(emitted)) {
return { ok: false, reason: REASON.MISSING_EMIT, detail: `no emit produced at ${emitted} from src/cli-exit.cts` };
}
const compiled = fs.readFileSync(emitted, 'utf8');
return { ok: true, content: BANNER + compiled };
} finally {
fs.rmSync(build.dir, { recursive: true, force: true });
}
}
function doWrite() {
const result = buildExpectedContent();
if (!result.ok) {
console.error(`FAIL gen-scripts-cli-exit: ${result.reason}`);
if (result.detail) console.error(result.detail);
return 1;
}
fs.mkdirSync(path.dirname(OUTPUT_PATH), { recursive: true });
fs.writeFileSync(OUTPUT_PATH, result.content, 'utf8');
console.log(`ok gen-scripts-cli-exit: wrote ${path.relative(REPO_ROOT, OUTPUT_PATH)}`);
return 0;
}
function doCheck() {
const result = buildExpectedContent();
if (!result.ok) {
console.error(`FAIL gen-scripts-cli-exit: ${result.reason}`);
if (result.detail) console.error(result.detail);
return 1;
}
if (!fs.existsSync(OUTPUT_PATH)) {
console.error(`FAIL gen-scripts-cli-exit: ${REASON.MISSING_EMIT}`);
console.error(` ${path.relative(REPO_ROOT, OUTPUT_PATH)} does not exist. Run:`);
console.error(' node scripts/gen-scripts-cli-exit.cjs --write');
return 1;
}
const committed = fs.readFileSync(OUTPUT_PATH, 'utf8');
if (committed !== result.content) {
console.error(`FAIL gen-scripts-cli-exit: ${REASON.DRIFTED}`);
console.error(
` ${path.relative(REPO_ROOT, OUTPUT_PATH)} (${committed.length} bytes) != ` +
`compile of src/cli-exit.cts (${result.content.length} bytes)`,
);
console.error('');
console.error('Regenerate with:');
console.error(' node scripts/gen-scripts-cli-exit.cjs --write');
return 1;
}
console.log(`ok gen-scripts-cli-exit: ${path.relative(REPO_ROOT, OUTPUT_PATH)} matches src/cli-exit.cts`);
return 0;
}
function main() {
const flag = process.argv[2];
const extra = process.argv[3];
if (flag !== undefined && flag !== '--write' && flag !== '--check') {
console.error(`FAIL gen-scripts-cli-exit: ${REASON.USAGE}`);
console.error(` unrecognized argument: ${flag}`);
console.error(USAGE_MESSAGE);
return 1;
}
if (extra !== undefined) {
console.error(`FAIL gen-scripts-cli-exit: ${REASON.USAGE}`);
console.error(` unexpected extra argument: ${extra}`);
console.error(USAGE_MESSAGE);
return 1;
}
if (flag === '--check') return doCheck();
return doWrite();
}
if (require.main === module) process.exitCode = main();
module.exports = { REASON, buildExpectedContent, OUTPUT_PATH, BANNER };