// What the child process call sites share: the exit status, read right after // `spawn` so the exit event cannot pass unseen while the caller reads the // streams, and echoing output without outrunning a slow reader. import type { ChildProcess } from "node:child_process"; import { once } from "node:events"; import { constants } from "node:os"; import type { Writable } from "node:stream"; // Resolves once the process exits, before its streams close, with its exit // code, or with 128 plus the signal number if a signal killed it, as a shell // reports. A process that fails to start rejects with the spawn error. export function exitCode(child: ChildProcess): Promise { const code = new Promise((resolve, reject) => { child.once("error", reject); child.once("exit", (exit, signal) => { resolve(exit ?? (signal === null ? 1 : 128 + constants.signals[signal])); }); }); // Callers await it after draining the streams; a failed start must not be // reported as an unhandled rejection before then. code.catch(() => {}); return code; } // Writes a chunk, waiting for the stream to drain when its buffer is full. export async function echo( stream: Writable, chunk: string | Uint8Array, ): Promise { if (!stream.write(chunk)) { await once(stream, "drain"); } }