| 69 | } |
| 70 | |
| 71 | class LocalProcess implements KaosProcess { |
| 72 | readonly stdin: Writable; |
| 73 | readonly stdout: Readable; |
| 74 | readonly stderr: Readable; |
| 75 | readonly pid: number; |
| 76 | |
| 77 | private readonly _child: ChildProcess; |
| 78 | private _exitCode: number | null = null; |
| 79 | private readonly _exitPromise: Promise<number>; |
| 80 | private _disposed = false; |
| 81 | |
| 82 | constructor(child: ChildProcess) { |
| 83 | if (child.stdin === null || child.stdout === null || child.stderr === null) { |
| 84 | throw new Error('Process must be created with stdin/stdout/stderr pipes.'); |
| 85 | } |
| 86 | |
| 87 | this._child = child; |
| 88 | this.stdin = child.stdin; |
| 89 | this.stdout = new BufferedReadable(child.stdout); |
| 90 | this.stderr = new BufferedReadable(child.stderr); |
| 91 | this.pid = child.pid ?? -1; |
| 92 | |
| 93 | this._exitPromise = new Promise<number>((resolve, reject) => { |
| 94 | child.on('exit', (code: number | null) => { |
| 95 | this._exitCode = code ?? -1; |
| 96 | resolve(this._exitCode); |
| 97 | }); |
| 98 | child.on('error', (error: Error) => { |
| 99 | reject(error); |
| 100 | }); |
| 101 | }); |
| 102 | } |
| 103 | |
| 104 | get exitCode(): number | null { |
| 105 | return this._exitCode; |
| 106 | } |
| 107 | |
| 108 | async wait(): Promise<number> { |
| 109 | return this._exitPromise; |
| 110 | } |
| 111 | |
| 112 | kill(signal?: NodeJS.Signals): Promise<void> { |
| 113 | // Reject if the process never actually started (spawn failed). |
| 114 | // pid <= 0 indicates ChildProcess.pid was undefined, which happens |
| 115 | // when spawn() fails to find/execute the command. Calling |
| 116 | // process.kill(-1, ...) on POSIX would signal the entire process |
| 117 | // group, potentially killing unrelated processes. |
| 118 | if (this.pid <= 0) { |
| 119 | return Promise.resolve(); |
| 120 | } |
| 121 | |
| 122 | // On Windows, `ChildProcess.kill()` only signals the shell parent, leaving |
| 123 | // grandchildren alive, so terminate the whole process tree with |
| 124 | // `taskkill /T`. A graceful `taskkill /T` (no `/F`) does not actually |
| 125 | // terminate a console node.exe tree, and Windows has no real graceful |
| 126 | // signal for it — Node's own `ChildProcess.kill()` is always a forceful |
| 127 | // TerminateProcess on Windows — so always force-terminate the tree. |
| 128 | if (isWindows) { |
nothing calls this directly
no outgoing calls
no test coverage detected