Skip to content

Commit

Permalink
fix(ng-dev/release): properly ensure local ng-dev version is up-to-date
Browse files Browse the repository at this point in the history
Fixes that the ng-dev release tool currently relies on a historically broken
`yarn check` command for ensuring it is up-to-date. This check has proven to
not work well within the Angular repositories, so we switch to a more future-proof
and reliable approach by checking the lock file and comparing with the running
version through a substitued placeholder.
  • Loading branch information
devversion committed Nov 10, 2021
1 parent 88d1b37 commit 0474a28
Show file tree
Hide file tree
Showing 14 changed files with 140 additions and 48 deletions.
8 changes: 8 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ build:remote --platforms=//bazel/remote-execution:platform_with_network
# Set remote caching settings
build:remote --remote_accept_cached=true

################################
# Release setup #
################################

# Releases should always be stamped with version control info
build:release --workspace_status_command="yarn -s ng-dev release build-env-stamp --mode=release"
build:release --stamp

####################################################
# User bazel configuration
# NOTE: This needs to be the *last* entry in the config to allow for it to override other
Expand Down
5 changes: 4 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
- yarn_install
- run:
name: Build npm package
command: yarn bazel build //:npm_package
command: yarn bazel build //:npm_package --config=release
- run:
name: Publish snapshot build to github
command: ./.circleci/publish_to_github.sh
Expand All @@ -150,3 +150,6 @@ workflows:
branches:
only:
- main
# Additional branch that can be used to test the snapshot build output.
# Developers can just push to that branch to test the built artifact.
- snapshot-test
13 changes: 2 additions & 11 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# BEGIN-INTERNAL
load("//:package.bzl", "NPM_PACKAGE_SUBSTITUTIONS")
load("//tools:defaults.bzl", "pkg_npm")
load("@npm//@bazel/typescript:index.bzl", "ts_config")

Expand All @@ -25,17 +26,7 @@ pkg_npm(
":index.bzl",
"//bazel:static_files",
],
substitutions = {
" \"prepare\": \"husky install\",\n": "",
"@dev-infra//bazel/": "@npm//@angular/dev-infra-private/bazel/",
"//bazel/": "@npm//@angular/dev-infra-private/bazel/",
"//bazel:": "@npm//@angular/dev-infra-private/bazel:",
"//ng-dev/": "@npm//@angular/dev-infra-private/ng-dev/",
"//ng-dev:": "@npm//@angular/dev-infra-private/ng-dev:",
"//tslint-rules/": "@npm//@angular/dev-infra-private/tslint-rules/",
"//tslint-rules:": "@npm//@angular/dev-infra-private/tslint-rules:",
"//:tsconfig.json": "@npm//@angular/dev-infra-private:tsconfig.json",
},
substitutions = NPM_PACKAGE_SUBSTITUTIONS,
deps = [
"//ng-dev",
"//ng-dev:lib",
Expand Down
2 changes: 2 additions & 0 deletions ng-dev/release/publish/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ ts_library(
"@npm//@types/node",
"@npm//@types/semver",
"@npm//@types/yargs",
"@npm//@types/yarnpkg__lockfile",
"@npm//@yarnpkg/lockfile",
"@npm//ejs",
"@npm//inquirer",
"@npm//semver",
Expand Down
3 changes: 3 additions & 0 deletions ng-dev/release/publish/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
/** Project-relative path for the "package.json" file. */
export const packageJsonPath = 'package.json';

/** Project-relative path for the "yarn.lock" file. */
export const yarnLockFilePath = 'yarn.lock';

/** Default interval in milliseconds to check whether a pull request has been merged. */
export const waitForPullRequestInterval = 10000;

Expand Down
28 changes: 0 additions & 28 deletions ng-dev/release/publish/external-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,31 +97,3 @@ export async function invokeYarnInstallCommand(projectDir: string): Promise<void
throw new FatalReleaseActionError();
}
}

/**
* Invokes the `yarn check --integrity` command in order to verify up to date dependencies.
*/
export async function invokeYarnIntegrityCheck(projectDir: string): Promise<void> {
try {
await spawn('yarn', ['check', '--integrity'], {cwd: projectDir, mode: 'silent'});
info(green(' ✓ Confirmed dependencies from package.json match those in yarn.lock.'));
} catch (e) {
error(red(' ✘ Failed yarn integrity check, your installed dependencies are likely out of'));
error(red(' date. Please run `yarn install` to update your installed dependencies.'));
throw new FatalReleaseActionError();
}
}

