Migrate 15 JavaScript files to TypeScript (#57875)
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
import fs from 'fs'
|
||||
import { spawn } from 'child_process'
|
||||
|
||||
const rule = process.argv[2]
|
||||
const rule: string | undefined = process.argv[2]
|
||||
if (!rule) {
|
||||
console.error('Please specify a rule to disable.')
|
||||
process.exit(1)
|
||||
@@ -38,36 +38,40 @@ const childProcess = spawn('npm', [
|
||||
rule,
|
||||
])
|
||||
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
childProcess.stdout.on('data', (data: Buffer) => {
|
||||
if (verbose) console.log(data.toString())
|
||||
})
|
||||
|
||||
childProcess.stderr.on('data', function (data) {
|
||||
childProcess.stderr.on('data', function (data: Buffer) {
|
||||
if (verbose) console.log(data.toString())
|
||||
})
|
||||
|
||||
let matchingRulesFound = 0
|
||||
childProcess.on('close', (code) => {
|
||||
childProcess.on('close', (code: number | null) => {
|
||||
if (code === 0) {
|
||||
console.log(`No violations for rule, "${rule}" found.`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const markdownViolations = JSON.parse(fs.readFileSync('markdown-violations.json', 'utf8'))
|
||||
const markdownViolations: Record<string, Array<{ lineNumber: number }>> = JSON.parse(
|
||||
fs.readFileSync('markdown-violations.json', 'utf8'),
|
||||
)
|
||||
console.log(`${Object.values(markdownViolations).flat().length} violations found.`)
|
||||
|
||||
Object.entries(markdownViolations).forEach(([fileName, results]) => {
|
||||
console.log(fileName)
|
||||
console.log(results)
|
||||
const fileLines = fs.readFileSync(fileName, 'utf8').split('\n')
|
||||
results.forEach((result) => {
|
||||
matchingRulesFound++
|
||||
const lineIndex = result.lineNumber - 1
|
||||
const offendingLine = fileLines[lineIndex]
|
||||
fileLines[lineIndex] = offendingLine.concat(` <!-- markdownlint-disable-line ${rule} -->`)
|
||||
})
|
||||
fs.writeFileSync(fileName, fileLines.join('\n'), 'utf8')
|
||||
})
|
||||
Object.entries(markdownViolations).forEach(
|
||||
([fileName, results]: [string, Array<{ lineNumber: number }>]) => {
|
||||
console.log(fileName)
|
||||
console.log(results)
|
||||
const fileLines = fs.readFileSync(fileName, 'utf8').split('\n')
|
||||
results.forEach((result) => {
|
||||
matchingRulesFound++
|
||||
const lineIndex = result.lineNumber - 1
|
||||
const offendingLine = fileLines[lineIndex]
|
||||
fileLines[lineIndex] = offendingLine.concat(` <!-- markdownlint-disable-line ${rule} -->`)
|
||||
})
|
||||
fs.writeFileSync(fileName, fileLines.join('\n'), 'utf8')
|
||||
},
|
||||
)
|
||||
|
||||
console.log(`${matchingRulesFound} violations ignored.`)
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import path from 'path'
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'vitest'
|
||||
|
||||
import { runRule } from '../../lib/init-test'
|
||||
import type { Rule } from '@/content-linter/types'
|
||||
import {
|
||||
liquidDataReferencesDefined,
|
||||
liquidDataTagFormat,
|
||||
@@ -31,7 +32,7 @@ describe(liquidDataReferencesDefined.names.join(' - '), () => {
|
||||
'{% data ui.nested.nested.not-there %}',
|
||||
'{% data some.random.path %}',
|
||||
]
|
||||
const result = await runRule(liquidDataReferencesDefined, {
|
||||
const result = await runRule(liquidDataReferencesDefined as Rule, {
|
||||
strings: { markdown: markdown.join('\n') },
|
||||
})
|
||||
const errors = result.markdown
|
||||
@@ -45,7 +46,7 @@ describe(liquidDataReferencesDefined.names.join(' - '), () => {
|
||||
'{% data variables.location.product_location %}',
|
||||
'{% data ui.header.notices.release_candidate %}',
|
||||
].join('\n')
|
||||
const result = await runRule(liquidDataReferencesDefined, { strings: { markdown } })
|
||||
const result = await runRule(liquidDataReferencesDefined as Rule, { strings: { markdown } })
|
||||
const errors = result.markdown
|
||||
expect(errors.length).toBe(0)
|
||||
})
|
||||
@@ -57,7 +58,7 @@ describe(liquidDataReferencesDefined.names.join(' - '), () => {
|
||||
'{% data %}',
|
||||
'{% indented_data_reference %}',
|
||||
]
|
||||
const result = await runRule(liquidDataTagFormat, {
|
||||
const result = await runRule(liquidDataTagFormat as Rule, {
|
||||
strings: { markdown: markdown.join('\n') },
|
||||
})
|
||||
const errors = result.markdown
|
||||
@@ -69,7 +70,7 @@ describe(liquidDataReferencesDefined.names.join(' - '), () => {
|
||||
'{% data ui.header.notices.release_candidate %}',
|
||||
'{% indented_data_reference ui.header.notices.release_candidate spaces=3 %}',
|
||||
]
|
||||
const result = await runRule(liquidDataTagFormat, {
|
||||
const result = await runRule(liquidDataTagFormat as Rule, {
|
||||
strings: { markdown: markdown.join('\n') },
|
||||
})
|
||||
const errors = result.markdown
|
||||
@@ -4,6 +4,7 @@ import { execSync } from 'child_process'
|
||||
|
||||
import { renderLiquid } from '@/content-render/liquid/index'
|
||||
import shortVersionsMiddleware from '@/versions/middleware/short-versions'
|
||||
import type { ExtendedRequest } from '@/types'
|
||||
|
||||
const { loadPages } = await import('@/frame/lib/page-data.js')
|
||||
const { allVersions } = await import('@/versions/lib/all-versions.js')
|
||||
@@ -32,20 +33,20 @@ for (const page of pages) {
|
||||
console.log(`---\nStart: Creating directories for: ${page.relativePath}`)
|
||||
const dirnames = page.relativePath.substring(0, page.relativePath.lastIndexOf('/'))
|
||||
|
||||
fs.mkdirSync(`${contentCopilotDir}/${dirnames}`, { recursive: true }, (err) => {
|
||||
if (err) throw err
|
||||
})
|
||||
fs.mkdirSync(`${contentCopilotDir}/${dirnames}`, { recursive: true })
|
||||
// Context needed to render the content liquid
|
||||
const req = { language: 'en' }
|
||||
const contextualize = (req) => {
|
||||
req.context.currentVersionObj = req.context.allVersions[req.context.currentVersion]
|
||||
const req = { language: 'en' } as ExtendedRequest
|
||||
const contextualize = (req: ExtendedRequest): void => {
|
||||
if (!req.context) return
|
||||
if (!req.context.currentVersion) return
|
||||
req.context.currentVersionObj = req.context.allVersions?.[req.context.currentVersion]
|
||||
shortVersionsMiddleware(req, null, () => {})
|
||||
}
|
||||
|
||||
req.context = {
|
||||
currentLanguage: 'en',
|
||||
currentVersion: 'free-pro-team@latest',
|
||||
page: {},
|
||||
page: {} as any, // Empty page object used only for context initialization
|
||||
allVersions,
|
||||
}
|
||||
contextualize(req)
|
||||
@@ -77,7 +78,8 @@ for (const page of pages) {
|
||||
'utf8',
|
||||
)
|
||||
console.log(`Done: written file\n---`)
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
// Standard catch-all for error handling in scripts
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,16 @@ Custom "Alerts", based on similar filter/styling in the monolith code.
|
||||
|
||||
import { visit } from 'unist-util-visit'
|
||||
import { h } from 'hastscript'
|
||||
// @ts-ignore - no types available for @primer/octicons
|
||||
import octicons from '@primer/octicons'
|
||||
import type { Element } from 'hast'
|
||||
|
||||
const alertTypes = {
|
||||
interface AlertType {
|
||||
icon: string
|
||||
color: string
|
||||
}
|
||||
|
||||
const alertTypes: Record<string, AlertType> = {
|
||||
NOTE: { icon: 'info', color: 'accent' },
|
||||
IMPORTANT: { icon: 'report', color: 'done' },
|
||||
WARNING: { icon: 'alert', color: 'attention' },
|
||||
@@ -17,14 +24,17 @@ const alertTypes = {
|
||||
// Must contain one of [!NOTE], [!IMPORTANT], ...
|
||||
const ALERT_REGEXP = new RegExp(`\\[!(${Object.keys(alertTypes).join('|')})\\]`, 'gi')
|
||||
|
||||
const matcher = (node) =>
|
||||
// Using any due to conflicting unist/hast type definitions between dependencies
|
||||
const matcher = (node: any): boolean =>
|
||||
node.type === 'element' &&
|
||||
node.tagName === 'blockquote' &&
|
||||
ALERT_REGEXP.test(JSON.stringify(node.children))
|
||||
|
||||
export default function alerts({ alertTitles = {} }) {
|
||||
return (tree) => {
|
||||
visit(tree, matcher, (node) => {
|
||||
export default function alerts({ alertTitles = {} }: { alertTitles?: Record<string, string> }) {
|
||||
// Using any due to conflicting unist/hast type definitions between dependencies
|
||||
return (tree: any) => {
|
||||
// Using any due to conflicting unist/hast type definitions between dependencies
|
||||
visit(tree, matcher, (node: any) => {
|
||||
const key = getAlertKey(node)
|
||||
if (!(key in alertTypes)) {
|
||||
console.warn(
|
||||
@@ -48,13 +58,14 @@ export default function alerts({ alertTitles = {} }) {
|
||||
}
|
||||
}
|
||||
|
||||
function getAlertKey(node) {
|
||||
function getAlertKey(node: Element): string {
|
||||
const body = JSON.stringify(node.children)
|
||||
const matches = body.match(ALERT_REGEXP)
|
||||
return matches[0].slice(2, -1)
|
||||
return matches![0].slice(2, -1)
|
||||
}
|
||||
|
||||
function removeAlertSyntax(node) {
|
||||
// Using any to handle both array and object node types recursively
|
||||
function removeAlertSyntax(node: any): any {
|
||||
if (Array.isArray(node)) {
|
||||
return node.map(removeAlertSyntax)
|
||||
}
|
||||
@@ -67,7 +78,7 @@ function removeAlertSyntax(node) {
|
||||
return node
|
||||
}
|
||||
|
||||
function getOcticonSVG(name) {
|
||||
function getOcticonSVG(name: string): Element {
|
||||
return h(
|
||||
'svg',
|
||||
{
|
||||
@@ -78,6 +89,6 @@ function getOcticonSVG(name) {
|
||||
className: 'octicon mr-2',
|
||||
ariaHidden: true,
|
||||
},
|
||||
h('path', { d: octicons[name].heights[16].path.match(/d="(.*)"/)[1] }),
|
||||
h('path', { d: octicons[name].heights[16].path.match(/d="(.*)"/)![1] }),
|
||||
)
|
||||
}
|
||||
@@ -32,7 +32,7 @@ const section = {
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
} as const
|
||||
|
||||
export default {
|
||||
type: 'object',
|
||||
@@ -69,7 +69,10 @@ export default {
|
||||
'errata',
|
||||
'closing_down',
|
||||
'retired',
|
||||
].reduce((prev, curr) => ({ ...prev, [curr]: section }), {}),
|
||||
].reduce(
|
||||
(prev: Record<string, typeof section>, curr: string) => ({ ...prev, [curr]: section }),
|
||||
{},
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
} as const
|
||||
@@ -28,13 +28,20 @@ page.permalinks is an array of objects that looks like this:
|
||||
]
|
||||
*/
|
||||
class Permalink {
|
||||
constructor(languageCode, pageVersion, relativePath, title) {
|
||||
languageCode: string
|
||||
pageVersion: string
|
||||
relativePath: string
|
||||
title: string
|
||||
hrefWithoutLanguage: string
|
||||
href: string
|
||||
|
||||
constructor(languageCode: string, pageVersion: string, relativePath: string, title: string) {
|
||||
this.languageCode = languageCode
|
||||
this.pageVersion = pageVersion
|
||||
this.relativePath = relativePath
|
||||
this.title = title
|
||||
|
||||
const permalinkSuffix = this.constructor.relativePathToSuffix(relativePath)
|
||||
const permalinkSuffix = Permalink.relativePathToSuffix(relativePath)
|
||||
|
||||
this.hrefWithoutLanguage = removeFPTFromPath(
|
||||
path.posix.join('/', pageVersion, permalinkSuffix),
|
||||
@@ -46,18 +53,23 @@ class Permalink {
|
||||
return this
|
||||
}
|
||||
|
||||
static derive(languageCode, relativePath, title, applicableVersions) {
|
||||
static derive(
|
||||
languageCode: string,
|
||||
relativePath: string,
|
||||
title: string,
|
||||
applicableVersions: string[],
|
||||
): Permalink[] {
|
||||
assert(relativePath, 'relativePath is required')
|
||||
assert(languageCode, 'languageCode is required')
|
||||
|
||||
const permalinks = applicableVersions.map((pageVersion) => {
|
||||
const permalinks = applicableVersions.map((pageVersion: string) => {
|
||||
return new Permalink(languageCode, pageVersion, relativePath, title)
|
||||
})
|
||||
|
||||
return permalinks
|
||||
}
|
||||
|
||||
static relativePathToSuffix(relativePath) {
|
||||
static relativePathToSuffix(relativePath: string): string {
|
||||
if (relativePath === 'index.md') return '/'
|
||||
// When you turn `foo/bar.md`, which is a file path, into a URL pathname,
|
||||
// you just need to chop off the `.md` suffix.
|
||||
@@ -2,7 +2,12 @@ import matter from '@gr2m/gray-matter'
|
||||
|
||||
import { validateJson } from '@/tests/lib/validate-json-schema'
|
||||
|
||||
function readFrontmatter(markdown, opts = {}) {
|
||||
interface ReadFrontmatterOptions {
|
||||
schema?: Record<string, any> // Schema can have arbitrary properties for validation
|
||||
filepath?: string | null
|
||||
}
|
||||
|
||||
function readFrontmatter(markdown: string, opts: ReadFrontmatterOptions = {}) {
|
||||
const schema = opts.schema || { type: 'object', properties: {} }
|
||||
const filepath = opts.filepath || null
|
||||
|
||||
@@ -10,7 +15,7 @@ function readFrontmatter(markdown, opts = {}) {
|
||||
|
||||
try {
|
||||
;({ content, data } = matter(markdown))
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
const defaultReason = 'invalid frontmatter entry'
|
||||
|
||||
const reason = e.reason
|
||||
@@ -21,7 +26,7 @@ function readFrontmatter(markdown, opts = {}) {
|
||||
: e.reason
|
||||
: defaultReason
|
||||
|
||||
const error = {
|
||||
const error: any = {
|
||||
reason,
|
||||
message: 'YML parsing error!',
|
||||
}
|
||||
@@ -33,7 +38,7 @@ function readFrontmatter(markdown, opts = {}) {
|
||||
return { errors }
|
||||
}
|
||||
|
||||
const validate = validateJson(schema, data)
|
||||
const validate: any = validateJson(schema, data)
|
||||
|
||||
// Combine the AJV-supplied `instancePath` and `params` into a more user-friendly frontmatter path.
|
||||
// For example, given:
|
||||
@@ -44,7 +49,7 @@ function readFrontmatter(markdown, opts = {}) {
|
||||
//
|
||||
// The purpose is to help users understand that the error is on the `fpt` key within the `versions` object.
|
||||
// Note if the error is on a top-level FM property like `title`, the `instancePath` will be empty.
|
||||
const cleanPropertyPath = (params, instancePath) => {
|
||||
const cleanPropertyPath = (params: Record<string, any>, instancePath: string) => {
|
||||
const mainProps = Object.values(params)[0]
|
||||
if (!instancePath) return mainProps
|
||||
|
||||
@@ -55,8 +60,8 @@ function readFrontmatter(markdown, opts = {}) {
|
||||
const errors = []
|
||||
|
||||
if (!validate.isValid && filepath) {
|
||||
const formattedErrors = validate.errors.map((error) => {
|
||||
const userFriendly = {}
|
||||
const formattedErrors = validate.errors.map((error: any) => {
|
||||
const userFriendly: any = {}
|
||||
userFriendly.property = cleanPropertyPath(error.params, error.instancePath)
|
||||
userFriendly.message = error.message
|
||||
userFriendly.reason = error.keyword
|
||||
@@ -1,3 +1,12 @@
|
||||
import type { Response } from 'express'
|
||||
|
||||
interface CacheControlOptions {
|
||||
key?: string
|
||||
public_?: boolean
|
||||
immutable?: boolean
|
||||
maxAgeZero?: boolean
|
||||
}
|
||||
|
||||
// Return a function you can pass a Response object to and it will
|
||||
// set the `Cache-Control` header.
|
||||
//
|
||||
@@ -11,9 +20,14 @@
|
||||
// Max age is in seconds
|
||||
// Max age should not be greater than 31536000 https://www.ietf.org/rfc/rfc2616.txt
|
||||
function cacheControlFactory(
|
||||
maxAge = 60 * 60,
|
||||
{ key = 'cache-control', public_ = true, immutable = false, maxAgeZero = false } = {},
|
||||
) {
|
||||
maxAge: number = 60 * 60,
|
||||
{
|
||||
key = 'cache-control',
|
||||
public_ = true,
|
||||
immutable = false,
|
||||
maxAgeZero = false,
|
||||
}: CacheControlOptions = {},
|
||||
): (res: Response) => void {
|
||||
const directives = [
|
||||
maxAge && public_ && 'public',
|
||||
maxAge && `max-age=${maxAge}`,
|
||||
@@ -26,7 +40,7 @@ function cacheControlFactory(
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(', ')
|
||||
return (res) => {
|
||||
return (res: Response) => {
|
||||
if (process.env.NODE_ENV !== 'production' && res.hasHeader('set-cookie') && maxAge) {
|
||||
console.warn(
|
||||
"You can't set a >0 cache-control header AND set-cookie or else the CDN will never respect the cache-control.",
|
||||
@@ -50,7 +64,7 @@ const searchBrowserCacheControl = cacheControlFactory(60 * 60)
|
||||
const searchCdnCacheControl = cacheControlFactory(60 * 60 * 24, {
|
||||
key: 'surrogate-control',
|
||||
})
|
||||
export function searchCacheControl(res) {
|
||||
export function searchCacheControl(res: Response): void {
|
||||
searchBrowserCacheControl(res)
|
||||
searchCdnCacheControl(res)
|
||||
}
|
||||
@@ -65,7 +79,7 @@ const defaultCDNCacheControl = cacheControlFactory(60 * 60 * 24, {
|
||||
const defaultBrowserCacheControl = cacheControlFactory(60)
|
||||
// A general default configuration that is useful to almost all responses
|
||||
// that can be cached.
|
||||
export function defaultCacheControl(res) {
|
||||
export function defaultCacheControl(res: Response): void {
|
||||
defaultCDNCacheControl(res)
|
||||
defaultBrowserCacheControl(res)
|
||||
}
|
||||
@@ -74,7 +88,7 @@ export function defaultCacheControl(res) {
|
||||
// x-user-language is a custom request header derived from req.cookie:user_language
|
||||
// accept-language is truncated to one of our available languages
|
||||
// https://bit.ly/3u5UeRN
|
||||
export function languageCacheControl(res) {
|
||||
export function languageCacheControl(res: Response): void {
|
||||
defaultCacheControl(res)
|
||||
res.set('vary', 'accept-language, x-user-language')
|
||||
}
|
||||
@@ -24,7 +24,7 @@ describe('Permalink class', () => {
|
||||
const homepagePermalink = permalinks.find(
|
||||
(permalink) => permalink.pageVersion === nonEnterpriseDefaultVersion,
|
||||
)
|
||||
expect(homepagePermalink.href).toBe('/en')
|
||||
expect(homepagePermalink?.href).toBe('/en')
|
||||
})
|
||||
|
||||
test('derives info for non-enterprise versioned homepage', () => {
|
||||
@@ -6,6 +6,7 @@ import EnterpriseServerReleases from '@/versions/lib/enterprise-server-releases'
|
||||
import { loadSiteTree } from '@/frame/lib/page-data'
|
||||
import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version'
|
||||
import { formatAjvErrors } from '@/tests/helpers/schemas'
|
||||
import type { SiteTree, Tree } from '@/types'
|
||||
|
||||
const latestEnterpriseRelease = EnterpriseServerReleases.latest
|
||||
|
||||
@@ -14,9 +15,9 @@ const siteTreeValidate = getJsonValidator(schema.childPage)
|
||||
describe('siteTree', () => {
|
||||
vi.setConfig({ testTimeout: 3 * 60 * 1000 })
|
||||
|
||||
let siteTree
|
||||
let siteTree: SiteTree
|
||||
beforeAll(async () => {
|
||||
siteTree = await loadSiteTree()
|
||||
siteTree = (await loadSiteTree()) as SiteTree
|
||||
})
|
||||
|
||||
test('has language codes as top-level keys', () => {
|
||||
@@ -39,12 +40,12 @@ describe('siteTree', () => {
|
||||
// TODO: use new findPageInSiteTree helper when it's available
|
||||
const pageWithDynamicTitle = ghesSiteTree.childPages
|
||||
.find((child) => child.href === `/en/${ghesLatest}/admin`)
|
||||
.childPages.find(
|
||||
?.childPages.find(
|
||||
(child) => child.href === `/en/${ghesLatest}/admin/installing-your-enterprise-server`,
|
||||
)
|
||||
|
||||
// Confirm the raw title contains Liquid
|
||||
expect(pageWithDynamicTitle.page.title).toEqual(
|
||||
expect(pageWithDynamicTitle?.page.title).toEqual(
|
||||
'Installing {% data variables.product.prodname_enterprise %}',
|
||||
)
|
||||
})
|
||||
@@ -58,18 +59,22 @@ describe('siteTree', () => {
|
||||
})
|
||||
})
|
||||
|
||||
function validate(currentPage) {
|
||||
;(currentPage.childPages || []).forEach((childPage) => {
|
||||
function validate(currentPage: Tree): void {
|
||||
const childPages: Tree[] = currentPage.childPages || []
|
||||
childPages.forEach((childPage) => {
|
||||
// Store page reference before validation to avoid type narrowing
|
||||
const pageRef: Tree = childPage
|
||||
const isValid = siteTreeValidate(childPage)
|
||||
let errors
|
||||
let errors: string | undefined
|
||||
|
||||
if (!isValid) {
|
||||
errors = `file ${childPage.page.fullPath}: ${formatAjvErrors(siteTreeValidate.errors)}`
|
||||
const fullPath = pageRef.page.fullPath
|
||||
errors = `file ${fullPath}: ${formatAjvErrors(siteTreeValidate.errors || [])}`
|
||||
}
|
||||
|
||||
expect(isValid, errors).toBe(true)
|
||||
|
||||
// Run recursively until we run out of child pages
|
||||
validate(childPage)
|
||||
validate(pageRef)
|
||||
})
|
||||
}
|
||||
@@ -58,7 +58,7 @@ export function updateContentFiles() {
|
||||
// To preserve newlines when stringifying,
|
||||
// you can set the lineWidth option to -1
|
||||
// This prevents updates to the file that aren't actual changes.
|
||||
fs.writeFileSync(file, frontmatter.stringify(content, data, { lineWidth: -1 } as any))
|
||||
fs.writeFileSync(file, frontmatter.stringify(content!, data, { lineWidth: -1 } as any))
|
||||
continue
|
||||
}
|
||||
if (featureAppliesToAllVersions) {
|
||||
@@ -71,7 +71,7 @@ export function updateContentFiles() {
|
||||
// To preserve newlines when stringifying,
|
||||
// you can set the lineWidth option to -1
|
||||
// This prevents updates to the file that aren't actual changes.
|
||||
fs.writeFileSync(file, frontmatter.stringify(content, data, { lineWidth: -1 } as any))
|
||||
fs.writeFileSync(file, frontmatter.stringify(content!, data, { lineWidth: -1 } as any))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function updateContentFiles() {
|
||||
// Remove the ghes property from versions Fm and return
|
||||
delete data.versions.ghes
|
||||
console.log('Removing GHES version from: ', file)
|
||||
fs.writeFileSync(file, frontmatter.stringify(content, data, { lineWidth: -1 } as any))
|
||||
fs.writeFileSync(file, frontmatter.stringify(content!, data, { lineWidth: -1 } as any))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import { GRAPHQL_DATA_DIR } from '../lib/index'
|
||||
|
||||
const allVersionValues = Object.values(allVersions)
|
||||
const graphqlVersions = allVersionValues.map((v) => v.openApiVersionName)
|
||||
const graphqlTypes = readJsonFile('./src/graphql/lib/types.json').map((t) => t.kind)
|
||||
const graphqlTypes = (readJsonFile('./src/graphql/lib/types.json') as Array<{ kind: string }>).map(
|
||||
(t) => t.kind,
|
||||
)
|
||||
|
||||
const previewsValidate = getJsonValidator(previewsValidator)
|
||||
const upcomingChangesValidate = getJsonValidator(upcomingChangesValidator)
|
||||
@@ -20,9 +22,11 @@ describe('graphql json files', () => {
|
||||
// The typeObj is repeated thousands of times in each .json file
|
||||
// so use a cache of which we've already validated to speed this
|
||||
// test up significantly.
|
||||
const typeObjsTested = new Set()
|
||||
const typeObjsTested = new Set<string>()
|
||||
graphqlVersions.forEach((version) => {
|
||||
const schemaJsonPerVersion = readJsonFile(`${GRAPHQL_DATA_DIR}/${version}/schema.json`)
|
||||
const schemaJsonPerVersion = readJsonFile(
|
||||
`${GRAPHQL_DATA_DIR}/${version}/schema.json`,
|
||||
) as Record<string, Array<{ kind: string; name: string }>>
|
||||
// all graphql types are arrays except for queries
|
||||
graphqlTypes.forEach((type) => {
|
||||
test(`${version} schemas object validation for ${type}`, () => {
|
||||
@@ -31,12 +35,15 @@ describe('graphql json files', () => {
|
||||
if (typeObjsTested.has(key)) return
|
||||
typeObjsTested.add(key)
|
||||
|
||||
const { isValid, errors } = validateJson(schemaValidator[type], typeObj)
|
||||
const { isValid, errors } = validateJson(
|
||||
schemaValidator[type as keyof typeof schemaValidator],
|
||||
typeObj,
|
||||
)
|
||||
|
||||
let formattedErrors = errors
|
||||
let formattedErrors: any = errors // Can be either raw errors array or formatted string
|
||||
if (!isValid) {
|
||||
formattedErrors = `kind: ${typeObj.kind}, name: ${typeObj.name}: ${formatAjvErrors(
|
||||
errors,
|
||||
errors || [],
|
||||
)}`
|
||||
}
|
||||
|
||||
@@ -48,13 +55,13 @@ describe('graphql json files', () => {
|
||||
|
||||
test('previews object validation', () => {
|
||||
graphqlVersions.forEach((version) => {
|
||||
const previews = readJsonFile(`${GRAPHQL_DATA_DIR}/${version}/previews.json`)
|
||||
const previews = readJsonFile(`${GRAPHQL_DATA_DIR}/${version}/previews.json`) as Array<any> // GraphQL preview schema structure is dynamic
|
||||
previews.forEach((preview) => {
|
||||
const isValid = previewsValidate(preview)
|
||||
let errors
|
||||
let errors: string | undefined
|
||||
|
||||
if (!isValid) {
|
||||
errors = formatAjvErrors(previewsValidate.errors)
|
||||
errors = formatAjvErrors(previewsValidate.errors || [])
|
||||
}
|
||||
|
||||
expect(isValid, errors).toBe(true)
|
||||
@@ -64,15 +71,17 @@ describe('graphql json files', () => {
|
||||
|
||||
test('upcoming changes object validation', () => {
|
||||
graphqlVersions.forEach((version) => {
|
||||
const upcomingChanges = readJsonFile(`${GRAPHQL_DATA_DIR}/${version}/upcoming-changes.json`)
|
||||
const upcomingChanges = readJsonFile(
|
||||
`${GRAPHQL_DATA_DIR}/${version}/upcoming-changes.json`,
|
||||
) as Record<string, Array<any>> // GraphQL change object structure is dynamic
|
||||
for (const changes of Object.values(upcomingChanges)) {
|
||||
// each object value is an array of changes
|
||||
changes.forEach((changeObj) => {
|
||||
const isValid = upcomingChangesValidate(changeObj)
|
||||
let errors
|
||||
let errors: string | undefined
|
||||
|
||||
if (!isValid) {
|
||||
errors = formatAjvErrors(upcomingChangesValidate.errors)
|
||||
errors = formatAjvErrors(upcomingChangesValidate.errors || [])
|
||||
}
|
||||
|
||||
expect(isValid, errors).toBe(true)
|
||||
@@ -66,7 +66,7 @@ async function main(): Promise<void> {
|
||||
for (const file of files) {
|
||||
const contents = await fs.promises.readFile(file)
|
||||
const contentPath = path.relative(ROOTDIR, file)
|
||||
const { data } = readFrontmatter(contents)
|
||||
const { data } = readFrontmatter(contents.toString())
|
||||
const versionString = JSON.stringify(data?.versions || {}).replaceAll('"', "'")
|
||||
const pathToQuery = getPathToQuery(file)
|
||||
// Pass null to get all versions (the default if no version is provided)
|
||||
|
||||
@@ -5,17 +5,21 @@ import path from 'path'
|
||||
import { allVersions } from '@/versions/lib/all-versions'
|
||||
|
||||
const OPEN_API_RELEASES_DIR = '../github/app/api/description/config/releases'
|
||||
const configData = JSON.parse(await readFile('src/rest/lib/config.json', 'utf8'))
|
||||
const configData: { versionMapping: Record<string, string> } = JSON.parse(
|
||||
await readFile('src/rest/lib/config.json', 'utf8'),
|
||||
)
|
||||
// Gets the full list of unpublished + active, deprecated + active,
|
||||
// or active schemas from the github/github repo
|
||||
// `openApiReleaseDir` is the path to the `app/api/description/config/releases`
|
||||
// directory in `github/github`
|
||||
// You can also specify getting specific versions of schemas.
|
||||
export async function getSchemas(directory = OPEN_API_RELEASES_DIR) {
|
||||
export async function getSchemas(
|
||||
directory: string = OPEN_API_RELEASES_DIR,
|
||||
): Promise<{ currentReleases: string[]; unpublished: string[]; deprecated: string[] }> {
|
||||
const openAPIConfigs = await readdir(directory)
|
||||
const unpublished = []
|
||||
const deprecated = []
|
||||
const currentReleases = []
|
||||
const unpublished: string[] = []
|
||||
const deprecated: string[] = []
|
||||
const currentReleases: string[] = []
|
||||
|
||||
// The file content in the `github/github` repo is YAML before it is
|
||||
// bundled into JSON.
|
||||
@@ -23,7 +27,7 @@ export async function getSchemas(directory = OPEN_API_RELEASES_DIR) {
|
||||
const fileBaseName = path.basename(file, '.yaml')
|
||||
const newFileName = `${fileBaseName}.deref.json`
|
||||
const content = await readFile(path.join(directory, file), 'utf8')
|
||||
const yamlContent = yaml.load(content)
|
||||
const yamlContent = yaml.load(content) as { published?: boolean; deprecated?: boolean }
|
||||
|
||||
const releaseMatch = Object.keys(configData.versionMapping).find((name) =>
|
||||
fileBaseName.startsWith(name),
|
||||
@@ -62,7 +66,7 @@ export async function getSchemas(directory = OPEN_API_RELEASES_DIR) {
|
||||
return { currentReleases, unpublished, deprecated }
|
||||
}
|
||||
|
||||
export async function validateVersionsOptions(versions) {
|
||||
export async function validateVersionsOptions(versions: string[]): Promise<void> {
|
||||
const schemas = await getSchemas()
|
||||
// Validate individual versions provided
|
||||
versions.forEach((version) => {
|
||||
@@ -4,14 +4,17 @@ import { allVersions } from '@/versions/lib/all-versions'
|
||||
import { liquid } from '@/content-render/index'
|
||||
import { supported } from '@/versions/lib/enterprise-server-releases'
|
||||
import shortVersionsMiddleware from '@/versions/middleware/short-versions'
|
||||
import type { ExtendedRequest } from '@/types'
|
||||
|
||||
const contextualize = (req) => {
|
||||
req.context.currentVersionObj = req.context.allVersions[req.context.currentVersion]
|
||||
const contextualize = (req: ExtendedRequest): void => {
|
||||
if (!req.context) throw new Error('No context on request')
|
||||
if (!req.context.currentVersion) throw new Error('No currentVersion in context')
|
||||
req.context.currentVersionObj = req.context.allVersions?.[req.context.currentVersion]
|
||||
shortVersionsMiddleware(req, null, () => {})
|
||||
}
|
||||
|
||||
describe('ifversion conditionals', () => {
|
||||
const req = {}
|
||||
const req: ExtendedRequest = {} as ExtendedRequest
|
||||
beforeAll(async () => {
|
||||
req.context = {
|
||||
allVersions,
|
||||
Reference in New Issue
Block a user