1
0
mirror of synced 2025-12-19 09:57:42 -05:00

Replace any types with unknown/specific types (#58658)

This commit is contained in:
Kevin Heis
2025-12-03 07:43:11 -08:00
committed by GitHub
parent 97b71c20d7
commit 03945b8c27
20 changed files with 185 additions and 91 deletions

View File

@@ -109,6 +109,7 @@ export default [
'rest-api-description/', 'rest-api-description/',
'docs-internal-data/', 'docs-internal-data/',
'src/code-scanning/scripts/generate-code-scanning-query-list.ts', 'src/code-scanning/scripts/generate-code-scanning-query-list.ts',
'next-env.d.ts',
], ],
}, },

View File

@@ -2,6 +2,7 @@ import { getUIDataMerged } from '@/data-directory/lib/get-data'
import { type LanguageCode } from '@/languages/lib/languages' import { type LanguageCode } from '@/languages/lib/languages'
import { translate } from '@/languages/lib/translation-utils' import { translate } from '@/languages/lib/translation-utils'
import { extractLanguageFromPath } from '@/app/lib/language-utils' import { extractLanguageFromPath } from '@/app/lib/language-utils'
import { type UIStrings } from '@/frame/components/context/MainContext'
export interface AppRouterContext { export interface AppRouterContext {
currentLanguage: LanguageCode currentLanguage: LanguageCode
@@ -9,7 +10,7 @@ export interface AppRouterContext {
sitename: string sitename: string
site: { site: {
data: { data: {
ui: any ui: UIStrings
} }
} }
} }

View File

@@ -100,7 +100,7 @@ export async function getPageInfoFromCache(page: Page, pathname: string) {
cacheInfo = 'initial-load' cacheInfo = 'initial-load'
} catch (error) { } catch (error) {
cacheInfo = 'initial-fail' cacheInfo = 'initial-fail'
if (error instanceof Error && (error as any).code !== 'ENOENT') { if (error instanceof Error && (error as NodeJS.ErrnoException).code !== 'ENOENT') {
throw error throw error
} }
_cache = {} _cache = {}

View File

@@ -3,11 +3,13 @@ import path from 'path'
import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest' import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest'
import nock from 'nock' import nock from 'nock'
import type { Response } from 'express'
import { get } from '@/tests/helpers/e2etest' import { get } from '@/tests/helpers/e2etest'
import { checkCachingHeaders } from '@/tests/helpers/caching-headers' import { checkCachingHeaders } from '@/tests/helpers/caching-headers'
import { setDefaultFastlySurrogateKey } from '@/frame/middleware/set-fastly-surrogate-key' import { setDefaultFastlySurrogateKey } from '@/frame/middleware/set-fastly-surrogate-key'
import archivedEnterpriseVersionsAssets from '@/archives/middleware/archived-enterprise-versions-assets' import archivedEnterpriseVersionsAssets from '@/archives/middleware/archived-enterprise-versions-assets'
import type { ExtendedRequest } from '@/types'
function getNextStaticAsset(directory: string) { function getNextStaticAsset(directory: string) {
const root = path.join('.next', 'static', directory) const root = path.join('.next', 'static', directory)
@@ -34,10 +36,10 @@ function mockRequest(requestPath: string, { headers }: { headers?: Record<string
} }
type MockResponse = { type MockResponse = {
status: number status: number | undefined
statusCode: number statusCode: number | undefined
json?: (payload: any) => void json?: (payload: unknown) => void
send?: (body: any) => void send?: (body: unknown) => void
sendStatus?: (statusCode: number) => void sendStatus?: (statusCode: number) => void
end?: () => void end?: () => void
_json?: string _json?: string
@@ -50,17 +52,17 @@ type MockResponse = {
const mockResponse = () => { const mockResponse = () => {
const res: MockResponse = { const res: MockResponse = {
status: undefined as any, status: undefined,
statusCode: undefined as any, statusCode: undefined,
headers: {}, headers: {},
} }
res.json = (payload) => { res.json = (payload) => {
res._json = payload res._json = payload as string
} }
res.send = (body) => { res.send = (body) => {
res.status = 200 res.status = 200
res.statusCode = 200 res.statusCode = 200
res._send = body res._send = body as string
} }
res.end = () => { res.end = () => {
// Mock end method // Mock end method
@@ -86,7 +88,7 @@ const mockResponse = () => {
return key in res.headers return key in res.headers
} }
// Add Express-style status method that supports chaining // Add Express-style status method that supports chaining
;(res as any).status = (code: number) => { ;(res as unknown as { status: (code: number) => MockResponse }).status = (code: number) => {
res.status = code res.status = code
res.statusCode = code res.statusCode = code
return res return res
@@ -222,7 +224,11 @@ describe('archived enterprise static assets', () => {
throw new Error('did not expect this to ever happen') throw new Error('did not expect this to ever happen')
} }
setDefaultFastlySurrogateKey(req, res, () => {}) setDefaultFastlySurrogateKey(req, res, () => {})
await archivedEnterpriseVersionsAssets(req as any, res as any, next) await archivedEnterpriseVersionsAssets(
req as unknown as ExtendedRequest,
res as unknown as Response,
next,
)
expect(res.statusCode).toBe(200) expect(res.statusCode).toBe(200)
checkCachingHeaders(res, false, 60) checkCachingHeaders(res, false, 60)
}) })
@@ -238,7 +244,11 @@ describe('archived enterprise static assets', () => {
throw new Error('did not expect this to ever happen') throw new Error('did not expect this to ever happen')
} }
setDefaultFastlySurrogateKey(req, res, () => {}) setDefaultFastlySurrogateKey(req, res, () => {})
await archivedEnterpriseVersionsAssets(req as any, res as any, next) await archivedEnterpriseVersionsAssets(
req as unknown as ExtendedRequest,
res as unknown as Response,
next,
)
expect(res.statusCode).toBe(200) expect(res.statusCode).toBe(200)
checkCachingHeaders(res, false, 60) checkCachingHeaders(res, false, 60)
}) })
@@ -254,7 +264,11 @@ describe('archived enterprise static assets', () => {
throw new Error('did not expect this to ever happen') throw new Error('did not expect this to ever happen')
} }
setDefaultFastlySurrogateKey(req, res, () => {}) setDefaultFastlySurrogateKey(req, res, () => {})
await archivedEnterpriseVersionsAssets(req as any, res as any, next) await archivedEnterpriseVersionsAssets(
req as unknown as ExtendedRequest,
res as unknown as Response,
next,
)
expect(res.statusCode).toBe(200) expect(res.statusCode).toBe(200)
checkCachingHeaders(res, false, 60) checkCachingHeaders(res, false, 60)
}) })
@@ -271,7 +285,11 @@ describe('archived enterprise static assets', () => {
nexted = true nexted = true
} }
setDefaultFastlySurrogateKey(req, res, next) setDefaultFastlySurrogateKey(req, res, next)
await archivedEnterpriseVersionsAssets(req as any, res as any, next) await archivedEnterpriseVersionsAssets(
req as unknown as ExtendedRequest,
res as unknown as Response,
next,
)
// It didn't exit in that middleware but called next() to move on // It didn't exit in that middleware but called next() to move on
// with any other middlewares. // with any other middlewares.
expect(nexted).toBe(true) expect(nexted).toBe(true)
@@ -289,7 +307,11 @@ describe('archived enterprise static assets', () => {
nexted = true nexted = true
} }
setDefaultFastlySurrogateKey(req, res, () => {}) setDefaultFastlySurrogateKey(req, res, () => {})
await archivedEnterpriseVersionsAssets(req as any, res as any, next) await archivedEnterpriseVersionsAssets(
req as unknown as ExtendedRequest,
res as unknown as Response,
next,
)
// It tried to go via the proxy, but it wasn't there, but then it // It tried to go via the proxy, but it wasn't there, but then it
// tried "our disk" and it's eventually there. // tried "our disk" and it's eventually there.
expect(nexted).toBe(true) expect(nexted).toBe(true)
@@ -335,7 +357,11 @@ describe('archived enterprise static assets', () => {
nexted = true nexted = true
} }
setDefaultFastlySurrogateKey(req, res, () => {}) setDefaultFastlySurrogateKey(req, res, () => {})
await archivedEnterpriseVersionsAssets(req as any, res as any, next) await archivedEnterpriseVersionsAssets(
req as unknown as ExtendedRequest,
res as unknown as Response,
next,
)
expect(res.statusCode).toBe(expectStatus) expect(res.statusCode).toBe(expectStatus)
if (shouldCallNext) { if (shouldCallNext) {
expect(nexted).toBe(true) expect(nexted).toBe(true)
@@ -374,7 +400,11 @@ describe('archived enterprise static assets', () => {
nexted = true nexted = true
} }
setDefaultFastlySurrogateKey(req, res, () => {}) setDefaultFastlySurrogateKey(req, res, () => {})
await archivedEnterpriseVersionsAssets(req as any, res as any, next) await archivedEnterpriseVersionsAssets(
req as unknown as ExtendedRequest,
res as unknown as Response,
next,
)
expect(nexted).toBe(shouldCallNext) expect(nexted).toBe(shouldCallNext)
expect(res.statusCode).toBe(expectStatus) expect(res.statusCode).toBe(expectStatus)
}) })

