Spawn a hook command, pipe `input` to its stdin, and collect its output.
( command: string, input: string, timeoutMs: number, cwd: string, env: NodeJS.ProcessEnv, signal?: AbortSignal, )
| 390 | |
| 391 | /** Spawn a hook command, pipe `input` to its stdin, and collect its output. */ |
| 392 | function execHookCommand( |
| 393 | command: string, |
| 394 | input: string, |
| 395 | timeoutMs: number, |
| 396 | cwd: string, |
| 397 | env: NodeJS.ProcessEnv, |
| 398 | signal?: AbortSignal, |
| 399 | ): Promise<RawHookOutput> { |
| 400 | return new Promise((resolve) => { |
| 401 | let settled = false |
| 402 | let timedOut = false |
| 403 | let stdout = "" |
| 404 | let stderr = "" |
| 405 | |
| 406 | const child = spawn(getShell(), getShellRunArgs(command), { |
| 407 | cwd, |
| 408 | env, |
| 409 | stdio: ["pipe", "pipe", "pipe"], |
| 410 | windowsVerbatimArguments: process.platform === "win32", |
| 411 | }) |
| 412 | |
| 413 | const onAbort = () => child.kill("SIGTERM") |
| 414 | let killTimer: ReturnType<typeof setTimeout> | undefined |
| 415 | const timer = setTimeout(() => { |
| 416 | timedOut = true |
| 417 | child.kill("SIGTERM") |
| 418 | // Escalate to SIGKILL if SIGTERM is ignored; unref'd so this never |
| 419 | // keeps the process alive on its own. |
| 420 | killTimer = setTimeout(() => child.kill("SIGKILL"), 2000) |
| 421 | killTimer.unref?.() |
| 422 | }, timeoutMs) |
| 423 | timer.unref?.() |
| 424 | |
| 425 | const finish = (code: number) => { |
| 426 | if (settled) return |
| 427 | settled = true |
| 428 | clearTimeout(timer) |
| 429 | if (killTimer) clearTimeout(killTimer) |
| 430 | signal?.removeEventListener("abort", onAbort) |
| 431 | resolve({ stdout, stderr, code, timedOut }) |
| 432 | } |
| 433 | |
| 434 | child.stdout.setEncoding("utf8") |
| 435 | child.stderr.setEncoding("utf8") |
| 436 | child.stdout.on("data", (d: string) => { |
| 437 | if (stdout.length < MAX_HOOK_OUTPUT_CHARS) stdout += d |
| 438 | }) |
| 439 | child.stderr.on("data", (d: string) => { |
| 440 | if (stderr.length < MAX_HOOK_OUTPUT_CHARS) stderr += d |
| 441 | }) |
| 442 | |
| 443 | child.on("error", (error) => { |
| 444 | stderr += (stderr ? "\n" : "") + (error as Error).message |
| 445 | finish(1) |
| 446 | }) |
| 447 | child.on("close", (code) => finish(code ?? (timedOut ? 124 : 0))) |
| 448 | |
| 449 | if (signal) { |
no test coverage detected