| 261 | } |
| 262 | |
| 263 | #createResultPromise(): Promise<ExecResult> { |
| 264 | this.#boundAbortHandler = this.#abortHandler.bind(this) |
| 265 | this.#abortSignal.addEventListener('abort', this.#boundAbortHandler, { |
| 266 | once: true, |
| 267 | }) |
| 268 | |
| 269 | // Use 'exit' not 'close': 'close' waits for stdio to close, which includes |
| 270 | // grandchild processes that inherit file descriptors (e.g. `sleep 30 &`). |
| 271 | // 'exit' fires when the shell itself exits, returning control immediately. |
| 272 | this.#childProcess.once('exit', this.#exitHandler.bind(this)) |
| 273 | this.#childProcess.once('error', this.#errorHandler.bind(this)) |
| 274 | |
| 275 | this.#timeoutId = setTimeout( |
| 276 | ShellCommandImpl.#handleTimeout, |
| 277 | this.#timeout, |
| 278 | this, |
| 279 | ) as NodeJS.Timeout |
| 280 | |
| 281 | const exitPromise = new Promise<number>(resolve => { |
| 282 | this.#exitCodeResolver = resolve |
| 283 | }) |
| 284 | |
| 285 | return new Promise<ExecResult>(resolve => { |
| 286 | this.#resultResolver = resolve |
| 287 | void exitPromise.then(this.#handleExit.bind(this)) |
| 288 | }) |
| 289 | } |
| 290 | |
| 291 | async #handleExit(code: number): Promise<void> { |
| 292 | this.#cleanupListeners() |