/**
* Invokes the `yarn check --verify-tree` command in order to verify up to date dependencies.
*/
export async function invokeYarnVerifyTreeCheck(projectDir: string): Promise<void> {
try {
await spawn('yarn', ['check', '--verify-tree'], {cwd: projectDir, mode: 'silent'});
info(green(' ✓ Confirmed installed dependencies match those defined in package.json.'));
} catch (e) {
error(red(' ✘ Failed yarn verify tree check, your installed dependencies are likely out'));
error(red(' of date. Please run `yarn install` to update your installed dependencies.'));
throw new FatalReleaseActionError();
}
}
46 changes: 42 additions & 4 deletions ng-dev/release/publish/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/

import * as path from 'path';
import * as fs from 'fs';
import {ListChoiceOptions, prompt} from 'inquirer';
import {parse as parseYarnLockfile, LockFileObject} from '@yarnpkg/lockfile';

import {GithubConfig} from '../../utils/config';
import {debug, error, info, log, promptConfirm, red, yellow} from '../../utils/console';
import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client';
Expand All @@ -15,11 +19,12 @@ import {ActiveReleaseTrains, fetchActiveReleaseTrains} from '../versioning/activ
import {npmIsLoggedIn, npmLogin, npmLogout} from '../versioning/npm-publish';
import {printActiveReleaseTrains} from '../versioning/print-active-trains';
import {getNextBranchName, ReleaseRepoWithApi} from '../versioning/version-branches';
import {ngDevNpmPackageName} from '../../utils/constants';

import {ReleaseAction} from './actions';
import {FatalReleaseActionError, UserAbortedReleaseActionError} from './actions-error';
import {actions} from './actions/index';
import {invokeYarnIntegrityCheck, invokeYarnVerifyTreeCheck} from './external-commands';
import {packageJsonPath, yarnLockFilePath} from './constants';

export enum CompletionState {
SUCCESS,
Expand Down Expand Up @@ -152,11 +157,44 @@ export class ReleaseTool {
* @returns a boolean indicating success or failure.
*/
private async _verifyInstalledDependenciesAreUpToDate(): Promise<boolean> {
// The placeholder will be replaced by the `pkg_npm` substitutions.
const localVersion = `0.0.0-{SCM_HEAD_SHA}`;
const projectPackageJsonFile = path.join(this._projectRoot, packageJsonPath);
const projectDirLockFile = path.join(this._projectRoot, yarnLockFilePath);

try {
await invokeYarnVerifyTreeCheck(this._projectRoot);
await invokeYarnIntegrityCheck(this._projectRoot);
const lockFileContent = fs.readFileSync(projectDirLockFile, 'utf8');
const packageJson = JSON.parse(fs.readFileSync(projectPackageJsonFile, 'utf8')) as any;
const lockFile = parseYarnLockfile(lockFileContent);

if (lockFile.type !== 'success') {
throw Error('Unable to parse project lock file. Please ensure the file is valid.');
}

// If we are operating in the actual dev-infra repo, always return `true`.
if (packageJson.name === ngDevNpmPackageName) {
return true;
}

const lockFileObject = lockFile.object as LockFileObject;
const devInfraPkgVersion =
packageJson?.dependencies?.[ngDevNpmPackageName] ??
packageJson?.devDependencies?.[ngDevNpmPackageName] ??
packageJson?.optionalDependencies?.[ngDevNpmPackageName];
const expectedVersion =
lockFileObject[`${ngDevNpmPackageName}@${devInfraPkgVersion}`].version;

if (localVersion !== expectedVersion) {
error(red(' ✘ Your locally installed version of the `ng-dev` tool is outdated and not'));
error(red(' matching with the version in the `package.json` file.'));
error(
red(' Re-install the dependencies to ensure you are using the correct version.'),
);
return false;
}
return true;
} catch {
} catch (e) {
error(e);
return false;
}
}
Expand Down
13 changes: 13 additions & 0 deletions ng-dev/release/publish/yarn-lock-file.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @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
*/

// Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1033
// TODO: Remove when the type resolution is fixed.
declare module '@yarnpkg/lockfile' {
export * from 'yarnpkg__lockfile';
}
14 changes: 12 additions & 2 deletions ng-dev/release/stamping/env-stamp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ export type EnvStampMode = 'snapshot' | 'release';
export function buildEnvStamp(mode: EnvStampMode) {
const git = GitClient.get();
console.info(`BUILD_SCM_BRANCH ${getCurrentBranch(git)}`);
console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentBranchOrRevision(git)}`);
console.info(`BUILD_SCM_HASH ${getCurrentBranchOrRevision(git)}`);
console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentSha(git)}`);
console.info(`BUILD_SCM_HASH ${getCurrentSha(git)}`);
console.info(`BUILD_SCM_BRANCH ${getCurrentBranchOrRevision(git)}`);
console.info(`BUILD_SCM_LOCAL_CHANGES ${hasLocalChanges(git)}`);
console.info(`BUILD_SCM_USER ${getCurrentGitUser(git)}`);
const {version, experimentalVersion} = getSCMVersions(git, mode);
Expand Down Expand Up @@ -89,6 +90,15 @@ function getSCMVersions(
}
}

