121 lines
5.6 KiB
TypeScript
121 lines
5.6 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 {green, info, yellow} from '../../../utils/console';
|
|
import {semverInc} from '../../../utils/semver';
|
|
import {ReleaseNotes} from '../../notes/release-notes';
|
|
import {ActiveReleaseTrains} from '../../versioning/active-release-trains';
|
|
import {computeNewPrereleaseVersionForNext} from '../../versioning/next-prerelease-version';
|
|
import {ReleaseAction} from '../actions';
|
|
import {getCommitMessageForExceptionalNextVersionBump, getReleaseNoteCherryPickCommitMessage} from '../commit-message';
|
|
import {changelogPath, packageJsonPath} from '../constants';
|
|
|
|
/**
|
|
* Base action that can be used to move the next release-train into the feature-freeze or
|
|
* release-candidate phase. This means that a new version branch is created from the next
|
|
* branch, and a new pre-release (either RC or another `next`) is cut indicating the new phase.
|
|
*/
|
|
export abstract class BranchOffNextBranchBaseAction extends ReleaseAction {
|
|
/**
|
|
* Phase which the release-train currently in the `next` phase will move into.
|
|
*
|
|
* Note that we only allow for a next version to branch into feature-freeze or
|
|
* directly into the release-candidate phase. A stable version cannot be released
|
|
* without release-candidate.
|
|
*/
|
|
abstract newPhaseName: 'feature-freeze'|'release-candidate';
|
|
|
|
override async getDescription() {
|
|
const {branchName} = this.active.next;
|
|
const newVersion = await this._computeNewVersion();
|
|
return `Move the "${branchName}" branch into ${this.newPhaseName} phase (v${newVersion}).`;
|
|
}
|
|
|
|
override async perform() {
|
|
const newVersion = await this._computeNewVersion();
|
|
const newBranch = `${newVersion.major}.${newVersion.minor}.x`;
|
|
|
|
// Branch-off the next branch into a new version 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 {pullRequest, releaseNotes} =
|
|
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(pullRequest);
|
|
await this.buildAndPublish(releaseNotes, newBranch, 'next');
|
|
await this._createNextBranchUpdatePullRequest(releaseNotes, newVersion);
|
|
}
|
|
|
|
/** Computes the new version for the release-train being branched-off. */
|
|
private async _computeNewVersion() {
|
|
if (this.newPhaseName === 'feature-freeze') {
|
|
return computeNewPrereleaseVersionForNext(this.active, this.config);
|
|
} else {
|
|
return semverInc(this.active.next.version, 'prerelease', 'rc');
|
|
}
|
|
}
|
|
|
|
/** 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 release-candidate
|
|
* or feature-freeze version.
|
|
*/
|
|
private async _createNextBranchUpdatePullRequest(
|
|
releaseNotes: ReleaseNotes, newVersion: semver.SemVer) {
|
|
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]);
|
|
|
|
await this.prependReleaseNotesToChangelog(releaseNotes);
|
|
|
|
const commitMessage = getReleaseNoteCherryPickCommitMessage(releaseNotes.version);
|
|
|
|
await this.createCommit(commitMessage, [changelogPath]);
|
|
|
|
let nextPullRequestMessage = `The previous "next" release-train has moved into the ` +
|
|
`${this.newPhaseName} phase. This PR updates the next branch to the subsequent ` +
|
|
`release-train.\n\nAlso this PR cherry-picks the changelog for ` +
|
|
`v${newVersion} into the ${nextBranch} branch so that the changelog is up to date.`;
|
|
|
|
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}.`));
|
|
}
|
|
}
|