refactor(dev-infra): several code style and typo fixes (#39135)
This commit addresses comments from [my review][1] on PR #38656 (which was merged without comments addressed). The changes are mostly related to code style and typos. [1]: https://github.com/angular/angular/pull/38656#pullrequestreview-482129333 PR Close #39135
This commit is contained in:
parent
5471789664
commit
6947ceaf44
|
@ -26,7 +26,7 @@ export const release: ReleaseConfig = {
|
||||||
],
|
],
|
||||||
// TODO: Implement release package building here.
|
// TODO: Implement release package building here.
|
||||||
buildPackages: async () => [],
|
buildPackages: async () => [],
|
||||||
// TODO: This can be removed once there is a org-wide tool for changelog generation.
|
// TODO: This can be removed once there is an org-wide tool for changelog generation.
|
||||||
generateReleaseNotesForHead: async () => {
|
generateReleaseNotesForHead: async () => {
|
||||||
exec('yarn -s gulp changelog', {cwd: join(__dirname, '../')});
|
exec('yarn -s gulp changelog', {cwd: join(__dirname, '../')});
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,8 +41,8 @@ export async function getDefaultTargetLabelConfiguration(
|
||||||
// allow merging of PRs with `target: major`.
|
// allow merging of PRs with `target: major`.
|
||||||
if (!next.isMajor) {
|
if (!next.isMajor) {
|
||||||
throw new InvalidTargetLabelError(
|
throw new InvalidTargetLabelError(
|
||||||
`Unable to merge pull request. The "${nextBranchName}" branch will be ` +
|
`Unable to merge pull request. The "${nextBranchName}" branch will be released as ` +
|
||||||
`released as a minor version.`);
|
'a minor version.');
|
||||||
}
|
}
|
||||||
return [nextBranchName];
|
return [nextBranchName];
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,8 +30,8 @@ export async function assertActiveLtsBranch(
|
||||||
const ltsNpmTag = getLtsNpmDistTagOfMajor(version.major);
|
const ltsNpmTag = getLtsNpmDistTagOfMajor(version.major);
|
||||||
const ltsVersion = semver.parse(distTags[ltsNpmTag]);
|
const ltsVersion = semver.parse(distTags[ltsNpmTag]);
|
||||||
|
|
||||||
// Ensure that there is a LTS version tagged for the given version-branch major. e.g.
|
// Ensure that there is an LTS version tagged for the given version-branch major. e.g.
|
||||||
// if the version branch is `9.2.x` then we want to make sure that there is a LTS
|
// if the version branch is `9.2.x` then we want to make sure that there is an LTS
|
||||||
// version tagged in NPM for `v9`, following the `v{major}-lts` tag convention.
|
// version tagged in NPM for `v9`, following the `v{major}-lts` tag convention.
|
||||||
if (ltsVersion === null) {
|
if (ltsVersion === null) {
|
||||||
throw new InvalidTargetBranchError(`No LTS version tagged for v${version.major} in NPM.`);
|
throw new InvalidTargetBranchError(`No LTS version tagged for v${version.major} in NPM.`);
|
||||||
|
|
|
@ -33,7 +33,7 @@ export async function mergePullRequest(
|
||||||
prNumber: number, githubToken: string, projectRoot: string = getRepoBaseDir(),
|
prNumber: number, githubToken: string, projectRoot: string = getRepoBaseDir(),
|
||||||
config?: MergeConfigWithRemote) {
|
config?: MergeConfigWithRemote) {
|
||||||
// Set the environment variable to skip all git commit hooks triggered by husky. We are unable to
|
// Set the environment variable to skip all git commit hooks triggered by husky. We are unable to
|
||||||
// rely on `---no-verify` as some hooks still run, notably the `prepare-commit-msg` hook.
|
// rely on `--no-verify` as some hooks still run, notably the `prepare-commit-msg` hook.
|
||||||
process.env['HUSKY_SKIP_HOOKS'] = '1';
|
process.env['HUSKY_SKIP_HOOKS'] = '1';
|
||||||
|
|
||||||
const api = await createPullRequestMergeTask(githubToken, projectRoot, config);
|
const api = await createPullRequestMergeTask(githubToken, projectRoot, config);
|
||||||
|
|
|
@ -60,7 +60,7 @@ describe('ng-dev release build', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error if package has not been built', async () => {
|
it('should error if package has not been built', async () => {
|
||||||
// Set up a NPM package that is not built.
|
// Set up an NPM package that is not built.
|
||||||
npmPackages.push('@angular/non-existent');
|
npmPackages.push('@angular/non-existent');
|
||||||
|
|
||||||
spyOn(console, 'error');
|
spyOn(console, 'error');
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {promises as fs} from 'fs';
|
import {promises as fs} from 'fs';
|
||||||
import * as Ora from 'ora';
|
import * as ora from 'ora';
|
||||||
import {join} from 'path';
|
import {join} from 'path';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ export interface PullRequest {
|
||||||
forkBranch: string;
|
forkBranch: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructor type for a instantiating a release action */
|
/** Constructor type for instantiating a release action */
|
||||||
export interface ReleaseActionConstructor<T extends ReleaseAction = ReleaseAction> {
|
export interface ReleaseActionConstructor<T extends ReleaseAction = ReleaseAction> {
|
||||||
/** Whether the release action is currently active. */
|
/** Whether the release action is currently active. */
|
||||||
isActive(active: ActiveReleaseTrains): Promise<boolean>;
|
isActive(active: ActiveReleaseTrains): Promise<boolean>;
|
||||||
|
@ -107,25 +107,22 @@ export abstract class ReleaseAction {
|
||||||
if (state === 'failure') {
|
if (state === 'failure') {
|
||||||
error(
|
error(
|
||||||
red(` ✘ Cannot stage release. Commit "${commitSha}" does not pass all github ` +
|
red(` ✘ Cannot stage release. Commit "${commitSha}" does not pass all github ` +
|
||||||
`status checks. Please make sure this commit passes all checks before re-running.`));
|
'status checks. Please make sure this commit passes all checks before re-running.'));
|
||||||
error(` Please have a look at: ${branchCommitsUrl}`);
|
error(` Please have a look at: ${branchCommitsUrl}`);
|
||||||
|
|
||||||
if (await promptConfirm('Do you want to ignore the Github status and proceed?')) {
|
if (await promptConfirm('Do you want to ignore the Github status and proceed?')) {
|
||||||
info(yellow(
|
info(yellow(
|
||||||
` ⚠ Upstream commit is failing CI checks, but status has been ` +
|
' ⚠ Upstream commit is failing CI checks, but status has been forcibly ignored.'));
|
||||||
`forcibly ignored.`));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new UserAbortedReleaseActionError();
|
throw new UserAbortedReleaseActionError();
|
||||||
} else if (state === 'pending') {
|
} else if (state === 'pending') {
|
||||||
error(
|
error(
|
||||||
red(` ✘ Commit "${commitSha}" still has pending github statuses that ` +
|
red(` ✘ Commit "${commitSha}" still has pending github statuses that ` +
|
||||||
`need to succeed before staging a release.`));
|
'need to succeed before staging a release.'));
|
||||||
error(red(` Please have a look at: ${branchCommitsUrl}`));
|
error(red(` Please have a look at: ${branchCommitsUrl}`));
|
||||||
if (await promptConfirm('Do you want to ignore the Github status and proceed?')) {
|
if (await promptConfirm('Do you want to ignore the Github status and proceed?')) {
|
||||||
info(yellow(
|
info(yellow(' ⚠ Upstream commit is pending CI, but status has been forcibly ignored.'));
|
||||||
` ⚠ Upstream commit is pending CI, but status has been ` +
|
|
||||||
`forcibly ignored.`));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new UserAbortedReleaseActionError();
|
throw new UserAbortedReleaseActionError();
|
||||||
|
@ -157,9 +154,9 @@ export abstract class ReleaseAction {
|
||||||
*/
|
*/
|
||||||
protected async waitForEditsAndCreateReleaseCommit(newVersion: semver.SemVer) {
|
protected async waitForEditsAndCreateReleaseCommit(newVersion: semver.SemVer) {
|
||||||
info(yellow(
|
info(yellow(
|
||||||
` ⚠ Please review the changelog and ensure that the log contains only changes ` +
|
' ⚠ Please review the changelog and ensure that the log contains only changes ' +
|
||||||
`that apply to the public API surface. Manual changes can be made. When done, please ` +
|
'that apply to the public API surface. Manual changes can be made. When done, please ' +
|
||||||
`proceed with the prompt below.`));
|
'proceed with the prompt below.'));
|
||||||
|
|
||||||
if (!await promptConfirm('Do you want to proceed and commit the changes?')) {
|
if (!await promptConfirm('Do you want to proceed and commit the changes?')) {
|
||||||
throw new UserAbortedReleaseActionError();
|
throw new UserAbortedReleaseActionError();
|
||||||
|
@ -188,7 +185,7 @@ export abstract class ReleaseAction {
|
||||||
const forks = result.repository.forks.nodes;
|
const forks = result.repository.forks.nodes;
|
||||||
|
|
||||||
if (forks.length === 0) {
|
if (forks.length === 0) {
|
||||||
error(red(` ✘ Unable to find fork for currently authenticated user.`));
|
error(red(' ✘ Unable to find fork for currently authenticated user.'));
|
||||||
error(red(` Please ensure you created a fork of: ${owner}/${name}.`));
|
error(red(` Please ensure you created a fork of: ${owner}/${name}.`));
|
||||||
throw new FatalReleaseActionError();
|
throw new FatalReleaseActionError();
|
||||||
}
|
}
|
||||||
|
@ -304,7 +301,7 @@ export abstract class ReleaseAction {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
debug(`Waiting for pull request #${id} to be merged.`);
|
debug(`Waiting for pull request #${id} to be merged.`);
|
||||||
|
|
||||||
const spinner = Ora().start(`Waiting for pull request #${id} to be merged.`);
|
const spinner = ora().start(`Waiting for pull request #${id} to be merged.`);
|
||||||
const intervalId = setInterval(async () => {
|
const intervalId = setInterval(async () => {
|
||||||
const prState = await getPullRequestState(this.git, id);
|
const prState = await getPullRequestState(this.git, id);
|
||||||
if (prState === 'merged') {
|
if (prState === 'merged') {
|
||||||
|
@ -451,7 +448,7 @@ export abstract class ReleaseAction {
|
||||||
|
|
||||||
info(green(
|
info(green(
|
||||||
` ✓ Pull request for cherry-picking the changelog into "${nextBranch}" ` +
|
` ✓ Pull request for cherry-picking the changelog into "${nextBranch}" ` +
|
||||||
`has been created.`));
|
'has been created.'));
|
||||||
info(yellow(` Please ask team members to review: ${url}.`));
|
info(yellow(` Please ask team members to review: ${url}.`));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -490,7 +487,7 @@ export abstract class ReleaseAction {
|
||||||
|
|
||||||
if (!await this._isCommitForVersionStaging(newVersion, versionBumpCommitSha)) {
|
if (!await this._isCommitForVersionStaging(newVersion, versionBumpCommitSha)) {
|
||||||
error(red(` ✘ Latest commit in "${publishBranch}" branch is not a staging commit.`));
|
error(red(` ✘ Latest commit in "${publishBranch}" branch is not a staging commit.`));
|
||||||
error(red(` Please make sure the staging pull request has been merged.`));
|
error(red(' Please make sure the staging pull request has been merged.'));
|
||||||
throw new FatalReleaseActionError();
|
throw new FatalReleaseActionError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,13 +511,13 @@ export abstract class ReleaseAction {
|
||||||
await this._publishBuiltPackageToNpm(builtPackage, npmDistTag);
|
await this._publishBuiltPackageToNpm(builtPackage, npmDistTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
info(green(` ✓ Published all packages successfully`));
|
info(green(' ✓ Published all packages successfully'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Publishes the given built package to NPM with the specified NPM dist tag. */
|
/** Publishes the given built package to NPM with the specified NPM dist tag. */
|
||||||
private async _publishBuiltPackageToNpm(pkg: BuiltPackage, npmDistTag: string) {
|
private async _publishBuiltPackageToNpm(pkg: BuiltPackage, npmDistTag: string) {
|
||||||
debug(`Starting publish of "${pkg.name}".`);
|
debug(`Starting publish of "${pkg.name}".`);
|
||||||
const spinner = Ora().start(`Publishing "${pkg.name}"`);
|
const spinner = ora().start(`Publishing "${pkg.name}"`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await runNpmPublish(pkg.outputPath, npmDistTag, this.config.publishRegistry);
|
await runNpmPublish(pkg.outputPath, npmDistTag, this.config.publishRegistry);
|
||||||
|
|
|
@ -65,14 +65,14 @@ export class CutLongTermSupportPatchAction extends ReleaseAction {
|
||||||
{
|
{
|
||||||
name: 'activeLtsBranch',
|
name: 'activeLtsBranch',
|
||||||
type: 'list',
|
type: 'list',
|
||||||
message: 'Please select a version for which you want to cut a LTS patch',
|
message: 'Please select a version for which you want to cut an LTS patch',
|
||||||
choices: activeBranchChoices,
|
choices: activeBranchChoices,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'inactiveLtsBranch',
|
name: 'inactiveLtsBranch',
|
||||||
type: 'list',
|
type: 'list',
|
||||||
when: o => o.activeLtsBranch === null,
|
when: o => o.activeLtsBranch === null,
|
||||||
message: 'Please select an inactive LTS version for which you want to cut a LTS patch',
|
message: 'Please select an inactive LTS version for which you want to cut an LTS patch',
|
||||||
choices: inactive.map(branch => this._getChoiceForLtsBranch(branch)),
|
choices: inactive.map(branch => this._getChoiceForLtsBranch(branch)),
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Ora from 'ora';
|
import * as ora from 'ora';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
import {spawnWithDebugOutput} from '../../utils/child-process';
|
import {spawnWithDebugOutput} from '../../utils/child-process';
|
||||||
|
@ -19,11 +19,11 @@ import {FatalReleaseActionError} from './actions-error';
|
||||||
* ###############################################################
|
* ###############################################################
|
||||||
*
|
*
|
||||||
* This file contains helpers for invoking external `ng-dev` commands. A subset of actions,
|
* This file contains helpers for invoking external `ng-dev` commands. A subset of actions,
|
||||||
* like building release output or setting a NPM dist tag for release packages, cannot be
|
* like building release output or setting aν NPM dist tag for release packages, cannot be
|
||||||
* performed directly as part of the release tool and need to be delegated to external `ng-dev`
|
* performed directly as part of the release tool and need to be delegated to external `ng-dev`
|
||||||
* commands that exist across arbitrary version branches.
|
* commands that exist across arbitrary version branches.
|
||||||
*
|
*
|
||||||
* In an concrete example: Consider a new patch version is released and that a new release
|
* In a concrete example: Consider a new patch version is released and that a new release
|
||||||
* package has been added to the `next` branch. The patch branch will not contain the new
|
* package has been added to the `next` branch. The patch branch will not contain the new
|
||||||
* release package, so we could not build the release output for it. To work around this, we
|
* release package, so we could not build the release output for it. To work around this, we
|
||||||
* call the ng-dev build command for the patch version branch and expect it to return a list
|
* call the ng-dev build command for the patch version branch and expect it to return a list
|
||||||
|
@ -44,7 +44,7 @@ export async function invokeSetNpmDistCommand(npmDistTag: string, version: semve
|
||||||
info(green(` ✓ Set "${npmDistTag}" NPM dist tag for all packages to v${version}.`));
|
info(green(` ✓ Set "${npmDistTag}" NPM dist tag for all packages to v${version}.`));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error(e);
|
error(e);
|
||||||
error(red(` ✘ An error occurred while setting the NPM dist tag for ${npmDistTag}.`));
|
error(red(' ✘ An error occurred while setting the NPM dist tag for "${npmDistTag}".'));
|
||||||
throw new FatalReleaseActionError();
|
throw new FatalReleaseActionError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,21 +54,21 @@ export async function invokeSetNpmDistCommand(npmDistTag: string, version: semve
|
||||||
* packages for the currently checked out branch.
|
* packages for the currently checked out branch.
|
||||||
*/
|
*/
|
||||||
export async function invokeReleaseBuildCommand(): Promise<BuiltPackage[]> {
|
export async function invokeReleaseBuildCommand(): Promise<BuiltPackage[]> {
|
||||||
const spinner = Ora().start('Building release output.');
|
const spinner = ora().start('Building release output.');
|
||||||
try {
|
try {
|
||||||
// Since we expect JSON to be printed from the `ng-dev release build` command,
|
// Since we expect JSON to be printed from the `ng-dev release build` command,
|
||||||
// we spawn the process in silent mode. We have set up an Ora progress spinner.
|
// we spawn the process in silent mode. We have set up an Ora progress spinner.
|
||||||
const {stdout} = await spawnWithDebugOutput(
|
const {stdout} = await spawnWithDebugOutput(
|
||||||
'yarn', ['--silent', 'ng-dev', 'release', 'build', '--json'], {mode: 'silent'});
|
'yarn', ['--silent', 'ng-dev', 'release', 'build', '--json'], {mode: 'silent'});
|
||||||
spinner.stop();
|
spinner.stop();
|
||||||
info(green(` ✓ Built release output for all packages.`));
|
info(green(' ✓ Built release output for all packages.'));
|
||||||
// The `ng-dev release build` command prints a JSON array to stdout
|
// The `ng-dev release build` command prints a JSON array to stdout
|
||||||
// that represents the built release packages and their output paths.
|
// that represents the built release packages and their output paths.
|
||||||
return JSON.parse(stdout.trim());
|
return JSON.parse(stdout.trim());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
spinner.stop();
|
spinner.stop();
|
||||||
error(e);
|
error(e);
|
||||||
error(red(` ✘ An error occurred while building the release packages.`));
|
error(red(' ✘ An error occurred while building the release packages.'));
|
||||||
throw new FatalReleaseActionError();
|
throw new FatalReleaseActionError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,10 +83,10 @@ export async function invokeYarnInstallCommand(projectDir: string): Promise<void
|
||||||
// TODO: Consider using an Ora spinner instead to ensure minimal console output.
|
// TODO: Consider using an Ora spinner instead to ensure minimal console output.
|
||||||
await spawnWithDebugOutput(
|
await spawnWithDebugOutput(
|
||||||
'yarn', ['install', '--frozen-lockfile', '--non-interactive'], {cwd: projectDir});
|
'yarn', ['install', '--frozen-lockfile', '--non-interactive'], {cwd: projectDir});
|
||||||
info(green(` ✓ Installed project dependencies.`));
|
info(green(' ✓ Installed project dependencies.'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error(e);
|
error(e);
|
||||||
error(red(` ✘ An error occurred while installing dependencies.`));
|
error(red(' ✘ An error occurred while installing dependencies.'));
|
||||||
throw new FatalReleaseActionError();
|
throw new FatalReleaseActionError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,8 +66,7 @@ export class ReleaseTool {
|
||||||
// Only print the error message and stack if the error is not a known fatal release
|
// Only print the error message and stack if the error is not a known fatal release
|
||||||
// action error (for which we print the error gracefully to the console with colors).
|
// action error (for which we print the error gracefully to the console with colors).
|
||||||
if (!(e instanceof FatalReleaseActionError) && e instanceof Error) {
|
if (!(e instanceof FatalReleaseActionError) && e instanceof Error) {
|
||||||
console.error(e.message);
|
console.error(e);
|
||||||
console.error(e.stack);
|
|
||||||
}
|
}
|
||||||
return CompletionState.FATAL_ERROR;
|
return CompletionState.FATAL_ERROR;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -90,7 +89,7 @@ export class ReleaseTool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info(`Please select the type of release you want to perform.`);
|
info('Please select the type of release you want to perform.');
|
||||||
|
|
||||||
const {releaseAction} = await prompt<{releaseAction: ReleaseAction}>({
|
const {releaseAction} = await prompt<{releaseAction: ReleaseAction}>({
|
||||||
name: 'releaseAction',
|
name: 'releaseAction',
|
||||||
|
@ -108,9 +107,7 @@ export class ReleaseTool {
|
||||||
*/
|
*/
|
||||||
private async _verifyNoUncommittedChanges(): Promise<boolean> {
|
private async _verifyNoUncommittedChanges(): Promise<boolean> {
|
||||||
if (this._git.hasUncommittedChanges()) {
|
if (this._git.hasUncommittedChanges()) {
|
||||||
error(
|
error(red(' ✘ There are changes which are not committed and should be discarded.'));
|
||||||
red(` ✘ There are changes which are not committed and should be ` +
|
|
||||||
`discarded.`));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -126,7 +123,7 @@ export class ReleaseTool {
|
||||||
await this._git.github.repos.getBranch({...this._git.remoteParams, branch: nextBranchName});
|
await this._git.github.repos.getBranch({...this._git.remoteParams, branch: nextBranchName});
|
||||||
|
|
||||||
if (headSha !== data.commit.sha) {
|
if (headSha !== data.commit.sha) {
|
||||||
error(red(` ✘ Running release tool from an outdated local branch.`));
|
error(red(' ✘ Running release tool from an outdated local branch.'));
|
||||||
error(red(` Please make sure you are running from the "${nextBranchName}" branch.`));
|
error(red(` Please make sure you are running from the "${nextBranchName}" branch.`));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {CutLongTermSupportPatchAction} from '../actions/cut-lts-patch';
|
||||||
|
|
||||||
import {expectStagingAndPublishWithCherryPick, fakeNpmPackageQueryRequest, getTestingMocksForReleaseAction, parse, setupReleaseActionForTesting, testTmpDir} from './test-utils';
|
import {expectStagingAndPublishWithCherryPick, fakeNpmPackageQueryRequest, getTestingMocksForReleaseAction, parse, setupReleaseActionForTesting, testTmpDir} from './test-utils';
|
||||||
|
|
||||||
describe('cut a LTS patch action', () => {
|
describe('cut an LTS patch action', () => {
|
||||||
it('should be active', async () => {
|
it('should be active', async () => {
|
||||||
expect(await CutLongTermSupportPatchAction.isActive({
|
expect(await CutLongTermSupportPatchAction.isActive({
|
||||||
releaseCandidate: null,
|
releaseCandidate: null,
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Ora from 'ora';
|
import * as ora from 'ora';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import {Arguments, Argv, CommandModule} from 'yargs';
|
import {Arguments, Argv, CommandModule} from 'yargs';
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import {getReleaseConfig} from '../config/index';
|
||||||
import {setNpmTagForPackage} from '../versioning/npm-publish';
|
import {setNpmTagForPackage} from '../versioning/npm-publish';
|
||||||
|
|
||||||
|
|
||||||
/** Command line options for setting a NPM dist tag. */
|
/** Command line options for setting an NPM dist tag. */
|
||||||
export interface ReleaseSetDistTagOptions {
|
export interface ReleaseSetDistTagOptions {
|
||||||
tagName: string;
|
tagName: string;
|
||||||
targetVersion: string;
|
targetVersion: string;
|
||||||
|
@ -42,11 +42,11 @@ async function handler(args: Arguments<ReleaseSetDistTagOptions>) {
|
||||||
const version = semver.parse(rawVersion);
|
const version = semver.parse(rawVersion);
|
||||||
|
|
||||||
if (version === null) {
|
if (version === null) {
|
||||||
error(red(`Invalid version specified. Unable to set NPM dist tag.`));
|
error(red(`Invalid version specified (${rawVersion}). Unable to set NPM dist tag.`));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const spinner = Ora().start();
|
const spinner = ora().start();
|
||||||
debug(`Setting "${tagName}" NPM dist tag for release packages to v${version}.`);
|
debug(`Setting "${tagName}" NPM dist tag for release packages to v${version}.`);
|
||||||
|
|
||||||
for (const pkgName of npmPackages) {
|
for (const pkgName of npmPackages) {
|
||||||
|
@ -69,7 +69,7 @@ async function handler(args: Arguments<ReleaseSetDistTagOptions>) {
|
||||||
info(green(` ${bold(tagName)} will now point to ${bold(`v${version}`)}.`));
|
info(green(` ${bold(tagName)} will now point to ${bold(`v${version}`)}.`));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** CLI command module for setting a NPM dist tag. */
|
/** CLI command module for setting an NPM dist tag. */
|
||||||
export const ReleaseSetDistTagCommand: CommandModule<{}, ReleaseSetDistTagOptions> = {
|
export const ReleaseSetDistTagCommand: CommandModule<{}, ReleaseSetDistTagOptions> = {
|
||||||
builder,
|
builder,
|
||||||
handler,
|
handler,
|
||||||
|
|
|
@ -66,7 +66,7 @@ describe('ng-dev release set-dist-tag', () => {
|
||||||
await invokeCommand('latest', '10.0');
|
await invokeCommand('latest', '10.0');
|
||||||
|
|
||||||
expect(console.error)
|
expect(console.error)
|
||||||
.toHaveBeenCalledWith('Invalid version specified. Unable to set NPM dist tag.');
|
.toHaveBeenCalledWith('Invalid version specified (10.0). Unable to set NPM dist tag.');
|
||||||
expect(process.exit).toHaveBeenCalledWith(1);
|
expect(process.exit).toHaveBeenCalledWith(1);
|
||||||
expect(process.exit).toHaveBeenCalledTimes(1);
|
expect(process.exit).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
## Versioning for the Angular organization
|
## Versioning for the Angular organization
|
||||||
|
|
||||||
The folder contains common tooling needed for implementing the versioning as proposed
|
The folder contains common tooling needed for implementing the versioning as proposed
|
||||||
by [this design document](https://docs.google.com/document/d/197kVillDwx-RZtSVOBtPb4BBIAw0E9RT3q3v6DZkykU/edit#heading=h.s3qlps8f4zq7).
|
by [this design document](https://docs.google.com/document/d/197kVillDwx-RZtSVOBtPb4BBIAw0E9RT3q3v6DZkykU).
|
||||||
Primary tooling is the determination of _active_ release trains.
|
Primary tooling is the determination of _active_ release trains.
|
||||||
|
|
|
@ -17,7 +17,7 @@ export interface ActiveReleaseTrains {
|
||||||
releaseCandidate: ReleaseTrain|null;
|
releaseCandidate: ReleaseTrain|null;
|
||||||
/** Release-train currently in the "latest" phase. */
|
/** Release-train currently in the "latest" phase. */
|
||||||
latest: ReleaseTrain;
|
latest: ReleaseTrain;
|
||||||
/** Release-train in the `next` phase */
|
/** Release-train in the `next` phase. */
|
||||||
next: ReleaseTrain;
|
next: ReleaseTrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,16 +34,16 @@ export interface LtsBranch {
|
||||||
* Number of months a major version in Angular is actively supported. See:
|
* Number of months a major version in Angular is actively supported. See:
|
||||||
* https://angular.io/guide/releases#support-policy-and-schedule.
|
* https://angular.io/guide/releases#support-policy-and-schedule.
|
||||||
*/
|
*/
|
||||||
export const majorActiveSupportDuration = 6;
|
const majorActiveSupportDuration = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of months a major version has active long-term support. See:
|
* Number of months a major version has active long-term support. See:
|
||||||
* https://angular.io/guide/releases#support-policy-and-schedule.
|
* https://angular.io/guide/releases#support-policy-and-schedule.
|
||||||
*/
|
*/
|
||||||
export const majorActiveTermSupportDuration = 12;
|
const majorLongTermSupportDuration = 12;
|
||||||
|
|
||||||
/** Regular expression that matches LTS NPM dist tags. */
|
/** Regular expression that matches LTS NPM dist tags. */
|
||||||
export const ltsNpmDistTagRegex = /^v(\d+)-lts$/;
|
const ltsNpmDistTagRegex = /^v(\d+)-lts$/;
|
||||||
|
|
||||||
/** Finds all long-term support release trains from the specified NPM package. */
|
/** Finds all long-term support release trains from the specified NPM package. */
|
||||||
export async function fetchLongTermSupportBranchesFromNpm(config: ReleaseConfig):
|
export async function fetchLongTermSupportBranchesFromNpm(config: ReleaseConfig):
|
||||||
|
@ -54,7 +54,7 @@ export async function fetchLongTermSupportBranchesFromNpm(config: ReleaseConfig)
|
||||||
const inactive: LtsBranch[] = [];
|
const inactive: LtsBranch[] = [];
|
||||||
|
|
||||||
// Iterate through the NPM package information and determine active/inactive LTS versions with
|
// Iterate through the NPM package information and determine active/inactive LTS versions with
|
||||||
// their corresponding branches. We assume that a LTS tagged version in NPM belongs to the
|
// their corresponding branches. We assume that an LTS tagged version in NPM belongs to the
|
||||||
// last-minor branch of a given major (i.e. we assume there are no outdated LTS NPM dist tags).
|
// last-minor branch of a given major (i.e. we assume there are no outdated LTS NPM dist tags).
|
||||||
for (const npmDistTag in distTags) {
|
for (const npmDistTag in distTags) {
|
||||||
if (ltsNpmDistTagRegex.test(npmDistTag)) {
|
if (ltsNpmDistTagRegex.test(npmDistTag)) {
|
||||||
|
@ -64,7 +64,7 @@ export async function fetchLongTermSupportBranchesFromNpm(config: ReleaseConfig)
|
||||||
const ltsEndDate = computeLtsEndDateOfMajor(majorReleaseDate);
|
const ltsEndDate = computeLtsEndDateOfMajor(majorReleaseDate);
|
||||||
const ltsBranch: LtsBranch = {name: branchName, version, npmDistTag};
|
const ltsBranch: LtsBranch = {name: branchName, version, npmDistTag};
|
||||||
// Depending on whether the LTS phase is still active, add the branch
|
// Depending on whether the LTS phase is still active, add the branch
|
||||||
// the list of active or inactive LTS branches.
|
// to the list of active or inactive LTS branches.
|
||||||
if (today <= ltsEndDate) {
|
if (today <= ltsEndDate) {
|
||||||
active.push(ltsBranch);
|
active.push(ltsBranch);
|
||||||
} else {
|
} else {
|
||||||
|
@ -87,7 +87,7 @@ export async function fetchLongTermSupportBranchesFromNpm(config: ReleaseConfig)
|
||||||
export function computeLtsEndDateOfMajor(majorReleaseDate: Date): Date {
|
export function computeLtsEndDateOfMajor(majorReleaseDate: Date): Date {
|
||||||
return new Date(
|
return new Date(
|
||||||
majorReleaseDate.getFullYear(),
|
majorReleaseDate.getFullYear(),
|
||||||
majorReleaseDate.getMonth() + majorActiveSupportDuration + majorActiveTermSupportDuration,
|
majorReleaseDate.getMonth() + majorActiveSupportDuration + majorLongTermSupportDuration,
|
||||||
majorReleaseDate.getDate(), majorReleaseDate.getHours(), majorReleaseDate.getMinutes(),
|
majorReleaseDate.getDate(), majorReleaseDate.getHours(), majorReleaseDate.getMinutes(),
|
||||||
majorReleaseDate.getSeconds(), majorReleaseDate.getMilliseconds());
|
majorReleaseDate.getSeconds(), majorReleaseDate.getMilliseconds());
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,10 +57,9 @@ function getRepresentativeNpmPackage(config: ReleaseConfig) {
|
||||||
|
|
||||||
/** Fetches the specified NPM package from the NPM registry. */
|
/** Fetches the specified NPM package from the NPM registry. */
|
||||||
async function fetchPackageInfoFromNpmRegistry(pkgName: string): Promise<NpmPackageInfo> {
|
async function fetchPackageInfoFromNpmRegistry(pkgName: string): Promise<NpmPackageInfo> {
|
||||||
if (_npmPackageInfoCache[pkgName] !== undefined) {
|
if (_npmPackageInfoCache[pkgName] === undefined) {
|
||||||
return await _npmPackageInfoCache[pkgName];
|
_npmPackageInfoCache[pkgName] =
|
||||||
|
fetch(`https://registry.npmjs.org/${pkgName}`).then(r => r.json());
|
||||||
}
|
}
|
||||||
const result = _npmPackageInfoCache[pkgName] =
|
return await _npmPackageInfoCache[pkgName];
|
||||||
fetch(`https://registry.npmjs.org/${pkgName}`).then(r => r.json());
|
|
||||||
return await result;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,14 +59,14 @@ export async function printActiveReleaseTrains(
|
||||||
`published yet.`);
|
`published yet.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no release-train in release-candidate of feature-freeze phase is active,
|
// If no release-train in release-candidate or feature-freeze phase is active,
|
||||||
// we print a message as last bullet point to make this clear.
|
// we print a message as last bullet point to make this clear.
|
||||||
if (releaseCandidate === null) {
|
if (releaseCandidate === null) {
|
||||||
info(` • No release-candidate or feature-freeze branch currently active.`);
|
info(' • No release-candidate or feature-freeze branch currently active.');
|
||||||
}
|
}
|
||||||
|
|
||||||
info();
|
info();
|
||||||
info(blue(`Current active LTS version branches:`));
|
info(blue('Current active LTS version branches:'));
|
||||||
|
|
||||||
// Print all active LTS branches (each branch as own bullet point).
|
// Print all active LTS branches (each branch as own bullet point).
|
||||||
if (ltsBranches.active.length !== 0) {
|
if (ltsBranches.active.length !== 0) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ export interface VersionBranch {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Regular expression that matches version-branches. */
|
/** Regular expression that matches version-branches. */
|
||||||
const versionBranchNameRegex = /(\d+)\.(\d+)\.x/;
|
const versionBranchNameRegex = /^(\d+)\.(\d+)\.x$/;
|
||||||
|
|
||||||
/** Gets the version of a given branch by reading the `package.json` upstream. */
|
/** Gets the version of a given branch by reading the `package.json` upstream. */
|
||||||
export async function getVersionOfBranch(
|
export async function getVersionOfBranch(
|
||||||
|
@ -56,8 +56,6 @@ export function isVersionBranch(branchName: string): boolean {
|
||||||
* relevant but needed for parsing. SemVer does not allow `x` as patch digit.
|
* relevant but needed for parsing. SemVer does not allow `x` as patch digit.
|
||||||
*/
|
*/
|
||||||
export function getVersionForVersionBranch(branchName: string): semver.SemVer|null {
|
export function getVersionForVersionBranch(branchName: string): semver.SemVer|null {
|
||||||
// Convert a given version-branch into a SemVer version that can be used
|
|
||||||
// with the SemVer utilities. i.e. to determine semantic order.
|
|
||||||
return semver.parse(branchName.replace(versionBranchNameRegex, '$1.$2.0'));
|
return semver.parse(branchName.replace(versionBranchNameRegex, '$1.$2.0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ export function spawnWithDebugOutput(
|
||||||
childProcess.stderr.on('data', message => {
|
childProcess.stderr.on('data', message => {
|
||||||
logOutput += message;
|
logOutput += message;
|
||||||
// If console output is enabled, print the message directly to the stderr. Note that
|
// If console output is enabled, print the message directly to the stderr. Note that
|
||||||
// we intentionally print all output to stderr as stderr should not be polluted.
|
// we intentionally print all output to stderr as stdout should not be polluted.
|
||||||
if (outputMode === undefined || outputMode === 'enabled') {
|
if (outputMode === undefined || outputMode === 'enabled') {
|
||||||
process.stderr.write(message);
|
process.stderr.write(message);
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ export function spawnWithDebugOutput(
|
||||||
stdout += message;
|
stdout += message;
|
||||||
logOutput += message;
|
logOutput += message;
|
||||||
// If console output is enabled, print the message directly to the stderr. Note that
|
// If console output is enabled, print the message directly to the stderr. Note that
|
||||||
// we intentionally print all output to stderr as stderr should not be polluted.
|
// we intentionally print all output to stderr as stdout should not be polluted.
|
||||||
if (outputMode === undefined || outputMode === 'enabled') {
|
if (outputMode === undefined || outputMode === 'enabled') {
|
||||||
process.stderr.write(message);
|
process.stderr.write(message);
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ export function spawnWithDebugOutput(
|
||||||
const exitDescription = status !== null ? `exit code "${status}"` : `signal "${signal}"`;
|
const exitDescription = status !== null ? `exit code "${status}"` : `signal "${signal}"`;
|
||||||
const printFn = outputMode === 'on-error' ? error : debug;
|
const printFn = outputMode === 'on-error' ? error : debug;
|
||||||
|
|
||||||
printFn(`Command ${commandText} completed with ${exitDescription}.`);
|
printFn(`Command "${commandText}" completed with ${exitDescription}.`);
|
||||||
printFn(`Process output: \n${logOutput}`);
|
printFn(`Process output: \n${logOutput}`);
|
||||||
|
|
||||||
// On success, resolve the promise. Otherwise reject with the captured stderr
|
// On success, resolve the promise. Otherwise reject with the captured stderr
|
||||||
|
|
|
@ -12,16 +12,16 @@ import {GithubConfig} from '../config';
|
||||||
import {GitClient} from './index';
|
import {GitClient} from './index';
|
||||||
|
|
||||||
/** URL to the Github page where personal access tokens can be managed. */
|
/** URL to the Github page where personal access tokens can be managed. */
|
||||||
export const GITHUB_TOKEN_SETTINGS_URL = `https://github.com/settings/tokens`;
|
export const GITHUB_TOKEN_SETTINGS_URL = 'https://github.com/settings/tokens';
|
||||||
|
|
||||||
/** URL to the Github page where personal access tokens can be generated. */
|
/** URL to the Github page where personal access tokens can be generated. */
|
||||||
export const GITHUB_TOKEN_GENERATE_URL = `https://github.com/settings/tokens/new`;
|
export const GITHUB_TOKEN_GENERATE_URL = 'https://github.com/settings/tokens/new';
|
||||||
|
|
||||||
/** Adds the provided token to the given Github HTTPs remote url. */
|
/** Adds the provided token to the given Github HTTPs remote url. */
|
||||||
export function addTokenToGitHttpsUrl(githubHttpsUrl: string, token: string) {
|
export function addTokenToGitHttpsUrl(githubHttpsUrl: string, token: string) {
|
||||||
const url = new URL(githubHttpsUrl);
|
const url = new URL(githubHttpsUrl);
|
||||||
url.username = token;
|
url.username = token;
|
||||||
return url.toString();
|
return url.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the repository Git URL for the given github config. */
|
/** Gets the repository Git URL for the given github config. */
|
||||||
|
@ -36,7 +36,7 @@ export function getRepositoryGitUrl(config: GithubConfig, githubToken?: string):
|
||||||
return baseHttpUrl;
|
return baseHttpUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets a Github URL that refers to a lists of recent commits within a specified branch. */
|
/** Gets a Github URL that refers to a list of recent commits within a specified branch. */
|
||||||
export function getListCommitsInBranchUrl({remoteParams}: GitClient, branchName: string) {
|
export function getListCommitsInBranchUrl({remoteParams}: GitClient, branchName: string) {
|
||||||
return `https://github.com/${remoteParams.owner}/${remoteParams.repo}/commits/${branchName}`;
|
return `https://github.com/${remoteParams.owner}/${remoteParams.repo}/commits/${branchName}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,8 +88,8 @@ export class GitClient {
|
||||||
*/
|
*/
|
||||||
runGraceful(args: string[], options: SpawnSyncOptions = {}): SpawnSyncReturns<string> {
|
runGraceful(args: string[], options: SpawnSyncOptions = {}): SpawnSyncReturns<string> {
|
||||||
// To improve the debugging experience in case something fails, we print all executed Git
|
// To improve the debugging experience in case something fails, we print all executed Git
|
||||||
// commands unless the `stdio` is explicitly to `ignore` (which is equivalent to silent).
|
// commands unless the `stdio` is explicitly set to `ignore` (which is equivalent to silent).
|
||||||
// Note that we do not want to print the token if is contained in the command. It's common
|
// Note that we do not want to print the token if it is contained in the command. It's common
|
||||||
// to share errors with others if the tool failed, and we do not want to leak tokens.
|
// to share errors with others if the tool failed, and we do not want to leak tokens.
|
||||||
// TODO: Add support for configuring this on a per-client basis. Some tools do not want
|
// TODO: Add support for configuring this on a per-client basis. Some tools do not want
|
||||||
// to print the Git command messages to the console at all (e.g. to maintain clean output).
|
// to print the Git command messages to the console at all (e.g. to maintain clean output).
|
||||||
|
@ -202,7 +202,7 @@ export class GitClient {
|
||||||
/**
|
/**
|
||||||
* Retrieve the OAuth scopes for the loaded Github token.
|
* Retrieve the OAuth scopes for the loaded Github token.
|
||||||
**/
|
**/
|
||||||
private async getAuthScopesForToken() {
|
private getAuthScopesForToken() {
|
||||||
// If the OAuth scopes have already been loaded, return the Promise containing them.
|
// If the OAuth scopes have already been loaded, return the Promise containing them.
|
||||||
if (this._cachedOauthScopes !== null) {
|
if (this._cachedOauthScopes !== null) {
|
||||||
return this._cachedOauthScopes;
|
return this._cachedOauthScopes;
|
||||||
|
|
|
@ -6,6 +6,6 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from './semver-matchers';
|
||||||
export * from './virtual-git-client';
|
export * from './virtual-git-client';
|
||||||
export * from './virtual-git-matchers';
|
export * from './virtual-git-matchers';
|
||||||
export * from './semver-matchers';
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ export class VirtualGitClient extends GitClient {
|
||||||
this.branches[ref.destination] = {
|
this.branches[ref.destination] = {
|
||||||
branch: ref.destination,
|
branch: ref.destination,
|
||||||
ref: this.fetchHeadRef,
|
ref: this.fetchHeadRef,
|
||||||
newCommits: []
|
newCommits: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ export class VirtualGitClient extends GitClient {
|
||||||
}
|
}
|
||||||
this.head = {ref: this.fetchHeadRef, newCommits: []};
|
this.head = {ref: this.fetchHeadRef, newCommits: []};
|
||||||
} else if (this.branches[target]) {
|
} else if (this.branches[target]) {
|
||||||
this.head = {...this._cloneHead(this.branches[target], detached)};
|
this.head = this._cloneHead(this.branches[target], detached);
|
||||||
} else if (createBranch) {
|
} else if (createBranch) {
|
||||||
this.head = this.branches[target] = {branch: target, ...this._cloneHead(this.head, detached)};
|
this.head = this.branches[target] = {branch: target, ...this._cloneHead(this.head, detached)};
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue