refactor(dev-infra): remove usages and dependency on shelljs (#42911)

Remove usages of shelljs and instead use spawn/spawnSync.

PR Close #42911
This commit is contained in:
Joey Perrott 2021-05-27 12:04:39 -07:00 committed by Dylan Hunn
parent d3b0d1e3fb
commit 992dc93ea3
21 changed files with 208 additions and 228 deletions

View File

@ -10,10 +10,8 @@ ts_library(
"//packages/benchpress", "//packages/benchpress",
"@npm//@types/node", "@npm//@types/node",
"@npm//@types/selenium-webdriver", "@npm//@types/selenium-webdriver",
"@npm//@types/shelljs",
"@npm//node-uuid", "@npm//node-uuid",
"@npm//protractor", "@npm//protractor",
"@npm//selenium-webdriver", "@npm//selenium-webdriver",
"@npm//shelljs",
], ],
) )

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {mkdir} from 'shelljs'; import {mkdirSync} from 'fs';
export {verifyNoBrowserErrors} from './e2e_util'; export {verifyNoBrowserErrors} from './e2e_util';
@ -61,7 +61,9 @@ function createBenchpressRunner(): Runner {
runId = process.env.GIT_SHA + ' ' + runId; runId = process.env.GIT_SHA + ' ' + runId;
} }
const resultsFolder = './dist/benchmark_results'; const resultsFolder = './dist/benchmark_results';
mkdir('-p', resultsFolder); mkdirSync(resultsFolder, {
recursive: true,
});
const providers: StaticProvider[] = [ const providers: StaticProvider[] = [
SeleniumWebDriverAdapter.PROTRACTOR_PROVIDERS, SeleniumWebDriverAdapter.PROTRACTOR_PROVIDERS,
{provide: Options.FORCE_GC, useValue: globalOptions.forceGc}, {provide: Options.FORCE_GC, useValue: globalOptions.forceGc},

View File

@ -145,6 +145,9 @@ 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;
// Set the prototype explicitly because in ES5, the prototype is accidentally lost due to
// a limitation in down-leveling.
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
Object.setPrototypeOf(_this, GitCommandError.prototype); Object.setPrototypeOf(_this, GitCommandError.prototype);
return _this; return _this;
} }

View File

@ -13,12 +13,10 @@ ts_library(
"@npm//@types/git-raw-commits", "@npm//@types/git-raw-commits",
"@npm//@types/inquirer", "@npm//@types/inquirer",
"@npm//@types/node", "@npm//@types/node",
"@npm//@types/shelljs",
"@npm//@types/yargs", "@npm//@types/yargs",
"@npm//conventional-commits-parser", "@npm//conventional-commits-parser",
"@npm//git-raw-commits", "@npm//git-raw-commits",
"@npm//inquirer", "@npm//inquirer",
"@npm//shelljs",
"@npm//yargs", "@npm//yargs",
], ],
) )

View File

@ -10,11 +10,9 @@ ts_library(
"//dev-infra/utils", "//dev-infra/utils",
"@npm//@types/cli-progress", "@npm//@types/cli-progress",
"@npm//@types/node", "@npm//@types/node",
"@npm//@types/shelljs",
"@npm//@types/yargs", "@npm//@types/yargs",
"@npm//cli-progress", "@npm//cli-progress",
"@npm//multimatch", "@npm//multimatch",
"@npm//shelljs",
"@npm//yargs", "@npm//yargs",
], ],
) )

View File

@ -10,7 +10,8 @@ import {GitClient} from '../../utils/git/git-client';
import {FormatConfig} from '../config'; import {FormatConfig} from '../config';
// A callback to determine if the formatter run found a failure in formatting. // A callback to determine if the formatter run found a failure in formatting.
export type CallbackFunc = (file: string, code: number, stdout: string, stderr: string) => boolean; export type CallbackFunc =
(file: string, code: number|NodeJS.Signals, stdout: string, stderr: string) => boolean;
// The actions a formatter can take. // The actions a formatter can take.
export type FormatterAction = 'check'|'format'; export type FormatterAction = 'check'|'format';

View File

@ -26,14 +26,14 @@ export class Buildifier extends Formatter {
check: { check: {
commandFlags: `${BAZEL_WARNING_FLAG} --lint=warn --mode=check --format=json`, commandFlags: `${BAZEL_WARNING_FLAG} --lint=warn --mode=check --format=json`,
callback: callback:
(_: string, code: number, stdout: string) => { (_: string, code: number|NodeJS.Signals, stdout: string) => {
return code !== 0 || !(JSON.parse(stdout) as {success: string}).success; return code !== 0 || !(JSON.parse(stdout) as {success: string}).success;
}, },
}, },
format: { format: {
commandFlags: `${BAZEL_WARNING_FLAG} --lint=fix --mode=fix`, commandFlags: `${BAZEL_WARNING_FLAG} --lint=fix --mode=fix`,
callback: callback:
(file: string, code: number, _: string, stderr: string) => { (file: string, code: number|NodeJS.Signals, _: string, stderr: string) => {
if (code !== 0) { if (code !== 0) {
error(`Error running buildifier on: ${file}`); error(`Error running buildifier on: ${file}`);
error(stderr); error(stderr);

View File

@ -26,14 +26,14 @@ export class ClangFormat extends Formatter {
check: { check: {
commandFlags: `--Werror -n -style=file`, commandFlags: `--Werror -n -style=file`,
callback: callback:
(_: string, code: number) => { (_: string, code: number|NodeJS.Signals) => {
return code !== 0; return code !== 0;
}, },
}, },
format: { format: {
commandFlags: `-i -style=file`, commandFlags: `-i -style=file`,
callback: callback:
(file: string, code: number, _: string, stderr: string) => { (file: string, code: number|NodeJS.Signals, _: string, stderr: string) => {
if (code !== 0) { if (code !== 0) {
error(`Error running clang-format on: ${file}`); error(`Error running clang-format on: ${file}`);
error(stderr); error(stderr);

View File

@ -7,8 +7,8 @@
*/ */
import {join} from 'path'; import {join} from 'path';
import {exec} from 'shelljs';
import {spawnSync} from '../../utils/child-process';
import {error} from '../../utils/console'; import {error} from '../../utils/console';
import {Formatter} from './base-formatter'; import {Formatter} from './base-formatter';
@ -27,21 +27,22 @@ export class Prettier extends Formatter {
* The configuration path of the prettier config, obtained during construction to prevent needing * The configuration path of the prettier config, obtained during construction to prevent needing
* to discover it repeatedly for each execution. * to discover it repeatedly for each execution.
*/ */
private configPath = private configPath = this.config['prettier'] ?
this.config['prettier'] ? exec(`${this.binaryFilePath} --find-config-path .`).trim() : ''; spawnSync(this.binaryFilePath, ['--find-config-path', '.']).stdout.trim() :
'';
override actions = { override actions = {
check: { check: {
commandFlags: `--config ${this.configPath} --check`, commandFlags: `--config ${this.configPath} --check`,
callback: callback:
(_: string, code: number, stdout: string) => { (_: string, code: number|NodeJS.Signals, stdout: string) => {
return code !== 0; return code !== 0;
}, },
}, },
format: { format: {
commandFlags: `--config ${this.configPath} --write`, commandFlags: `--config ${this.configPath} --write`,
callback: callback:
(file: string, code: number, _: string, stderr: string) => { (file: string, code: number|NodeJS.Signals, _: string, stderr: string) => {
if (code !== 0) { if (code !== 0) {
error(`Error running prettier on: ${file}`); error(`Error running prettier on: ${file}`);
error(stderr); error(stderr);

View File

@ -9,8 +9,8 @@
import {Bar} from 'cli-progress'; import {Bar} from 'cli-progress';
import * as multimatch from 'multimatch'; import * as multimatch from 'multimatch';
import {cpus} from 'os'; import {cpus} from 'os';
import {exec} from 'shelljs';
import {spawn, SpawnResult} from '../utils/child-process';
import {info} from '../utils/console'; import {info} from '../utils/console';
import {Formatter, FormatterAction, getActiveFormatters} from './formatters/index'; import {Formatter, FormatterAction, getActiveFormatters} from './formatters/index';
@ -86,12 +86,11 @@ export function runFormatterInParallel(allFiles: string[], action: FormatterActi
// Get the file and formatter for the next command. // Get the file and formatter for the next command.
const {file, formatter} = nextCommand; const {file, formatter} = nextCommand;
exec( const [spawnCmd, ...spawnArgs] = [...formatter.commandFor(action).split(' '), file];
`${formatter.commandFor(action)} ${file}`, spawn(spawnCmd, spawnArgs, {suppressErrorOnFailingExitCode: true, mode: 'silent'})
{async: true, silent: true}, .then(({stdout, stderr, status}: SpawnResult) => {
(code, stdout, stderr) => {
// Run the provided callback function. // Run the provided callback function.
const failed = formatter.callbackFor(action)(file, code, stdout, stderr); const failed = formatter.callbackFor(action)(file, status, stdout, stderr);
if (failed) { if (failed) {
failures.push({filePath: file, message: stderr}); failures.push({filePath: file, message: stderr});
} }
@ -110,8 +109,7 @@ export function runFormatterInParallel(allFiles: string[], action: FormatterActi
progressBar.stop(); progressBar.stop();
resolve(failures); resolve(failures);
} }
}, });
);
// Mark the thread as in use as the command execution has been started. // Mark the thread as in use as the command execution has been started.
threads[thread] = true; threads[thread] = true;
} }

View File

@ -7,13 +7,13 @@
*/ */
import {green} from 'chalk'; import {green} from 'chalk';
import {lstatSync, stat, Stats} from 'fs'; import {lstatSync} from 'fs';
import {isAbsolute, join, resolve} from 'path'; import {resolve} from 'path';
import {Arguments, Argv, CommandModule} from 'yargs'; import {Arguments, Argv, CommandModule} from 'yargs';
import {buildReleaseOutput} from '../../release/build/index'; import {buildReleaseOutput} from '../../release/build/index';
import {spawn} from '../../utils/child-process';
import {error, info, red} from '../../utils/console'; import {error, info, red} from '../../utils/console';
import {exec} from '../../utils/shelljs';
/** Command line options. */ /** Command line options. */
@ -52,8 +52,8 @@ async function handler({projectRoot}: Arguments<BuildAndLinkOptions>) {
info(green(` ✓ Built release output.`)); info(green(` ✓ Built release output.`));
for (const {outputPath, name} of releaseOutputs) { for (const {outputPath, name} of releaseOutputs) {
exec(`yarn link --cwd ${outputPath}`); await spawn('yarn', ['link', '--cwd', outputPath]);
exec(`yarn link --cwd ${projectRoot} ${name}`); await spawn('yarn', ['link', '--cwd', projectRoot, name]);
} }
info(green(` ✓ Linked release packages in provided project.`)); info(green(` ✓ Linked release packages in provided project.`));

View File

@ -22,7 +22,6 @@ var conventionalCommitsParser = require('conventional-commits-parser');
var gitCommits_ = require('git-raw-commits'); var gitCommits_ = require('git-raw-commits');
var cliProgress = require('cli-progress'); var cliProgress = require('cli-progress');
var os = require('os'); var os = require('os');
var shelljs = require('shelljs');
var minimatch = require('minimatch'); var minimatch = require('minimatch');
var ejs = require('ejs'); var ejs = require('ejs');
var ora = require('ora'); var ora = require('ora');
@ -317,6 +316,9 @@ 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;
// Set the prototype explicitly because in ES5, the prototype is accidentally lost due to
// a limitation in down-leveling.
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
Object.setPrototypeOf(_this, GitCommandError.prototype); Object.setPrototypeOf(_this, GitCommandError.prototype);
return _this; return _this;
} }
@ -2440,6 +2442,113 @@ function buildCommitMessageParser(localYargs) {
.command(ValidateRangeModule); .command(ValidateRangeModule);
} }
/**
* @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
*/
/**
* 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.
*/
function spawnInteractive(command, args, options) {
if (options === void 0) { options = {}; }
return new Promise(function (resolve, reject) {
var commandText = command + " " + args.join(' ');
debug("Executing command: " + commandText);
var childProcess = child_process.spawn(command, args, tslib.__assign(tslib.__assign({}, options), { shell: true, stdio: 'inherit' }));
childProcess.on('exit', function (status) { return status === 0 ? resolve() : reject(status); });
});
}
/**
* 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.
*
* @returns a Promise resolving with captured stdout and stderr on success. The promise
* rejects on command failure.
*/
function spawn(command, args, options) {
if (options === void 0) { options = {}; }
return new Promise(function (resolve, reject) {
var commandText = command + " " + args.join(' ');
var outputMode = options.mode;
debug("Executing command: " + commandText);
var childProcess = child_process.spawn(command, args, tslib.__assign(tslib.__assign({}, options), { shell: true, stdio: 'pipe' }));
var logOutput = '';
var stdout = '';
var stderr = '';
// 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', function (message) {
stderr += message;
logOutput += message;
// If console output is enabled, print the message directly to the stderr. Note that
// we intentionally print all output to stderr as stdout should not be polluted.
if (outputMode === undefined || outputMode === 'enabled') {
process.stderr.write(message);
}
});
childProcess.stdout.on('data', function (message) {
stdout += message;
logOutput += message;
// If console output is enabled, print the message directly to the stderr. Note that
// we intentionally print all output to stderr as stdout should not be polluted.
if (outputMode === undefined || outputMode === 'enabled') {
process.stderr.write(message);
}
});
childProcess.on('exit', function (exitCode, signal) {
var exitDescription = exitCode !== null ? "exit code \"" + exitCode + "\"" : "signal \"" + signal + "\"";
var printFn = outputMode === 'on-error' ? error : debug;
var status = statusFromExitCodeAndSignal(exitCode, signal);
printFn("Command \"" + commandText + "\" completed with " + exitDescription + ".");
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`.
if (status === 0 || options.suppressErrorOnFailingExitCode) {
resolve({ stdout: stdout, stderr: stderr, status: status });
}
else {
reject(outputMode === 'silent' ? logOutput : undefined);
}
});
});
}
/**
* Spawns a given command with the specified arguments inside a shell synchronously.
*
* @returns The command's stdout and stderr.
*/
function spawnSync(command, args, options) {
if (options === void 0) { options = {}; }
var commandText = command + " " + args.join(' ');
debug("Executing command: " + commandText);
var _a = child_process.spawnSync(command, args, tslib.__assign(tslib.__assign({}, options), { encoding: 'utf8', shell: true, stdio: 'pipe' })), exitCode = _a.status, signal = _a.signal, stdout = _a.stdout, stderr = _a.stderr;
/** The status of the spawn result. */
var status = statusFromExitCodeAndSignal(exitCode, signal);
if (status === 0 || options.suppressErrorOnFailingExitCode) {
return { status: status, stdout: stdout, stderr: stderr };
}
throw new Error(stderr);
}
/**
* Convert the provided exitCode and signal to a single status code.
*
* During `exit` node provides either a `code` or `signal`, one of which is guaranteed to be
* non-null.
*
* For more details see: https://nodejs.org/api/child_process.html#child_process_event_exit
*/
function statusFromExitCodeAndSignal(exitCode, signal) {
var _a;
return (_a = exitCode !== null && exitCode !== void 0 ? exitCode : signal) !== null && _a !== void 0 ? _a : -1;
}
/** /**
* @license * @license
* Copyright Google LLC All Rights Reserved. * Copyright Google LLC All Rights Reserved.
@ -2644,7 +2753,9 @@ class Prettier extends Formatter {
* The configuration path of the prettier config, obtained during construction to prevent needing * The configuration path of the prettier config, obtained during construction to prevent needing
* to discover it repeatedly for each execution. * to discover it repeatedly for each execution.
*/ */
this.configPath = this.config['prettier'] ? shelljs.exec(`${this.binaryFilePath} --find-config-path .`).trim() : ''; this.configPath = this.config['prettier'] ?
spawnSync(this.binaryFilePath, ['--find-config-path', '.']).stdout.trim() :
'';
this.actions = { this.actions = {
check: { check: {
commandFlags: `--config ${this.configPath} --check`, commandFlags: `--config ${this.configPath} --check`,
@ -2747,9 +2858,11 @@ function runFormatterInParallel(allFiles, action) {
} }
// Get the file and formatter for the next command. // Get the file and formatter for the next command.
const { file, formatter } = nextCommand; const { file, formatter } = nextCommand;
shelljs.exec(`${formatter.commandFor(action)} ${file}`, { async: true, silent: true }, (code, stdout, stderr) => { const [spawnCmd, ...spawnArgs] = [...formatter.commandFor(action).split(' '), file];
spawn(spawnCmd, spawnArgs, { suppressErrorOnFailingExitCode: true, mode: 'silent' })
.then(({ stdout, stderr, status }) => {
// Run the provided callback function. // Run the provided callback function.
const failed = formatter.callbackFor(action)(file, code, stdout, stderr); const failed = formatter.callbackFor(action)(file, status, stdout, stderr);
if (failed) { if (failed) {
failures.push({ filePath: file, message: stderr }); failures.push({ filePath: file, message: stderr });
} }
@ -3376,21 +3489,6 @@ const CheckoutCommandModule = {
describe: 'Checkout a PR from the upstream repo', describe: 'Checkout a PR from the upstream repo',
}; };
/**
* @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
*/
/**
* Runs an given command as child process. By default, child process
* output will not be printed.
*/
function exec(cmd, opts) {
return shelljs.exec(cmd, tslib.__assign(tslib.__assign({ silent: true }, opts), { async: false }));
}
/** /**
* @license * @license
* Copyright Google LLC All Rights Reserved. * Copyright Google LLC All Rights Reserved.
@ -3494,7 +3592,9 @@ function discoverNewConflictsForPr(newPrNumber, updatedAfter) {
if (err instanceof GitCommandError) { if (err instanceof GitCommandError) {
conflicts.push(pr); conflicts.push(pr);
} }
throw err; else {
throw err;
}
} }
// Abort any outstanding rebase attempt. // Abort any outstanding rebase attempt.
git.runGraceful(['rebase', '--abort'], { stdio: 'ignore' }); git.runGraceful(['rebase', '--abort'], { stdio: 'ignore' });
@ -3504,7 +3604,7 @@ function discoverNewConflictsForPr(newPrNumber, updatedAfter) {
progressBar.stop(); progressBar.stop();
info(); info();
info(`Result:`); info(`Result:`);
cleanUpGitState(previousBranchOrRevision); git.checkout(previousBranchOrRevision, true);
// If no conflicts are found, exit successfully. // If no conflicts are found, exit successfully.
if (conflicts.length === 0) { if (conflicts.length === 0) {
info(`No new conflicting PRs found after #${newPrNumber} merging`); info(`No new conflicting PRs found after #${newPrNumber} merging`);
@ -3519,17 +3619,6 @@ function discoverNewConflictsForPr(newPrNumber, updatedAfter) {
process.exit(1); process.exit(1);
}); });
} }
/** Reset git back to the provided branch or revision. */
function cleanUpGitState(previousBranchOrRevision) {
// Ensure that any outstanding rebases are aborted.
exec(`git rebase --abort`);
// Ensure that any changes in the current repo state are cleared.
exec(`git reset --hard`);
// Checkout the original branch from before the run began.
exec(`git checkout ${previousBranchOrRevision}`);
// Delete the generated branch.
exec(`git branch -D ${tempWorkingBranch}`);
}
/** /**
* @license * @license
@ -5976,88 +6065,6 @@ const ReleaseNotesCommandModule = {
describe: 'Generate release notes', describe: 'Generate release notes',
}; };
/**
* @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
*/
/**
* 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.
*/
function spawnInteractive(command, args, options) {
if (options === void 0) { options = {}; }
return new Promise(function (resolve, reject) {
var commandText = command + " " + args.join(' ');
debug("Executing command: " + commandText);
var childProcess = child_process.spawn(command, args, tslib.__assign(tslib.__assign({}, options), { shell: true, stdio: 'inherit' }));
childProcess.on('exit', function (status) { return status === 0 ? resolve() : reject(status); });
});
}
/**
* 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.
*
* @returns a Promise resolving with captured stdout and stderr on success. The promise
* rejects on command failure
*/
function spawn(command, args, options) {
if (options === void 0) { options = {}; }
return new Promise(function (resolve, reject) {
var commandText = command + " " + args.join(' ');
var outputMode = options.mode;
debug("Executing command: " + commandText);
var childProcess = child_process.spawn(command, args, tslib.__assign(tslib.__assign({}, options), { shell: true, stdio: 'pipe' }));
var logOutput = '';
var stdout = '';
var stderr = '';
// 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', function (message) {
stderr += message;
logOutput += message;
// If console output is enabled, print the message directly to the stderr. Note that
// we intentionally print all output to stderr as stdout should not be polluted.
if (outputMode === undefined || outputMode === 'enabled') {
process.stderr.write(message);
}
});
childProcess.stdout.on('data', function (message) {
stdout += message;
logOutput += message;
// If console output is enabled, print the message directly to the stderr. Note that
// we intentionally print all output to stderr as stdout should not be polluted.
if (outputMode === undefined || outputMode === 'enabled') {
process.stderr.write(message);
}
});
childProcess.on('exit', function (exitCode, signal) {
var exitDescription = exitCode !== null ? "exit code \"" + exitCode + "\"" : "signal \"" + signal + "\"";
var printFn = outputMode === 'on-error' ? error : debug;
var status = statusFromExitCodeAndSignal(exitCode, signal);
printFn("Command \"" + commandText + "\" completed with " + exitDescription + ".");
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`.
if (status === 0 || options.suppressErrorOnFailingExitCode) {
resolve({ stdout: stdout, stderr: stderr, status: status });
}
else {
reject(outputMode === 'silent' ? logOutput : undefined);
}
});
});
}
/** 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
* Copyright Google LLC All Rights Reserved. * Copyright Google LLC All Rights Reserved.
@ -7655,20 +7662,17 @@ const ReleaseSetDistTagCommand = {
*/ */
function buildEnvStamp(mode) { function buildEnvStamp(mode) {
console.info(`BUILD_SCM_BRANCH ${getCurrentBranch()}`); console.info(`BUILD_SCM_BRANCH ${getCurrentBranch()}`);
console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentSha()}`); console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentBranchOrRevision()}`);
console.info(`BUILD_SCM_HASH ${getCurrentSha()}`); console.info(`BUILD_SCM_HASH ${getCurrentBranchOrRevision()}`);
console.info(`BUILD_SCM_LOCAL_CHANGES ${hasLocalChanges()}`); console.info(`BUILD_SCM_LOCAL_CHANGES ${hasLocalChanges()}`);
console.info(`BUILD_SCM_USER ${getCurrentGitUser()}`); console.info(`BUILD_SCM_USER ${getCurrentGitUser()}`);
console.info(`BUILD_SCM_VERSION ${getSCMVersion(mode)}`); console.info(`BUILD_SCM_VERSION ${getSCMVersion(mode)}`);
process.exit(0); process.exit();
}
/** Run the exec command and return the stdout as a trimmed string. */
function exec$1(cmd) {
return exec(cmd).trim();
} }
/** Whether the repo has local changes. */ /** Whether the repo has local changes. */
function hasLocalChanges() { function hasLocalChanges() {
return !!exec$1(`git status --untracked-files=no --porcelain`); const git = GitClient.get();
return git.hasUncommittedChanges();
} }
/** /**
* Get the version for generated packages. * Get the version for generated packages.
@ -7677,30 +7681,34 @@ function hasLocalChanges() {
* In release mode, the version is based on the base package.json version. * In release mode, the version is based on the base package.json version.
*/ */
function getSCMVersion(mode) { function getSCMVersion(mode) {
const git = GitClient.get();
if (mode === 'release') { if (mode === 'release') {
const git = GitClient.get();
const packageJsonPath = path.join(git.baseDir, 'package.json'); const packageJsonPath = path.join(git.baseDir, 'package.json');
const { version } = require(packageJsonPath); const { version } = require(packageJsonPath);
return version; return version;
} }
if (mode === 'snapshot') { if (mode === 'snapshot') {
const version = exec$1(`git describe --match [0-9]*.[0-9]*.[0-9]* --abbrev=7 --tags HEAD`); const version = git.run(['describe', '--match', '[0-9]*.[0-9]*.[0-9]*', '--abbrev=7', '--tags', 'HEAD'])
.stdout.trim();
return `${version.replace(/-([0-9]+)-g/, '+$1.sha-')}${(hasLocalChanges() ? '.with-local-changes' : '')}`; return `${version.replace(/-([0-9]+)-g/, '+$1.sha-')}${(hasLocalChanges() ? '.with-local-changes' : '')}`;
} }
return '0.0.0'; return '0.0.0';
} }
/** Get the current SHA of HEAD. */ /** Get the current branch or revision of HEAD. */
function getCurrentSha() { function getCurrentBranchOrRevision() {
return exec$1(`git rev-parse HEAD`); const git = GitClient.get();
return git.getCurrentBranchOrRevision();
} }
/** Get the currently checked out branch. */ /** Get the currently checked out branch. */
function getCurrentBranch() { function getCurrentBranch() {
return exec$1(`git symbolic-ref --short HEAD`); const git = GitClient.get();
return git.run(['symbolic-ref', '--short', 'HEAD']).stdout.trim();
} }
/** Get the current git user based on the git config. */ /** Get the current git user based on the git config. */
function getCurrentGitUser() { function getCurrentGitUser() {
const userName = exec$1(`git config user.name`); const git = GitClient.get();
const userEmail = exec$1(`git config user.email`); let userName = git.runGraceful(['config', 'user.name']).stdout.trim() || 'Unknown User';
let userEmail = git.runGraceful(['config', 'user.email']).stdout.trim() || 'unknown_email';
return `${userName} <${userEmail}>`; return `${userName} <${userEmail}>`;
} }
@ -8187,8 +8195,8 @@ function handler$e({ projectRoot }) {
} }
info(chalk.green(` ✓ Built release output.`)); info(chalk.green(` ✓ Built release output.`));
for (const { outputPath, name } of releaseOutputs) { for (const { outputPath, name } of releaseOutputs) {
exec(`yarn link --cwd ${outputPath}`); yield spawn('yarn', ['link', '--cwd', outputPath]);
exec(`yarn link --cwd ${projectRoot} ${name}`); yield spawn('yarn', ['link', '--cwd', projectRoot, name]);
} }
info(chalk.green(` ✓ Linked release packages in provided project.`)); info(chalk.green(` ✓ Linked release packages in provided project.`));
}); });

View File

@ -11,7 +11,6 @@ ts_library(
"//dev-infra/utils", "//dev-infra/utils",
"@npm//@types/cli-progress", "@npm//@types/cli-progress",
"@npm//@types/node", "@npm//@types/node",
"@npm//@types/shelljs",
"@npm//@types/yargs", "@npm//@types/yargs",
"@npm//typed-graphqlify", "@npm//typed-graphqlify",
], ],

View File

@ -13,7 +13,6 @@ import {error, info} from '../../utils/console';
import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client'; import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client';
import {GitCommandError} 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';
/* Graphql schema for the response body for each pending PR. */ /* Graphql schema for the response body for each pending PR. */
@ -125,8 +124,9 @@ export async function discoverNewConflictsForPr(newPrNumber: number, updatedAfte
} catch (err) { } catch (err) {
if (err instanceof GitCommandError) { if (err instanceof GitCommandError) {
conflicts.push(pr); conflicts.push(pr);
} else {
throw err;
} }
throw err;
} }
// Abort any outstanding rebase attempt. // Abort any outstanding rebase attempt.
git.runGraceful(['rebase', '--abort'], {stdio: 'ignore'}); git.runGraceful(['rebase', '--abort'], {stdio: 'ignore'});
@ -138,7 +138,7 @@ export async function discoverNewConflictsForPr(newPrNumber: number, updatedAfte
info(); info();
info(`Result:`); info(`Result:`);
cleanUpGitState(previousBranchOrRevision); git.checkout(previousBranchOrRevision, true);
// If no conflicts are found, exit successfully. // If no conflicts are found, exit successfully.
if (conflicts.length === 0) { if (conflicts.length === 0) {
@ -154,15 +154,3 @@ export async function discoverNewConflictsForPr(newPrNumber: number, updatedAfte
error.groupEnd(); error.groupEnd();
process.exit(1); process.exit(1);
} }
/** Reset git back to the provided branch or revision. */
export function cleanUpGitState(previousBranchOrRevision: string) {
// Ensure that any outstanding rebases are aborted.
exec(`git rebase --abort`);
// Ensure that any changes in the current repo state are cleared.
exec(`git reset --hard`);
// Checkout the original branch from before the run began.
exec(`git checkout ${previousBranchOrRevision}`);
// Delete the generated branch.
exec(`git branch -D ${tempWorkingBranch}`);
}

View File

@ -17,11 +17,9 @@ ts_library(
"//dev-infra/utils", "//dev-infra/utils",
"@npm//@types/minimatch", "@npm//@types/minimatch",
"@npm//@types/node", "@npm//@types/node",
"@npm//@types/shelljs",
"@npm//@types/yaml", "@npm//@types/yaml",
"@npm//@types/yargs", "@npm//@types/yargs",
"@npm//minimatch", "@npm//minimatch",
"@npm//shelljs",
"@npm//yaml", "@npm//yaml",
"@npm//yargs", "@npm//yargs",
], ],

View File

@ -9,8 +9,6 @@
import {join} from 'path'; import {join} from 'path';
import {GitClient} from '../../utils/git/git-client'; import {GitClient} from '../../utils/git/git-client';
import {exec as _exec} from '../../utils/shelljs';
export type EnvStampMode = 'snapshot'|'release'; export type EnvStampMode = 'snapshot'|'release';
/** /**
@ -25,22 +23,18 @@ export type EnvStampMode = 'snapshot'|'release';
*/ */
export function buildEnvStamp(mode: EnvStampMode) { export function buildEnvStamp(mode: EnvStampMode) {
console.info(`BUILD_SCM_BRANCH ${getCurrentBranch()}`); console.info(`BUILD_SCM_BRANCH ${getCurrentBranch()}`);
console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentSha()}`); console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentBranchOrRevision()}`);
console.info(`BUILD_SCM_HASH ${getCurrentSha()}`); console.info(`BUILD_SCM_HASH ${getCurrentBranchOrRevision()}`);
console.info(`BUILD_SCM_LOCAL_CHANGES ${hasLocalChanges()}`); console.info(`BUILD_SCM_LOCAL_CHANGES ${hasLocalChanges()}`);
console.info(`BUILD_SCM_USER ${getCurrentGitUser()}`); console.info(`BUILD_SCM_USER ${getCurrentGitUser()}`);
console.info(`BUILD_SCM_VERSION ${getSCMVersion(mode)}`); console.info(`BUILD_SCM_VERSION ${getSCMVersion(mode)}`);
process.exit(0); process.exit();
}
/** Run the exec command and return the stdout as a trimmed string. */
function exec(cmd: string) {
return _exec(cmd).trim();
} }
/** Whether the repo has local changes. */ /** Whether the repo has local changes. */
function hasLocalChanges() { function hasLocalChanges() {
return !!exec(`git status --untracked-files=no --porcelain`); const git = GitClient.get();
return git.hasUncommittedChanges();
} }
/** /**
@ -50,34 +44,38 @@ function hasLocalChanges() {
* In release mode, the version is based on the base package.json version. * In release mode, the version is based on the base package.json version.
*/ */
function getSCMVersion(mode: EnvStampMode) { function getSCMVersion(mode: EnvStampMode) {
const git = GitClient.get();
if (mode === 'release') { if (mode === 'release') {
const git = GitClient.get();
const packageJsonPath = join(git.baseDir, 'package.json'); const packageJsonPath = join(git.baseDir, 'package.json');
const {version} = require(packageJsonPath); const {version} = require(packageJsonPath);
return version; return version;
} }
if (mode === 'snapshot') { if (mode === 'snapshot') {
const version = exec(`git describe --match [0-9]*.[0-9]*.[0-9]* --abbrev=7 --tags HEAD`); const version =
git.run(['describe', '--match', '[0-9]*.[0-9]*.[0-9]*', '--abbrev=7', '--tags', 'HEAD'])
.stdout.trim();
return `${version.replace(/-([0-9]+)-g/, '+$1.sha-')}${ return `${version.replace(/-([0-9]+)-g/, '+$1.sha-')}${
(hasLocalChanges() ? '.with-local-changes' : '')}`; (hasLocalChanges() ? '.with-local-changes' : '')}`;
} }
return '0.0.0'; return '0.0.0';
} }
/** Get the current SHA of HEAD. */ /** Get the current branch or revision of HEAD. */
function getCurrentSha() { function getCurrentBranchOrRevision() {
return exec(`git rev-parse HEAD`); const git = GitClient.get();
return git.getCurrentBranchOrRevision();
} }
/** Get the currently checked out branch. */ /** Get the currently checked out branch. */
function getCurrentBranch() { function getCurrentBranch() {
return exec(`git symbolic-ref --short HEAD`); const git = GitClient.get();
return git.run(['symbolic-ref', '--short', 'HEAD']).stdout.trim();
} }
/** Get the current git user based on the git config. */ /** Get the current git user based on the git config. */
function getCurrentGitUser() { function getCurrentGitUser() {
const userName = exec(`git config user.name`); const git = GitClient.get();
const userEmail = exec(`git config user.email`); let userName = git.runGraceful(['config', 'user.name']).stdout.trim() || 'Unknown User';
let userEmail = git.runGraceful(['config', 'user.email']).stdout.trim() || 'unknown_email';
return `${userName} <${userEmail}>`; return `${userName} <${userEmail}>`;
} }

View File

@ -33,7 +33,6 @@
"protractor": "<from-root>", "protractor": "<from-root>",
"selenium-webdriver": "<from-root>", "selenium-webdriver": "<from-root>",
"semver": "<from-root>", "semver": "<from-root>",
"shelljs": "<from-root>",
"ts-node": "<from-root>", "ts-node": "<from-root>",
"tslib": "<from-root>", "tslib": "<from-root>",
"typed-graphqlify": "<from-root>", "typed-graphqlify": "<from-root>",

View File

@ -21,12 +21,10 @@ ts_library(
"@npm//@types/inquirer", "@npm//@types/inquirer",
"@npm//@types/node", "@npm//@types/node",
"@npm//@types/semver", "@npm//@types/semver",
"@npm//@types/shelljs",
"@npm//@types/yargs", "@npm//@types/yargs",
"@npm//chalk", "@npm//chalk",
"@npm//inquirer", "@npm//inquirer",
"@npm//semver", "@npm//semver",
"@npm//shelljs",
"@npm//tslib", "@npm//tslib",
"@npm//typed-graphqlify", "@npm//typed-graphqlify",
"@npm//yargs", "@npm//yargs",

View File

@ -10,6 +10,7 @@ import {spawn as _spawn, SpawnOptions as _SpawnOptions, spawnSync as _spawnSync,
import {debug, error} from './console'; import {debug, error} from './console';
/** Interface describing the options for spawning a process synchronously. */
export interface SpawnSyncOptions extends Omit<_SpawnSyncOptions, 'shell'|'stdio'> { export interface SpawnSyncOptions extends Omit<_SpawnSyncOptions, 'shell'|'stdio'> {
/** Whether to prevent exit codes being treated as failures. */ /** Whether to prevent exit codes being treated as failures. */
suppressErrorOnFailingExitCode?: boolean; suppressErrorOnFailingExitCode?: boolean;
@ -58,7 +59,7 @@ export function spawnInteractive(
* 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 spawn( export function spawn(
command: string, args: string[], options: SpawnOptions = {}): Promise<SpawnResult> { command: string, args: string[], options: SpawnOptions = {}): Promise<SpawnResult> {
@ -115,12 +116,12 @@ export function spawn(
} }
/** /**
* Spawns a given command with the specified arguments inside a shell syncronously. * Spawns a given command with the specified arguments inside a shell synchronously.
* *
* @returns The command's stdout and stderr. * @returns The command's stdout and stderr.
*/ */
export function spawnSync( export function spawnSync(
command: string, args: string[], options: SpawnOptions = {}): SpawnResult { command: string, args: string[], options: SpawnSyncOptions = {}): SpawnResult {
const commandText = `${command} ${args.join(' ')}`; const commandText = `${command} ${args.join(' ')}`;
debug(`Executing command: ${commandText}`); debug(`Executing command: ${commandText}`);
@ -137,9 +138,14 @@ export function spawnSync(
throw new Error(stderr); throw new Error(stderr);
} }
/**
* Convert the provided exitCode and signal to a single status code.
/** Convert the provided exitCode and signal to a single status code. */ *
* During `exit` node provides either a `code` or `signal`, one of which is guaranteed to be
* non-null.
*
* For more details see: https://nodejs.org/api/child_process.html#child_process_event_exit
*/
function statusFromExitCodeAndSignal(exitCode: number|null, signal: NodeJS.Signals|null) { function statusFromExitCodeAndSignal(exitCode: number|null, signal: NodeJS.Signals|null) {
return exitCode !== null ? exitCode : signal !== null ? signal : -1; return exitCode ?? signal ?? -1;
} }

View File

@ -23,6 +23,10 @@ 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(' '))}`);
// Set the prototype explicitly because in ES5, the prototype is accidentally lost due to
// a limitation in down-leveling.
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
Object.setPrototypeOf(this, GitCommandError.prototype); Object.setPrototypeOf(this, GitCommandError.prototype);
} }
} }

View File

@ -1,17 +0,0 @@
/**
* @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
*/
import {exec as _exec, ExecOptions, ShellString} from 'shelljs';
/**
* Runs an given command as child process. By default, child process
* output will not be printed.
*/
export function exec(cmd: string, opts?: Omit<ExecOptions, 'async'>): ShellString {
return _exec(cmd, {silent: true, ...opts, async: false});
}