From 629632fc809ade575aa9b1a0fa1b4fca82f7f7d7 Mon Sep 17 00:00:00 2001 From: Rachael Sewell Date: Tue, 5 Nov 2024 13:07:08 -0800 Subject: [PATCH] Update GHES proxy middleware to proxy to individual GitHub Pages sites (#52856) Co-authored-by: Kevin Heis --- .../archived-enterprise-versions-assets.ts | 40 ++--- .../archived-enterprise-versions.ts | 165 ++++++++++++++---- src/archives/scripts/warmup-remotejson.js | 4 +- .../tests/deprecated-enterprise-versions.js | 6 +- src/assets/tests/static-assets-1.ts | 10 +- src/frame/lib/get-remote-json.js | 4 +- src/frame/middleware/helmet.ts | 31 +++- src/frame/tests/server.js | 4 - src/redirects/README.md | 5 +- src/release-notes/tests/release-notes-1.ts | 12 +- .../lib/enterprise-server-releases.js | 9 +- 11 files changed, 192 insertions(+), 98 deletions(-) diff --git a/src/archives/middleware/archived-enterprise-versions-assets.ts b/src/archives/middleware/archived-enterprise-versions-assets.ts index b3ec8908ddcb..5d7799777ee6 100644 --- a/src/archives/middleware/archived-enterprise-versions-assets.ts +++ b/src/archives/middleware/archived-enterprise-versions-assets.ts @@ -1,5 +1,3 @@ -import path from 'path' - import got from 'got' import type { Response, NextFunction } from 'express' @@ -14,13 +12,7 @@ import type { ExtendedRequest } from '@/types' // This module handles requests for the CSS and JS assets for // deprecated GitHub Enterprise versions by routing them to static content in -// help-docs-archived-enterprise-versions -// -// Note that as of GHES 3.2, we no longer store assets for deprecated versions -// in help-docs-archived-enterprise-versions. Instead, we store them in the -// Azure blob storage `githubdocs` in the `enterprise` container. All HTML files -// have been updated to use references to this blob storage for all assets. -// +// one of the docs-ghes- repos. // See also ./archived-enterprise-versions.js for non-CSS/JS paths export default async function archivedEnterpriseVersionsAssets( @@ -33,12 +25,13 @@ export default async function archivedEnterpriseVersionsAssets( // or /_next/static/foo.css if (!patterns.assetPaths.test(req.path)) return next() - // We now know the URL is either /enterprise/2.22/_next/static/foo.css - // or the regular /_next/static/foo.css. But we're only going to - // bother looking it up on https://github.github.com/help-docs-archived-enterprise-versions - // if the URL has the enterprise bit in it, or if the path was - // /_next/static/foo.css *and* its Referrer had the enterprise - // bit in it. + // The URL is either in the format + // /enterprise/2.22/_next/static/foo.css, + // /enterprise-server@, + // or /_next/static/foo.css. + // If the URL is prefixed with the enterprise version and release number + // or if the Referrer contains the enterprise version and release number, + // then we'll fetch it from the docs-ghes- repo. if ( !( patterns.getEnterpriseVersionNumber.test(req.path) || @@ -59,12 +52,17 @@ export default async function archivedEnterpriseVersionsAssets( const { isArchived, requestedVersion } = isArchivedVersion(req) if (!isArchived || !requestedVersion) return next() - const assetPath = req.path.replace(`/enterprise/${requestedVersion}`, '') + // In all of the `docs-ghes- repos. export default async function archivedEnterpriseVersions( req: ExtendedRequest, @@ -108,6 +104,7 @@ export default async function archivedEnterpriseVersions( const redirectCode = pathLanguagePrefixed(req.path) ? 301 : 302 + // Redirects for releases 3.0+ if (deprecatedWithFunctionalRedirects.includes(requestedVersion)) { const redirectTo = getRedirect(req.path, req.context) if (redirectTo) { @@ -138,8 +135,7 @@ export default async function archivedEnterpriseVersions( return res.redirect(redirectCode, `/${language}${newRedirectTo}`) } } - // redirect language-prefixed URLs like /en/enterprise/2.10 -> /enterprise/2.10 - // (this only applies to versions <2.13) + // For releases 2.13 and lower, redirect language-prefixed URLs like /en/enterprise/2.10 -> /enterprise/2.10 if ( req.path.startsWith('/en/') && versionSatisfiesRange(requestedVersion, `<${firstVersionDeprecatedOnNewSite}`) @@ -148,8 +144,7 @@ export default async function archivedEnterpriseVersions( return res.redirect(redirectCode, req.baseUrl + req.path.replace(/^\/en/, '')) } - // find redirects for versions between 2.13 and 2.17 - // starting with 2.18, we updated the archival script to create a redirects.json file + // Redirects for releases 2.13 - 2.17 if ( versionSatisfiesRange(requestedVersion, `>=${firstVersionDeprecatedOnNewSite}`) && versionSatisfiesRange(requestedVersion, `<=${lastVersionWithoutArchivedRedirectsFile}`) @@ -173,7 +168,8 @@ export default async function archivedEnterpriseVersions( return res.redirect(redirectCode, redirect) } } - + // Redirects for 2.18 - 3.0. Starting with 2.18, we updated the archival + // script to create a redirects.json file if ( versionSatisfiesRange(requestedVersion, `>${lastVersionWithoutArchivedRedirectsFile}`) && !deprecatedWithFunctionalRedirects.includes(requestedVersion) @@ -195,19 +191,25 @@ export default async function archivedEnterpriseVersions( return res.redirect(redirectCode, redirectJson[req.path]) } } - - const statsdTags = [`version:${requestedVersion}`] + // Retrieve the page from the archived repo const doGet = () => got(getProxyPath(req.path, requestedVersion), { throwHttpErrors: false, retry: retryConfiguration, timeout: timeoutConfiguration, }) + + const statsdTags = [`version:${requestedVersion}`] const r = await statsd.asyncTimer(doGet, 'archive_enterprise_proxy', [ ...statsdTags, `path:${req.path}`, ])() + if (r.statusCode === 200) { + const [, withoutLanguagePath] = splitByLanguage(req.path) + const isDeveloperPage = withoutLanguagePath?.startsWith( + `/enterprise/${requestedVersion}/developer`, + ) res.set('x-robots-tag', 'noindex') // make stubbed redirect files (which exist in versions <2.13) redirect with a 301 @@ -221,11 +223,74 @@ export default async function archivedEnterpriseVersions( cacheAggressively(res) + // Releases 3.2 and higher contain image asset paths with the + // old Azure Blob Storage URL. These need to be rewritten to + // the new archived enterprise repo URL. + if (versionSatisfiesRange(requestedVersion, `>=${firstReleaseStoredInBlobStorage}`)) { + r.body = r.body + .replaceAll( + `${OLD_AZURE_BLOB_ENTERPRISE_DIR}/${requestedVersion}/assets/cb-`, + `${ENTERPRISE_GH_PAGES_URL_PREFIX}${requestedVersion}/assets/cb-`, + ) + .replaceAll( + `${OLD_AZURE_BLOB_ENTERPRISE_DIR}/${requestedVersion}/`, + `${req.protocol}://${req.get('host')}/enterprise-server@${requestedVersion}/`, + ) + } + + // Releases 3.1 and lower were previously hosted in the + // help-docs-archived-enterprise-versions repo. Only the images + // were stored in the old Azure Blob Storage `github-images` container. + // The image paths all need to be updated to reference the images in the + // new archived enterprise repo's root assets directory. + if (versionSatisfiesRange(requestedVersion, `<${firstReleaseStoredInBlobStorage}`)) { + r.body = r.body.replaceAll( + `${OLD_GITHUB_IMAGES_ENTERPRISE_DIR}/${requestedVersion}`, + `${ENTERPRISE_GH_PAGES_URL_PREFIX}${requestedVersion}`, + ) + if (versionSatisfiesRange(requestedVersion, '<=2.18') && isDeveloperPage) { + r.body = r.body.replaceAll( + `${OLD_DEVELOPER_SITE_CONTAINER}/${requestedVersion}`, + `${ENTERPRISE_GH_PAGES_URL_PREFIX}${requestedVersion}/developer`, + ) + // Update all hrefs to add /developer to the path + r.body = r.body.replaceAll( + `="/enterprise/${requestedVersion}`, + `="/enterprise/${requestedVersion}/developer`, + ) + // The changelog is the only thing remaining on developer.github.com + r.body = r.body.replaceAll('href="/changes', 'href="https://developer.github.com/changes') + } + } + + // In all releases, some assets were incorrectly scraped and contain + // deep relative paths. For example, releases 3.4+ use the webp format + // for images. The URLs for those images were never rewritten to pull + // from the Azure Blob Storage container. This may be due to not + // updating our scraping tool to handle the new image types. There + // are additional images in older versions that also have a relative path. + // We want to update the URLs in the format + // "../../../../../../assets/" to prefix the assets directory with the + // new archived enterprise repo URL. + r.body = r.body.replaceAll( + /="(\.\.\/)*assets/g, + `="${ENTERPRISE_GH_PAGES_URL_PREFIX}${requestedVersion}/assets`, + ) + + // Fix broken hrefs on the 2.16 landing page + if (requestedVersion === '2.16' && req.path === '/en/enterprise/2.16') { + r.body = r.body.replaceAll('ref="/en/enterprise', 'ref="/en/enterprise/2.16') + } + + // Remove the search results container from the page, which removes a white + // box that prevents clicking on page links + r.body = r.body.replaceAll('
', '') + return res.send(r.body) } - - // from 2.13 to 2.17, we lost access to frontmatter redirects during the archival process - // this workaround finds potentially relevant frontmatter redirects in currently supported pages + // In releases 2.13 - 2.17, we lost access to frontmatter redirects + // during the archival process. This workaround finds potentially + // relevant frontmatter redirects in currently supported pages if ( versionSatisfiesRange(requestedVersion, `>=${firstVersionDeprecatedOnNewSite}`) && versionSatisfiesRange(requestedVersion, `<=${lastVersionWithoutArchivedRedirectsFile}`) @@ -244,18 +309,35 @@ export default async function archivedEnterpriseVersions( return next() } -// paths are slightly different depending on the version -// for >=2.13: /2.13/en/enterprise/2.13/user/articles/viewing-contributions-on-your-profile -// for <2.13: /2.12/user/articles/viewing-contributions-on-your-profile function getProxyPath(reqPath: string, requestedVersion: string) { - if (versionSatisfiesRange(requestedVersion, `>=${firstReleaseStoredInBlobStorage}`)) { + const [, withoutLanguagePath] = splitByLanguage(reqPath) + const isDeveloperPage = withoutLanguagePath?.startsWith( + `/enterprise/${requestedVersion}/developer`, + ) + + // This was the last release supported on developer.github.com + if (isDeveloperPage) { + const enterprisePath = `/enterprise/${requestedVersion}` + const newReqPath = reqPath.replace(enterprisePath, '') + return ENTERPRISE_GH_PAGES_URL_PREFIX + requestedVersion + newReqPath + } + + // Releases 2.18 and higher + if (versionSatisfiesRange(requestedVersion, `>${lastVersionWithoutArchivedRedirectsFile}`)) { const newReqPath = reqPath.includes('redirects.json') ? `/${reqPath}` : reqPath + '/index.html' - return `${REMOTE_ENTERPRISE_STORAGE_URL}/${requestedVersion}${newReqPath}` + return ENTERPRISE_GH_PAGES_URL_PREFIX + requestedVersion + newReqPath } - const proxyPath = versionSatisfiesRange(requestedVersion, `>=${firstVersionDeprecatedOnNewSite}`) - ? slash(path.join('/', requestedVersion, reqPath)) - : reqPath.replace(/^\/enterprise/, '') - return `https://github.github.com/help-docs-archived-enterprise-versions${proxyPath}` + + // Releases 2.13 - 2.17 + // redirect.json files don't exist for these versions + if (versionSatisfiesRange(requestedVersion, `>=2.13`)) { + return ENTERPRISE_GH_PAGES_URL_PREFIX + requestedVersion + reqPath + '/index.html' + } + + // Releases 2.12 and lower + const enterprisePath = `/enterprise/${requestedVersion}` + const newReqPath = reqPath.replace(enterprisePath, '') + return ENTERPRISE_GH_PAGES_URL_PREFIX + requestedVersion + newReqPath } // Module-level global cache object. @@ -276,7 +358,7 @@ function getFallbackRedirect(req: ExtendedRequest) { // // The keys are valid URLs that it can redirect to. I.e. these are // URLs that we definitely know are valid and will be found - // in https://github.com/github/help-docs-archived-enterprise-versions + // in one of the docs-ghes- repos. // The array values are possible URLs we deem acceptable redirect // sources. // But to avoid an unnecessary, O(n), loop every time, we turn this @@ -311,3 +393,14 @@ function getFallbackRedirect(req: ExtendedRequest) { return `/${language}${fallback}` } } + +function splitByLanguage(uri: string) { + let language = null + let withoutLanguage = uri + const match = uri.match(languagePrefixPathRegex) + if (match) { + language = match[1] + withoutLanguage = uri.replace(languagePrefixPathRegex, '/') + } + return [language, withoutLanguage] +} diff --git a/src/archives/scripts/warmup-remotejson.js b/src/archives/scripts/warmup-remotejson.js index 6cc031746eef..7a2d431d9958 100755 --- a/src/archives/scripts/warmup-remotejson.js +++ b/src/archives/scripts/warmup-remotejson.js @@ -41,9 +41,7 @@ function version2url(version) { semver.coerce(version).raw, semver.coerce(firstReleaseStoredInBlobStorage).raw, ) - return inBlobStorage - ? `https://githubdocs.azureedge.net/enterprise/${version}/redirects.json` - : `https://github.github.com/help-docs-archived-enterprise-versions/${version}/redirects.json` + return `https://github.github.com/docs-ghes-${version}/redirects.json` } function withArchivedRedirectsFile(version) { diff --git a/src/archives/tests/deprecated-enterprise-versions.js b/src/archives/tests/deprecated-enterprise-versions.js index 466364422cae..8dc3b7a3d461 100644 --- a/src/archives/tests/deprecated-enterprise-versions.js +++ b/src/archives/tests/deprecated-enterprise-versions.js @@ -122,7 +122,7 @@ describe('recently deprecated redirects', () => { expect(res.headers.vary).toContain('accept-language') expect(res.headers.vary).toContain('x-user-language') // This is based on - // https://github.com/github/help-docs-archived-enterprise-versions/blob/master/3.0/redirects.json + // https://github.com/github/docs-ghes-3.0/blob/main/redirects.json expect(res.headers.location).toBe( '/en/enterprise-server@3.0/get-started/learning-about-github/githubs-products', ) @@ -309,8 +309,8 @@ describe('JS and CSS assets', () => { expect(result.headers['x-is-archived']).toBeUndefined() }) - test('404 if the pathname contains URL characters (..)', async () => { - const result = await get('/enterprise/2.18/dist/index..css', { + test('404 if the pathname contains URL characters (../)', async () => { + const result = await get('/enterprise/2.18/dist/index../css', { headers: { Referrer: '/en/enterprise/2.18', }, diff --git a/src/assets/tests/static-assets-1.ts b/src/assets/tests/static-assets-1.ts index f948a94679b2..e0718a084709 100644 --- a/src/assets/tests/static-assets-1.ts +++ b/src/assets/tests/static-assets-1.ts @@ -83,30 +83,30 @@ describe('archived enterprise static assets', () => { const sampleCSS = '/* nice CSS */' nock('https://github.github.com') - .get('/help-docs-archived-enterprise-versions/2.21/_next/static/foo.css') + .get('/docs-ghes-2.21/_next/static/foo.css') .reply(200, sampleCSS, { 'content-type': 'text/css', 'content-length': `${sampleCSS.length}`, }) nock('https://github.github.com') - .get('/help-docs-archived-enterprise-versions/2.21/_next/static/only-on-proxy.css') + .get('/docs-ghes-2.21/_next/static/only-on-proxy.css') .reply(200, sampleCSS, { 'content-type': 'text/css', 'content-length': `${sampleCSS.length}`, }) nock('https://github.github.com') - .get('/help-docs-archived-enterprise-versions/2.3/_next/static/only-on-2.3.css') + .get('/docs-ghes-2.3/_next/static/only-on-2.3.css') .reply(200, sampleCSS, { 'content-type': 'text/css', 'content-length': `${sampleCSS.length}`, }) nock('https://github.github.com') - .get('/help-docs-archived-enterprise-versions/2.3/_next/static/fourofour.css') + .get('/docs-ghes-2.3/_next/static/fourofour.css') .reply(404, 'Not found', { 'content-type': 'text/plain', }) nock('https://github.github.com') - .get('/help-docs-archived-enterprise-versions/2.3/assets/images/site/logo.png') + .get('/docs-ghes-2.3/assets/images/site/logo.png') .reply(404, 'Not found', { 'content-type': 'text/plain', }) diff --git a/src/frame/lib/get-remote-json.js b/src/frame/lib/get-remote-json.js index ddaeb04476d4..237255fc3d5a 100644 --- a/src/frame/lib/get-remote-json.js +++ b/src/frame/lib/get-remote-json.js @@ -13,8 +13,8 @@ const inProd = process.env.NODE_ENV === 'production' // Wrapper on `got()` that is able to both cache in memory and on disk. // The on-disk caching is in `.remotejson/`. -// We use this for downloading `redirects.json` files from the -// help-docs-archived-enterprise-versions repo as a proxy. A lot of those +// We use this for downloading `redirects.json` files from one of the +// docs-ghes- repos as a proxy. A lot of those // .json files are large and they're also static which makes them // ideal for caching. // Note that there's 2 layers of caching here: diff --git a/src/frame/middleware/helmet.ts b/src/frame/middleware/helmet.ts index 93b62205c334..d5e3e8487575 100644 --- a/src/frame/middleware/helmet.ts +++ b/src/frame/middleware/helmet.ts @@ -2,9 +2,9 @@ import type { NextFunction, Request, Response } from 'express' import helmet from 'helmet' import { isArchivedVersion } from '@/archives/lib/is-archived-version.js' import versionSatisfiesRange from '@/versions/lib/version-satisfies-range.js' +import { languagePrefixPathRegex } from '@/languages/lib/languages.js' const isDev = process.env.NODE_ENV === 'development' -const AZURE_STORAGE_URL = 'githubdocs.azureedge.net' const GITHUB_DOMAINS = [ "'self'", 'github.com', @@ -30,15 +30,14 @@ const DEFAULT_OPTIONS = { // When doing local dev, especially in Safari, you need to add `ws:` // which NextJS uses for the hot module reloading. connectSrc: ["'self'", isDev && 'ws:'].filter(Boolean) as string[], - fontSrc: ["'self'", 'data:', AZURE_STORAGE_URL], - imgSrc: [...GITHUB_DOMAINS, 'data:', AZURE_STORAGE_URL, 'placehold.it'], + fontSrc: ["'self'", 'data:'], + imgSrc: [...GITHUB_DOMAINS, 'data:', 'placehold.it'], objectSrc: ["'self'"], // For use during development only! // `unsafe-eval` allows us to use a performant webpack devtool setting (eval) // https://webpack.js.org/configuration/devtool/#devtool - scriptSrc: ["'self'", 'data:', AZURE_STORAGE_URL, isDev && "'unsafe-eval'"].filter( - Boolean, - ) as string[], + scriptSrc: ["'self'", 'data:', isDev && "'unsafe-eval'"].filter(Boolean) as string[], + scriptSrcAttr: ["'self'"], frameSrc: [ ...GITHUB_DOMAINS, isDev && 'http://localhost:3000', @@ -50,7 +49,7 @@ const DEFAULT_OPTIONS = { 'https://www.youtube-nocookie.com', ].filter(Boolean) as string[], frameAncestors: isDev ? ['*'] : [...GITHUB_DOMAINS], - styleSrc: ["'self'", "'unsafe-inline'", 'data:', AZURE_STORAGE_URL], + styleSrc: ["'self'", "'unsafe-inline'", 'data:'], childSrc: ["'self'"], // exception for search in deprecated GHE versions manifestSrc: ["'self'"], upgradeInsecureRequests: isDev ? null : [], @@ -59,7 +58,7 @@ const DEFAULT_OPTIONS = { } const NODE_DEPRECATED_OPTIONS = structuredClone(DEFAULT_OPTIONS) -const { directives: ndDirs } = NODE_DEPRECATED_OPTIONS.contentSecurityPolicy +const ndDirs = NODE_DEPRECATED_OPTIONS.contentSecurityPolicy.directives ndDirs.scriptSrc.push( "'unsafe-eval'", "'unsafe-inline'", @@ -69,12 +68,20 @@ ndDirs.scriptSrc.push( ndDirs.connectSrc.push('https://www.google-analytics.com') ndDirs.imgSrc.push('http://www.google-analytics.com', 'https://ssl.google-analytics.com') +const DEVELOPER_DEPRECATED_OPTIONS = structuredClone(DEFAULT_OPTIONS) +const devDirs = DEVELOPER_DEPRECATED_OPTIONS.contentSecurityPolicy.directives +devDirs.styleSrc.push('*.googleapis.com') +devDirs.scriptSrc.push("'unsafe-inline'", '*.googleapis.com', 'http://www.google-analytics.com') +devDirs.fontSrc.push('*.gstatic.com') +devDirs.scriptSrcAttr.push("'unsafe-inline'") + const STATIC_DEPRECATED_OPTIONS = structuredClone(DEFAULT_OPTIONS) STATIC_DEPRECATED_OPTIONS.contentSecurityPolicy.directives.scriptSrc.push("'unsafe-inline'") const defaultHelmet = helmet(DEFAULT_OPTIONS) const nodeDeprecatedHelmet = helmet(NODE_DEPRECATED_OPTIONS) const staticDeprecatedHelmet = helmet(STATIC_DEPRECATED_OPTIONS) +const developerDeprecatedHelmet = helmet(DEVELOPER_DEPRECATED_OPTIONS) export default function helmetMiddleware(req: Request, res: Response, next: NextFunction) { // Enable CORS @@ -85,6 +92,14 @@ export default function helmetMiddleware(req: Request, res: Response, next: Next // Determine version for exceptions const { requestedVersion } = isArchivedVersion(req) + // Check if this is a legacy developer.github.com path + const isDeveloper = req.path + .replace(languagePrefixPathRegex, '/') + .startsWith(`/enterprise/${requestedVersion}/developer`) + if (versionSatisfiesRange(requestedVersion, '<=2.18') && isDeveloper) { + return developerDeprecatedHelmet(req, res, next) + } + // Exception for deprecated Enterprise docs (Node.js era) if ( versionSatisfiesRange(requestedVersion, '<=2.19') && diff --git a/src/frame/tests/server.js b/src/frame/tests/server.js index c45d7d34b97a..91c1a8617383 100644 --- a/src/frame/tests/server.js +++ b/src/frame/tests/server.js @@ -10,8 +10,6 @@ import { makeLanguageSurrogateKey, } from '#src/frame/middleware/set-fastly-surrogate-key.js' -const AZURE_STORAGE_URL = 'githubdocs.azureedge.net' - describe('server', () => { vi.setConfig({ testTimeout: 60 * 1000 }) @@ -49,12 +47,10 @@ describe('server', () => { expect(csp.get('default-src')).toBe("'none'") expect(csp.get('font-src').includes("'self'")).toBe(true) - expect(csp.get('font-src').includes(AZURE_STORAGE_URL)).toBe(true) expect(csp.get('connect-src').includes("'self'")).toBe(true) expect(csp.get('img-src').includes("'self'")).toBe(true) - expect(csp.get('img-src').includes(AZURE_STORAGE_URL)).toBe(true) expect(csp.get('script-src').includes("'self'")).toBe(true) diff --git a/src/redirects/README.md b/src/redirects/README.md index 0494b4b80564..d5577ddef1a6 100644 --- a/src/redirects/README.md +++ b/src/redirects/README.md @@ -58,12 +58,11 @@ As a workaround for these lost redirects, we have two files in `lib/redirects/st which had a record of each possible redirect candidate that we should bother redirecting too. Now, this new file has been created by accurately comparing it to the actual - content inside the `github/help-docs-archived-enterprise-versions` repo for the + content inside one of the `github/docs-ghes-` repos for the version range of 2.13 to 2.17. So every key in `archived-frontmatter-valid-urls.json` corresponds to a file that would work. -Here's how the `src/archives/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 it in `archived-frontmatter-valid-urls.json` that contains the requested path. If it finds it, it will redirect to it to because that file knows exactly which URLs are valid in -`help-docs-archived-enterprise-versions`. +Here's how the `src/archives/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 it in `archived-frontmatter-valid-urls.json` that contains the requested path. If it finds it, it will redirect to it to because that file knows exactly which URLs are valid in the `docs-ghes-` repos. ## Tests diff --git a/src/release-notes/tests/release-notes-1.ts b/src/release-notes/tests/release-notes-1.ts index 85f7f43661c4..87255b3b456c 100644 --- a/src/release-notes/tests/release-notes-1.ts +++ b/src/release-notes/tests/release-notes-1.ts @@ -14,15 +14,11 @@ describe('release notes', () => { await get('/') nock('https://github.github.com') - .get( - '/help-docs-archived-enterprise-versions/2.19/en/enterprise-server@2.19/admin/release-notes', - ) + .get('/docs-ghes-2.19/en/enterprise-server@2.19/admin/release-notes') .reply(404) - nock('https://github.github.com') - .get('/help-docs-archived-enterprise-versions/2.19/redirects.json') - .reply(200, { - emp: 'ty', - }) + nock('https://github.github.com').get('/docs-ghes-2.19/redirects.json').reply(200, { + emp: 'ty', + }) }) afterAll(() => nock.cleanAll()) diff --git a/src/versions/lib/enterprise-server-releases.js b/src/versions/lib/enterprise-server-releases.js index 4254d4a2e755..332d8af33b42 100644 --- a/src/versions/lib/enterprise-server-releases.js +++ b/src/versions/lib/enterprise-server-releases.js @@ -81,10 +81,11 @@ export const deprecated = [ ] export const legacyAssetVersions = ['3.0', '2.22', '2.21'] -// As of GHES 3.2, the archived enterprise content in no longer stored -// in the help-docs-archived-enterprise-versions repository. Instead, it -// is stored in our githubdocs Azure blog storage, in the `enterprise` -// container. +// As of GHES 3.2, we started storing the scraped assets and html +// in Azure blob storage. All enterprise deprecated veresions are +// now stored in individual docs-ghes- repos. This +// release number now indicates a change in the way the archived html +// references assets. export const firstReleaseStoredInBlobStorage = '3.2' export const all = supported.concat(deprecated)