Skip to content

Commit 2cec7e1

Browse files
authored
Merge pull request #17370 from github/make-developer-redirects-static
Make developer redirects static
2 parents dba95da + 7e0e865 commit 2cec7e1

9 files changed

Lines changed: 307348 additions & 3837 deletions

lib/redirects/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Redirects
2+
3+
Docs redirects are complex! Some reasons why:
4+
5+
* Docs URLs have changed many times over the years, whether because docs team members have renamed individual articles or made global changes (e.g., moving all `/articles` to `/github`).
6+
* Redirects can be hardcoded in frontmatter or generated via code in this directory (or both!).
7+
* Live docs and archived docs require different redirect handling because they may have differently formatted URLs (e.g., legacy `/enterprise/2.17` vs. modern `/enterprise-server@2.22`).
8+
9+
Read on for more about how redirects work under the hood.
10+
11+
## Precompiled redirects
12+
13+
Precompiled redirects account for the majority of the docs site's redirect handling.
14+
15+
When [`lib/warm-server.js`](lib/warm-server.js) runs on server start, it creates all pages in the site by instantiating the [`Page` class](lib/page.js) for each content file, then passes the pages to `lib/redirects/precompile.js` to create redirects. The precompile script runs `lib/redirects/permalinks.js`, which:
16+
17+
1. Loops over each page's [permalinks](contributing/permalinks.md) and creates an array of legacy paths for each one (via `lib/redirects/get-old-paths-from-permalink.js`). For example, a permalink that starts with `/en/enterprise-server@2.22` results in an array that includes `/en/enterprise/2.22`, `/enterprise/2.22`, etc.
18+
2. Loops over each page's [frontmatter `redirect_from` entries](content/README.md#redirect_from) and creates an array of legacy paths for each one (using the same handling as for permalinks).
19+
20+
The results comprise the `page.redirects` object, where the **keys are legacy paths** and the **values are current permalinks**.
21+
22+
Additionally, a [static JSON file](lib/redirects/static/developer.json) gets `require`d that contains keys with legacy developer.github.com paths (e.g., `/v4/object/app`) and values with new docs.github.com paths (e.g., `/graphql/reference/objects#app`).
23+
24+
All of the above are merged into a global redirects object. This object gets added to `req.context` via `middleware/context.js` and is made accessible on every request.
25+
26+
Because the redirects are precompiled via `warm-server`, that means `middleware/redirects/handle-redirects.js` just needs to do a simple lookup of the requested path in the redirects object.
27+
28+
### Archived Enterprise redirects
29+
30+
Archived Enterprise redirects account for a much smaller percentage of redirects on the docs site.
31+
32+
Some background on archival: a snapshot of the HTML files for each deprecated Enterprise Server version is archived in a separate repo and proxied to docs.github.com via `middleware/archived-enterprise-versions.js`.
33+
34+
Starting with Enterprise Server 2.18, we updated the archival process to start preserving frontmatter and permalink redirects. But these redirects for 2.13 to 2.17 are not recoverable.
35+
36+
As a workaround for these lost redirects, we have two files in `lib/redirects/static`:
37+
38+
* `archived-redirects-from-213-to-217.json`
39+
40+
This file contains keys equal to old routes and values equal to new routes (aka snapshots of permalinks at the time) for versions 2.13 to 2.17. (The old routes were generated via `lib/redirects/get-old-paths-from-permalink.js`.)
41+
42+
* `archived-frontmatter-fallbacks.json`
43+
44+
This file contains an array of arrays, where the child arrays are a group of all frontmatter redirects for each content file. This is essentially list of all the historical paths for the articles in old versions. The problem is, we don't know which historical paths correspond to which versions.
45+
46+
Here's how the `middleware/archived-enterprise-versions.js` fallback works: if someone tries to access an article that was updated via a now-lost frontmatter redirect (for example, an article at the path `/en/enterprise/2.15/user/articles/viewing-contributions-on-your-profile-page`), the middleware will first look for a redirect in `archived-redirects-from-213-to-217.json`. If it does not find one, it will look for a child array in `archived-frontmatter-fallbacks.json` that contains the requested path. If it finds a relevant array, it will try accessing all the other paths in the array until it finds one that returns a 200. For this example, it would successfully reach `/en/enterprise/2.15/user/articles/viewing-contributions-on-your-profile` (no `-page`).
47+
48+
This is admittedly an inefficient brute-force approach. But requests for archived docs <2.18 are getting less and less common as organizations upgrade their Enterprise instances, and all the expensive calculation happens in the middleware on page request, not on server warmup, so at least it's a relatively isolated process.
49+
50+
## Tests
51+
52+
Redirect tests are mainly found in `tests/routing/*`, with some additional tests in `tests/rendering/server.js`.
53+
54+
The `tests/fixtures/*` directory includes `developer-redirects.json`, `graphql-redirects.json`, and `rest-redirects.json`.

lib/redirects/get-docs-path-from-developer-path.js

