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.
186 lines
6.7 KiB
JavaScript
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 };
|