{"version":3,"file":"process-runner.d.ts","sourceRoot":"","sources":["../../src/core/process-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKvD,MAAM,WAAW,8BAA8B;IAC9C,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,8BAA8B,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAuJ9G","sourcesContent":["import type { ChildProcess } from \"node:child_process\";\n\nconst DEFAULT_DRAIN_TIMEOUT_MS = 250;\nconst DEFAULT_TERMINATION_TIMEOUT_MS = 2000;\n\nexport interface ForegroundProcessRunnerOptions {\n\tchild: ChildProcess;\n\tonStdout?: (data: Buffer) => void;\n\tonStderr?: (data: Buffer) => void;\n\tsignal?: AbortSignal;\n\ttimeout?: number;\n\tkill: () => void;\n\tdrainTimeoutMs?: number;\n\tterminationTimeoutMs?: number;\n}\n\nexport interface ForegroundProcessResult {\n\texitCode: number | null;\n}\n\n/**\n * Run a foreground wrapper without making inherited descendant handles part\n * of its lifecycle. The wrapper exit code is authoritative; stdout/stderr\n * receive a short bounded drain, then local read handles are closed.\n */\nexport function runForegroundProcess(options: ForegroundProcessRunnerOptions): Promise<ForegroundProcessResult> {\n\tconst {\n\t\tchild,\n\t\tonStdout,\n\t\tonStderr,\n\t\tsignal,\n\t\ttimeout,\n\t\tkill,\n\t\tdrainTimeoutMs = DEFAULT_DRAIN_TIMEOUT_MS,\n\t\tterminationTimeoutMs = DEFAULT_TERMINATION_TIMEOUT_MS,\n\t} = options;\n\n\treturn new Promise((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet processExited = false;\n\t\tlet exitCode: number | null = null;\n\t\tlet timedOut = false;\n\t\tlet aborted = signal?.aborted ?? false;\n\t\tlet drainTimer: NodeJS.Timeout | undefined;\n\t\tlet terminationTimer: NodeJS.Timeout | undefined;\n\t\tlet timeoutTimer: NodeJS.Timeout | undefined;\n\t\tlet stdoutEnded = child.stdout === null;\n\t\tlet stderrEnded = child.stderr === null;\n\n\t\tconst clearTimers = () => {\n\t\t\tif (drainTimer) clearTimeout(drainTimer);\n\t\t\tif (terminationTimer) clearTimeout(terminationTimer);\n\t\t\tif (timeoutTimer) clearTimeout(timeoutTimer);\n\t\t};\n\n\t\tconst detachStreams = () => {\n\t\t\tconst streams = [child.stdout, child.stderr];\n\t\t\tfor (const stream of streams) {\n\t\t\t\tif (!stream) continue;\n\t\t\t\tstream.removeListener(\"data\", stream === child.stdout ? handleStdout : handleStderr);\n\t\t\t\tstream.removeListener(\"end\", stream === child.stdout ? handleStdoutEnd : handleStderrEnd);\n\t\t\t\tstream.removeListener(\"close\", stream === child.stdout ? handleStdoutEnd : handleStderrEnd);\n\t\t\t\tstream.removeListener(\"error\", stream === child.stdout ? handleStdoutEnd : handleStderrEnd);\n\t\t\t\tif (!stream.destroyed) stream.destroy();\n\t\t\t}\n\t\t};\n\n\t\tconst cleanup = () => {\n\t\t\tclearTimers();\n\t\t\tchild.removeListener(\"error\", handleChildError);\n\t\t\tchild.removeListener(\"exit\", handleChildExit);\n\t\t\tchild.removeListener(\"close\", handleChildClose);\n\t\t\tsignal?.removeEventListener(\"abort\", handleAbort);\n\t\t\tdetachStreams();\n\t\t};\n\n\t\tconst finish = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tif (aborted) {\n\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t} else if (timedOut) {\n\t\t\t\treject(new Error(`timeout:${timeout}`));\n\t\t\t} else {\n\t\t\t\tresolve({ exitCode });\n\t\t\t}\n\t\t};\n\n\t\tconst scheduleNormalFinish = () => {\n\t\t\tif (settled || !processExited) return;\n\t\t\tif (stdoutEnded && stderrEnded) {\n\t\t\t\tfinish();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdrainTimer = setTimeout(finish, drainTimeoutMs);\n\t\t};\n\n\t\tconst terminate = () => {\n\t\t\ttry {\n\t\t\t\tkill();\n\t\t\t} catch {\n\t\t\t\t// Process may have exited between timeout/abort and kill.\n\t\t\t}\n\t\t\tterminationTimer = setTimeout(finish, terminationTimeoutMs);\n\t\t\tif (processExited) finish();\n\t\t};\n\n\t\tconst handleStdout = (data: Buffer) => onStdout?.(data);\n\t\tconst handleStderr = (data: Buffer) => onStderr?.(data);\n\t\tconst handleStdoutEnd = () => {\n\t\t\tstdoutEnded = true;\n\t\t\tscheduleNormalFinish();\n\t\t};\n\t\tconst handleStderrEnd = () => {\n\t\t\tstderrEnded = true;\n\t\t\tscheduleNormalFinish();\n\t\t};\n\t\tconst handleChildError = (error: Error) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\treject(error);\n\t\t};\n\t\tconst handleChildExit = (code: number | null) => {\n\t\t\tprocessExited = true;\n\t\t\texitCode = code;\n\t\t\tif (timedOut || aborted) {\n\t\t\t\tfinish();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tscheduleNormalFinish();\n\t\t};\n\t\tconst handleChildClose = (code: number | null) => {\n\t\t\tif (!processExited) {\n\t\t\t\tprocessExited = true;\n\t\t\t\texitCode = code;\n\t\t\t}\n\t\t\tif (timedOut || aborted) finish();\n\t\t\telse scheduleNormalFinish();\n\t\t};\n\t\tconst handleAbort = () => {\n\t\t\tif (settled) return;\n\t\t\taborted = true;\n\t\t\tterminate();\n\t\t};\n\n\t\tif (child.stdout) {\n\t\t\tchild.stdout.on(\"data\", handleStdout);\n\t\t\tchild.stdout.on(\"end\", handleStdoutEnd);\n\t\t\tchild.stdout.on(\"close\", handleStdoutEnd);\n\t\t\tchild.stdout.on(\"error\", handleStdoutEnd);\n\t\t}\n\t\tif (child.stderr) {\n\t\t\tchild.stderr.on(\"data\", handleStderr);\n\t\t\tchild.stderr.on(\"end\", handleStderrEnd);\n\t\t\tchild.stderr.on(\"close\", handleStderrEnd);\n\t\t\tchild.stderr.on(\"error\", handleStderrEnd);\n\t\t}\n\t\tchild.once(\"error\", handleChildError);\n\t\tchild.once(\"exit\", handleChildExit);\n\t\tchild.once(\"close\", handleChildClose);\n\n\t\tif (timeout !== undefined && timeout > 0) {\n\t\t\ttimeoutTimer = setTimeout(() => {\n\t\t\t\tif (settled) return;\n\t\t\t\ttimedOut = true;\n\t\t\t\tterminate();\n\t\t\t}, timeout * 1000);\n\t\t}\n\n\t\tif (signal) {\n\t\t\tif (signal.aborted) handleAbort();\n\t\t\telse signal.addEventListener(\"abort\", handleAbort, { once: true });\n\t\t}\n\t});\n}\n"]}