refactor(dev-infra): add spawnSync to child process utils, normalize naming of child-process utils (#42911)
Create a `spawnSync` command for common usage, additionally update naming to use `spawn` instead of `spawnWithDebugOutput` PR Close #42911
This commit is contained in:
parent
9d747e3aa3
commit
d3b0d1e3fb
|
@ -145,6 +145,7 @@ var GitCommandError = /** @class */ (function (_super) {
|
||||||
// we sanitize the command that will be part of the error message.
|
// we sanitize the command that will be part of the error message.
|
||||||
_super.call(this, "Command failed: git " + client.sanitizeConsoleOutput(args.join(' '))) || this;
|
_super.call(this, "Command failed: git " + client.sanitizeConsoleOutput(args.join(' '))) || this;
|
||||||
_this.args = args;
|
_this.args = args;
|
||||||
|
Object.setPrototypeOf(_this, GitCommandError.prototype);
|
||||||
return _this;
|
return _this;
|
||||||
}
|
}
|
||||||
return GitCommandError;
|
return GitCommandError;
|
||||||
|
|
|
@ -317,6 +317,7 @@ var GitCommandError = /** @class */ (function (_super) {
|
||||||
// we sanitize the command that will be part of the error message.
|
// we sanitize the command that will be part of the error message.
|
||||||
_super.call(this, "Command failed: git " + client.sanitizeConsoleOutput(args.join(' '))) || this;
|
_super.call(this, "Command failed: git " + client.sanitizeConsoleOutput(args.join(' '))) || this;
|
||||||
_this.args = args;
|
_this.args = args;
|
||||||
|
Object.setPrototypeOf(_this, GitCommandError.prototype);
|
||||||
return _this;
|
return _this;
|
||||||
}
|
}
|
||||||
return GitCommandError;
|
return GitCommandError;
|
||||||
|
@ -3463,30 +3464,40 @@ function discoverNewConflictsForPr(newPrNumber, updatedAfter) {
|
||||||
info(`Retrieved ${allPendingPRs.length} total pending PRs`);
|
info(`Retrieved ${allPendingPRs.length} total pending PRs`);
|
||||||
info(`Checking ${pendingPrs.length} PRs for conflicts after a merge of #${newPrNumber}`);
|
info(`Checking ${pendingPrs.length} PRs for conflicts after a merge of #${newPrNumber}`);
|
||||||
// Fetch and checkout the PR being checked.
|
// Fetch and checkout the PR being checked.
|
||||||
exec(`git fetch ${requestedPr.headRef.repository.url} ${requestedPr.headRef.name}`);
|
git.run(['fetch', '-q', requestedPr.headRef.repository.url, requestedPr.headRef.name]);
|
||||||
exec(`git checkout -B ${tempWorkingBranch} FETCH_HEAD`);
|
git.run(['checkout', '-q', '-B', tempWorkingBranch, 'FETCH_HEAD']);
|
||||||
// Rebase the PR against the PRs target branch.
|
// Rebase the PR against the PRs target branch.
|
||||||
exec(`git fetch ${requestedPr.baseRef.repository.url} ${requestedPr.baseRef.name}`);
|
git.run(['fetch', '-q', requestedPr.baseRef.repository.url, requestedPr.baseRef.name]);
|
||||||
const result = exec(`git rebase FETCH_HEAD`);
|
try {
|
||||||
if (result.code) {
|
git.run(['rebase', 'FETCH_HEAD'], { stdio: 'ignore' });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
if (err instanceof GitCommandError) {
|
||||||
error('The requested PR currently has conflicts');
|
error('The requested PR currently has conflicts');
|
||||||
cleanUpGitState(previousBranchOrRevision);
|
git.checkout(previousBranchOrRevision, true);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
// Start the progress bar
|
// Start the progress bar
|
||||||
progressBar.start(pendingPrs.length, 0);
|
progressBar.start(pendingPrs.length, 0);
|
||||||
// Check each PR to determine if it can merge cleanly into the repo after the target PR.
|
// Check each PR to determine if it can merge cleanly into the repo after the target PR.
|
||||||
for (const pr of pendingPrs) {
|
for (const pr of pendingPrs) {
|
||||||
// Fetch and checkout the next PR
|
// Fetch and checkout the next PR
|
||||||
exec(`git fetch ${pr.headRef.repository.url} ${pr.headRef.name}`);
|
git.run(['fetch', '-q', pr.headRef.repository.url, pr.headRef.name]);
|
||||||
exec(`git checkout --detach FETCH_HEAD`);
|
git.run(['checkout', '-q', '--detach', 'FETCH_HEAD']);
|
||||||
// Check if the PR cleanly rebases into the repo after the target PR.
|
// Check if the PR cleanly rebases into the repo after the target PR.
|
||||||
const result = exec(`git rebase ${tempWorkingBranch}`);
|
try {
|
||||||
if (result.code !== 0) {
|
git.run(['rebase', tempWorkingBranch], { stdio: 'ignore' });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
if (err instanceof GitCommandError) {
|
||||||
conflicts.push(pr);
|
conflicts.push(pr);
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
// Abort any outstanding rebase attempt.
|
// Abort any outstanding rebase attempt.
|
||||||
exec(`git rebase --abort`);
|
git.runGraceful(['rebase', '--abort'], { stdio: 'ignore' });
|
||||||
progressBar.increment(1);
|
progressBar.increment(1);
|
||||||
}
|
}
|
||||||
// End the progress bar as all PRs have been processed.
|
// End the progress bar as all PRs have been processed.
|
||||||
|
@ -5978,7 +5989,7 @@ const ReleaseNotesCommandModule = {
|
||||||
*
|
*
|
||||||
* @returns a Promise resolving on success, and rejecting on command failure with the status code.
|
* @returns a Promise resolving on success, and rejecting on command failure with the status code.
|
||||||
*/
|
*/
|
||||||
function spawnInteractiveCommand(command, args, options) {
|
function spawnInteractive(command, args, options) {
|
||||||
if (options === void 0) { options = {}; }
|
if (options === void 0) { options = {}; }
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
var commandText = command + " " + args.join(' ');
|
var commandText = command + " " + args.join(' ');
|
||||||
|
@ -5993,9 +6004,9 @@ function spawnInteractiveCommand(command, args, options) {
|
||||||
* output mode, stdout/stderr output is also printed to the console, or only on error.
|
* output mode, stdout/stderr output is also printed to the console, or only on error.
|
||||||
*
|
*
|
||||||
* @returns a Promise resolving with captured stdout and stderr on success. The promise
|
* @returns a Promise resolving with captured stdout and stderr on success. The promise
|
||||||
* rejects on command failure.
|
* rejects on command failure
|
||||||
*/
|
*/
|
||||||
function spawnWithDebugOutput(command, args, options) {
|
function spawn(command, args, options) {
|
||||||
if (options === void 0) { options = {}; }
|
if (options === void 0) { options = {}; }
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
var commandText = command + " " + args.join(' ');
|
var commandText = command + " " + args.join(' ');
|
||||||
|
@ -6025,15 +6036,16 @@ function spawnWithDebugOutput(command, args, options) {
|
||||||
process.stderr.write(message);
|
process.stderr.write(message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
childProcess.on('exit', function (status, signal) {
|
childProcess.on('exit', function (exitCode, signal) {
|
||||||
var exitDescription = status !== null ? "exit code \"" + status + "\"" : "signal \"" + signal + "\"";
|
var exitDescription = exitCode !== null ? "exit code \"" + exitCode + "\"" : "signal \"" + signal + "\"";
|
||||||
var printFn = outputMode === 'on-error' ? error : debug;
|
var printFn = outputMode === 'on-error' ? error : debug;
|
||||||
|
var status = statusFromExitCodeAndSignal(exitCode, signal);
|
||||||
printFn("Command \"" + commandText + "\" completed with " + exitDescription + ".");
|
printFn("Command \"" + commandText + "\" completed with " + exitDescription + ".");
|
||||||
printFn("Process output: \n" + logOutput);
|
printFn("Process output: \n" + logOutput);
|
||||||
// On success, resolve the promise. Otherwise reject with the captured stderr
|
// On success, resolve the promise. Otherwise reject with the captured stderr
|
||||||
// and stdout log output if the output mode was set to `silent`.
|
// and stdout log output if the output mode was set to `silent`.
|
||||||
if (status === 0) {
|
if (status === 0 || options.suppressErrorOnFailingExitCode) {
|
||||||
resolve({ stdout: stdout, stderr: stderr });
|
resolve({ stdout: stdout, stderr: stderr, status: status });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
reject(outputMode === 'silent' ? logOutput : undefined);
|
reject(outputMode === 'silent' ? logOutput : undefined);
|
||||||
|
@ -6041,6 +6053,10 @@ function spawnWithDebugOutput(command, args, options) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/** Convert the provided exitCode and signal to a single status code. */
|
||||||
|
function statusFromExitCodeAndSignal(exitCode, signal) {
|
||||||
|
return exitCode !== null ? exitCode : signal !== null ? signal : -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
|
@ -6060,7 +6076,7 @@ function runNpmPublish(packagePath, distTag, registryUrl) {
|
||||||
if (registryUrl !== undefined) {
|
if (registryUrl !== undefined) {
|
||||||
args.push('--registry', registryUrl);
|
args.push('--registry', registryUrl);
|
||||||
}
|
}
|
||||||
yield spawnWithDebugOutput('npm', args, { cwd: packagePath, mode: 'silent' });
|
yield spawn('npm', args, { cwd: packagePath, mode: 'silent' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -6074,7 +6090,7 @@ function setNpmTagForPackage(packageName, distTag, version, registryUrl) {
|
||||||
if (registryUrl !== undefined) {
|
if (registryUrl !== undefined) {
|
||||||
args.push('--registry', registryUrl);
|
args.push('--registry', registryUrl);
|
||||||
}
|
}
|
||||||
yield spawnWithDebugOutput('npm', args, { mode: 'silent' });
|
yield spawn('npm', args, { mode: 'silent' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -6089,7 +6105,7 @@ function npmIsLoggedIn(registryUrl) {
|
||||||
args.push('--registry', registryUrl);
|
args.push('--registry', registryUrl);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
yield spawnWithDebugOutput('npm', args, { mode: 'silent' });
|
yield spawn('npm', args, { mode: 'silent' });
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -6112,7 +6128,7 @@ function npmLogin(registryUrl) {
|
||||||
}
|
}
|
||||||
// The login command prompts for username, password and other profile information. Hence
|
// The login command prompts for username, password and other profile information. Hence
|
||||||
// the process needs to be interactive (i.e. respecting current TTYs stdin).
|
// the process needs to be interactive (i.e. respecting current TTYs stdin).
|
||||||
yield spawnInteractiveCommand('npm', args);
|
yield spawnInteractive('npm', args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -6129,7 +6145,7 @@ function npmLogout(registryUrl) {
|
||||||
args.splice(1, 0, '--registry', registryUrl);
|
args.splice(1, 0, '--registry', registryUrl);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
yield spawnWithDebugOutput('npm', args, { mode: 'silent' });
|
yield spawn('npm', args, { mode: 'silent' });
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
return npmIsLoggedIn(registryUrl);
|
return npmIsLoggedIn(registryUrl);
|
||||||
|
@ -6251,7 +6267,7 @@ function invokeSetNpmDistCommand(npmDistTag, version) {
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
try {
|
try {
|
||||||
// Note: No progress indicator needed as that is the responsibility of the command.
|
// Note: No progress indicator needed as that is the responsibility of the command.
|
||||||
yield spawnWithDebugOutput('yarn', ['--silent', 'ng-dev', 'release', 'set-dist-tag', npmDistTag, version.format()]);
|
yield spawn('yarn', ['--silent', 'ng-dev', 'release', 'set-dist-tag', npmDistTag, version.format()]);
|
||||||
info(green(` ✓ Set "${npmDistTag}" NPM dist tag for all packages to v${version}.`));
|
info(green(` ✓ Set "${npmDistTag}" NPM dist tag for all packages to v${version}.`));
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
|
@ -6271,7 +6287,7 @@ function invokeReleaseBuildCommand() {
|
||||||
try {
|
try {
|
||||||
// Since we expect JSON to be printed from the `ng-dev release build` command,
|
// Since we expect JSON to be printed from the `ng-dev release build` command,
|
||||||
// we spawn the process in silent mode. We have set up an Ora progress spinner.
|
// we spawn the process in silent mode. We have set up an Ora progress spinner.
|
||||||
const { stdout } = yield spawnWithDebugOutput('yarn', ['--silent', 'ng-dev', 'release', 'build', '--json'], { mode: 'silent' });
|
const { stdout } = yield spawn('yarn', ['--silent', 'ng-dev', 'release', 'build', '--json'], { mode: 'silent' });
|
||||||
spinner.stop();
|
spinner.stop();
|
||||||
info(green(' ✓ Built release output for all packages.'));
|
info(green(' ✓ Built release output for all packages.'));
|
||||||
// The `ng-dev release build` command prints a JSON array to stdout
|
// The `ng-dev release build` command prints a JSON array to stdout
|
||||||
|
@ -6295,7 +6311,7 @@ function invokeYarnInstallCommand(projectDir) {
|
||||||
try {
|
try {
|
||||||
// Note: No progress indicator needed as that is the responsibility of the command.
|
// Note: No progress indicator needed as that is the responsibility of the command.
|
||||||
// TODO: Consider using an Ora spinner instead to ensure minimal console output.
|
// TODO: Consider using an Ora spinner instead to ensure minimal console output.
|
||||||
yield spawnWithDebugOutput('yarn', ['install', '--frozen-lockfile', '--non-interactive'], { cwd: projectDir });
|
yield spawn('yarn', ['install', '--frozen-lockfile', '--non-interactive'], { cwd: projectDir });
|
||||||
info(green(' ✓ Installed project dependencies.'));
|
info(green(' ✓ Installed project dependencies.'));
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
|
@ -7450,7 +7466,7 @@ class ReleaseTool {
|
||||||
try {
|
try {
|
||||||
// Note: We do not rely on `/usr/bin/env` but rather access the `env` binary directly as it
|
// Note: We do not rely on `/usr/bin/env` but rather access the `env` binary directly as it
|
||||||
// should be part of the shell's `$PATH`. This is necessary for compatibility with Windows.
|
// should be part of the shell's `$PATH`. This is necessary for compatibility with Windows.
|
||||||
const pyVersion = yield spawnWithDebugOutput('env', ['python', '--version'], { mode: 'silent' });
|
const pyVersion = yield spawn('env', ['python', '--version'], { mode: 'silent' });
|
||||||
const version = pyVersion.stdout.trim() || pyVersion.stderr.trim();
|
const version = pyVersion.stdout.trim() || pyVersion.stderr.trim();
|
||||||
if (version.startsWith('Python 3.')) {
|
if (version.startsWith('Python 3.')) {
|
||||||
debug(`Local python version: ${version}`);
|
debug(`Local python version: ${version}`);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {types as graphqlTypes} from 'typed-graphqlify';
|
||||||
|
|
||||||
import {error, info} from '../../utils/console';
|
import {error, info} from '../../utils/console';
|
||||||
import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client';
|
import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client';
|
||||||
import {GitClient} from '../../utils/git/git-client';
|
import {GitCommandError} from '../../utils/git/git-client';
|
||||||
import {getPendingPrs} from '../../utils/github';
|
import {getPendingPrs} from '../../utils/github';
|
||||||
import {exec} from '../../utils/shelljs';
|
import {exec} from '../../utils/shelljs';
|
||||||
|
|
||||||
|
@ -95,17 +95,21 @@ export async function discoverNewConflictsForPr(newPrNumber: number, updatedAfte
|
||||||
info(`Checking ${pendingPrs.length} PRs for conflicts after a merge of #${newPrNumber}`);
|
info(`Checking ${pendingPrs.length} PRs for conflicts after a merge of #${newPrNumber}`);
|
||||||
|
|
||||||
// Fetch and checkout the PR being checked.
|
// Fetch and checkout the PR being checked.
|
||||||
exec(`git fetch ${requestedPr.headRef.repository.url} ${requestedPr.headRef.name}`);
|
git.run(['fetch', '-q', requestedPr.headRef.repository.url, requestedPr.headRef.name]);
|
||||||
exec(`git checkout -B ${tempWorkingBranch} FETCH_HEAD`);
|
git.run(['checkout', '-q', '-B', tempWorkingBranch, 'FETCH_HEAD']);
|
||||||
|
|
||||||
// Rebase the PR against the PRs target branch.
|
// Rebase the PR against the PRs target branch.
|
||||||
exec(`git fetch ${requestedPr.baseRef.repository.url} ${requestedPr.baseRef.name}`);
|
git.run(['fetch', '-q', requestedPr.baseRef.repository.url, requestedPr.baseRef.name]);
|
||||||
const result = exec(`git rebase FETCH_HEAD`);
|
try {
|
||||||
if (result.code) {
|
git.run(['rebase', 'FETCH_HEAD'], {stdio: 'ignore'});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof GitCommandError) {
|
||||||
error('The requested PR currently has conflicts');
|
error('The requested PR currently has conflicts');
|
||||||
cleanUpGitState(previousBranchOrRevision);
|
git.checkout(previousBranchOrRevision, true);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
// Start the progress bar
|
// Start the progress bar
|
||||||
progressBar.start(pendingPrs.length, 0);
|
progressBar.start(pendingPrs.length, 0);
|
||||||
|
@ -113,15 +117,19 @@ export async function discoverNewConflictsForPr(newPrNumber: number, updatedAfte
|
||||||
// Check each PR to determine if it can merge cleanly into the repo after the target PR.
|
// Check each PR to determine if it can merge cleanly into the repo after the target PR.
|
||||||
for (const pr of pendingPrs) {
|
for (const pr of pendingPrs) {
|
||||||
// Fetch and checkout the next PR
|
// Fetch and checkout the next PR
|
||||||
exec(`git fetch ${pr.headRef.repository.url} ${pr.headRef.name}`);
|
git.run(['fetch', '-q', pr.headRef.repository.url, pr.headRef.name]);
|
||||||
exec(`git checkout --detach FETCH_HEAD`);
|
git.run(['checkout', '-q', '--detach', 'FETCH_HEAD']);
|
||||||
// Check if the PR cleanly rebases into the repo after the target PR.
|
// Check if the PR cleanly rebases into the repo after the target PR.
|
||||||
const result = exec(`git rebase ${tempWorkingBranch}`);
|
try {
|
||||||
if (result.code !== 0) {
|
git.run(['rebase', tempWorkingBranch], {stdio: 'ignore'});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof GitCommandError) {
|
||||||
conflicts.push(pr);
|
conflicts.push(pr);
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
// Abort any outstanding rebase attempt.
|
// Abort any outstanding rebase attempt.
|
||||||
exec(`git rebase --abort`);
|
git.runGraceful(['rebase', '--abort'], {stdio: 'ignore'});
|
||||||
|
|
||||||
progressBar.increment(1);
|
progressBar.increment(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import * as ora from 'ora';
|
import * as ora from 'ora';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
import {spawnWithDebugOutput} from '../../utils/child-process';
|
import {spawn} from '../../utils/child-process';
|
||||||
import {error, green, info, red} from '../../utils/console';
|
import {error, green, info, red} from '../../utils/console';
|
||||||
import {BuiltPackage} from '../config/index';
|
import {BuiltPackage} from '../config/index';
|
||||||
import {NpmDistTag} from '../versioning';
|
import {NpmDistTag} from '../versioning';
|
||||||
|
@ -40,7 +40,7 @@ import {FatalReleaseActionError} from './actions-error';
|
||||||
export async function invokeSetNpmDistCommand(npmDistTag: NpmDistTag, version: semver.SemVer) {
|
export async function invokeSetNpmDistCommand(npmDistTag: NpmDistTag, version: semver.SemVer) {
|
||||||
try {
|
try {
|
||||||
// Note: No progress indicator needed as that is the responsibility of the command.
|
// Note: No progress indicator needed as that is the responsibility of the command.
|
||||||
await spawnWithDebugOutput(
|
await spawn(
|
||||||
'yarn', ['--silent', 'ng-dev', 'release', 'set-dist-tag', npmDistTag, version.format()]);
|
'yarn', ['--silent', 'ng-dev', 'release', 'set-dist-tag', npmDistTag, version.format()]);
|
||||||
info(green(` ✓ Set "${npmDistTag}" NPM dist tag for all packages to v${version}.`));
|
info(green(` ✓ Set "${npmDistTag}" NPM dist tag for all packages to v${version}.`));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -59,8 +59,8 @@ export async function invokeReleaseBuildCommand(): Promise<BuiltPackage[]> {
|
||||||
try {
|
try {
|
||||||
// Since we expect JSON to be printed from the `ng-dev release build` command,
|
// Since we expect JSON to be printed from the `ng-dev release build` command,
|
||||||
// we spawn the process in silent mode. We have set up an Ora progress spinner.
|
// we spawn the process in silent mode. We have set up an Ora progress spinner.
|
||||||
const {stdout} = await spawnWithDebugOutput(
|
const {stdout} =
|
||||||
'yarn', ['--silent', 'ng-dev', 'release', 'build', '--json'], {mode: 'silent'});
|
await spawn('yarn', ['--silent', 'ng-dev', 'release', 'build', '--json'], {mode: 'silent'});
|
||||||
spinner.stop();
|
spinner.stop();
|
||||||
info(green(' ✓ Built release output for all packages.'));
|
info(green(' ✓ Built release output for all packages.'));
|
||||||
// The `ng-dev release build` command prints a JSON array to stdout
|
// The `ng-dev release build` command prints a JSON array to stdout
|
||||||
|
@ -82,8 +82,7 @@ export async function invokeYarnInstallCommand(projectDir: string): Promise<void
|
||||||
try {
|
try {
|
||||||
// Note: No progress indicator needed as that is the responsibility of the command.
|
// Note: No progress indicator needed as that is the responsibility of the command.
|
||||||
// TODO: Consider using an Ora spinner instead to ensure minimal console output.
|
// TODO: Consider using an Ora spinner instead to ensure minimal console output.
|
||||||
await spawnWithDebugOutput(
|
await spawn('yarn', ['install', '--frozen-lockfile', '--non-interactive'], {cwd: projectDir});
|
||||||
'yarn', ['install', '--frozen-lockfile', '--non-interactive'], {cwd: projectDir});
|
|
||||||
info(green(' ✓ Installed project dependencies.'));
|
info(green(' ✓ Installed project dependencies.'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error(e);
|
error(e);
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {ListChoiceOptions, prompt} from 'inquirer';
|
import {ListChoiceOptions, prompt} from 'inquirer';
|
||||||
|
|
||||||
import {spawnWithDebugOutput} from '../../utils/child-process';
|
import {spawn} from '../../utils/child-process';
|
||||||
import {GithubConfig} from '../../utils/config';
|
import {GithubConfig} from '../../utils/config';
|
||||||
import {debug, error, info, log, promptConfirm, red, yellow} from '../../utils/console';
|
import {debug, error, info, log, promptConfirm, red, yellow} from '../../utils/console';
|
||||||
import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client';
|
import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client';
|
||||||
|
@ -138,8 +138,7 @@ export class ReleaseTool {
|
||||||
try {
|
try {
|
||||||
// Note: We do not rely on `/usr/bin/env` but rather access the `env` binary directly as it
|
// Note: We do not rely on `/usr/bin/env` but rather access the `env` binary directly as it
|
||||||
// should be part of the shell's `$PATH`. This is necessary for compatibility with Windows.
|
// should be part of the shell's `$PATH`. This is necessary for compatibility with Windows.
|
||||||
const pyVersion =
|
const pyVersion = await spawn('env', ['python', '--version'], {mode: 'silent'});
|
||||||
await spawnWithDebugOutput('env', ['python', '--version'], {mode: 'silent'});
|
|
||||||
const version = pyVersion.stdout.trim() || pyVersion.stderr.trim();
|
const version = pyVersion.stdout.trim() || pyVersion.stderr.trim();
|
||||||
if (version.startsWith('Python 3.')) {
|
if (version.startsWith('Python 3.')) {
|
||||||
debug(`Local python version: ${version}`);
|
debug(`Local python version: ${version}`);
|
||||||
|
|
|
@ -7,7 +7,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import {spawnInteractiveCommand, spawnWithDebugOutput} from '../../utils/child-process';
|
|
||||||
|
import {spawn, spawnInteractive} from '../../utils/child-process';
|
||||||
|
|
||||||
import {NpmDistTag} from './npm-registry';
|
import {NpmDistTag} from './npm-registry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,7 +23,7 @@ export async function runNpmPublish(
|
||||||
if (registryUrl !== undefined) {
|
if (registryUrl !== undefined) {
|
||||||
args.push('--registry', registryUrl);
|
args.push('--registry', registryUrl);
|
||||||
}
|
}
|
||||||
await spawnWithDebugOutput('npm', args, {cwd: packagePath, mode: 'silent'});
|
await spawn('npm', args, {cwd: packagePath, mode: 'silent'});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,7 +37,7 @@ export async function setNpmTagForPackage(
|
||||||
if (registryUrl !== undefined) {
|
if (registryUrl !== undefined) {
|
||||||
args.push('--registry', registryUrl);
|
args.push('--registry', registryUrl);
|
||||||
}
|
}
|
||||||
await spawnWithDebugOutput('npm', args, {mode: 'silent'});
|
await spawn('npm', args, {mode: 'silent'});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,7 +51,7 @@ export async function npmIsLoggedIn(registryUrl: string|undefined): Promise<bool
|
||||||
args.push('--registry', registryUrl);
|
args.push('--registry', registryUrl);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await spawnWithDebugOutput('npm', args, {mode: 'silent'});
|
await spawn('npm', args, {mode: 'silent'});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +72,7 @@ export async function npmLogin(registryUrl: string|undefined) {
|
||||||
}
|
}
|
||||||
// The login command prompts for username, password and other profile information. Hence
|
// The login command prompts for username, password and other profile information. Hence
|
||||||
// the process needs to be interactive (i.e. respecting current TTYs stdin).
|
// the process needs to be interactive (i.e. respecting current TTYs stdin).
|
||||||
await spawnInteractiveCommand('npm', args);
|
await spawnInteractive('npm', args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +88,7 @@ export async function npmLogout(registryUrl: string|undefined): Promise<boolean>
|
||||||
args.splice(1, 0, '--registry', registryUrl);
|
args.splice(1, 0, '--registry', registryUrl);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await spawnWithDebugOutput('npm', args, {mode: 'silent'});
|
await spawn('npm', args, {mode: 'silent'});
|
||||||
} finally {
|
} finally {
|
||||||
return npmIsLoggedIn(registryUrl);
|
return npmIsLoggedIn(registryUrl);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,21 +6,34 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {spawn, SpawnOptions} from 'child_process';
|
import {spawn as _spawn, SpawnOptions as _SpawnOptions, spawnSync as _spawnSync, SpawnSyncOptions as _SpawnSyncOptions} from 'child_process';
|
||||||
import {debug, error} from './console';
|
import {debug, error} from './console';
|
||||||
|
|
||||||
/** Interface describing the options for spawning a process. */
|
|
||||||
export interface SpawnedProcessOptions extends Omit<SpawnOptions, 'stdio'> {
|
export interface SpawnSyncOptions extends Omit<_SpawnSyncOptions, 'shell'|'stdio'> {
|
||||||
/** Console output mode. Defaults to "enabled". */
|
/** Whether to prevent exit codes being treated as failures. */
|
||||||
mode?: 'enabled'|'silent'|'on-error';
|
suppressErrorOnFailingExitCode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Interface describing the options for spawning a process. */
|
||||||
|
export interface SpawnOptions extends Omit<_SpawnOptions, 'shell'|'stdio'> {
|
||||||
|
/** Console output mode. Defaults to "enabled". */
|
||||||
|
mode?: 'enabled'|'silent'|'on-error';
|
||||||
|
/** Whether to prevent exit codes being treated as failures. */
|
||||||
|
suppressErrorOnFailingExitCode?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Interface describing the options for spawning an interactive process. */
|
||||||
|
export type SpawnInteractiveCommandOptions = Omit<_SpawnOptions, 'shell'|'stdio'>;
|
||||||
|
|
||||||
/** Interface describing the result of a spawned process. */
|
/** Interface describing the result of a spawned process. */
|
||||||
export interface SpawnedProcessResult {
|
export interface SpawnResult {
|
||||||
/** Captured stdout in string format. */
|
/** Captured stdout in string format. */
|
||||||
stdout: string;
|
stdout: string;
|
||||||
/** Captured stderr in string format. */
|
/** Captured stderr in string format. */
|
||||||
stderr: string;
|
stderr: string;
|
||||||
|
/** The exit code or signal of the process. */
|
||||||
|
status: number|NodeJS.Signals;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,12 +42,12 @@ export interface SpawnedProcessResult {
|
||||||
*
|
*
|
||||||
* @returns a Promise resolving on success, and rejecting on command failure with the status code.
|
* @returns a Promise resolving on success, and rejecting on command failure with the status code.
|
||||||
*/
|
*/
|
||||||
export function spawnInteractiveCommand(
|
export function spawnInteractive(
|
||||||
command: string, args: string[], options: Omit<SpawnOptions, 'stdio'> = {}) {
|
command: string, args: string[], options: SpawnInteractiveCommandOptions = {}) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const commandText = `${command} ${args.join(' ')}`;
|
const commandText = `${command} ${args.join(' ')}`;
|
||||||
debug(`Executing command: ${commandText}`);
|
debug(`Executing command: ${commandText}`);
|
||||||
const childProcess = spawn(command, args, {...options, shell: true, stdio: 'inherit'});
|
const childProcess = _spawn(command, args, {...options, shell: true, stdio: 'inherit'});
|
||||||
childProcess.on('exit', status => status === 0 ? resolve() : reject(status));
|
childProcess.on('exit', status => status === 0 ? resolve() : reject(status));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -45,18 +58,17 @@ export function spawnInteractiveCommand(
|
||||||
* output mode, stdout/stderr output is also printed to the console, or only on error.
|
* output mode, stdout/stderr output is also printed to the console, or only on error.
|
||||||
*
|
*
|
||||||
* @returns a Promise resolving with captured stdout and stderr on success. The promise
|
* @returns a Promise resolving with captured stdout and stderr on success. The promise
|
||||||
* rejects on command failure.
|
* rejects on command failure
|
||||||
*/
|
*/
|
||||||
export function spawnWithDebugOutput(
|
export function spawn(
|
||||||
command: string, args: string[],
|
command: string, args: string[], options: SpawnOptions = {}): Promise<SpawnResult> {
|
||||||
options: SpawnedProcessOptions = {}): Promise<SpawnedProcessResult> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const commandText = `${command} ${args.join(' ')}`;
|
const commandText = `${command} ${args.join(' ')}`;
|
||||||
const outputMode = options.mode;
|
const outputMode = options.mode;
|
||||||
|
|
||||||
debug(`Executing command: ${commandText}`);
|
debug(`Executing command: ${commandText}`);
|
||||||
|
|
||||||
const childProcess = spawn(command, args, {...options, shell: true, stdio: 'pipe'});
|
const childProcess = _spawn(command, args, {...options, shell: true, stdio: 'pipe'});
|
||||||
let logOutput = '';
|
let logOutput = '';
|
||||||
let stdout = '';
|
let stdout = '';
|
||||||
let stderr = '';
|
let stderr = '';
|
||||||
|
@ -83,20 +95,51 @@ export function spawnWithDebugOutput(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
childProcess.on('exit', (status, signal) => {
|
childProcess.on('exit', (exitCode, signal) => {
|
||||||
const exitDescription = status !== null ? `exit code "${status}"` : `signal "${signal}"`;
|
const exitDescription = exitCode !== null ? `exit code "${exitCode}"` : `signal "${signal}"`;
|
||||||
const printFn = outputMode === 'on-error' ? error : debug;
|
const printFn = outputMode === 'on-error' ? error : debug;
|
||||||
|
const status = statusFromExitCodeAndSignal(exitCode, signal);
|
||||||
|
|
||||||
printFn(`Command "${commandText}" completed with ${exitDescription}.`);
|
printFn(`Command "${commandText}" completed with ${exitDescription}.`);
|
||||||
printFn(`Process output: \n${logOutput}`);
|
printFn(`Process output: \n${logOutput}`);
|
||||||
|
|
||||||
// On success, resolve the promise. Otherwise reject with the captured stderr
|
// On success, resolve the promise. Otherwise reject with the captured stderr
|
||||||
// and stdout log output if the output mode was set to `silent`.
|
// and stdout log output if the output mode was set to `silent`.
|
||||||
if (status === 0) {
|
if (status === 0 || options.suppressErrorOnFailingExitCode) {
|
||||||
resolve({stdout, stderr});
|
resolve({stdout, stderr, status});
|
||||||
} else {
|
} else {
|
||||||
reject(outputMode === 'silent' ? logOutput : undefined);
|
reject(outputMode === 'silent' ? logOutput : undefined);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
import {spawnSync, SpawnSyncOptions, SpawnSyncReturns} from 'child_process';
|
import {spawnSync, SpawnSyncOptions, SpawnSyncReturns} from 'child_process';
|
||||||
import {Options as SemVerOptions, parse, SemVer} from 'semver';
|
import {Options as SemVerOptions, parse, SemVer} from 'semver';
|
||||||
|
|
||||||
import {spawnWithDebugOutput} from '../child-process';
|
|
||||||
import {getConfig, GithubConfig, NgDevConfig} from '../config';
|
import {getConfig, GithubConfig, NgDevConfig} from '../config';
|
||||||
import {debug, info} from '../console';
|
import {debug, info} from '../console';
|
||||||
import {DryRunError, isDryRun} from '../dry-run';
|
import {DryRunError, isDryRun} from '../dry-run';
|
||||||
|
@ -24,6 +23,7 @@ export class GitCommandError extends Error {
|
||||||
// accidentally leak the Github token that might be used in a command,
|
// accidentally leak the Github token that might be used in a command,
|
||||||
// we sanitize the command that will be part of the error message.
|
// we sanitize the command that will be part of the error message.
|
||||||
super(`Command failed: git ${client.sanitizeConsoleOutput(args.join(' '))}`);
|
super(`Command failed: git ${client.sanitizeConsoleOutput(args.join(' '))}`);
|
||||||
|
Object.setPrototypeOf(this, GitCommandError.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue