diff --git a/dev-infra/BUILD.bazel b/dev-infra/BUILD.bazel index b8ca09d8e5..44a7a2930b 100644 --- a/dev-infra/BUILD.bazel +++ b/dev-infra/BUILD.bazel @@ -11,6 +11,7 @@ ts_library( "//dev-infra/commit-message", "//dev-infra/format", "//dev-infra/pullapprove", + "//dev-infra/release", "//dev-infra/ts-circular-dependencies", "//dev-infra/utils", "@npm//@types/node", diff --git a/dev-infra/cli.ts b/dev-infra/cli.ts index 86eb3ed563..836b90afa9 100644 --- a/dev-infra/cli.ts +++ b/dev-infra/cli.ts @@ -11,14 +11,16 @@ import {tsCircularDependenciesBuilder} from './ts-circular-dependencies/index'; import {buildPullapproveParser} from './pullapprove/cli'; import {buildCommitMessageParser} from './commit-message/cli'; import {buildFormatParser} from './format/cli'; +import {buildReleaseParser} from './release/cli'; yargs.scriptName('ng-dev') .demandCommand() .recommendCommands() - .command('ts-circular-deps ', '', tsCircularDependenciesBuilder) - .command('pullapprove ', '', buildPullapproveParser) .command('commit-message ', '', buildCommitMessageParser) .command('format ', '', buildFormatParser) + .command('pullapprove ', '', buildPullapproveParser) + .command('release ', '', buildReleaseParser) + .command('ts-circular-deps ', '', tsCircularDependenciesBuilder) .wrap(120) .strict() .parse(); diff --git a/dev-infra/release/BUILD.bazel b/dev-infra/release/BUILD.bazel new file mode 100644 index 0000000000..bf2ffdd6db --- /dev/null +++ b/dev-infra/release/BUILD.bazel @@ -0,0 +1,17 @@ +load("@npm_bazel_typescript//:index.bzl", "ts_library") + +ts_library( + name = "release", + srcs = glob([ + "*.ts", + ]), + module_name = "@angular/dev-infra-private/release", + visibility = ["//dev-infra:__subpackages__"], + deps = [ + "@npm//@types/node", + "@npm//@types/shelljs", + "@npm//@types/yargs", + "@npm//shelljs", + "@npm//yargs", + ], +) diff --git a/dev-infra/release/cli.ts b/dev-infra/release/cli.ts new file mode 100644 index 0000000000..33c343c106 --- /dev/null +++ b/dev-infra/release/cli.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright Google Inc. 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 * as yargs from 'yargs'; +import {buildEnvStamp} from './env-stamp'; + +/** Build the parser for the release commands. */ +export function buildReleaseParser(localYargs: yargs.Argv) { + return localYargs.help().strict().demandCommand().command( + 'build-env-stamp', 'Build the environment stamping information', {}, () => buildEnvStamp()); +} + +if (require.main === module) { + buildReleaseParser(yargs).parse(); +} diff --git a/dev-infra/release/env-stamp.ts b/dev-infra/release/env-stamp.ts new file mode 100644 index 0000000000..97e0331b69 --- /dev/null +++ b/dev-infra/release/env-stamp.ts @@ -0,0 +1,64 @@ +/** + * @license + * Copyright Google Inc. 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} from 'shelljs'; + +/** + * Log the environment variables expected by bazel for stamping. + * + * See the section on stamping in docs / BAZEL.md + * + * This script must be a NodeJS script in order to be cross-platform. + * See https://github.com/bazelbuild/bazel/issues/5958 + * Note: git operations, especially git status, take a long time inside mounted docker volumes + * in Windows or OSX hosts (https://github.com/docker/for-win/issues/188). + */ +export function buildEnvStamp() { + console.info(`BUILD_SCM_BRANCH ${getCurrentBranch()}`); + 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()}`); + process.exit(0); +} + +/** Run the exec command and return the stdout as a trimmed string. */ +function exec(cmd: string) { + return _exec(cmd, {silent: true}).toString().trim(); +} + +/** Whether the repo has local changes. */ +function hasLocalChanges() { + return !!exec(`git status --untracked-files=no --porcelain`); +} + +/** Get the version based on the most recent semver tag. */ +function getSCMVersion() { + 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' : '')}`; +} + +/** Get the current SHA of HEAD. */ +function getCurrentSha() { + return exec(`git rev-parse HEAD`); +} + +/** Get the currently checked out branch. */ +function getCurrentBranch() { + return exec(`git symbolic-ref --short HEAD`); +} + +/** Get the current git user based on the git config. */ +function getCurrentGitUser() { + const userName = exec(`git config user.name`); + const userEmail = exec(`git config user.email`); + + return `${userName} <${userEmail}>`; +}