f96dcc5ce0
Creates a tool for staging and publishing releases as per the new branching and versioning that has been outlined in the following document. The tool is intended to be used across the organization to ensure consistent branching/versioning and labeling: https://docs.google.com/document/d/197kVillDwx-RZtSVOBtPb4BBIAw0E9RT3q3v6DZkykU/edit#heading=h.s3qlps8f4zq7dd The tool implements the actions as outlined in the following initial plan: https://hackmd.io/2Le8leq0S6G_R5VEVTNK9A. The implementation slightly diverged in so far that it performs staging and publishing together so that releasing is a single convenient command. In case of errors for which re-running the full command is not sufficient, we want to consider adding recover functionality. e.g. when the staging completed, but the actual NPM publishing aborted unexpectedly due to build errors. PR Close #38656
110 lines
5.2 KiB
TypeScript
110 lines
5.2 KiB
TypeScript
/**
|
|
* @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 * as semver from 'semver';
|
|
|
|
import {error, green, info, yellow} from '../../../utils/console';
|
|
import {ActiveReleaseTrains} from '../../versioning/active-release-trains';
|
|
import {computeNewPrereleaseVersionForNext} from '../../versioning/next-prerelease-version';
|
|
import {ReleaseAction} from '../actions';
|
|
import {getCommitMessageForExceptionalNextVersionBump} from '../commit-message';
|
|
import {packageJsonPath} from '../constants';
|
|
|
|
/**
|
|
* Release action that moves the next release-train into the feature-freeze phase. This means
|
|
* that a new version branch is created from the next branch, and a new next pre-release is
|
|
* cut indicating the started feature-freeze.
|
|
*/
|
|
export class MoveNextIntoFeatureFreezeAction extends ReleaseAction {
|
|
private _newVersion = computeNewPrereleaseVersionForNext(this.active, this.config);
|
|
|
|
async getDescription() {
|
|
const {branchName} = this.active.next;
|
|
const newVersion = await this._newVersion;
|
|
return `Move the "${branchName}" branch into feature-freeze phase (v${newVersion}).`;
|
|
}
|
|
|
|
async perform() {
|
|
const newVersion = await this._newVersion;
|
|
const newBranch = `${newVersion.major}.${newVersion.minor}.x`;
|
|
|
|
// Branch-off the next branch into a feature-freeze branch.
|
|
await this._createNewVersionBranchFromNext(newBranch);
|
|
|
|
// Stage the new version for the newly created branch, and push changes to a
|
|
// fork in order to create a staging pull request. Note that we re-use the newly
|
|
// created branch instead of re-fetching from the upstream.
|
|
const stagingPullRequest =
|
|
await this.stageVersionForBranchAndCreatePullRequest(newVersion, newBranch);
|
|
|
|
// Wait for the staging PR to be merged. Then build and publish the feature-freeze next
|
|
// pre-release. Finally, cherry-pick the release notes into the next branch in combination
|
|
// with bumping the version to the next minor too.
|
|
await this.waitForPullRequestToBeMerged(stagingPullRequest.id);
|
|
await this.buildAndPublish(newVersion, newBranch, 'next');
|
|
await this._createNextBranchUpdatePullRequest(newVersion, newBranch);
|
|
}
|
|
|
|
/** Creates a new version branch from the next branch. */
|
|
private async _createNewVersionBranchFromNext(newBranch: string) {
|
|
const {branchName: nextBranch} = this.active.next;
|
|
await this.verifyPassingGithubStatus(nextBranch);
|
|
await this.checkoutUpstreamBranch(nextBranch);
|
|
await this.createLocalBranchFromHead(newBranch);
|
|
await this.pushHeadToRemoteBranch(newBranch);
|
|
info(green(` ✓ Version branch "${newBranch}" created.`));
|
|
}
|
|
|
|
/**
|
|
* Creates a pull request for the next branch that bumps the version to the next
|
|
* minor, and cherry-picks the changelog for the newly branched-off feature-freeze version.
|
|
*/
|
|
private async _createNextBranchUpdatePullRequest(newVersion: semver.SemVer, newBranch: string) {
|
|
const {branchName: nextBranch, version} = this.active.next;
|
|
// We increase the version for the next branch to the next minor. The team can decide
|
|
// later if they want next to be a major through the `Configure Next as Major` release action.
|
|
const newNextVersion = semver.parse(`${version.major}.${version.minor + 1}.0-next.0`)!;
|
|
const bumpCommitMessage = getCommitMessageForExceptionalNextVersionBump(newNextVersion);
|
|
|
|
await this.checkoutUpstreamBranch(nextBranch);
|
|
await this.updateProjectVersion(newNextVersion);
|
|
|
|
// Create an individual commit for the next version bump. The changelog should go into
|
|
// a separate commit that makes it clear where the changelog is cherry-picked from.
|
|
await this.createCommit(bumpCommitMessage, [packageJsonPath]);
|
|
|
|
let nextPullRequestMessage = `The previous "next" release-train has moved into the ` +
|
|
`release-candidate phase. This PR updates the next branch to the subsequent ` +
|
|
`release-train.`;
|
|
const hasChangelogCherryPicked =
|
|
await this.createCherryPickReleaseNotesCommitFrom(newVersion, newBranch);
|
|
|
|
if (hasChangelogCherryPicked) {
|
|
nextPullRequestMessage += `\n\nAlso this PR cherry-picks the changelog for ` +
|
|
`v${newVersion} into the ${nextBranch} branch so that the changelog is up to date.`;
|
|
} else {
|
|
error(yellow(` ✘ Could not cherry-pick release notes for v${newVersion}.`));
|
|
error(yellow(` Please copy the release note manually into "${nextBranch}".`));
|
|
}
|
|
|
|
const nextUpdatePullRequest = await this.pushChangesToForkAndCreatePullRequest(
|
|
nextBranch, `next-release-train-${newNextVersion}`,
|
|
`Update next branch to reflect new release-train "v${newNextVersion}".`,
|
|
nextPullRequestMessage);
|
|
|
|
info(green(` ✓ Pull request for updating the "${nextBranch}" branch has been created.`));
|
|
info(yellow(` Please ask team members to review: ${nextUpdatePullRequest.url}.`));
|
|
}
|
|
|
|
static async isActive(active: ActiveReleaseTrains) {
|
|
// A new feature-freeze/release-candidate branch can only be created if there
|
|
// is no active release-train in feature-freeze/release-candidate phase.
|
|
return active.releaseCandidate === null;
|
|
}
|
|
}
|