/** Bounded, escalating process shutdown for the long-lived CLI surfaces. */ /** Maximum grace allowed for the application tree to dispose before process exit. */ export const PROCESS_SHUTDOWN_TIMEOUT_MS = 5_000 /** Process-exit controller shared by normal completion and Unix signal handlers. */ export interface ProcessShutdown { /** Start or join graceful disposal before allowing natural completion with `code`. */ shutdown(code: number): Promise /** Start graceful disposal followed by exit, or force exit when shutdown is already running. */ interrupt(code: number): void } /** * Create one process-exit controller around an application disposer. * @param dispose - Whole-application teardown that resolves at quiescence. * @param forceExit - Function that exits the process immediately, replaceable by tests. * @param complete - Function that records the natural completion code, replaceable by tests. * @param timeoutMs - Grace before forced exit, replaceable by tests. * @returns A controller whose normal calls coalesce and whose repeated signal call escalates. */ export function createProcessShutdown( dispose: () => Promise, forceExit: (code: number) => void = (code) => { process.exit(code) }, complete: (code: number) => void = (code) => { process.exitCode = code }, timeoutMs = PROCESS_SHUTDOWN_TIMEOUT_MS, ): ProcessShutdown { let pending: Promise | undefined let timeout: ReturnType | undefined let completed = false let forceExited = false const clearExitTimeout = (): void => { /* v8 ignore else -- shutdown() arms the timer before any asynchronous exit path can run. */ if (timeout !== undefined) clearTimeout(timeout) } const forceExitOnce = (code: number): void => { if (forceExited) return forceExited = true clearExitTimeout() forceExit(code) } const completeOnce = (code: number): void => { if (completed || forceExited) return completed = true clearExitTimeout() complete(code) } const start = (code: number, forceAfterDispose: boolean): Promise => { if (pending !== undefined) return pending timeout = setTimeout(() => { forceExitOnce(code) }, timeoutMs) pending = Promise.resolve().then(dispose).then( () => { if (forceAfterDispose) forceExitOnce(code) else completeOnce(code) }, () => { forceExitOnce(code) }, ) return pending } return { shutdown(code) { return start(code, false) }, interrupt(code) { if (pending !== undefined) { forceExitOnce(code) return } void start(code, true) }, } }