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. */