View File

@@ -3,6 +3,7 @@ import path from 'path'
import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file' import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file'
import { getOpenApiVersion } from '@/versions/lib/all-versions' import { getOpenApiVersion } from '@/versions/lib/all-versions'
import findPage from '@/frame/lib/find-page' import findPage from '@/frame/lib/find-page'
import type { Context, Page } from '@/types'
import type { import type {
AuditLogEventT, AuditLogEventT,
CategorizedEvents, CategorizedEvents,
@@ -30,8 +31,8 @@ export function getCategoryNotes(): CategoryNotes {
return auditLogConfig.categoryNotes || {} return auditLogConfig.categoryNotes || {}
} }
type TitleResolutionContext = { type TitleResolutionContext = Context & {
pages: Record<string, any> pages: Record<string, Page>
redirects: Record<string, string> redirects: Record<string, string>
} }
@@ -61,7 +62,7 @@ async function resolveReferenceLinksToTitles(
currentVersion: 'free-pro-team@latest', currentVersion: 'free-pro-team@latest',
pages: context.pages, pages: context.pages,
redirects: context.redirects, redirects: context.redirects,
} } as unknown as Context
const title = await page.renderProp('title', renderContext, { textOnly: true }) const title = await page.renderProp('title', renderContext, { textOnly: true })
titles.push(title) titles.push(title)
} else { } else {

View File

@@ -14,7 +14,7 @@ import type { MarkdownFrontmatter } from '@/types'
// Type definitions - extending existing type to add missing fields and make most fields optional // Type definitions - extending existing type to add missing fields and make most fields optional
type FrontmatterData = Partial<MarkdownFrontmatter> & { type FrontmatterData = Partial<MarkdownFrontmatter> & {
autogenerated?: string autogenerated?: string
[key: string]: any [key: string]: unknown
} }
type SourceContentItem = { type SourceContentItem = {

View File

@@ -1,11 +1,11 @@
import { gitHubDocsMarkdownlint } from '@/content-linter/lib/linting-rules/index' import { gitHubDocsMarkdownlint } from '@/content-linter/lib/linting-rules/index'
import { baseConfig } from '@/content-linter/style/base' import { baseConfig } from '@/content-linter/style/base'
import { customConfig } from '@/content-linter/style/github-docs' import { customConfig } from '@/content-linter/style/github-docs'
import type { Rule } from '@/content-linter/types' import type { Rule, RuleConfig } from '@/content-linter/types'
// Import markdownlint rules - external library without TypeScript declarations // Import markdownlint rules - external library without TypeScript declarations
import markdownlintRules from '../../../../node_modules/markdownlint/lib/rules' import markdownlintRules from '../../../../node_modules/markdownlint/lib/rules'
export const customRules: Rule[] = gitHubDocsMarkdownlint.rules export const customRules: Rule[] = gitHubDocsMarkdownlint.rules
export const allRules: any[] = [...markdownlintRules, ...gitHubDocsMarkdownlint.rules] export const allRules: Rule[] = [...markdownlintRules, ...gitHubDocsMarkdownlint.rules]
export const allConfig: Record<string, any> = { ...baseConfig, ...customConfig } export const allConfig = { ...baseConfig, ...customConfig } as unknown as RuleConfig

View File

@@ -7,7 +7,7 @@ import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types
interface Frontmatter { interface Frontmatter {
redirect_from?: string | string[] redirect_from?: string | string[]
children?: string[] children?: string[]
[key: string]: any [key: string]: unknown
} }
const ERROR_MESSAGE = const ERROR_MESSAGE =

View File

@@ -7,7 +7,7 @@ import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types
interface Frontmatter { interface Frontmatter {
heroImage?: string heroImage?: string
[key: string]: any [key: string]: unknown
} }
// Get the list of valid hero images (without extensions) // Get the list of valid hero images (without extensions)

View File

@@ -6,7 +6,7 @@ import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types
interface Frontmatter { interface Frontmatter {
introLinks?: Record<string, string> introLinks?: Record<string, string>
[key: string]: any [key: string]: unknown
} }
// Get the valid introLinks keys from ui.yml // Get the valid introLinks keys from ui.yml

View File

@@ -5,6 +5,12 @@ import { addError } from 'markdownlint-rule-helpers'
import { getFrontmatter } from '../helpers/utils' import { getFrontmatter } from '../helpers/utils'
import type { RuleParams, RuleErrorCallback } from '@/content-linter/types' import type { RuleParams, RuleErrorCallback } from '@/content-linter/types'
interface Frontmatter {
recommended?: string[]
layout?: string
[key: string]: unknown
}
function isValidArticlePath(articlePath: string, currentFilePath: string): boolean { function isValidArticlePath(articlePath: string, currentFilePath: string): boolean {
const ROOT = process.env.ROOT || '.' const ROOT = process.env.ROOT || '.'
@@ -53,7 +59,7 @@ export const frontmatterLandingRecommended = {
tags: ['frontmatter', 'landing', 'recommended'], tags: ['frontmatter', 'landing', 'recommended'],
function: (params: RuleParams, onError: RuleErrorCallback) => { function: (params: RuleParams, onError: RuleErrorCallback) => {
// Using any for frontmatter as it's a dynamic YAML object with varying properties // Using any for frontmatter as it's a dynamic YAML object with varying properties
const fm: any = getFrontmatter(params.lines) const fm = getFrontmatter(params.lines) as Frontmatter | null
if (!fm || !fm.recommended) return if (!fm || !fm.recommended) return
const recommendedLine: string | undefined = params.lines.find((line) => const recommendedLine: string | undefined = params.lines.find((line) =>

View File

@@ -37,7 +37,9 @@ export const frontmatterSchema: Rule = {
// Check that the frontmatter matches the schema // Check that the frontmatter matches the schema
const { errors } = readFrontmatter(params.lines.join('\n'), { schema: frontmatter.schema }) const { errors } = readFrontmatter(params.lines.join('\n'), { schema: frontmatter.schema })
const formattedErrors = formatAjvErrors(errors as any) const formattedErrors = formatAjvErrors(
errors as unknown as Parameters<typeof formatAjvErrors>[0],
)
for (const error of formattedErrors) { for (const error of formattedErrors) {
// If the missing property is at the top level, we don't have a line // If the missing property is at the top level, we don't have a line
// to point to. In that case, the error will be added to line 1. // to point to. In that case, the error will be added to line 1.

View File

@@ -4,7 +4,7 @@ import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types
interface Frontmatter { interface Frontmatter {
versions?: Record<string, string | string[]> versions?: Record<string, string | string[]>
[key: string]: any [key: string]: unknown
} }
export const frontmatterVersionsWhitespace: Rule = { export const frontmatterVersionsWhitespace: Rule = {

View File

@@ -9,7 +9,7 @@ interface Frontmatter {
product_video_transcript?: string product_video_transcript?: string
title?: string title?: string
layout?: string layout?: string
[key: string]: any [key: string]: unknown
} }
export const frontmatterVideoTranscripts: Rule = { export const frontmatterVideoTranscripts: Rule = {

View File

@@ -672,6 +672,7 @@ function getMarkdownLintConfig(
const ymlSearchReplaceRules = [] const ymlSearchReplaceRules = []
const frontmatterSearchReplaceRules = [] const frontmatterSearchReplaceRules = []
if (!ruleConfig.rules) continue
for (const searchRule of ruleConfig.rules) { for (const searchRule of ruleConfig.rules) {
const searchRuleSeverity = getSeverity(searchRule, isPrecommit) const searchRuleSeverity = getSeverity(searchRule, isPrecommit)
if (filterErrorsOnly && searchRuleSeverity !== 'error') continue if (filterErrorsOnly && searchRuleSeverity !== 'error') continue

View File

@@ -16,7 +16,7 @@ export interface RuleParams {
frontMatterLines: string[] // array of frontmatter lines frontMatterLines: string[] // array of frontmatter lines
tokens?: MarkdownToken[] // markdown tokens (when using markdownit parser) tokens?: MarkdownToken[] // markdown tokens (when using markdownit parser)
config?: { config?: {
[key: string]: any // rule-specific configuration [key: string]: unknown // rule-specific configuration
} }
} }
@@ -26,7 +26,7 @@ export interface RuleErrorCallback {
detail?: string, detail?: string,
context?: string, context?: string,
range?: [number, number], range?: [number, number],
fixInfo?: any, fixInfo?: unknown,
): void ): void
} }
@@ -44,6 +44,8 @@ export type Rule = {
type RuleDetail = Rule & { type RuleDetail = Rule & {
name: string name: string
'partial-markdown-files': boolean 'partial-markdown-files': boolean
'yml-files'?: boolean
applyToFrontmatter?: boolean
message: string message: string
severity: string severity: string
searchPattern: string searchPattern: string
@@ -54,6 +56,7 @@ type RuleDetail = Rule & {
export type Config = { export type Config = {
severity: string severity: string
'partial-markdown-files': boolean 'partial-markdown-files': boolean
'yml-files'?: boolean
allowed_languages?: string[] allowed_languages?: string[]
style?: string style?: string
rules?: RuleDetail[] rules?: RuleDetail[]

View File

@@ -29,13 +29,14 @@ export default async function categoriesForSupport(req: ExtendedRequest, res: Re
// We can't get the rendered titles from middleware/render-tree-titles // We can't get the rendered titles from middleware/render-tree-titles
// here because that middleware only runs on the current version, and this // here because that middleware only runs on the current version, and this
// middleware processes all versions. // middleware processes all versions.
if (!req.context) return
const name = categoryPage.page.title.includes('{') const name = categoryPage.page.title.includes('{')
? await categoryPage.page.renderProp('title', req.context, renderOpts) ? await categoryPage.page.renderProp('title', req.context, renderOpts)
: categoryPage.page.title : categoryPage.page.title
allCategories.push({ allCategories.push({
name, name,
published_articles: await findArticlesPerCategory(categoryPage, [], req.context!), published_articles: await findArticlesPerCategory(categoryPage, [], req.context),
}) })
}), }),
) )

View File

@@ -106,7 +106,7 @@ export default async function renderPage(req: ExtendedRequest, res: Response) {
req.context.currentVersion === 'free-pro-team@latest' || req.context.currentVersion === 'free-pro-team@latest' ||
!allVersions[req.context.currentVersion!] !allVersions[req.context.currentVersion!]
) { ) {
page.fullTitle += ` - ${context.site!.data.ui.header.github_docs}` page.fullTitle += ` - ${get(context.site!.data.ui, 'header.github_docs')}`
} else { } else {
const { versionTitle } = allVersions[req.context.currentVersion!] const { versionTitle } = allVersions[req.context.currentVersion!]
page.fullTitle += ' - ' page.fullTitle += ' - '

View File

@@ -1,6 +1,7 @@
import { describe, expect, test } from 'vitest' import { describe, expect, test } from 'vitest'
import getRedirect from '../../lib/get-redirect' import getRedirect from '../../lib/get-redirect'
import type { Context } from '@/types'
import { import {
latest, latest,
latestStable, latestStable,
@@ -10,7 +11,8 @@ import {
// Test helper type for mocking contexts // Test helper type for mocking contexts
type TestContext = { type TestContext = {
pages: Record<string, any> [key: string]: unknown
pages: Record<string, unknown>
redirects: Record<string, string> redirects: Record<string, string>
} }
@@ -31,7 +33,7 @@ describe('getRedirect basics', () => {
'/enterprise/3.0/foo/bar': '/something/else', '/enterprise/3.0/foo/bar': '/something/else',
}, },
} }
expect(getRedirect(uri, ctx)).toBe('/en/something/else') expect(getRedirect(uri, ctx as unknown as Context)).toBe('/en/something/else')
}) })
test('should return undefined if nothing could be found', () => { test('should return undefined if nothing could be found', () => {
@@ -39,7 +41,7 @@ describe('getRedirect basics', () => {
pages: {}, pages: {},
redirects: {}, redirects: {},
} }
expect(getRedirect('/foo/pizza', ctx)).toBeUndefined() expect(getRedirect('/foo/pizza', ctx as unknown as Context)).toBeUndefined()
}) })
test('should just inject language on version "home pages"', () => { test('should just inject language on version "home pages"', () => {
@@ -47,16 +49,20 @@ describe('getRedirect basics', () => {
pages: {}, pages: {},
redirects: {}, redirects: {},
} }
expect(getRedirect('/enterprise-cloud@latest', ctx)).toBe('/en/enterprise-cloud@latest') expect(getRedirect('/enterprise-cloud@latest', ctx as unknown as Context)).toBe(
'/en/enterprise-cloud@latest',
)
expect(getRedirect(`/enterprise-server@${oldestSupported}`, ctx)).toBe( expect(getRedirect(`/enterprise-server@${oldestSupported}`, ctx as unknown as Context)).toBe(
`/en/enterprise-server@${oldestSupported}`, `/en/enterprise-server@${oldestSupported}`,
) )
expect(getRedirect('/enterprise-server@latest', ctx)).toBe( expect(getRedirect('/enterprise-server@latest', ctx as unknown as Context)).toBe(
`/en/enterprise-server@${latestStable}`,
)
expect(getRedirect('/enterprise-server', ctx as unknown as Context)).toBe(
`/en/enterprise-server@${latestStable}`, `/en/enterprise-server@${latestStable}`,
) )
expect(getRedirect('/enterprise-server', ctx)).toBe(`/en/enterprise-server@${latestStable}`)
}) })
test('should always "remove" the free-pro-team prefix', () => { test('should always "remove" the free-pro-team prefix', () => {
@@ -66,12 +72,14 @@ describe('getRedirect basics', () => {
'/foo': '/bar', '/foo': '/bar',
}, },
} }
expect(getRedirect('/free-pro-team@latest', ctx)).toBe('/en') expect(getRedirect('/free-pro-team@latest', ctx as unknown as Context)).toBe('/en')
// Language is fine, but the version needs to be "removed" // Language is fine, but the version needs to be "removed"
expect(getRedirect('/en/free-pro-team@latest', ctx)).toBe('/en') expect(getRedirect('/en/free-pro-team@latest', ctx as unknown as Context)).toBe('/en')
expect(getRedirect('/free-pro-team@latest/pizza', ctx)).toBe('/en/pizza') expect(getRedirect('/free-pro-team@latest/pizza', ctx as unknown as Context)).toBe('/en/pizza')
expect(getRedirect('/free-pro-team@latest/foo', ctx)).toBe('/en/bar') expect(getRedirect('/free-pro-team@latest/foo', ctx as unknown as Context)).toBe('/en/bar')
expect(getRedirect('/free-pro-team@latest/github', ctx)).toBe('/en/github') expect(getRedirect('/free-pro-team@latest/github', ctx as unknown as Context)).toBe(
'/en/github',
)
}) })
test('should handle some odd exceptions', () => { test('should handle some odd exceptions', () => {
@@ -79,14 +87,16 @@ describe('getRedirect basics', () => {
pages: {}, pages: {},
redirects: {}, redirects: {},
} }
expect(getRedirect('/desktop/guides/foo/bar', ctx)).toBe('/en/desktop/foo/bar') expect(getRedirect('/desktop/guides/foo/bar', ctx as unknown as Context)).toBe(
expect(getRedirect('/admin/guides/foo/bar', ctx)).toBe( '/en/desktop/foo/bar',
)
expect(getRedirect('/admin/guides/foo/bar', ctx as unknown as Context)).toBe(
`/en/enterprise-server@${latest}/admin/foo/bar`, `/en/enterprise-server@${latest}/admin/foo/bar`,
) )
expect(getRedirect('/admin/something/else', ctx)).toBe( expect(getRedirect('/admin/something/else', ctx as unknown as Context)).toBe(
`/en/enterprise-server@${latest}/admin/something/else`, `/en/enterprise-server@${latest}/admin/something/else`,
) )
expect(getRedirect('/insights/stuff', ctx)).toBe( expect(getRedirect('/insights/stuff', ctx as unknown as Context)).toBe(
`/en/enterprise-server@${latest}/insights/stuff`, `/en/enterprise-server@${latest}/insights/stuff`,
) )
}) })
@@ -101,12 +111,15 @@ describe('getRedirect basics', () => {
} }
// Replacing `/user` with `` worked because there exits a page of such name. // Replacing `/user` with `` worked because there exits a page of such name.
expect( expect(
getRedirect(`/enterprise-server@${previousEnterpriserServerVersion}/user/foo/bar`, ctx), getRedirect(
`/enterprise-server@${previousEnterpriserServerVersion}/user/foo/bar`,
ctx as unknown as Context,
),
).toBe(`/en/enterprise-server@${previousEnterpriserServerVersion}/foo/bar`) ).toBe(`/en/enterprise-server@${previousEnterpriserServerVersion}/foo/bar`)
expect( expect(
getRedirect( getRedirect(
`/enterprise-server@${previousEnterpriserServerVersion}/admin/guides/user-management`, `/enterprise-server@${previousEnterpriserServerVersion}/admin/guides/user-management`,
ctx, ctx as unknown as Context,
), ),
).toBe(`/en/enterprise-server@${previousEnterpriserServerVersion}/admin/github-management`) ).toBe(`/en/enterprise-server@${previousEnterpriserServerVersion}/admin/github-management`)
}) })
@@ -118,20 +131,25 @@ describe('getRedirect basics', () => {
[`/enterprise-server@${previousEnterpriserServerVersion}/foo`]: `/enterprise-server@${previousEnterpriserServerVersion}/bar`, [`/enterprise-server@${previousEnterpriserServerVersion}/foo`]: `/enterprise-server@${previousEnterpriserServerVersion}/bar`,
}, },
} }
expect(getRedirect('/enterprise', ctx)).toBe(`/en/enterprise-server@${latest}`) expect(getRedirect('/enterprise', ctx as unknown as Context)).toBe(
expect(getRedirect(`/enterprise/${previousEnterpriserServerVersion}`, ctx)).toBe( `/en/enterprise-server@${latest}`,
`/en/enterprise-server@${previousEnterpriserServerVersion}`,
)
expect(getRedirect(`/enterprise/${previousEnterpriserServerVersion}/something`, ctx)).toBe(
`/en/enterprise-server@${previousEnterpriserServerVersion}/something`,
) )
expect(
getRedirect(`/enterprise/${previousEnterpriserServerVersion}`, ctx as unknown as Context),
).toBe(`/en/enterprise-server@${previousEnterpriserServerVersion}`)
expect(
getRedirect(
`/enterprise/${previousEnterpriserServerVersion}/something`,
ctx as unknown as Context,
),
).toBe(`/en/enterprise-server@${previousEnterpriserServerVersion}/something`)
// but also respect redirects if there are some // but also respect redirects if there are some
expect(getRedirect(`/enterprise/${previousEnterpriserServerVersion}/foo`, ctx)).toBe( expect(
`/en/enterprise-server@${previousEnterpriserServerVersion}/bar`, getRedirect(`/enterprise/${previousEnterpriserServerVersion}/foo`, ctx as unknown as Context),
) ).toBe(`/en/enterprise-server@${previousEnterpriserServerVersion}/bar`)
// Unique snowflake pattern // Unique snowflake pattern
expect(getRedirect('/enterprise/github/admin/foo', ctx)).toBe( expect(getRedirect('/enterprise/github/admin/foo', ctx as unknown as Context)).toBe(
`/en/enterprise-server@${latest}/github/admin/foo`, `/en/enterprise-server@${latest}/github/admin/foo`,
) )
}) })
@@ -143,8 +161,15 @@ describe('getRedirect basics', () => {
} }
// Nothing's needed here because it's not /admin/guides and // Nothing's needed here because it's not /admin/guides and
// it already has the enterprise-server prefix. // it already has the enterprise-server prefix.
expect(getRedirect(`/en/enterprise-server@${latest}/admin/something/else`, ctx)).toBeUndefined() expect(
expect(getRedirect(`/en/enterprise-cloud@latest/user/foo`, ctx)).toBeUndefined() getRedirect(
`/en/enterprise-server@${latest}/admin/something/else`,
ctx as unknown as Context,
),
).toBeUndefined()
expect(
getRedirect(`/en/enterprise-cloud@latest/user/foo`, ctx as unknown as Context),
).toBeUndefined()
}) })
test('should redirect both the prefix and the path needs to change', () => { test('should redirect both the prefix and the path needs to change', () => {
@@ -157,7 +182,7 @@ describe('getRedirect basics', () => {
} }
// Nothing's needed here because it's not /admin/guides and // Nothing's needed here because it's not /admin/guides and
// it already has the enterprise-server prefix. // it already has the enterprise-server prefix.
expect(getRedirect('/enterprise-server/foo', ctx)).toBe( expect(getRedirect('/enterprise-server/foo', ctx as unknown as Context)).toBe(
`/en/enterprise-server@${latestStable}/bar`, `/en/enterprise-server@${latestStable}/bar`,
) )
}) })
@@ -169,8 +194,12 @@ describe('getRedirect basics', () => {
pages: {}, pages: {},
redirects: {}, redirects: {},
} }
expect(getRedirect('/enterprise/3.0', ctx)).toBe('/en/enterprise-server@3.0') expect(getRedirect('/enterprise/3.0', ctx as unknown as Context)).toBe(
expect(getRedirect('/enterprise/3.0/foo', ctx)).toBe('/en/enterprise-server@3.0/foo') '/en/enterprise-server@3.0',
)
expect(getRedirect('/enterprise/3.0/foo', ctx as unknown as Context)).toBe(
'/en/enterprise-server@3.0/foo',
)
}) })
}) })
@@ -180,16 +209,24 @@ describe('github-ae@latest', () => {
pages: {}, pages: {},
redirects: {}, redirects: {},
} }
expect(getRedirect('/github-ae@latest', ctx)).toBe('/en/enterprise-cloud@latest') expect(getRedirect('/github-ae@latest', ctx as unknown as Context)).toBe(
expect(getRedirect('/en/github-ae@latest', ctx)).toBe('/en/enterprise-cloud@latest') '/en/enterprise-cloud@latest',
)
expect(getRedirect('/en/github-ae@latest', ctx as unknown as Context)).toBe(
'/en/enterprise-cloud@latest',
)
}) })
test('should redirect to home page for admin/release-notes', () => { test('should redirect to home page for admin/release-notes', () => {
const ctx: TestContext = { const ctx: TestContext = {
pages: {}, pages: {},
redirects: {}, redirects: {},
} }
expect(getRedirect('/github-ae@latest/admin/release-notes', ctx)).toBe('/en') expect(getRedirect('/github-ae@latest/admin/release-notes', ctx as unknown as Context)).toBe(
expect(getRedirect('/en/github-ae@latest/admin/release-notes', ctx)).toBe('/en') '/en',
)
expect(getRedirect('/en/github-ae@latest/admin/release-notes', ctx as unknown as Context)).toBe(
'/en',
)
}) })
test('a page that does exits, without correction, in enterprise-cloud', () => { test('a page that does exits, without correction, in enterprise-cloud', () => {
const ctx: TestContext = { const ctx: TestContext = {
@@ -198,8 +235,12 @@ describe('github-ae@latest', () => {
}, },
redirects: {}, redirects: {},
} }
expect(getRedirect('/github-ae@latest/foo', ctx)).toBe('/en/enterprise-cloud@latest/foo') expect(getRedirect('/github-ae@latest/foo', ctx as unknown as Context)).toBe(
expect(getRedirect('/en/github-ae@latest/foo', ctx)).toBe('/en/enterprise-cloud@latest/foo') '/en/enterprise-cloud@latest/foo',
)
expect(getRedirect('/en/github-ae@latest/foo', ctx as unknown as Context)).toBe(
'/en/enterprise-cloud@latest/foo',
)
}) })
test("a page that doesn't exist in enterprise-cloud but in FPT", () => { test("a page that doesn't exist in enterprise-cloud but in FPT", () => {
const ctx: TestContext = { const ctx: TestContext = {
@@ -208,8 +249,8 @@ describe('github-ae@latest', () => {
}, },
redirects: {}, redirects: {},
} }
expect(getRedirect('/github-ae@latest/foo', ctx)).toBe('/en/foo') expect(getRedirect('/github-ae@latest/foo', ctx as unknown as Context)).toBe('/en/foo')
expect(getRedirect('/en/github-ae@latest/foo', ctx)).toBe('/en/foo') expect(getRedirect('/en/github-ae@latest/foo', ctx as unknown as Context)).toBe('/en/foo')
}) })
test("a page that doesn't exist in enterprise-cloud or in FPT", () => { test("a page that doesn't exist in enterprise-cloud or in FPT", () => {
const ctx: TestContext = { const ctx: TestContext = {
@@ -218,8 +259,8 @@ describe('github-ae@latest', () => {
}, },
redirects: {}, redirects: {},
} }
expect(getRedirect('/github-ae@latest/bar', ctx)).toBe('/en') expect(getRedirect('/github-ae@latest/bar', ctx as unknown as Context)).toBe('/en')
expect(getRedirect('/en/github-ae@latest/bar', ctx)).toBe('/en') expect(getRedirect('/en/github-ae@latest/bar', ctx as unknown as Context)).toBe('/en')
}) })
test('a URL with legacy redirects, that redirects to enterprise-cloud', () => { test('a URL with legacy redirects, that redirects to enterprise-cloud', () => {
const ctx: TestContext = { const ctx: TestContext = {
@@ -231,8 +272,12 @@ describe('github-ae@latest', () => {
'/food': '/foo', '/food': '/foo',
}, },
} }
expect(getRedirect('/github-ae@latest/food', ctx)).toBe('/en/enterprise-cloud@latest/foo') expect(getRedirect('/github-ae@latest/food', ctx as unknown as Context)).toBe(
expect(getRedirect('/en/github-ae@latest/food', ctx)).toBe('/en/enterprise-cloud@latest/foo') '/en/enterprise-cloud@latest/foo',
)
expect(getRedirect('/en/github-ae@latest/food', ctx as unknown as Context)).toBe(
'/en/enterprise-cloud@latest/foo',
)
}) })
test("a URL with legacy redirects, that can't redirect to enterprise-cloud", () => { test("a URL with legacy redirects, that can't redirect to enterprise-cloud", () => {
const ctx: TestContext = { const ctx: TestContext = {
@@ -244,15 +289,17 @@ describe('github-ae@latest', () => {
'/food': '/foo', '/food': '/foo',
}, },
} }
expect(getRedirect('/github-ae@latest/food', ctx)).toBe('/en/foo') expect(getRedirect('/github-ae@latest/food', ctx as unknown as Context)).toBe('/en/foo')
expect(getRedirect('/en/github-ae@latest/food', ctx)).toBe('/en/foo') expect(getRedirect('/en/github-ae@latest/food', ctx as unknown as Context)).toBe('/en/foo')
}) })
test('should 404 if nothing matches at all', () => { test('should 404 if nothing matches at all', () => {
const ctx = { const ctx = {
pages: {}, pages: {},
redirects: {}, redirects: {},
} }
expect(getRedirect('/github-ae@latest/never/heard/of', ctx)).toBe('/en') expect(getRedirect('/github-ae@latest/never/heard/of', ctx as unknown as Context)).toBe('/en')
expect(getRedirect('/en/github-ae@latest/never/heard/of', ctx)).toBe('/en') expect(getRedirect('/en/github-ae@latest/never/heard/of', ctx as unknown as Context)).toBe(
'/en',
)
}) })
}) })

