fix(docs-infra): do not deploy as `archive` when major is not lower than stable (#39853)

Previously, a branch would be deployed as `archive` even if it had a
major version that was equal/higher than that of the stable branch (as
long as it was not the RC branch - i.e. not the most recent minor
branch). For example, with `11.0.x` as the stable branch  and `12.0.x`
as the RC branch, `11.1.x` would be deployed as archive.

Theoretically, we should never find ourselves in such a situation.
Typically, there will only be at most one minor branch most recent than
the stable one (and that branch will be the RC branch). However, it
is possible under unusual circumstances.

This commit adds additional checks to guard against this problem. It
also refactors the code in preparation of fixing an issue with
`rc.angular.io` redirects in the presence of a ServiceWorker, which will
require identifying whether there is an active RC version or not.
See #39760 for more details.

PR Close #39853
This commit is contained in:
George Kalpakas 2020-11-30 13:59:11 +02:00 committed by Misko Hevery
parent ab4e9e1d52
commit 1e39e493fb
2 changed files with 108 additions and 46 deletions

View File

@ -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 `<number>.<number>.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);
}

View File

@ -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 = {