Lines changed: 0 additions & 90 deletions
This file was deleted.

lib/redirects/get-old-paths-from-permalink.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,10 @@ module.exports = function getOldPathsFromPath (currentPath, languageCode, curren
2929
.replace(`/${languageCode}/enterprise/${latest}/user/insights`, '/insights'))
3030

3131
// create old path /desktop/guides from current path /desktop
32-
if (!currentPath.includes('/guides')) {
33-
oldPaths.add(currentPath
34-
.replace('/desktop', '/desktop/guides'))
35-
}
36-
3732
// create old path /admin/guides from current path /admin
3833
if (!currentPath.includes('/guides')) {
3934
oldPaths.add(currentPath
35+
.replace('/desktop', '/desktop/guides')
4036
.replace('/admin', '/admin/guides'))
4137
}
4238

@@ -90,10 +86,6 @@ module.exports = function getOldPathsFromPath (currentPath, languageCode, curren
9086
oldPaths.add(oldPath
9187
.replace(`/enterprise-server@${latest}`, '/enterprise-server'))
9288

93-
// create old path /enterprise-server@latest from new path /enterprise-server@<latest>
94-
oldPaths.add(oldPath
95-
.replace(`/enterprise-server@${latest}`, '/enterprise-server@latest'))
96-
9789
if (!patterns.adminProduct.test(oldPath)) {
9890
// create old path /enterprise/<version>/user/foo from new path /enterprise-server@<version>/foo
9991
oldPaths.add(currentPath
@@ -116,9 +108,6 @@ module.exports = function getOldPathsFromPath (currentPath, languageCode, curren
116108

117109
// add language code
118110
oldPaths.add(getPathWithLanguage(oldPath, languageCode))
119-
120-
// create /enterprise from /enterprise/latest
121-
oldPaths.add(oldPath.replace(`/enterprise/${latest}`, '/enterprise'))
122111
})
123112

124113
// exclude any empty old paths that may have been derived

lib/redirects/precompile.js

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,20 @@
1-
const { latest } = require('../enterprise-server-releases')
2-
const getDocsPathFromDevPath = require('../redirects/get-docs-path-from-developer-path')
3-
const DEVELOPER_ROUTES = require('../redirects/static/developer-docs-routes-for-supported-versions')
1+
const developerRedirects = require('../redirects/static/developer')
2+
const { latest } = require('../../lib/enterprise-server-releases')
3+
const latestDevRedirects = {}
4+
5+
// Replace hardcoded 'latest' with real value
6+
Object.entries(developerRedirects).forEach(([oldPath, newPath]) => {
7+
latestDevRedirects[oldPath] = newPath.replace('enterprise-server@latest', `enterprise-server@${latest}`)
8+
})
49

510
// This function runs at server warmup and precompiles possible redirect routes.
611
// It outputs them in key-value pairs within a neat Javascript object: { oldPath: newPath }
7-
module.exports = function precompileRedirects (pageList, pageMap) {
8-
const allRedirects = {}
12+
module.exports = function precompileRedirects (pageList) {
13+
const allRedirects = Object.assign({}, latestDevRedirects)
914

10-
// 1. CURRENT PAGES PERMALINKS AND FRONTMATTER
15+
// CURRENT PAGES PERMALINKS AND FRONTMATTER
1116
// create backwards-compatible old paths for page permalinks and frontmatter redirects
1217
pageList.forEach(page => Object.assign(allRedirects, page.buildRedirects()))
1318

14-
// 2. DEVELOPER ROUTES FOR CURRENTLY SUPPORTED VERSIONS
15-
// From the list of developer docs routes, create new docs.github.com-style paths.
16-
// Note that the list only includes supported enterprise paths up to 2.21, which is
17-
// the last version that was available on developer.github.com before the migration.
18-
DEVELOPER_ROUTES.forEach(developerRoute => {
19-
const newPath = getDocsPathFromDevPath(developerRoute, allRedirects, pageMap)
20-
21-
// add the redirect to the object
22-
allRedirects[developerRoute] = newPath
23-
24-
// also add a variation with language code
25-
const developerRouteWithLanguage = `/en${developerRoute}`
26-
allRedirects[developerRouteWithLanguage] = newPath
27-
28-
// although we only support developer Enterprise paths up to 2.21, we make
29-
// an exception to always redirect versionless paths to the latest version
30-
if (developerRoute.includes('/2.21/')) {
31-
const newPathOnLatestVersion = newPath.replace('@2.21/', `@${latest}/`)
32-
const developerRouteWithoutVersion = developerRoute.replace('/2.21/', '/')
33-
const developerRouteWithLanguageWithoutVersion = `/en${developerRouteWithoutVersion}`
34-
allRedirects[developerRouteWithoutVersion] = newPathOnLatestVersion
35-
allRedirects[developerRouteWithLanguageWithoutVersion] = newPathOnLatestVersion
36-
}
37-
})
38-
3919
return allRedirects
4020
}

0 commit comments

Comments
 (0)