From 1c2690d540254d9b0f929e15b27363ffb8ff27da Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Wed, 26 Jun 2024 10:23:07 -0400 Subject: [PATCH 1/2] Port `features.js` to TypeScript (#51422) --- .../scripts/all-documents/lib.ts | 4 +- .../tests/{liquid.js => liquid.ts} | 74 +++++++++---------- src/frame/middleware/index.ts | 2 +- src/links/lib/validate-docs-urls.ts | 2 +- .../scripts/rendered-content-link-checker.ts | 4 +- src/pageinfo/middleware.ts | 2 +- src/types.ts | 6 +- .../middleware/{features.js => features.ts} | 27 +++++-- 8 files changed, 66 insertions(+), 55 deletions(-) rename src/content-render/tests/{liquid.js => liquid.ts} (76%) rename src/versions/middleware/{features.js => features.ts} (57%) diff --git a/src/content-render/scripts/all-documents/lib.ts b/src/content-render/scripts/all-documents/lib.ts index 6fc5514dc4..aca8d1a0e4 100644 --- a/src/content-render/scripts/all-documents/lib.ts +++ b/src/content-render/scripts/all-documents/lib.ts @@ -2,7 +2,7 @@ import type { Response } from 'express' import type { ExtendedRequest, Page } from '@/types' import contextualize from '@/frame/middleware/context/context' -import features from '@/versions/middleware/features.js' +import features from '@/versions/middleware/features' import shortVersions from '@/versions/middleware/short-versions.js' import warmServer from '@/frame/lib/warm-server.js' @@ -68,7 +68,7 @@ export async function allDocuments(options: Options): Promise { await contextualize(req as ExtendedRequest, res as Response, next) await shortVersions(req, res, next) req.context.page = page - await features(req, res, next) + features(req as any, res as any, next) const title = fields.includes('title') ? await page.renderProp('title', req.context, { textOnly: true }) diff --git a/src/content-render/tests/liquid.js b/src/content-render/tests/liquid.ts similarity index 76% rename from src/content-render/tests/liquid.js rename to src/content-render/tests/liquid.ts index 5424cfbc42..46eeb9c65a 100644 --- a/src/content-render/tests/liquid.js +++ b/src/content-render/tests/liquid.ts @@ -1,10 +1,12 @@ import { describe, expect, test, vi } from 'vitest' +import type { Response } from 'express' -import { liquid } from '#src/content-render/index.js' -import shortVersionsMiddleware from '#src/versions/middleware/short-versions.js' -import featureVersionsMiddleware from '#src/versions/middleware/features.js' -import { allVersions } from '#src/versions/lib/all-versions.js' -import enterpriseServerReleases from '#src/versions/lib/enterprise-server-releases.js' +import { liquid } from '@/content-render/index.js' +import shortVersionsMiddleware from '@/versions/middleware/short-versions.js' +import featureVersionsMiddleware from '@/versions/middleware/features' +import { allVersions } from '@/versions/lib/all-versions.js' +import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases.js' +import type { Context, ExtendedRequest, Page } from '@/types' // Setup these variables so we don't need to manually update tests as GHES // versions continually get deprecated. For example, if we deprecate GHES 3.0, @@ -37,8 +39,10 @@ const featureVersionsTemplate = ` {% if placeholder %} I am placeholder content {% endif %} ` -const contextualize = (req) => { - req.context.currentVersionObj = req.context.allVersions[req.context.currentVersion] +const contextualize = (req: ExtendedRequest) => { + if (!req.context || !req.context.allVersions || !req.context.currentVersion) + throw new Error('request not contextualized') + req.context!.currentVersionObj = req.context.allVersions[req.context.currentVersion] shortVersionsMiddleware(req, null, () => {}) } @@ -47,15 +51,14 @@ describe('liquid template parser', () => { describe('short versions', () => { // Create a fake req so we can test the shortVersions middleware - const req = { language: 'en', query: {} } + const req = { language: 'en', query: {} } as ExtendedRequest test('FPT works as expected when it is FPT', async () => { req.context = { currentVersion: 'free-pro-team@latest', - page: {}, allVersions, enterpriseServerReleases, - } + } as Context contextualize(req) const output = await liquid.parseAndRender(shortVersionsTemplate, req.context) // We should have TWO results because we are supporting two shortcuts @@ -67,10 +70,10 @@ describe('liquid template parser', () => { test('GHEC works as expected', async () => { req.context = { currentVersion: 'enterprise-cloud@latest', - page: {}, + // page: {}, allVersions, enterpriseServerReleases, - } + } as Context contextualize(req) const output = await liquid.parseAndRender(shortVersionsTemplate, req.context) expect(output.trim()).toBe('I am GHEC') @@ -79,10 +82,9 @@ describe('liquid template parser', () => { test('GHES works as expected', async () => { req.context = { currentVersion: `enterprise-server@${oldestSupportedGhes}`, - page: {}, allVersions, enterpriseServerReleases, - } + } as Context contextualize(req) const output = await liquid.parseAndRender(shortVersionsTemplate, req.context) expect(output.replace(/\s\s+/g, ' ').trim()).toBe( @@ -93,10 +95,9 @@ describe('liquid template parser', () => { test('AND statements work as expected', async () => { req.context = { currentVersion: `enterprise-server@${secondOldestSupportedGhes}`, - page: {}, allVersions, enterpriseServerReleases, - } + } as Context contextualize(req) const output = await liquid.parseAndRender(shortVersionsTemplate, req.context) expect(output.replace(/\s\s+/g, ' ').trim()).toBe( @@ -107,10 +108,9 @@ describe('liquid template parser', () => { test('NOT statements work as expected on versions without numbered releases', async () => { req.context = { currentVersion: 'free-pro-team@latest', - page: {}, allVersions, enterpriseServerReleases, - } + } as Context contextualize(req) const output = await liquid.parseAndRender(negativeVersionsTemplate, req.context) expect(output.replace(/\s\s+/g, ' ').trim()).toBe( @@ -121,10 +121,9 @@ describe('liquid template parser', () => { test('NOT statements work as expected on versions with numbered releases', async () => { req.context = { currentVersion: `enterprise-server@${oldestSupportedGhes}`, - page: {}, allVersions, enterpriseServerReleases, - } + } as Context contextualize(req) const output = await liquid.parseAndRender(negativeVersionsTemplate, req.context) expect(output.replace(/\s\s+/g, ' ').trim()).toBe( @@ -135,10 +134,9 @@ describe('liquid template parser', () => { test('The != operator works as expected', async () => { req.context = { currentVersion: `enterprise-server@${secondOldestSupportedGhes}`, - page: {}, allVersions, enterpriseServerReleases, - } + } as Context contextualize(req) const output = await liquid.parseAndRender(negativeVersionsTemplate, req.context) expect(output.replace(/\s\s+/g, ' ').trim()).toBe('I am not GHEC') @@ -147,42 +145,42 @@ describe('liquid template parser', () => { describe('feature versions', () => { // Create a fake req so we can test the feature versions middleware - const req = { language: 'en', query: {} } + const req = { language: 'en', query: {} } as ExtendedRequest test('does not render in FPT because feature is not available in FPT', async () => { req.context = { currentVersion: 'free-pro-team@latest', - page: {}, + page: {} as Page, // it just has to be any truthy value allVersions, enterpriseServerReleases, - } - await featureVersionsMiddleware(req, null, () => {}) - const outputFpt = await liquid.parseAndRender(featureVersionsTemplate, req.context) - expect(outputFpt.includes('placeholder content')).toBe(false) + } as Context + featureVersionsMiddleware(req as ExtendedRequest, {} as Response, () => {}) + const output = await liquid.parseAndRender(featureVersionsTemplate, req.context) + expect(output.includes('placeholder content')).toBe(false) }) test('renders in GHES because feature is available in GHES', async () => { req.context = { currentVersion: `enterprise-server@${enterpriseServerReleases.latest}`, - page: {}, + page: {} as Page, // it just has to be any truthy value allVersions, enterpriseServerReleases, - } - await featureVersionsMiddleware(req, null, () => {}) - const outputFpt = await liquid.parseAndRender(featureVersionsTemplate, req.context) - expect(outputFpt.includes('placeholder content')).toBe(true) + } as Context + featureVersionsMiddleware(req, {} as Response, () => {}) + const output = await liquid.parseAndRender(featureVersionsTemplate, req.context) + expect(output.includes('placeholder content')).toBe(true) }) test('renders in GHEC because feature is available in GHEC', async () => { req.context = { currentVersion: 'enterprise-cloud@latest', - page: {}, + page: {} as Page, // it just has to be any truthy value allVersions, enterpriseServerReleases, - } - await featureVersionsMiddleware(req, null, () => {}) - const outputFpt = await liquid.parseAndRender(featureVersionsTemplate, req.context) - expect(outputFpt.includes('placeholder content')).toBe(true) + } as Context + featureVersionsMiddleware(req, {} as Response, () => {}) + const output = await liquid.parseAndRender(featureVersionsTemplate, req.context) + expect(output.includes('placeholder content')).toBe(true) }) }) }) diff --git a/src/frame/middleware/index.ts b/src/frame/middleware/index.ts index 37fbcb52a4..e7bb0e765d 100644 --- a/src/frame/middleware/index.ts +++ b/src/frame/middleware/index.ts @@ -45,7 +45,7 @@ import genericToc from './context/generic-toc' import breadcrumbs from './context/breadcrumbs' import glossaries from './context/glossaries' import renderProductName from './context/render-product-name' -import features from '@/versions/middleware/features.js' +import features from '@/versions/middleware/features' import productExamples from './context/product-examples' import productGroups from './context/product-groups' import featuredLinks from '@/landings/middleware/featured-links' diff --git a/src/links/lib/validate-docs-urls.ts b/src/links/lib/validate-docs-urls.ts index 9cea760375..26e245dc23 100644 --- a/src/links/lib/validate-docs-urls.ts +++ b/src/links/lib/validate-docs-urls.ts @@ -130,7 +130,7 @@ async function renderInnerHTML(page: Page, permalink: Permalink) { await contextualize(req as ExtendedRequest, res as Response, next) await shortVersions(req, res, next) await findPage(req, res, next) - await features(req, res, next) + features(req as ExtendedRequest, res as Response, next) const markdown = await liquid.parseAndRender(page.markdown, req.context) const processor = createMinimalProcessor(req.context) diff --git a/src/links/scripts/rendered-content-link-checker.ts b/src/links/scripts/rendered-content-link-checker.ts index 6bac26c1e0..0918f54f04 100755 --- a/src/links/scripts/rendered-content-link-checker.ts +++ b/src/links/scripts/rendered-content-link-checker.ts @@ -45,8 +45,6 @@ type LinkFlaw = { flaw: Flaw } -// type Core = CoreInject - type Redirects = Record type PageMap = Record @@ -1313,7 +1311,7 @@ async function renderInnerHTML(page: Page, permalink: Permalink) { await contextualize(req as ExtendedRequest, res as Response, next) await shortVersions(req, res, next) req.context.page = page - await features(req, res, next) + features(req as ExtendedRequest, res as Response, next) req.context.relativePath = page.relativePath diff --git a/src/pageinfo/middleware.ts b/src/pageinfo/middleware.ts index 19d9a70579..797e2023ef 100644 --- a/src/pageinfo/middleware.ts +++ b/src/pageinfo/middleware.ts @@ -133,7 +133,7 @@ export async function getPageInfo(page: Page, pathname: string) { await contextualize(renderingReq as ExtendedRequest, res as Response, next) await shortVersions(renderingReq, res, next) renderingReq.context.page = page - await features(renderingReq, res, next) + features(renderingReq as ExtendedRequest, res as Response, next) const context = renderingReq.context const title = await page.renderProp('title', context, { textOnly: true }) diff --git a/src/types.ts b/src/types.ts index 684a5e717c..af25f1c2e5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -99,7 +99,11 @@ type Redirects = { [key: string]: string } -export type Context = { +type Features = { + [feature: string]: boolean +} + +export type Context = Features & { currentCategory?: string error?: Error siteTree?: SiteTree diff --git a/src/versions/middleware/features.js b/src/versions/middleware/features.ts similarity index 57% rename from src/versions/middleware/features.js rename to src/versions/middleware/features.ts index b0518228f4..3d3c1bc901 100644 --- a/src/versions/middleware/features.js +++ b/src/versions/middleware/features.ts @@ -1,14 +1,19 @@ import path from 'path' +import type { Response, NextFunction } from 'express' -import { ROOT } from '#src/frame/lib/constants.js' -import getApplicableVersions from '#src/versions/lib/get-applicable-versions.js' -import { getDeepDataByLanguage } from '#src/data-directory/lib/get-data.js' +import type { ExtendedRequest, FrontmatterVersions } from '@/types' +import { ROOT } from '@/frame/lib/constants.js' +import getApplicableVersions from '@/versions/lib/get-applicable-versions.js' +import { getDeepDataByLanguage } from '@/data-directory/lib/get-data.js' -export default function features(req, res, next) { +export default function features(req: ExtendedRequest, res: Response, next: NextFunction) { + if (!req.context) throw new Error('request is not contextualized') if (!req.context.page) return next() + if (!req.context.currentVersion) throw new Error('currentVersion is not contextualized') Object.entries(getFeaturesByVersion(req.context.currentVersion)).forEach( ([featureName, isFeatureAvailableInCurrentVersion]) => { + if (!req.context) throw new Error('request is not contextualized') req.context[featureName] = isFeatureAvailableInCurrentVersion }, ) @@ -16,19 +21,25 @@ export default function features(req, res, next) { return next() } -let allFeatures +type FeatureVersions = { + versions: FrontmatterVersions +} + +let allFeatures: Record const cache = new Map() -function getFeaturesByVersion(currentVersion) { +function getFeaturesByVersion(currentVersion: string): Record { if (!cache.has(currentVersion)) { if (!allFeatures) { // As of Oct 2022, the `data/features/**` reading is *not* JIT. // The `data/features` is deliberately not ignored in nodemon.json. // See internal issue #2389 - allFeatures = getDeepDataByLanguage('features', 'en') + allFeatures = getDeepDataByLanguage('features', 'en') as Record } - const features = {} + const features: { + [feature: string]: boolean + } = {} // Determine whether the currentVersion belongs to the list of versions the feature is available in. for (const [featureName, feature] of Object.entries(allFeatures)) { const { versions } = feature From 850ee728ad168bc01a8eed7771189ece9aa00d9d Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Wed, 26 Jun 2024 10:24:47 -0400 Subject: [PATCH 2/2] Delete leftover debugging logging (#51424) --- src/frame/middleware/context/context.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/frame/middleware/context/context.ts b/src/frame/middleware/context/context.ts index 439b3f3c54..464a3567d0 100644 --- a/src/frame/middleware/context/context.ts +++ b/src/frame/middleware/context/context.ts @@ -35,7 +35,6 @@ export default async function contextualize( const context: Context = {} req.context = context - console.log('CREATING CONTEXT') req.context.process = { env: {} }