fix(cli): lint sets process.exitCode instead of process.exit() to flush stdout

`hyperframes lint --json` wrote the JSON payload with console.log() and
then immediately called process.exit(). process.exit() terminates the
process before Node flushes an asynchronously-buffered stdout, which is
what a non-TTY (piped) stdout is — so `hyperframes lint --json | tee`,
`> out.json`, or any agent/CI capture silently lost the entire payload
on Windows (reported on 0.7.31 non-TTY). The same console.log-then-exit
pattern was on all four exit sites (both --json branches and the
human-readable + thrown-error paths), so any of them could truncate.

Fix: set process.exitCode and return, letting run() unwind so Node
drains stdout before exiting with the code. This is exactly the pattern
the other commands (publish/transcribe/upgrade/play/present) already
use; lint was the outlier still calling process.exit() after writing.

Test: new lint.test.ts drives the command's run() with mocked
lintProject/resolveProject and a process.exit spy that throws if
called. Covers the --json-with-errors, --json-clean, --json-thrown, and
human-readable paths — each asserts process.exit is never called and
the correct exitCode is set. Fails against the pre-fix code (the spy
throws on the first process.exit).
This commit is contained in:
Miguel Ángel
2026-07-07 17:10:02 -04:00
committed by GitHub
parent ce175ebe98
commit 4834de37f4
2 changed files with 103 additions and 4 deletions
+13 -4
View File
@@ -36,6 +36,12 @@ export default defineCommand({
},
},
async run({ args }) {
// Set process.exitCode + return instead of process.exit(): process.exit()
// terminates before Node flushes an async (non-TTY / piped) stdout, so
// `hyperframes lint --json | ...` on Windows silently loses the entire JSON
// payload written just above the exit. Letting run() return drains stdout
// first, then Node exits with the set code — the pattern the other commands
// (publish/transcribe/upgrade/play/present) already use.
try {
const project = resolveProject(args.dir);
const lintResult = await lintProject(project.dir);
@@ -51,7 +57,8 @@ export default defineCommand({
filesScanned: lintResult.results.length,
};
console.log(JSON.stringify(withMeta(combined), null, 2));
process.exit(combined.ok ? 0 : 1);
process.exitCode = combined.ok ? 0 : 1;
return;
}
const fileCount = lintResult.results.length;
@@ -72,7 +79,8 @@ export default defineCommand({
});
for (const line of lines) console.log(line);
process.exit(lintResult.totalErrors > 0 ? 1 : 0);
process.exitCode = lintResult.totalErrors > 0 ? 1 : 0;
return;
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
if (args.json) {
@@ -91,10 +99,11 @@ export default defineCommand({
2,
),
);
process.exit(1);
process.exitCode = 1;
return;
}
console.error(message);
process.exit(1);
process.exitCode = 1;
}
},
});