Migrate 6 JavaScript files to TypeScript (#57969)
This commit is contained in:
@@ -2,9 +2,15 @@ import { describe, expect, test } from 'vitest'
|
||||
import { shouldIncludeResult } from '../../lib/helpers/should-include-result'
|
||||
import { reportingConfig } from '../../style/github-docs'
|
||||
|
||||
interface LintFlaw {
|
||||
severity: string
|
||||
ruleNames: string[]
|
||||
errorDetail?: string
|
||||
}
|
||||
|
||||
describe('lint report exclusions', () => {
|
||||
// Helper function to simulate the reporting logic from lint-report.ts
|
||||
function shouldIncludeInReport(flaw, filePath) {
|
||||
function shouldIncludeInReport(flaw: LintFlaw, filePath: string): boolean {
|
||||
if (!flaw.ruleNames || !Array.isArray(flaw.ruleNames)) {
|
||||
return false
|
||||
}
|
||||
@@ -1,18 +1,34 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { octiconAriaLabels } from '../../lib/linting-rules/octicon-aria-labels'
|
||||
|
||||
interface ErrorInfo {
|
||||
lineNumber: number
|
||||
detail?: string
|
||||
context?: string
|
||||
range?: [number, number]
|
||||
fixInfo?: any // Matches RuleErrorCallback signature - fixInfo structure varies by rule
|
||||
}
|
||||
|
||||
describe('octicon-aria-labels', () => {
|
||||
const rule = octiconAriaLabels
|
||||
|
||||
test('detects octicon without aria-label', () => {
|
||||
const errors = []
|
||||
const onError = (errorInfo) => {
|
||||
// Helper to create onError callback that captures errors
|
||||
function createErrorCollector() {
|
||||
const errors: ErrorInfo[] = []
|
||||
// Using any because the actual rule implementation calls onError with an object,
|
||||
// not individual parameters as defined in RuleErrorCallback
|
||||
const onError = (errorInfo: any) => {
|
||||
errors.push(errorInfo)
|
||||
}
|
||||
return { errors, onError }
|
||||
}
|
||||
|
||||
test('detects octicon without aria-label', () => {
|
||||
const { errors, onError } = createErrorCollector()
|
||||
|
||||
const content = ['This is a test with an octicon:', '{% octicon "alert" %}', 'Some more text.']
|
||||
|
||||
rule.function({ lines: content }, onError)
|
||||
rule.function({ name: 'test.md', lines: content, frontMatterLines: [] }, onError)
|
||||
|
||||
expect(errors.length).toBe(1)
|
||||
expect(errors[0].lineNumber).toBe(2)
|
||||
@@ -21,10 +37,7 @@ describe('octicon-aria-labels', () => {
|
||||
})
|
||||
|
||||
test('ignores octicons with aria-label', () => {
|
||||
const errors = []
|
||||
const onError = (errorInfo) => {
|
||||
errors.push(errorInfo)
|
||||
}
|
||||
const { errors, onError } = createErrorCollector()
|
||||
|
||||
const content = [
|
||||
'This is a test with a proper octicon:',
|
||||
@@ -32,16 +45,13 @@ describe('octicon-aria-labels', () => {
|
||||
'Some more text.',
|
||||
]
|
||||
|
||||
rule.function({ lines: content }, onError)
|
||||
rule.function({ name: 'test.md', lines: content, frontMatterLines: [] }, onError)
|
||||
|
||||
expect(errors.length).toBe(0)
|
||||
})
|
||||
|
||||
test('detects multiple octicons without aria-label', () => {
|
||||
const errors = []
|
||||
const onError = (errorInfo) => {
|
||||
errors.push(errorInfo)
|
||||
}
|
||||
const { errors, onError } = createErrorCollector()
|
||||
|
||||
const content = [
|
||||
'This is a test with multiple octicons:',
|
||||
@@ -51,7 +61,7 @@ describe('octicon-aria-labels', () => {
|
||||
'More text.',
|
||||
]
|
||||
|
||||
rule.function({ lines: content }, onError)
|
||||
rule.function({ name: 'test.md', lines: content, frontMatterLines: [] }, onError)
|
||||
|
||||
expect(errors.length).toBe(2)
|
||||
expect(errors[0].lineNumber).toBe(2)
|
||||
@@ -61,10 +71,7 @@ describe('octicon-aria-labels', () => {
|
||||
})
|
||||
|
||||
test('ignores non-octicon liquid tags', () => {
|
||||
const errors = []
|
||||
const onError = (errorInfo) => {
|
||||
errors.push(errorInfo)
|
||||
}
|
||||
const { errors, onError } = createErrorCollector()
|
||||
|
||||
const content = [
|
||||
'This is a test with non-octicon tags:',
|
||||
@@ -74,16 +81,13 @@ describe('octicon-aria-labels', () => {
|
||||
'{% endif %}',
|
||||
]
|
||||
|
||||
rule.function({ lines: content }, onError)
|
||||
rule.function({ name: 'test.md', lines: content, frontMatterLines: [] }, onError)
|
||||
|
||||
expect(errors.length).toBe(0)
|
||||
})
|
||||
|
||||
test('suggests correct fix for octicon with other attributes', () => {
|
||||
const errors = []
|
||||
const onError = (errorInfo) => {
|
||||
errors.push(errorInfo)
|
||||
}
|
||||
const { errors, onError } = createErrorCollector()
|
||||
|
||||
const content = [
|
||||
'This is a test with an octicon with other attributes:',
|
||||
@@ -91,7 +95,7 @@ describe('octicon-aria-labels', () => {
|
||||
'Some more text.',
|
||||
]
|
||||
|
||||
rule.function({ lines: content }, onError)
|
||||
rule.function({ name: 'test.md', lines: content, frontMatterLines: [] }, onError)
|
||||
|
||||
expect(errors.length).toBe(1)
|
||||
expect(errors[0].lineNumber).toBe(2)
|
||||
@@ -101,10 +105,7 @@ describe('octicon-aria-labels', () => {
|
||||
})
|
||||
|
||||
test('handles octicons with unusual spacing', () => {
|
||||
const errors = []
|
||||
const onError = (errorInfo) => {
|
||||
errors.push(errorInfo)
|
||||
}
|
||||
const { errors, onError } = createErrorCollector()
|
||||
|
||||
const content = [
|
||||
'This is a test with unusual spacing:',
|
||||
@@ -112,7 +113,7 @@ describe('octicon-aria-labels', () => {
|
||||
'Some more text.',
|
||||
]
|
||||
|
||||
rule.function({ lines: content }, onError)
|
||||
rule.function({ name: 'test.md', lines: content, frontMatterLines: [] }, onError)
|
||||
|
||||
expect(errors.length).toBe(1)
|
||||
expect(errors[0].lineNumber).toBe(2)
|
||||
@@ -120,10 +121,7 @@ describe('octicon-aria-labels', () => {
|
||||
})
|
||||
|
||||
test('handles octicons split across multiple lines', () => {
|
||||
const errors = []
|
||||
const onError = (errorInfo) => {
|
||||
errors.push(errorInfo)
|
||||
}
|
||||
const { errors, onError } = createErrorCollector()
|
||||
|
||||
const content = [
|
||||
'This is a test with a multi-line octicon:',
|
||||
@@ -133,17 +131,14 @@ describe('octicon-aria-labels', () => {
|
||||
'Some more text.',
|
||||
]
|
||||
|
||||
rule.function({ lines: content }, onError)
|
||||
rule.function({ name: 'test.md', lines: content, frontMatterLines: [] }, onError)
|
||||
|
||||
expect(errors.length).toBe(1)
|
||||
expect(errors[0].detail).toContain('aria-label=chevron-down')
|
||||
})
|
||||
|
||||
test('falls back to "icon" when octicon name cannot be determined', () => {
|
||||
const errors = []
|
||||
const onError = (errorInfo) => {
|
||||
errors.push(errorInfo)
|
||||
}
|
||||
const { errors, onError } = createErrorCollector()
|
||||
|
||||
const content = [
|
||||
'This is a test with a malformed octicon:',
|
||||
@@ -151,7 +146,7 @@ describe('octicon-aria-labels', () => {
|
||||
'Some more text.',
|
||||
]
|
||||
|
||||
rule.function({ lines: content }, onError)
|
||||
rule.function({ name: 'test.md', lines: content, frontMatterLines: [] }, onError)
|
||||
|
||||
expect(errors.length).toBe(1)
|
||||
expect(errors[0].detail).toContain('aria-label=icon')
|
||||
@@ -1,12 +1,13 @@
|
||||
import { describe, expect, test, beforeEach, afterEach } from 'vitest'
|
||||
import { renderContent } from '@/content-render/index'
|
||||
import { TitleFromAutotitleError } from '@/content-render/unified/rewrite-local-links'
|
||||
import type { Context } from '@/types'
|
||||
|
||||
describe('link error line numbers', () => {
|
||||
let fs
|
||||
let originalReadFileSync
|
||||
let originalExistsSync
|
||||
let mockContext
|
||||
let fs: any // Dynamic import of fs module for mocking in tests
|
||||
let originalReadFileSync: any // Storing original fs.readFileSync for restoration after test
|
||||
let originalExistsSync: any // Storing original fs.existsSync for restoration after test
|
||||
let mockContext: Context
|
||||
|
||||
beforeEach(async () => {
|
||||
// Set up file system mocking
|
||||
@@ -20,11 +21,11 @@ describe('link error line numbers', () => {
|
||||
mockContext = {
|
||||
currentLanguage: 'en',
|
||||
currentVersion: 'free-pro-team@latest',
|
||||
pages: new Map(),
|
||||
redirects: new Map(),
|
||||
pages: {} as any,
|
||||
redirects: {} as any,
|
||||
page: {
|
||||
fullPath: '/fake/test-file.md',
|
||||
},
|
||||
} as any,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -60,14 +61,16 @@ More content here.`
|
||||
// The broken link is on line 10 in the original file
|
||||
// (3 lines of frontmatter + 1 blank line + 1 title + 1 blank + 1 content + 1 blank + 1 link line)
|
||||
// The error message should reference the correct line number
|
||||
expect(error.message).toContain('/nonexistent/page')
|
||||
expect(error.message).toContain('could not be resolved')
|
||||
expect(error.message).toContain('(Line: 10)')
|
||||
expect((error as TitleFromAutotitleError).message).toContain('/nonexistent/page')
|
||||
expect((error as TitleFromAutotitleError).message).toContain('could not be resolved')
|
||||
expect((error as TitleFromAutotitleError).message).toContain('(Line: 10)')
|
||||
}
|
||||
})
|
||||
|
||||
test('reports correct line numbers with different frontmatter sizes', async () => {
|
||||
mockContext.page.fullPath = '/fake/test-file-2.md'
|
||||
mockContext.page = {
|
||||
fullPath: '/fake/test-file-2.md',
|
||||
} as any
|
||||
|
||||
// Test with more extensive frontmatter
|
||||
const template = `---
|
||||
@@ -96,13 +99,15 @@ Content with a [AUTOTITLE](/another/nonexistent/page) link.`
|
||||
expect.fail('Expected TitleFromAutotitleError to be thrown')
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(TitleFromAutotitleError)
|
||||
expect(error.message).toContain('/another/nonexistent/page')
|
||||
expect(error.message).toContain('could not be resolved')
|
||||
expect((error as TitleFromAutotitleError).message).toContain('/another/nonexistent/page')
|
||||
expect((error as TitleFromAutotitleError).message).toContain('could not be resolved')
|
||||
}
|
||||
})
|
||||
|
||||
test('handles files without frontmatter correctly', async () => {
|
||||
mockContext.page.fullPath = '/fake/no-frontmatter.md'
|
||||
mockContext.page = {
|
||||
fullPath: '/fake/no-frontmatter.md',
|
||||
} as any
|
||||
|
||||
// Test content without frontmatter
|
||||
const template = `# Simple Title
|
||||
@@ -118,13 +123,15 @@ Here is a broken link: [AUTOTITLE](/missing/page).`
|
||||
expect.fail('Expected TitleFromAutotitleError to be thrown')
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(TitleFromAutotitleError)
|
||||
expect(error.message).toContain('/missing/page')
|
||||
expect(error.message).toContain('could not be resolved')
|
||||
expect((error as TitleFromAutotitleError).message).toContain('/missing/page')
|
||||
expect((error as TitleFromAutotitleError).message).toContain('could not be resolved')
|
||||
}
|
||||
})
|
||||
|
||||
test('error message format is improved', async () => {
|
||||
mockContext.page.fullPath = '/fake/message-test.md'
|
||||
mockContext.page = {
|
||||
fullPath: '/fake/message-test.md',
|
||||
} as any
|
||||
|
||||
const template = `---
|
||||
title: Message Test
|
||||
@@ -142,13 +149,17 @@ title: Message Test
|
||||
expect(error).toBeInstanceOf(TitleFromAutotitleError)
|
||||
|
||||
// Check that the new error message format is used
|
||||
expect(error.message).toContain('could not be resolved in one or more versions')
|
||||
expect(error.message).toContain('Make sure that this link can be reached from all versions')
|
||||
expect(error.message).toContain('/test/broken/link')
|
||||
expect((error as TitleFromAutotitleError).message).toContain(
|
||||
'could not be resolved in one or more versions',
|
||||
)
|
||||
expect((error as TitleFromAutotitleError).message).toContain(
|
||||
'Make sure that this link can be reached from all versions',
|
||||
)
|
||||
expect((error as TitleFromAutotitleError).message).toContain('/test/broken/link')
|
||||
|
||||
// Check that the old error message format is NOT used
|
||||
expect(error.message).not.toContain('Unable to find Page by')
|
||||
expect(error.message).not.toContain('To fix it, look at')
|
||||
expect((error as TitleFromAutotitleError).message).not.toContain('Unable to find Page by')
|
||||
expect((error as TitleFromAutotitleError).message).not.toContain('To fix it, look at')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -2,9 +2,33 @@ import cheerio from 'cheerio'
|
||||
import { range } from 'lodash-es'
|
||||
|
||||
import { renderContent } from '@/content-render/index'
|
||||
import type { Context } from '@/types'
|
||||
|
||||
interface MiniTocContents {
|
||||
href: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export interface MiniTocItem {
|
||||
contents: MiniTocContents
|
||||
items?: MiniTocItem[]
|
||||
platform?: string
|
||||
}
|
||||
|
||||
interface FlatTocItem {
|
||||
contents: MiniTocContents
|
||||
headingLevel: number
|
||||
platform: string
|
||||
indentationLevel: number
|
||||
items?: FlatTocItem[]
|
||||
}
|
||||
|
||||
// Keep maxHeadingLevel=2 for accessibility reasons, see docs-engineering#2701 for more info
|
||||
export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope = '') {
|
||||
export default function getMiniTocItems(
|
||||
html: string,
|
||||
maxHeadingLevel = 2,
|
||||
headingScope = '',
|
||||
): MiniTocItem[] {
|
||||
const $ = cheerio.load(html, { xmlMode: true })
|
||||
|
||||
// eg `h2, h3` or `h2, h3, h4` depending on maxHeadingLevel
|
||||
@@ -20,7 +44,7 @@ export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope
|
||||
// - `platform` to show or hide platform-specific headings via client JS
|
||||
|
||||
// H1 = highest importance, H6 = lowest importance
|
||||
let mostImportantHeadingLevel
|
||||
let mostImportantHeadingLevel: number | undefined
|
||||
const flatToc = headings
|
||||
.get()
|
||||
.filter((item) => {
|
||||
@@ -48,13 +72,14 @@ export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope
|
||||
// remove any <strong> tags but leave content
|
||||
$('strong', item).map((i, el) => $(el).replaceWith($(el).contents()))
|
||||
|
||||
const contents = { href, title: $(item).text().trim() }
|
||||
const headingLevel = parseInt($(item)[0].name.match(/\d+/)[0], 10) || 0 // the `2` from `h2`
|
||||
const contents: MiniTocContents = { href, title: $(item).text().trim() }
|
||||
const element = $(item)[0] as cheerio.TagElement
|
||||
const headingLevel = parseInt(element.name.match(/\d+/)![0], 10) || 0 // the `2` from `h2`
|
||||
|
||||
const platform = $(item).parent('.ghd-tool').attr('class') || ''
|
||||
|
||||
// track the most important heading level while we're looping through the items
|
||||
if (headingLevel < mostImportantHeadingLevel || mostImportantHeadingLevel === undefined) {
|
||||
if (headingLevel < mostImportantHeadingLevel! || mostImportantHeadingLevel === undefined) {
|
||||
mostImportantHeadingLevel = headingLevel
|
||||
}
|
||||
|
||||
@@ -65,8 +90,8 @@ export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope
|
||||
// set the indentation level for each item based on the most important
|
||||
// heading level in the current article
|
||||
return {
|
||||
...item,
|
||||
indentationLevel: item.headingLevel - mostImportantHeadingLevel,
|
||||
...item!,
|
||||
indentationLevel: item!.headingLevel - mostImportantHeadingLevel!,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -77,18 +102,18 @@ export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope
|
||||
}
|
||||
|
||||
// Recursively build a tree from the list of allItems
|
||||
function buildNestedToc(allItems, startIndex = 0) {
|
||||
function buildNestedToc(allItems: FlatTocItem[], startIndex = 0): FlatTocItem[] {
|
||||
const startItem = allItems[startIndex]
|
||||
if (!startItem) {
|
||||
return []
|
||||
}
|
||||
let curLevelIndentation = startItem.indentationLevel
|
||||
const currentLevel = []
|
||||
const currentLevel: FlatTocItem[] = []
|
||||
|
||||
for (let cursor = startIndex; cursor < allItems.length; cursor++) {
|
||||
const cursorItem = allItems[cursor]
|
||||
const nextItem = allItems[cursor + 1]
|
||||
const nextItemIsNested = nextItem && nextItem.indentationLevel > cursorItem.indentationLevel
|
||||
const nextItemIsNested = nextItem && nextItem.indentationLevel! > cursorItem.indentationLevel!
|
||||
|
||||
// if it's the current indentation level, push it on and keep going
|
||||
if (curLevelIndentation === cursorItem.indentationLevel) {
|
||||
@@ -125,10 +150,10 @@ function buildNestedToc(allItems, startIndex = 0) {
|
||||
|
||||
// Strip the bits and pieces from each object in the array that are
|
||||
// not needed in the React component rendering.
|
||||
function minimalMiniToc(toc) {
|
||||
function minimalMiniToc(toc: FlatTocItem[]): MiniTocItem[] {
|
||||
return toc.map(({ platform, contents, items }) => {
|
||||
const minimal = { contents }
|
||||
const subItems = minimalMiniToc(items)
|
||||
const minimal: MiniTocItem = { contents }
|
||||
const subItems = minimalMiniToc(items || [])
|
||||
if (subItems.length) minimal.items = subItems
|
||||
if (platform) minimal.platform = platform
|
||||
return minimal
|
||||
@@ -136,11 +161,11 @@ function minimalMiniToc(toc) {
|
||||
}
|
||||
|
||||
export async function getAutomatedPageMiniTocItems(
|
||||
items,
|
||||
context,
|
||||
items: string[],
|
||||
context: Context,
|
||||
depth = 2,
|
||||
markdownHeading = '',
|
||||
) {
|
||||
): Promise<MiniTocItem[]> {
|
||||
const titles =
|
||||
markdownHeading +
|
||||
items
|
||||
@@ -28,7 +28,7 @@ async function buildRenderedPage(req: ExtendedRequest): Promise<string> {
|
||||
return (await pageRenderTimed(context)) as string
|
||||
}
|
||||
|
||||
async function buildMiniTocItems(req: ExtendedRequest): Promise<string | undefined> {
|
||||
function buildMiniTocItems(req: ExtendedRequest) {
|
||||
const { context } = req
|
||||
if (!context) throw new Error('request not contextualized')
|
||||
const { page } = context
|
||||
@@ -38,7 +38,7 @@ async function buildMiniTocItems(req: ExtendedRequest): Promise<string | undefin
|
||||
return
|
||||
}
|
||||
|
||||
return getMiniTocItems(context.renderedPage, 0)
|
||||
return getMiniTocItems(context.renderedPage || '', 0)
|
||||
}
|
||||
|
||||
export default async function renderPage(req: ExtendedRequest, res: Response) {
|
||||
@@ -92,7 +92,7 @@ export default async function renderPage(req: ExtendedRequest, res: Response) {
|
||||
|
||||
if (!req.context) throw new Error('request not contextualized')
|
||||
req.context.renderedPage = await buildRenderedPage(req)
|
||||
req.context.miniTocItems = await buildMiniTocItems(req)
|
||||
req.context.miniTocItems = buildMiniTocItems(req)
|
||||
|
||||
// Stop processing if the connection was already dropped
|
||||
if (isConnectionDropped(req, res)) return
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('mini toc items', () => {
|
||||
].join('\n')
|
||||
const tocItems = getMiniTocItems(html, 3)
|
||||
expect(tocItems.length).toBe(2)
|
||||
expect(tocItems[0].items.length).toBe(3)
|
||||
expect(tocItems[0].items?.length).toBe(3)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -56,10 +56,11 @@ describe('mini toc items', () => {
|
||||
].join('\n')
|
||||
const tocItems = getMiniTocItems(html, 3)
|
||||
expect(tocItems.length).toBe(4)
|
||||
expect(tocItems[3].items.length).toBe(1)
|
||||
expect(tocItems[3].items?.length).toBe(1)
|
||||
})
|
||||
|
||||
// Mock scenario from: /en/organizations/managing-membership-in-your-organization/inviting-users-to-join-your-organization
|
||||
// Mock scenario from:
|
||||
// /en/organizations/managing-membership-in-your-organization/inviting-users-to-join-your-organization
|
||||
test('creates empty toc', async () => {
|
||||
const html = h1('test')
|
||||
const tocItems = getMiniTocItems(html, 3)
|
||||
@@ -86,6 +87,6 @@ describe('mini toc items', () => {
|
||||
].join('\n')
|
||||
const tocItems = getMiniTocItems(html, 5)
|
||||
expect(tocItems.length).toBe(3)
|
||||
expect(tocItems[1].items[0].items[0].items.length).toBe(1)
|
||||
expect(tocItems[1].items?.[0].items?.[0].items?.length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function GraphqlBreakingChanges({
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async (context) => {
|
||||
const { getGraphqlBreakingChanges } = await import('@/graphql/lib/index')
|
||||
const { getAutomatedPageMiniTocItems } = await import('@/frame/lib/get-mini-toc-items.js')
|
||||
const { getAutomatedPageMiniTocItems } = await import('@/frame/lib/get-mini-toc-items')
|
||||
|
||||
const req = context.req as any
|
||||
const res = context.res as any
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function GraphqlChangelog({ mainContext, schema, automatedPageCon
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async (context) => {
|
||||
const { getGraphqlChangelog } = await import('@/graphql/lib/index')
|
||||
const { getAutomatedPageMiniTocItems } = await import('@/frame/lib/get-mini-toc-items.js')
|
||||
const { getAutomatedPageMiniTocItems } = await import('@/frame/lib/get-mini-toc-items')
|
||||
|
||||
const req = context.req as any
|
||||
const res = context.res as any
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function GraphqlPreviews({ mainContext, schema, automatedPageCont
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async (context) => {
|
||||
const { getPreviews } = await import('@/graphql/lib/index')
|
||||
const { getAutomatedPageMiniTocItems } = await import('@/frame/lib/get-mini-toc-items.js')
|
||||
const { getAutomatedPageMiniTocItems } = await import('@/frame/lib/get-mini-toc-items')
|
||||
|
||||
const req = context.req as any
|
||||
const res = context.res as any
|
||||
|
||||
@@ -15,7 +15,13 @@ import { nonAutomatedRestPaths } from '../lib/config'
|
||||
import { deprecated } from '@/versions/lib/enterprise-server-releases'
|
||||
import walkFiles from '@/workflows/walk-files'
|
||||
|
||||
export async function getDiffOpenAPIContentRest() {
|
||||
type CheckObject = Record<string, Record<string, string[]>>
|
||||
|
||||
type DifferenceResult = Record<string, string[]>
|
||||
|
||||
type ErrorMessages = Record<string, Record<string, { contentDir: string[]; openAPI: string[] }>>
|
||||
|
||||
export async function getDiffOpenAPIContentRest(): Promise<ErrorMessages> {
|
||||
const contentFiles = getAutomatedMarkdownFiles('content/rest')
|
||||
// Creating the categories/subcategories based on the current content directory
|
||||
const checkContentDir = await createCheckContentDirectory(contentFiles)
|
||||
@@ -25,15 +31,13 @@ export async function getDiffOpenAPIContentRest() {
|
||||
|
||||
// Get Differences between categories/subcategories from dereferenced schemas and the content/rest directory frontmatter versions
|
||||
const differences = getDifferences(openAPISchemaCheck, checkContentDir)
|
||||
const errorMessages = {}
|
||||
const errorMessages: ErrorMessages = {}
|
||||
|
||||
if (Object.keys(differences).length > 0) {
|
||||
for (const schemaName in differences) {
|
||||
errorMessages[schemaName] = {}
|
||||
|
||||
differences[schemaName].forEach((category) => {
|
||||
if (!errorMessages[schemaName]) errorMessages[schemaName] = category
|
||||
|
||||
errorMessages[schemaName][category] = {
|
||||
contentDir: checkContentDir[schemaName][category],
|
||||
openAPI: openAPISchemaCheck[schemaName][category],
|
||||
@@ -45,7 +49,7 @@ export async function getDiffOpenAPIContentRest() {
|
||||
return errorMessages
|
||||
}
|
||||
|
||||
async function createOpenAPISchemasCheck() {
|
||||
async function createOpenAPISchemasCheck(): Promise<CheckObject> {
|
||||
const openAPICheck = createCheckObj()
|
||||
const restDirectory = fs
|
||||
.readdirSync(REST_DATA_DIR)
|
||||
@@ -55,12 +59,12 @@ async function createOpenAPISchemasCheck() {
|
||||
|
||||
restDirectory.forEach((dir) => {
|
||||
const filename = path.join(REST_DATA_DIR, dir, REST_SCHEMA_FILENAME)
|
||||
const fileSchema = JSON.parse(fs.readFileSync(filename))
|
||||
const fileSchema = JSON.parse(fs.readFileSync(filename, 'utf8'))
|
||||
const categories = Object.keys(fileSchema).sort()
|
||||
const version = getDocsVersion(dir)
|
||||
|
||||
categories.forEach((category) => {
|
||||
const subcategories = Object.keys(fileSchema[category])
|
||||
const subcategories = Object.keys(fileSchema[category]) as string[]
|
||||
if (isApiVersioned(version)) {
|
||||
getOnlyApiVersions(version).forEach(
|
||||
(apiVersion) => (openAPICheck[apiVersion][category] = subcategories.sort()),
|
||||
@@ -74,12 +78,12 @@ async function createOpenAPISchemasCheck() {
|
||||
return openAPICheck
|
||||
}
|
||||
|
||||
async function createCheckContentDirectory(contentFiles) {
|
||||
async function createCheckContentDirectory(contentFiles: string[]): Promise<CheckObject> {
|
||||
const checkContent = createCheckObj()
|
||||
|
||||
for (const filename of contentFiles) {
|
||||
const { data } = frontmatter(await fs.promises.readFile(filename, 'utf8'))
|
||||
const applicableVersions = getApplicableVersions(data.versions, filename)
|
||||
const applicableVersions = getApplicableVersions(data?.versions, filename)
|
||||
const splitPath = filename.split('/')
|
||||
const subCategory = splitPath[splitPath.length - 1].replace('.md', '')
|
||||
const category =
|
||||
@@ -104,18 +108,18 @@ async function createCheckContentDirectory(contentFiles) {
|
||||
return checkContent
|
||||
}
|
||||
|
||||
function isApiVersioned(version) {
|
||||
function isApiVersioned(version: string): boolean {
|
||||
return allVersions[version] && allVersions[version].apiVersions.length > 0
|
||||
}
|
||||
|
||||
function getOnlyApiVersions(version) {
|
||||
function getOnlyApiVersions(version: string): string[] {
|
||||
return allVersions[version].apiVersions.map(
|
||||
(apiVersion) => `${allVersions[version].version}.${apiVersion}`,
|
||||
)
|
||||
}
|
||||
|
||||
function createCheckObj() {
|
||||
const versions = {}
|
||||
function createCheckObj(): CheckObject {
|
||||
const versions: CheckObject = {}
|
||||
Object.keys(allVersions).forEach((version) => {
|
||||
isApiVersioned(version)
|
||||
? getOnlyApiVersions(version).forEach((apiVersion) => (versions[apiVersion] = {}))
|
||||
@@ -125,8 +129,11 @@ function createCheckObj() {
|
||||
return versions
|
||||
}
|
||||
|
||||
function getDifferences(openAPISchemaCheck, contentCheck) {
|
||||
const differences = {}
|
||||
function getDifferences(
|
||||
openAPISchemaCheck: CheckObject,
|
||||
contentCheck: CheckObject,
|
||||
): DifferenceResult {
|
||||
const differences: DifferenceResult = {}
|
||||
for (const version in openAPISchemaCheck) {
|
||||
const diffOpenApiContent = difference(openAPISchemaCheck[version], contentCheck[version])
|
||||
if (Object.keys(diffOpenApiContent).length > 0) differences[version] = diffOpenApiContent
|
||||
@@ -135,7 +142,7 @@ function getDifferences(openAPISchemaCheck, contentCheck) {
|
||||
return differences
|
||||
}
|
||||
|
||||
function difference(obj1, obj2) {
|
||||
function difference(obj1: Record<string, string[]>, obj2: Record<string, string[]>): string[] {
|
||||
const diff = Object.keys(obj1).reduce((result, key) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(obj2, key)) {
|
||||
result.push(key)
|
||||
@@ -149,7 +156,7 @@ function difference(obj1, obj2) {
|
||||
return diff
|
||||
}
|
||||
|
||||
export function getAutomatedMarkdownFiles(rootDir) {
|
||||
export function getAutomatedMarkdownFiles(rootDir: string): string[] {
|
||||
return walkFiles(rootDir, '.md')
|
||||
.filter((file) => !file.includes('index.md'))
|
||||
.filter((file) => !nonAutomatedRestPaths.some((path) => file.includes(path)))
|
||||
@@ -3,6 +3,7 @@ import type { Failbot } from '@github/failbot'
|
||||
|
||||
import type enterpriseServerReleases from '@/versions/lib/enterprise-server-releases.d'
|
||||
import type { ValidOcticon } from '@/landings/types'
|
||||
import type { MiniTocItem } from '@/frame/lib/get-mini-toc-items'
|
||||
|
||||
// Shared type for resolved article information used across landing pages and carousels
|
||||
export interface ResolvedArticle {
|
||||
@@ -180,7 +181,7 @@ export type Context = {
|
||||
featuredLinks?: FeaturedLinksExpanded
|
||||
currentLearningTrack?: LearningTrack | null
|
||||
renderedPage?: string
|
||||
miniTocItems?: string | undefined
|
||||
miniTocItems?: MiniTocItem[]
|
||||
markdownRequested?: boolean
|
||||
}
|
||||
export type LearningTracks = {
|
||||
|
||||
Reference in New Issue
Block a user