/** Get the current SHA of HEAD. */
function getCurrentSha(git: GitClient) {
try {
return git.run(['rev-parse', 'HEAD']).stdout.trim();
} catch {
return '';
}
}

/** Get the current branch or revision of HEAD. */
function getCurrentBranchOrRevision(git: GitClient) {
try {
Expand Down
10 changes: 10 additions & 0 deletions ng-dev/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @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
*/

/** NPM package name that is used for the `ng-dev` tool. */
export const ngDevNpmPackageName = '@angular/dev-infra-private';
24 changes: 24 additions & 0 deletions package.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
stampSubstitutions = {
# The variables are special statuses generated within the Bazel workspace
# status command stamping script.
"{SCM_HEAD_SHA}": "{BUILD_SCM_COMMIT_SHA}",
}

noStampSubstitutions = dict(stampSubstitutions, **{})

basePackageSubstitutions = {
" \"prepare\": \"husky install\",\n": "",
"@dev-infra//bazel/": "@npm//@angular/dev-infra-private/bazel/",
"//bazel/": "@npm//@angular/dev-infra-private/bazel/",
"//bazel:": "@npm//@angular/dev-infra-private/bazel:",
"//ng-dev/": "@npm//@angular/dev-infra-private/ng-dev/",
"//ng-dev:": "@npm//@angular/dev-infra-private/ng-dev:",
"//tslint-rules/": "@npm//@angular/dev-infra-private/tslint-rules/",
"//tslint-rules:": "@npm//@angular/dev-infra-private/tslint-rules:",
"//:tsconfig.json": "@npm//@angular/dev-infra-private:tsconfig.json",
}

NPM_PACKAGE_SUBSTITUTIONS = select({
"//tools:stamp": dict(basePackageSubstitutions, **stampSubstitutions),
"//conditions:default": dict(basePackageSubstitutions, **noStampSubstitutions),
})
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@angular/dev-infra-private",
"version": "0.0.0",
"version": "0.0.0-{SCM_HEAD_SHA}",
"bin": {
"ng-dev": "./ng-dev/cli-bundle.js"
},
Expand Down Expand Up @@ -34,6 +34,7 @@
"@rollup/plugin-commonjs": "^21.0.0",
"@rollup/plugin-node-resolve": "^13.0.4",
"@types/tmp": "^0.2.1",
"@yarnpkg/lockfile": "^1.1.0",
"chalk": "^4.1.0",
"clang-format": "^1.4.0",
"cli-progress": "^3.7.0",
Expand Down Expand Up @@ -80,6 +81,7 @@
"@types/shelljs": "^0.8.8",
"@types/uuid": "^8.3.1",
"@types/yargs": "^17.0.0",
"@types/yarnpkg__lockfile": "^1.1.5",
"jsdoc": "^3.6.7",
"minimist": "^1.2.5",
"protobufjs": "^6.11.2",
Expand Down
8 changes: 7 additions & 1 deletion tools/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# This BUILD.bazel file needs to exist to allow for //tools to be considered a package.
package(default_visibility = ["//visibility:public"])

# Detect if the build is running with stamping enabled.
config_setting(
name = "stamp",
values = {"stamp": "true"},
)
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,16 @@
dependencies:
"@types/yargs-parser" "*"

"@types/yarnpkg__lockfile@^1.1.5":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.5.tgz#9639020e1fb65120a2f4387db8f1e8b63efdf229"
integrity sha512-8NYnGOctzsI4W0ApsP/BIHD/LnxpJ6XaGf2AZmz4EyDYJMxtprN4279dLNI1CPZcwC9H18qYcaFv4bXi0wmokg==

"@yarnpkg/lockfile@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==

JSONStream@^1.0.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
Expand Down

0 comments on commit 0474a28

Please sign in to comment.