// Child process bridge adapts child process events into typed lifecycle callbacks. import type { ChildProcess } from "node:child_process"; import process from "node:process"; /** Signal forwarding options for a child process bridge. */ type ChildProcessBridgeOptions = { signals?: NodeJS.Signals[]; onSignal?: (signal: NodeJS.Signals) => void; }; const defaultSignals: NodeJS.Signals[] = process.platform === "win32" ? ["SIGTERM", "SIGINT", "SIGBREAK"] : ["SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT"]; /** Forwards process termination signals to a child and detaches on terminal lifecycle events. */ export function attachChildProcessBridge( child: ChildProcess, { signals = defaultSignals, onSignal }: ChildProcessBridgeOptions = {}, ): { detach: () => void } { const listeners = new Map void>(); for (const signal of signals) { const listener = (): void => { onSignal?.(signal); try { child.kill(signal); } catch { // ignore } }; try { process.on(signal, listener); listeners.set(signal, listener); } catch { // Unsupported signal on this platform. } } const detach = (): void => { for (const [signal, listener] of listeners) { process.off(signal, listener); } listeners.clear(); }; // Child errors can report failed signal/IPC operations while the PID stays live. // Keep forwarding until exit, with close covering failed spawn and final handle cleanup. child.once("exit", detach); child.once("close", detach); return { detach }; }