docs/lib/get-redirect.js

217 строки
8.2 KiB
JavaScript

import { languageKeys } from './languages.js'
import nonEnterpriseDefaultVersion from './non-enterprise-default-version.js'
import { allVersions } from './all-versions.js'
import {
latest,
supported,
deprecatedWithFunctionalRedirects,
} from './enterprise-server-releases.js'
const languagePrefixRegex = new RegExp(`^/(${languageKeys.join('|')})/`)
const nonEnterpriseDefaultVersionPrefix = `/${nonEnterpriseDefaultVersion}`
const supportedAndRecentlyDeprecated = [...supported, ...deprecatedWithFunctionalRedirects]
export function splitPathByLanguage(uri, userLanguage) {
let language = userLanguage || 'en'
let withoutLanguage = uri
if (languagePrefixRegex.test(uri)) {
language = uri.match(languagePrefixRegex)[1]
withoutLanguage = uri.replace(languagePrefixRegex, '/')
}
return [language, withoutLanguage]
}
// Return the new URI if there is one, otherwise return undefined.
export default function getRedirect(uri, context) {
const { redirects, userLanguage } = context
const [language, withoutLanguage] = splitPathByLanguage(uri, userLanguage)
let destination
// `redirects` is sourced from more than one thing. The primary use
// case is gathering up the `redirect_from` frontmatter key.
// But we also has `developer.json` which contains legacy redirects.
// For example, the `developer.json` will have entries such
// `/enterprise/v4/enum/auditlogorderfield` which clearly is using
// the old formatting of the version. So to leverage the redirects
// from `developer.json` we'll look at it right away.
if (withoutLanguage in redirects) {
// But only inject the language if it's NOT an external redirect
if (redirects[withoutLanguage].includes('://')) {
return redirects[withoutLanguage]
}
return `/${language}` + redirects[withoutLanguage]
}
let basicCorrection
if (withoutLanguage.startsWith(nonEnterpriseDefaultVersionPrefix)) {
// E.g. '/free-pro-team@latest/foo/bar' or '/free-pro-team@latest'
basicCorrection =
`/${language}` + withoutLanguage.replace(nonEnterpriseDefaultVersionPrefix, '')
} else if (withoutLanguage.replace('/', '') in allVersions && !languagePrefixRegex.test(uri)) {
// E.g. just '/github-ae@latest' or '/enterprise-cloud@latest'
basicCorrection = `/${language}` + withoutLanguage
return basicCorrection
}
if (
withoutLanguage === '/enterprise-server' ||
withoutLanguage.startsWith('/enterprise-server/')
) {
// E.g. '/enterprise-server' or '/enterprise-server/3.0/foo'
basicCorrection =
`/${language}` + withoutLanguage.replace('/enterprise-server', `/enterprise-server@${latest}`)
// If it's now just the version, without anything after, exit here
if (withoutLanguage === '/enterprise-server') {
return basicCorrection
}
} else if (withoutLanguage.startsWith('/enterprise-server@latest')) {
// E.g. '/enterprise-server@latest' or '/enterprise-server@latest/3.3/foo'
basicCorrection =
`/${language}` +
withoutLanguage.replace('/enterprise-server@latest', `/enterprise-server@${latest}`)
// If it was *just* '/enterprise-server@latest' all that's needed is
// the language but with 'latest' replaced with the value of `latest`
if (withoutLanguage === '/enterprise-server@latest') {
return basicCorrection
}
} else if (
withoutLanguage.startsWith('/enterprise/') &&
supportedAndRecentlyDeprecated.includes(withoutLanguage.split('/')[2])
) {
// E.g. '/enterprise/3.3' or '/enterprise/3.3/foo' or '/enterprise/3.0/foo
// If the URL is without a language, and no redirect is necessary,
// but it has as version prefix, the language has to be there
// otherwise it will never be found in `req.context.pages`
const version = withoutLanguage.split('/')[2]
if (withoutLanguage === `/enterprise/${version}`) {
// E.g. `/enterprise/3.0`
basicCorrection =
`/${language}` +
withoutLanguage.replace(`/enterprise/${version}`, `/enterprise-server@${version}`)
return basicCorrection
} else {
basicCorrection =
`/${language}` +
withoutLanguage.replace(`/enterprise/${version}/`, `/enterprise-server@${version}/`)
}
} else if (withoutLanguage === '/enterprise') {
// E.g. `/enterprise` exactly
basicCorrection = `/${language}/enterprise-server@${latest}`
return basicCorrection
} else if (
withoutLanguage.startsWith('/enterprise/') &&
!supported.includes(withoutLanguage.split('/')[2])
) {
// E.g. '/en/enterprise/user/github/foo'
// If the URL is without a language, and no redirect is necessary,
// but it has as version prefix, the language has to be there
// otherwise it will never be found in `req.context.pages`
basicCorrection =
`/${language}` +
withoutLanguage
.replace(`/enterprise/`, `/enterprise-server@${latest}/`)
.replace('/user/', '/')
} else if (withoutLanguage.startsWith('/insights')) {
// E.g. '/insights/foo'
basicCorrection = uri.replace('/insights', `${language}/enterprise-server@${latest}/insights`)
}
if (basicCorrection) {
return getRedirect(basicCorrection, context) || basicCorrection
}
if (withoutLanguage.startsWith('/admin/')) {
const prefix = `/enterprise-server@${latest}`
let suffix = withoutLanguage
if (suffix.startsWith('/admin/guides/')) {
suffix = suffix.replace('/admin/guides/', '/admin/')
}
const newURL = prefix + suffix
destination = redirects[newURL] || newURL
} else if (
withoutLanguage.split('/')[1].includes('@') &&
withoutLanguage.split('/')[1] in allVersions
) {
// E.g. '/enterprise-server@latest' or '/github-ae@latest' or '/enterprise-server@3.3'
const majorVersion = withoutLanguage.split('/')[1].split('@')[0]
const split = withoutLanguage.split('/')
const version = split[1].split('@')[1]
let prefix
let suffix
if (supported.includes(version) || version === 'latest') {
prefix = `/${majorVersion}@${version}`
suffix = '/' + split.slice(2).join('/')
if (
suffix.includes('/user') ||
suffix.startsWith('/admin/guide') ||
suffix.startsWith('/articles/user')
) {
suffix = tryReplacements(prefix, suffix, context) || suffix
}
}
const newURL = prefix + suffix
if (newURL !== withoutLanguage) {
// At least the prefix changed!
destination = redirects[newURL] || newURL
} else {
destination = redirects[newURL]
}
} else if (withoutLanguage.startsWith('/desktop/guides/')) {
// E.g. /desktop/guides/contributing-and-collaborat
const newURL = withoutLanguage.replace('/desktop/guides/', '/desktop/')
destination = redirects[newURL] || newURL
} else {
destination = redirects[withoutLanguage]
}
if (destination !== undefined) {
// There's hope! Now we just need to attach the correct language
// to the destination URL.
return `/${language}${destination}`
}
}
// Over time, we've developed multiple ambiguous patterns of URLs
// You can't simply assume that all `/admin/guides` should become
// `/admin` for example.
// This function tries different string replacement on the suffix
// (the pathname after the language and version part) until it
// finds one string replacement that yields either a page or a redirect.
function tryReplacements(prefix, suffix, { pages, redirects }) {
const test = (suffix) => {
// This is a generally broad search and replace and this particular
// replacement has never been present in api documentation only enterprise
// admin documentation, so we're excluding the REST api pages
if (suffix.includes('/rest')) {
return false
}
const candidateAsRedirect = prefix + suffix
const candidateAsURL = '/en' + candidateAsRedirect
return candidateAsRedirect in redirects || candidateAsURL in pages
}
let attempt = suffix.replace('/user', '/github')
if (test(attempt)) return attempt
attempt = suffix.replace('/user', '')
if (test(attempt)) return attempt
attempt = suffix.replace('/admin/guides', '/admin')
if (test(attempt)) return attempt
attempt = suffix.replace('/admin/guides/user', '/admin/github')
if (test(attempt)) return attempt
attempt = suffix.replace('/admin/guides', '/admin').replace('/user', '/github')
if (test(attempt)) return attempt
}