| | import type { Response } from 'express' |
| | import cheerio from 'cheerio' |
| |
|
| | import warmServer from '@/frame/lib/warm-server' |
| | import { liquid } from '@/content-render/index' |
| | import shortVersions from '@/versions/middleware/short-versions' |
| | import contextualize from '@/frame/middleware/context/context' |
| | import features from '@/versions/middleware/features' |
| | import findPage from '@/frame/middleware/find-page' |
| | import { createMinimalProcessor } from '@/content-render/unified/processor' |
| | import getRedirect from '@/redirects/lib/get-redirect' |
| | import type { ExtendedRequest, Page } from '@/types' |
| |
|
| | export type DocsUrls = { |
| | [identifier: string]: string |
| | } |
| |
|
| | type Permalink = { |
| | href: string |
| | languageCode: string |
| | } |
| | type Redirects = { |
| | [from: string]: string |
| | } |
| |
|
| | export type Check = { |
| | identifier: string |
| | url: string |
| | pageURL: string |
| | found: boolean |
| | fragment: string | undefined |
| | fragmentFound?: boolean |
| | fragmentCandidates?: string[] |
| | |
| | redirectPageURL?: string |
| | |
| | |
| | redirect?: string |
| | } |
| |
|
| | export async function validateDocsUrl(docsUrls: DocsUrls, { checkFragments = false } = {}) { |
| | const site = await warmServer(['en']) |
| | const pages = site.pages |
| | const redirects: Redirects = site.redirects |
| |
|
| | const checks: Check[] = [] |
| | for (const [identifier, url] of Object.entries(docsUrls)) { |
| | if (!url.startsWith('/')) { |
| | throw new Error(`URL doesn't start with '/': ${url} (identifier: ${identifier})`) |
| | } |
| | const pathname = url.split('?')[0] |
| | |
| | |
| | const [pageURL, fragment] = `/en${pathname === '/' ? '' : pathname}`.split('#') |
| |
|
| | const page = pages[pageURL] |
| | const check: Check = { |
| | identifier, |
| | url, |
| | pageURL, |
| | fragment, |
| | found: !!page, |
| | } |
| | let redirectedPage: Page | null = null |
| | if (!page) { |
| | const redirect = getRedirect(pageURL, { |
| | userLanguage: 'en', |
| | redirects, |
| | pages, |
| | }) |
| | if (redirect && isEnterpriseCloudRedirectOnly(pageURL, redirect)) { |
| | |
| | continue |
| | } |
| | if (redirect) { |
| | redirectedPage = pages[redirect] |
| | if (!redirectedPage) { |
| | throw new Error(`The redirected page doesn't exist: ${redirect}`) |
| | } |
| | check.found = true |
| | check.redirectPageURL = redirect |
| | check.redirect = stripLanguagePrefix(redirect) |
| | if (fragment) { |
| | check.redirect += `#${fragment}` |
| | } |
| | } |
| | } |
| |
|
| | if (checkFragments && fragment) { |
| | const permalink = (redirectedPage || page).permalinks[0] |
| | const html = await renderInnerHTML(redirectedPage || page, permalink) |
| | const $ = cheerio.load(html) |
| | check.fragmentFound = $(`#${fragment}`).length > 0 || $(`a[name="${fragment}"]`).length > 0 |
| | if (!check.fragmentFound) { |
| | const fragmentCandidates: string[] = [] |
| | $('h2[id], h3[id]').each((_, el) => { |
| | const id = $(el).attr('id') |
| | if (id) { |
| | fragmentCandidates.push(id) |
| | } |
| | }) |
| | check.fragmentCandidates = fragmentCandidates |
| | } |
| | } |
| | checks.push(check) |
| | } |
| | return checks |
| | } |
| |
|
| | function isEnterpriseCloudRedirectOnly(originalUrl: string, redirectUrl: string) { |
| | |
| | |
| | return redirectUrl.replace('/enterprise-cloud@latest', '') === originalUrl |
| | } |
| |
|
| | async function renderInnerHTML(page: Page, permalink: Permalink) { |
| | const next = () => {} |
| | const res = {} |
| |
|
| | const pagePath = permalink.href |
| | const req = { |
| | path: pagePath, |
| | language: permalink.languageCode, |
| | pagePath, |
| | cookies: {}, |
| | |
| | |
| | context: {}, |
| | } |
| | await contextualize(req as ExtendedRequest, res as Response, next) |
| | await shortVersions(req as ExtendedRequest, res as Response, next) |
| | await findPage(req as ExtendedRequest, res as Response, next) |
| | features(req as ExtendedRequest, res as Response, next) |
| |
|
| | const markdown = await liquid.parseAndRender(page.markdown, req.context) |
| | const processor = createMinimalProcessor(req.context) |
| | const vFile = await processor.process(markdown) |
| | return vFile.toString() |
| | } |
| |
|
| | function stripLanguagePrefix(url: string) { |
| | return url.replace(/^\/en\//, '/') |
| | } |
| |
|