* Process the ChildProcess object created by an async command.
( command: string, options: CommonCmdOpts, childProcess: _ChildProcess, )
| 181 | * Process the ChildProcess object created by an async command. |
| 182 | */ |
| 183 | function processAsyncCmd( |
| 184 | command: string, |
| 185 | options: CommonCmdOpts, |
| 186 | childProcess: _ChildProcess, |
| 187 | ): Promise<SpawnResult> { |
| 188 | return new Promise((resolve, reject) => { |
| 189 | let logOutput = ''; |
| 190 | let stdout = ''; |
| 191 | let stderr = ''; |
| 192 | |
| 193 | Log.debug(`Executing command: ${sanitize(command)}`); |
| 194 | |
| 195 | childProcess.on('error', (err) => { |
| 196 | reject(err); |
| 197 | }); |
| 198 | |
| 199 | // If provided, write `input` text to the process `stdin`. |
| 200 | if (options.input !== undefined) { |
| 201 | assert( |
| 202 | childProcess.stdin, |
| 203 | 'Cannot write process `input` if there is no pipe `stdin` channel.', |
| 204 | ); |
| 205 | childProcess.stdin.write(options.input); |
| 206 | childProcess.stdin.end(); |
| 207 | } |
| 208 | |
| 209 | // Capture the stdout separately so that it can be passed as resolve value. |
| 210 | // This is useful if commands return parsable stdout. |
| 211 | childProcess.stderr?.on('data', (message) => { |
| 212 | stderr += message; |
| 213 | logOutput += message; |
| 214 | // If console output is enabled, print the message directly to the stderr. Note that |
| 215 | // we intentionally print all output to stderr as stdout should not be polluted. |
| 216 | if (options.mode === undefined || options.mode === 'enabled') { |
| 217 | process.stderr.write(sanitize(String(message))); |
| 218 | } |
| 219 | }); |
| 220 | |
| 221 | childProcess.stdout?.on('data', (message) => { |
| 222 | stdout += message; |
| 223 | logOutput += message; |
| 224 | // If console output is enabled, print the message directly to the stderr. Note that |
| 225 | // we intentionally print all output to stderr as stdout should not be polluted. |
| 226 | if (options.mode === undefined || options.mode === 'enabled') { |
| 227 | process.stderr.write(sanitize(String(message))); |
| 228 | } |
| 229 | }); |
| 230 | |
| 231 | // The `close` event is used because the process is guaranteed to have completed writing to |
| 232 | // stdout and stderr, using the `exit` event can cause inconsistent information in stdout and |
| 233 | // stderr due to a race condition around exiting. |
| 234 | childProcess.on('close', (exitCode, signal) => { |
| 235 | const exitDescription = exitCode !== null ? `exit code "${exitCode}"` : `signal "${signal}"`; |
| 236 | const status = statusFromExitCodeAndSignal(exitCode, signal); |
| 237 | const printFn = status !== 0 && options.mode === 'on-error' ? Log.error : Log.debug; |
| 238 | printFn(`Command "${sanitize(command)}" completed with ${exitDescription}.`); |
| 239 | printFn(`Process output: \n${sanitize(logOutput)}`); |
| 240 |