From d3d333e51bba8c7c7cf63139a94f22ad8c757c5e Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Fri, 8 Oct 2021 10:04:29 -0700 Subject: [PATCH] fix(ng-dev): allow deprecations in PRs during feature freeze Currently our tooling does not distinguish between when RC is occurring and when feature freeze is occurring. Since they are treated as the same state, we are unable to allow deprecations messages on feature freeze without allowing it during RC periods. By creating this distinction we can properly handle this case. --- ng-dev/caretaker/check/ci.spec.ts | 7 ++-- ng-dev/caretaker/check/ci.ts | 5 ++- ng-dev/pr/common/targeting/labels.ts | 4 +-- ng-dev/pr/common/targeting/target-label.ts | 12 +++++-- ng-dev/pr/common/validation/validations.ts | 4 ++- ng-dev/pr/merge/integration.spec.ts | 10 +++++- ng-dev/release/publish/test/common.spec.ts | 8 ++--- .../test/configure-next-as-major.spec.ts | 17 +++++----- .../publish/test/cut-lts-patch.spec.ts | 25 +++++++------- .../publish/test/cut-new-patch.spec.ts | 21 ++++++------ .../publish/test/cut-next-prerelease.spec.ts | 29 ++++++++-------- ...lease-candidate-for-feature-freeze.spec.ts | 21 ++++++------ .../release/publish/test/cut-stable.spec.ts | 29 ++++++++-------- .../move-next-into-feature-freeze.spec.ts | 33 ++++++++++--------- .../move-next-into-release-candidate.spec.ts | 33 ++++++++++--------- .../test/tag-recent-major-as-latest.spec.ts | 21 ++++++------ .../versioning/active-release-trains.ts | 25 ++++++++++---- 17 files changed, 171 insertions(+), 133 deletions(-) diff --git a/ng-dev/caretaker/check/ci.spec.ts b/ng-dev/caretaker/check/ci.spec.ts index fb1041e3ab..adf71aac65 100644 --- a/ng-dev/caretaker/check/ci.spec.ts +++ b/ng-dev/caretaker/check/ci.spec.ts @@ -89,7 +89,8 @@ describe('CiModule', () => { status: 'failed', }, ]); - fetchActiveReleaseTrainsSpy.and.resolveTo([]); + const trains = buildMockActiveReleaseTrains(true); + fetchActiveReleaseTrainsSpy.and.resolveTo(trains); const module = new CiModule({caretaker: {}, ...mockNgDevConfig}); Object.defineProperty(module, 'data', {value: fakeData}); @@ -113,9 +114,9 @@ function buildMockActiveReleaseTrains(withRc: boolean): versioning.ActiveRelease isMajor: false, version: new SemVer('0.0.0'), }; - return { + return new versioning.ActiveReleaseTrains({ releaseCandidate: withRc ? {branchName: 'rc-branch', ...baseResult} : null, latest: {branchName: 'latest-branch', ...baseResult}, next: {branchName: 'next-branch', ...baseResult}, - }; + }); } diff --git a/ng-dev/caretaker/check/ci.ts b/ng-dev/caretaker/check/ci.ts index 37e3f79806..7ef92ed073 100644 --- a/ng-dev/caretaker/check/ci.ts +++ b/ng-dev/caretaker/check/ci.ts @@ -36,9 +36,8 @@ export class CiModule extends BaseModule { ...this.git.remoteConfig, nextBranchName, }; - const releaseTrains = await fetchActiveReleaseTrains(repo); - - const ciResultPromises = Object.entries(releaseTrains).map( + const {latest, next, releaseCandidate} = await fetchActiveReleaseTrains(repo); + const ciResultPromises = Object.entries({releaseCandidate, latest, next}).map( async ([trainName, train]: [string, ReleaseTrain | null]) => { if (train === null) { return { diff --git a/ng-dev/pr/common/targeting/labels.ts b/ng-dev/pr/common/targeting/labels.ts index 82e54cee3d..007cdee693 100644 --- a/ng-dev/pr/common/targeting/labels.ts +++ b/ng-dev/pr/common/targeting/labels.ts @@ -8,7 +8,7 @@ import {assertValidReleaseConfig, ReleaseConfig} from '../../../release/config/index'; import { - fetchActiveReleaseTrains, + ActiveReleaseTrains, getNextBranchName, isVersionBranch, ReleaseRepoWithApi, @@ -38,6 +38,7 @@ import {debug} from '../../../utils/console'; * NPM version data when LTS version branches are validated. */ export async function getTargetLabelsForActiveReleaseTrains( + {latest, releaseCandidate, next}: ActiveReleaseTrains, api: GithubClient, config: Partial<{github: GithubConfig; release: ReleaseConfig}>, ): Promise { @@ -50,7 +51,6 @@ export async function getTargetLabelsForActiveReleaseTrains( nextBranchName, api, }; - const {latest, releaseCandidate, next} = await fetchActiveReleaseTrains(repo); const targetLabels: TargetLabel[] = [ { diff --git a/ng-dev/pr/common/targeting/target-label.ts b/ng-dev/pr/common/targeting/target-label.ts index 0c8bb70f9b..dab85043b6 100644 --- a/ng-dev/pr/common/targeting/target-label.ts +++ b/ng-dev/pr/common/targeting/target-label.ts @@ -13,6 +13,7 @@ import {Commit} from '../../../commit-message/parse'; import {assertChangesAllowForTargetLabel} from '../validation/validations'; import {PullRequestFailure} from '../validation/failures'; import {GithubClient} from '../../../utils/git/github'; +import {fetchActiveReleaseTrains} from '../../../release/versioning'; /** * Enum capturing available target label names in the Angular organization. A target @@ -111,7 +112,14 @@ export async function getTargetBranchesForPullRequest( // can lazily compute branches for a target label and throw. e.g. if an invalid target // label is applied, we want to exit the script gracefully with an error message. try { - const targetLabels = await getTargetLabelsForActiveReleaseTrains(api, config); + const {mainBranchName, name, owner} = config.github; + const releaseTrains = await fetchActiveReleaseTrains({ + name, + nextBranchName: mainBranchName, + owner, + api, + }); + const targetLabels = await getTargetLabelsForActiveReleaseTrains(releaseTrains, api, config); const matchingLabel = await getMatchingTargetLabelForPullRequest( config.pullRequest, labelsOnPullRequest, @@ -119,7 +127,7 @@ export async function getTargetBranchesForPullRequest( ); const targetBranches = await getBranchesFromTargetLabel(matchingLabel, githubTargetBranch); - assertChangesAllowForTargetLabel(commits, matchingLabel, config.pullRequest); + assertChangesAllowForTargetLabel(commits, matchingLabel, config.pullRequest, releaseTrains); return targetBranches; } catch (error) { diff --git a/ng-dev/pr/common/validation/validations.ts b/ng-dev/pr/common/validation/validations.ts index fdae26c346..5256683df5 100644 --- a/ng-dev/pr/common/validation/validations.ts +++ b/ng-dev/pr/common/validation/validations.ts @@ -16,6 +16,7 @@ import { PullRequestFromGithub, PullRequestStatus, } from '../fetch-pull-request'; +import {ActiveReleaseTrains} from '../../../release/versioning'; /** * Assert the commits provided are allowed to merge to the provided target label, @@ -26,6 +27,7 @@ export function assertChangesAllowForTargetLabel( commits: Commit[], label: TargetLabel, config: PullRequestConfig, + releaseTrains: ActiveReleaseTrains, ) { /** * List of commit scopes which are exempted from target label content requirements. i.e. no `feat` @@ -57,7 +59,7 @@ export function assertChangesAllowForTargetLabel( // Deprecations should not be merged into RC, patch or LTS branches. // https://semver.org/#spec-item-7. Deprecations should be part of // minor releases, or major releases according to SemVer. - if (hasDeprecations) { + if (hasDeprecations && !releaseTrains.isFeatureFreeze()) { throw PullRequestFailure.hasDeprecations(label); } break; diff --git a/ng-dev/pr/merge/integration.spec.ts b/ng-dev/pr/merge/integration.spec.ts index 1a4cfac554..d2f251efad 100644 --- a/ng-dev/pr/merge/integration.spec.ts +++ b/ng-dev/pr/merge/integration.spec.ts @@ -20,6 +20,7 @@ import { } from '../common/targeting/target-label'; import {fakeGithubPaginationResponse} from '../../utils/testing/github-interception'; import {getTargetLabelsForActiveReleaseTrains} from '../common/targeting/labels'; +import { fetchActiveReleaseTrains } from '../../release/versioning'; const API_ENDPOINT = `https://api.github.com`; @@ -103,7 +104,14 @@ describe('default target labels', () => { name: string, githubTargetBranch = 'master', ): Promise { - const targetLabels = await getTargetLabelsForActiveReleaseTrains(api, { + const {mainBranchName, name: repoName, owner} = githubConfig; + const releaseTrains = await fetchActiveReleaseTrains({ + name: repoName, + nextBranchName: mainBranchName, + owner, + api, + }); + const targetLabels = await getTargetLabelsForActiveReleaseTrains(releaseTrains, api, { github: githubConfig, release: releaseConfig, }); diff --git a/ng-dev/release/publish/test/common.spec.ts b/ng-dev/release/publish/test/common.spec.ts index e46aa236ea..e5c831f27d 100644 --- a/ng-dev/release/publish/test/common.spec.ts +++ b/ng-dev/release/publish/test/common.spec.ts @@ -33,18 +33,18 @@ import {getMockGitClient} from '../../../utils/testing'; import {SandboxGitRepo} from '../../../utils/testing'; describe('common release action logic', () => { - const baseReleaseTrains: ActiveReleaseTrains = { + const baseReleaseTrains= new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.1')), - }; + }); describe('version computation', () => { - const testReleaseTrain: ActiveReleaseTrains = { + const testReleaseTrain= new ActiveReleaseTrains({ releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.3')), next: new ReleaseTrain('master', parse('10.2.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.1')), - }; + }); it('should not modify release train versions and cause invalid other actions', async () => { const {releaseConfig, githubConfig} = getTestConfigurationsForAction(); diff --git a/ng-dev/release/publish/test/configure-next-as-major.spec.ts b/ng-dev/release/publish/test/configure-next-as-major.spec.ts index 5bcdee014b..dea8900323 100644 --- a/ng-dev/release/publish/test/configure-next-as-major.spec.ts +++ b/ng-dev/release/publish/test/configure-next-as-major.spec.ts @@ -7,6 +7,7 @@ */ import {getBranchPushMatcher} from '../../../utils/testing'; +import { ActiveReleaseTrains } from '../../versioning'; import {ReleaseTrain} from '../../versioning/release-trains'; import {ConfigureNextAsMajorAction} from '../actions/configure-next-as-major'; import {parse, setupReleaseActionForTesting} from './test-utils/test-utils'; @@ -14,40 +15,40 @@ import {parse, setupReleaseActionForTesting} from './test-utils/test-utils'; describe('configure next as major action', () => { it('should be active if the next branch is for a minor', async () => { expect( - await ConfigureNextAsMajorAction.isActive({ + await ConfigureNextAsMajorAction.isActive(new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }), + })), ).toBe(true); }); it('should be active regardless of a feature-freeze/release-candidate train', async () => { expect( - await ConfigureNextAsMajorAction.isActive({ + await ConfigureNextAsMajorAction.isActive(new ActiveReleaseTrains({ releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-rc.1')), next: new ReleaseTrain('master', parse('10.2.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }), + })), ).toBe(true); }); it('should not be active if the next branch is for a major', async () => { expect( - await ConfigureNextAsMajorAction.isActive({ + await ConfigureNextAsMajorAction.isActive(new ActiveReleaseTrains({ 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 compute proper version and create staging pull request', async () => { - const action = setupReleaseActionForTesting(ConfigureNextAsMajorAction, { + const action = setupReleaseActionForTesting(ConfigureNextAsMajorAction, new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.2')), - }); + })); const {repo, fork, gitClient} = action; const expectedVersion = `11.0.0-next.0`; diff --git a/ng-dev/release/publish/test/cut-lts-patch.spec.ts b/ng-dev/release/publish/test/cut-lts-patch.spec.ts index 39ad09176c..1573ea7f14 100644 --- a/ng-dev/release/publish/test/cut-lts-patch.spec.ts +++ b/ng-dev/release/publish/test/cut-lts-patch.spec.ts @@ -25,44 +25,45 @@ import {readFileSync} from 'fs'; import {testTmpDir} from '../../../utils/testing'; import {SandboxGitRepo} from '../../../utils/testing'; import {getMockGitClient} from '../../../utils/testing'; +import { ActiveReleaseTrains } from '../../versioning'; describe('cut an LTS patch action', () => { it('should be active', async () => { expect( - await CutLongTermSupportPatchAction.isActive({ + await CutLongTermSupportPatchAction.isActive(new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }), + })), ).toBe(true); }); it('should be active if there is a feature-freeze train', async () => { expect( - await CutLongTermSupportPatchAction.isActive({ + await CutLongTermSupportPatchAction.isActive(new ActiveReleaseTrains({ releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.3')), next: new ReleaseTrain('master', parse('10.2.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }), + })), ).toBe(true); }); it('should be active if there is a release-candidate train', async () => { expect( - await CutLongTermSupportPatchAction.isActive({ + await CutLongTermSupportPatchAction.isActive(new ActiveReleaseTrains({ releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-rc.0')), next: new ReleaseTrain('master', parse('10.2.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }), + })), ).toBe(true); }); it('should compute proper new version and select correct branch', async () => { - const action = setupReleaseActionForTesting(CutLongTermSupportPatchAction, { + const action = setupReleaseActionForTesting(CutLongTermSupportPatchAction, new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.2')), - }); + })); spyOn(action.instance, '_promptForTargetLtsBranch').and.resolveTo({ name: '9.2.x', @@ -76,11 +77,11 @@ describe('cut an LTS patch action', () => { it('should generate release notes capturing changes to previous latest LTS version', async () => { const action = setupReleaseActionForTesting( CutLongTermSupportPatchAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.2')), - }, + }), true, {useSandboxGitClient: true}, ); @@ -118,11 +119,11 @@ describe('cut an LTS patch action', () => { it('should include number of active LTS branches in action description', async () => { const {releaseConfig, githubConfig} = getTestConfigurationsForAction(); const gitClient = getMockGitClient(githubConfig, /* useSandboxGitClient */ false); - const activeReleaseTrains = { + const activeReleaseTrains = new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.2')), - }; + }); fakeNpmPackageQueryRequest(releaseConfig.npmPackages[0], { 'dist-tags': {'v9-lts': '9.1.2', 'v8-lts': '8.2.2'}, diff --git a/ng-dev/release/publish/test/cut-new-patch.spec.ts b/ng-dev/release/publish/test/cut-new-patch.spec.ts index 72173480e6..9ea4125421 100644 --- a/ng-dev/release/publish/test/cut-new-patch.spec.ts +++ b/ng-dev/release/publish/test/cut-new-patch.spec.ts @@ -16,44 +16,45 @@ import { import {readFileSync} from 'fs'; import {testTmpDir} from '../../../utils/testing'; import {SandboxGitRepo} from '../../../utils/testing'; +import { ActiveReleaseTrains } from '../../versioning'; describe('cut new patch action', () => { it('should be active', async () => { expect( - await CutNewPatchAction.isActive({ + await CutNewPatchAction.isActive(new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }), + })), ).toBe(true); }); it('should compute proper new version and select correct branch', async () => { - const action = setupReleaseActionForTesting(CutNewPatchAction, { + const action = setupReleaseActionForTesting(CutNewPatchAction, new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.2')), - }); + })); await expectStagingAndPublishWithCherryPick(action, '10.0.x', '10.0.3', 'latest'); }); it('should create a proper new version if there is a feature-freeze release-train', async () => { - const action = setupReleaseActionForTesting(CutNewPatchAction, { + const action = setupReleaseActionForTesting(CutNewPatchAction, new ActiveReleaseTrains({ releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.3')), next: new ReleaseTrain('master', parse('10.2.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.9')), - }); + })); await expectStagingAndPublishWithCherryPick(action, '10.0.x', '10.0.10', 'latest'); }); it('should create a proper new version if there is a release-candidate train', async () => { - const action = setupReleaseActionForTesting(CutNewPatchAction, { + const action = setupReleaseActionForTesting(CutNewPatchAction, new ActiveReleaseTrains({ 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.9')), - }); + })); await expectStagingAndPublishWithCherryPick(action, '10.0.x', '10.0.10', 'latest'); }); @@ -61,11 +62,11 @@ describe('cut new patch action', () => { it('should generate release notes capturing changes to the previous latest patch version', async () => { const action = setupReleaseActionForTesting( CutNewPatchAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.3')), latest: new ReleaseTrain('10.0.x', parse('10.0.2')), - }, + }), true, {useSandboxGitClient: true}, ); diff --git a/ng-dev/release/publish/test/cut-next-prerelease.spec.ts b/ng-dev/release/publish/test/cut-next-prerelease.spec.ts index 6243241f7f..0ecc7e0013 100644 --- a/ng-dev/release/publish/test/cut-next-prerelease.spec.ts +++ b/ng-dev/release/publish/test/cut-next-prerelease.spec.ts @@ -20,6 +20,7 @@ import { } from './test-utils/staging-test'; import {testTmpDir} from '../../../utils/testing'; import {SandboxGitRepo} from '../../../utils/testing'; +import { ActiveReleaseTrains } from '../../versioning'; describe('cut next pre-release action', () => { it('should always be active regardless of release-trains', async () => { @@ -27,11 +28,11 @@ describe('cut next pre-release action', () => { }); it('should cut a pre-release for the next branch if there is no FF/RC branch', async () => { - const action = setupReleaseActionForTesting(CutNextPrereleaseAction, { + const action = setupReleaseActionForTesting(CutNextPrereleaseAction, new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.2.0-next.0')), latest: new ReleaseTrain('10.1.x', parse('10.1.2')), - }); + })); await expectStagingAndPublishWithoutCherryPick(action, 'master', '10.2.0-next.1', 'next'); }); @@ -47,11 +48,11 @@ describe('cut next pre-release action', () => { it('should not bump the version', async () => { const action = setupReleaseActionForTesting( CutNextPrereleaseAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.2.0-next.0')), latest: new ReleaseTrain('10.1.x', parse('10.1.0')), - }, + }), /* isNextPublishedToNpm */ false, ); @@ -68,11 +69,11 @@ describe('cut next pre-release action', () => { async () => { const action = setupReleaseActionForTesting( CutNextPrereleaseAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.2.0-next.0')), latest: new ReleaseTrain('10.1.x', parse('10.1.0')), - }, + }), /* isNextPublishedToNpm */ false, {useSandboxGitClient: true}, ); @@ -111,11 +112,11 @@ describe('cut next pre-release action', () => { describe('with active feature-freeze', () => { it('should create a proper new version and select correct branch', async () => { - const action = setupReleaseActionForTesting(CutNextPrereleaseAction, { + const action = setupReleaseActionForTesting(CutNextPrereleaseAction, new ActiveReleaseTrains({ releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.4')), next: new ReleaseTrain('master', parse('10.2.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.2')), - }); + })); await expectStagingAndPublishWithCherryPick(action, '10.1.x', '10.1.0-next.5', 'next'); }); @@ -123,11 +124,11 @@ describe('cut next pre-release action', () => { it('should generate release notes capturing changes to the previous pre-release', async () => { const action = setupReleaseActionForTesting( CutNextPrereleaseAction, - { + new ActiveReleaseTrains({ releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.4')), next: new ReleaseTrain('master', parse('10.2.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.2')), - }, + }), true, {useSandboxGitClient: true}, ); @@ -159,11 +160,11 @@ describe('cut next pre-release action', () => { describe('with active release-candidate', () => { it('should create a proper new version and select correct branch', async () => { - const action = setupReleaseActionForTesting(CutNextPrereleaseAction, { + const action = setupReleaseActionForTesting(CutNextPrereleaseAction, new ActiveReleaseTrains({ 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.2')), - }); + })); await expectStagingAndPublishWithCherryPick(action, '10.1.x', '10.1.0-rc.1', 'next'); }); @@ -171,11 +172,11 @@ describe('cut next pre-release action', () => { it('should generate release notes capturing changes to the previous pre-release', async () => { const action = setupReleaseActionForTesting( CutNextPrereleaseAction, - { + new ActiveReleaseTrains({ 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.2')), - }, + }), true, {useSandboxGitClient: true}, ); diff --git a/ng-dev/release/publish/test/cut-release-candidate-for-feature-freeze.spec.ts b/ng-dev/release/publish/test/cut-release-candidate-for-feature-freeze.spec.ts index 4241e8b4b6..30c65a5ff0 100644 --- a/ng-dev/release/publish/test/cut-release-candidate-for-feature-freeze.spec.ts +++ b/ng-dev/release/publish/test/cut-release-candidate-for-feature-freeze.spec.ts @@ -16,45 +16,46 @@ import { import {readFileSync} from 'fs'; import {testTmpDir} from '../../../utils/testing'; import {SandboxGitRepo} from '../../../utils/testing'; +import { ActiveReleaseTrains } from '../../versioning'; describe('cut release candidate for feature-freeze action', () => { it('should activate if a feature-freeze release-train is active', async () => { expect( - await CutReleaseCandidateForFeatureFreezeAction.isActive({ + await CutReleaseCandidateForFeatureFreezeAction.isActive(new ActiveReleaseTrains({ 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(true); }); it('should not activate if release-candidate release-train is active', async () => { expect( - await CutReleaseCandidateForFeatureFreezeAction.isActive({ + await CutReleaseCandidateForFeatureFreezeAction.isActive(new ActiveReleaseTrains({ // 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 no FF/RC release-train is active', async () => { expect( - await CutReleaseCandidateForFeatureFreezeAction.isActive({ + await CutReleaseCandidateForFeatureFreezeAction.isActive(new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }), + })), ).toBe(false); }); it('should create a proper new version and select correct branch', async () => { - const action = setupReleaseActionForTesting(CutReleaseCandidateForFeatureFreezeAction, { + const action = setupReleaseActionForTesting(CutReleaseCandidateForFeatureFreezeAction, new ActiveReleaseTrains({ 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')), - }); + })); await expectStagingAndPublishWithCherryPick(action, '10.1.x', '10.1.0-rc.0', 'next'); }); @@ -62,11 +63,11 @@ describe('cut release candidate for feature-freeze action', () => { it('should generate release notes capturing changes to the previous pre-release', async () => { const action = setupReleaseActionForTesting( CutReleaseCandidateForFeatureFreezeAction, - { + new ActiveReleaseTrains({ 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')), - }, + }), true, {useSandboxGitClient: true}, ); diff --git a/ng-dev/release/publish/test/cut-stable.spec.ts b/ng-dev/release/publish/test/cut-stable.spec.ts index cd692230d2..0aa6a53657 100644 --- a/ng-dev/release/publish/test/cut-stable.spec.ts +++ b/ng-dev/release/publish/test/cut-stable.spec.ts @@ -19,69 +19,70 @@ import { } from './test-utils/staging-test'; import {testTmpDir} from '../../../utils/testing'; import {SandboxGitRepo} from '../../../utils/testing'; +import { ActiveReleaseTrains } from '../../versioning'; describe('cut stable action', () => { it('should not activate if a feature-freeze release-train is active', async () => { expect( - await CutStableAction.isActive({ + await CutStableAction.isActive(new ActiveReleaseTrains({ 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 activate if release-candidate release-train is active', async () => { expect( - await CutStableAction.isActive({ + await CutStableAction.isActive(new ActiveReleaseTrains({ // 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(true); }); it('should not activate if no FF/RC release-train is active', async () => { expect( - await CutStableAction.isActive({ + await CutStableAction.isActive(new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }), + })), ).toBe(false); }); it('should create a proper new version and select correct branch', async () => { - const action = setupReleaseActionForTesting(CutStableAction, { + const action = setupReleaseActionForTesting(CutStableAction, new ActiveReleaseTrains({ // 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')), - }); + })); await expectStagingAndPublishWithCherryPick(action, '10.1.x', '10.1.0', 'latest'); }); it('should not tag the previous latest release-train if a minor has been cut', async () => { - const action = setupReleaseActionForTesting(CutStableAction, { + const action = setupReleaseActionForTesting(CutStableAction, new ActiveReleaseTrains({ // 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')), - }); + })); await expectStagingAndPublishWithCherryPick(action, '10.1.x', '10.1.0', 'latest'); expect(externalCommands.invokeSetNpmDistCommand).toHaveBeenCalledTimes(0); }); it('should tag the previous latest release-train if a major has been cut', async () => { - const action = setupReleaseActionForTesting(CutStableAction, { + const action = setupReleaseActionForTesting(CutStableAction, new ActiveReleaseTrains({ // No longer in feature-freeze but in release-candidate phase. releaseCandidate: new ReleaseTrain('11.0.x', parse('11.0.0-rc.0')), next: new ReleaseTrain('master', parse('10.2.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }); + })); // Ensure that the NPM dist tag is set only for packages that were available in the previous // major version. A spy has already been installed on the function. @@ -106,11 +107,11 @@ describe('cut stable action', () => { async () => { const action = setupReleaseActionForTesting( CutStableAction, - { + new ActiveReleaseTrains({ 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')), - }, + }), true, {useSandboxGitClient: true}, ); diff --git a/ng-dev/release/publish/test/move-next-into-feature-freeze.spec.ts b/ng-dev/release/publish/test/move-next-into-feature-freeze.spec.ts index 29787b23ae..912d5f4255 100644 --- a/ng-dev/release/publish/test/move-next-into-feature-freeze.spec.ts +++ b/ng-dev/release/publish/test/move-next-into-feature-freeze.spec.ts @@ -7,6 +7,7 @@ */ import {SandboxGitRepo} from '../../../utils/testing'; +import { ActiveReleaseTrains } from '../../versioning'; import {ReleaseTrain} from '../../versioning/release-trains'; import {MoveNextIntoFeatureFreezeAction} from '../actions/move-next-into-feature-freeze'; @@ -19,53 +20,53 @@ import {changelogPattern, parse} from './test-utils/test-utils'; describe('move next into feature-freeze action', () => { it('should not activate if a feature-freeze release-train is active', async () => { expect( - await MoveNextIntoFeatureFreezeAction.isActive({ + await MoveNextIntoFeatureFreezeAction.isActive(new ActiveReleaseTrains({ 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 MoveNextIntoFeatureFreezeAction.isActive({ + await MoveNextIntoFeatureFreezeAction.isActive(new ActiveReleaseTrains({ // 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 minor', async () => { expect( - await MoveNextIntoFeatureFreezeAction.isActive({ + await MoveNextIntoFeatureFreezeAction.isActive(new ActiveReleaseTrains({ 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 () => { expect( - await MoveNextIntoFeatureFreezeAction.isActive({ + await MoveNextIntoFeatureFreezeAction.isActive(new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('11.0.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }), + })), ).toBe(true); }); it('should create pull requests and feature-freeze branch', async () => { await expectBranchOffActionToRun( MoveNextIntoFeatureFreezeAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }, + }), /* isNextPublishedToNpm */ true, '10.2.0-next.0', '10.1.0-next.1', @@ -81,11 +82,11 @@ describe('move next into feature-freeze action', () => { it('should not increment the version', async () => { await expectBranchOffActionToRun( MoveNextIntoFeatureFreezeAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }, + }), /* isNextPublishedToNpm */ false, '10.2.0-next.0', '10.1.0-next.0', @@ -99,11 +100,11 @@ describe('move next into feature-freeze action', () => { async () => { const {action, buildChangelog} = prepareBranchOffActionForChangelog( MoveNextIntoFeatureFreezeAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }, + }), /* isNextPublishedToNpm */ false, '10.2.0-next.0', '10.1.0-next.0', @@ -139,11 +140,11 @@ describe('move next into feature-freeze action', () => { it('should generate release notes capturing changes to the previous next pre-release', async () => { const {action, buildChangelog} = prepareBranchOffActionForChangelog( MoveNextIntoFeatureFreezeAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }, + }), /* isNextPublishedToNpm */ true, '10.2.0-next.0', '10.1.0-next.1', diff --git a/ng-dev/release/publish/test/move-next-into-release-candidate.spec.ts b/ng-dev/release/publish/test/move-next-into-release-candidate.spec.ts index 7cc0bece6e..ff2763b492 100644 --- a/ng-dev/release/publish/test/move-next-into-release-candidate.spec.ts +++ b/ng-dev/release/publish/test/move-next-into-release-candidate.spec.ts @@ -7,6 +7,7 @@ */ import {SandboxGitRepo} from '../../../utils/testing'; +import { ActiveReleaseTrains } from '../../versioning'; import {ReleaseTrain} from '../../versioning/release-trains'; import {MoveNextIntoReleaseCandidateAction} from '../actions/move-next-into-release-candidate'; @@ -19,42 +20,42 @@ import {changelogPattern, parse} from './test-utils/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({ + await MoveNextIntoReleaseCandidateAction.isActive(new ActiveReleaseTrains({ 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({ + await MoveNextIntoReleaseCandidateAction.isActive(new ActiveReleaseTrains({ // 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({ + await MoveNextIntoReleaseCandidateAction.isActive(new ActiveReleaseTrains({ 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({ + await MoveNextIntoReleaseCandidateAction.isActive(new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }), + })), ).toBe(true); }); @@ -64,11 +65,11 @@ describe('move next into release-candidate action', () => { it('should update the version regardless', async () => { await expectBranchOffActionToRun( MoveNextIntoReleaseCandidateAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }, + }), /* isNextPublishedToNpm */ false, '10.2.0-next.0', '10.1.0-rc.0', @@ -82,11 +83,11 @@ describe('move next into release-candidate action', () => { async () => { const {action, buildChangelog} = prepareBranchOffActionForChangelog( MoveNextIntoReleaseCandidateAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }, + }), /* isNextPublishedToNpm */ false, '10.2.0-next.0', '10.1.0-rc.0', @@ -122,11 +123,11 @@ describe('move next into release-candidate action', () => { it('should generate release notes capturing changes to the previous next pre-release', async () => { const {action, buildChangelog} = prepareBranchOffActionForChangelog( MoveNextIntoReleaseCandidateAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }, + }), /* isNextPublishedToNpm */ true, '10.2.0-next.0', '10.1.0-rc.0', @@ -154,11 +155,11 @@ describe('move next into release-candidate action', () => { it('should create pull requests and new version-branch', async () => { await expectBranchOffActionToRun( MoveNextIntoReleaseCandidateAction, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.3')), - }, + }), /* isNextPublishedToNpm */ true, '10.2.0-next.0', '10.1.0-rc.0', diff --git a/ng-dev/release/publish/test/tag-recent-major-as-latest.spec.ts b/ng-dev/release/publish/test/tag-recent-major-as-latest.spec.ts index 7bf7fae563..4d489f1ed7 100644 --- a/ng-dev/release/publish/test/tag-recent-major-as-latest.spec.ts +++ b/ng-dev/release/publish/test/tag-recent-major-as-latest.spec.ts @@ -7,6 +7,7 @@ */ import {matchesVersion} from '../../../utils/testing'; +import { ActiveReleaseTrains } from '../../versioning'; import {ReleaseTrain} from '../../versioning/release-trains'; import {TagRecentMajorAsLatest} from '../actions/tag-recent-major-as-latest'; import * as externalCommands from '../external-commands'; @@ -22,11 +23,11 @@ describe('tag recent major as latest action', () => { const {releaseConfig} = getTestConfigurationsForAction(); expect( await TagRecentMajorAsLatest.isActive( - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.1')), - }, + }), releaseConfig, ), ).toBe(false); @@ -47,11 +48,11 @@ describe('tag recent major as latest action', () => { expect( await TagRecentMajorAsLatest.isActive( - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.0')), - }, + }), releaseConfig, ), ).toBe(false); @@ -74,11 +75,11 @@ describe('tag recent major as latest action', () => { expect( await TagRecentMajorAsLatest.isActive( - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.0')), - }, + }), releaseConfig, ), ).toBe(false); @@ -98,11 +99,11 @@ describe('tag recent major as latest action', () => { expect( await TagRecentMajorAsLatest.isActive( - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.0')), - }, + }), releaseConfig, ), ).toBe(true); @@ -112,11 +113,11 @@ describe('tag recent major as latest action', () => { it('should re-tag the version in the NPM registry and update the Github release', async () => { const {instance, gitClient, releaseConfig, repo} = setupReleaseActionForTesting( TagRecentMajorAsLatest, - { + new ActiveReleaseTrains({ releaseCandidate: null, next: new ReleaseTrain('master', parse('10.1.0-next.0')), latest: new ReleaseTrain('10.0.x', parse('10.0.0')), - }, + }), ); // NPM `@latest` will point to a patch release of the previous major. diff --git a/ng-dev/release/versioning/active-release-trains.ts b/ng-dev/release/versioning/active-release-trains.ts index c549b9ba40..fe6ed142e4 100644 --- a/ng-dev/release/versioning/active-release-trains.ts +++ b/ng-dev/release/versioning/active-release-trains.ts @@ -16,14 +16,25 @@ import { VersionBranch, } from './version-branches'; -/** Interface describing determined active release trains for a project. */ -export interface ActiveReleaseTrains { +/** The active release trains for a project. */ +export class ActiveReleaseTrains { /** Release-train currently in the "release-candidate" or "feature-freeze" phase. */ - releaseCandidate: ReleaseTrain | null; - /** Release-train currently in the "latest" phase. */ - latest: ReleaseTrain; + readonly releaseCandidate: ReleaseTrain | null = this.trains.releaseCandidate || null; /** Release-train in the `next` phase. */ - next: ReleaseTrain; + readonly next: ReleaseTrain = this.trains.next; + /** Release-train currently in the "latest" phase. */ + readonly latest: ReleaseTrain = this.trains.latest + + constructor(private trains: { + releaseCandidate: ReleaseTrain | null, + next: ReleaseTrain, + latest: ReleaseTrain, + }) {} + + /** Whether the active release trains indicate the repository is in a feature freeze state. */ + isFeatureFreeze() { + return this.releaseCandidate !== null && this.releaseCandidate.version.prerelease[0] === 'next'; + } } /** Fetches the active release trains for the configured project. */ @@ -78,7 +89,7 @@ export async function fetchActiveReleaseTrains( ); } - return {releaseCandidate, latest, next}; + return new ActiveReleaseTrains({releaseCandidate, next, latest}); } /** Finds the currently active release trains from the specified version branches. */