From 3d668162d9cf0918c7746e9f805c06fc01f31bcb Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Mon, 12 Jul 2021 14:58:35 -0700 Subject: [PATCH] Revert "refactor(dev-infra): remove usages and dependency on shelljs (#42394)" (#42829) This reverts commit f0d857eff876033359a7c284171775041f75db5d. PR Close #42829 --- .../benchmark/driver-utilities/BUILD.bazel | 2 + .../benchmark/driver-utilities/perf_util.ts | 6 +- dev-infra/build-worker.js | 3 - dev-infra/commit-message/BUILD.bazel | 2 + dev-infra/format/BUILD.bazel | 2 + dev-infra/format/formatters/base-formatter.ts | 3 +- dev-infra/format/formatters/buildifier.ts | 4 +- dev-infra/format/formatters/clang-format.ts | 4 +- dev-infra/format/formatters/prettier.ts | 11 +- dev-infra/format/run-commands-parallel.ts | 14 +- dev-infra/misc/build-and-link/cli.ts | 10 +- dev-infra/ng-dev.js | 272 +++++++++--------- .../pr/discover-new-conflicts/BUILD.bazel | 1 + dev-infra/pr/discover-new-conflicts/index.ts | 18 +- dev-infra/pullapprove/BUILD.bazel | 2 + dev-infra/release/stamping/env-stamp.ts | 38 +-- dev-infra/tmpl-package.json | 1 + dev-infra/utils/BUILD.bazel | 2 + dev-infra/utils/child-process.ts | 20 +- dev-infra/utils/git/git-client.ts | 4 - dev-infra/utils/shelljs.ts | 17 ++ 21 files changed, 228 insertions(+), 208 deletions(-) create mode 100644 dev-infra/utils/shelljs.ts diff --git a/dev-infra/benchmark/driver-utilities/BUILD.bazel b/dev-infra/benchmark/driver-utilities/BUILD.bazel index 3ef04bd319..fd575c92b3 100644 --- a/dev-infra/benchmark/driver-utilities/BUILD.bazel +++ b/dev-infra/benchmark/driver-utilities/BUILD.bazel @@ -10,8 +10,10 @@ ts_library( "//packages/benchpress", "@npm//@types/node", "@npm//@types/selenium-webdriver", + "@npm//@types/shelljs", "@npm//node-uuid", "@npm//protractor", "@npm//selenium-webdriver", + "@npm//shelljs", ], ) diff --git a/dev-infra/benchmark/driver-utilities/perf_util.ts b/dev-infra/benchmark/driver-utilities/perf_util.ts index a9043b9a24..b6d59a4a1d 100644 --- a/dev-infra/benchmark/driver-utilities/perf_util.ts +++ b/dev-infra/benchmark/driver-utilities/perf_util.ts @@ -5,7 +5,7 @@ * 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 {mkdirSync} from 'fs'; +import {mkdir} from 'shelljs'; export {verifyNoBrowserErrors} from './e2e_util'; @@ -61,9 +61,7 @@ function createBenchpressRunner(): Runner { runId = process.env.GIT_SHA + ' ' + runId; } const resultsFolder = './dist/benchmark_results'; - mkdirSync(resultsFolder, { - recursive: true, - }); + mkdir('-p', resultsFolder); const providers: StaticProvider[] = [ SeleniumWebDriverAdapter.PROTRACTOR_PROVIDERS, {provide: Options.FORCE_GC, useValue: globalOptions.forceGc}, diff --git a/dev-infra/build-worker.js b/dev-infra/build-worker.js index dff23b63c4..9654f967cd 100644 --- a/dev-infra/build-worker.js +++ b/dev-infra/build-worker.js @@ -144,9 +144,6 @@ var GitCommandError = /** @class */ (function (_super) { // we sanitize the command that will be part of the error message. _super.call(this, "Command failed: git " + client.sanitizeConsoleOutput(args.join(' '))) || this; _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); return _this; } diff --git a/dev-infra/commit-message/BUILD.bazel b/dev-infra/commit-message/BUILD.bazel index e0299445f8..bbdc0cd5ba 100644 --- a/dev-infra/commit-message/BUILD.bazel +++ b/dev-infra/commit-message/BUILD.bazel @@ -13,10 +13,12 @@ ts_library( "@npm//@types/git-raw-commits", "@npm//@types/inquirer", "@npm//@types/node", + "@npm//@types/shelljs", "@npm//@types/yargs", "@npm//conventional-commits-parser", "@npm//git-raw-commits", "@npm//inquirer", + "@npm//shelljs", "@npm//yargs", ], ) diff --git a/dev-infra/format/BUILD.bazel b/dev-infra/format/BUILD.bazel index 1c5a82ea85..c280f06d51 100644 --- a/dev-infra/format/BUILD.bazel +++ b/dev-infra/format/BUILD.bazel @@ -10,9 +10,11 @@ ts_library( "//dev-infra/utils", "@npm//@types/cli-progress", "@npm//@types/node", + "@npm//@types/shelljs", "@npm//@types/yargs", "@npm//cli-progress", "@npm//multimatch", + "@npm//shelljs", "@npm//yargs", ], ) diff --git a/dev-infra/format/formatters/base-formatter.ts b/dev-infra/format/formatters/base-formatter.ts index 65930adadb..24ba0fa516 100644 --- a/dev-infra/format/formatters/base-formatter.ts +++ b/dev-infra/format/formatters/base-formatter.ts @@ -10,8 +10,7 @@ import {GitClient} from '../../utils/git/git-client'; import {FormatConfig} from '../config'; // A callback to determine if the formatter run found a failure in formatting. -export type CallbackFunc = - (file: string, code: number|NodeJS.Signals, stdout: string, stderr: string) => boolean; +export type CallbackFunc = (file: string, code: number, stdout: string, stderr: string) => boolean; // The actions a formatter can take. export type FormatterAction = 'check'|'format'; diff --git a/dev-infra/format/formatters/buildifier.ts b/dev-infra/format/formatters/buildifier.ts index 3f49dc1b3c..b18ba62a3b 100644 --- a/dev-infra/format/formatters/buildifier.ts +++ b/dev-infra/format/formatters/buildifier.ts @@ -26,14 +26,14 @@ export class Buildifier extends Formatter { check: { commandFlags: `${BAZEL_WARNING_FLAG} --lint=warn --mode=check --format=json`, callback: - (_: string, code: number|NodeJS.Signals, stdout: string) => { + (_: string, code: number, stdout: string) => { return code !== 0 || !(JSON.parse(stdout) as {success: string}).success; }, }, format: { commandFlags: `${BAZEL_WARNING_FLAG} --lint=fix --mode=fix`, callback: - (file: string, code: number|NodeJS.Signals, _: string, stderr: string) => { + (file: string, code: number, _: string, stderr: string) => { if (code !== 0) { error(`Error running buildifier on: ${file}`); error(stderr); diff --git a/dev-infra/format/formatters/clang-format.ts b/dev-infra/format/formatters/clang-format.ts index 616849a579..637f427223 100644 --- a/dev-infra/format/formatters/clang-format.ts +++ b/dev-infra/format/formatters/clang-format.ts @@ -26,14 +26,14 @@ export class ClangFormat extends Formatter { check: { commandFlags: `--Werror -n -style=file`, callback: - (_: string, code: number|NodeJS.Signals) => { + (_: string, code: number) => { return code !== 0; }, }, format: { commandFlags: `-i -style=file`, callback: - (file: string, code: number|NodeJS.Signals, _: string, stderr: string) => { + (file: string, code: number, _: string, stderr: string) => { if (code !== 0) { error(`Error running clang-format on: ${file}`); error(stderr); diff --git a/dev-infra/format/formatters/prettier.ts b/dev-infra/format/formatters/prettier.ts index f2969203c7..e56da8fa67 100644 --- a/dev-infra/format/formatters/prettier.ts +++ b/dev-infra/format/formatters/prettier.ts @@ -7,8 +7,8 @@ */ import {join} from 'path'; +import {exec} from 'shelljs'; -import {spawnSync} from '../../utils/child-process'; import {error} from '../../utils/console'; import {Formatter} from './base-formatter'; @@ -27,22 +27,21 @@ export class Prettier extends Formatter { * The configuration path of the prettier config, obtained during construction to prevent needing * to discover it repeatedly for each execution. */ - private configPath = this.config['prettier'] ? - spawnSync(this.binaryFilePath, ['--find-config-path', '.']).stdout.trim() : - ''; + private configPath = + this.config['prettier'] ? exec(`${this.binaryFilePath} --find-config-path .`).trim() : ''; override actions = { check: { commandFlags: `--config ${this.configPath} --check`, callback: - (_: string, code: number|NodeJS.Signals, stdout: string) => { + (_: string, code: number, stdout: string) => { return code !== 0; }, }, format: { commandFlags: `--config ${this.configPath} --write`, callback: - (file: string, code: number|NodeJS.Signals, _: string, stderr: string) => { + (file: string, code: number, _: string, stderr: string) => { if (code !== 0) { error(`Error running prettier on: ${file}`); error(stderr); diff --git a/dev-infra/format/run-commands-parallel.ts b/dev-infra/format/run-commands-parallel.ts index c10a27f9c9..5671d9486a 100644 --- a/dev-infra/format/run-commands-parallel.ts +++ b/dev-infra/format/run-commands-parallel.ts @@ -9,8 +9,8 @@ import {Bar} from 'cli-progress'; import * as multimatch from 'multimatch'; import {cpus} from 'os'; +import {exec} from 'shelljs'; -import {spawn, SpawnResult} from '../utils/child-process'; import {info} from '../utils/console'; import {Formatter, FormatterAction, getActiveFormatters} from './formatters/index'; @@ -86,11 +86,12 @@ export function runFormatterInParallel(allFiles: string[], action: FormatterActi // Get the file and formatter for the next command. const {file, formatter} = nextCommand; - const [spawnCmd, ...spawnArgs] = [...formatter.commandFor(action).split(' '), file]; - spawn(spawnCmd, spawnArgs, {suppressErrorOnFailingExitCode: true, mode: 'silent'}) - .then(({stdout, stderr, status}: SpawnResult) => { + exec( + `${formatter.commandFor(action)} ${file}`, + {async: true, silent: true}, + (code, stdout, stderr) => { // Run the provided callback function. - const failed = formatter.callbackFor(action)(file, status, stdout, stderr); + const failed = formatter.callbackFor(action)(file, code, stdout, stderr); if (failed) { failures.push({filePath: file, message: stderr}); } @@ -109,7 +110,8 @@ export function runFormatterInParallel(allFiles: string[], action: FormatterActi progressBar.stop(); resolve(failures); } - }); + }, + ); // Mark the thread as in use as the command execution has been started. threads[thread] = true; } diff --git a/dev-infra/misc/build-and-link/cli.ts b/dev-infra/misc/build-and-link/cli.ts index d9144b6a32..d32fd4a021 100644 --- a/dev-infra/misc/build-and-link/cli.ts +++ b/dev-infra/misc/build-and-link/cli.ts @@ -7,13 +7,13 @@ */ import {green} from 'chalk'; -import {lstatSync} from 'fs'; -import {resolve} from 'path'; +import {lstatSync, stat, Stats} from 'fs'; +import {isAbsolute, join, resolve} from 'path'; import {Arguments, Argv, CommandModule} from 'yargs'; import {buildReleaseOutput} from '../../release/build/index'; -import {spawn} from '../../utils/child-process'; import {error, info, red} from '../../utils/console'; +import {exec} from '../../utils/shelljs'; /** Command line options. */ @@ -52,8 +52,8 @@ async function handler({projectRoot}: Arguments) { info(green(` ✓ Built release output.`)); for (const {outputPath, name} of releaseOutputs) { - await spawn('yarn', ['link', '--cwd', outputPath]); - await spawn('yarn', ['link', '--cwd', projectRoot, name]); + exec(`yarn link --cwd ${outputPath}`); + exec(`yarn link --cwd ${projectRoot} ${name}`); } info(green(` ✓ Linked release packages in provided project.`)); diff --git a/dev-infra/ng-dev.js b/dev-infra/ng-dev.js index fb6eaf6eec..c05c96d301 100755 --- a/dev-infra/ng-dev.js +++ b/dev-infra/ng-dev.js @@ -22,6 +22,7 @@ var conventionalCommitsParser = require('conventional-commits-parser'); var gitCommits_ = require('git-raw-commits'); var cliProgress = require('cli-progress'); var os = require('os'); +var shelljs = require('shelljs'); var minimatch = require('minimatch'); var ejs = require('ejs'); var ora = require('ora'); @@ -315,9 +316,6 @@ var GitCommandError = /** @class */ (function (_super) { // we sanitize the command that will be part of the error message. _super.call(this, "Command failed: git " + client.sanitizeConsoleOutput(args.join(' '))) || this; _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); return _this; } @@ -2288,113 +2286,6 @@ function buildCommitMessageParser(localYargs) { .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 * Copyright Google LLC All Rights Reserved. @@ -2599,9 +2490,7 @@ class Prettier extends Formatter { * The configuration path of the prettier config, obtained during construction to prevent needing * to discover it repeatedly for each execution. */ - this.configPath = this.config['prettier'] ? - spawnSync(this.binaryFilePath, ['--find-config-path', '.']).stdout.trim() : - ''; + this.configPath = this.config['prettier'] ? shelljs.exec(`${this.binaryFilePath} --find-config-path .`).trim() : ''; this.actions = { check: { commandFlags: `--config ${this.configPath} --check`, @@ -2704,11 +2593,9 @@ function runFormatterInParallel(allFiles, action) { } // Get the file and formatter for the next command. const { file, formatter } = nextCommand; - const [spawnCmd, ...spawnArgs] = [...formatter.commandFor(action).split(' '), file]; - spawn(spawnCmd, spawnArgs, { suppressErrorOnFailingExitCode: true, mode: 'silent' }) - .then(({ stdout, stderr, status }) => { + shelljs.exec(`${formatter.commandFor(action)} ${file}`, { async: true, silent: true }, (code, stdout, stderr) => { // Run the provided callback function. - const failed = formatter.callbackFor(action)(file, status, stdout, stderr); + const failed = formatter.callbackFor(action)(file, code, stdout, stderr); if (failed) { failures.push({ filePath: file, message: stderr }); } @@ -3335,6 +3222,21 @@ const CheckoutCommandModule = { 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 * Copyright Google LLC All Rights Reserved. @@ -3438,9 +3340,7 @@ function discoverNewConflictsForPr(newPrNumber, updatedAfter) { if (err instanceof GitCommandError) { conflicts.push(pr); } - else { - throw err; - } + throw err; } // Abort any outstanding rebase attempt. git.runGraceful(['rebase', '--abort'], { stdio: 'ignore' }); @@ -3450,7 +3350,7 @@ function discoverNewConflictsForPr(newPrNumber, updatedAfter) { progressBar.stop(); info(); info(`Result:`); - git.checkout(previousBranchOrRevision, true); + cleanUpGitState(previousBranchOrRevision); // If no conflicts are found, exit successfully. if (conflicts.length === 0) { info(`No new conflicting PRs found after #${newPrNumber} merging`); @@ -3465,6 +3365,17 @@ function discoverNewConflictsForPr(newPrNumber, updatedAfter) { 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 @@ -5911,6 +5822,88 @@ const ReleaseNotesCommandModule = { 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 * Copyright Google LLC All Rights Reserved. @@ -7503,17 +7496,20 @@ const ReleaseSetDistTagCommand = { */ function buildEnvStamp(mode) { console.info(`BUILD_SCM_BRANCH ${getCurrentBranch()}`); - console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentBranchOrRevision()}`); - console.info(`BUILD_SCM_HASH ${getCurrentBranchOrRevision()}`); + console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentSha()}`); + console.info(`BUILD_SCM_HASH ${getCurrentSha()}`); console.info(`BUILD_SCM_LOCAL_CHANGES ${hasLocalChanges()}`); console.info(`BUILD_SCM_USER ${getCurrentGitUser()}`); console.info(`BUILD_SCM_VERSION ${getSCMVersion(mode)}`); - process.exit(); + process.exit(0); +} +/** 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. */ function hasLocalChanges() { - const git = GitClient.get(); - return git.hasUncommittedChanges(); + return !!exec$1(`git status --untracked-files=no --porcelain`); } /** * Get the version for generated packages. @@ -7522,34 +7518,30 @@ function hasLocalChanges() { * In release mode, the version is based on the base package.json version. */ function getSCMVersion(mode) { - const git = GitClient.get(); if (mode === 'release') { + const git = GitClient.get(); const packageJsonPath = path.join(git.baseDir, 'package.json'); const { version } = require(packageJsonPath); return version; } if (mode === 'snapshot') { - const version = git.run(['describe', '--match', '[0-9]*.[0-9]*.[0-9]*', '--abbrev=7', '--tags', 'HEAD']) - .stdout.trim(); + const version = exec$1(`git describe --match [0-9]*.[0-9]*.[0-9]* --abbrev=7 --tags HEAD`); return `${version.replace(/-([0-9]+)-g/, '+$1.sha-')}${(hasLocalChanges() ? '.with-local-changes' : '')}`; } return '0.0.0'; } -/** Get the current branch or revision of HEAD. */ -function getCurrentBranchOrRevision() { - const git = GitClient.get(); - return git.getCurrentBranchOrRevision(); +/** Get the current SHA of HEAD. */ +function getCurrentSha() { + return exec$1(`git rev-parse HEAD`); } /** Get the currently checked out branch. */ function getCurrentBranch() { - const git = GitClient.get(); - return git.run(['symbolic-ref', '--short', 'HEAD']).stdout.trim(); + return exec$1(`git symbolic-ref --short HEAD`); } /** Get the current git user based on the git config. */ function getCurrentGitUser() { - const git = GitClient.get(); - const userName = git.run(['config', 'user.name']).stdout.trim(); - const userEmail = git.run(['config', 'user.email']).stdout.trim(); + const userName = exec$1(`git config user.name`); + const userEmail = exec$1(`git config user.email`); return `${userName} <${userEmail}>`; } @@ -8036,8 +8028,8 @@ function handler$d({ projectRoot }) { } info(chalk.green(` ✓ Built release output.`)); for (const { outputPath, name } of releaseOutputs) { - yield spawn('yarn', ['link', '--cwd', outputPath]); - yield spawn('yarn', ['link', '--cwd', projectRoot, name]); + exec(`yarn link --cwd ${outputPath}`); + exec(`yarn link --cwd ${projectRoot} ${name}`); } info(chalk.green(` ✓ Linked release packages in provided project.`)); }); diff --git a/dev-infra/pr/discover-new-conflicts/BUILD.bazel b/dev-infra/pr/discover-new-conflicts/BUILD.bazel index e88ed7eaed..3ea331122b 100644 --- a/dev-infra/pr/discover-new-conflicts/BUILD.bazel +++ b/dev-infra/pr/discover-new-conflicts/BUILD.bazel @@ -11,6 +11,7 @@ ts_library( "//dev-infra/utils", "@npm//@types/cli-progress", "@npm//@types/node", + "@npm//@types/shelljs", "@npm//@types/yargs", "@npm//typed-graphqlify", ], diff --git a/dev-infra/pr/discover-new-conflicts/index.ts b/dev-infra/pr/discover-new-conflicts/index.ts index 08ee4e01cc..32138da310 100644 --- a/dev-infra/pr/discover-new-conflicts/index.ts +++ b/dev-infra/pr/discover-new-conflicts/index.ts @@ -13,6 +13,7 @@ import {error, info} from '../../utils/console'; import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client'; import {GitCommandError} from '../../utils/git/git-client'; import {getPendingPrs} from '../../utils/github'; +import {exec} from '../../utils/shelljs'; /* Graphql schema for the response body for each pending PR. */ @@ -124,9 +125,8 @@ export async function discoverNewConflictsForPr(newPrNumber: number, updatedAfte } catch (err) { if (err instanceof GitCommandError) { conflicts.push(pr); - } else { - throw err; } + throw err; } // Abort any outstanding rebase attempt. git.runGraceful(['rebase', '--abort'], {stdio: 'ignore'}); @@ -138,7 +138,7 @@ export async function discoverNewConflictsForPr(newPrNumber: number, updatedAfte info(); info(`Result:`); - git.checkout(previousBranchOrRevision, true); + cleanUpGitState(previousBranchOrRevision); // If no conflicts are found, exit successfully. if (conflicts.length === 0) { @@ -154,3 +154,15 @@ export async function discoverNewConflictsForPr(newPrNumber: number, updatedAfte error.groupEnd(); 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}`); +} diff --git a/dev-infra/pullapprove/BUILD.bazel b/dev-infra/pullapprove/BUILD.bazel index 7ab68b7d64..94bda2d509 100644 --- a/dev-infra/pullapprove/BUILD.bazel +++ b/dev-infra/pullapprove/BUILD.bazel @@ -17,9 +17,11 @@ ts_library( "//dev-infra/utils", "@npm//@types/minimatch", "@npm//@types/node", + "@npm//@types/shelljs", "@npm//@types/yaml", "@npm//@types/yargs", "@npm//minimatch", + "@npm//shelljs", "@npm//yaml", "@npm//yargs", ], diff --git a/dev-infra/release/stamping/env-stamp.ts b/dev-infra/release/stamping/env-stamp.ts index 96b77c6f49..45d5221275 100644 --- a/dev-infra/release/stamping/env-stamp.ts +++ b/dev-infra/release/stamping/env-stamp.ts @@ -9,6 +9,8 @@ import {join} from 'path'; import {GitClient} from '../../utils/git/git-client'; +import {exec as _exec} from '../../utils/shelljs'; + export type EnvStampMode = 'snapshot'|'release'; /** @@ -23,18 +25,22 @@ export type EnvStampMode = 'snapshot'|'release'; */ export function buildEnvStamp(mode: EnvStampMode) { console.info(`BUILD_SCM_BRANCH ${getCurrentBranch()}`); - console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentBranchOrRevision()}`); - console.info(`BUILD_SCM_HASH ${getCurrentBranchOrRevision()}`); + console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentSha()}`); + console.info(`BUILD_SCM_HASH ${getCurrentSha()}`); console.info(`BUILD_SCM_LOCAL_CHANGES ${hasLocalChanges()}`); console.info(`BUILD_SCM_USER ${getCurrentGitUser()}`); console.info(`BUILD_SCM_VERSION ${getSCMVersion(mode)}`); - process.exit(); + process.exit(0); +} + +/** 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. */ function hasLocalChanges() { - const git = GitClient.get(); - return git.hasUncommittedChanges(); + return !!exec(`git status --untracked-files=no --porcelain`); } /** @@ -44,38 +50,34 @@ function hasLocalChanges() { * In release mode, the version is based on the base package.json version. */ function getSCMVersion(mode: EnvStampMode) { - const git = GitClient.get(); if (mode === 'release') { + const git = GitClient.get(); const packageJsonPath = join(git.baseDir, 'package.json'); const {version} = require(packageJsonPath); return version; } if (mode === 'snapshot') { - const version = - git.run(['describe', '--match', '[0-9]*.[0-9]*.[0-9]*', '--abbrev=7', '--tags', 'HEAD']) - .stdout.trim(); + const version = exec(`git describe --match [0-9]*.[0-9]*.[0-9]* --abbrev=7 --tags HEAD`); return `${version.replace(/-([0-9]+)-g/, '+$1.sha-')}${ (hasLocalChanges() ? '.with-local-changes' : '')}`; } return '0.0.0'; } -/** Get the current branch or revision of HEAD. */ -function getCurrentBranchOrRevision() { - const git = GitClient.get(); - return git.getCurrentBranchOrRevision(); +/** Get the current SHA of HEAD. */ +function getCurrentSha() { + return exec(`git rev-parse HEAD`); } /** Get the currently checked out branch. */ function getCurrentBranch() { - const git = GitClient.get(); - return git.run(['symbolic-ref', '--short', 'HEAD']).stdout.trim(); + return exec(`git symbolic-ref --short HEAD`); } /** Get the current git user based on the git config. */ function getCurrentGitUser() { - const git = GitClient.get(); - const userName = git.run(['config', 'user.name']).stdout.trim(); - const userEmail = git.run(['config', 'user.email']).stdout.trim(); + const userName = exec(`git config user.name`); + const userEmail = exec(`git config user.email`); + return `${userName} <${userEmail}>`; } diff --git a/dev-infra/tmpl-package.json b/dev-infra/tmpl-package.json index 901b3b9d36..3fb79079ff 100644 --- a/dev-infra/tmpl-package.json +++ b/dev-infra/tmpl-package.json @@ -33,6 +33,7 @@ "protractor": "", "selenium-webdriver": "", "semver": "", + "shelljs": "", "ts-node": "", "tslib": "", "typed-graphqlify": "", diff --git a/dev-infra/utils/BUILD.bazel b/dev-infra/utils/BUILD.bazel index cc51124606..09c4f9954b 100644 --- a/dev-infra/utils/BUILD.bazel +++ b/dev-infra/utils/BUILD.bazel @@ -21,10 +21,12 @@ ts_library( "@npm//@types/inquirer", "@npm//@types/node", "@npm//@types/semver", + "@npm//@types/shelljs", "@npm//@types/yargs", "@npm//chalk", "@npm//inquirer", "@npm//semver", + "@npm//shelljs", "@npm//tslib", "@npm//typed-graphqlify", "@npm//yargs", diff --git a/dev-infra/utils/child-process.ts b/dev-infra/utils/child-process.ts index 809b39baef..64fcdcb634 100644 --- a/dev-infra/utils/child-process.ts +++ b/dev-infra/utils/child-process.ts @@ -10,7 +10,6 @@ import {spawn as _spawn, SpawnOptions as _SpawnOptions, spawnSync as _spawnSync, import {debug, error} from './console'; -/** Interface describing the options for spawning a process synchronously. */ export interface SpawnSyncOptions extends Omit<_SpawnSyncOptions, 'shell'|'stdio'> { /** Whether to prevent exit codes being treated as failures. */ suppressErrorOnFailingExitCode?: boolean; @@ -59,7 +58,7 @@ export function spawnInteractive( * 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. + * rejects on command failure */ export function spawn( command: string, args: string[], options: SpawnOptions = {}): Promise { @@ -116,12 +115,12 @@ export function spawn( } /** - * Spawns a given command with the specified arguments inside a shell synchronously. + * 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: SpawnSyncOptions = {}): SpawnResult { + command: string, args: string[], options: SpawnOptions = {}): SpawnResult { const commandText = `${command} ${args.join(' ')}`; debug(`Executing command: ${commandText}`); @@ -138,14 +137,9 @@ export function spawnSync( 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 - */ + + +/** Convert the provided exitCode and signal to a single status code. */ function statusFromExitCodeAndSignal(exitCode: number|null, signal: NodeJS.Signals|null) { - return exitCode ?? signal ?? -1; + return exitCode !== null ? exitCode : signal !== null ? signal : -1; } diff --git a/dev-infra/utils/git/git-client.ts b/dev-infra/utils/git/git-client.ts index bdb45371d8..3f9f6042b0 100644 --- a/dev-infra/utils/git/git-client.ts +++ b/dev-infra/utils/git/git-client.ts @@ -23,10 +23,6 @@ export class GitCommandError extends Error { // accidentally leak the Github token that might be used in a command, // we sanitize the command that will be part of the error message. 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); } } diff --git a/dev-infra/utils/shelljs.ts b/dev-infra/utils/shelljs.ts new file mode 100644 index 0000000000..0040b4135b --- /dev/null +++ b/dev-infra/utils/shelljs.ts @@ -0,0 +1,17 @@ +/** + * @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): ShellString { + return _exec(cmd, {silent: true, ...opts, async: false}); +}