feat(dev-infra): introduce release action for directly branching-off into RC (#42973)
Introduces a new release action for cutting a release-action by directly moving the next release-train into the `release-candidate` phase. This allows the Angular team to release minor versions without needing to branch-off first into the feature-freeze phase. For minors this phase can be skipped. Switching into the feature-freeze phase beforehand as a workaround would have allowed for branching-off but has the downside that `target: minor` would no longer point to the branched-off release train (only `target: rc` would work then). PR Close #42973
This commit is contained in:
parent
0d81b007e4
commit
047994b048
|
@ -7094,7 +7094,7 @@ class CutNextPrereleaseAction extends ReleaseAction {
|
||||||
* Cuts the first release candidate for a release-train currently in the
|
* Cuts the first release candidate for a release-train currently in the
|
||||||
* feature-freeze phase. The version is bumped from `next` to `rc.0`.
|
* feature-freeze phase. The version is bumped from `next` to `rc.0`.
|
||||||
*/
|
*/
|
||||||
class CutReleaseCandidateAction extends ReleaseAction {
|
class CutReleaseCandidateForFeatureFreezeAction extends ReleaseAction {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this._newVersion = semverInc(this.active.releaseCandidate.version, 'prerelease', 'rc');
|
this._newVersion = semverInc(this.active.releaseCandidate.version, 'prerelease', 'rc');
|
||||||
|
@ -7208,27 +7208,23 @@ class CutStableAction extends ReleaseAction {
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Release action that moves the next release-train into the feature-freeze phase. This means
|
* Base action that can be used to move the next release-train into the feature-freeze or
|
||||||
* that a new version branch is created from the next branch, and a new next pre-release is
|
* release-candidate phase. This means that a new version branch is created from the next
|
||||||
* cut indicating the started feature-freeze.
|
* branch, and a new pre-release (either RC or another `next`) is cut indicating the new phase.
|
||||||
*/
|
*/
|
||||||
class MoveNextIntoFeatureFreezeAction extends ReleaseAction {
|
class BranchOffNextBranchBaseAction extends ReleaseAction {
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
this._newVersion = computeNewPrereleaseVersionForNext(this.active, this.config);
|
|
||||||
}
|
|
||||||
getDescription() {
|
getDescription() {
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
const { branchName } = this.active.next;
|
const { branchName } = this.active.next;
|
||||||
const newVersion = yield this._newVersion;
|
const newVersion = yield this._computeNewVersion();
|
||||||
return `Move the "${branchName}" branch into feature-freeze phase (v${newVersion}).`;
|
return `Move the "${branchName}" branch into ${this.newPhaseName} phase (v${newVersion}).`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
perform() {
|
perform() {
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
const newVersion = yield this._newVersion;
|
const newVersion = yield this._computeNewVersion();
|
||||||
const newBranch = `${newVersion.major}.${newVersion.minor}.x`;
|
const newBranch = `${newVersion.major}.${newVersion.minor}.x`;
|
||||||
// Branch-off the next branch into a feature-freeze branch.
|
// Branch-off the next branch into a new version branch.
|
||||||
yield this._createNewVersionBranchFromNext(newBranch);
|
yield this._createNewVersionBranchFromNext(newBranch);
|
||||||
// Stage the new version for the newly created branch, and push changes to a
|
// 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
|
// fork in order to create a staging pull request. Note that we re-use the newly
|
||||||
|
@ -7242,6 +7238,17 @@ class MoveNextIntoFeatureFreezeAction extends ReleaseAction {
|
||||||
yield this._createNextBranchUpdatePullRequest(releaseNotes, newVersion);
|
yield this._createNextBranchUpdatePullRequest(releaseNotes, newVersion);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/** Computes the new version for the release-train being branched-off. */
|
||||||
|
_computeNewVersion() {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
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. */
|
/** Creates a new version branch from the next branch. */
|
||||||
_createNewVersionBranchFromNext(newBranch) {
|
_createNewVersionBranchFromNext(newBranch) {
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
@ -7255,7 +7262,8 @@ class MoveNextIntoFeatureFreezeAction extends ReleaseAction {
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Creates a pull request for the next branch that bumps the version to the next
|
* 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.
|
* minor, and cherry-picks the changelog for the newly branched-off release-candidate
|
||||||
|
* or feature-freeze version.
|
||||||
*/
|
*/
|
||||||
_createNextBranchUpdatePullRequest(releaseNotes, newVersion) {
|
_createNextBranchUpdatePullRequest(releaseNotes, newVersion) {
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
@ -7273,7 +7281,7 @@ class MoveNextIntoFeatureFreezeAction extends ReleaseAction {
|
||||||
const commitMessage = getReleaseNoteCherryPickCommitMessage(releaseNotes.version);
|
const commitMessage = getReleaseNoteCherryPickCommitMessage(releaseNotes.version);
|
||||||
yield this.createCommit(commitMessage, [changelogPath]);
|
yield this.createCommit(commitMessage, [changelogPath]);
|
||||||
let nextPullRequestMessage = `The previous "next" release-train has moved into the ` +
|
let nextPullRequestMessage = `The previous "next" release-train has moved into the ` +
|
||||||
`release-candidate phase. This PR updates the next branch to the subsequent ` +
|
`${this.newPhaseName} phase. This PR updates the next branch to the subsequent ` +
|
||||||
`release-train.\n\nAlso this PR cherry-picks the changelog for ` +
|
`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.`;
|
`v${newVersion} into the ${nextBranch} branch so that the changelog is up to date.`;
|
||||||
const nextUpdatePullRequest = yield this.pushChangesToForkAndCreatePullRequest(nextBranch, `next-release-train-${newNextVersion}`, `Update next branch to reflect new release-train "v${newNextVersion}".`, nextPullRequestMessage);
|
const nextUpdatePullRequest = yield this.pushChangesToForkAndCreatePullRequest(nextBranch, `next-release-train-${newNextVersion}`, `Update next branch to reflect new release-train "v${newNextVersion}".`, nextPullRequestMessage);
|
||||||
|
@ -7281,11 +7289,59 @@ class MoveNextIntoFeatureFreezeAction extends ReleaseAction {
|
||||||
info(yellow(` Please ask team members to review: ${nextUpdatePullRequest.url}.`));
|
info(yellow(` Please ask team members to review: ${nextUpdatePullRequest.url}.`));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
class MoveNextIntoFeatureFreezeAction extends BranchOffNextBranchBaseAction {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.newPhaseName = 'feature-freeze';
|
||||||
|
}
|
||||||
static isActive(active) {
|
static isActive(active) {
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
// A new feature-freeze/release-candidate branch can only be created if there
|
// A new feature-freeze branch can only be created if there is no active
|
||||||
// is no active release-train in feature-freeze/release-candidate phase.
|
// release-train in feature-freeze/release-candidate phase and the version
|
||||||
return active.releaseCandidate === null;
|
// currently in the `next` branch is for a major. The feature-freeze phase
|
||||||
|
// is not foreseen for minor versions.
|
||||||
|
return active.releaseCandidate === null && active.next.isMajor;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Release action that moves the next release-train into the release-candidate phase. This means
|
||||||
|
* that a new version branch is created from the next branch, and the first release candidate
|
||||||
|
* version is cut indicating the new phase.
|
||||||
|
*/
|
||||||
|
class MoveNextIntoReleaseCandidateAction extends BranchOffNextBranchBaseAction {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.newPhaseName = 'release-candidate';
|
||||||
|
}
|
||||||
|
static isActive(active) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
// Directly switching a next release-train into the `release-candidate`
|
||||||
|
// phase is only allowed for minor releases. Major version always need to
|
||||||
|
// go through the `feature-freeze` phase.
|
||||||
|
return active.releaseCandidate === null && !active.next.isMajor;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7352,10 +7408,11 @@ class TagRecentMajorAsLatest extends ReleaseAction {
|
||||||
const actions = [
|
const actions = [
|
||||||
TagRecentMajorAsLatest,
|
TagRecentMajorAsLatest,
|
||||||
CutStableAction,
|
CutStableAction,
|
||||||
CutReleaseCandidateAction,
|
CutReleaseCandidateForFeatureFreezeAction,
|
||||||
CutNewPatchAction,
|
CutNewPatchAction,
|
||||||
CutNextPrereleaseAction,
|
CutNextPrereleaseAction,
|
||||||
MoveNextIntoFeatureFreezeAction,
|
MoveNextIntoFeatureFreezeAction,
|
||||||
|
MoveNextIntoReleaseCandidateAction,
|
||||||
CutLongTermSupportPatchAction,
|
CutLongTermSupportPatchAction,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* @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}.`));
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ import {ReleaseAction} from '../actions';
|
||||||
* Cuts the first release candidate for a release-train currently in the
|
* Cuts the first release candidate for a release-train currently in the
|
||||||
* feature-freeze phase. The version is bumped from `next` to `rc.0`.
|
* feature-freeze phase. The version is bumped from `next` to `rc.0`.
|
||||||
*/
|
*/
|
||||||
export class CutReleaseCandidateAction extends ReleaseAction {
|
export class CutReleaseCandidateForFeatureFreezeAction extends ReleaseAction {
|
||||||
private _newVersion = semverInc(this.active.releaseCandidate!.version, 'prerelease', 'rc');
|
private _newVersion = semverInc(this.active.releaseCandidate!.version, 'prerelease', 'rc');
|
||||||
|
|
||||||
override async getDescription() {
|
override async getDescription() {
|
|
@ -11,9 +11,10 @@ import {ReleaseActionConstructor} from '../actions';
|
||||||
import {CutLongTermSupportPatchAction} from './cut-lts-patch';
|
import {CutLongTermSupportPatchAction} from './cut-lts-patch';
|
||||||
import {CutNewPatchAction} from './cut-new-patch';
|
import {CutNewPatchAction} from './cut-new-patch';
|
||||||
import {CutNextPrereleaseAction} from './cut-next-prerelease';
|
import {CutNextPrereleaseAction} from './cut-next-prerelease';
|
||||||
import {CutReleaseCandidateAction} from './cut-release-candidate';
|
import {CutReleaseCandidateForFeatureFreezeAction} from './cut-release-candidate-for-feature-freeze';
|
||||||
import {CutStableAction} from './cut-stable';
|
import {CutStableAction} from './cut-stable';
|
||||||
import {MoveNextIntoFeatureFreezeAction} from './move-next-into-feature-freeze';
|
import {MoveNextIntoFeatureFreezeAction} from './move-next-into-feature-freeze';
|
||||||
|
import {MoveNextIntoReleaseCandidateAction} from './move-next-into-release-candidate';
|
||||||
import {TagRecentMajorAsLatest} from './tag-recent-major-as-latest';
|
import {TagRecentMajorAsLatest} from './tag-recent-major-as-latest';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,9 +24,10 @@ import {TagRecentMajorAsLatest} from './tag-recent-major-as-latest';
|
||||||
export const actions: ReleaseActionConstructor[] = [
|
export const actions: ReleaseActionConstructor[] = [
|
||||||
TagRecentMajorAsLatest,
|
TagRecentMajorAsLatest,
|
||||||
CutStableAction,
|
CutStableAction,
|
||||||
CutReleaseCandidateAction,
|
CutReleaseCandidateForFeatureFreezeAction,
|
||||||
CutNewPatchAction,
|
CutNewPatchAction,
|
||||||
CutNextPrereleaseAction,
|
CutNextPrereleaseAction,
|
||||||
MoveNextIntoFeatureFreezeAction,
|
MoveNextIntoFeatureFreezeAction,
|
||||||
|
MoveNextIntoReleaseCandidateAction,
|
||||||
CutLongTermSupportPatchAction,
|
CutLongTermSupportPatchAction,
|
||||||
];
|
];
|
||||||
|
|
|
@ -6,103 +6,23 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as semver from 'semver';
|
import {ActiveReleaseTrains} from '../../versioning';
|
||||||
|
|
||||||
import {green, info, yellow} from '../../../utils/console';
|
import {BranchOffNextBranchBaseAction} from './branch-off-next-branch';
|
||||||
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';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release action that moves the next release-train into the feature-freeze phase. This means
|
* 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
|
* that a new version branch is created from the next branch, and a new next pre-release is
|
||||||
* cut indicating the started feature-freeze.
|
* cut indicating the started feature-freeze.
|
||||||
*/
|
*/
|
||||||
export class MoveNextIntoFeatureFreezeAction extends ReleaseAction {
|
export class MoveNextIntoFeatureFreezeAction extends BranchOffNextBranchBaseAction {
|
||||||
private _newVersion = computeNewPrereleaseVersionForNext(this.active, this.config);
|
override newPhaseName = 'feature-freeze' as const;
|
||||||
|
|
||||||
override async getDescription() {
|
|
||||||
const {branchName} = this.active.next;
|
|
||||||
const newVersion = await this._newVersion;
|
|
||||||
return `Move the "${branchName}" branch into feature-freeze phase (v${newVersion}).`;
|
|
||||||
}
|
|
||||||
|
|
||||||
override 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 {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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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(
|
|
||||||
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 ` +
|
|
||||||
`release-candidate 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}.`));
|
|
||||||
}
|
|
||||||
|
|
||||||
static override async isActive(active: ActiveReleaseTrains) {
|
static override async isActive(active: ActiveReleaseTrains) {
|
||||||
// A new feature-freeze/release-candidate branch can only be created if there
|
// A new feature-freeze branch can only be created if there is no active
|
||||||
// is no active release-train in feature-freeze/release-candidate phase.
|
// release-train in feature-freeze/release-candidate phase and the version
|
||||||
return active.releaseCandidate === null;
|
// currently in the `next` branch is for a major. The feature-freeze phase
|
||||||
|
// is not foreseen for minor versions.
|
||||||
|
return active.releaseCandidate === null && active.next.isMajor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* @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 {ActiveReleaseTrains} from '../../versioning';
|
||||||
|
|
||||||
|
import {BranchOffNextBranchBaseAction} from './branch-off-next-branch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release action that moves the next release-train into the release-candidate phase. This means
|
||||||
|
* that a new version branch is created from the next branch, and the first release candidate
|
||||||
|
* version is cut indicating the new phase.
|
||||||
|
*/
|
||||||
|
export class MoveNextIntoReleaseCandidateAction extends BranchOffNextBranchBaseAction {
|
||||||
|
override newPhaseName = 'release-candidate' as const;
|
||||||
|
|
||||||
|
static override async isActive(active: ActiveReleaseTrains) {
|
||||||
|
// Directly switching a next release-train into the `release-candidate`
|
||||||
|
// phase is only allowed for minor releases. Major version always need to
|
||||||
|
// go through the `feature-freeze` phase.
|
||||||
|
return active.releaseCandidate === null && !active.next.isMajor;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/**
|
||||||
|
* @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 {getBranchPushMatcher} from '../../../utils/testing';
|
||||||
|
import {ActiveReleaseTrains} from '../../versioning';
|
||||||
|
import * as npm from '../../versioning/npm-publish';
|
||||||
|
import {ReleaseActionConstructor} from '../actions';
|
||||||
|
import {BranchOffNextBranchBaseAction} from '../actions/branch-off-next-branch';
|
||||||
|
import * as externalCommands from '../external-commands';
|
||||||
|
|
||||||
|
import {setupReleaseActionForTesting, testTmpDir} from './test-utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the given branch-off release action and expects versions and
|
||||||
|
* branches to be determined and created properly.
|
||||||
|
*/
|
||||||
|
export async function expectBranchOffActionToRun(
|
||||||
|
action: ReleaseActionConstructor<BranchOffNextBranchBaseAction>, active: ActiveReleaseTrains,
|
||||||
|
isNextPublishedToNpm: boolean, expectedNextVersion: string, expectedVersion: string,
|
||||||
|
expectedNewBranch: string) {
|
||||||
|
const {repo, fork, instance, gitClient} =
|
||||||
|
setupReleaseActionForTesting(action, active, isNextPublishedToNpm);
|
||||||
|
|
||||||
|
const expectedNextUpdateBranch = `next-release-train-${expectedNextVersion}`;
|
||||||
|
const expectedStagingForkBranch = `release-stage-${expectedVersion}`;
|
||||||
|
const expectedTagName = expectedVersion;
|
||||||
|
|
||||||
|
// We first mock the commit status check for the next branch, then expect two pull
|
||||||
|
// requests from a fork that are targeting next and the new feature-freeze branch.
|
||||||
|
repo.expectBranchRequest('master', 'MASTER_COMMIT_SHA')
|
||||||
|
.expectCommitStatusCheck('MASTER_COMMIT_SHA', 'success')
|
||||||
|
.expectFindForkRequest(fork)
|
||||||
|
.expectPullRequestToBeCreated(expectedNewBranch, fork, expectedStagingForkBranch, 200)
|
||||||
|
.expectPullRequestWait(200)
|
||||||
|
.expectBranchRequest(expectedNewBranch, 'STAGING_COMMIT_SHA')
|
||||||
|
.expectCommitRequest(
|
||||||
|
'STAGING_COMMIT_SHA', `release: cut the v${expectedVersion} release\n\nPR Close #200.`)
|
||||||
|
.expectTagToBeCreated(expectedTagName, 'STAGING_COMMIT_SHA')
|
||||||
|
.expectReleaseToBeCreated(`v${expectedVersion}`, expectedTagName)
|
||||||
|
.expectPullRequestToBeCreated('master', fork, expectedNextUpdateBranch, 100);
|
||||||
|
|
||||||
|
// In the fork, we make the following branches appear as non-existent,
|
||||||
|
// so that the PRs can be created properly without collisions.
|
||||||
|
fork.expectBranchRequest(expectedStagingForkBranch, null)
|
||||||
|
.expectBranchRequest(expectedNextUpdateBranch, null);
|
||||||
|
|
||||||
|
await instance.perform();
|
||||||
|
|
||||||
|
expect(gitClient.pushed.length).toBe(3);
|
||||||
|
expect(gitClient.pushed[0])
|
||||||
|
.toEqual(
|
||||||
|
getBranchPushMatcher({
|
||||||
|
baseRepo: repo,
|
||||||
|
baseBranch: 'master',
|
||||||
|
targetRepo: repo,
|
||||||
|
targetBranch: expectedNewBranch,
|
||||||
|
expectedCommits: [],
|
||||||
|
}),
|
||||||
|
'Expected new version-branch to be created upstream and based on "master".');
|
||||||
|
expect(gitClient.pushed[1])
|
||||||
|
.toEqual(
|
||||||
|
getBranchPushMatcher({
|
||||||
|
baseBranch: 'master',
|
||||||
|
baseRepo: repo,
|
||||||
|
targetBranch: expectedStagingForkBranch,
|
||||||
|
targetRepo: fork,
|
||||||
|
expectedCommits: [{
|
||||||
|
message: `release: cut the v${expectedVersion} release`,
|
||||||
|
files: ['package.json', 'CHANGELOG.md'],
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
'Expected release staging branch to be created in fork.');
|
||||||
|
|
||||||
|
expect(gitClient.pushed[2])
|
||||||
|
.toEqual(
|
||||||
|
getBranchPushMatcher({
|
||||||
|
baseBranch: 'master',
|
||||||
|
baseRepo: repo,
|
||||||
|
targetBranch: expectedNextUpdateBranch,
|
||||||
|
targetRepo: fork,
|
||||||
|
expectedCommits: [
|
||||||
|
{
|
||||||
|
message: `release: bump the next branch to v${expectedNextVersion}`,
|
||||||
|
files: ['package.json']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: `docs: release notes for the v${expectedVersion} release`,
|
||||||
|
files: ['CHANGELOG.md']
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
'Expected next release-train update branch be created in fork.');
|
||||||
|
|
||||||
|
expect(externalCommands.invokeReleaseBuildCommand).toHaveBeenCalledTimes(1);
|
||||||
|
expect(npm.runNpmPublish).toHaveBeenCalledTimes(2);
|
||||||
|
expect(npm.runNpmPublish).toHaveBeenCalledWith(`${testTmpDir}/dist/pkg1`, 'next', undefined);
|
||||||
|
expect(npm.runNpmPublish).toHaveBeenCalledWith(`${testTmpDir}/dist/pkg2`, 'next', undefined);
|
||||||
|
}
|
|
@ -7,13 +7,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ReleaseTrain} from '../../versioning/release-trains';
|
import {ReleaseTrain} from '../../versioning/release-trains';
|
||||||
import {CutReleaseCandidateAction} from '../actions/cut-release-candidate';
|
import {CutReleaseCandidateForFeatureFreezeAction} from '../actions/cut-release-candidate-for-feature-freeze';
|
||||||
|
|
||||||
import {expectStagingAndPublishWithCherryPick, parse, setupReleaseActionForTesting} from './test-utils';
|
import {expectStagingAndPublishWithCherryPick, parse, setupReleaseActionForTesting} from './test-utils';
|
||||||
|
|
||||||
describe('cut release candidate action', () => {
|
describe('cut release candidate for feature-freeze action', () => {
|
||||||
it('should activate if a feature-freeze release-train is active', async () => {
|
it('should activate if a feature-freeze release-train is active', async () => {
|
||||||
expect(await CutReleaseCandidateAction.isActive({
|
expect(await CutReleaseCandidateForFeatureFreezeAction.isActive({
|
||||||
releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.1')),
|
releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.1')),
|
||||||
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
||||||
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
|
@ -21,7 +21,7 @@ describe('cut release candidate action', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not activate if release-candidate release-train is active', async () => {
|
it('should not activate if release-candidate release-train is active', async () => {
|
||||||
expect(await CutReleaseCandidateAction.isActive({
|
expect(await CutReleaseCandidateForFeatureFreezeAction.isActive({
|
||||||
// No longer in feature-freeze but in release-candidate phase.
|
// No longer in feature-freeze but in release-candidate phase.
|
||||||
releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-rc.0')),
|
releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-rc.0')),
|
||||||
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
||||||
|
@ -30,7 +30,7 @@ describe('cut release candidate action', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not activate if no FF/RC release-train is active', async () => {
|
it('should not activate if no FF/RC release-train is active', async () => {
|
||||||
expect(await CutReleaseCandidateAction.isActive({
|
expect(await CutReleaseCandidateForFeatureFreezeAction.isActive({
|
||||||
releaseCandidate: null,
|
releaseCandidate: null,
|
||||||
next: new ReleaseTrain('master', parse('10.1.0-next.0')),
|
next: new ReleaseTrain('master', parse('10.1.0-next.0')),
|
||||||
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
|
@ -38,7 +38,7 @@ describe('cut release candidate action', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a proper new version and select correct branch', async () => {
|
it('should create a proper new version and select correct branch', async () => {
|
||||||
const action = setupReleaseActionForTesting(CutReleaseCandidateAction, {
|
const action = setupReleaseActionForTesting(CutReleaseCandidateForFeatureFreezeAction, {
|
||||||
releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.1')),
|
releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.1')),
|
||||||
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
||||||
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
|
@ -6,14 +6,11 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getBranchPushMatcher} from '../../../utils/testing';
|
|
||||||
import {ActiveReleaseTrains} from '../../versioning/active-release-trains';
|
|
||||||
import * as npm from '../../versioning/npm-publish';
|
|
||||||
import {ReleaseTrain} from '../../versioning/release-trains';
|
import {ReleaseTrain} from '../../versioning/release-trains';
|
||||||
import {MoveNextIntoFeatureFreezeAction} from '../actions/move-next-into-feature-freeze';
|
import {MoveNextIntoFeatureFreezeAction} from '../actions/move-next-into-feature-freeze';
|
||||||
import * as externalCommands from '../external-commands';
|
|
||||||
|
|
||||||
import {parse, setupReleaseActionForTesting, testTmpDir} from './test-utils';
|
import {expectBranchOffActionToRun} from './branch-off-next-branch-testing';
|
||||||
|
import {parse} from './test-utils';
|
||||||
|
|
||||||
describe('move next into feature-freeze action', () => {
|
describe('move next into feature-freeze action', () => {
|
||||||
it('should not activate if a feature-freeze release-train is active', async () => {
|
it('should not activate if a feature-freeze release-train is active', async () => {
|
||||||
|
@ -33,17 +30,25 @@ describe('move next into feature-freeze action', () => {
|
||||||
})).toBe(false);
|
})).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not activate if the next release-train is for a minor', async () => {
|
||||||
|
expect(await MoveNextIntoFeatureFreezeAction.isActive({
|
||||||
|
releaseCandidate: null,
|
||||||
|
next: new ReleaseTrain('master', parse('10.1.0-next.2')),
|
||||||
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
|
})).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('should activate if no FF/RC release-train is active', async () => {
|
it('should activate if no FF/RC release-train is active', async () => {
|
||||||
expect(await MoveNextIntoFeatureFreezeAction.isActive({
|
expect(await MoveNextIntoFeatureFreezeAction.isActive({
|
||||||
releaseCandidate: null,
|
releaseCandidate: null,
|
||||||
next: new ReleaseTrain('master', parse('10.1.0-next.0')),
|
next: new ReleaseTrain('master', parse('11.0.0-next.0')),
|
||||||
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
})).toBe(true);
|
})).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create pull requests and feature-freeze branch', async () => {
|
it('should create pull requests and feature-freeze branch', async () => {
|
||||||
await expectVersionAndBranchToBeCreated(
|
await expectBranchOffActionToRun(
|
||||||
{
|
MoveNextIntoFeatureFreezeAction, {
|
||||||
releaseCandidate: null,
|
releaseCandidate: null,
|
||||||
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
||||||
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
|
@ -52,95 +57,12 @@ describe('move next into feature-freeze action', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not increment the version if "next" version is not yet published', async () => {
|
it('should not increment the version if "next" version is not yet published', async () => {
|
||||||
await expectVersionAndBranchToBeCreated(
|
await expectBranchOffActionToRun(
|
||||||
{
|
MoveNextIntoFeatureFreezeAction, {
|
||||||
releaseCandidate: null,
|
releaseCandidate: null,
|
||||||
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
||||||
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
},
|
},
|
||||||
/* isNextPublishedToNpm */ false, '10.3.0-next.0', '10.2.0-next.0', '10.2.x');
|
/* isNextPublishedToNpm */ false, '10.3.0-next.0', '10.2.0-next.0', '10.2.x');
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Performs the action and expects versions and branches to be determined properly. */
|
|
||||||
async function expectVersionAndBranchToBeCreated(
|
|
||||||
active: ActiveReleaseTrains, isNextPublishedToNpm: boolean, expectedNextVersion: string,
|
|
||||||
expectedVersion: string, expectedNewBranch: string) {
|
|
||||||
const {repo, fork, instance, gitClient, releaseConfig} =
|
|
||||||
setupReleaseActionForTesting(MoveNextIntoFeatureFreezeAction, active, isNextPublishedToNpm);
|
|
||||||
|
|
||||||
const expectedNextUpdateBranch = `next-release-train-${expectedNextVersion}`;
|
|
||||||
const expectedStagingForkBranch = `release-stage-${expectedVersion}`;
|
|
||||||
const expectedTagName = expectedVersion;
|
|
||||||
|
|
||||||
// We first mock the commit status check for the next branch, then expect two pull
|
|
||||||
// requests from a fork that are targeting next and the new feature-freeze branch.
|
|
||||||
repo.expectBranchRequest('master', 'MASTER_COMMIT_SHA')
|
|
||||||
.expectCommitStatusCheck('MASTER_COMMIT_SHA', 'success')
|
|
||||||
.expectFindForkRequest(fork)
|
|
||||||
.expectPullRequestToBeCreated(expectedNewBranch, fork, expectedStagingForkBranch, 200)
|
|
||||||
.expectPullRequestWait(200)
|
|
||||||
.expectBranchRequest(expectedNewBranch, 'STAGING_COMMIT_SHA')
|
|
||||||
.expectCommitRequest(
|
|
||||||
'STAGING_COMMIT_SHA', `release: cut the v${expectedVersion} release\n\nPR Close #200.`)
|
|
||||||
.expectTagToBeCreated(expectedTagName, 'STAGING_COMMIT_SHA')
|
|
||||||
.expectReleaseToBeCreated(`v${expectedVersion}`, expectedTagName)
|
|
||||||
.expectPullRequestToBeCreated('master', fork, expectedNextUpdateBranch, 100);
|
|
||||||
|
|
||||||
// In the fork, we make the following branches appear as non-existent,
|
|
||||||
// so that the PRs can be created properly without collisions.
|
|
||||||
fork.expectBranchRequest(expectedStagingForkBranch, null)
|
|
||||||
.expectBranchRequest(expectedNextUpdateBranch, null);
|
|
||||||
|
|
||||||
await instance.perform();
|
|
||||||
|
|
||||||
expect(gitClient.pushed.length).toBe(3);
|
|
||||||
expect(gitClient.pushed[0])
|
|
||||||
.toEqual(
|
|
||||||
getBranchPushMatcher({
|
|
||||||
baseRepo: repo,
|
|
||||||
baseBranch: 'master',
|
|
||||||
targetRepo: repo,
|
|
||||||
targetBranch: expectedNewBranch,
|
|
||||||
expectedCommits: [],
|
|
||||||
}),
|
|
||||||
'Expected feature-freeze branch to be created upstream and based on "master".');
|
|
||||||
expect(gitClient.pushed[1])
|
|
||||||
.toEqual(
|
|
||||||
getBranchPushMatcher({
|
|
||||||
baseBranch: 'master',
|
|
||||||
baseRepo: repo,
|
|
||||||
targetBranch: expectedStagingForkBranch,
|
|
||||||
targetRepo: fork,
|
|
||||||
expectedCommits: [{
|
|
||||||
message: `release: cut the v${expectedVersion} release`,
|
|
||||||
files: ['package.json', 'CHANGELOG.md'],
|
|
||||||
}],
|
|
||||||
}),
|
|
||||||
'Expected release staging branch to be created in fork.');
|
|
||||||
|
|
||||||
expect(gitClient.pushed[2])
|
|
||||||
.toEqual(
|
|
||||||
getBranchPushMatcher({
|
|
||||||
baseBranch: 'master',
|
|
||||||
baseRepo: repo,
|
|
||||||
targetBranch: expectedNextUpdateBranch,
|
|
||||||
targetRepo: fork,
|
|
||||||
expectedCommits: [
|
|
||||||
{
|
|
||||||
message: `release: bump the next branch to v${expectedNextVersion}`,
|
|
||||||
files: ['package.json']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
message: `docs: release notes for the v${expectedVersion} release`,
|
|
||||||
files: ['CHANGELOG.md']
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
'Expected next release-train update branch be created in fork.');
|
|
||||||
|
|
||||||
expect(externalCommands.invokeReleaseBuildCommand).toHaveBeenCalledTimes(1);
|
|
||||||
expect(npm.runNpmPublish).toHaveBeenCalledTimes(2);
|
|
||||||
expect(npm.runNpmPublish).toHaveBeenCalledWith(`${testTmpDir}/dist/pkg1`, 'next', undefined);
|
|
||||||
expect(npm.runNpmPublish).toHaveBeenCalledWith(`${testTmpDir}/dist/pkg2`, 'next', undefined);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* @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 {ReleaseTrain} from '../../versioning/release-trains';
|
||||||
|
import {MoveNextIntoReleaseCandidateAction} from '../actions/move-next-into-release-candidate';
|
||||||
|
|
||||||
|
import {expectBranchOffActionToRun} from './branch-off-next-branch-testing';
|
||||||
|
import {parse} from './test-utils';
|
||||||
|
|
||||||
|
describe('move next into release-candidate action', () => {
|
||||||
|
it('should not activate if a feature-freeze release-train is active', async () => {
|
||||||
|
expect(await MoveNextIntoReleaseCandidateAction.isActive({
|
||||||
|
releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.1')),
|
||||||
|
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
||||||
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
|
})).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not activate if release-candidate release-train is active', async () => {
|
||||||
|
expect(await MoveNextIntoReleaseCandidateAction.isActive({
|
||||||
|
// No longer in feature-freeze but in release-candidate phase.
|
||||||
|
releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-rc.0')),
|
||||||
|
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
||||||
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
|
})).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not activate if the next release-train is for a major', async () => {
|
||||||
|
expect(await MoveNextIntoReleaseCandidateAction.isActive({
|
||||||
|
releaseCandidate: null,
|
||||||
|
next: new ReleaseTrain('master', parse('11.0.0-next.0')),
|
||||||
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
|
})).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should activate if no FF/RC release-train is active', async () => {
|
||||||
|
expect(await MoveNextIntoReleaseCandidateAction.isActive({
|
||||||
|
releaseCandidate: null,
|
||||||
|
next: new ReleaseTrain('master', parse('10.1.0-next.0')),
|
||||||
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
|
})).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create pull requests and new version-branch', async () => {
|
||||||
|
await expectBranchOffActionToRun(
|
||||||
|
MoveNextIntoReleaseCandidateAction, {
|
||||||
|
releaseCandidate: null,
|
||||||
|
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
|
||||||
|
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
|
||||||
|
},
|
||||||
|
/* isNextPublishedToNpm */ true, '10.3.0-next.0', '10.2.0-rc.0', '10.2.x');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue