2020-09-09 08:56:58 -04:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google LLC All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2021-06-03 13:54:36 -04:00
|
|
|
import {spawn as _spawn, SpawnOptions as _SpawnOptions, spawnSync as _spawnSync, SpawnSyncOptions as _SpawnSyncOptions} from 'child_process';
|
2020-09-09 08:56:58 -04:00
|
|
|
import {debug, error} from './console';
|
|
|
|
|
2021-06-03 13:54:36 -04:00
|
|
|
|
|
|
|
export interface SpawnSyncOptions extends Omit<_SpawnSyncOptions, 'shell'|'stdio'> {
|
|
|
|
/** Whether to prevent exit codes being treated as failures. */
|
|
|
|
suppressErrorOnFailingExitCode?: boolean;
|
|
|
|
}
|
|
|
|
|
2020-09-09 08:56:58 -04:00
|
|
|
/** Interface describing the options for spawning a process. */
|
2021-06-03 13:54:36 -04:00
|
|
|
export interface SpawnOptions extends Omit<_SpawnOptions, 'shell'|'stdio'> {
|
2020-09-09 08:56:58 -04:00
|
|
|
/** Console output mode. Defaults to "enabled". */
|
|
|
|
mode?: 'enabled'|'silent'|'on-error';
|
2021-06-03 13:54:36 -04:00
|
|
|
/** Whether to prevent exit codes being treated as failures. */
|
|
|
|
suppressErrorOnFailingExitCode?: boolean;
|
2020-09-09 08:56:58 -04:00
|
|
|
}
|
|
|
|
|
2021-06-03 13:54:36 -04:00
|
|
|
/** Interface describing the options for spawning an interactive process. */
|
|
|
|
export type SpawnInteractiveCommandOptions = Omit<_SpawnOptions, 'shell'|'stdio'>;
|
|
|
|
|
2020-09-09 08:56:58 -04:00
|
|
|
/** Interface describing the result of a spawned process. */
|
2021-06-03 13:54:36 -04:00
|
|
|
export interface SpawnResult {
|
2020-09-09 08:56:58 -04:00
|
|
|
/** Captured stdout in string format. */
|
|
|
|
stdout: string;
|
2021-04-21 13:46:17 -04:00
|
|
|
/** Captured stderr in string format. */
|
|
|
|
stderr: string;
|
2021-06-03 13:54:36 -04:00
|
|
|
/** The exit code or signal of the process. */
|
|
|
|
status: number|NodeJS.Signals;
|
2020-09-09 08:56:58 -04:00
|
|
|
}
|
|
|
|
|
2021-05-04 11:22:11 -04:00
|
|
|
/**
|
|
|
|
* Spawns a given command with the specified arguments inside an interactive shell. All process
|
|
|
|
* stdin, stdout and stderr output is printed to the current console.
|
|
|
|
*
|
|
|
|
* @returns a Promise resolving on success, and rejecting on command failure with the status code.
|
|
|
|
*/
|
2021-06-03 13:54:36 -04:00
|
|
|
export function spawnInteractive(
|
|
|
|
command: string, args: string[], options: SpawnInteractiveCommandOptions = {}) {
|
2021-05-04 11:22:11 -04:00
|
|
|
return new Promise<void>((resolve, reject) => {
|
|
|
|
const commandText = `${command} ${args.join(' ')}`;
|
|
|
|
debug(`Executing command: ${commandText}`);
|
2021-06-03 13:54:36 -04:00
|
|
|
const childProcess = _spawn(command, args, {...options, shell: true, stdio: 'inherit'});
|
2021-05-04 11:22:11 -04:00
|
|
|
childProcess.on('exit', status => status === 0 ? resolve() : reject(status));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-09-09 08:56:58 -04:00
|
|
|
/**
|
|
|
|
* Spawns a given command with the specified arguments inside a shell. All process stdout
|
|
|
|
* output is captured and returned as resolution on completion. Depending on the chosen
|
|
|
|
* output mode, stdout/stderr output is also printed to the console, or only on error.
|
|
|
|
*
|
2021-04-21 13:46:17 -04:00
|
|
|
* @returns a Promise resolving with captured stdout and stderr on success. The promise
|
2021-06-03 13:54:36 -04:00
|
|
|
* rejects on command failure
|
2020-09-09 08:56:58 -04:00
|
|
|
*/
|
2021-06-03 13:54:36 -04:00
|
|
|
export function spawn(
|
|
|
|
command: string, args: string[], options: SpawnOptions = {}): Promise<SpawnResult> {
|
2020-09-09 08:56:58 -04:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const commandText = `${command} ${args.join(' ')}`;
|
|
|
|
const outputMode = options.mode;
|
|
|
|
|
|
|
|
debug(`Executing command: ${commandText}`);
|
|
|
|
|
2021-06-03 13:54:36 -04:00
|
|
|
const childProcess = _spawn(command, args, {...options, shell: true, stdio: 'pipe'});
|
2020-09-09 08:56:58 -04:00
|
|
|
let logOutput = '';
|
|
|
|
let stdout = '';
|
2021-04-21 13:46:17 -04:00
|
|
|
let stderr = '';
|
2020-09-09 08:56:58 -04:00
|
|
|
|
|
|
|
// Capture the stdout separately so that it can be passed as resolve value.
|
|
|
|
// This is useful if commands return parsable stdout.
|
|
|
|
childProcess.stderr.on('data', message => {
|
2021-04-21 13:46:17 -04:00
|
|
|
stderr += message;
|
2020-09-09 08:56:58 -04:00
|
|
|
logOutput += message;
|
|
|
|
// If console output is enabled, print the message directly to the stderr. Note that
|
2020-10-10 15:02:47 -04:00
|
|
|
// we intentionally print all output to stderr as stdout should not be polluted.
|
2020-09-09 08:56:58 -04:00
|
|
|
if (outputMode === undefined || outputMode === 'enabled') {
|
|
|
|
process.stderr.write(message);
|
|
|
|
}
|
|
|
|
});
|
2021-05-04 11:22:11 -04:00
|
|
|
|
2020-09-09 08:56:58 -04:00
|
|
|
childProcess.stdout.on('data', message => {
|
|
|
|
stdout += message;
|
|
|
|
logOutput += message;
|
|
|
|
// If console output is enabled, print the message directly to the stderr. Note that
|
2020-10-10 15:02:47 -04:00
|
|
|
// we intentionally print all output to stderr as stdout should not be polluted.
|
2020-09-09 08:56:58 -04:00
|
|
|
if (outputMode === undefined || outputMode === 'enabled') {
|
|
|
|
process.stderr.write(message);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-06-03 13:54:36 -04:00
|
|
|
childProcess.on('exit', (exitCode, signal) => {
|
|
|
|
const exitDescription = exitCode !== null ? `exit code "${exitCode}"` : `signal "${signal}"`;
|
2020-09-09 08:56:58 -04:00
|
|
|
const printFn = outputMode === 'on-error' ? error : debug;
|
2021-06-03 13:54:36 -04:00
|
|
|
const status = statusFromExitCodeAndSignal(exitCode, signal);
|
2020-09-09 08:56:58 -04:00
|
|
|
|
2020-10-10 15:02:47 -04:00
|
|
|
printFn(`Command "${commandText}" completed with ${exitDescription}.`);
|
2020-09-09 08:56:58 -04:00
|
|
|
printFn(`Process output: \n${logOutput}`);
|
|
|
|
|
|
|
|
// On success, resolve the promise. Otherwise reject with the captured stderr
|
|
|
|
// and stdout log output if the output mode was set to `silent`.
|
2021-06-03 13:54:36 -04:00
|
|
|
if (status === 0 || options.suppressErrorOnFailingExitCode) {
|
|
|
|
resolve({stdout, stderr, status});
|
2020-09-09 08:56:58 -04:00
|
|
|
} else {
|
|
|
|
reject(outputMode === 'silent' ? logOutput : undefined);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-06-03 13:54:36 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Spawns a given command with the specified arguments inside a shell syncronously.
|
|
|
|
*
|
|
|
|
* @returns The command's stdout and stderr.
|
|
|
|
*/
|
|
|
|
export function spawnSync(
|
|
|
|
command: string, args: string[], options: SpawnOptions = {}): SpawnResult {
|
|
|
|
const commandText = `${command} ${args.join(' ')}`;
|
|
|
|
debug(`Executing command: ${commandText}`);
|
|
|
|
|
|
|
|
const {status: exitCode, signal, stdout, stderr} =
|
|
|
|
_spawnSync(command, args, {...options, encoding: 'utf8', shell: true, stdio: 'pipe'});
|
|
|
|
|
|
|
|
/** The status of the spawn result. */
|
|
|
|
const status = statusFromExitCodeAndSignal(exitCode, signal);
|
|
|
|
|
|
|
|
if (status === 0 || options.suppressErrorOnFailingExitCode) {
|
|
|
|
return {status, stdout, stderr};
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error(stderr);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Convert the provided exitCode and signal to a single status code. */
|
|
|
|
function statusFromExitCodeAndSignal(exitCode: number|null, signal: NodeJS.Signals|null) {
|
|
|
|
return exitCode !== null ? exitCode : signal !== null ? signal : -1;
|
|
|
|
}
|