diff --git a/aio/scripts/deploy-to-firebase.js b/aio/scripts/deploy-to-firebase.js index 0869a70cca..caba35ff19 100644 --- a/aio/scripts/deploy-to-firebase.js +++ b/aio/scripts/deploy-to-firebase.js @@ -18,6 +18,7 @@ module.exports = { computeDeploymentsInfo, computeInputVars, getLatestCommit, + getMostRecentMinorBranch, }; // Run @@ -131,47 +132,58 @@ function computeDeploymentsInfo( }, }; + // If the current branch is `master`, deploy as `next`. if (currentBranch === 'master') { return [deploymentInfoPerTarget.next]; - } else if (currentBranch === stableBranch) { - return [deploymentInfoPerTarget.stable]; - } else { - const stableBranchMajorVersion = computeMajorVersion(stableBranch); - - // Find the branch that has highest minor version for the given `currentBranchMajorVersion`. - const mostRecentMinorVersionBranch = - // List the branches that start with the major version. - getRemoteRefs(`refs/heads/${currentBranchMajorVersion}.*.x`) - // Extract the version number. - .map(line => line.split('/')[2]) - // Sort by the minor version. - .sort((a, b) => a.split('.')[1] - b.split('.')[1]) - // Get the highest version. - .pop(); - - // Do not deploy if it is not the latest branch for the given major version. - // NOTE: At this point, we know the current branch is not the stable branch. - if (currentBranch !== mostRecentMinorVersionBranch) { - return [ - skipDeployment( - `Skipping deploy of branch "${currentBranch}" to Firebase.\n` + - 'There is a more recent branch with the same major version: ' + - `"${mostRecentMinorVersionBranch}"`), - ]; - } - - return (currentBranchMajorVersion < stableBranchMajorVersion) ? - // This is the latest minor version for a major that is less than the stable major version: - // Deploy as `archive`. - [deploymentInfoPerTarget.archive] : - // This is the latest minor version for a major that is equal or greater than the stable - // major version, but not the stable version itself: - // Deploy as `rc`. - [deploymentInfoPerTarget.rc]; } - // We should never get here. - throw new Error('Failed to determine deployment info.'); + // Determine if there is an active RC version by checking whether the most recent minor branch is + // the stable branch or not. + const mostRecentMinorBranch = getMostRecentMinorBranch(); + const rcBranch = (mostRecentMinorBranch !== stableBranch) ? mostRecentMinorBranch : null; + + // If the current branch is the RC branch, deploy as `rc`. + if (currentBranch === rcBranch) { + return [deploymentInfoPerTarget.rc]; + } + + // If the current branch is the stable branch, deploy as `stable`. + if (currentBranch === stableBranch) { + return [deploymentInfoPerTarget.stable]; + } + + // If we get here, it means that the current branch is neither `master`, nor the RC or stable + // branches. At this point, we may only deploy as `archive` and only if the following criteria are + // met: + // 1. The current branch must have the highest minor version among all branches with the same + // major version. + // 2. The current branch must have a major version that is lower than the stable major version. + + // Do not deploy if it is not the branch with the highest minor for the given major version. + const mostRecentMinorBranchForMajor = getMostRecentMinorBranch(currentBranchMajorVersion); + if (currentBranch !== mostRecentMinorBranchForMajor) { + return [ + skipDeployment( + `Skipping deploy of branch "${currentBranch}" to Firebase.\n` + + 'There is a more recent branch with the same major version: ' + + `"${mostRecentMinorBranchForMajor}"`), + ]; + } + + // Do not deploy if it does not have a lower major version than stable. + const stableBranchMajorVersion = computeMajorVersion(stableBranch); + if (currentBranchMajorVersion >= stableBranchMajorVersion) { + return [ + skipDeployment( + `Skipping deploy of branch "${currentBranch}" to Firebase.\n` + + 'This branch has an equal or higher major version than the stable branch ' + + `("${stableBranch}") and is not the most recent minor branch.`), + ]; + } + + // This is the highest minor version for a major that is lower than the stable major version: + // Deploy as `archive`. + return [deploymentInfoPerTarget.archive]; } function computeInputVars({ @@ -230,6 +242,23 @@ function getRemoteRefs(refOrPattern, remote = NG_REMOTE_URL) { return exec(`git ls-remote ${remote} ${refOrPattern}`, {silent: true}).trim().split('\n'); } +function getMostRecentMinorBranch(major = '*') { + // List the branches that start with the given major version (or any major if none given). + return getRemoteRefs(`refs/heads/${major}.*.x`) + // Extract the branch name. + .map(line => line.split('/')[2]) + // Filter out branches that are not of the format `..x`. + .filter(name => /^\d+\.\d+\.x$/.test(name)) + // Sort by version. + .sort((a, b) => { + const [majorA, minorA] = a.split('.'); + const [majorB, minorB] = b.split('.'); + return (majorA - majorB) || (minorA - minorB); + }) + // Get the branch corresponding to the highest version. + .pop(); +} + function getLatestCommit(branchName, remote = undefined) { return getRemoteRefs(branchName, remote)[0].slice(0, 40); } diff --git a/aio/scripts/deploy-to-firebase.spec.js b/aio/scripts/deploy-to-firebase.spec.js index dbe87d69a7..f65b28bc50 100644 --- a/aio/scripts/deploy-to-firebase.spec.js +++ b/aio/scripts/deploy-to-firebase.spec.js @@ -2,11 +2,17 @@ 'use strict'; const {execSync} = require('child_process'); -const {computeDeploymentsInfo, computeInputVars, getLatestCommit} = require('./deploy-to-firebase'); +const { + computeDeploymentsInfo, + computeInputVars, + getLatestCommit, + getMostRecentMinorBranch, +} = require('./deploy-to-firebase'); describe('deploy-to-firebase:', () => { - // Pre-computed latest commits to avoid unnecessary re-computations. + // Pre-computed values to avoid unnecessary re-computations. + const mostRecentMinorBranch = getMostRecentMinorBranch(); const latestCommits = { master: getLatestCommit('master'), '2.1.x': getLatestCommit('2.1.x'), @@ -14,6 +20,7 @@ describe('deploy-to-firebase:', () => { '4.3.x': getLatestCommit('4.3.x'), '4.4.x': getLatestCommit('4.4.x'), '9.1.x': getLatestCommit('9.1.x'), + [mostRecentMinorBranch]: getLatestCommit(mostRecentMinorBranch), }; // Helpers @@ -236,9 +243,9 @@ describe('deploy-to-firebase:', () => { CI_REPO_OWNER: 'angular', CI_REPO_NAME: 'angular', CI_PULL_REQUEST: 'false', - CI_BRANCH: '4.4.x', + CI_BRANCH: mostRecentMinorBranch, CI_STABLE_BRANCH: '2.2.x', - CI_COMMIT: latestCommits['4.4.x'], + CI_COMMIT: latestCommits[mostRecentMinorBranch], })).toEqual([ { deployEnv: 'rc', @@ -252,13 +259,20 @@ describe('deploy-to-firebase:', () => { }); it('rc - deploy success - major same as stable, minor higher', () => { + // Create a stable branch name that has the same major and lower minor than + // `mostRecentMinorBranch`. + // NOTE: Since `mostRecentMinorBranch` can have a minor version of `0`, we may end up with `-1` + // as the minor version for stable. This is a hack, but it works ¯\_(ツ)_/¯ + const stableBranch = mostRecentMinorBranch.replace( + /^(\d+)\.(\d+)\.x$/, (_, major, minor) => `${major}.${minor - 1}.x`); + expect(getDeploymentsInfoFor({ CI_REPO_OWNER: 'angular', CI_REPO_NAME: 'angular', CI_PULL_REQUEST: 'false', - CI_BRANCH: '2.4.x', - CI_STABLE_BRANCH: '2.2.x', - CI_COMMIT: latestCommits['2.4.x'], + CI_BRANCH: mostRecentMinorBranch, + CI_STABLE_BRANCH: stableBranch, + CI_COMMIT: latestCommits[mostRecentMinorBranch], })).toEqual([ { deployEnv: 'rc', @@ -276,7 +290,7 @@ describe('deploy-to-firebase:', () => { CI_REPO_OWNER: 'angular', CI_REPO_NAME: 'angular', CI_PULL_REQUEST: 'false', - CI_BRANCH: '2.4.x', + CI_BRANCH: mostRecentMinorBranch, CI_STABLE_BRANCH: '2.2.x', CI_COMMIT: 'DUMMY_TEST_COMMIT', })).toEqual([ @@ -284,7 +298,7 @@ describe('deploy-to-firebase:', () => { skipped: true, reason: 'Skipping deploy because DUMMY_TEST_COMMIT is not the latest commit ' + - `(${latestCommits['2.4.x']}).`, + `(${latestCommits[mostRecentMinorBranch]}).`, }, ]); }); @@ -325,6 +339,25 @@ describe('deploy-to-firebase:', () => { ]); }); + it('rc - skip deploy - major higher than stable but lower than most recent, minor latest', () => { + expect(getDeploymentsInfoFor({ + CI_REPO_OWNER: 'angular', + CI_REPO_NAME: 'angular', + CI_PULL_REQUEST: 'false', + CI_BRANCH: '4.4.x', + CI_STABLE_BRANCH: '2.4.x', + CI_COMMIT: latestCommits['4.4.x'], + })).toEqual([ + { + skipped: true, + reason: + 'Skipping deploy of branch "4.4.x" to Firebase.\n' + + 'This branch has an equal or higher major version than the stable branch ("2.4.x") ' + + 'and is not the most recent minor branch.', + }, + ]); + }); + it('integration - should run the main script without error', () => { const cmd = `"${process.execPath}" "${__dirname}/deploy-to-firebase" --dry-run`; const env = {