| 57 | export type Child = ChildProcess & { exited: Promise<number> } |
| 58 | |
| 59 | export function spawn(cmd: string[], opts: Options = {}): Child { |
| 60 | if (cmd.length === 0) throw new Error("Command is required") |
| 61 | opts.abort?.throwIfAborted() |
| 62 | |
| 63 | const proc = launch(cmd[0], cmd.slice(1), { |
| 64 | cwd: opts.cwd, |
| 65 | shell: opts.shell, |
| 66 | env: opts.env === null ? {} : opts.env ? { ...process.env, ...opts.env } : undefined, |
| 67 | stdio: [opts.stdin ?? "ignore", opts.stdout ?? "ignore", opts.stderr ?? "ignore"], |
| 68 | windowsHide: process.platform === "win32", |
| 69 | }) |
| 70 | |
| 71 | let closed = false |
| 72 | let timer: ReturnType<typeof setTimeout> | undefined |
| 73 | |
| 74 | const abort = () => { |
| 75 | if (closed) return |
| 76 | if (proc.exitCode !== null || proc.signalCode !== null) return |
| 77 | closed = true |
| 78 | |
| 79 | proc.kill(opts.kill ?? "SIGTERM") |
| 80 | |
| 81 | const ms = opts.timeout ?? 5_000 |
| 82 | if (ms <= 0) return |
| 83 | timer = setTimeout(() => proc.kill("SIGKILL"), ms) |
| 84 | } |
| 85 | |
| 86 | const exited = new Promise<number>((resolve, reject) => { |
| 87 | const done = () => { |
| 88 | opts.abort?.removeEventListener("abort", abort) |
| 89 | if (timer) clearTimeout(timer) |
| 90 | } |
| 91 | |
| 92 | proc.once("exit", (code, signal) => { |
| 93 | done() |
| 94 | resolve(code ?? (signal ? 1 : 0)) |
| 95 | }) |
| 96 | |
| 97 | proc.once("error", (error) => { |
| 98 | done() |
| 99 | reject(error) |
| 100 | }) |
| 101 | }) |
| 102 | void exited.catch(() => undefined) |
| 103 | |
| 104 | if (opts.abort) { |
| 105 | opts.abort.addEventListener("abort", abort, { once: true }) |
| 106 | if (opts.abort.aborted) abort() |
| 107 | } |
| 108 | |
| 109 | const child = proc as Child |
| 110 | child.exited = exited |
| 111 | return child |
| 112 | } |
| 113 | |
| 114 | export async function run(cmd: string[], opts: RunOptions = {}): Promise<Result> { |
| 115 | const proc = spawn(cmd, { |