diff --git a/assets/images/search/copilot-action.png b/assets/images/search/copilot-action.png deleted file mode 100644 index 83d42b5f2255..000000000000 Binary files a/assets/images/search/copilot-action.png and /dev/null differ diff --git a/data/reusables/copilot/repository-cust-instr-code-review.md b/data/reusables/copilot/repository-cust-instr-code-review.md deleted file mode 100644 index 763a7e68e216..000000000000 --- a/data/reusables/copilot/repository-cust-instr-code-review.md +++ /dev/null @@ -1 +0,0 @@ -For examples of repository custom instructions used to configure {% data variables.copilot.copilot_code-review_short %}, see [AUTOTITLE](/copilot/how-tos/agents/copilot-code-review/using-copilot-code-review?tool=webui#customizing-copilots-reviews-with-custom-instructions). diff --git a/data/reusables/organizations/ent-ownership-recommendation.md b/data/reusables/organizations/ent-ownership-recommendation.md deleted file mode 100644 index 436a04770aed..000000000000 --- a/data/reusables/organizations/ent-ownership-recommendation.md +++ /dev/null @@ -1 +0,0 @@ -If an enterprise only has one owner, the enterprise's resources can become inaccessible if the owner is unreachable. To protect access to your resources, we recommend that at least two people within the enterprise have the owner role. diff --git a/eslint.config.ts b/eslint.config.ts index aeb30e33a9ad..3668a5fde641 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -67,63 +67,33 @@ export default [ // JSX A11y recommended rules ...jsxA11y.configs.recommended.rules, - // TypeScript-specific overrides + // Overrides 'import/no-extraneous-dependencies': [ 'error', { packageDir: '.', }, ], - 'import/extensions': 'off', - 'no-console': 'off', - camelcase: 'off', - 'no-shadow': 'off', - 'prefer-template': 'off', - 'no-constant-condition': 'off', - 'no-unused-vars': 'off', - 'no-undef': 'off', - 'no-use-before-define': 'off', - 'no-redeclare': 'off', // Allow function overloads in TypeScript - 'import/no-named-as-default-member': 'off', - 'one-var': 'off', - 'import/no-namespace': 'off', - 'import/no-anonymous-default-export': 'off', - 'object-shorthand': 'off', - 'no-empty': 'off', - 'prefer-const': 'off', - 'import/no-named-as-default': 'off', - 'no-useless-concat': 'off', - 'func-style': 'off', - - // TypeScript ESLint specific rules + 'import/extensions': ['error', { json: 'always' }], + 'no-empty': ['error', { allowEmptyCatch: true }], '@typescript-eslint/no-unused-vars': 'error', + 'prefer-const': ['error', { destructuring: 'all' }], - // Disable GitHub plugin rules that were disabled in original config - 'github/array-foreach': 'off', - 'github/no-then': 'off', - - // Disable rules that might not exist or cause issues initially - 'i18n-text/no-en': 'off', - 'filenames/match-regex': 'off', - 'eslint-comments/no-use': 'off', - 'eslint-comments/no-unused-disable': 'off', - 'eslint-comments/no-unlimited-disable': 'off', - - // Disable new ESLint 9 rules that are causing issues - 'no-constant-binary-expression': 'off', - - // Disable stricter TypeScript rules initially - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unused-expressions': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/no-wrapper-object-types': 'off', - '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', - '@typescript-eslint/no-unsafe-function-type': 'off', - '@typescript-eslint/no-empty-object-type': 'off', - '@typescript-eslint/prefer-as-const': 'off', + // Rules that must be disabled + 'no-redeclare': 'off', // Allow function overloads in TypeScript + 'i18n-text/no-en': 'off', // This rule causes eslint to not run at all + 'filenames/match-regex': 'off', // This rule causes eslint to not run at all - // React/JSX specific rules - 'jsx-a11y/no-onchange': 'off', + // Disabled rules to review + 'github/no-then': 'off', // 30+ + '@typescript-eslint/ban-ts-comment': 'off', // 50+ + 'no-undef': 'off', // 50+ + 'no-shadow': 'off', // 150+ + 'prefer-template': 'off', // 150+ + 'github/array-foreach': 'off', // 250+ + camelcase: 'off', // 600+ + 'no-console': 'off', // 800+ + '@typescript-eslint/no-explicit-any': 'off', // 1000+ }, }, diff --git a/package.json b/package.json index eae47fc34dcc..802bb7bfb1c6 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "index-general-search": "tsx src/search/scripts/index/index-cli general-search", "index-test-fixtures": "./src/search/scripts/index-test-fixtures.sh", "labeler": "tsx .github/actions/labeler/labeler.ts", - "lint": "eslint '**/*.{js,mjs,ts,tsx}'", + "lint": "eslint '**/*.{ts,tsx}'", "lint-content": "tsx src/content-linter/scripts/lint-content.ts", "lint-translation": "vitest src/content-linter/tests/lint-files.ts", "liquid-markdown-tables": "tsx src/tools/scripts/liquid-markdown-tables/index.ts", @@ -67,8 +67,8 @@ "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", - "prettier": "prettier -w \"**/*.{ts,tsx,js,mjs,scss,yml,yaml}\"", - "prettier-check": "prettier -c \"**/*.{ts,tsx,js,mjs,scss,yml,yaml}\"", + "prettier": "prettier -w \"**/*.{ts,tsx,scss,yml,yaml}\"", + "prettier-check": "prettier -c \"**/*.{ts,tsx,scss,yml,yaml}\"", "prevent-pushes-to-main": "tsx src/workflows/prevent-pushes-to-main.ts", "purge-fastly-edge-cache": "tsx src/workflows/purge-fastly-edge-cache.ts", "purge-fastly-edge-cache-per-language": "tsx src/languages/scripts/purge-fastly-edge-cache-per-language.ts", @@ -105,12 +105,12 @@ "what-docs-early-access-branch": "tsx src/early-access/scripts/what-docs-early-access-branch.ts" }, "lint-staged": { - "*.{js,mjs,ts,tsx}": "eslint --cache --fix", - "*.{js,mjs,scss,ts,tsx,yml,yaml}": "prettier --write", + "*.{ts,tsx}": "eslint --cache --fix", + "*.{scss,ts,tsx,yml,yaml}": "prettier --write", "{content,data}/**/*.md": "npm run lint-content -- --precommit --paths" }, "nodemonConfig": { - "ext": "ts,js,json,yml,md,html,scss", + "ext": "ts,json,yml,md,html,scss", "exec": "tsx", "ignore": [ "assets", @@ -139,7 +139,7 @@ }, { "files": [ - "**/*.{ts,tsx,js,mjs}" + "**/*.{ts,tsx}" ], "options": { "printWidth": 100, diff --git a/src/archives/middleware/archived-enterprise-versions.ts b/src/archives/middleware/archived-enterprise-versions.ts index fd2be04e9251..848a151f0e03 100644 --- a/src/archives/middleware/archived-enterprise-versions.ts +++ b/src/archives/middleware/archived-enterprise-versions.ts @@ -235,7 +235,7 @@ export default async function archivedEnterpriseVersions( // `x-host` is a custom header set by Fastly. // GLB automatically deletes the `x-forwarded-host` header. const host = req.get('x-host') || req.get('x-forwarded-host') || req.get('host') - let modifiedBody = body + const modifiedBody = body .replaceAll( `${OLD_AZURE_BLOB_ENTERPRISE_DIR}/${requestedVersion}/assets/cb-`, `${ENTERPRISE_GH_PAGES_URL_PREFIX}${requestedVersion}/assets/cb-`, diff --git a/src/assets/scripts/deleted-assets-pr-comment.ts b/src/assets/scripts/deleted-assets-pr-comment.ts index cc7802a79918..19aca379f7aa 100755 --- a/src/assets/scripts/deleted-assets-pr-comment.ts +++ b/src/assets/scripts/deleted-assets-pr-comment.ts @@ -1,4 +1,4 @@ -import * as github from '@actions/github' +import github from '@actions/github' import core from '@actions/core' const { GITHUB_TOKEN } = process.env diff --git a/src/assets/scripts/find-orphaned-assets.ts b/src/assets/scripts/find-orphaned-assets.ts index 7b9aa2dbff7b..da4fd6d8b5dd 100755 --- a/src/assets/scripts/find-orphaned-assets.ts +++ b/src/assets/scripts/find-orphaned-assets.ts @@ -145,7 +145,9 @@ async function main(opts: MainOptions) { // Add exceptions sourceFiles.push('.github/CONTRIBUTING.md') sourceFiles.push('README.md') - verbose && console.log(`${sourceFiles.length.toLocaleString()} source files found in total.`) + if (verbose) { + console.log(`${sourceFiles.length.toLocaleString()} source files found in total.`) + } const allImages = new Set( walk( @@ -159,7 +161,9 @@ async function main(opts: MainOptions) { ).filter((filePath) => !filePath.endsWith('.md')), ) - verbose && console.log(`${allImages.size.toLocaleString()} images found in total.`) + if (verbose) { + console.log(`${allImages.size.toLocaleString()} images found in total.`) + } for (const sourceFile of sourceFiles) { const content = fs.readFileSync(sourceFile, 'utf-8') diff --git a/src/assets/tests/static-assets.ts b/src/assets/tests/static-assets.ts index 13ee2083cc07..479b6af44f96 100644 --- a/src/assets/tests/static-assets.ts +++ b/src/assets/tests/static-assets.ts @@ -43,7 +43,7 @@ type MockResponse = { _json?: string _send?: string headers: Record - set?: (key: string | Object, value: string) => void + set?: (key: string | object, value: string) => void removeHeader?: (key: string) => void hasHeader?: (key: string) => boolean } diff --git a/src/audit-logs/lib/index.ts b/src/audit-logs/lib/index.ts index 9f1608e309a4..3af7c77a7bc2 100644 --- a/src/audit-logs/lib/index.ts +++ b/src/audit-logs/lib/index.ts @@ -96,7 +96,7 @@ async function resolveReferenceLinksToTitles( // docs_reference_links: 'event reference links' // }, // ] -export function getAuditLogEvents(page: string, version: string) { +export function getAuditLogEvents(page: string, version: string): AuditLogEventT[] { const openApiVersion = getOpenApiVersion(version) const auditLogFileName = path.join(AUDIT_LOG_DATA_DIR, openApiVersion, `${page}.json`) @@ -115,14 +115,14 @@ export function getAuditLogEvents(page: string, version: string) { ?.set(page, readCompressedJsonFileFallback(auditLogFileName)) } - const auditLogEvents = auditLogEventsCache.get(openApiVersion)?.get(page)! + const auditLogEvents = auditLogEventsCache.get(openApiVersion)?.get(page) // If an event doesn't yet have a description (value will be empty string or // "N/A"), then we don't show the event. - const filteredAuditLogEvents = auditLogEvents.filter( + const filteredAuditLogEvents = auditLogEvents?.filter( (event) => event.description !== 'N/A' && event.description !== '', ) - return filteredAuditLogEvents + return filteredAuditLogEvents || [] } // get categorized audit log event data for the requested page and version @@ -137,7 +137,7 @@ export function getAuditLogEvents(page: string, version: string) { // repo: [ [Object] ], // user: [ [Object], [Object] ] // } -export function getCategorizedAuditLogEvents(page: string, version: string) { +export function getCategorizedAuditLogEvents(page: string, version: string): CategorizedEvents { const events = getAuditLogEvents(page, version) const openApiVersion = getOpenApiVersion(version) @@ -148,7 +148,7 @@ export function getCategorizedAuditLogEvents(page: string, version: string) { categorizedAuditLogEventsCache.get(openApiVersion)?.set(page, categorizeEvents(events)) } - return categorizedAuditLogEventsCache.get(openApiVersion)?.get(page)! + return categorizedAuditLogEventsCache.get(openApiVersion)?.get(page) || {} } // Filters audit log events based on allowlist values. diff --git a/src/audit-logs/pages/audit-log-events.tsx b/src/audit-logs/pages/audit-log-events.tsx index ac114c6c37d2..27e84296aaf7 100644 --- a/src/audit-logs/pages/audit-log-events.tsx +++ b/src/audit-logs/pages/audit-log-events.tsx @@ -86,7 +86,9 @@ export const getServerSideProps: GetServerSideProps = async (context) => Object.keys(auditLogEvents).map((category) => category), context, ) - auditLogEventsMiniTocs && miniTocItems.push(...auditLogEventsMiniTocs) + if (auditLogEventsMiniTocs) { + miniTocItems.push(...auditLogEventsMiniTocs) + } return { props: { diff --git a/src/content-linter/lib/helpers/should-include-result.ts b/src/content-linter/lib/helpers/should-include-result.ts index c6824eb16641..afa77e5afd44 100644 --- a/src/content-linter/lib/helpers/should-include-result.ts +++ b/src/content-linter/lib/helpers/should-include-result.ts @@ -1,4 +1,4 @@ -import * as nodePath from 'path' +import nodePath from 'path' import { reportingConfig } from '@/content-linter/style/github-docs' interface LintFlaw { diff --git a/src/content-linter/lib/linting-rules/liquid-ifversion-versions.ts b/src/content-linter/lib/linting-rules/liquid-ifversion-versions.ts index f5b37395ad2d..97da99f41abe 100644 --- a/src/content-linter/lib/linting-rules/liquid-ifversion-versions.ts +++ b/src/content-linter/lib/linting-rules/liquid-ifversion-versions.ts @@ -30,7 +30,7 @@ export const liquidIfversionVersions = { // The versions frontmatter object or all versions if the file // being processed is a data file. const fm = getFrontmatter(params.lines) - let content = fm ? getFrontmatterLines(params.lines).join('\n') : params.lines.join('\n') + const content = fm ? getFrontmatterLines(params.lines).join('\n') : params.lines.join('\n') const fileVersionsFm = params.name.startsWith('data') ? { ghec: '*', ghes: '*', fpt: '*' } diff --git a/src/content-linter/lib/linting-rules/note-warning-formatting.ts b/src/content-linter/lib/linting-rules/note-warning-formatting.ts index 820ac069f8b9..e55a57da2295 100644 --- a/src/content-linter/lib/linting-rules/note-warning-formatting.ts +++ b/src/content-linter/lib/linting-rules/note-warning-formatting.ts @@ -72,7 +72,7 @@ export const noteWarningFormatting = { // Collect content inside legacy notes if (inLegacyNote) { - noteContent.push({ text: line, lineNumber: lineNumber }) + noteContent.push({ text: line, lineNumber }) continue } diff --git a/src/content-linter/lib/linting-rules/outdated-release-phase-terminology.ts b/src/content-linter/lib/linting-rules/outdated-release-phase-terminology.ts index d91249170547..28ce1d3e2c8b 100644 --- a/src/content-linter/lib/linting-rules/outdated-release-phase-terminology.ts +++ b/src/content-linter/lib/linting-rules/outdated-release-phase-terminology.ts @@ -78,8 +78,8 @@ function findOutdatedTerminologyMatches(line: string): MatchInfo[] { start: match.index, end: match.index + match[0].length, text: match[0], - replacement: replacement, - outdatedTerm: outdatedTerm, + replacement, + outdatedTerm, }) } } diff --git a/src/content-linter/tests/lint-files.ts b/src/content-linter/tests/lint-files.ts index f0ad3f5c4e8d..e6de28e097e0 100755 --- a/src/content-linter/tests/lint-files.ts +++ b/src/content-linter/tests/lint-files.ts @@ -240,7 +240,9 @@ if (ymlToLint.length === 0) { describe('lint yaml content', () => { if (ymlToLint.length < 1) return describe.each(ymlToLint)('%s', (yamlRelPath, yamlAbsPath) => { - let dictionary, isEarlyAccess, fileContents + let dictionary + let isEarlyAccess + let fileContents // This variable is used to determine if the file was parsed successfully. // When `yaml.load()` fails to parse the file, it is overwritten with the error message. // `false` is intentionally chosen since `null` and `undefined` are valid return values. diff --git a/src/content-render/liquid/ifversion.ts b/src/content-render/liquid/ifversion.ts index 64059f4bf349..5ff38fe44843 100644 --- a/src/content-render/liquid/ifversion.ts +++ b/src/content-render/liquid/ifversion.ts @@ -37,7 +37,7 @@ const notRegex = /(?:^|\s)not\s/ // native Liquid `if` block tag. It has special handling for statements like {% ifversion ghes < 3.0 %}, // using semver to evaluate release numbers instead of doing standard number comparisons, which // don't work the way we want because they evaluate 3.2 > 3.10 = true. -export default class extends Tag { +export default class Ifversion extends Tag { tagToken: TagToken branches: Branch[] elseTemplates: Template[] diff --git a/src/content-render/liquid/prompt.ts b/src/content-render/liquid/prompt.ts index a01ebfaedff6..0b5cf74b1ec3 100644 --- a/src/content-render/liquid/prompt.ts +++ b/src/content-render/liquid/prompt.ts @@ -29,7 +29,7 @@ export const Prompt: LiquidTag = { }, // Render the inner Markdown, wrap in , then append the SVG - render: function* (scope: any): Generator { + *render(scope: any): Generator { const content = yield this.liquid.renderer.renderTemplates(this.templates, scope) // build a URL with the prompt text encoded as query parameter diff --git a/src/content-render/liquid/spotlight.ts b/src/content-render/liquid/spotlight.ts index 3a895714092b..4a48154976cb 100644 --- a/src/content-render/liquid/spotlight.ts +++ b/src/content-render/liquid/spotlight.ts @@ -53,7 +53,7 @@ export const Spotlight = { stream.start() }, - render: function* (scope: Record): Generator { + *render(scope: Record): Generator { const output = yield this.liquid!.renderer.renderTemplates(this.templates, scope) return yield this.liquid!.parseAndRender(template, { diff --git a/src/content-render/liquid/tool.ts b/src/content-render/liquid/tool.ts index 42152db538a5..c62dedf1ea1b 100644 --- a/src/content-render/liquid/tool.ts +++ b/src/content-render/liquid/tool.ts @@ -66,7 +66,7 @@ export const Tool = { }, // scope is a Liquid scope object, Generator yields/returns Liquid template values - no TypeScript definitions available - render: function* (scope: any): Generator { + *render(scope: any): Generator { const output = yield this.liquid.renderer.renderTemplates(this.templates, scope) return yield this.liquid.parseAndRender(template, { tagName: this.tagName, diff --git a/src/data-directory/scripts/deleted-features-pr-comment.ts b/src/data-directory/scripts/deleted-features-pr-comment.ts index 271e9ebbcfc1..e50a50c16483 100644 --- a/src/data-directory/scripts/deleted-features-pr-comment.ts +++ b/src/data-directory/scripts/deleted-features-pr-comment.ts @@ -8,7 +8,7 @@ * */ -import * as github from '@actions/github' +import github from '@actions/github' import core from '@actions/core' import { program } from 'commander' diff --git a/src/data-directory/tests/index.ts b/src/data-directory/tests/index.ts index 6ba9dde7b2a1..f4f6818da043 100644 --- a/src/data-directory/tests/index.ts +++ b/src/data-directory/tests/index.ts @@ -20,7 +20,7 @@ describe('data-directory', () => { }) test('option: preprocess function', async () => { - const preprocess = function (content: string) { + function preprocess(content: string) { return content.replace('markdown', 'MARKDOWN') } const data = dataDirectory(fixturesDir, { preprocess }) diff --git a/src/events/middleware.ts b/src/events/middleware.ts index 691b2f4a987d..73ba96aff54f 100644 --- a/src/events/middleware.ts +++ b/src/events/middleware.ts @@ -42,7 +42,7 @@ const sentValidationErrors = new QuickLRU({ // to prevent sending multiple validation errors that can spam requests to Hydro const getValidationErrorHash = (validateErrors: ErrorObject[]) => { // limit to 10 second windows - const window: Number = Math.floor(new Date().getTime() / 10000) + const window: number = Math.floor(new Date().getTime() / 10000) return `${window}:${(validateErrors || []) .map((error: ErrorObject) => error.message + error.instancePath + JSON.stringify(error.params)) .join(':')}` diff --git a/src/events/types.ts b/src/events/types.ts index a75c58b1591c..5ce0be05c8cf 100644 --- a/src/events/types.ts +++ b/src/events/types.ts @@ -105,12 +105,12 @@ export type EventPropsByType = { link_samepage?: boolean link_container?: string } - [EventType.page]: {} + [EventType.page]: { type: string } // no unique properties [EventType.preference]: { preference_name: string preference_value: string } - [EventType.print]: {} + [EventType.print]: { type: string } // no unique properties [EventType.search]: { search_query: string search_context?: string diff --git a/src/fixtures/tests/annotations.ts b/src/fixtures/tests/annotations.ts index 7784c947323e..f75df8e8b895 100644 --- a/src/fixtures/tests/annotations.ts +++ b/src/fixtures/tests/annotations.ts @@ -47,7 +47,7 @@ describe('annotations', () => { const noteTexts = notes.map((i: number, el: any) => $(el).text()).get() expect(noteTexts).toEqual([ 'Configures this workflow to run every time a change is pushed to the branch called release.', - 'This job checks out the repository contents ...\n' + "And here's the second comment line.", + "This job checks out the repository contents ...\nAnd here's the second comment line.", ]) } }) diff --git a/src/fixtures/tests/playwright-rendering.spec.ts b/src/fixtures/tests/playwright-rendering.spec.ts index 99bd97eb2684..03c2c65add91 100644 --- a/src/fixtures/tests/playwright-rendering.spec.ts +++ b/src/fixtures/tests/playwright-rendering.spec.ts @@ -115,7 +115,8 @@ test('open search, and select a general search article', async ({ page }) => { await page.getByTestId('overlay-search-input').fill('serve playwright') // Let new suggestions load - await page.waitForTimeout(1000) + const searchOverlay = page.getByTestId('general-autocomplete-suggestions') + await expect(searchOverlay.getByText('For Playwright')).toBeVisible() // Navigate to general search item, "For Playwright" await page.keyboard.press('ArrowDown') // Select the general search item, "For Playwright" @@ -747,7 +748,7 @@ test.describe('survey', () => { await page.evaluate(() => { Object.defineProperty(document, 'visibilityState', { configurable: true, - get: function () { + get() { return 'hidden' }, }) @@ -802,7 +803,7 @@ test.describe('survey', () => { await page.evaluate(() => { Object.defineProperty(document, 'visibilityState', { configurable: true, - get: function () { + get() { return 'hidden' }, }) diff --git a/src/frame/components/context/SharedUIContext.tsx b/src/frame/components/context/SharedUIContext.tsx index 2f484c19b302..5beff34f1683 100644 --- a/src/frame/components/context/SharedUIContext.tsx +++ b/src/frame/components/context/SharedUIContext.tsx @@ -24,8 +24,8 @@ export const SharedUIContextProvider = ({ children }: { children: React.ReactNod return ( {children} diff --git a/src/frame/components/hooks/useFeatureFlags.ts b/src/frame/components/hooks/useFeatureFlags.ts index a0345da561f4..1cd834b6eb55 100644 --- a/src/frame/components/hooks/useFeatureFlags.ts +++ b/src/frame/components/hooks/useFeatureFlags.ts @@ -1,6 +1,6 @@ import { useMainContext } from '@/frame/components/context/MainContext' -export type FeatureFlags = {} +export type FeatureFlags = Record export const useFeatureFlags = (): FeatureFlags => { const { featureFlags } = useMainContext() diff --git a/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx b/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx index ef770b608cbc..98f02c43c773 100644 --- a/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx +++ b/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx @@ -86,8 +86,8 @@ export const UnrenderedMarkdownContent = ({ sendEvent({ type: EventType.clipboard, clipboard_operation: 'copy', - eventGroupKey: eventGroupKey, - eventGroupId: eventGroupId, + eventGroupKey, + eventGroupId, }) }} > diff --git a/src/frame/lib/frontmatter.ts b/src/frame/lib/frontmatter.ts index 6d26a951ef39..bd8267a44d38 100644 --- a/src/frame/lib/frontmatter.ts +++ b/src/frame/lib/frontmatter.ts @@ -405,6 +405,14 @@ export const schema: Schema = { maxItems: 9, description: 'Array of articles to feature in the carousel section', }, + // Included categories for article grid filtering + includedCategories: { + type: 'array', + items: { + type: 'string', + }, + description: 'Array of category names to include in the article grid dropdown filter', + }, }, } diff --git a/src/frame/lib/read-frontmatter.ts b/src/frame/lib/read-frontmatter.ts index be21dbca6b41..f1b7f8878fdf 100644 --- a/src/frame/lib/read-frontmatter.ts +++ b/src/frame/lib/read-frontmatter.ts @@ -11,7 +11,8 @@ function readFrontmatter(markdown: string, opts: ReadFrontmatterOptions = {}) { const schema = opts.schema || { type: 'object', properties: {} } const filepath = opts.filepath || null - let content, data + let content + let data try { ;({ content, data } = matter(markdown)) diff --git a/src/frame/lib/warm-server.ts b/src/frame/lib/warm-server.ts index d8df339db58f..dccaf093576a 100644 --- a/src/frame/lib/warm-server.ts +++ b/src/frame/lib/warm-server.ts @@ -5,9 +5,18 @@ import { createLogger } from '@/observability/logger' const logger = createLogger(import.meta.url) +type WarmServerResult = { + pages: Awaited> + redirects: Awaited> + unversionedTree: Awaited> + siteTree: Awaited> + pageList: Awaited> + pageMap: Awaited> +} + // Instrument these functions so that // it's wrapped in a timer that reports to Datadog -const dog: Record = { +const dog = { loadUnversionedTree: statsd.asyncTimer(loadUnversionedTree, 'load_unversioned_tree'), loadSiteTree: statsd.asyncTimer(loadSiteTree, 'load_site_tree'), loadPages: statsd.asyncTimer(loadPages, 'load_pages'), @@ -17,9 +26,9 @@ const dog: Record = { } // For multiple-triggered Promise sharing -let promisedWarmServer: any +let promisedWarmServer: Promise | undefined -async function warmServer(languagesOnly = []) { +async function warmServer(languagesOnly: string[] = []): Promise { const startTime = Date.now() logger.debug( @@ -42,6 +51,7 @@ async function warmServer(languagesOnly = []) { unversionedTree, siteTree, pageList, + pageMap, } } diff --git a/src/frame/middleware/api.ts b/src/frame/middleware/api.ts index 706bf88cf42c..1c460344f4f6 100644 --- a/src/frame/middleware/api.ts +++ b/src/frame/middleware/api.ts @@ -42,7 +42,7 @@ if (process.env.ELASTICSEARCH_URL) { createProxyMiddleware({ target: 'https://docs.github.com', changeOrigin: true, - pathRewrite: function (path, req: ExtendedRequest) { + pathRewrite(path, req: ExtendedRequest) { return req.originalUrl }, }), diff --git a/src/frame/middleware/block-robots.ts b/src/frame/middleware/block-robots.ts index 0a9b1013f54e..298575ec9f0c 100644 --- a/src/frame/middleware/block-robots.ts +++ b/src/frame/middleware/block-robots.ts @@ -23,7 +23,7 @@ export function blockIndex(path: string) { return pathRegExps.some((pathRe) => pathRe.test(path)) } -const middleware = function blockRobots(req: Request, res: Response, next: NextFunction) { +function middleware(req: Request, res: Response, next: NextFunction) { if (blockIndex(req.path)) res.set('x-robots-tag', 'noindex') return next() } diff --git a/src/frame/middleware/context/generic-toc.ts b/src/frame/middleware/context/generic-toc.ts index d5147448cdb0..1c110f114459 100644 --- a/src/frame/middleware/context/generic-toc.ts +++ b/src/frame/middleware/context/generic-toc.ts @@ -170,7 +170,7 @@ async function getTocItems(node: Tree, context: Context, opts: Options): Promise } } - let childTocItems = [] + const childTocItems = [] if (child.childPages) { childTocItems.push(...(await getTocItems(child, context, opts))) } diff --git a/src/frame/middleware/helmet.ts b/src/frame/middleware/helmet.ts index 978ed674d825..e045cbbaeb80 100644 --- a/src/frame/middleware/helmet.ts +++ b/src/frame/middleware/helmet.ts @@ -18,9 +18,7 @@ const DEFAULT_OPTIONS = { crossOriginResourcePolicy: true, crossOriginEmbedderPolicy: false, // doesn't work with youtube referrerPolicy: { - // See docs-engineering #2426 - // The `... as 'no-referrer-when-downgrade'` is a workaround for TypeScript - policy: 'no-referrer-when-downgrade' as 'no-referrer-when-downgrade', + policy: 'no-referrer-when-downgrade' as const, }, // This module defines a Content Security Policy (CSP) to disallow // inline scripts and content from untrusted sources. diff --git a/src/frame/middleware/index.ts b/src/frame/middleware/index.ts index bf4fc11b46dd..54c543eae829 100644 --- a/src/frame/middleware/index.ts +++ b/src/frame/middleware/index.ts @@ -78,11 +78,15 @@ const ENABLE_FASTLY_TESTING = JSON.parse(process.env.ENABLE_FASTLY_TESTING || 'f // Catch unhandled promise rejections and passing them to Express's error handler // https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016 -const asyncMiddleware = (fn: Function) => (req: Request, res: Response, next: NextFunction) => { - Promise.resolve(fn(req, res, next)).catch(next) -} +const asyncMiddleware = + ( + fn: (req: TReq, res: Response, next: NextFunction) => T | Promise, + ) => + (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req as TReq, res, next)).catch(next) + } -export default function (app: Express) { +export default function index(app: Express) { // *** Request connection management *** if (!isTest) app.use(timeout(MAX_REQUEST_TIMEOUT)) app.use(abort) diff --git a/src/ghes-releases/scripts/create-enterprise-issue.ts b/src/ghes-releases/scripts/create-enterprise-issue.ts index 7ae0ee7f2be7..e7fe92ee61f1 100644 --- a/src/ghes-releases/scripts/create-enterprise-issue.ts +++ b/src/ghes-releases/scripts/create-enterprise-issue.ts @@ -356,7 +356,7 @@ async function isExistingIssue( ): Promise { const { labels, searchQuery, titleMatch } = opts const labelQuery = labels && labels.map((label) => `label:"${encodeURI(label)}"`).join('+') - let query = encodeURIComponent('is:issue ' + `repo:${repo} `) + let query = encodeURIComponent(`is:issue repo:${repo} `) if (searchQuery) { query += '+' + searchQuery diff --git a/src/ghes-releases/scripts/deprecate/archive-version.ts b/src/ghes-releases/scripts/deprecate/archive-version.ts index e3f0c3435b3b..b0e037fe87e5 100755 --- a/src/ghes-releases/scripts/deprecate/archive-version.ts +++ b/src/ghes-releases/scripts/deprecate/archive-version.ts @@ -63,10 +63,11 @@ main() async function main() { console.log(`Archiving Enterprise version: ${version}`) - let pageList: PageList, urls: Array + let pageList: PageList + let urls: Array if (singlePage) { const pageName = singlePage.trim().startsWith('/') ? singlePage.slice(1) : singlePage - const urls = languageKeys + urls = languageKeys .map((key) => `/${key}/enterprise-server@${version}/${pageName}`) .map((href) => `${host}${href}`) console.log(`\nScraping HTML for a single page only:\n${urls.join('\n')}\n`) diff --git a/src/ghes-releases/scripts/deprecate/rewrite-asset-paths.ts b/src/ghes-releases/scripts/deprecate/rewrite-asset-paths.ts index f5f2ff3a31f1..a86ff9231f81 100644 --- a/src/ghes-releases/scripts/deprecate/rewrite-asset-paths.ts +++ b/src/ghes-releases/scripts/deprecate/rewrite-asset-paths.ts @@ -1,6 +1,18 @@ import fs from 'fs' import path from 'path' +interface ScraperResource { + isHtml(): boolean + isCss(): boolean + getText(): string + getFilename(): string + encoding: BufferEncoding +} + +interface ResourceSavedArgs { + resource: ScraperResource +} + export class RewriteAssetPathsPlugin { tempDirectory: string localDev: boolean @@ -12,8 +24,10 @@ export class RewriteAssetPathsPlugin { this.replaceUrl = replaceUrl } - apply(registerAction: Function) { - registerAction('onResourceSaved', async ({ resource }: any) => { + apply( + registerAction: (event: string, callback: (args: ResourceSavedArgs) => Promise) => void, + ) { + registerAction('onResourceSaved', async ({ resource }: ResourceSavedArgs) => { // Show some activity process.stdout.write('.') diff --git a/src/github-apps/lib/index.ts b/src/github-apps/lib/index.ts index ed0cfa35a8f9..0aab639fb5c1 100644 --- a/src/github-apps/lib/index.ts +++ b/src/github-apps/lib/index.ts @@ -70,7 +70,9 @@ export async function getAppsServerSideProps( ? Object.values(appsItems).map((item: any) => item.displayTitle!) : Object.keys(appsItems) const appMiniToc = await getAutomatedPageMiniTocItems(titles, context) - appMiniToc && miniTocItems.push(...appMiniToc) + if (appMiniToc) { + miniTocItems.push(...appMiniToc) + } return { currentVersion, appsItems, categoriesWithoutSubcategories } } diff --git a/src/github-apps/scripts/sync.ts b/src/github-apps/scripts/sync.ts index d52b5092f0ca..000b510e0062 100755 --- a/src/github-apps/scripts/sync.ts +++ b/src/github-apps/scripts/sync.ts @@ -452,7 +452,7 @@ function getDisplayTitle( : `"${sentenceCase(title)}" ` + resourceGroup + ' permissions' : !resourceGroup ? sentenceCase(title) + ' permissions' - : sentenceCase(resourceGroup) + ' permissions for ' + `"${title}"` + : sentenceCase(resourceGroup) + ` permissions for "${title}"` return { title, displayTitle } } diff --git a/src/graphql/pages/reference.tsx b/src/graphql/pages/reference.tsx index 872c444da5db..41bf36fbdb1d 100644 --- a/src/graphql/pages/reference.tsx +++ b/src/graphql/pages/reference.tsx @@ -18,7 +18,7 @@ import { type Props = { mainContext: MainContextT automatedPageContext: AutomatedPageContextT - schema: Object + schema: object language: string graphqlPageName: string objects?: ObjectT[] diff --git a/src/graphql/scripts/sync.ts b/src/graphql/scripts/sync.ts index 11af29181759..772b811f051d 100755 --- a/src/graphql/scripts/sync.ts +++ b/src/graphql/scripts/sync.ts @@ -28,7 +28,7 @@ const versionsToBuild = Object.keys(allVersions) main() -let allIgnoredChanges = [] +const allIgnoredChanges = [] async function main() { for (const version of versionsToBuild) { diff --git a/src/landings/components/ProductSelectionCard.tsx b/src/landings/components/ProductSelectionCard.tsx index 80d83c1ff11f..415d217cc941 100644 --- a/src/landings/components/ProductSelectionCard.tsx +++ b/src/landings/components/ProductSelectionCard.tsx @@ -2,12 +2,38 @@ import type { ProductGroupT } from '@/landings/components/ProductSelections' import React from 'react' import { Link } from '@/frame/components/Link' -import * as Octicons from '@primer/octicons-react' -import { LinkExternalIcon } from '@primer/octicons-react' +import { + LinkExternalIcon, + RocketIcon, + CommentDiscussionIcon, + CopilotIcon, + GearIcon, + ShieldLockIcon, + DeviceMobileIcon, + ProjectIcon, + OrganizationIcon, + CodeSquareIcon, + GlobeIcon, + PencilIcon, +} from '@primer/octicons-react' type ProductSelectionCardProps = { group: ProductGroupT } +const octiconMap: { [name: string]: React.FunctionComponent } = { + LinkExternalIcon, + RocketIcon, + CommentDiscussionIcon, + CopilotIcon, + GearIcon, + ShieldLockIcon, + DeviceMobileIcon, + ProjectIcon, + OrganizationIcon, + CodeSquareIcon, + GlobeIcon, + PencilIcon, +} export const ProductSelectionCard = ({ group }: ProductSelectionCardProps) => { // Don't display the group if it has no children due to versioning @@ -27,9 +53,11 @@ export const ProductSelectionCard = ({ group }: ProductSelectionCardProps) => { ) } else if (group.octicon) { - const octicon: React.FunctionComponent = ( - Octicons as { [name: string]: React.FunctionComponent } - )[group.octicon] as React.FunctionComponent + const octicon: React.FunctionComponent = octiconMap[group.octicon] + + if (!octicon) { + throw new Error(`Octicon ${group.octicon} not found`) + } return (
diff --git a/src/landings/components/bespoke/BespokeLanding.tsx b/src/landings/components/bespoke/BespokeLanding.tsx index 5dc38b35890a..a7d46ff8871a 100644 --- a/src/landings/components/bespoke/BespokeLanding.tsx +++ b/src/landings/components/bespoke/BespokeLanding.tsx @@ -6,7 +6,16 @@ import { UtmPreserver } from '@/frame/components/UtmPreserver' import { LandingCarousel } from '@/landings/components/shared/LandingCarousel' export const BespokeLanding = () => { - const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext() + const { + title, + intro, + heroImage, + introLinks, + tocItems, + recommended, + includedCategories, + landingType, + } = useLandingContext() return ( @@ -16,7 +25,11 @@ export const BespokeLanding = () => {
- +
diff --git a/src/landings/components/discovery/DiscoveryLanding.tsx b/src/landings/components/discovery/DiscoveryLanding.tsx index c8fe5a2d5974..6e090e5492e9 100644 --- a/src/landings/components/discovery/DiscoveryLanding.tsx +++ b/src/landings/components/discovery/DiscoveryLanding.tsx @@ -6,7 +6,16 @@ import { LandingCarousel } from '@/landings/components/shared/LandingCarousel' import { UtmPreserver } from '@/frame/components/UtmPreserver' export const DiscoveryLanding = () => { - const { title, intro, heroImage, introLinks, tocItems, recommended } = useLandingContext() + const { + title, + intro, + heroImage, + introLinks, + tocItems, + recommended, + includedCategories, + landingType, + } = useLandingContext() return ( @@ -15,7 +24,11 @@ export const DiscoveryLanding = () => {
- +
diff --git a/src/landings/components/shared/LandingArticleGridWithFilter.tsx b/src/landings/components/shared/LandingArticleGridWithFilter.tsx index 841f86bd81c7..e23ae00c90f3 100644 --- a/src/landings/components/shared/LandingArticleGridWithFilter.tsx +++ b/src/landings/components/shared/LandingArticleGridWithFilter.tsx @@ -6,11 +6,14 @@ import cx from 'classnames' import { Link } from '@/frame/components/Link' import { useTranslation } from '@/languages/components/useTranslation' import { ArticleCardItems, ChildTocItem, TocItem } from '@/landings/types' +import { LandingType } from '@/landings/context/LandingContext' import styles from './LandingArticleGridWithFilter.module.scss' type ArticleGridProps = { tocItems: TocItem[] + includedCategories?: string[] + landingType: LandingType } const ALL_CATEGORIES = 'all_categories' @@ -66,7 +69,7 @@ const useResponsiveArticlesPerPage = () => { return articlesPerPage } -export const ArticleGrid = ({ tocItems }: ArticleGridProps) => { +export const ArticleGrid = ({ tocItems, includedCategories, landingType }: ArticleGridProps) => { const { t } = useTranslation('product_landing') const [searchQuery, setSearchQuery] = useState('') const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORIES) @@ -83,21 +86,42 @@ export const ArticleGrid = ({ tocItems }: ArticleGridProps) => { [tocItems], ) + // Filter articles based on includedCategories for discovery landing pages + // For bespoke landing pages, show all articles regardless of includedCategories + const filteredArticlesByLandingType = useMemo(() => { + if (landingType === 'discovery' && includedCategories && includedCategories.length > 0) { + // For discovery pages, only include articles that have at least one matching category + return allArticles.filter((article) => { + if (!article.category || article.category.length === 0) return false + return article.category.some((cat) => + includedCategories.some((included) => included.toLowerCase() === cat.toLowerCase()), + ) + }) + } + // For bespoke pages or when includedCategories is empty/undefined, return all articles + return allArticles + }, [allArticles, includedCategories, landingType]) + // Reset to first page when articlesPerPage changes (screen size changes) useEffect(() => { setCurrentPage(1) }, [articlesPerPage]) - // Extract unique categories from the articles + // Extract unique categories from the filtered articles, filtering dropdown by includedCategories if provided const categories: string[] = [ ALL_CATEGORIES, - ...Array.from(new Set(allArticles.flatMap((item) => item.category || []))).sort((a, b) => - a.localeCompare(b), - ), + ...Array.from(new Set(filteredArticlesByLandingType.flatMap((item) => item.category || []))) + .filter((category) => { + if (!includedCategories || includedCategories.length === 0) return true + // Case-insensitive comparison for dropdown filtering + const lowerCategory = category.toLowerCase() + return includedCategories.some((included) => included.toLowerCase() === lowerCategory) + }) + .sort((a, b) => a.localeCompare(b)), ] const applyFilters = () => { - let results = allArticles + let results = filteredArticlesByLandingType if (searchQuery) { results = results.filter((token) => { diff --git a/src/landings/context/LandingContext.tsx b/src/landings/context/LandingContext.tsx index 1fa89ab55e72..e6dc490b38cb 100644 --- a/src/landings/context/LandingContext.tsx +++ b/src/landings/context/LandingContext.tsx @@ -26,6 +26,8 @@ export type LandingContextT = { introLinks?: Record // For journey landing pages journeyTracks?: JourneyTrack[] + // For article grid category filtering + includedCategories?: string[] } export const LandingContext = createContext(null) @@ -83,5 +85,6 @@ export const getLandingContextFromRequest = async ( introLinks: page.introLinks || null, recommended, journeyTracks, + includedCategories: page.includedCategories || [], } } diff --git a/src/languages/lib/render-with-fallback.ts b/src/languages/lib/render-with-fallback.ts index 502c8c38b7e5..3a76c90da54c 100644 --- a/src/languages/lib/render-with-fallback.ts +++ b/src/languages/lib/render-with-fallback.ts @@ -78,7 +78,7 @@ export function createTranslationFallbackComment(error: Error, property: string) } else if (isAutotitleError(error)) { // For AUTOTITLE errors, include the error message if (error.message) { - let cleanMessage = error.message + const cleanMessage = error.message .replace(/\n/g, ' ') .replace(/\s+/g, ' ') .trim() diff --git a/src/learning-track/middleware/learning-track.ts b/src/learning-track/middleware/learning-track.ts index b9c1cab7911e..1bec3b34c55a 100644 --- a/src/learning-track/middleware/learning-track.ts +++ b/src/learning-track/middleware/learning-track.ts @@ -175,7 +175,7 @@ async function indexOfLearningTrackGuide( const renderOpts = { textOnly: true } for (let i = 0; i < trackGuidePaths.length; i++) { // Learning track URLs may have Liquid conditionals. - let renderedGuidePath = await executeWithFallback( + const renderedGuidePath = await executeWithFallback( context, () => renderContent(trackGuidePaths[i], context, renderOpts), () => '', // todo use english trackGuidePaths[i] diff --git a/src/links/scripts/check-github-github-links.ts b/src/links/scripts/check-github-github-links.ts index 7baa9a83dbec..4a3b78e8369d 100755 --- a/src/links/scripts/check-github-github-links.ts +++ b/src/links/scripts/check-github-github-links.ts @@ -180,7 +180,10 @@ async function main(opts: MainOptions, args: string[]) { 'utf-8', ) } - const brokenLinks: {}[] = [] + const brokenLinks: { + linkPath: string + file: string + }[] = [] // Break up the long list of URLs to test into batches for (const batch of [...Array(Math.floor(docsLinksFiles.length / BATCH_SIZE)).keys()]) { diff --git a/src/metrics/scripts/docstat.ts b/src/metrics/scripts/docstat.ts index 2e1f6bad7afc..261bb0c821dc 100644 --- a/src/metrics/scripts/docstat.ts +++ b/src/metrics/scripts/docstat.ts @@ -145,7 +145,7 @@ let cleanPath = getCleanPath(providedPath) // Get the version let version: string | null = getVersion(cleanPath) -let usingFptOnly = !!options.fptOnly +const usingFptOnly = !!options.fptOnly // If the URL does not specify a version, default to all versions unless --fptOnly is passed if (version === FREE_PRO_TEAM) { diff --git a/src/observability/middleware/handle-errors.ts b/src/observability/middleware/handle-errors.ts index c9545cc61fb4..dd637d91a1be 100644 --- a/src/observability/middleware/handle-errors.ts +++ b/src/observability/middleware/handle-errors.ts @@ -1,4 +1,4 @@ -import type { NextFunction, Response, ErrorRequestHandler } from 'express' +import type { NextFunction, Response } from 'express' import FailBot from '../lib/failbot' import { nextApp } from '@/frame/middleware/next' @@ -50,7 +50,7 @@ function timedOut(req: ExtendedRequest) { statsd.increment('middleware.timeout', 1, incrementTags) } -const handleError: ErrorRequestHandler = async function handleError( +async function handleError( error: ErrorWithCode | number, req: ExtendedRequest, res: Response, diff --git a/src/observability/tests/get-automatic-request-logger.ts b/src/observability/tests/get-automatic-request-logger.ts index 290d2ef28b5d..1f66fe812ff9 100644 --- a/src/observability/tests/get-automatic-request-logger.ts +++ b/src/observability/tests/get-automatic-request-logger.ts @@ -43,7 +43,7 @@ describe('getAutomaticRequestLogger', () => { } // Override res.end to simulate response completion - const endOverride = function (this: any, chunk?: any, encoding?: any) { + function endOverride(this: any, chunk?: any, encoding?: any) { if (!responseEnded) { responseEnded = true // Simulate a small delay for response time @@ -143,7 +143,7 @@ describe('getAutomaticRequestLogger', () => { } // Override res.end to simulate response completion - const endOverride = function (this: any, chunk?: any, encoding?: any) { + function endOverride(this: any, chunk?: any, encoding?: any) { if (!responseEnded) { responseEnded = true // Simulate a small delay for response time diff --git a/src/rest/components/RestAuth.tsx b/src/rest/components/RestAuth.tsx index 902244501dfa..aaed841f339a 100644 --- a/src/rest/components/RestAuth.tsx +++ b/src/rest/components/RestAuth.tsx @@ -75,7 +75,7 @@ function FineGrainedAccess({ progAccess }: FineGrainedProps) { // Each object represents a set of permissions containing one // or more key-value pairs. All permissions in a set are required. // If there is more than one set of permissions, any set can be used. - const formattedPermissions = progAccess.permissions.map((permissionSet: Object, index) => { + const formattedPermissions = progAccess.permissions.map((permissionSet: object, index) => { // Given the example above, the first object is now an array of tuples // [['"Actions" repository permissions', 'read'], ['"Administration" organization permissions', 'read']] // that can be formatted as a string like `"Administration" organization permissions (write)' diff --git a/src/rest/components/types.ts b/src/rest/components/types.ts index 49e8d4731f84..4213a3d2e1c1 100644 --- a/src/rest/components/types.ts +++ b/src/rest/components/types.ts @@ -18,7 +18,7 @@ export interface ProgAccessT { userToServerRest?: boolean serverToServer?: boolean fineGrainedPat?: boolean - permissions: Array + permissions: Array allowPermissionlessAccess?: boolean allowsPublicRead?: boolean basicAuth?: boolean @@ -90,8 +90,8 @@ export type ExampleT = { statusCode: string contentType?: string description: string - example?: Object - schema?: Object + example?: object + schema?: object } } diff --git a/src/rest/pages/category.tsx b/src/rest/pages/category.tsx index 0a7f1d5701d2..cb0fbf2dd86d 100644 --- a/src/rest/pages/category.tsx +++ b/src/rest/pages/category.tsx @@ -200,7 +200,9 @@ export const getServerSideProps: GetServerSideProps = async (context) => req.context, )) as MinitocItemsT - restOperationsMiniTocItems && miniTocItems.push(...restOperationsMiniTocItems) + if (restOperationsMiniTocItems) { + miniTocItems.push(...restOperationsMiniTocItems) + } } // Replace the toc items in the context with the REST toc items we just diff --git a/src/rest/pages/subcategory.tsx b/src/rest/pages/subcategory.tsx index b6d7774f2efb..6a8958a0ec58 100644 --- a/src/rest/pages/subcategory.tsx +++ b/src/rest/pages/subcategory.tsx @@ -80,7 +80,9 @@ export const getServerSideProps: GetServerSideProps = async (context) => req.context, )) as MinitocItemsT - restOperationsMiniTocItems && miniTocItems.push(...restOperationsMiniTocItems) + if (restOperationsMiniTocItems) { + miniTocItems.push(...restOperationsMiniTocItems) + } } const mainContext = await getMainContext(req, res) diff --git a/src/rest/scripts/test-open-api-schema.ts b/src/rest/scripts/test-open-api-schema.ts index 77e90dd85264..bf006936a5ec 100755 --- a/src/rest/scripts/test-open-api-schema.ts +++ b/src/rest/scripts/test-open-api-schema.ts @@ -98,9 +98,11 @@ async function createCheckContentDirectory(contentFiles: string[]): Promise { - !checkContent[version][category] - ? (checkContent[version][category] = [subCategory]) - : checkContent[version][category].push(subCategory) + if (!checkContent[version][category]) { + checkContent[version][category] = [subCategory] + } else { + checkContent[version][category].push(subCategory) + } checkContent[version][category].sort() }) } @@ -121,9 +123,11 @@ function getOnlyApiVersions(version: string): string[] { function createCheckObj(): CheckObject { const versions: CheckObject = {} Object.keys(allVersions).forEach((version) => { - isApiVersioned(version) - ? getOnlyApiVersions(version).forEach((apiVersion) => (versions[apiVersion] = {})) - : (versions[`${allVersions[version].version}`] = {}) + if (isApiVersioned(version)) { + getOnlyApiVersions(version).forEach((apiVersion) => (versions[apiVersion] = {})) + } else { + versions[`${allVersions[version].version}`] = {} + } }) return versions diff --git a/src/rest/scripts/update-files.ts b/src/rest/scripts/update-files.ts index a9698ba9ad41..c4619cf5a6d5 100755 --- a/src/rest/scripts/update-files.ts +++ b/src/rest/scripts/update-files.ts @@ -242,7 +242,7 @@ async function validateInputParameters(): Promise { } // Check that the source repo exists. - for (let sourceRepoDirectory of sourceRepoDirectories) { + for (const sourceRepoDirectory of sourceRepoDirectories) { if (!existsSync(sourceRepoDirectory)) { const errorMsg = sourceRepoDirectory === 'github' || sourceRepoDirectory === GITHUB_REP_DIR diff --git a/src/rest/scripts/utils/inject-models-schema.ts b/src/rest/scripts/utils/inject-models-schema.ts index 7cb723d140a2..4b40dd0c0ac9 100644 --- a/src/rest/scripts/utils/inject-models-schema.ts +++ b/src/rest/scripts/utils/inject-models-schema.ts @@ -77,13 +77,13 @@ export async function injectModelsSchema(schema: any, schemaName: string): Promi tags: operationObject.tags || ['models'], // Only use 'models' if no tags present verb: operation, requestPath: path, - category: category, + category, subcategory: operationObject['x-github']?.subcategory || '', summary: name, - description: description, + description, 'x-github': { ...operationObject['x-github'], // Preserve all x-github metadata - category: category, + category, enabledForGitHubApps: operationObject['x-github']?.enabledForGitHubApps, githubCloudOnly: operationObject['x-github']?.githubCloudOnly, permissions: operationObject['x-github']?.permissions || {}, diff --git a/src/search/components/helpers/execute-search-actions.ts b/src/search/components/helpers/execute-search-actions.ts index e6aa9a43bc1c..3ed226121858 100644 --- a/src/search/components/helpers/execute-search-actions.ts +++ b/src/search/components/helpers/execute-search-actions.ts @@ -74,9 +74,9 @@ export async function executeCombinedSearch( debug = false, abortSignal?: AbortSignal, ) { - let language = router.locale || 'en' + const language = router.locale || 'en' - const params = new URLSearchParams({ query: query, version, language }) + const params = new URLSearchParams({ query, version, language }) if (debug) { params.set('debug', '1') } diff --git a/src/search/components/hooks/useAISearchAutocomplete.ts b/src/search/components/hooks/useAISearchAutocomplete.ts index 49940db0d583..5f4531c59e6a 100644 --- a/src/search/components/hooks/useAISearchAutocomplete.ts +++ b/src/search/components/hooks/useAISearchAutocomplete.ts @@ -29,7 +29,7 @@ const DEBOUNCE_TIME = 100 // In milliseconds // Results are only cached for the current session // We cache results so if a user presses backspace, we can show the results immediately without burdening the API -let sessionCache = {} as Record +const sessionCache = {} as Record // Helper to incorporate version & locale into the cache key function getCacheKey(query: string, version: string, locale: string) { diff --git a/src/search/components/hooks/useMultiQueryParams.ts b/src/search/components/hooks/useMultiQueryParams.ts index 07f5935a7a09..30b87916a44f 100644 --- a/src/search/components/hooks/useMultiQueryParams.ts +++ b/src/search/components/hooks/useMultiQueryParams.ts @@ -45,9 +45,17 @@ export function useMultiQueryParams() { const searchParams = new URLSearchParams(asPathQuery) initialKeys.forEach((key) => { if (key === 'search-overlay-ask-ai') { - newParams[key] === 'true' ? searchParams.set(key, 'true') : searchParams.delete(key) + if (newParams[key] === 'true') { + searchParams.set(key, 'true') + } else { + searchParams.delete(key) + } } else { - newParams[key] ? searchParams.set(key, newParams[key]) : searchParams.delete(key) + if (newParams[key]) { + searchParams.set(key, newParams[key]) + } else { + searchParams.delete(key) + } } }) const paramsString = searchParams.toString() ? `?${searchParams.toString()}` : '' diff --git a/src/search/components/input/AskAIResults.tsx b/src/search/components/input/AskAIResults.tsx index 7638c4284717..5fce5019c0d6 100644 --- a/src/search/components/input/AskAIResults.tsx +++ b/src/search/components/input/AskAIResults.tsx @@ -566,6 +566,6 @@ function sendAISearchResultEvent({ ai_search_result_response_status: status, ai_search_result_connected_event_id: connectedEventId, eventGroupKey: ASK_AI_EVENT_GROUP, - eventGroupId: eventGroupId, + eventGroupId, }) } diff --git a/src/search/components/input/SearchOverlay.tsx b/src/search/components/input/SearchOverlay.tsx index 126d2ead66e8..af97f3a04992 100644 --- a/src/search/components/input/SearchOverlay.tsx +++ b/src/search/components/input/SearchOverlay.tsx @@ -358,7 +358,7 @@ export function SearchOverlay({ if (searchParams.has('query')) { searchParams.delete('query') } - router.push(`${selectedOption.url}?${searchParams.toString()}` || '') + router.push(`${selectedOption.url}?${searchParams.toString()}`) onClose() } @@ -412,12 +412,12 @@ export function SearchOverlay({ if (searchParams.has('query')) { searchParams.delete('query') } - window.open(`${url}?${searchParams.toString()}` || '', '_blank') + window.open(`${url}?${searchParams.toString()}`, '_blank') } // Handle keyboard navigation of suggestions const handleKeyDown = (event: React.KeyboardEvent) => { - let optionsLength = listElementsRef.current?.length ?? 0 + const optionsLength = listElementsRef.current?.length ?? 0 if (event.key === 'ArrowDown') { event.preventDefault() if (optionsLength > 0) { @@ -855,8 +855,8 @@ function renderSearchGroups( ) { const groups = [] - let isInAskAIState = askAIState?.isAskAIState && !askAIState.aiSearchError - let isInAskAIStateButNoAnswer = isInAskAIState && askAIState.aiCouldNotAnswer + const isInAskAIState = askAIState?.isAskAIState && !askAIState.aiSearchError + const isInAskAIStateButNoAnswer = isInAskAIState && askAIState.aiCouldNotAnswer // This spinner is for both the AI search and the general search results. // We already show a spinner when streaming AI response, so don't want to show 2 here diff --git a/src/search/lib/get-elasticsearch-results/ai-search-autocomplete.ts b/src/search/lib/get-elasticsearch-results/ai-search-autocomplete.ts index 1a3bdab704b4..554dd156ed19 100644 --- a/src/search/lib/get-elasticsearch-results/ai-search-autocomplete.ts +++ b/src/search/lib/get-elasticsearch-results/ai-search-autocomplete.ts @@ -19,7 +19,7 @@ export async function getAISearchAutocompleteResults({ const t0 = new Date() const client = getElasticsearchClient() as Client - let searchQuery: any = { + const searchQuery: any = { index: indexName, size, // Send absolutely minimal from Elasticsearch to here. Less data => faster. diff --git a/src/search/middleware/search-routes.ts b/src/search/middleware/search-routes.ts index 1138f569e432..145addcf3a67 100644 --- a/src/search/middleware/search-routes.ts +++ b/src/search/middleware/search-routes.ts @@ -70,7 +70,7 @@ router.get( // If no query is provided, we want to return the top 5 most popular terms // This is a special case for AI search autocomplete // So we use `force` to allow the query to be empty without the usual validation error - let force = {} as any + const force = {} as any if (!req.query.query) { force.query = '' } diff --git a/src/search/scripts/index/utils/indexing-elasticsearch-utils.ts b/src/search/scripts/index/utils/indexing-elasticsearch-utils.ts index cf7fcc087cf8..5b2b559d5499 100644 --- a/src/search/scripts/index/utils/indexing-elasticsearch-utils.ts +++ b/src/search/scripts/index/utils/indexing-elasticsearch-utils.ts @@ -76,8 +76,8 @@ export async function populateIndex( }, ) - if (bulkResponse.errors) { - console.error(`Bulk response errors: ${bulkResponse.errors}`) + if (bulkResponse.failed > 0) { + console.error(`Bulk response failed: ${bulkResponse.failed} documents failed`) throw new Error('Bulk errors happened.') } const t1 = new Date() @@ -140,7 +140,7 @@ export async function updateAlias( ) for (const index of indices) { - if (index.index !== indexAlias && index.index.startsWith(indexName)) { + if (index.index && index.index !== indexAlias && index.index.startsWith(indexName)) { aliasUpdates.push({ remove_index: { index: index.index } }) console.log('Deleting old index', index.index) } diff --git a/src/search/scripts/index/utils/retry-on-error-test.ts b/src/search/scripts/index/utils/retry-on-error-test.ts index bed48738f492..93e251b32d01 100644 --- a/src/search/scripts/index/utils/retry-on-error-test.ts +++ b/src/search/scripts/index/utils/retry-on-error-test.ts @@ -21,9 +21,9 @@ import { sleep } from '@/search/lib/helpers/time' -export async function retryOnErrorTest( - errorTest: (error: any) => boolean, - callback: Function, +export async function retryOnErrorTest( + errorTest: (error: unknown) => boolean, + callback: () => Promise, { attempts = 4, sleepTime = 1000, @@ -37,7 +37,7 @@ export async function retryOnErrorTest( jitterPercent?: number onError?: (error: Error, attempts: number, sleepTime: number) => void } = {}, -) { +): Promise { while (true) { try { return await callback() diff --git a/src/search/tests/api-ai-search.ts b/src/search/tests/api-ai-search.ts index abf9dddcd545..3a0ce311ca70 100644 --- a/src/search/tests/api-ai-search.ts +++ b/src/search/tests/api-ai-search.ts @@ -10,7 +10,7 @@ describe('AI Search Routes', () => { afterAll(() => stopMockServer()) test('/api/ai-search/v1 should handle a successful response', async () => { - let apiBody = { query: 'How do I create a Repository?', language: 'en', version: 'dotcom' } + const apiBody = { query: 'How do I create a Repository?', language: 'en', version: 'dotcom' } const response = await fetch('http://localhost:4000/api/ai-search/v1', { method: 'POST', @@ -80,7 +80,7 @@ describe('AI Search Routes', () => { }) test('should handle validation errors: query missing', async () => { - let body = { language: 'en', version: 'dotcom' } + const body = { language: 'en', version: 'dotcom' } const response = await post('/api/ai-search/v1', { body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' }, @@ -95,7 +95,7 @@ describe('AI Search Routes', () => { }) test('should handle validation errors: version missing', async () => { - let body = { query: 'example query' } + const body = { query: 'example query' } const response = await post('/api/ai-search/v1', { body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' }, @@ -110,7 +110,7 @@ describe('AI Search Routes', () => { }) test('should handle multiple validation errors: query missing and version', async () => { - let body = { language: 'fr', version: 'fpt' } + const body = { language: 'fr', version: 'fpt' } const response = await post('/api/ai-search/v1', { body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' }, @@ -140,7 +140,7 @@ describe('AI Search Routes', () => { if (response.body) { const reader = response.body.getReader() const decoder = new TextDecoder() - let chunks = [] + const chunks = [] try { while (true) { diff --git a/src/types.ts b/src/types.ts index 1a709d0c8efa..3c39cac8056f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -139,7 +139,7 @@ export type Context = { enPage?: Page productNames?: ProductNames currentVersion?: string - process?: { env: {} } + process?: { env: Record } site?: { data: { ui: any diff --git a/src/versions/scripts/use-short-versions.ts b/src/versions/scripts/use-short-versions.ts index e4597a2dbd42..bbcfc6cb8584 100755 --- a/src/versions/scripts/use-short-versions.ts +++ b/src/versions/scripts/use-short-versions.ts @@ -131,7 +131,8 @@ main().then( // Using any for token objects as liquidjs doesn't provide TypeScript types function removeInputProps(arrayOfObjects: any[]): any[] { return arrayOfObjects.map((obj: any) => { - delete obj.input || delete obj.token.input + delete obj.input + delete obj.token.input return obj }) } diff --git a/src/webhooks/components/types.ts b/src/webhooks/components/types.ts index 70cf3536891f..4c1ae0f77236 100644 --- a/src/webhooks/components/types.ts +++ b/src/webhooks/components/types.ts @@ -21,7 +21,7 @@ interface WebhookActionData { codeExamples: Array availability: Array action: string - payloadExample?: Object + payloadExample?: object } export interface WebhookAction { name: string diff --git a/src/webhooks/pages/webhook-events-and-payloads.tsx b/src/webhooks/pages/webhook-events-and-payloads.tsx index caf2d19653aa..96c48ea8d29d 100644 --- a/src/webhooks/pages/webhook-events-and-payloads.tsx +++ b/src/webhooks/pages/webhook-events-and-payloads.tsx @@ -96,7 +96,9 @@ export const getServerSideProps: GetServerSideProps = async (context) => webhooks.map((webhook) => webhook.data.category), context, ) - webhooksMiniTocs && miniTocItems.push(...webhooksMiniTocs) + if (webhooksMiniTocs) { + miniTocItems.push(...webhooksMiniTocs) + } return { props: { diff --git a/src/workflows/content-changes-table-comment.ts b/src/workflows/content-changes-table-comment.ts index 1bd6949653ee..26750f4658d3 100755 --- a/src/workflows/content-changes-table-comment.ts +++ b/src/workflows/content-changes-table-comment.ts @@ -10,7 +10,7 @@ import fs from 'node:fs' import path from 'node:path' -import * as github from '@actions/github' +import github from '@actions/github' import core from '@actions/core' import walk from 'walk-sync' diff --git a/src/workflows/find-past-built-pr.ts b/src/workflows/find-past-built-pr.ts index a2bf28a91006..c2dbdebb1108 100644 --- a/src/workflows/find-past-built-pr.ts +++ b/src/workflows/find-past-built-pr.ts @@ -22,7 +22,7 @@ async function main() { console.log('ID:', issue.id) console.log('Number:', issue.number) console.log('URL:', issue.html_url) - number = issue.number + number = String(issue.number) if (number) { // We've found the issue (pull request), but before we accept // this `number`, check that the issue isn't locked. diff --git a/src/workflows/git-utils.ts b/src/workflows/git-utils.ts index fef5e873ea53..d5ac998628a3 100644 --- a/src/workflows/git-utils.ts +++ b/src/workflows/git-utils.ts @@ -234,12 +234,12 @@ async function searchCode( } } -async function secondaryRateLimitRetry( - callable: Function, - args: Record, +async function secondaryRateLimitRetry>( + callable: (args: TArgs) => Promise, + args: TArgs, maxAttempts = 10, sleepTime = 1000, -) { +): Promise { try { const response = await callable(args) return response diff --git a/src/workflows/secondary-ratelimit-retry.ts b/src/workflows/secondary-ratelimit-retry.ts index b12a087e4cbc..ad0fe2e0e88b 100644 --- a/src/workflows/secondary-ratelimit-retry.ts +++ b/src/workflows/secondary-ratelimit-retry.ts @@ -7,10 +7,10 @@ const DEFAULT_ATTEMPTS = parseInt(process.env.SECONDARY_RATELIMIT_RETRY_ATTEMPTS // "You have exceeded a secondary rate limit". // More info about what they are here: // https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#about-secondary-rate-limits -export async function octoSecondaryRatelimitRetry( - fn: Function, +export async function octoSecondaryRatelimitRetry( + fn: () => Promise, { attempts = DEFAULT_ATTEMPTS, sleepTime = DEFAULT_SLEEPTIME } = {}, -) { +): Promise { let tries = 0 while (true) { try { diff --git a/src/workflows/walk-files.ts b/src/workflows/walk-files.ts index e1343334c165..017855e7f982 100644 --- a/src/workflows/walk-files.ts +++ b/src/workflows/walk-files.ts @@ -25,11 +25,17 @@ export function readFiles(dir = 'content', ext = 'md', opts = {}) { return paths.map((path) => [path, fs.readFileSync(path, 'utf8')]) } -export function filterFiles(files: [path: string, file: string][], fn: Function) { +export function filterFiles( + files: [path: string, file: string][], + fn: (path: string, file: string) => boolean, +) { return files.filter(([path, file]) => fn(path, file)) } -export function withFiles(files: [path: string, file: string][], fn: Function) { +export function withFiles( + files: [path: string, file: string][], + fn: (path: string, file: string) => string, +) { return files.map(([path, file]) => [path, fn(path, file)]) }