From 0dcc58d93df44d1e29e476d05f92342eade6118c Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 9 Jul 2025 10:08:26 -0700 Subject: [PATCH 1/7] Convert src/graphql/lib/validator.js to TypeScript (#56407) --- .../lib/{validator.js => validator.ts} | 62 ++++++++++++++----- src/graphql/tests/validate-schema.js | 2 +- 2 files changed, 46 insertions(+), 18 deletions(-) rename src/graphql/lib/{validator.js => validator.ts} (74%) diff --git a/src/graphql/lib/validator.js b/src/graphql/lib/validator.ts similarity index 74% rename from src/graphql/lib/validator.js rename to src/graphql/lib/validator.ts index af1ca18b7481..7857842d41c7 100644 --- a/src/graphql/lib/validator.js +++ b/src/graphql/lib/validator.ts @@ -1,8 +1,23 @@ // the tests in tests/graphql.js use this schema to ensure the integrity // of the data in src/graphql/data/*.json +// JSON Schema type definitions for AJV validation +interface JSONSchema { + type?: string + required?: string[] + properties?: Record + items?: JSONSchema + pattern?: string +} + +interface ValidatorSchema extends JSONSchema { + type: 'object' + required: string[] + properties: Record +} + // PREVIEWS -export const previewsValidator = { +export const previewsValidator: ValidatorSchema = { type: 'object', required: [ 'title', @@ -39,7 +54,7 @@ export const previewsValidator = { } // UPCOMING CHANGES -export const upcomingChangesValidator = { +export const upcomingChangesValidator: ValidatorSchema = { type: 'object', required: ['location', 'description', 'reason', 'date', 'criticality', 'owner'], properties: { @@ -69,7 +84,7 @@ export const upcomingChangesValidator = { // SCHEMAS // many GraphQL schema members have these core properties -const coreProps = { +const coreProps: JSONSchema = { properties: { name: { type: 'string', @@ -102,7 +117,7 @@ const coreProps = { // some GraphQL schema members have the core properties plus an 'args' object const corePropsPlusArgs = dup(coreProps) -corePropsPlusArgs.properties.args = { +corePropsPlusArgs.properties!.args = { type: 'array', items: { type: 'object', @@ -111,24 +126,24 @@ corePropsPlusArgs.properties.args = { } // the args object can have defaultValue prop -corePropsPlusArgs.properties.args.items.properties.defaultValue = { +corePropsPlusArgs.properties!.args.items!.properties!.defaultValue = { type: 'boolean', } const corePropsNoType = dup(coreProps) -delete corePropsNoType.properties.type +delete corePropsNoType.properties!.type const corePropsNoDescription = dup(coreProps) -delete corePropsNoDescription.properties.description +delete corePropsNoDescription.properties!.description // QUERIES -const queries = dup(corePropsPlusArgs) +const queries = dup(corePropsPlusArgs) as ValidatorSchema queries.type = 'object' queries.required = ['name', 'type', 'kind', 'id', 'href', 'description'] // MUTATIONS -const mutations = dup(corePropsNoType) +const mutations = dup(corePropsNoType) as ValidatorSchema mutations.type = 'object' mutations.required = ['name', 'kind', 'id', 'href', 'description', 'inputFields', 'returnFields'] @@ -150,7 +165,7 @@ mutations.properties.returnFields = { } // OBJECTS -const objects = dup(corePropsNoType) +const objects = dup(corePropsNoType) as ValidatorSchema objects.type = 'object' objects.required = ['name', 'kind', 'id', 'href', 'description', 'fields'] @@ -183,7 +198,7 @@ objects.properties.implements = { } // INTERFACES -const interfaces = dup(corePropsNoType) +const interfaces = dup(corePropsNoType) as ValidatorSchema interfaces.type = 'object' interfaces.required = ['name', 'kind', 'id', 'href', 'description', 'fields'] @@ -197,7 +212,7 @@ interfaces.properties.fields = { } // ENUMS -const enums = dup(corePropsNoType) +const enums = dup(corePropsNoType) as ValidatorSchema enums.type = 'object' enums.required = ['name', 'kind', 'id', 'href', 'description', 'values'] @@ -219,7 +234,7 @@ enums.properties.values = { } // UNIONS -const unions = dup(corePropsNoType) +const unions = dup(corePropsNoType) as ValidatorSchema unions.type = 'object' unions.required = ['name', 'kind', 'id', 'href', 'description', 'possibleTypes'] @@ -244,7 +259,7 @@ unions.properties.possibleTypes = { } // INPUT OBJECTS -const inputObjects = dup(corePropsNoType) +const inputObjects = dup(corePropsNoType) as ValidatorSchema inputObjects.type = 'object' inputObjects.required = ['name', 'kind', 'id', 'href', 'description', 'inputFields'] @@ -258,16 +273,29 @@ inputObjects.properties.inputFields = { } // SCALARS -const scalars = dup(corePropsNoType) +const scalars = dup(corePropsNoType) as ValidatorSchema scalars.type = 'object' scalars.required = ['name', 'id', 'href', 'description'] -function dup(obj) { +// Deep clone utility function with proper typing +function dup(obj: T): T { return JSON.parse(JSON.stringify(obj)) } -export const schemaValidator = { +// Schema validator collection with proper typing +interface SchemaValidators { + queries: ValidatorSchema + mutations: ValidatorSchema + objects: ValidatorSchema + interfaces: ValidatorSchema + enums: ValidatorSchema + unions: ValidatorSchema + inputObjects: ValidatorSchema + scalars: ValidatorSchema +} + +export const schemaValidator: SchemaValidators = { queries, mutations, objects, diff --git a/src/graphql/tests/validate-schema.js b/src/graphql/tests/validate-schema.js index fcdb85cdba55..726d23c77788 100644 --- a/src/graphql/tests/validate-schema.js +++ b/src/graphql/tests/validate-schema.js @@ -2,7 +2,7 @@ import { describe, expect, test, vi } from 'vitest' import { getJsonValidator, validateJson } from '#src/tests/lib/validate-json-schema.js' import readJsonFile from '#src/frame/lib/read-json-file.js' -import { schemaValidator, previewsValidator, upcomingChangesValidator } from '../lib/validator.js' +import { schemaValidator, previewsValidator, upcomingChangesValidator } from '../lib/validator.ts' import { formatAjvErrors } from '#src/tests/helpers/schemas.js' import { allVersions } from '#src/versions/lib/all-versions.js' import { GRAPHQL_DATA_DIR } from '../lib/index.js' From 0db9781280f8c107605cb4dd06cd90a56fec74a5 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 9 Jul 2025 10:47:16 -0700 Subject: [PATCH 2/7] Add table column integrity checker content linter rule (GHD047) (#56099) --- .../contributing/content-linter-rules.md | 1 + src/content-linter/lib/linting-rules/index.js | 2 + .../linting-rules/table-column-integrity.js | 134 ++++++++++++++++++ src/content-linter/style/github-docs.js | 6 + .../unit/table-column-integrity-simple.js | 63 ++++++++ 5 files changed, 206 insertions(+) create mode 100644 src/content-linter/lib/linting-rules/table-column-integrity.js create mode 100644 src/content-linter/tests/unit/table-column-integrity-simple.js diff --git a/data/reusables/contributing/content-linter-rules.md b/data/reusables/contributing/content-linter-rules.md index ea01a4548154..09f1b78266db 100644 --- a/data/reusables/contributing/content-linter-rules.md +++ b/data/reusables/contributing/content-linter-rules.md @@ -64,6 +64,7 @@ | GHD038 | expired-content | Expired content must be remediated. | warning | expired | | GHD039 | expiring-soon | Content that expires soon should be proactively addressed. | warning | expired | | [GHD040](https://github.com/github/docs/blob/main/src/content-linter/README.md) | table-liquid-versioning | Tables must use the correct liquid versioning format | error | tables | +| GHD047 | table-column-integrity | Tables must have consistent column counts across all rows | warning | tables, accessibility, formatting | | GHD041 | third-party-action-pinning | Code examples that use third-party actions must always pin to a full length commit SHA | error | feature, actions | | GHD042 | liquid-tag-whitespace | Liquid tags should start and end with one whitespace. Liquid tag arguments should be separated by only one whitespace. | error | liquid, format | | GHD043 | link-quotation | Internal link titles must not be surrounded by quotations | error | links, url | diff --git a/src/content-linter/lib/linting-rules/index.js b/src/content-linter/lib/linting-rules/index.js index f82f5b8ddc15..b3b7de2f0e49 100644 --- a/src/content-linter/lib/linting-rules/index.js +++ b/src/content-linter/lib/linting-rules/index.js @@ -31,6 +31,7 @@ import { raiReusableUsage } from './rai-reusable-usage.js' import { imageNoGif } from './image-no-gif.js' import { expiredContent, expiringSoon } from './expired-content.js' import { tableLiquidVersioning } from './table-liquid-versioning.js' +import { tableColumnIntegrity } from './table-column-integrity.js' import { thirdPartyActionPinning } from './third-party-action-pinning.js' import { liquidTagWhitespace } from './liquid-tag-whitespace.js' import { linkQuotation } from './link-quotation.js' @@ -85,6 +86,7 @@ export const gitHubDocsMarkdownlint = { expiredContent, expiringSoon, tableLiquidVersioning, + tableColumnIntegrity, thirdPartyActionPinning, liquidTagWhitespace, linkQuotation, diff --git a/src/content-linter/lib/linting-rules/table-column-integrity.js b/src/content-linter/lib/linting-rules/table-column-integrity.js new file mode 100644 index 000000000000..ca437b8557a3 --- /dev/null +++ b/src/content-linter/lib/linting-rules/table-column-integrity.js @@ -0,0 +1,134 @@ +import { addError } from 'markdownlint-rule-helpers' +import { getRange } from '../helpers/utils.js' +import frontmatter from '#src/frame/lib/read-frontmatter.js' + +// Regex to detect table rows (must start with |, contain at least one more |, and end with optional whitespace) +const TABLE_ROW_REGEX = /^\s*\|.*\|\s*$/ +// Regex to detect table separator rows (contains only |, :, -, and whitespace) +const TABLE_SEPARATOR_REGEX = /^\s*\|[\s\-:|\s]*\|\s*$/ +// Regex to detect Liquid-only cells (whitespace, liquid tag, whitespace) +const LIQUID_ONLY_CELL_REGEX = /^\s*{%\s*(ifversion|else|endif|elsif).*%}\s*$/ + +/** + * Counts the number of columns in a table row by splitting on | and handling edge cases + */ +function countColumns(row) { + // Remove leading and trailing whitespace + const trimmed = row.trim() + + // Handle empty rows + if (!trimmed || !trimmed.includes('|')) { + return 0 + } + + // Split by | and filter out empty cells at start/end (from leading/trailing |) + const cells = trimmed.split('|') + + // Remove first and last elements if they're empty (from leading/trailing |) + if (cells.length > 0 && cells[0].trim() === '') { + cells.shift() + } + if (cells.length > 0 && cells[cells.length - 1].trim() === '') { + cells.pop() + } + + return cells.length +} + +/** + * Checks if a table row contains only Liquid conditionals + */ +function isLiquidOnlyRow(row) { + const trimmed = row.trim() + if (!trimmed.includes('|')) return false + + const cells = trimmed.split('|') + // Remove empty cells from leading/trailing | + const filteredCells = cells.filter((cell, index) => { + if (index === 0 && cell.trim() === '') return false + if (index === cells.length - 1 && cell.trim() === '') return false + return true + }) + + // Check if all cells contain only Liquid tags + return ( + filteredCells.length > 0 && filteredCells.every((cell) => LIQUID_ONLY_CELL_REGEX.test(cell)) + ) +} + +export const tableColumnIntegrity = { + names: ['GHD047', 'table-column-integrity'], + description: 'Tables must have consistent column counts across all rows', + tags: ['tables', 'accessibility', 'formatting'], + severity: 'error', + function: (params, onError) => { + // Skip autogenerated files + const frontmatterString = params.frontMatterLines.join('\n') + const fm = frontmatter(frontmatterString).data + if (fm && fm.autogenerated) return + + const lines = params.lines + let inTable = false + let expectedColumnCount = null + let tableStartLine = null + let headerRow = null + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + const isTableRow = TABLE_ROW_REGEX.test(line) + const isSeparatorRow = TABLE_SEPARATOR_REGEX.test(line) + + // Check if we're starting a new table + if (!inTable && isTableRow) { + // Look ahead to see if next line is a separator (confirming this is a table) + const nextLine = lines[i + 1] + if (nextLine && TABLE_SEPARATOR_REGEX.test(nextLine)) { + inTable = true + tableStartLine = i + 1 + headerRow = line + expectedColumnCount = countColumns(line) + continue + } + } + + // Check if we're ending a table + if (inTable && !isTableRow) { + inTable = false + expectedColumnCount = null + tableStartLine = null + headerRow = null + continue + } + + // If we're in a table, validate column count + if (inTable && isTableRow && !isSeparatorRow) { + // Skip Liquid-only rows as they're allowed to have different column counts + if (isLiquidOnlyRow(line)) { + continue + } + + const actualColumnCount = countColumns(line) + + if (actualColumnCount !== expectedColumnCount) { + const range = getRange(line, line.trim()) + let errorMessage + + if (actualColumnCount > expectedColumnCount) { + errorMessage = `Table row has ${actualColumnCount} columns but header has ${expectedColumnCount}. Add ${actualColumnCount - expectedColumnCount} more column(s) to the header row to match this row.` + } else { + errorMessage = `Table row has ${actualColumnCount} columns but header has ${expectedColumnCount}. Add ${expectedColumnCount - actualColumnCount} missing column(s) to this row.` + } + + addError( + onError, + i + 1, + errorMessage, + line, + range, + null, // No auto-fix available due to complexity + ) + } + } + } + }, +} diff --git a/src/content-linter/style/github-docs.js b/src/content-linter/style/github-docs.js index 91d1c6cfd047..5b0c89598949 100644 --- a/src/content-linter/style/github-docs.js +++ b/src/content-linter/style/github-docs.js @@ -186,6 +186,12 @@ const githubDocsConfig = { 'partial-markdown-files': true, 'yml-files': true, }, + 'table-column-integrity': { + // GHD047 + severity: 'warning', + 'partial-markdown-files': true, + 'yml-files': true, + }, 'british-english-quotes': { // GHD048 severity: 'warning', diff --git a/src/content-linter/tests/unit/table-column-integrity-simple.js b/src/content-linter/tests/unit/table-column-integrity-simple.js new file mode 100644 index 000000000000..a86940de739e --- /dev/null +++ b/src/content-linter/tests/unit/table-column-integrity-simple.js @@ -0,0 +1,63 @@ +import { describe, expect, test } from 'vitest' + +import { runRule } from '../../lib/init-test.js' +import { tableColumnIntegrity } from '../../lib/linting-rules/table-column-integrity.js' + +describe(tableColumnIntegrity.names.join(' - '), () => { + test('Valid table with consistent columns passes', async () => { + const markdown = [ + '| Artist | Album | Year |', + '|--------|-------|------|', + '| Beyoncé | Lemonade | 2016 |', + '| Kendrick Lamar | DAMN. | 2017 |', + ].join('\n') + const result = await runRule(tableColumnIntegrity, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(0) + }) + + test('Table with extra columns causes error', async () => { + const markdown = ['| Name | Age |', '|------|-----|', '| Alice | 25 | Extra |'].join('\n') + const result = await runRule(tableColumnIntegrity, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(1) + expect(errors[0].lineNumber).toBe(3) + if (errors[0].detail) { + expect(errors[0].detail).toContain('Table row has 3 columns but header has 2') + } else if (errors[0].errorDetail) { + expect(errors[0].errorDetail).toContain('Table row has 3 columns but header has 2') + } else { + console.log('Error structure:', JSON.stringify(errors[0], null, 2)) + expect(errors[0]).toHaveProperty('detail') + } + }) + + test('Table with missing columns causes error', async () => { + const markdown = ['| Name | Age | City |', '|------|-----|------|', '| Bob | 30 |'].join('\n') + const result = await runRule(tableColumnIntegrity, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(1) + expect(errors[0].lineNumber).toBe(3) + if (errors[0].detail) { + expect(errors[0].detail).toContain('Table row has 2 columns but header has 3') + } else if (errors[0].errorDetail) { + expect(errors[0].errorDetail).toContain('Table row has 2 columns but header has 3') + } else { + console.log('Error structure:', JSON.stringify(errors[0], null, 2)) + expect(errors[0]).toHaveProperty('detail') + } + }) + + test('Liquid-only rows are ignored', async () => { + const markdown = [ + '| Feature | Status |', + '|---------|--------|', + '| {% ifversion ghes %} |', + '| Advanced | Enabled |', + '| {% endif %} |', + ].join('\n') + const result = await runRule(tableColumnIntegrity, { strings: { markdown } }) + const errors = result.markdown + expect(errors.length).toBe(0) + }) +}) From e5e13f89544c6a7955d2385a9010c7061827edad Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 9 Jul 2025 10:51:25 -0700 Subject: [PATCH 3/7] Update content linting report to only include errors and expired content warnings (#56479) --- .../lint-entire-content-data-markdown.yml | 2 +- package.json | 2 +- .../scripts/{post-lints.js => lint-report.js} | 58 +++++++++++++++++-- src/content-linter/style/github-docs.js | 20 +++++++ 4 files changed, 75 insertions(+), 7 deletions(-) rename src/content-linter/scripts/{post-lints.js => lint-report.js} (62%) diff --git a/.github/workflows/lint-entire-content-data-markdown.yml b/.github/workflows/lint-entire-content-data-markdown.yml index ee97eb16cca3..151e02340c90 100644 --- a/.github/workflows/lint-entire-content-data-markdown.yml +++ b/.github/workflows/lint-entire-content-data-markdown.yml @@ -41,7 +41,7 @@ jobs: REPORT_AUTHOR: docs-bot REPORT_LABEL: broken content markdown report REPORT_REPOSITORY: github/docs-content - run: npm run post-lints -- --path /tmp/lint-results.json + run: npm run lint-report -- --path /tmp/lint-results.json - uses: ./.github/actions/slack-alert if: ${{ failure() && github.event_name != 'workflow_dispatch' }} diff --git a/package.json b/package.json index 2d60e5fc64b1..7544fb15037b 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "move-content": "tsx src/content-render/scripts/move-content.js", "openapi-docs": "tsx src/rest/docs.js", "playwright-test": "playwright test --config src/fixtures/playwright.config.ts --project=\"Google Chrome\"", - "post-lints": "tsx src/content-linter/scripts/post-lints.js", + "lint-report": "tsx src/content-linter/scripts/lint-report.js", "postinstall": "cp package-lock.json .installed.package-lock.json && echo \"Updated .installed.package-lock.json\" # see husky/post-checkout and husky/post-merge", "precompute-pageinfo": "tsx src/article-api/scripts/precompute-pageinfo.ts", "prepare": "husky src/workflows/husky", diff --git a/src/content-linter/scripts/post-lints.js b/src/content-linter/scripts/lint-report.js similarity index 62% rename from src/content-linter/scripts/post-lints.js rename to src/content-linter/scripts/lint-report.js index b11ba5fbfcec..24961cdb7014 100644 --- a/src/content-linter/scripts/post-lints.js +++ b/src/content-linter/scripts/lint-report.js @@ -5,10 +5,47 @@ import coreLib from '@actions/core' import github from '#src/workflows/github.ts' import { getEnvInputs } from '#src/workflows/get-env-inputs.ts' import { createReportIssue, linkReports } from '#src/workflows/issue-report.js' +import { reportingConfig } from '#src/content-linter/style/github-docs.js' // GitHub issue body size limit is ~65k characters, so we'll use 60k as a safe limit const MAX_ISSUE_BODY_SIZE = 60000 +/** + * Determines if a lint result should be included in the automated report + * @param {Object} flaw - The lint result object + * @param {string} flaw.severity - 'error' or 'warning' + * @param {string[]} flaw.ruleNames - Array of rule names for this flaw + * @returns {boolean} - True if this flaw should be included in the report + */ +function shouldIncludeInReport(flaw) { + if (!flaw.ruleNames || !Array.isArray(flaw.ruleNames)) { + return false + } + + // Check if any rule name is in the exclude list + const hasExcludedRule = flaw.ruleNames.some((ruleName) => + reportingConfig.excludeRules.includes(ruleName), + ) + if (hasExcludedRule) { + return false + } + + // Check if severity should be included + if (reportingConfig.includeSeverities.includes(flaw.severity)) { + return true + } + + // Check if any rule name is in the include list + const hasIncludedRule = flaw.ruleNames.some((ruleName) => + reportingConfig.includeRules.includes(ruleName), + ) + if (hasIncludedRule) { + return true + } + + return false +} + // [start-readme] // // This script runs once a week via a scheduled GitHub Action to lint @@ -46,15 +83,26 @@ async function main() { // or open an issue report, you might get cryptic error messages from Octokit. getEnvInputs(['GITHUB_TOKEN']) - core.info(`Creating issue for errors and warnings...`) + core.info(`Creating issue for configured lint rules...`) const parsedResults = JSON.parse(lintResults) - const totalFiles = Object.keys(parsedResults).length - let reportBody = 'The following files have markdown lint warnings/errors:\n\n' + + // Filter results based on reporting configuration + const filteredResults = {} + for (const [file, flaws] of Object.entries(parsedResults)) { + const filteredFlaws = flaws.filter(shouldIncludeInReport) + + // Only include files that have remaining flaws after filtering + if (filteredFlaws.length > 0) { + filteredResults[file] = filteredFlaws + } + } + const totalFiles = Object.keys(filteredResults).length + let reportBody = 'The following files have markdown lint issues that require attention:\n\n' let filesIncluded = 0 let truncated = false - for (const [file, flaws] of Object.entries(parsedResults)) { + for (const [file, flaws] of Object.entries(filteredResults)) { const fileEntry = `File: \`${file}\`:\n\`\`\`json\n${JSON.stringify(flaws, null, 2)}\n\`\`\`\n` // Check if adding this file would exceed the size limit @@ -77,7 +125,7 @@ async function main() { const reportProps = { core, octokit, - reportTitle: `Error(s) and warning(s) in content markdown file(s)`, + reportTitle: `Content linting issues requiring attention`, reportBody, reportRepository: REPORT_REPOSITORY, reportLabel: REPORT_LABEL, diff --git a/src/content-linter/style/github-docs.js b/src/content-linter/style/github-docs.js index 5b0c89598949..706fc8cc5462 100644 --- a/src/content-linter/style/github-docs.js +++ b/src/content-linter/style/github-docs.js @@ -1,3 +1,23 @@ +// Configuration for which rules should be included in automated weekly reports +export const reportingConfig = { + // Always include all rules with these severities + includeSeverities: ['error'], + + // Specific rules to include regardless of severity + // Add rule names (short or long form) that should always be reported + includeRules: [ + 'GHD038', // expired-content - Content that has passed its expiration date + 'expired-content', + ], + + // Specific rules to exclude from reports (overrides severity-based inclusion) + // Add rule names here if you want to suppress them from reports + excludeRules: [ + // Example: 'GHD030' // Uncomment to exclude code-fence-line-length warnings + // Example: 'british-english-quotes' // Uncomment to exclude punctuation warnings + ], +} + const githubDocsConfig = { 'link-punctuation': { // GHD001 From 8872a88693dc9fc8c3851ec0d34045be09a3f279 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 9 Jul 2025 11:04:14 -0700 Subject: [PATCH 4/7] Convert additional data-directory files to TypeScript (#56396) --- .../lib/helpers/get-lintable-yml.js | 2 +- src/data-directory/lib/data-directory.d.ts | 4 -- .../{data-directory.js => data-directory.ts} | 56 +++++++++++++------ .../lib/data-schemas/{index.js => index.ts} | 8 ++- ...{filename-to-key.js => filename-to-key.ts} | 5 +- src/data-directory/tests/data-schemas.js | 2 +- src/data-directory/tests/filename-to-key.js | 2 +- src/data-directory/tests/index.js | 2 +- src/ghes-releases/scripts/version-utils.ts | 2 +- 9 files changed, 53 insertions(+), 30 deletions(-) delete mode 100644 src/data-directory/lib/data-directory.d.ts rename src/data-directory/lib/{data-directory.js => data-directory.ts} (54%) rename src/data-directory/lib/data-schemas/{index.js => index.ts} (83%) rename src/data-directory/lib/{filename-to-key.js => filename-to-key.ts} (86%) diff --git a/src/content-linter/lib/helpers/get-lintable-yml.js b/src/content-linter/lib/helpers/get-lintable-yml.js index 438d7690b72d..104eaf95c6b5 100755 --- a/src/content-linter/lib/helpers/get-lintable-yml.js +++ b/src/content-linter/lib/helpers/get-lintable-yml.js @@ -1,7 +1,7 @@ import yaml from 'js-yaml' import fs from 'fs/promises' -import dataSchemas from '#src/data-directory/lib/data-schemas/index.js' +import dataSchemas from '#src/data-directory/lib/data-schemas/index.ts' import ajv from '#src/tests/lib/validate-json-schema.js' // AJV already has a built-in way to extract out properties diff --git a/src/data-directory/lib/data-directory.d.ts b/src/data-directory/lib/data-directory.d.ts deleted file mode 100644 index 4443af370cea..000000000000 --- a/src/data-directory/lib/data-directory.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { FeatureData } from '@/types.js' - -declare function dataDirectory(dir: string, opts?: Object): FeatureData -export default dataDirectory diff --git a/src/data-directory/lib/data-directory.js b/src/data-directory/lib/data-directory.ts similarity index 54% rename from src/data-directory/lib/data-directory.js rename to src/data-directory/lib/data-directory.ts index 0150c7a66c8e..f9739b4f7289 100644 --- a/src/data-directory/lib/data-directory.js +++ b/src/data-directory/lib/data-directory.ts @@ -4,45 +4,65 @@ import path from 'path' import walk from 'walk-sync' import yaml from 'js-yaml' import { isRegExp, setWith } from 'lodash-es' -import filenameToKey from './filename-to-key.js' +import filenameToKey from './filename-to-key' import matter from 'gray-matter' -export default function dataDirectory(dir, opts = {}) { - const defaultOpts = { - preprocess: (content) => { +interface DataDirectoryOptions { + preprocess?: (content: string) => string + ignorePatterns?: RegExp[] + extensions?: string[] +} + +interface DataDirectoryResult { + [key: string]: any +} + +export default function dataDirectory( + dir: string, + opts: DataDirectoryOptions = {}, +): DataDirectoryResult { + const defaultOpts: Required = { + preprocess: (content: string) => { return content }, ignorePatterns: [/README\.md$/i], extensions: ['.json', '.md', '.markdown', '.yml'], } - opts = Object.assign({}, defaultOpts, opts) + const mergedOpts = Object.assign({}, defaultOpts, opts) // validate input - assert(Array.isArray(opts.ignorePatterns)) - assert(opts.ignorePatterns.every(isRegExp)) - assert(Array.isArray(opts.extensions)) - assert(opts.extensions.length) + assert(Array.isArray(mergedOpts.ignorePatterns)) + assert(mergedOpts.ignorePatterns.every(isRegExp)) + assert(Array.isArray(mergedOpts.extensions)) + assert(mergedOpts.extensions.length) // start with an empty data object - const data = {} + const data: DataDirectoryResult = {} // find YAML and Markdown files in the given directory, recursively - const filenames = walk(dir, { includeBasePath: true }).filter((filename) => { + const filenames = walk(dir, { includeBasePath: true }).filter((filename: string) => { // ignore files that match any of ignorePatterns regexes - if (opts.ignorePatterns.some((pattern) => pattern.test(filename))) return false + if (mergedOpts.ignorePatterns.some((pattern) => pattern.test(filename))) return false // ignore files that don't have a whitelisted file extension - return opts.extensions.includes(path.extname(filename).toLowerCase()) + return mergedOpts.extensions.includes(path.extname(filename).toLowerCase()) }) - const files = filenames.map((filename) => [filename, fs.readFileSync(filename, 'utf8')]) + const files: [string, string][] = filenames.map((filename: string) => [ + filename, + fs.readFileSync(filename, 'utf8'), + ]) + files.forEach(([filename, fileContent]) => { // derive `foo.bar.baz` object key from `foo/bar/baz.yml` filename const key = filenameToKey(path.relative(dir, filename)) const extension = path.extname(filename).toLowerCase() - if (opts.preprocess) fileContent = opts.preprocess(fileContent) + let processedContent = fileContent + if (mergedOpts.preprocess) { + processedContent = mergedOpts.preprocess(fileContent) + } // Add this file's data to the global data object. // Note we want to use `setWith` instead of `set` so we can customize the type during path creation. @@ -51,17 +71,17 @@ export default function dataDirectory(dir, opts = {}) { // See https://lodash.com/docs#set for an explanation. switch (extension) { case '.json': - setWith(data, key, JSON.parse(fileContent), Object) + setWith(data, key, JSON.parse(processedContent), Object) break case '.yml': - setWith(data, key, yaml.load(fileContent, { filename }), Object) + setWith(data, key, yaml.load(processedContent, { filename }), Object) break case '.md': case '.markdown': // Use `matter` to drop frontmatter, since localized reusable Markdown files // can potentially have frontmatter, but we want to prevent the frontmatter // from being rendered. - setWith(data, key, matter(fileContent).content, Object) + setWith(data, key, matter(processedContent).content, Object) break } }) diff --git a/src/data-directory/lib/data-schemas/index.js b/src/data-directory/lib/data-schemas/index.ts similarity index 83% rename from src/data-directory/lib/data-schemas/index.js rename to src/data-directory/lib/data-schemas/index.ts index c7fb2d767c6f..6b60269fcecc 100644 --- a/src/data-directory/lib/data-schemas/index.js +++ b/src/data-directory/lib/data-schemas/index.ts @@ -1,4 +1,8 @@ -export default { +interface DataSchemas { + [key: string]: string +} + +const dataSchemas: DataSchemas = { 'data/features': '#src/data-directory/lib/data-schemas/features.js', 'data/variables': '#src/data-directory/lib/data-schemas/variables.js', 'data/learning-tracks': '#src/data-directory/lib/data-schemas/learning-tracks.js', @@ -7,3 +11,5 @@ export default { 'data/glossaries/candidates.yml': '#src/data-directory/lib/data-schemas/glossaries-candidates.js', 'data/glossaries/external.yml': '#src/data-directory/lib/data-schemas/glossaries-external.js', } + +export default dataSchemas diff --git a/src/data-directory/lib/filename-to-key.js b/src/data-directory/lib/filename-to-key.ts similarity index 86% rename from src/data-directory/lib/filename-to-key.js rename to src/data-directory/lib/filename-to-key.ts index 8fc07f05c804..96591e43f377 100644 --- a/src/data-directory/lib/filename-to-key.js +++ b/src/data-directory/lib/filename-to-key.ts @@ -1,5 +1,6 @@ import path from 'path' import { escapeRegExp } from 'lodash-es' + /* eslint-disable prefer-regex-literals */ // slash at the beginning of a filename @@ -14,8 +15,8 @@ const windowsPathSeparator = new RegExp('/', 'g') const windowsDoubleSlashSeparator = new RegExp('\\\\', 'g') // derive `foo.bar.baz` object key from `foo/bar/baz.yml` filename -export default function filenameToKey(filename) { - const extension = new RegExp(`${path.extname(filename)}$`) +export default function filenameToKey(filename: string): string { + const extension = new RegExp(`${escapeRegExp(path.extname(filename))}$`) const key = filename .replace(extension, '') .replace(leadingPathSeparator, '') diff --git a/src/data-directory/tests/data-schemas.js b/src/data-directory/tests/data-schemas.js index a68724bd89e9..067fe4f23c03 100644 --- a/src/data-directory/tests/data-schemas.js +++ b/src/data-directory/tests/data-schemas.js @@ -7,7 +7,7 @@ import { beforeAll, describe, expect, test } from 'vitest' import { getJsonValidator, validateJson } from '#src/tests/lib/validate-json-schema.js' import { formatAjvErrors } from '#src/tests/helpers/schemas.js' -import dataSchemas from '#src/data-directory/lib/data-schemas/index.js' +import dataSchemas from '#src/data-directory/lib/data-schemas/index.ts' const schemaPaths = Object.keys(dataSchemas) const singleFilesSchemas = schemaPaths.filter((schemaPath) => extname(schemaPath)) diff --git a/src/data-directory/tests/filename-to-key.js b/src/data-directory/tests/filename-to-key.js index b8bbcd085282..3141140d0680 100644 --- a/src/data-directory/tests/filename-to-key.js +++ b/src/data-directory/tests/filename-to-key.js @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' -import filenameToKey from '#src/data-directory/lib/filename-to-key.js' +import filenameToKey from '#src/data-directory/lib/filename-to-key.ts' describe('filename-to-key', () => { test('converts filenames to object keys', () => { diff --git a/src/data-directory/tests/index.js b/src/data-directory/tests/index.js index 462934603f99..80961b5f299b 100644 --- a/src/data-directory/tests/index.js +++ b/src/data-directory/tests/index.js @@ -3,7 +3,7 @@ import path from 'path' import { describe, expect, test } from 'vitest' -import dataDirectory from '#src/data-directory/lib/data-directory.js' +import dataDirectory from '#src/data-directory/lib/data-directory.ts' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const fixturesDir = path.join(__dirname, 'fixtures') diff --git a/src/ghes-releases/scripts/version-utils.ts b/src/ghes-releases/scripts/version-utils.ts index 25ea9ca82f31..202aa48b88a6 100644 --- a/src/ghes-releases/scripts/version-utils.ts +++ b/src/ghes-releases/scripts/version-utils.ts @@ -1,7 +1,7 @@ import semver from 'semver' import { supported } from '@/versions/lib/enterprise-server-releases.js' -import getDataDirectory from '@/data-directory/lib/data-directory.js' +import getDataDirectory from '@/data-directory/lib/data-directory' import { FeatureData, FrontmatterVersions } from '@/types.js' // Return true if lowestSupportedVersion > semVerRange From d01b58210f6af704f5916a8642ae6956c1dda3c4 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 9 Jul 2025 11:12:39 -0700 Subject: [PATCH 5/7] Convert all-versions.js to TypeScript (#56404) --- src/archives/lib/old-versions-utils.ts | 2 +- src/article-api/tests/pagelist.ts | 2 +- src/audit-logs/lib/index.ts | 2 +- src/audit-logs/tests/rendering.ts | 2 +- .../lib/update-markdown.ts | 2 +- .../tests/frontmatter-versions.ts | 2 +- .../linting-rules/image-alt-text-length.js | 2 +- .../liquid-ifversion-versions.js | 2 +- .../lib/linting-rules/liquid-versioning.js | 2 +- .../third-party-action-pinning.js | 2 +- .../lib/linting-rules/yaml-scheduled-jobs.js | 2 +- .../scripts/all-documents/cli.ts | 2 +- .../reconcile-category-dirs-with-ids.js | 2 +- src/content-render/tests/liquid.ts | 2 +- .../unified/rewrite-local-links.js | 2 +- src/dev-toc/generate.ts | 2 +- src/events/lib/schema.ts | 2 +- src/frame/lib/frontmatter.js | 2 +- src/frame/lib/page-data.js | 2 +- src/frame/lib/path-utils.js | 2 +- src/frame/middleware/context/context.ts | 2 +- .../middleware/context/product-groups.ts | 2 +- src/frame/middleware/llms-txt.ts | 2 +- src/frame/middleware/render-page.ts | 2 +- src/frame/tests/page.js | 2 +- src/frame/tests/toc-links.js | 2 +- src/ghes-releases/scripts/release-banner.ts | 2 +- src/github-apps/lib/index.js | 2 +- src/github-apps/tests/rendering.js | 2 +- src/graphql/lib/index.js | 2 +- src/graphql/scripts/sync.js | 2 +- src/graphql/tests/get-schema-files.js | 2 +- src/graphql/tests/validate-schema.js | 2 +- .../rendered-content-link-checker-cli.ts | 2 +- src/redirects/lib/get-redirect.ts | 2 +- src/release-notes/tests/yaml.ts | 2 +- src/rest/docs.js | 2 +- src/rest/lib/index.js | 2 +- src/rest/scripts/test-open-api-schema.js | 2 +- src/rest/scripts/utils/get-openapi-schemas.js | 2 +- src/rest/scripts/utils/update-markdown.js | 2 +- src/rest/tests/get-schema-files.ts | 2 +- src/rest/tests/openapi-schema.js | 2 +- src/rest/tests/rendering.js | 2 +- src/search/scripts/analyze-text.ts | 2 +- .../lib/{all-versions.js => all-versions.ts} | 59 +++++++++++++++---- src/versions/lib/get-applicable-versions.js | 2 +- .../lib/non-enterprise-default-version.js | 2 +- src/versions/scripts/use-short-versions.js | 2 +- src/versions/tests/get-applicable-versions.js | 2 +- src/versions/tests/ghes-versioning.js | 2 +- src/versions/tests/versions.js | 2 +- src/webhooks/lib/index.ts | 2 +- src/webhooks/middleware/webhooks.ts | 2 +- src/webhooks/tests/get-schema-files.ts | 2 +- src/webhooks/tests/rendering.ts | 2 +- .../content-changes-table-comment.ts | 2 +- 57 files changed, 103 insertions(+), 68 deletions(-) rename src/versions/lib/{all-versions.js => all-versions.ts} (71%) mode change 100755 => 100644 diff --git a/src/archives/lib/old-versions-utils.ts b/src/archives/lib/old-versions-utils.ts index 0952c97fba0d..75d6c2749d13 100644 --- a/src/archives/lib/old-versions-utils.ts +++ b/src/archives/lib/old-versions-utils.ts @@ -2,7 +2,7 @@ import path from 'path' import { supported, latest } from '@/versions/lib/enterprise-server-releases.js' import patterns from '@/frame/lib/patterns.js' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' const latestNewVersion = `enterprise-server@${latest}` const oldVersions = ['dotcom'].concat(supported) const newVersions = Object.keys(allVersions) diff --git a/src/article-api/tests/pagelist.ts b/src/article-api/tests/pagelist.ts index 2fb6247edbad..97c33f0bff3b 100644 --- a/src/article-api/tests/pagelist.ts +++ b/src/article-api/tests/pagelist.ts @@ -2,7 +2,7 @@ import { beforeAll, describe, expect, test } from 'vitest' import { get } from '@/tests/helpers/e2etest.js' -import { allVersionKeys } from '@/versions/lib/all-versions.js' +import { allVersionKeys } from '@/versions/lib/all-versions' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version.js' describe.each(allVersionKeys)('pagelist api for %s', async (versionKey) => { diff --git a/src/audit-logs/lib/index.ts b/src/audit-logs/lib/index.ts index b468bc6168bf..7f0cb1d6b108 100644 --- a/src/audit-logs/lib/index.ts +++ b/src/audit-logs/lib/index.ts @@ -1,7 +1,7 @@ import path from 'path' import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file.js' -import { getOpenApiVersion } from '@/versions/lib/all-versions.js' +import { getOpenApiVersion } from '@/versions/lib/all-versions' import type { AuditLogEventT, CategorizedEvents, diff --git a/src/audit-logs/tests/rendering.ts b/src/audit-logs/tests/rendering.ts index f3d85852506f..cd63d3efc17c 100644 --- a/src/audit-logs/tests/rendering.ts +++ b/src/audit-logs/tests/rendering.ts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from 'vitest' import { getDOM } from '@/tests/helpers/e2etest.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import { getCategorizedAuditLogEvents } from '../lib' describe('audit log events docs', () => { diff --git a/src/automated-pipelines/lib/update-markdown.ts b/src/automated-pipelines/lib/update-markdown.ts index 8953f35362fc..a02dddc6dfe8 100644 --- a/src/automated-pipelines/lib/update-markdown.ts +++ b/src/automated-pipelines/lib/update-markdown.ts @@ -7,7 +7,7 @@ import { rimraf } from 'rimraf' import { mkdirp } from 'mkdirp' import { difference, isEqual } from 'lodash-es' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import getApplicableVersions from '@/versions/lib/get-applicable-versions.js' import type { MarkdownFrontmatter } from '@/types' diff --git a/src/automated-pipelines/tests/frontmatter-versions.ts b/src/automated-pipelines/tests/frontmatter-versions.ts index bdc5493d1494..10fc07f1a399 100644 --- a/src/automated-pipelines/tests/frontmatter-versions.ts +++ b/src/automated-pipelines/tests/frontmatter-versions.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest' import { supported } from '@/versions/lib/enterprise-server-releases.js' -import { allVersionKeys, allVersions } from '@/versions/lib/all-versions.js' +import { allVersionKeys, allVersions } from '@/versions/lib/all-versions' import { convertVersionsToFrontmatter } from '../lib/update-markdown.js' describe('frontmatter versions are generated correctly from automated data', () => { diff --git a/src/content-linter/lib/linting-rules/image-alt-text-length.js b/src/content-linter/lib/linting-rules/image-alt-text-length.js index aaf8ab4f39d1..df149f21434c 100644 --- a/src/content-linter/lib/linting-rules/image-alt-text-length.js +++ b/src/content-linter/lib/linting-rules/image-alt-text-length.js @@ -1,7 +1,7 @@ import { addError } from 'markdownlint-rule-helpers' import { liquid } from '#src/content-render/index.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import { forEachInlineChild, getRange } from '../helpers/utils.js' export const incorrectAltTextLength = { diff --git a/src/content-linter/lib/linting-rules/liquid-ifversion-versions.js b/src/content-linter/lib/linting-rules/liquid-ifversion-versions.js index 0fe1dce949fa..8f6359b74f9a 100644 --- a/src/content-linter/lib/linting-rules/liquid-ifversion-versions.js +++ b/src/content-linter/lib/linting-rules/liquid-ifversion-versions.js @@ -8,7 +8,7 @@ import { } from '../helpers/liquid-utils.js' import { getFrontmatter, getFrontmatterLines } from '../helpers/utils.js' import getApplicableVersions from '#src/versions/lib/get-applicable-versions.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import { difference } from 'lodash-es' import { convertVersionsToFrontmatter } from '#src/automated-pipelines/lib/update-markdown.js' import { diff --git a/src/content-linter/lib/linting-rules/liquid-versioning.js b/src/content-linter/lib/linting-rules/liquid-versioning.js index 2b5e42c1d995..249ff4e29bb4 100644 --- a/src/content-linter/lib/linting-rules/liquid-versioning.js +++ b/src/content-linter/lib/linting-rules/liquid-versioning.js @@ -3,7 +3,7 @@ import { TokenKind } from 'liquidjs' import { addError } from 'markdownlint-rule-helpers' import { getRange, addFixErrorDetail } from '../helpers/utils.js' -import { allVersions, allVersionShortnames } from '#src/versions/lib/all-versions.js' +import { allVersions, allVersionShortnames } from '#src/versions/lib/all-versions.ts' import { supported, next, diff --git a/src/content-linter/lib/linting-rules/third-party-action-pinning.js b/src/content-linter/lib/linting-rules/third-party-action-pinning.js index b99c12eb24bc..bc56d73a48dc 100644 --- a/src/content-linter/lib/linting-rules/third-party-action-pinning.js +++ b/src/content-linter/lib/linting-rules/third-party-action-pinning.js @@ -2,7 +2,7 @@ import yaml from 'js-yaml' import { addError, filterTokens } from 'markdownlint-rule-helpers' import { liquid } from '#src/content-render/index.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' // Detects third-party actions in the format `owner/repo@ref` const actionRegex = /[\w-]+\/[\w-]+@[\w-]+/ diff --git a/src/content-linter/lib/linting-rules/yaml-scheduled-jobs.js b/src/content-linter/lib/linting-rules/yaml-scheduled-jobs.js index 2b4ea3a3c637..640b503552d4 100644 --- a/src/content-linter/lib/linting-rules/yaml-scheduled-jobs.js +++ b/src/content-linter/lib/linting-rules/yaml-scheduled-jobs.js @@ -2,7 +2,7 @@ import yaml from 'js-yaml' import { addError, filterTokens } from 'markdownlint-rule-helpers' import { liquid } from '#src/content-render/index.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' const scheduledYamlJobs = [] diff --git a/src/content-render/scripts/all-documents/cli.ts b/src/content-render/scripts/all-documents/cli.ts index 9035cde6b4dc..3a98d7bb4a0d 100644 --- a/src/content-render/scripts/all-documents/cli.ts +++ b/src/content-render/scripts/all-documents/cli.ts @@ -44,7 +44,7 @@ import { writeFileSync, statSync } from 'fs' import { program, Option } from 'commander' import { languageKeys } from '@/languages/lib/languages.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import { allDocuments, POSSIBLE_FIELDS, type AllDocument } from './lib' // E.g. enteprise-server@3.12, free-pro-team@latest, etc diff --git a/src/content-render/scripts/reconcile-category-dirs-with-ids.js b/src/content-render/scripts/reconcile-category-dirs-with-ids.js index 9381a17c171b..487a79ba6849 100755 --- a/src/content-render/scripts/reconcile-category-dirs-with-ids.js +++ b/src/content-render/scripts/reconcile-category-dirs-with-ids.js @@ -16,7 +16,7 @@ import { decode } from 'html-entities' import frontmatter from '#src/frame/lib/read-frontmatter.js' import { renderContent } from '#src/content-render/index.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import { ROOT } from '#src/frame/lib/constants.js' const slugger = new GithubSlugger() diff --git a/src/content-render/tests/liquid.ts b/src/content-render/tests/liquid.ts index 46eeb9c65a5a..1cb8a9be426e 100644 --- a/src/content-render/tests/liquid.ts +++ b/src/content-render/tests/liquid.ts @@ -4,7 +4,7 @@ import type { Response } from 'express' import { liquid } from '@/content-render/index.js' import shortVersionsMiddleware from '@/versions/middleware/short-versions.js' import featureVersionsMiddleware from '@/versions/middleware/features' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases.js' import type { Context, ExtendedRequest, Page } from '@/types' diff --git a/src/content-render/unified/rewrite-local-links.js b/src/content-render/unified/rewrite-local-links.js index 1e68cda98f0d..1bfc47c65666 100644 --- a/src/content-render/unified/rewrite-local-links.js +++ b/src/content-render/unified/rewrite-local-links.js @@ -8,7 +8,7 @@ import { getNewVersionedPath } from '#src/archives/lib/old-versions-utils.ts' import patterns from '#src/frame/lib/patterns.js' import { deprecated, latest } from '#src/versions/lib/enterprise-server-releases.js' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import removeFPTFromPath from '#src/versions/lib/remove-fpt-from-path.js' import readJsonFile from '#src/frame/lib/read-json-file.js' import findPage from '#src/frame/lib/find-page.js' diff --git a/src/dev-toc/generate.ts b/src/dev-toc/generate.ts index 63fb7ca29aa3..fae9d7341c13 100644 --- a/src/dev-toc/generate.ts +++ b/src/dev-toc/generate.ts @@ -5,7 +5,7 @@ import { program } from 'commander' import type { NextFunction, Response } from 'express' import type { ExtendedRequest } from '@/types' import fpt from '@/versions/lib/non-enterprise-default-version.js' -import { allVersionKeys } from '@/versions/lib/all-versions.js' +import { allVersionKeys } from '@/versions/lib/all-versions' import { liquid } from '@/content-render/index.js' import contextualize from '@/frame/middleware/context/context.js' diff --git a/src/events/lib/schema.ts b/src/events/lib/schema.ts index 302997ac7335..089110eb77a2 100644 --- a/src/events/lib/schema.ts +++ b/src/events/lib/schema.ts @@ -1,5 +1,5 @@ import { languageKeys } from '@/languages/lib/languages.js' -import { allVersionKeys } from '@/versions/lib/all-versions.js' +import { allVersionKeys } from '@/versions/lib/all-versions' import { productIds } from '@/products/lib/all-products' import { allTools } from '@/tools/lib/all-tools.js' diff --git a/src/frame/lib/frontmatter.js b/src/frame/lib/frontmatter.js index 79f8e55b3bac..9c9403679b58 100644 --- a/src/frame/lib/frontmatter.js +++ b/src/frame/lib/frontmatter.js @@ -1,5 +1,5 @@ import parse from './read-frontmatter.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import { allTools } from '#src/tools/lib/all-tools.ts' import { getDeepDataByLanguage } from '#src/data-directory/lib/get-data.js' diff --git a/src/frame/lib/page-data.js b/src/frame/lib/page-data.js index 149eb4aa9bc9..d9a4a1c8d1f4 100644 --- a/src/frame/lib/page-data.js +++ b/src/frame/lib/page-data.js @@ -1,7 +1,7 @@ import path from 'path' import languages from '#src/languages/lib/languages.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import createTree from './create-tree.js' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' import readFileContents from './read-file-contents.js' diff --git a/src/frame/lib/path-utils.js b/src/frame/lib/path-utils.js index a8585509cb2d..2fc8d43f8abf 100644 --- a/src/frame/lib/path-utils.js +++ b/src/frame/lib/path-utils.js @@ -3,7 +3,7 @@ import path from 'path' import patterns from './patterns.js' import { latest } from '#src/versions/lib/enterprise-server-releases.js' import { productIds } from '#src/products/lib/all-products.ts' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' const supportedVersions = new Set(Object.keys(allVersions)) diff --git a/src/frame/middleware/context/context.ts b/src/frame/middleware/context/context.ts index 944a3f890fb2..ea517bee8ed7 100644 --- a/src/frame/middleware/context/context.ts +++ b/src/frame/middleware/context/context.ts @@ -4,7 +4,7 @@ import type { ExtendedRequest, Context } from '@/types' import languages from '@/languages/lib/languages.js' import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import { productMap } from '@/products/lib/all-products.js' import { getVersionStringFromPath, diff --git a/src/frame/middleware/context/product-groups.ts b/src/frame/middleware/context/product-groups.ts index a4fc04f6372e..6c5a5f3e70c6 100644 --- a/src/frame/middleware/context/product-groups.ts +++ b/src/frame/middleware/context/product-groups.ts @@ -4,7 +4,7 @@ import type { ExtendedRequest } from '@/types' import { getProductGroups } from '@/products/lib/get-product-groups' import warmServer from '@/frame/lib/warm-server' import { languageKeys } from '@/languages/lib/languages.js' -import { allVersionKeys } from '@/versions/lib/all-versions.js' +import { allVersionKeys } from '@/versions/lib/all-versions' const isHomepage = (path: string) => { const split = path.split('/') diff --git a/src/frame/middleware/llms-txt.ts b/src/frame/middleware/llms-txt.ts index dd4135786119..75166f96e140 100644 --- a/src/frame/middleware/llms-txt.ts +++ b/src/frame/middleware/llms-txt.ts @@ -6,7 +6,7 @@ import { defaultCacheControl } from '@/frame/middleware/cache-control.js' import catchMiddlewareError from '@/observability/middleware/catch-middleware-error.js' import statsd from '@/observability/lib/statsd.js' import languages from '@/languages/lib/languages.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' const router = express.Router() const BASE_API_URL = 'https://docs.github.com/api/pagelist' diff --git a/src/frame/middleware/render-page.ts b/src/frame/middleware/render-page.ts index 149ba73798be..4b4816c594ac 100644 --- a/src/frame/middleware/render-page.ts +++ b/src/frame/middleware/render-page.ts @@ -10,7 +10,7 @@ import patterns from '@/frame/lib/patterns.js' import getMiniTocItems from '@/frame/lib/get-mini-toc-items.js' import { pathLanguagePrefixed } from '@/languages/lib/languages.js' import statsd from '@/observability/lib/statsd.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import { isConnectionDropped } from './halt-on-dropped-connection' import { nextHandleRequest } from './next.js' import { defaultCacheControl } from './cache-control.js' diff --git a/src/frame/tests/page.js b/src/frame/tests/page.js index 3619c7678655..b623a8bcf844 100644 --- a/src/frame/tests/page.js +++ b/src/frame/tests/page.js @@ -5,7 +5,7 @@ import cheerio from 'cheerio' import { beforeAll, beforeEach, describe, expect, test } from 'vitest' import Page, { FrontmatterErrorsError } from '#src/frame/lib/page.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import enterpriseServerReleases, { latest } from '#src/versions/lib/enterprise-server-releases.js' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' diff --git a/src/frame/tests/toc-links.js b/src/frame/tests/toc-links.js index 976e8c74e5fd..713a985d1d65 100644 --- a/src/frame/tests/toc-links.js +++ b/src/frame/tests/toc-links.js @@ -2,7 +2,7 @@ import { describe, expect, test, vi } from 'vitest' import { loadPageMap, loadPages } from '#src/frame/lib/page-data.js' import { renderContent } from '#src/content-render/index.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' describe('toc links', () => { vi.setConfig({ testTimeout: 3 * 60 * 1000 }) diff --git a/src/ghes-releases/scripts/release-banner.ts b/src/ghes-releases/scripts/release-banner.ts index 2fe9e809534e..358dd8185f2f 100644 --- a/src/ghes-releases/scripts/release-banner.ts +++ b/src/ghes-releases/scripts/release-banner.ts @@ -7,7 +7,7 @@ import fs from 'fs/promises' import { program } from 'commander' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' const releaseCandidateJSFile = 'src/versions/lib/enterprise-server-releases.js' const allowedActions = ['create', 'remove'] as const diff --git a/src/github-apps/lib/index.js b/src/github-apps/lib/index.js index 944f37bd59ab..1975e4d21426 100644 --- a/src/github-apps/lib/index.js +++ b/src/github-apps/lib/index.js @@ -2,7 +2,7 @@ import path from 'path' import { readFile } from 'fs/promises' import { readCompressedJsonFileFallback } from '#src/frame/lib/read-json-file.js' -import { getOpenApiVersion } from '#src/versions/lib/all-versions.js' +import { getOpenApiVersion } from '#src/versions/lib/all-versions.ts' import { categoriesWithoutSubcategories } from '../../rest/lib/index.js' const ENABLED_APPS_DIR = 'src/github-apps/data' diff --git a/src/github-apps/tests/rendering.js b/src/github-apps/tests/rendering.js index a566e5bb8aff..9dda42f8c44d 100644 --- a/src/github-apps/tests/rendering.js +++ b/src/github-apps/tests/rendering.js @@ -2,7 +2,7 @@ import { readFile } from 'fs/promises' import { describe, expect, test, vi } from 'vitest' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import { get, getDOM } from '#src/tests/helpers/e2etest.js' import { categoriesWithoutSubcategories } from '#src/rest/lib/index.js' import { getAppsData } from '#src/github-apps/lib/index.js' diff --git a/src/graphql/lib/index.js b/src/graphql/lib/index.js index 9f8e77f64baf..44168b24235b 100644 --- a/src/graphql/lib/index.js +++ b/src/graphql/lib/index.js @@ -4,7 +4,7 @@ import { } from '#src/frame/lib/read-json-file.js' import { getAutomatedPageMiniTocItems } from '#src/frame/lib/get-mini-toc-items.js' import languages from '#src/languages/lib/languages.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' export const GRAPHQL_DATA_DIR = 'src/graphql/data' /* ADD LANGUAGE KEY */ diff --git a/src/graphql/scripts/sync.js b/src/graphql/scripts/sync.js index 9449b247267c..e84e66c4a216 100755 --- a/src/graphql/scripts/sync.js +++ b/src/graphql/scripts/sync.js @@ -4,7 +4,7 @@ import { mkdirp } from 'mkdirp' import yaml from 'js-yaml' import { execSync } from 'child_process' import { getContents, hasMatchingRef } from '#src/workflows/git-utils.ts' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import processPreviews from './utils/process-previews.js' import processUpcomingChanges from './utils/process-upcoming-changes.js' import processSchemas from './utils/process-schemas.js' diff --git a/src/graphql/tests/get-schema-files.js b/src/graphql/tests/get-schema-files.js index c95781d1da09..c27902c3803f 100644 --- a/src/graphql/tests/get-schema-files.js +++ b/src/graphql/tests/get-schema-files.js @@ -2,7 +2,7 @@ import { readFileSync } from 'fs' import { describe, expect, test } from 'vitest' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import { getGraphqlSchema, getGraphqlChangelog, diff --git a/src/graphql/tests/validate-schema.js b/src/graphql/tests/validate-schema.js index 726d23c77788..978eadb8c677 100644 --- a/src/graphql/tests/validate-schema.js +++ b/src/graphql/tests/validate-schema.js @@ -4,7 +4,7 @@ import { getJsonValidator, validateJson } from '#src/tests/lib/validate-json-sch import readJsonFile from '#src/frame/lib/read-json-file.js' import { schemaValidator, previewsValidator, upcomingChangesValidator } from '../lib/validator.ts' import { formatAjvErrors } from '#src/tests/helpers/schemas.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import { GRAPHQL_DATA_DIR } from '../lib/index.js' const allVersionValues = Object.values(allVersions) diff --git a/src/links/scripts/rendered-content-link-checker-cli.ts b/src/links/scripts/rendered-content-link-checker-cli.ts index 8e14c780bec6..f7f2792f6c5a 100755 --- a/src/links/scripts/rendered-content-link-checker-cli.ts +++ b/src/links/scripts/rendered-content-link-checker-cli.ts @@ -10,7 +10,7 @@ import path from 'path' import { program, Option, InvalidArgumentError } from 'commander' import renderedContentLinkChecker from './rendered-content-link-checker' import { getCoreInject, getUploadArtifactInject } from '@/links/scripts/action-injections.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import github from '@/workflows/github.js' const STATIC_PREFIXES = { diff --git a/src/redirects/lib/get-redirect.ts b/src/redirects/lib/get-redirect.ts index 2d84a672874f..d6ce89da011e 100644 --- a/src/redirects/lib/get-redirect.ts +++ b/src/redirects/lib/get-redirect.ts @@ -1,6 +1,6 @@ import { languageKeys } from '#src/languages/lib/languages.js' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import { latest, latestStable, diff --git a/src/release-notes/tests/yaml.ts b/src/release-notes/tests/yaml.ts index 16b5fcd41936..177aebad3b1f 100644 --- a/src/release-notes/tests/yaml.ts +++ b/src/release-notes/tests/yaml.ts @@ -7,7 +7,7 @@ import yaml from 'js-yaml' import { liquid } from '@/content-render/index.js' import { getDataByLanguage } from '@/data-directory/lib/get-data.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' interface ReleaseNoteContent { intro: string diff --git a/src/rest/docs.js b/src/rest/docs.js index 3bc1ddbbe495..de00a9f20e6c 100755 --- a/src/rest/docs.js +++ b/src/rest/docs.js @@ -1,6 +1,6 @@ import chalk from 'chalk' import { readFile } from 'fs/promises' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' // Translate the docs versioning nomenclature back to the OpenAPI names const invertedVersionMapping = JSON.parse(await readFile('src/rest/lib/config.json')).versionMapping diff --git a/src/rest/lib/index.js b/src/rest/lib/index.js index ff7208fb7d7f..32ddb6e5d39a 100644 --- a/src/rest/lib/index.js +++ b/src/rest/lib/index.js @@ -3,7 +3,7 @@ import path from 'path' import { readCompressedJsonFileFallback } from '#src/frame/lib/read-json-file.js' import { getAutomatedPageMiniTocItems } from '#src/frame/lib/get-mini-toc-items.js' -import { allVersions, getOpenApiVersion } from '#src/versions/lib/all-versions.js' +import { allVersions, getOpenApiVersion } from '#src/versions/lib/all-versions.ts' import languages from '#src/languages/lib/languages.js' export const REST_DATA_DIR = 'src/rest/data' diff --git a/src/rest/scripts/test-open-api-schema.js b/src/rest/scripts/test-open-api-schema.js index 7f4cdda72f93..5c5295a98dce 100755 --- a/src/rest/scripts/test-open-api-schema.js +++ b/src/rest/scripts/test-open-api-schema.js @@ -9,7 +9,7 @@ import _ from 'lodash' import frontmatter from '#src/frame/lib/read-frontmatter.js' import getApplicableVersions from '#src/versions/lib/get-applicable-versions.js' -import { allVersions, getDocsVersion } from '#src/versions/lib/all-versions.js' +import { allVersions, getDocsVersion } from '#src/versions/lib/all-versions.ts' import { REST_DATA_DIR, REST_SCHEMA_FILENAME } from '../lib/index.js' import { nonAutomatedRestPaths } from '../lib/config.js' import { deprecated } from '#src/versions/lib/enterprise-server-releases.js' diff --git a/src/rest/scripts/utils/get-openapi-schemas.js b/src/rest/scripts/utils/get-openapi-schemas.js index c7cd97ffe804..261aa661b724 100644 --- a/src/rest/scripts/utils/get-openapi-schemas.js +++ b/src/rest/scripts/utils/get-openapi-schemas.js @@ -2,7 +2,7 @@ import { readFile, readdir } from 'fs/promises' import yaml from 'js-yaml' import path from 'path' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' const OPEN_API_RELEASES_DIR = '../github/app/api/description/config/releases' const configData = JSON.parse(await readFile('src/rest/lib/config.json', 'utf8')) diff --git a/src/rest/scripts/utils/update-markdown.js b/src/rest/scripts/utils/update-markdown.js index d3664115b4ff..ac8d55ae390a 100644 --- a/src/rest/scripts/utils/update-markdown.js +++ b/src/rest/scripts/utils/update-markdown.js @@ -6,7 +6,7 @@ import { updateContentDirectory, convertVersionsToFrontmatter, } from '../../../automated-pipelines/lib/update-markdown.js' -import { getDocsVersion } from '#src/versions/lib/all-versions.js' +import { getDocsVersion } from '#src/versions/lib/all-versions.ts' import { REST_DATA_DIR, REST_SCHEMA_FILENAME } from '../../lib/index.js' import { deprecated } from '#src/versions/lib/enterprise-server-releases.js' diff --git a/src/rest/tests/get-schema-files.ts b/src/rest/tests/get-schema-files.ts index 7633754292a3..d5894abcaa0b 100644 --- a/src/rest/tests/get-schema-files.ts +++ b/src/rest/tests/get-schema-files.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest' import { getOpenApiSchemaFiles } from '../scripts/utils/sync' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' const supportedReleases = Object.keys(allVersions).map( (version) => allVersions[version].openApiVersionName, diff --git a/src/rest/tests/openapi-schema.js b/src/rest/tests/openapi-schema.js index c06b3c76726d..2aff779c6fa0 100644 --- a/src/rest/tests/openapi-schema.js +++ b/src/rest/tests/openapi-schema.js @@ -5,7 +5,7 @@ import { beforeAll, describe, expect, test } from 'vitest' import walk from 'walk-sync' import { isPlainObject, difference } from 'lodash-es' -import { isApiVersioned, allVersions } from '#src/versions/lib/all-versions.js' +import { isApiVersioned, allVersions } from '#src/versions/lib/all-versions.ts' import getRest from '../lib/index.js' import readFrontmatter from '#src/frame/lib/read-frontmatter.js' import frontmatter from '#src/frame/lib/frontmatter.js' diff --git a/src/rest/tests/rendering.js b/src/rest/tests/rendering.js index 03825d25b3ab..537dd66cf8f2 100644 --- a/src/rest/tests/rendering.js +++ b/src/rest/tests/rendering.js @@ -2,7 +2,7 @@ import { describe, expect, test, vi } from 'vitest' import { slug } from 'github-slugger' import { get, getDOM } from '#src/tests/helpers/e2etest.js' -import { isApiVersioned, allVersions } from '#src/versions/lib/all-versions.js' +import { isApiVersioned, allVersions } from '#src/versions/lib/all-versions.ts' import { getDiffOpenAPIContentRest } from '../scripts/test-open-api-schema.js' import getRest from '#src/rest/lib/index.js' diff --git a/src/search/scripts/analyze-text.ts b/src/search/scripts/analyze-text.ts index 668836734c5c..a042232e9016 100755 --- a/src/search/scripts/analyze-text.ts +++ b/src/search/scripts/analyze-text.ts @@ -11,7 +11,7 @@ import chalk from 'chalk' import dotenv from 'dotenv' import { languageKeys } from '@/languages/lib/languages.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import type { IndicesAnalyzeAnalyzeToken } from '@elastic/elasticsearch/lib/api/types' diff --git a/src/versions/lib/all-versions.js b/src/versions/lib/all-versions.ts old mode 100755 new mode 100644 similarity index 71% rename from src/versions/lib/all-versions.js rename to src/versions/lib/all-versions.ts index 2fa4c364310c..08fd68acda0c --- a/src/versions/lib/all-versions.js +++ b/src/versions/lib/all-versions.ts @@ -1,6 +1,7 @@ import fs from 'fs' - +import type { AllVersions, Version } from '@/types' import enterpriseServerReleases from './enterprise-server-releases.js' + // version = "plan"@"release" // example: enterprise-server@2.21 // where "enterprise-server" is the plan and "2.21" is the release @@ -8,6 +9,26 @@ const versionDelimiter = '@' const latestNonNumberedRelease = 'latest' const REST_DATA_META_FILE = 'src/rest/lib/config.json' +// Type for the plan configuration +interface PlanConfig { + plan: string + planTitle: string + shortName: string + releases: string[] + latestRelease: string + nonEnterpriseDefault?: boolean + hasNumberedReleases: boolean + openApiBaseName: string + miscBaseName: string +} + +// Type for the REST API config file +interface RestApiConfig { + 'api-versions': { + [key: string]: string[] + } +} + // !Explanation of versionless redirect fallbacks! // This array is **in order** of the versions the site should try to fall back to if // no version is provided in a URL. For example, if /foo refers to a page that is available @@ -15,7 +36,7 @@ const REST_DATA_META_FILE = 'src/rest/lib/config.json' // But if /foo refers to a page that is only available in GHEC and GHES, we should redirect it // to /enterprise-cloud@latest/foo (since GHEC comes first in the hierarchy of version fallbacks). // The implementation lives in lib/redirects/permalinks.js. -const plans = [ +const plans: PlanConfig[] = [ { // free-pro-team is **not** a user-facing version and is stripped from URLs. // See lib/remove-fpt-from-path.js for details. @@ -51,7 +72,7 @@ const plans = [ }, ] -const allVersions = {} +const allVersions: AllVersions = {} // combine the plans and releases to get allVersions object // e.g. free-pro-team@latest, enterprise-server@2.21, enterprise-server@2.20, etc. @@ -59,7 +80,7 @@ plans.forEach((planObj) => { planObj.releases.forEach((release) => { const version = `${planObj.plan}${versionDelimiter}${release}` - const versionObj = { + const versionObj: Version = { version, versionTitle: planObj.hasNumberedReleases ? `${planObj.planTitle} ${release}` @@ -74,33 +95,47 @@ plans.forEach((planObj) => { : planObj.miscBaseName, apiVersions: [], // REST Calendar Date Versions, this may be empty for non calendar date versioned products latestApiVersion: '', // Latest REST Calendar Date Version, this may be empty for non calendar date versioned products + plan: planObj.plan, + planTitle: planObj.planTitle, + shortName: planObj.shortName, + releases: planObj.releases, + latestRelease: planObj.latestRelease, + hasNumberedReleases: planObj.hasNumberedReleases, + openApiBaseName: planObj.openApiBaseName, + miscBaseName: planObj.miscBaseName, + ...(planObj.nonEnterpriseDefault && { nonEnterpriseDefault: planObj.nonEnterpriseDefault }), } - allVersions[version] = Object.assign(versionObj, planObj) + allVersions[version] = versionObj }) }) // Adds the calendar date (or api versions) to the allVersions object -const apiVersions = JSON.parse(fs.readFileSync(REST_DATA_META_FILE, 'utf8'))['api-versions'] +const apiVersions: RestApiConfig['api-versions'] = JSON.parse( + fs.readFileSync(REST_DATA_META_FILE, 'utf8'), +)['api-versions'] + Object.keys(apiVersions).forEach((key) => { const docsVersion = getDocsVersion(key) allVersions[docsVersion].apiVersions.push(...apiVersions[key].sort()) - allVersions[docsVersion].latestApiVersion = apiVersions[key].pop() + // Create a copy of the array to avoid mutating the original when using pop() + const sortedVersions = [...apiVersions[key].sort()] + allVersions[docsVersion].latestApiVersion = sortedVersions.pop() || '' }) -export const allVersionKeys = Object.keys(allVersions) -export const allVersionShortnames = Object.fromEntries( +export const allVersionKeys: string[] = Object.keys(allVersions) +export const allVersionShortnames: Record = Object.fromEntries( Object.values(allVersions).map((v) => [v.shortName, v.plan]), ) -export function isApiVersioned(version) { +export function isApiVersioned(version: string): boolean { return allVersions[version] && allVersions[version].apiVersions.length > 0 } // Currently the versions from the OpenAPI do not match the versions on Docs. // There is a mapping between the version names. This gets the Docs version from // the OpenAPI version name (the filename ) -export function getDocsVersion(openApiVersion) { +export function getDocsVersion(openApiVersion: string): string { const matchingVersion = Object.values(allVersions).find((version) => openApiVersion.startsWith(version.openApiVersionName), ) @@ -112,7 +147,7 @@ export function getDocsVersion(openApiVersion) { return matchingVersion.version } -export function getOpenApiVersion(version) { +export function getOpenApiVersion(version: string): string { if (!(version in allVersions)) { throw new Error(`Unrecognized version '${version}'. Not found in ${Object.keys(allVersions)}`) } diff --git a/src/versions/lib/get-applicable-versions.js b/src/versions/lib/get-applicable-versions.js index 11c09a781ef2..0163ab82b023 100644 --- a/src/versions/lib/get-applicable-versions.js +++ b/src/versions/lib/get-applicable-versions.js @@ -1,5 +1,5 @@ import { reduce, sortBy } from 'lodash-es' -import { allVersions } from './all-versions.js' +import { allVersions } from './all-versions.ts' import versionSatisfiesRange from './version-satisfies-range.js' import { next, nextNext } from './enterprise-server-releases.js' import { getDeepDataByLanguage } from '#src/data-directory/lib/get-data.js' diff --git a/src/versions/lib/non-enterprise-default-version.js b/src/versions/lib/non-enterprise-default-version.js index b3fc7e4954d9..571be4e9656f 100644 --- a/src/versions/lib/non-enterprise-default-version.js +++ b/src/versions/lib/non-enterprise-default-version.js @@ -1,4 +1,4 @@ -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' const nonEnterpriseDefaultVersion = Object.values(allVersions).find( (version) => version.nonEnterpriseDefault, ).version diff --git a/src/versions/scripts/use-short-versions.js b/src/versions/scripts/use-short-versions.js index c4612bcb11ad..92a0a19db88a 100755 --- a/src/versions/scripts/use-short-versions.js +++ b/src/versions/scripts/use-short-versions.js @@ -4,7 +4,7 @@ import path from 'path' import { escapeRegExp } from 'lodash-es' import { Tokenizer } from 'liquidjs' import frontmatter from '#src/frame/lib/read-frontmatter.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import { deprecated, oldestSupported } from '#src/versions/lib/enterprise-server-releases.js' const allVersionKeys = Object.values(allVersions) diff --git a/src/versions/tests/get-applicable-versions.js b/src/versions/tests/get-applicable-versions.js index 2e5659f2ab9e..1741fb650957 100644 --- a/src/versions/tests/get-applicable-versions.js +++ b/src/versions/tests/get-applicable-versions.js @@ -3,7 +3,7 @@ import path from 'path' import { describe, expect, test } from 'vitest' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import getApplicableVersions from '#src/versions/lib/get-applicable-versions.js' import { latest } from '#src/versions/lib/enterprise-server-releases.js' diff --git a/src/versions/tests/ghes-versioning.js b/src/versions/tests/ghes-versioning.js index 47eea2a6c27c..2d737f77e6a8 100644 --- a/src/versions/tests/ghes-versioning.js +++ b/src/versions/tests/ghes-versioning.js @@ -1,6 +1,6 @@ import { beforeAll, describe, expect, test } from 'vitest' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import { liquid } from '#src/content-render/index.js' import { supported } from '#src/versions/lib/enterprise-server-releases.js' import shortVersionsMiddleware from '#src/versions/middleware/short-versions' diff --git a/src/versions/tests/versions.js b/src/versions/tests/versions.js index 7d879ded4634..a904eae27de0 100644 --- a/src/versions/tests/versions.js +++ b/src/versions/tests/versions.js @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest' import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' -import { allVersions } from '#src/versions/lib/all-versions.js' +import { allVersions } from '#src/versions/lib/all-versions.ts' import { latest } from '#src/versions/lib/enterprise-server-releases.js' import schema from '#src/tests/helpers/schemas/versions-schema.js' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' diff --git a/src/webhooks/lib/index.ts b/src/webhooks/lib/index.ts index 89e219628a9c..a77f1f4b22ef 100644 --- a/src/webhooks/lib/index.ts +++ b/src/webhooks/lib/index.ts @@ -1,6 +1,6 @@ import path from 'path' -import { getOpenApiVersion } from '@/versions/lib/all-versions.js' +import { getOpenApiVersion } from '@/versions/lib/all-versions' import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file.js' export const WEBHOOK_DATA_DIR = 'src/webhooks/data' diff --git a/src/webhooks/middleware/webhooks.ts b/src/webhooks/middleware/webhooks.ts index 84ce38b3708d..49924089d38c 100644 --- a/src/webhooks/middleware/webhooks.ts +++ b/src/webhooks/middleware/webhooks.ts @@ -1,6 +1,6 @@ import express from 'express' import { getWebhook } from '../lib/index.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import { defaultCacheControl } from '@/frame/middleware/cache-control.js' const router = express.Router() diff --git a/src/webhooks/tests/get-schema-files.ts b/src/webhooks/tests/get-schema-files.ts index 117f53a2d2eb..80a2cceafc51 100644 --- a/src/webhooks/tests/get-schema-files.ts +++ b/src/webhooks/tests/get-schema-files.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest' import { getOpenApiSchemaFiles } from '../../rest/scripts/utils/sync' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' describe('webhook data files are generated correctly from dereferenced openapi files', () => { test('webhook schema list should not include calendar date versions', async () => { diff --git a/src/webhooks/tests/rendering.ts b/src/webhooks/tests/rendering.ts index 1a7fd506cb31..5cdafc5164cb 100644 --- a/src/webhooks/tests/rendering.ts +++ b/src/webhooks/tests/rendering.ts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from 'vitest' import { getDOM } from '@/tests/helpers/e2etest.js' -import { allVersions } from '@/versions/lib/all-versions.js' +import { allVersions } from '@/versions/lib/all-versions' import { getWebhooks } from '../lib/index.js' describe('webhooks events and payloads', () => { diff --git a/src/workflows/content-changes-table-comment.ts b/src/workflows/content-changes-table-comment.ts index 4b95e53a492a..03d27562c514 100755 --- a/src/workflows/content-changes-table-comment.ts +++ b/src/workflows/content-changes-table-comment.ts @@ -20,7 +20,7 @@ import { retry } from '@octokit/plugin-retry' import { getContents } from './git-utils.js' import getApplicableVersions from '@/versions/lib/get-applicable-versions.js' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version.js' -import { allVersionShortnames } from '@/versions/lib/all-versions.js' +import { allVersionShortnames } from '@/versions/lib/all-versions' import readFrontmatter from '@/frame/lib/read-frontmatter.js' import { inLiquid } from './lib/in-liquid' From e6d1d298505981059d5a38deeb18051bb1c6c7a1 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 9 Jul 2025 12:17:13 -0700 Subject: [PATCH 6/7] Convert languages.js to TypeScript (#56393) --- next.config.js | 4 +- .../archived-enterprise-versions.ts | 2 +- .../scripts/precompute-pageinfo.ts | 2 +- .../scripts/convert-markdown-for-docs.js | 2 +- .../linting-rules/internal-links-no-lang.js | 2 +- src/content-linter/scripts/lint-content.js | 2 +- src/content-linter/tests/lint-files.js | 2 +- .../scripts/all-documents/cli.ts | 2 +- src/content-render/tests/data.js | 2 +- src/content-render/tests/liquid-helpers.js | 2 +- src/data-directory/lib/get-data.js | 2 +- .../scripts/find-orphaned-features/delete.ts | 2 +- .../scripts/find-orphaned-features/find.ts | 2 +- src/data-directory/tests/get-data.js | 2 +- src/early-access/tests/early-access-unit.ts | 2 +- src/events/lib/schema.ts | 2 +- src/frame/lib/page-data.js | 2 +- src/frame/middleware/context/context.ts | 2 +- .../middleware/context/product-groups.ts | 2 +- src/frame/middleware/find-page.js | 2 +- src/frame/middleware/helmet.ts | 2 +- src/frame/middleware/llms-txt.ts | 2 +- src/frame/middleware/reload-tree.ts | 2 +- src/frame/middleware/render-page.ts | 2 +- src/frame/tests/pages.js | 2 +- .../scripts/deprecate/archive-version.ts | 2 +- src/graphql/lib/index.js | 2 +- src/languages/lib/languages.d.ts | 23 --------- .../lib/{languages.js => languages.ts} | 47 ++++++++++++++----- src/languages/middleware/detect-language.ts | 2 +- .../scripts/count-translation-corruptions.ts | 2 +- .../purge-fastly-edge-cache-per-language.js | 2 +- src/languages/tests/files.js | 2 +- src/languages/tests/frame.ts | 2 +- src/languages/tests/glossary.ts | 2 +- src/languages/tests/llms-txt-translations.ts | 2 +- src/languages/tests/redirects.js | 2 +- src/languages/tests/search.js | 2 +- src/redirects/lib/get-redirect.ts | 2 +- src/redirects/middleware/handle-redirects.ts | 2 +- .../middleware/language-code-redirects.ts | 2 +- src/rest/lib/index.js | 2 +- src/search/lib/elasticsearch-indexes.ts | 2 +- .../search-params-objects.ts | 4 +- src/search/scripts/analyze-text.ts | 2 +- src/search/scripts/index/index-cli.ts | 2 +- .../scripts/index/lib/index-general-search.ts | 2 +- .../scripts/scrape/lib/build-records.ts | 2 +- .../scrape/lib/scrape-into-index-json.ts | 2 +- 49 files changed, 86 insertions(+), 82 deletions(-) delete mode 100644 src/languages/lib/languages.d.ts rename src/languages/lib/{languages.js => languages.ts} (78%) diff --git a/next.config.js b/next.config.js index 00ad1ad9da5c..2972b2fcd1af 100644 --- a/next.config.js +++ b/next.config.js @@ -2,9 +2,11 @@ import fs from 'fs' import path from 'path' import frontmatter from 'gray-matter' -import { languageKeys } from '#src/languages/lib/languages.js' import { ROOT } from '#src/frame/lib/constants.js' +// Hard-coded language keys to avoid TypeScript import in config file +const languageKeys = ['en', 'es', 'ja', 'pt', 'zh', 'ru', 'fr', 'ko', 'de'] + const homepage = path.posix.join(ROOT, 'content/index.md') const { data } = frontmatter(fs.readFileSync(homepage, 'utf8')) const productIds = data.children diff --git a/src/archives/middleware/archived-enterprise-versions.ts b/src/archives/middleware/archived-enterprise-versions.ts index 3fd087d9263d..452a48d8d2a5 100644 --- a/src/archives/middleware/archived-enterprise-versions.ts +++ b/src/archives/middleware/archived-enterprise-versions.ts @@ -17,7 +17,7 @@ import { } from '@/frame/middleware/set-fastly-surrogate-key.js' import { readCompressedJsonFileFallbackLazily } from '@/frame/lib/read-json-file.js' import { archivedCacheControl, languageCacheControl } from '@/frame/middleware/cache-control.js' -import { pathLanguagePrefixed, languagePrefixPathRegex } from '@/languages/lib/languages.js' +import { pathLanguagePrefixed, languagePrefixPathRegex } from '@/languages/lib/languages' import getRedirect, { splitPathByLanguage } from '@/redirects/lib/get-redirect.js' import getRemoteJSON from '@/frame/lib/get-remote-json.js' import { ExtendedRequest } from '@/types' diff --git a/src/article-api/scripts/precompute-pageinfo.ts b/src/article-api/scripts/precompute-pageinfo.ts index 0aeb84753a68..de546cb2a912 100644 --- a/src/article-api/scripts/precompute-pageinfo.ts +++ b/src/article-api/scripts/precompute-pageinfo.ts @@ -30,7 +30,7 @@ import { brotliCompressSync } from 'zlib' import chalk from 'chalk' import { program, Option } from 'commander' -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import { loadPages, loadUnversionedTree } from '@/frame/lib/page-data.js' import { CACHE_FILE_PATH, getPageInfo } from '../middleware/article-pageinfo' diff --git a/src/codeql-cli/scripts/convert-markdown-for-docs.js b/src/codeql-cli/scripts/convert-markdown-for-docs.js index 10d3dc74ca4c..0513b547d0b0 100644 --- a/src/codeql-cli/scripts/convert-markdown-for-docs.js +++ b/src/codeql-cli/scripts/convert-markdown-for-docs.js @@ -7,7 +7,7 @@ import { visitParents } from 'unist-util-visit-parents' import { visit, SKIP } from 'unist-util-visit' import { remove } from 'unist-util-remove' -import { languageKeys } from '#src/languages/lib/languages.js' +import { languageKeys } from '#src/languages/lib/languages.ts' import { MARKDOWN_OPTIONS } from '../../content-linter/lib/helpers/unified-formatter-options.js' const { targetDirectory, removeKeywords } = JSON.parse( diff --git a/src/content-linter/lib/linting-rules/internal-links-no-lang.js b/src/content-linter/lib/linting-rules/internal-links-no-lang.js index c378d90d6ea4..338e654fb11a 100644 --- a/src/content-linter/lib/linting-rules/internal-links-no-lang.js +++ b/src/content-linter/lib/linting-rules/internal-links-no-lang.js @@ -1,7 +1,7 @@ import { filterTokens } from 'markdownlint-rule-helpers' import { addFixErrorDetail, getRange } from '../helpers/utils.js' -import { allLanguageKeys } from '#src/languages/lib/languages.js' +import { allLanguageKeys } from '#src/languages/lib/languages.ts' export const internalLinksNoLang = { names: ['GHD002', 'internal-links-no-lang'], diff --git a/src/content-linter/scripts/lint-content.js b/src/content-linter/scripts/lint-content.js index ec687da35c09..b25c87fdc531 100755 --- a/src/content-linter/scripts/lint-content.js +++ b/src/content-linter/scripts/lint-content.js @@ -14,7 +14,7 @@ import { defaultConfig } from '../lib/default-markdownlint-options.js' import { prettyPrintResults } from './pretty-print-results.js' import { getLintableYml } from '#src/content-linter/lib/helpers/get-lintable-yml.js' import { printAnnotationResults } from '../lib/helpers/print-annotations.js' -import languages from '#src/languages/lib/languages.js' +import languages from '#src/languages/lib/languages.ts' program .description('Run GitHub Docs Markdownlint rules.') diff --git a/src/content-linter/tests/lint-files.js b/src/content-linter/tests/lint-files.js index 18992c036b9a..ae5daec88d73 100755 --- a/src/content-linter/tests/lint-files.js +++ b/src/content-linter/tests/lint-files.js @@ -8,7 +8,7 @@ import walk from 'walk-sync' import { zip } from 'lodash-es' import { beforeAll, describe, expect, test } from 'vitest' -import languages from '#src/languages/lib/languages.js' +import languages from '#src/languages/lib/languages.ts' import { getDiffFiles } from '../lib/diff-files.js' const __dirname = path.dirname(fileURLToPath(import.meta.url)) diff --git a/src/content-render/scripts/all-documents/cli.ts b/src/content-render/scripts/all-documents/cli.ts index 3a98d7bb4a0d..db32f513bc77 100644 --- a/src/content-render/scripts/all-documents/cli.ts +++ b/src/content-render/scripts/all-documents/cli.ts @@ -43,7 +43,7 @@ import { writeFileSync, statSync } from 'fs' import { program, Option } from 'commander' -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import { allVersions } from '@/versions/lib/all-versions' import { allDocuments, POSSIBLE_FIELDS, type AllDocument } from './lib' diff --git a/src/content-render/tests/data.js b/src/content-render/tests/data.js index 61922e69526c..6563c8e2db07 100644 --- a/src/content-render/tests/data.js +++ b/src/content-render/tests/data.js @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from 'vitest' import Page from '#src/frame/lib/page.js' -import languages from '#src/languages/lib/languages.js' +import languages from '#src/languages/lib/languages.ts' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' import { DataDirectory } from '#src/tests/helpers/data-directory.js' diff --git a/src/content-render/tests/liquid-helpers.js b/src/content-render/tests/liquid-helpers.js index 116edbc54671..0da692d3b6e7 100644 --- a/src/content-render/tests/liquid-helpers.js +++ b/src/content-render/tests/liquid-helpers.js @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest' import { liquid } from '#src/content-render/index.js' -import languages from '#src/languages/lib/languages.js' +import languages from '#src/languages/lib/languages.ts' import { DataDirectory } from '#src/tests/helpers/data-directory.js' describe('liquid helper tags', () => { diff --git a/src/data-directory/lib/get-data.js b/src/data-directory/lib/get-data.js index 6b1ada916eb7..564013432c9e 100644 --- a/src/data-directory/lib/get-data.js +++ b/src/data-directory/lib/get-data.js @@ -5,7 +5,7 @@ import yaml from 'js-yaml' import matter from 'gray-matter' import { merge, get } from 'lodash-es' -import languages from '#src/languages/lib/languages.js' +import languages from '#src/languages/lib/languages.ts' import { correctTranslatedContentStrings } from '#src/languages/lib/correct-translation-content.js' // If you run `export DEBUG_JIT_DATA_READS=true` in your terminal, diff --git a/src/data-directory/scripts/find-orphaned-features/delete.ts b/src/data-directory/scripts/find-orphaned-features/delete.ts index 2d4b783f1dc8..fe2d8964f62a 100644 --- a/src/data-directory/scripts/find-orphaned-features/delete.ts +++ b/src/data-directory/scripts/find-orphaned-features/delete.ts @@ -2,7 +2,7 @@ import fs from 'fs' import path from 'path' import chalk from 'chalk' -import languages from '@/languages/lib/languages.js' +import languages from '@/languages/lib/languages' type Options = { verbose?: boolean diff --git a/src/data-directory/scripts/find-orphaned-features/find.ts b/src/data-directory/scripts/find-orphaned-features/find.ts index 136227e61d4f..9aa5a3d5fec0 100644 --- a/src/data-directory/scripts/find-orphaned-features/find.ts +++ b/src/data-directory/scripts/find-orphaned-features/find.ts @@ -38,7 +38,7 @@ import type { Page } from '@/types' import warmServer from '@/frame/lib/warm-server' import { getDeepDataByLanguage } from '@/data-directory/lib/get-data.js' import { getLiquidTokens } from '@/content-linter/lib/helpers/liquid-utils.js' -import languages from '@/languages/lib/languages.js' +import languages from '@/languages/lib/languages' import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content.js' const EXCEPTIONS = new Set([ diff --git a/src/data-directory/tests/get-data.js b/src/data-directory/tests/get-data.js index bc2191bc779c..673f4395ed70 100644 --- a/src/data-directory/tests/get-data.js +++ b/src/data-directory/tests/get-data.js @@ -3,7 +3,7 @@ import path from 'path' import { afterAll, beforeAll, describe, expect, test } from 'vitest' -import languages from '#src/languages/lib/languages.js' +import languages from '#src/languages/lib/languages.ts' import { getDataByLanguage, getDeepDataByLanguage, diff --git a/src/early-access/tests/early-access-unit.ts b/src/early-access/tests/early-access-unit.ts index 3cfcaa4cbb24..6c38783772ad 100644 --- a/src/early-access/tests/early-access-unit.ts +++ b/src/early-access/tests/early-access-unit.ts @@ -2,7 +2,7 @@ import { expect, test, vi } from 'vitest' import { get, getDOM } from '@/tests/helpers/e2etest.js' import { describeIfDocsEarlyAccess } from '@/tests/helpers/conditional-runs.js' -import languages from '@/languages/lib/languages.js' +import languages from '@/languages/lib/languages' const VALID_EARLY_ACCESS_URI = '/early-access/github/save-time-with-slash-commands' diff --git a/src/events/lib/schema.ts b/src/events/lib/schema.ts index 089110eb77a2..29c90c46fbf8 100644 --- a/src/events/lib/schema.ts +++ b/src/events/lib/schema.ts @@ -1,4 +1,4 @@ -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import { allVersionKeys } from '@/versions/lib/all-versions' import { productIds } from '@/products/lib/all-products' import { allTools } from '@/tools/lib/all-tools.js' diff --git a/src/frame/lib/page-data.js b/src/frame/lib/page-data.js index d9a4a1c8d1f4..92105fea8ba9 100644 --- a/src/frame/lib/page-data.js +++ b/src/frame/lib/page-data.js @@ -1,6 +1,6 @@ import path from 'path' -import languages from '#src/languages/lib/languages.js' +import languages from '#src/languages/lib/languages.ts' import { allVersions } from '#src/versions/lib/all-versions.ts' import createTree from './create-tree.js' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' diff --git a/src/frame/middleware/context/context.ts b/src/frame/middleware/context/context.ts index ea517bee8ed7..6c2129d20148 100644 --- a/src/frame/middleware/context/context.ts +++ b/src/frame/middleware/context/context.ts @@ -2,7 +2,7 @@ import type { NextFunction, Response } from 'express' import type { ExtendedRequest, Context } from '@/types' -import languages from '@/languages/lib/languages.js' +import languages from '@/languages/lib/languages' import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases.js' import { allVersions } from '@/versions/lib/all-versions' import { productMap } from '@/products/lib/all-products.js' diff --git a/src/frame/middleware/context/product-groups.ts b/src/frame/middleware/context/product-groups.ts index 6c5a5f3e70c6..0c88acfc3474 100644 --- a/src/frame/middleware/context/product-groups.ts +++ b/src/frame/middleware/context/product-groups.ts @@ -3,7 +3,7 @@ import type { Response, NextFunction } from 'express' import type { ExtendedRequest } from '@/types' import { getProductGroups } from '@/products/lib/get-product-groups' import warmServer from '@/frame/lib/warm-server' -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import { allVersionKeys } from '@/versions/lib/all-versions' const isHomepage = (path: string) => { diff --git a/src/frame/middleware/find-page.js b/src/frame/middleware/find-page.js index ba05aecc650d..b4726c32eb21 100644 --- a/src/frame/middleware/find-page.js +++ b/src/frame/middleware/find-page.js @@ -3,7 +3,7 @@ import { existsSync } from 'fs' import { ROOT } from '#src/frame/lib/constants.js' import Page from '#src/frame/lib/page.js' -import { languagePrefixPathRegex } from '#src/languages/lib/languages.js' +import { languagePrefixPathRegex } from '#src/languages/lib/languages.ts' const englishPrefixRegex = /^\/en(\/|$)/ const CONTENT_ROOT = path.join(ROOT, 'content') diff --git a/src/frame/middleware/helmet.ts b/src/frame/middleware/helmet.ts index f23d3108b3c7..7f8d4e2ba679 100644 --- a/src/frame/middleware/helmet.ts +++ b/src/frame/middleware/helmet.ts @@ -2,7 +2,7 @@ import type { NextFunction, Request, Response } from 'express' import helmet from 'helmet' import { isArchivedVersion } from '@/archives/lib/is-archived-version' import versionSatisfiesRange from '@/versions/lib/version-satisfies-range.js' -import { languagePrefixPathRegex } from '@/languages/lib/languages.js' +import { languagePrefixPathRegex } from '@/languages/lib/languages' const isDev = process.env.NODE_ENV === 'development' const GITHUB_DOMAINS = [ diff --git a/src/frame/middleware/llms-txt.ts b/src/frame/middleware/llms-txt.ts index 75166f96e140..38b095bc740d 100644 --- a/src/frame/middleware/llms-txt.ts +++ b/src/frame/middleware/llms-txt.ts @@ -5,7 +5,7 @@ import type { ExtendedRequest } from '@/types' import { defaultCacheControl } from '@/frame/middleware/cache-control.js' import catchMiddlewareError from '@/observability/middleware/catch-middleware-error.js' import statsd from '@/observability/lib/statsd.js' -import languages from '@/languages/lib/languages.js' +import languages from '@/languages/lib/languages' import { allVersions } from '@/versions/lib/all-versions' const router = express.Router() diff --git a/src/frame/middleware/reload-tree.ts b/src/frame/middleware/reload-tree.ts index e47564ff076c..abb7f36147e7 100644 --- a/src/frame/middleware/reload-tree.ts +++ b/src/frame/middleware/reload-tree.ts @@ -19,7 +19,7 @@ import path from 'path' import type { Response, NextFunction } from 'express' import type { ExtendedRequest, UnversionedTree, SiteTree } from '@/types' -import languages, { languageKeys } from '@/languages/lib/languages.js' +import languages, { languageKeys } from '@/languages/lib/languages' import createTree from '@/frame/lib/create-tree.js' import warmServer from '@/frame/lib/warm-server' import { loadSiteTree, loadPages, loadPageMap } from '@/frame/lib/page-data.js' diff --git a/src/frame/middleware/render-page.ts b/src/frame/middleware/render-page.ts index 4b4816c594ac..25226e9f5ca0 100644 --- a/src/frame/middleware/render-page.ts +++ b/src/frame/middleware/render-page.ts @@ -8,7 +8,7 @@ import type { ExtendedRequest } from '@/types' import FailBot from '@/observability/lib/failbot.js' import patterns from '@/frame/lib/patterns.js' import getMiniTocItems from '@/frame/lib/get-mini-toc-items.js' -import { pathLanguagePrefixed } from '@/languages/lib/languages.js' +import { pathLanguagePrefixed } from '@/languages/lib/languages' import statsd from '@/observability/lib/statsd.js' import { allVersions } from '@/versions/lib/all-versions' import { isConnectionDropped } from './halt-on-dropped-connection' diff --git a/src/frame/tests/pages.js b/src/frame/tests/pages.js index 03947cf4f60a..d346f1c7c513 100644 --- a/src/frame/tests/pages.js +++ b/src/frame/tests/pages.js @@ -6,7 +6,7 @@ import { decode } from 'html-entities' import { chain, pick } from 'lodash-es' import { loadPages } from '#src/frame/lib/page-data.js' -import libLanguages from '#src/languages/lib/languages.js' +import libLanguages from '#src/languages/lib/languages.ts' import { liquid } from '#src/content-render/index.js' import patterns from '#src/frame/lib/patterns.js' import removeFPTFromPath from '#src/versions/lib/remove-fpt-from-path.js' diff --git a/src/ghes-releases/scripts/deprecate/archive-version.ts b/src/ghes-releases/scripts/deprecate/archive-version.ts index 3a111ea12176..cf756816afa3 100755 --- a/src/ghes-releases/scripts/deprecate/archive-version.ts +++ b/src/ghes-releases/scripts/deprecate/archive-version.ts @@ -17,7 +17,7 @@ import createApp from '@/frame/lib/app' import EnterpriseServerReleases from '@/versions/lib/enterprise-server-releases.js' import loadRedirects from '@/redirects/lib/precompile.js' import { loadPageMap, loadPages } from '@/frame/lib/page-data.js' -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import { RewriteAssetPathsPlugin } from '@/ghes-releases/scripts/deprecate/rewrite-asset-paths' const port = '4001' diff --git a/src/graphql/lib/index.js b/src/graphql/lib/index.js index 44168b24235b..c1fcb22c9db6 100644 --- a/src/graphql/lib/index.js +++ b/src/graphql/lib/index.js @@ -3,7 +3,7 @@ import { readCompressedJsonFileFallback, } from '#src/frame/lib/read-json-file.js' import { getAutomatedPageMiniTocItems } from '#src/frame/lib/get-mini-toc-items.js' -import languages from '#src/languages/lib/languages.js' +import languages from '#src/languages/lib/languages.ts' import { allVersions } from '#src/versions/lib/all-versions.ts' export const GRAPHQL_DATA_DIR = 'src/graphql/data' diff --git a/src/languages/lib/languages.d.ts b/src/languages/lib/languages.d.ts deleted file mode 100644 index 6129a4ac88b2..000000000000 --- a/src/languages/lib/languages.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -type Language = { - name: string - nativeName?: string - code: string - hreflang: string - redirectPatterns?: RegExp[] - dir: string -} -type Languages = { - [code: string]: Language -} - -export const allLanguageKeys: string[] - -export const languageKeys: string[] - -export const languagePrefixPathRegex: RegExp - -export declare function pathLanguagePrefixed(path: string): boolean - -const languages: Languages - -export default languages diff --git a/src/languages/lib/languages.js b/src/languages/lib/languages.ts similarity index 78% rename from src/languages/lib/languages.js rename to src/languages/lib/languages.ts index fda143c60151..8a86dd277ae4 100644 --- a/src/languages/lib/languages.js +++ b/src/languages/lib/languages.ts @@ -6,11 +6,35 @@ import fs from 'fs' import dotenv from 'dotenv' -import { ROOT, TRANSLATIONS_ROOT, TRANSLATIONS_FIXTURE_ROOT } from '#src/frame/lib/constants.js' +import { ROOT, TRANSLATIONS_ROOT, TRANSLATIONS_FIXTURE_ROOT } from '@/frame/lib/constants' dotenv.config({ quiet: true }) -const possibleEnvVars = { +export interface Language { + name: string + nativeName?: string + code: string + hreflang: string + redirectPatterns?: RegExp[] + dir: string +} + +export type LanguageCode = 'en' | 'es' | 'ja' | 'pt' | 'zh' | 'ru' | 'fr' | 'ko' | 'de' +export type LocaleCode = + | 'es-es' + | 'ja-jp' + | 'pt-br' + | 'zh-cn' + | 'ru-ru' + | 'fr-fr' + | 'ko-kr' + | 'de-de' + +export interface Languages { + [code: string]: Language +} + +const possibleEnvVars: Record = { 'es-es': process.env.TRANSLATIONS_ROOT_ES_ES, 'ja-jp': process.env.TRANSLATIONS_ROOT_JA_JP, 'pt-br': process.env.TRANSLATIONS_ROOT_PT_BR, @@ -21,7 +45,7 @@ const possibleEnvVars = { 'de-de': process.env.TRANSLATIONS_ROOT_DE_DE, } -function getRoot(languageCode) { +function getRoot(languageCode: string): string { if (languageCode === 'en') return ROOT // This one trumps anything else. This makes it possible, and convenient, @@ -32,7 +56,7 @@ function getRoot(languageCode) { } if (languageCode in possibleEnvVars) { - const possibleEnvVar = possibleEnvVars[languageCode] + const possibleEnvVar = possibleEnvVars[languageCode as LocaleCode] if (possibleEnvVar) { return possibleEnvVar } @@ -44,7 +68,7 @@ function getRoot(languageCode) { } // Languages in order of accept-language header frequency -const allLanguages = { +const allLanguages: Languages = { en: { name: 'English', code: 'en', @@ -112,13 +136,14 @@ const allLanguages = { dir: getRoot('de-de'), }, } + // Some markdownlint tests depend on having access to all // language keys. Not modifying the original object makes // it possible to export all keys, even when those directories // don't exist on disk. Object.freeze(allLanguages) -export const allLanguageKeys = Object.keys(allLanguages) -const languages = { ...allLanguages } +export const allLanguageKeys: string[] = Object.keys(allLanguages) +const languages: Languages = { ...allLanguages } if (TRANSLATIONS_FIXTURE_ROOT) { // Keep all languages that have a directory in the fixture root. @@ -130,7 +155,7 @@ if (TRANSLATIONS_FIXTURE_ROOT) { } else if (process.env.ENABLED_LANGUAGES) { if (process.env.ENABLED_LANGUAGES.toLowerCase() !== 'all') { Object.keys(languages).forEach((code) => { - if (!process.env.ENABLED_LANGUAGES.includes(code)) { + if (!process.env.ENABLED_LANGUAGES!.includes(code)) { delete languages[code] } }) @@ -144,15 +169,15 @@ if (TRANSLATIONS_FIXTURE_ROOT) { }) } -export const languageKeys = Object.keys(languages) +export const languageKeys: string[] = Object.keys(languages) -export const languagePrefixPathRegex = new RegExp(`^/(${languageKeys.join('|')})(/|$)`) +export const languagePrefixPathRegex: RegExp = new RegExp(`^/(${languageKeys.join('|')})(/|$)`) /** Return true if the URL is something like /en/foo or /ja but return false * if it's something like /foo or /foo/bar or /fr (because French (fr) * is currently not an active language) */ -export function pathLanguagePrefixed(path) { +export function pathLanguagePrefixed(path: string): boolean { return languagePrefixPathRegex.test(path) } diff --git a/src/languages/middleware/detect-language.ts b/src/languages/middleware/detect-language.ts index 17134caf69fd..5efa03ca504a 100644 --- a/src/languages/middleware/detect-language.ts +++ b/src/languages/middleware/detect-language.ts @@ -2,7 +2,7 @@ import type { Request, Response, NextFunction } from 'express' import parser from 'accept-language-parser' import type { Language as parserLanguage } from 'accept-language-parser' -import languages, { languageKeys } from '@/languages/lib/languages.js' +import languages, { languageKeys } from '@/languages/lib/languages' import { USER_LANGUAGE_COOKIE_NAME } from '@/frame/lib/constants.js' import type { ExtendedRequest, Languages } from '@/types' diff --git a/src/languages/scripts/count-translation-corruptions.ts b/src/languages/scripts/count-translation-corruptions.ts index 69bd66ee1263..9029e7d6ffc8 100644 --- a/src/languages/scripts/count-translation-corruptions.ts +++ b/src/languages/scripts/count-translation-corruptions.ts @@ -7,7 +7,7 @@ import { TokenizationError } from 'liquidjs' import walk from 'walk-sync' import { getLiquidTokens } from '@/content-linter/lib/helpers/liquid-utils.js' -import languages from '@/languages/lib/languages.js' +import languages from '@/languages/lib/languages' import warmServer from '@/frame/lib/warm-server' import type { Site } from '@/types' import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content.js' diff --git a/src/languages/scripts/purge-fastly-edge-cache-per-language.js b/src/languages/scripts/purge-fastly-edge-cache-per-language.js index e7cac75f068a..0ca40f617856 100755 --- a/src/languages/scripts/purge-fastly-edge-cache-per-language.js +++ b/src/languages/scripts/purge-fastly-edge-cache-per-language.js @@ -1,4 +1,4 @@ -import { languageKeys } from '#src/languages/lib/languages.js' +import { languageKeys } from '#src/languages/lib/languages.ts' import { makeLanguageSurrogateKey } from '#src/frame/middleware/set-fastly-surrogate-key.js' import purgeEdgeCache from '#src/workflows/purge-edge-cache.ts' diff --git a/src/languages/tests/files.js b/src/languages/tests/files.js index 3b83f6fb14f8..a9daf351b654 100644 --- a/src/languages/tests/files.js +++ b/src/languages/tests/files.js @@ -1,4 +1,4 @@ -import languages from '#src/languages/lib/languages.js' +import languages from '#src/languages/lib/languages.ts' import { describe, expect, test, vi } from 'vitest' describe('files', () => { diff --git a/src/languages/tests/frame.ts b/src/languages/tests/frame.ts index e00c61fffd0b..9244ba1c8f52 100644 --- a/src/languages/tests/frame.ts +++ b/src/languages/tests/frame.ts @@ -1,6 +1,6 @@ import { describe, expect, test, vi } from 'vitest' -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import { blockIndex } from '@/frame/middleware/block-robots' import { get, getDOMCached as getDOM } from '@/tests/helpers/e2etest.js' import Page from '@/frame/lib/page.js' diff --git a/src/languages/tests/glossary.ts b/src/languages/tests/glossary.ts index 14c9a83f735f..9a23826ccf3c 100644 --- a/src/languages/tests/glossary.ts +++ b/src/languages/tests/glossary.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import { getDOM } from '@/tests/helpers/e2etest.js' const langs = languageKeys.filter((lang) => lang !== 'en') diff --git a/src/languages/tests/llms-txt-translations.ts b/src/languages/tests/llms-txt-translations.ts index f089d70b7927..f9f0484694d7 100644 --- a/src/languages/tests/llms-txt-translations.ts +++ b/src/languages/tests/llms-txt-translations.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' import { get } from '@/tests/helpers/e2etest.js' -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' const langs = languageKeys.filter((lang) => lang !== 'en') diff --git a/src/languages/tests/redirects.js b/src/languages/tests/redirects.js index fb185d033d49..865792736b89 100644 --- a/src/languages/tests/redirects.js +++ b/src/languages/tests/redirects.js @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' -import { languageKeys } from '#src/languages/lib/languages.js' +import { languageKeys } from '#src/languages/lib/languages.ts' import { get } from '#src/tests/helpers/e2etest.js' import { USER_LANGUAGE_COOKIE_NAME } from '#src/frame/lib/constants.js' diff --git a/src/languages/tests/search.js b/src/languages/tests/search.js index abde6f9107a4..ee0bedb6998d 100644 --- a/src/languages/tests/search.js +++ b/src/languages/tests/search.js @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest' -import { languageKeys } from '#src/languages/lib/languages' +import { languageKeys } from '#src/languages/lib/languages.ts' import { get } from '#src/tests/helpers/e2etest.js' const langs = languageKeys.filter((lang) => lang !== 'en') diff --git a/src/redirects/lib/get-redirect.ts b/src/redirects/lib/get-redirect.ts index d6ce89da011e..f09e2345055c 100644 --- a/src/redirects/lib/get-redirect.ts +++ b/src/redirects/lib/get-redirect.ts @@ -1,4 +1,4 @@ -import { languageKeys } from '#src/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' import { allVersions } from '@/versions/lib/all-versions' import { diff --git a/src/redirects/middleware/handle-redirects.ts b/src/redirects/middleware/handle-redirects.ts index 2dc421e36362..d6109112ba71 100644 --- a/src/redirects/middleware/handle-redirects.ts +++ b/src/redirects/middleware/handle-redirects.ts @@ -1,7 +1,7 @@ import type { NextFunction, Response } from 'express' import patterns from '@/frame/lib/patterns.js' -import { pathLanguagePrefixed } from '@/languages/lib/languages.js' +import { pathLanguagePrefixed } from '@/languages/lib/languages' import { deprecatedWithFunctionalRedirects } from '@/versions/lib/enterprise-server-releases.js' import getRedirect from '../lib/get-redirect.js' import { defaultCacheControl, languageCacheControl } from '@/frame/middleware/cache-control.js' diff --git a/src/redirects/middleware/language-code-redirects.ts b/src/redirects/middleware/language-code-redirects.ts index 1b64ef769855..793f8288a094 100644 --- a/src/redirects/middleware/language-code-redirects.ts +++ b/src/redirects/middleware/language-code-redirects.ts @@ -1,6 +1,6 @@ import type { NextFunction, Response } from 'express' -import languages from '@/languages/lib/languages.js' +import languages from '@/languages/lib/languages' import { defaultCacheControl } from '@/frame/middleware/cache-control.js' import { ExtendedRequest } from '@/types' diff --git a/src/rest/lib/index.js b/src/rest/lib/index.js index 32ddb6e5d39a..adb4216c0223 100644 --- a/src/rest/lib/index.js +++ b/src/rest/lib/index.js @@ -4,7 +4,7 @@ import path from 'path' import { readCompressedJsonFileFallback } from '#src/frame/lib/read-json-file.js' import { getAutomatedPageMiniTocItems } from '#src/frame/lib/get-mini-toc-items.js' import { allVersions, getOpenApiVersion } from '#src/versions/lib/all-versions.ts' -import languages from '#src/languages/lib/languages.js' +import languages from '#src/languages/lib/languages.ts' export const REST_DATA_DIR = 'src/rest/data' export const REST_SCHEMA_FILENAME = 'schema.json' diff --git a/src/search/lib/elasticsearch-indexes.ts b/src/search/lib/elasticsearch-indexes.ts index 08f3ba4c5286..a8a5ae43598b 100644 --- a/src/search/lib/elasticsearch-indexes.ts +++ b/src/search/lib/elasticsearch-indexes.ts @@ -1,4 +1,4 @@ -import languages from '@/languages/lib/languages.js' +import languages from '@/languages/lib/languages' import { utcTimestamp } from '@/search/lib/helpers/time' import { allIndexVersionKeys, versionToIndexVersionMap } from '@/search/lib/elasticsearch-versions' diff --git a/src/search/lib/search-request-params/search-params-objects.ts b/src/search/lib/search-request-params/search-params-objects.ts index 245fcc09661d..1532e4276fd3 100644 --- a/src/search/lib/search-request-params/search-params-objects.ts +++ b/src/search/lib/search-request-params/search-params-objects.ts @@ -1,6 +1,6 @@ -/* +/* When a request is made to a /search endpoint with query parameters, e.g. ?query=foo&version=free-pro-team, - we need to validate and parse the parameters. This file contains the configuration for which parameters + we need to validate and parse the parameters. This file contains the configuration for which parameters to expect based on the type of search request "e.g. general search vs autocomplete search" and how to validate them. */ import languages from '@/languages/lib/languages' diff --git a/src/search/scripts/analyze-text.ts b/src/search/scripts/analyze-text.ts index a042232e9016..c8025da60aeb 100755 --- a/src/search/scripts/analyze-text.ts +++ b/src/search/scripts/analyze-text.ts @@ -10,7 +10,7 @@ import { Command, Option } from 'commander' import chalk from 'chalk' import dotenv from 'dotenv' -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import { allVersions } from '@/versions/lib/all-versions' import type { IndicesAnalyzeAnalyzeToken } from '@elastic/elasticsearch/lib/api/types' diff --git a/src/search/scripts/index/index-cli.ts b/src/search/scripts/index/index-cli.ts index 18cd552ba931..1c494dd83422 100644 --- a/src/search/scripts/index/index-cli.ts +++ b/src/search/scripts/index/index-cli.ts @@ -2,7 +2,7 @@ import { program, Option, Command, InvalidArgumentError } from 'commander' import { errors } from '@elastic/elasticsearch' import dotenv from 'dotenv' -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import { indexGeneralSearch } from './lib/index-general-search' import { diff --git a/src/search/scripts/index/lib/index-general-search.ts b/src/search/scripts/index/lib/index-general-search.ts index eca5b82e31c9..595eedc90176 100644 --- a/src/search/scripts/index/lib/index-general-search.ts +++ b/src/search/scripts/index/lib/index-general-search.ts @@ -1,6 +1,6 @@ import { Client } from '@elastic/elasticsearch' -import { languageKeys } from '@/languages/lib/languages.js' +import { languageKeys } from '@/languages/lib/languages' import { getElasticSearchIndex } from '@/search/lib/elasticsearch-indexes' import { getElasticsearchClient } from '@/search/lib/helpers/get-client' import { diff --git a/src/search/scripts/scrape/lib/build-records.ts b/src/search/scripts/scrape/lib/build-records.ts index 329771487a85..9b7e1643e897 100644 --- a/src/search/scripts/scrape/lib/build-records.ts +++ b/src/search/scripts/scrape/lib/build-records.ts @@ -4,7 +4,7 @@ import dotenv from 'dotenv' import boxen from 'boxen' import { HTTPError } from 'got' -import languages from '@/languages/lib/languages.js' +import languages from '@/languages/lib/languages' import parsePageSectionsIntoRecords from '@/search/scripts/scrape/lib/parse-page-sections-into-records' import getPopularPages from '@/search/scripts/scrape/lib/popular-pages' import domwaiter from '@/search/scripts/scrape/lib/domwaiter' diff --git a/src/search/scripts/scrape/lib/scrape-into-index-json.ts b/src/search/scripts/scrape/lib/scrape-into-index-json.ts index 56cbe264d4e6..6d4b73733088 100644 --- a/src/search/scripts/scrape/lib/scrape-into-index-json.ts +++ b/src/search/scripts/scrape/lib/scrape-into-index-json.ts @@ -1,6 +1,6 @@ import chalk from 'chalk' -import languages from '@/languages/lib/languages.js' +import languages from '@/languages/lib/languages' import buildRecords from '@/search/scripts/scrape/lib/build-records' import findIndexablePages from '@/search/scripts/scrape/lib/find-indexable-pages' import { writeIndexRecords } from '@/search/scripts/scrape/lib/search-index-records' From ad1238c44b8c888a8739c3c6c12f1d229842b668 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 9 Jul 2025 12:35:54 -0700 Subject: [PATCH 7/7] Phase 1: Convert core validation utility to TypeScript (#56420) Co-authored-by: Evan Bonsignori Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../lib/helpers/get-lintable-yml.js | 2 +- src/data-directory/tests/data-schemas.js | 4 ++-- src/early-access/tests/early-access-rendering.ts | 2 +- src/early-access/tests/early-access-unit.ts | 2 +- src/events/tests/middleware-errors.ts | 2 +- src/frame/lib/read-frontmatter.js | 2 +- src/frame/tests/server.js | 2 +- src/frame/tests/site-tree.js | 4 ++-- src/github-apps/scripts/sync.js | 2 +- src/graphql/tests/validate-schema.js | 4 ++-- src/languages/tests/api-search.js | 2 +- src/products/tests/products.ts | 4 ++-- src/rest/scripts/utils/operation.js | 2 +- src/search/tests/api-ai-search-autocomplete.ts | 2 +- src/search/tests/api-combined-search.ts | 2 +- src/search/tests/api-search.ts | 2 +- src/search/tests/rendering.ts | 2 +- src/secret-scanning/scripts/sync.ts | 6 +++--- .../{conditional-runs.js => conditional-runs.ts} | 0 src/tests/helpers/{schemas.js => schemas.ts} | 6 ++++-- ...te-json-schema.js => validate-json-schema.ts} | 16 +++++++++++----- src/versions/tests/versions.js | 4 ++-- 22 files changed, 41 insertions(+), 33 deletions(-) rename src/tests/helpers/{conditional-runs.js => conditional-runs.ts} (100%) rename src/tests/helpers/{schemas.js => schemas.ts} (91%) rename src/tests/lib/{validate-json-schema.js => validate-json-schema.ts} (76%) diff --git a/src/content-linter/lib/helpers/get-lintable-yml.js b/src/content-linter/lib/helpers/get-lintable-yml.js index 104eaf95c6b5..c9f71de9d0a0 100755 --- a/src/content-linter/lib/helpers/get-lintable-yml.js +++ b/src/content-linter/lib/helpers/get-lintable-yml.js @@ -2,7 +2,7 @@ import yaml from 'js-yaml' import fs from 'fs/promises' import dataSchemas from '#src/data-directory/lib/data-schemas/index.ts' -import ajv from '#src/tests/lib/validate-json-schema.js' +import ajv from '#src/tests/lib/validate-json-schema.ts' // AJV already has a built-in way to extract out properties // with a specific keyword using a custom validator function. diff --git a/src/data-directory/tests/data-schemas.js b/src/data-directory/tests/data-schemas.js index 067fe4f23c03..91e454ddb985 100644 --- a/src/data-directory/tests/data-schemas.js +++ b/src/data-directory/tests/data-schemas.js @@ -5,8 +5,8 @@ import { extname, basename } from 'path' import walk from 'walk-sync' import { beforeAll, describe, expect, test } from 'vitest' -import { getJsonValidator, validateJson } from '#src/tests/lib/validate-json-schema.js' -import { formatAjvErrors } from '#src/tests/helpers/schemas.js' +import { getJsonValidator, validateJson } from '#src/tests/lib/validate-json-schema.ts' +import { formatAjvErrors } from '#src/tests/helpers/schemas.ts' import dataSchemas from '#src/data-directory/lib/data-schemas/index.ts' const schemaPaths = Object.keys(dataSchemas) diff --git a/src/early-access/tests/early-access-rendering.ts b/src/early-access/tests/early-access-rendering.ts index f11a31165909..ed62dc7cb68c 100644 --- a/src/early-access/tests/early-access-rendering.ts +++ b/src/early-access/tests/early-access-rendering.ts @@ -3,7 +3,7 @@ import path from 'path' import { describe, expect } from 'vitest' -import { testViaActionsOnly } from '@/tests/helpers/conditional-runs.js' +import { testViaActionsOnly } from '@/tests/helpers/conditional-runs' import { get, getDOM } from '@/tests/helpers/e2etest.js' describe('cloning early-access', () => { diff --git a/src/early-access/tests/early-access-unit.ts b/src/early-access/tests/early-access-unit.ts index 6c38783772ad..ee88e5201b82 100644 --- a/src/early-access/tests/early-access-unit.ts +++ b/src/early-access/tests/early-access-unit.ts @@ -1,7 +1,7 @@ import { expect, test, vi } from 'vitest' import { get, getDOM } from '@/tests/helpers/e2etest.js' -import { describeIfDocsEarlyAccess } from '@/tests/helpers/conditional-runs.js' +import { describeIfDocsEarlyAccess } from '@/tests/helpers/conditional-runs' import languages from '@/languages/lib/languages' const VALID_EARLY_ACCESS_URI = '/early-access/github/save-time-with-slash-commands' diff --git a/src/events/tests/middleware-errors.ts b/src/events/tests/middleware-errors.ts index 3cda6a741ddb..1349c8cdb4e7 100644 --- a/src/events/tests/middleware-errors.ts +++ b/src/events/tests/middleware-errors.ts @@ -1,6 +1,6 @@ import { describe, test } from 'vitest' -import { validateJson } from '@/tests/lib/validate-json-schema.js' +import { validateJson } from '@/tests/lib/validate-json-schema' import { formatErrors } from '../lib/middleware-errors.js' import { schemas } from '../lib/schema.js' diff --git a/src/frame/lib/read-frontmatter.js b/src/frame/lib/read-frontmatter.js index 0cfdb1bbeddf..2c6431015619 100644 --- a/src/frame/lib/read-frontmatter.js +++ b/src/frame/lib/read-frontmatter.js @@ -1,6 +1,6 @@ import matter from 'gray-matter' -import { validateJson } from '#src/tests/lib/validate-json-schema.js' +import { validateJson } from '#src/tests/lib/validate-json-schema.ts' function readFrontmatter(markdown, opts = {}) { const schema = opts.schema || { type: 'object', properties: {} } diff --git a/src/frame/tests/server.js b/src/frame/tests/server.js index 57d926bec414..ee0526eca03d 100644 --- a/src/frame/tests/server.js +++ b/src/frame/tests/server.js @@ -3,7 +3,7 @@ import { beforeAll, describe, expect, test, vi } from 'vitest' import enterpriseServerReleases from '#src/versions/lib/enterprise-server-releases.js' import { get, getDOM, head, post } from '#src/tests/helpers/e2etest.js' -import { describeViaActionsOnly } from '#src/tests/helpers/conditional-runs.js' +import { describeViaActionsOnly } from '#src/tests/helpers/conditional-runs.ts' import { loadPages } from '#src/frame/lib/page-data.js' import { SURROGATE_ENUMS, diff --git a/src/frame/tests/site-tree.js b/src/frame/tests/site-tree.js index 08b37465b535..149416bce068 100644 --- a/src/frame/tests/site-tree.js +++ b/src/frame/tests/site-tree.js @@ -1,11 +1,11 @@ import { beforeAll, describe, expect, test, vi } from 'vitest' -import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.ts' import schema from '#src/tests/helpers/schemas/site-tree-schema.js' import EnterpriseServerReleases from '#src/versions/lib/enterprise-server-releases.js' import { loadSiteTree } from '#src/frame/lib/page-data.js' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' -import { formatAjvErrors } from '#src/tests/helpers/schemas.js' +import { formatAjvErrors } from '#src/tests/helpers/schemas.ts' const latestEnterpriseRelease = EnterpriseServerReleases.latest diff --git a/src/github-apps/scripts/sync.js b/src/github-apps/scripts/sync.js index 6f0437f52498..89b338b584d9 100755 --- a/src/github-apps/scripts/sync.js +++ b/src/github-apps/scripts/sync.js @@ -9,7 +9,7 @@ import walk from 'walk-sync' import { getContents, getDirectoryContents } from '#src/workflows/git-utils.ts' import permissionSchema from './permission-list-schema.js' import enabledSchema from './enabled-list-schema.js' -import { validateJson } from '#src/tests/lib/validate-json-schema.js' +import { validateJson } from '#src/tests/lib/validate-json-schema.ts' const ENABLED_APPS_DIR = 'src/github-apps/data' const CONFIG_FILE = 'src/github-apps/lib/config.json' diff --git a/src/graphql/tests/validate-schema.js b/src/graphql/tests/validate-schema.js index 978eadb8c677..4ac4ecc90999 100644 --- a/src/graphql/tests/validate-schema.js +++ b/src/graphql/tests/validate-schema.js @@ -1,9 +1,9 @@ import { describe, expect, test, vi } from 'vitest' -import { getJsonValidator, validateJson } from '#src/tests/lib/validate-json-schema.js' +import { getJsonValidator, validateJson } from '#src/tests/lib/validate-json-schema.ts' import readJsonFile from '#src/frame/lib/read-json-file.js' import { schemaValidator, previewsValidator, upcomingChangesValidator } from '../lib/validator.ts' -import { formatAjvErrors } from '#src/tests/helpers/schemas.js' +import { formatAjvErrors } from '#src/tests/helpers/schemas.ts' import { allVersions } from '#src/versions/lib/all-versions.ts' import { GRAPHQL_DATA_DIR } from '../lib/index.js' diff --git a/src/languages/tests/api-search.js b/src/languages/tests/api-search.js index 4de21fabdf77..214a9c3b2308 100644 --- a/src/languages/tests/api-search.js +++ b/src/languages/tests/api-search.js @@ -1,6 +1,6 @@ import { expect, test, vi } from 'vitest' -import { describeIfElasticsearchURL } from '#src/tests/helpers/conditional-runs.js' +import { describeIfElasticsearchURL } from '#src/tests/helpers/conditional-runs.ts' import { get } from '#src/tests/helpers/e2etest.js' // This suite only runs if $ELASTICSEARCH_URL is set. diff --git a/src/products/tests/products.ts b/src/products/tests/products.ts index c870a52e114c..46032073bee1 100644 --- a/src/products/tests/products.ts +++ b/src/products/tests/products.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from 'vitest' -import { getJsonValidator } from '@/tests/lib/validate-json-schema.js' +import { getJsonValidator } from '@/tests/lib/validate-json-schema' import { productMap } from '@/products/lib/all-products' -import { formatAjvErrors } from '@/tests/helpers/schemas.js' +import { formatAjvErrors } from '@/tests/helpers/schemas' // @ts-ignore - Products schema doesn't have TypeScript types yet import schema from '@/tests/helpers/schemas/products-schema.js' diff --git a/src/rest/scripts/utils/operation.js b/src/rest/scripts/utils/operation.js index aefb275df24f..201e6fd05981 100644 --- a/src/rest/scripts/utils/operation.js +++ b/src/rest/scripts/utils/operation.js @@ -6,7 +6,7 @@ import mergeAllOf from 'json-schema-merge-allof' import { renderContent } from './render-content' import getCodeSamples from './create-rest-examples.js' import operationSchema from './operation-schema.js' -import { validateJson } from '#src/tests/lib/validate-json-schema.js' +import { validateJson } from '#src/tests/lib/validate-json-schema.ts' import { getBodyParams } from './get-body-params' export default class Operation { diff --git a/src/search/tests/api-ai-search-autocomplete.ts b/src/search/tests/api-ai-search-autocomplete.ts index 5daf0114170f..5daea89a0fa8 100644 --- a/src/search/tests/api-ai-search-autocomplete.ts +++ b/src/search/tests/api-ai-search-autocomplete.ts @@ -13,7 +13,7 @@ import { expect, test, vi } from 'vitest' -import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs.js' +import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs' import { get } from '@/tests/helpers/e2etest-ts' import type { AutocompleteSearchResponse } from '@/search/types' diff --git a/src/search/tests/api-combined-search.ts b/src/search/tests/api-combined-search.ts index defa23c3ae6c..f0e91dd8d2b7 100644 --- a/src/search/tests/api-combined-search.ts +++ b/src/search/tests/api-combined-search.ts @@ -13,7 +13,7 @@ import { expect, test, vi } from 'vitest' -import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs.js' +import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs' import { get } from '@/tests/helpers/e2etest-ts' import type { CombinedSearchResponse } from '@/search/types' diff --git a/src/search/tests/api-search.ts b/src/search/tests/api-search.ts index e1b2ee7c089d..aaee24a2fe45 100644 --- a/src/search/tests/api-search.ts +++ b/src/search/tests/api-search.ts @@ -12,7 +12,7 @@ */ import { expect, test, vi } from 'vitest' -import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs.js' +import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs' import { get } from '@/tests/helpers/e2etest-ts' import { GeneralSearchResponse, SearchResultAggregations, GeneralSearchHit } from '@/search/types' diff --git a/src/search/tests/rendering.ts b/src/search/tests/rendering.ts index 61697806f2ba..7c3d8ee0035b 100644 --- a/src/search/tests/rendering.ts +++ b/src/search/tests/rendering.ts @@ -13,7 +13,7 @@ import { expect, test, vi } from 'vitest' -import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs.js' +import { describeIfElasticsearchURL } from '@/tests/helpers/conditional-runs' import { get, getDOM } from '@/tests/helpers/e2etest-ts' import { SURROGATE_ENUMS } from '@/frame/middleware/set-fastly-surrogate-key.js' diff --git a/src/secret-scanning/scripts/sync.ts b/src/secret-scanning/scripts/sync.ts index b6ed4cec0d05..5f61f60e77d2 100755 --- a/src/secret-scanning/scripts/sync.ts +++ b/src/secret-scanning/scripts/sync.ts @@ -14,9 +14,9 @@ import yaml from 'js-yaml' import { getContentAndData, getCommitSha } from '@/workflows/git-utils.js' import schema from '@/secret-scanning/data/public-docs-schema' // This is temporarily being imported until the subsequent modules -// have beeen converted to TypeScript. -import { validateJson } from '@/tests/lib/validate-json-schema.js' -import { formatAjvErrors } from '@/tests/helpers/schemas.js' +// have been converted to TypeScript. +import { validateJson } from '@/tests/lib/validate-json-schema' +import { formatAjvErrors } from '@/tests/helpers/schemas' const SECRET_SCANNING_FILEPATH = 'src/secret-scanning/data/public-docs.yml' type PipelineConfig = { sha: string; 'blob-sha': string } diff --git a/src/tests/helpers/conditional-runs.js b/src/tests/helpers/conditional-runs.ts similarity index 100% rename from src/tests/helpers/conditional-runs.js rename to src/tests/helpers/conditional-runs.ts diff --git a/src/tests/helpers/schemas.js b/src/tests/helpers/schemas.ts similarity index 91% rename from src/tests/helpers/schemas.js rename to src/tests/helpers/schemas.ts index d7a4a8b61fd9..766419070047 100644 --- a/src/tests/helpers/schemas.js +++ b/src/tests/helpers/schemas.ts @@ -1,3 +1,5 @@ +import type { ErrorObject } from 'ajv' + // lightly format the schema errors object returned from ajv to connect the // error message to where the problem is -- for example, if a top level 'date' // property isn't correctly formatted as a date we return: @@ -8,7 +10,7 @@ // property and we misspell the property name in the first item: // // at 'sections > features > item 0': must have required property 'notes' -export const formatAjvErrors = (errors = []) => { +export const formatAjvErrors = (errors: ErrorObject[] = []): string => { return errors .map((errorObj) => { // ajv instancePath tells us in the data we're checking where there was a @@ -31,7 +33,7 @@ export const formatAjvErrors = (errors = []) => { const schemaErrorPath = split .map((item) => { - if (!isNaN(item)) { + if (!isNaN(Number(item))) { return `item ${item}` } else { return item diff --git a/src/tests/lib/validate-json-schema.js b/src/tests/lib/validate-json-schema.ts similarity index 76% rename from src/tests/lib/validate-json-schema.js rename to src/tests/lib/validate-json-schema.ts index f3ca90da5b21..d2696060bd6a 100644 --- a/src/tests/lib/validate-json-schema.js +++ b/src/tests/lib/validate-json-schema.ts @@ -1,4 +1,4 @@ -import Ajv from 'ajv' +import Ajv, { type ValidateFunction, type ErrorObject, type SchemaObject } from 'ajv' import addErrors from 'ajv-errors' import addFormats from 'ajv-formats' import semver from 'semver' @@ -24,7 +24,7 @@ ajv.addKeyword({ // Custom JSON formats ajv.addFormat('semver', { - validate: (x) => semver.validRange(x), + validate: (x: string): boolean => semver.validRange(x) !== null, }) // The ajv.validate function is supposed to cache @@ -35,18 +35,24 @@ ajv.addFormat('semver', { // this is the best function to use. If the schema // changes from one call to the next, then the validateJson // function makes more sense to use. -export function getJsonValidator(schema) { +export function getJsonValidator(schema: SchemaObject): ValidateFunction { return ajv.compile(schema) } // The next call to ajv.validate will overwrite // the ajv.errors property, so returning it here // ensures that it remains accessible. -export function validateJson(schema, data) { +export function validateJson( + schema: SchemaObject, + data: unknown, +): { + isValid: boolean + errors: ErrorObject[] | null +} { const isValid = ajv.validate(schema, data) return { isValid, - errors: isValid ? null : structuredClone(ajv.errors), + errors: isValid ? null : structuredClone(ajv.errors || []), } } diff --git a/src/versions/tests/versions.js b/src/versions/tests/versions.js index a904eae27de0..dca2addcd665 100644 --- a/src/versions/tests/versions.js +++ b/src/versions/tests/versions.js @@ -1,11 +1,11 @@ import { describe, expect, test } from 'vitest' -import { getJsonValidator } from '#src/tests/lib/validate-json-schema.js' +import { getJsonValidator } from '#src/tests/lib/validate-json-schema.ts' import { allVersions } from '#src/versions/lib/all-versions.ts' import { latest } from '#src/versions/lib/enterprise-server-releases.js' import schema from '#src/tests/helpers/schemas/versions-schema.js' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' -import { formatAjvErrors } from '#src/tests/helpers/schemas.js' +import { formatAjvErrors } from '#src/tests/helpers/schemas.ts' const validate = getJsonValidator(schema)