diff --git a/.github/workflows/reviewers-docs-engineering.yml b/.github/workflows/reviewers-docs-engineering.yml index 422139ef62d4..cb5e3527ea6f 100644 --- a/.github/workflows/reviewers-docs-engineering.yml +++ b/.github/workflows/reviewers-docs-engineering.yml @@ -54,11 +54,22 @@ jobs: # These are usually cross-platform `npm install` churn from contributors # editing content. We comment with reset instructions instead of pulling in # docs-engineering for review. + # + # Dependabot is exempt. Its security updates for transitive dependencies + # change only the lockfile, because the dependency is not in package.json. + # Those PRs are intentional, so the reset instructions are wrong and + # suppressing the review request leaves them with no reviewer at all. - name: Detect lockfile-only churn id: detect env: GH_TOKEN: ${{ secrets.DOCS_BOT_PAT_BASE }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} run: | + if [ "$PR_AUTHOR" = "dependabot[bot]" ]; then + echo "Author is Dependabot; skipping lockfile churn detection." + echo "lockfile_only=false" >> "$GITHUB_OUTPUT" + exit 0 + fi changed=$(gh pr diff "$PR" --name-only) echo "Changed files:" echo "$changed" diff --git a/src/ghes-releases/lib/deprecation-steps.md b/src/ghes-releases/lib/deprecation-steps.md index 1a4f7606a65b..2adcea739a3e 100644 --- a/src/ghes-releases/lib/deprecation-steps.md +++ b/src/ghes-releases/lib/deprecation-steps.md @@ -1,9 +1,9 @@ --- -title: Enterprise Server {{ release-number }} deprecation steps +title: "[{{ deprecation-date }}] Enterprise Server {{ release-number }} deprecation steps" labels: - enterprise deprecation - - priority-1 - - time sensitive + - engineering + - priority-2 - workflow-generated --- diff --git a/src/ghes-releases/scripts/create-enterprise-issue.ts b/src/ghes-releases/scripts/create-enterprise-issue.ts index d6aeed121b1b..99a78b00d1e4 100644 --- a/src/ghes-releases/scripts/create-enterprise-issue.ts +++ b/src/ghes-releases/scripts/create-enterprise-issue.ts @@ -104,12 +104,14 @@ async function createDeprecationIssue() { const { data, content } = matter(issueTemplate) const { title, labels } = data const renderedContent = content.replaceAll('{{ release-number }}', oldestSupported) - const body = `GHES ${oldestSupported} deprecation occurs on ${deprecationDate}. + const body = `GHES ${oldestSupported} deprecation occurs on ${deprecationDate}. Don't start before that date. Late is fine. ${renderedContent}` await createIssue( repo, - title.replaceAll('{{ release-number }}', oldestSupported), + title + .replaceAll('{{ release-number }}', oldestSupported) + .replaceAll('{{ deprecation-date }}', deprecationDate), body, labels, oldestSupported, @@ -378,13 +380,14 @@ async function isExistingIssue( const issues = await octokit.request(`GET /search/issues?q=${query}`) if (titleMatch) { - for (const issue of issues.data.items) { - if (issue.title.includes(titleMatch)) { - console.log(`Issue ${issue.html_url} already exists for this release.`) - return true - } - return false + const match = issues.data.items.find((issue: { title: string }) => + issue.title.includes(titleMatch), + ) + if (match) { + console.log(`Issue ${match.html_url} already exists for this release.`) + return true } + return false } const issueExists = !!issues.data.items.length diff --git a/src/graphql/data/fpt/schema-repos.json b/src/graphql/data/fpt/schema-repos.json index ad0a9764dc63..a61678bc41b6 100644 --- a/src/graphql/data/fpt/schema-repos.json +++ b/src/graphql/data/fpt/schema-repos.json @@ -683,14 +683,14 @@ "fields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" @@ -9990,14 +9990,14 @@ "inputFields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" diff --git a/src/graphql/data/ghec/schema-repos.json b/src/graphql/data/ghec/schema-repos.json index ad0a9764dc63..74470aee71f9 100644 --- a/src/graphql/data/ghec/schema-repos.json +++ b/src/graphql/data/ghec/schema-repos.json @@ -683,14 +683,14 @@ "fields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" @@ -9990,14 +9990,14 @@ "inputFields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" diff --git a/src/graphql/data/ghes-3.17/schema-repos.json b/src/graphql/data/ghes-3.17/schema-repos.json index 538637745abc..5f990c0a4686 100644 --- a/src/graphql/data/ghes-3.17/schema-repos.json +++ b/src/graphql/data/ghes-3.17/schema-repos.json @@ -411,14 +411,14 @@ "fields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" @@ -8408,14 +8408,14 @@ "inputFields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" diff --git a/src/graphql/data/ghes-3.18/schema-repos.json b/src/graphql/data/ghes-3.18/schema-repos.json index 36a30308cc46..e6f088208d0a 100644 --- a/src/graphql/data/ghes-3.18/schema-repos.json +++ b/src/graphql/data/ghes-3.18/schema-repos.json @@ -411,14 +411,14 @@ "fields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" @@ -8823,14 +8823,14 @@ "inputFields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" diff --git a/src/graphql/data/ghes-3.19/schema-repos.json b/src/graphql/data/ghes-3.19/schema-repos.json index 09efebdc0f8f..8a77c40a9d4a 100644 --- a/src/graphql/data/ghes-3.19/schema-repos.json +++ b/src/graphql/data/ghes-3.19/schema-repos.json @@ -411,14 +411,14 @@ "fields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" @@ -8843,14 +8843,14 @@ "inputFields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" diff --git a/src/graphql/data/ghes-3.20/schema-repos.json b/src/graphql/data/ghes-3.20/schema-repos.json index 38767d51314d..d1ab923420a4 100644 --- a/src/graphql/data/ghes-3.20/schema-repos.json +++ b/src/graphql/data/ghes-3.20/schema-repos.json @@ -566,14 +566,14 @@ "fields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" @@ -9362,14 +9362,14 @@ "inputFields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" diff --git a/src/graphql/data/ghes-3.21/schema-repos.json b/src/graphql/data/ghes-3.21/schema-repos.json index 7a6bf0ba4843..2002dedc47bc 100644 --- a/src/graphql/data/ghes-3.21/schema-repos.json +++ b/src/graphql/data/ghes-3.21/schema-repos.json @@ -566,14 +566,14 @@ "fields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" @@ -9409,14 +9409,14 @@ "inputFields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" diff --git a/src/graphql/data/ghes-3.22/schema-repos.json b/src/graphql/data/ghes-3.22/schema-repos.json index 6cd10f3fc5f8..31fb07a7c914 100644 --- a/src/graphql/data/ghes-3.22/schema-repos.json +++ b/src/graphql/data/ghes-3.22/schema-repos.json @@ -583,14 +583,14 @@ "fields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" @@ -9603,14 +9603,14 @@ "inputFields": [ { "name": "alertsThreshold", - "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise alerts block a\nreference update. For more information on alert severity levels, see \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" }, { "name": "securityAlertsThreshold", - "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", + "description": "

