check / check (push) Successful in 1m13s
An error a command throws now reaches the user as one `quak: MESSAGE` line on stderr, and the CLI exits 1 once output has drained. The wrapper moved from bin/quak.ts to src/cli-run.ts so it can be tested, and bin/quak.ts awaits program.parseAsync() so async actions are awaited. Model: opus-5-5
37 lines
1.1 KiB
TypeScript
37 lines
1.1 KiB
TypeScript
// Runs one CLI command for `bin/quak.ts` and exits with its code.
|
|
|
|
import type { Writable } from "node:stream";
|
|
|
|
// Run a command and exit with its code once stdout/stderr have drained.
|
|
// Exiting before the drain can truncate piped output, and the library can keep
|
|
// the event loop alive after a command returns, so a plain return could hang.
|
|
// An error the command throws is printed as one `quak: MESSAGE` line, without
|
|
// the stack trace, and exits 1.
|
|
export const run = async (
|
|
command: Promise<number>,
|
|
stdout: Writable,
|
|
stderr: Writable,
|
|
exit: (code: number) => void,
|
|
): Promise<void> => {
|
|
let code: number;
|
|
try {
|
|
code = await command;
|
|
} catch (err) {
|
|
stderr.write(
|
|
`quak: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
);
|
|
code = 1;
|
|
}
|
|
const pending = [stdout, stderr].filter((s) => s.writableLength > 0);
|
|
if (pending.length === 0) {
|
|
exit(code);
|
|
return;
|
|
}
|
|
let remaining = pending.length;
|
|
for (const s of pending) {
|
|
s.once("drain", () => {
|
|
if (--remaining === 0) exit(code);
|
|
});
|
|
}
|
|
};
|