| | import type { Response } from 'express' |
| | import type { ExtendedRequestWithPageInfo } from '../types' |
| |
|
| | import type { ExtendedRequest, Page, Context, Permalink } from '@/types' |
| | import shortVersions from '@/versions/middleware/short-versions' |
| | import contextualize from '@/frame/middleware/context/context' |
| | import features from '@/versions/middleware/features' |
| | import breadcrumbs from '@/frame/middleware/context/breadcrumbs' |
| | import currentProductTree from '@/frame/middleware/context/current-product-tree' |
| | import { readCompressedJsonFile } from '@/frame/lib/read-json-file' |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | export const CACHE_FILE_PATH = '.pageinfo-cache.json.br' |
| |
|
| | export async function getPageInfo(page: Page, pathname: string) { |
| | const mockedContext: Context = {} |
| | const renderingReq = { |
| | path: pathname, |
| | language: page.languageCode, |
| | pagePath: pathname, |
| | cookies: {}, |
| | context: mockedContext, |
| | } |
| | const next = () => {} |
| | const res = {} |
| | await contextualize(renderingReq as ExtendedRequest, res as Response, next) |
| | await shortVersions(renderingReq as ExtendedRequest, res as Response, next) |
| | renderingReq.context.page = page |
| | await currentProductTree(renderingReq as ExtendedRequest, res as Response, next) |
| | features(renderingReq as ExtendedRequest, res as Response, next) |
| | const context = renderingReq.context |
| |
|
| | const title = await page.renderProp('title', context, { textOnly: true }) |
| | const intro = await page.renderProp('intro', context, { textOnly: true }) |
| |
|
| | let productPage = null |
| | for (const permalink of page.permalinks) { |
| | const rootHref = permalink.href |
| | .split('/') |
| | .slice(0, permalink.pageVersion === 'free-pro-team@latest' ? 3 : 4) |
| | .join('/') |
| | if (!context.pages) throw new Error('context.pages not yet set') |
| | const rootPage = context.pages[rootHref] |
| | if (rootPage) { |
| | productPage = rootPage |
| | break |
| | } |
| | } |
| | const product = productPage ? await getProductPageInfo(productPage, context) : '' |
| |
|
| | |
| | breadcrumbs(renderingReq as ExtendedRequest, res as Response, next) |
| |
|
| | const { breadcrumbs: pageBreadcrumbs } = renderingReq.context |
| |
|
| | return { title, intro, product, breadcrumbs: pageBreadcrumbs } |
| | } |
| |
|
| | const _productPageCache: { |
| | [key: string]: string |
| | } = {} |
| | |
| | |
| | |
| | async function getProductPageInfo(page: Page, context: Context) { |
| | const cacheKey = `${page.relativePath}:${context.currentVersion}:${context.currentLanguage}` |
| | if (!(cacheKey in _productPageCache)) { |
| | const title = |
| | (await page.renderProp('shortTitle', context, { |
| | textOnly: true, |
| | })) || |
| | (await page.renderProp('title', context, { |
| | textOnly: true, |
| | })) |
| | _productPageCache[cacheKey] = title |
| | } |
| | return _productPageCache[cacheKey] |
| | } |
| |
|
| | type CachedPageInfo = { |
| | [url: string]: { |
| | title: string |
| | intro: string |
| | product: string |
| | cacheInfo?: string |
| | } |
| | } |
| |
|
| | let _cache: CachedPageInfo | null = null |
| | export async function getPageInfoFromCache(page: Page, pathname: string) { |
| | let cacheInfo = '' |
| | if (_cache === null) { |
| | try { |
| | _cache = readCompressedJsonFile(CACHE_FILE_PATH) as CachedPageInfo |
| | cacheInfo = 'initial-load' |
| | } catch (error) { |
| | cacheInfo = 'initial-fail' |
| | if (error instanceof Error && (error as NodeJS.ErrnoException).code !== 'ENOENT') { |
| | throw error |
| | } |
| | _cache = {} |
| | } |
| | } |
| |
|
| | let meta = _cache[pathname] |
| | if (!cacheInfo) { |
| | cacheInfo = meta ? 'hit' : 'miss' |
| | } |
| | if (!meta) { |
| | meta = await getPageInfo(page, pathname) |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | } |
| | meta.cacheInfo = cacheInfo |
| | return meta |
| | } |
| |
|
| | export async function getMetadata(req: ExtendedRequestWithPageInfo) { |
| | |
| | |
| | |
| | |
| | |
| | const { page, pathname, archived } = req.pageinfo |
| |
|
| | if (archived && archived.isArchived) { |
| | const { requestedVersion } = archived |
| | const title = `GitHub Enterprise Server ${requestedVersion} Help Documentation` |
| | const intro = '' |
| | const product = 'GitHub Enterprise Server' |
| | return { meta: { intro, title, product } } |
| | } |
| |
|
| | if (!page) { |
| | throw new Error(`No page found for '${pathname}'`) |
| | } |
| |
|
| | const pagePermalinks = page.permalinks.map((p: Permalink) => p.href) |
| | if (!pagePermalinks.includes(pathname)) { |
| | throw new Error(`pathname '${pathname}' not one of the page's permalinks`) |
| | } |
| |
|
| | const fromCache = await getPageInfoFromCache(page, pathname) |
| | const { cacheInfo, ...meta } = fromCache |
| |
|
| | return { meta, cacheInfo } |
| | } |
| |
|