The severity level at which code scanning results that raise security alerts\nblock a reference update. For more information on security severity levels,\nsee \"About code scanning alerts.\".

", "type": "String!", "id": "string", "href": "/graphql/reference/other#scalar-string" diff --git a/src/graphql/scripts/sync.ts b/src/graphql/scripts/sync.ts index e856ff33c5db..15fe3eb165e3 100755 --- a/src/graphql/scripts/sync.ts +++ b/src/graphql/scripts/sync.ts @@ -145,6 +145,7 @@ async function main() { latestSchema, previewsForSchema, fallbackCategoryMap, + { currentLanguage: 'en', currentVersion: version }, ) // This is slow! // Split the schema by category so the runtime can lazily load only the diff --git a/src/graphql/scripts/utils/process-schemas.ts b/src/graphql/scripts/utils/process-schemas.ts index 8e626e211c59..9004c9c81234 100755 --- a/src/graphql/scripts/utils/process-schemas.ts +++ b/src/graphql/scripts/utils/process-schemas.ts @@ -12,7 +12,8 @@ import type { DefinitionNode, TypeNode, } from 'graphql/language' -import helpers from './schema-helpers' +import baseHelpers, { createSchemaHelpers } from '@/graphql/scripts/utils/schema-helpers' +import type { Context } from '@/types/types' import { OTHER_CATEGORY, isValidCategory } from '@/graphql/lib/categories' import fs from 'fs/promises' import path from 'path' @@ -216,12 +217,14 @@ const externalScalarsJSON: Array<{ name: string; description: string }> = JSON.p ) const externalScalars: ScalarInfo[] = await Promise.all( externalScalarsJSON.map(async (scalar): Promise => { - const description = await helpers.getDescription(scalar.description) - const id = helpers.getId(scalar.name) + // These live in a local JSON file rather than the versioned schema, and + // their only link is external, so they need no version context. + const description = await baseHelpers.getDescription(scalar.description) + const id = baseHelpers.getId(scalar.name) // External scalars (e.g. Date, URI) are not annotated upstream and live // in the "other" bucket. Emit the legacy href; bucket-by-category will // rewrite it to the category-aware form for per-category files. - const href = helpers.getFullLink('scalars', id) + const href = baseHelpers.getFullLink('scalars', id) return { name: scalar.name, description, @@ -247,7 +250,11 @@ export default async function processSchemas( // Lookups for type-level categories use the type id; mutations look up // by mutation field name under the `mutations` key. fallbackCategoryMap?: CategoryMapFallback, + // The docs version being generated, e.g. `enterprise-server@3.22`. Without + // it, links inside schema descriptions render without a version segment. + context: Context = {}, ): Promise { + const helpers = createSchemaHelpers(context) const schemaAST: DocumentNode = parse(idl.toString()) const schema: GraphQLSchema = buildASTSchema(schemaAST) diff --git a/src/graphql/scripts/utils/schema-helpers.ts b/src/graphql/scripts/utils/schema-helpers.ts index bbf60d4eb759..f7914cd309c4 100644 --- a/src/graphql/scripts/utils/schema-helpers.ts +++ b/src/graphql/scripts/utils/schema-helpers.ts @@ -1,4 +1,5 @@ import { renderContent } from '@/content-render/index' +import type { Context } from '@/types/types' import fs from 'fs/promises' import { isScalarType, @@ -53,6 +54,15 @@ const graphqlTypes: GraphQLTypeInfo[] = JSON.parse( const singleQuotesInsteadOfBackticks = / '(\S+?)' / +// Upstream schema descriptions link with a `${externalDocsUrl}` placeholder, +// but nothing in this pipeline expands it. It ships percent-encoded as +// `href="$%7BexternalDocsUrl%7D/code-security/..."`, which the browser +// resolves against the current page and 404s. Dropping the placeholder leaves +// a root-relative link, which `getDescription` then versions using the +// `context` handed to `createSchemaHelpers`, so a GHES reader stays on GHES. +// The bare `helpers` export has no context and leaves links unversioned. +const unexpandedExternalDocsUrl = /\$\{externalDocsUrl\}(?=\/)/g + function addPeriod(string: string): string { return string.endsWith('.') ? string : `${string}.` } @@ -60,6 +70,7 @@ function addPeriod(string: string): string { async function getArguments( args: readonly InputValueDefinitionNode[], schema: GraphQLSchema, + context?: Context, ): Promise { if (!args.length) return @@ -71,7 +82,7 @@ async function getArguments( newArg.name = arg.name.value newArg.defaultValue = arg.defaultValue && 'value' in arg.defaultValue ? arg.defaultValue.value : undefined - newArg.description = arg.description ? await getDescription(arg.description.value) : '' + newArg.description = arg.description ? await getDescription(arg.description.value, context) : '' const typeName = getType(arg) if (!typeName) continue // Skip if type cannot be determined type.name = typeName @@ -100,6 +111,7 @@ export function buildCategoryHref(category: string, urlKind: string, id: string) async function getDeprecationReason( directives: readonly ConstDirectiveNode[], schemaMember: SchemaMember, + context?: Context, ): Promise { if (!schemaMember.isDeprecated) return @@ -114,7 +126,7 @@ async function getDeprecationReason( if (!arg) return const value = arg.value if (!value || value.kind !== 'StringValue' || !value.value) return - return renderContent(value.value) + return renderContent(value.value, context) } function getDeprecationStatus(directives: readonly ConstDirectiveNode[]): boolean | undefined { @@ -123,10 +135,11 @@ function getDeprecationStatus(directives: readonly ConstDirectiveNode[]): boolea return directives[0].name.value === 'deprecated' } -async function getDescription(rawDescription: string): Promise { +async function getDescription(rawDescription: string, context?: Context): Promise { rawDescription = rawDescription.replace(singleQuotesInsteadOfBackticks, '`$1`') + rawDescription = rawDescription.replace(unexpandedExternalDocsUrl, '') - return renderContent(addPeriod(rawDescription)) + return renderContent(addPeriod(rawDescription), context) } function getFullLink(baseType: string, id: string): string { @@ -269,7 +282,7 @@ function removeMarkers(str: string): string { return str.replace('[', '').replace(']', '').replace(/!/g, '') } -export default { +const helpers = { getArguments, getDeprecationReason, getDeprecationStatus, @@ -282,3 +295,21 @@ export default { getType, getTypeKind, } + +// The three helpers that render Markdown need to know which docs version they +// are rendering for, otherwise `rewrite-local-links` bails out and root-relative +// links ship without a language or version segment. Binding the context once +// here keeps the ~30 call sites in `process-schemas` unchanged, and keeps the +// context per-call rather than in module state, so two versions can never +// render against each other's context. +export function createSchemaHelpers(context: Context): typeof helpers { + return { + ...helpers, + getArguments: (args, schema) => getArguments(args, schema, context), + getDeprecationReason: (directives, schemaMember) => + getDeprecationReason(directives, schemaMember, context), + getDescription: (rawDescription) => getDescription(rawDescription, context), + } +} + +export default helpers diff --git a/src/graphql/tests/description-links.ts b/src/graphql/tests/description-links.ts new file mode 100644 index 000000000000..19a20e1ea6b2 --- /dev/null +++ b/src/graphql/tests/description-links.ts @@ -0,0 +1,54 @@ +import { existsSync, readdirSync, readFileSync } from 'fs' +import path from 'path' + +import { describe, expect, test } from 'vitest' + +import helpers, { createSchemaHelpers } from '@/graphql/scripts/utils/schema-helpers' + +const DATA_DIR = 'src/graphql/data' +const PLACEHOLDER_LINK = + 'See "[About code scanning alerts](${externalDocsUrl}/code-security/code-scanning#levels).".' + +describe('GraphQL description links', () => { + test('strips the unexpanded externalDocsUrl placeholder', async () => { + const rendered = await helpers.getDescription(PLACEHOLDER_LINK) + + // Left in place the placeholder percent-encodes into the href and the + // browser resolves it against the current page, which 404s. + expect(rendered).not.toContain('externalDocsUrl') + expect(rendered).toContain('href="/code-security/code-scanning#levels"') + }) + + test.each([ + ['free-pro-team@latest', '/en/code-security/code-scanning#levels'], + ['enterprise-cloud@latest', '/en/enterprise-cloud@latest/code-security/code-scanning#levels'], + ['enterprise-server@3.21', '/en/enterprise-server@3.21/code-security/code-scanning#levels'], + ])('versions the link for %s', async (currentVersion, expected) => { + const versioned = createSchemaHelpers({ currentLanguage: 'en', currentVersion }) + const rendered = await versioned.getDescription(PLACEHOLDER_LINK) + + expect(rendered).toContain(`href="${expected}"`) + }) + + test('leaves other link styles alone', async () => { + expect(await helpers.getDescription('See [a](/code-security/x).')).toContain( + 'href="/code-security/x"', + ) + expect(await helpers.getDescription('See [a](https://example.com/x).')).toContain( + 'href="https://example.com/x"', + ) + // A bare placeholder with no path after it is not a link, so leave it. + expect(await helpers.getDescription('Set ${externalDocsUrl} first.')).toContain( + '${externalDocsUrl}', + ) + }) + + test('no shipped schema data still contains the placeholder', () => { + const offenders = readdirSync(DATA_DIR, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => path.join(DATA_DIR, entry.name, 'schema-repos.json')) + .filter((file) => existsSync(file) && readFileSync(file, 'utf-8').includes('externalDocsUrl')) + + expect(offenders).toEqual([]) + }) +})