fix(docs-infra): fix redirecting `rc.angular.io` to `angular.io` when no active RC (#39853)
Currently there is an issue with redirecting `rc.angular.io` to `angular.io` when there is no active RC. If a user has visited `rc.angular.io` before and has a ServiceWorker registered for that subdomain, they will never "see" the redirect to `angular.io`. This commit fixes the problem by doing an additional deployment from the stable branch to the `rc-angular-io-site` Firebase site when there is no active RC. This additional deployment will ensure that: 1. Users will be temporarily redirected from `rc.angular.io` to `angular.io`. 2. Users with a registered ServiceWorker (who don't see the redirect) will have their ServiceWorker unregistered on the next visit. 3. The content on both sites is identical. See #39760 for more details on the problem and the solution. NOTE: As mentioned in #39760, for this fix to take affect, we need to remove the redirect from `rc.angular.io` to `angular.io` in the Firebase console for site `rc-angular-io-site`. Fixes #39760 PR Close #39853
This commit is contained in:
parent
1e39e493fb
commit
cf1f5a1e37
|
@ -4,7 +4,7 @@
|
||||||
//
|
//
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const {cd, cp, exec, set} = require('shelljs');
|
const {cd, cp, exec, mv, sed, set} = require('shelljs');
|
||||||
|
|
||||||
set('-e');
|
set('-e');
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ const NG_REMOTE_URL = `https://github.com/${REPO_SLUG}.git`;
|
||||||
module.exports = {
|
module.exports = {
|
||||||
computeDeploymentsInfo,
|
computeDeploymentsInfo,
|
||||||
computeInputVars,
|
computeInputVars,
|
||||||
|
computeMajorVersion,
|
||||||
getLatestCommit,
|
getLatestCommit,
|
||||||
getMostRecentMinorBranch,
|
getMostRecentMinorBranch,
|
||||||
};
|
};
|
||||||
|
@ -122,6 +123,17 @@ function computeDeploymentsInfo(
|
||||||
preDeployActions: [build, checkPayloadSize],
|
preDeployActions: [build, checkPayloadSize],
|
||||||
postDeployActions: [testPwaScore],
|
postDeployActions: [testPwaScore],
|
||||||
},
|
},
|
||||||
|
// Config for deploying the stable build to the RC Firebase site when there is no active RC.
|
||||||
|
// See https://github.com/angular/angular/issues/39760 for more info on the purpose of this
|
||||||
|
// special deployment.
|
||||||
|
noActiveRc: {
|
||||||
|
deployEnv: 'stable',
|
||||||
|
projectId: 'angular-io',
|
||||||
|
siteId: 'rc-angular-io-site',
|
||||||
|
deployedUrl: 'https://rc.angular.io/',
|
||||||
|
preDeployActions: [removeServiceWorker, redirectToAngularIo],
|
||||||
|
postDeployActions: [testNoActiveRcDeployment],
|
||||||
|
},
|
||||||
archive: {
|
archive: {
|
||||||
deployEnv: 'archive',
|
deployEnv: 'archive',
|
||||||
projectId: 'angular-io',
|
projectId: 'angular-io',
|
||||||
|
@ -149,7 +161,17 @@ function computeDeploymentsInfo(
|
||||||
|
|
||||||
// If the current branch is the stable branch, deploy as `stable`.
|
// If the current branch is the stable branch, deploy as `stable`.
|
||||||
if (currentBranch === stableBranch) {
|
if (currentBranch === stableBranch) {
|
||||||
return [deploymentInfoPerTarget.stable];
|
return (rcBranch !== null) ?
|
||||||
|
// There is an active RC version. Only deploy to the `stable` project/site.
|
||||||
|
[deploymentInfoPerTarget.stable] :
|
||||||
|
// There is no active RC version. In addition to deploying to the `stable` project/site,
|
||||||
|
// deploy to `rc` to ensure it redirects to `stable`.
|
||||||
|
// See https://github.com/angular/angular/issues/39760 for more info on the purpose of this
|
||||||
|
// special deployment.
|
||||||
|
[
|
||||||
|
deploymentInfoPerTarget.stable,
|
||||||
|
deploymentInfoPerTarget.noActiveRc,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get here, it means that the current branch is neither `master`, nor the RC or stable
|
// If we get here, it means that the current branch is neither `master`, nor the RC or stable
|
||||||
|
@ -263,6 +285,21 @@ function getLatestCommit(branchName, remote = undefined) {
|
||||||
return getRemoteRefs(branchName, remote)[0].slice(0, 40);
|
return getRemoteRefs(branchName, remote)[0].slice(0, 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function redirectToAngularIo() {
|
||||||
|
// Update the Firebase hosting configuration redirect all non-file requests (i.e. requests that do
|
||||||
|
// not contain a dot in their last path segment) to `angular.io`.
|
||||||
|
// See https://firebase.google.com/docs/hosting/full-config#redirects.
|
||||||
|
const redirectRule =
|
||||||
|
'{"type": 302, "regex": "^(.*/[^./]*)$", "destination": "https://angular.io:1"}';
|
||||||
|
sed('-i', /(\s*)"redirects": \[/, `$&\n$1 ${redirectRule},\n`, 'firebase.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeServiceWorker() {
|
||||||
|
// Rename the SW manifest (`ngsw.json`). This will cause the ServiceWorker to unregister itself.
|
||||||
|
// See https://angular.io/guide/service-worker-devops#fail-safe.
|
||||||
|
mv('dist/ngsw.json', 'dist/ngsw.json.bak');
|
||||||
|
}
|
||||||
|
|
||||||
function serializeActions(actions) {
|
function serializeActions(actions) {
|
||||||
return actions.map(fn => fn.name).join(', ');
|
return actions.map(fn => fn.name).join(', ');
|
||||||
}
|
}
|
||||||
|
@ -271,6 +308,41 @@ function skipDeployment(reason) {
|
||||||
return {reason, skipped: true};
|
return {reason, skipped: true};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testNoActiveRcDeployment({deployedUrl}) {
|
||||||
|
const deployedOrigin = deployedUrl.replace(/\/$/, '');
|
||||||
|
|
||||||
|
// Ensure a request for `ngsw.json` returns 404.
|
||||||
|
const ngswJsonUrl = `${deployedOrigin}/ngsw.json`;
|
||||||
|
const ngswJsonScript = `https.get('${ngswJsonUrl}', res => console.log(res.statusCode))`;
|
||||||
|
const ngswJsonActualStatusCode = exec(`node --eval "${ngswJsonScript}"`, {silent: true}).trim();
|
||||||
|
const ngswJsonExpectedStatusCode = '404';
|
||||||
|
|
||||||
|
if (ngswJsonActualStatusCode !== ngswJsonExpectedStatusCode) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected '${ngswJsonUrl}' to return a status code of '${ngswJsonExpectedStatusCode}', ` +
|
||||||
|
`but it returned '${ngswJsonActualStatusCode}'.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure a request for `foo/bar` is redirected to `https://angular.io/foo/bar`.
|
||||||
|
const fooBarUrl = `${deployedOrigin}/foo/bar?baz=qux`;
|
||||||
|
const fooBarScript =
|
||||||
|
`https.get('${fooBarUrl}', res => console.log(res.statusCode, res.headers.location))`;
|
||||||
|
const [fooBarActualStatusCode, fooBarActualRedirectUrl] =
|
||||||
|
exec(`node --eval "${fooBarScript}"`, {silent: true}).trim().split(' ');
|
||||||
|
const fooBarExpectedStatusCode = '302';
|
||||||
|
const fooBarExpectedRedirectUrl = 'https://angular.io/foo/bar?baz=qux';
|
||||||
|
|
||||||
|
if (fooBarActualStatusCode !== fooBarExpectedStatusCode) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected '${fooBarUrl}' to return a status code of '${fooBarExpectedStatusCode}', but ` +
|
||||||
|
`it returned '${fooBarActualStatusCode}'.`);
|
||||||
|
} else if (fooBarActualRedirectUrl !== fooBarExpectedRedirectUrl) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected '${fooBarUrl}' to be redirected to '${fooBarExpectedRedirectUrl}', but it was ` +
|
||||||
|
`but it was redirected to '${fooBarActualRedirectUrl}'.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function testPwaScore({deployedUrl, minPwaScore}) {
|
function testPwaScore({deployedUrl, minPwaScore}) {
|
||||||
console.log('\n\n\n==== Run PWA-score tests. ====\n');
|
console.log('\n\n\n==== Run PWA-score tests. ====\n');
|
||||||
yarn(`test-pwa-score "${deployedUrl}" "${minPwaScore}"`);
|
yarn(`test-pwa-score "${deployedUrl}" "${minPwaScore}"`);
|
||||||
|
|
|
@ -5,6 +5,7 @@ const {execSync} = require('child_process');
|
||||||
const {
|
const {
|
||||||
computeDeploymentsInfo,
|
computeDeploymentsInfo,
|
||||||
computeInputVars,
|
computeInputVars,
|
||||||
|
computeMajorVersion,
|
||||||
getLatestCommit,
|
getLatestCommit,
|
||||||
getMostRecentMinorBranch,
|
getMostRecentMinorBranch,
|
||||||
} = require('./deploy-to-firebase');
|
} = require('./deploy-to-firebase');
|
||||||
|
@ -104,7 +105,7 @@ describe('deploy-to-firebase:', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stable - deploy success', () => {
|
it('stable - deploy success - active RC', () => {
|
||||||
expect(getDeploymentsInfoFor({
|
expect(getDeploymentsInfoFor({
|
||||||
CI_REPO_OWNER: 'angular',
|
CI_REPO_OWNER: 'angular',
|
||||||
CI_REPO_NAME: 'angular',
|
CI_REPO_NAME: 'angular',
|
||||||
|
@ -124,6 +125,34 @@ describe('deploy-to-firebase:', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('stable - deploy success - no active RC', () => {
|
||||||
|
expect(getDeploymentsInfoFor({
|
||||||
|
CI_REPO_OWNER: 'angular',
|
||||||
|
CI_REPO_NAME: 'angular',
|
||||||
|
CI_PULL_REQUEST: 'false',
|
||||||
|
CI_BRANCH: mostRecentMinorBranch,
|
||||||
|
CI_STABLE_BRANCH: mostRecentMinorBranch,
|
||||||
|
CI_COMMIT: latestCommits[mostRecentMinorBranch],
|
||||||
|
})).toEqual([
|
||||||
|
{
|
||||||
|
deployEnv: 'stable',
|
||||||
|
projectId: 'angular-io',
|
||||||
|
siteId: `v${computeMajorVersion(mostRecentMinorBranch)}-angular-io-site`,
|
||||||
|
deployedUrl: 'https://angular.io/',
|
||||||
|
preDeployActions: ['function:build', 'function:checkPayloadSize'],
|
||||||
|
postDeployActions: ['function:testPwaScore'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deployEnv: 'stable',
|
||||||
|
projectId: 'angular-io',
|
||||||
|
siteId: 'rc-angular-io-site',
|
||||||
|
deployedUrl: 'https://rc.angular.io/',
|
||||||
|
preDeployActions: ['function:removeServiceWorker', 'function:redirectToAngularIo'],
|
||||||
|
postDeployActions: ['function:testNoActiveRcDeployment'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('stable - skip deploy - commit not HEAD', () => {
|
it('stable - skip deploy - commit not HEAD', () => {
|
||||||
expect(getDeploymentsInfoFor({
|
expect(getDeploymentsInfoFor({
|
||||||
CI_REPO_OWNER: 'angular',
|
CI_REPO_OWNER: 'angular',
|
||||||
|
|
Loading…
Reference in New Issue