View File

@@ -5,6 +5,7 @@ import type enterpriseServerReleases from '@/versions/lib/enterprise-server-rele
import type { ValidOcticon } from '@/landings/types' import type { ValidOcticon } from '@/landings/types'
import type { Language, Languages } from '@/languages/lib/languages-server' import type { Language, Languages } from '@/languages/lib/languages-server'
import type { MiniTocItem } from '@/frame/lib/get-mini-toc-items' import type { MiniTocItem } from '@/frame/lib/get-mini-toc-items'
import type { UIStrings } from '@/frame/components/context/MainContext'
// Shared type for resolved article information used across landing pages and carousels // Shared type for resolved article information used across landing pages and carousels
export interface ResolvedArticle { export interface ResolvedArticle {
@@ -121,7 +122,7 @@ type Redirects = {
export type Context = { export type Context = {
// Allows dynamic properties like features & version shortnames as keys // Allows dynamic properties like features & version shortnames as keys
[key: string]: any [key: string]: unknown
currentCategory?: string currentCategory?: string
error?: Error error?: Error
siteTree?: SiteTree siteTree?: SiteTree
@@ -134,7 +135,7 @@ export type Context = {
allVersions?: AllVersions allVersions?: AllVersions
currentPathWithoutLanguage?: string currentPathWithoutLanguage?: string
currentArticle?: string currentArticle?: string
query?: Record<string, any> query?: Record<string, unknown>
relativePath?: string relativePath?: string
page?: Page page?: Page
enPage?: Page enPage?: Page
@@ -143,13 +144,13 @@ export type Context = {
process?: { env: Record<string, string> } process?: { env: Record<string, string> }
site?: { site?: {
data: { data: {
ui: any ui: UIStrings
} }
} }
currentVersionObj?: Version currentVersionObj?: Version
currentProduct?: string currentProduct?: string
getEnglishPage?: (ctx: Context) => Page getEnglishPage?: (ctx: Context) => Page
getDottedData?: (dottedPath: string) => any getDottedData?: (dottedPath: string) => unknown
initialRestVersioningReleaseDate?: string initialRestVersioningReleaseDate?: string
initialRestVersioningReleaseDateLong?: string initialRestVersioningReleaseDateLong?: string
nonEnterpriseDefaultVersion?: string nonEnterpriseDefaultVersion?: string
@@ -360,8 +361,8 @@ export type Page = {
rawPermissions?: string rawPermissions?: string
languageCode: string languageCode: string
documentType: string documentType: string
renderProp: (prop: string, context: any, opts?: any) => Promise<string> renderProp: (prop: string, context: Context, opts?: Record<string, unknown>) => Promise<string>
renderTitle: (context: Context, opts?: any) => Promise<string> renderTitle: (context: Context, opts?: Record<string, unknown>) => Promise<string>
markdown: string markdown: string
versions: FrontmatterVersions versions: FrontmatterVersions
applicableVersions: string[] applicableVersions: string[]