public async run()

in packages/core/src/shared/utilities/processUtils.ts [291:411]


    public async run(params: ChildProcessRunOptions = {}): Promise<ChildProcessResult> {
        if (this.#childProcess) {
            throw new Error('process already started')
        }

        const options = {
            collect: true,
            waitForStreams: true,
            ...this.#baseOptions,
            ...params,
            spawnOptions: { ...this.#baseOptions.spawnOptions, ...params.spawnOptions },
        }

        const { rejectOnError, rejectOnErrorCode, timeout } = options
        const args = this.#args.concat(options.extraArgs ?? [])

        const debugDetail = this.#log.logLevelEnabled('debug')
            ? ` (running processes: ${ChildProcess.#runningProcesses.size})`
            : ''
        this.#log.info(`Command: ${this.toString(options.logging === 'noparams')}${debugDetail}`)

        const cleanup = () => {
            this.#childProcess?.stdout?.removeAllListeners()
            this.#childProcess?.stderr?.removeAllListeners()
        }

        return new Promise<ChildProcessResult>((resolve, reject) => {
            const errorHandler = (error: Error, force = options.useForceStop) => {
                this.#processErrors.push(error)
                if (!this.stopped) {
                    this.stop(force)
                }
                if (rejectOnError) {
                    if (typeof rejectOnError === 'function') {
                        reject(rejectOnError(error))
                    } else {
                        reject(error)
                    }
                }
            }

            const paramsContext: RunParameterContext = {
                timeout,
                logger: this.#log,
                stop: this.stop.bind(this),
                send: this.send.bind(this),
                reportError: (err) => errorHandler(err instanceof Error ? err : new Error(err)),
            }

            if (timeout && timeout?.completed) {
                throw new Error('Timeout token was already completed.')
            }

            // Async.
            // See also crossSpawn.spawnSync().
            // Arguments are forwarded[1] to node `child_process` module, see its documentation[2].
            // [1] https://github.com/moxystudio/node-cross-spawn/blob/master/index.js
            // [2] https://nodejs.org/api/child_process.html
            try {
                this.#childProcess = crossSpawn.spawn(this.#command, args, options.spawnOptions)
                this.#registerLifecycleListeners(this.#childProcess, errorHandler, options)
            } catch (err) {
                return reject(err)
            }

            // Emitted whenever:
            //  1. Process could not be spawned, or
            //  2. Process could not be killed, or
            //  3. Sending a message to the child process failed.
            // https://nodejs.org/api/child_process.html#child_process_class_childprocess
            // We also register error event handlers on the output/error streams in case a lower level library fails
            this.#childProcess.on('error', errorHandler)
            this.#childProcess.stdout?.on('error', errorHandler)
            this.#childProcess.stderr?.on('error', errorHandler)

            this.#childProcess.stdout?.on('data', (data: { toString(): string }) => {
                if (options.collect) {
                    this.#stdoutChunks.push(data.toString())
                }

                options.onStdout?.(data.toString(), paramsContext)
            })

            this.#childProcess.stderr?.on('data', (data: { toString(): string }) => {
                if (options.collect) {
                    this.#stderrChunks.push(data.toString())
                }

                options.onStderr?.(data.toString(), paramsContext)
            })

            // Emitted when streams are closed.
            // This will not be fired if `waitForStreams` is false
            this.#childProcess.once('close', (code, signal) => {
                this.#processResult = this.#makeResult(code ?? -1, signal ?? undefined)
                resolve(this.#processResult)
            })

            // Emitted when process exits or terminates.
            // https://nodejs.org/api/child_process.html#child_process_class_childprocess
            // - If the process exited, `code` is the final exit code of the process, else null.
            // - If the process terminated because of a signal, `signal` is the name of the signal, else null.
            // - One of `code` or `signal` will always be non-null.
            this.#childProcess.once('exit', (code, signal) => {
                this.#processResult = this.#makeResult(
                    typeof code === 'number' ? code : -1,
                    typeof signal === 'string' ? signal : undefined
                )
                if (code && rejectOnErrorCode) {
                    if (typeof rejectOnErrorCode === 'function') {
                        reject(rejectOnErrorCode(code))
                    } else {
                        reject(new Error(`Command exited with non-zero code (${code}): ${this.toString()}`))
                    }
                }
                if (options.waitForStreams === false) {
                    resolve(this.#processResult)
                }
            })
        }).finally(() => cleanup())
    }