+
**28 May 2026**
We published a new guide for teams that run the CodeQL CLI in their own CI/CD systems and want faster scans. The article covers two techniques that can reduce scan times: diff-informed analysis (report only alerts in changed lines) and overlay analysis (reuse a cached base database instead of rebuilding from scratch).
diff --git a/content/graphql/reference/index.md b/content/graphql/reference/index.md
index 6f37be1c4a6d..c19465d81286 100644
--- a/content/graphql/reference/index.md
+++ b/content/graphql/reference/index.md
@@ -11,6 +11,16 @@ redirect_from:
- /graphql/reference/unions
- /graphql/reference/input-objects
- /graphql/reference/scalars
+ - /graphql/reference/audit-log
+ - /graphql/reference/billing
+ - /graphql/reference/code-scanning
+ - /graphql/reference/code-security
+ - /graphql/reference/codespaces
+ - /graphql/reference/collaborators
+ - /graphql/reference/interactions
+ - /graphql/reference/pages
+ - /graphql/reference/scim
+ - /graphql/reference/secret-scanning
versions:
fpt: '*'
ghec: '*'
diff --git a/data/reusables/dependabot/ip-allow-list-dependabot.md b/data/reusables/dependabot/ip-allow-list-dependabot.md
index cfc76e19cf6b..f37a3b3784f7 100644
--- a/data/reusables/dependabot/ip-allow-list-dependabot.md
+++ b/data/reusables/dependabot/ip-allow-list-dependabot.md
@@ -1,7 +1,7 @@
-By default, dynamically provisioned {% data variables.product.github %}-hosted runners do not guarantee static IP addresses. This includes the runners that are used by default with {% data variables.product.prodname_dependabot %}.
+{% data variables.product.prodname_dependabot %} is a first-party {% data variables.product.github %} App whose repository access is exempt from IP allow list restrictions. This means {% data variables.product.prodname_dependabot %} can read dependency files and create pull requests regardless of your IP allow list configuration.
-If you use an IP allow list and {% data variables.product.prodname_dependabot %}, you must set up a self-hosted runner or enable {% data variables.product.prodname_dependabot %} for use with {% data variables.actions.hosted_runners %}. See [AUTOTITLE](/actions/concepts/runners/about-self-hosted-runners) and [AUTOTITLE](/code-security/dependabot/working-with-dependabot/about-dependabot-on-github-actions-runners#enabling-or-disabling-dependabot-on-larger-runners).
+If {% data variables.product.prodname_dependabot %} jobs running on {% data variables.product.prodname_actions %} runners need to reach external resources that require predictable IP addresses (for example, private package registries behind a firewall), you should set up a self-hosted runner or configure {% data variables.actions.hosted_runners %} with a static IP address range. See [AUTOTITLE](/actions/concepts/runners/about-self-hosted-runners) and [AUTOTITLE](/code-security/dependabot/working-with-dependabot/about-dependabot-on-github-actions-runners#enabling-or-disabling-dependabot-on-larger-runners).
-Additionally, to learn more about setting up a {% data variables.actions.hosted_runners %} with a static IP address configured, see [AUTOTITLE](/actions/concepts/runners/about-larger-runners).
+Additionally, to learn more about configuring {% data variables.actions.hosted_runners %} with a static IP address range, see [AUTOTITLE](/actions/concepts/runners/about-larger-runners).
To allow your self-hosted runners or {% data variables.actions.hosted_runners %} to communicate with {% data variables.product.github %}, add the IP address or IP address range of your runners to the IP allow list that you have configured for your enterprise.
diff --git a/data/ui.yml b/data/ui.yml
index 7d6c90dbe7a7..ca5e6b94ca15 100644
--- a/data/ui.yml
+++ b/data/ui.yml
@@ -273,6 +273,8 @@ secret_scanning:
filter_all: All
filter_yes: 'Yes'
filter_no: 'No'
+ clear_filters: Clear filters
+ clear_filters_aria_label: Clear all filters and search
showing_patterns: 'Showing {filtered} of {total} patterns'
column_provider: Provider
column_secret: Secret
diff --git a/package.json b/package.json
index 247113c0c334..543eb1028096 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
"deleted-assets-pr-comment": "tsx src/assets/scripts/deleted-assets-pr-comment.ts",
"deleted-features-pr-comment": "tsx src/data-directory/scripts/deleted-features-pr-comment.ts",
"deprecate-ghes": "tsx src/ghes-releases/scripts/deprecate/index.ts",
- "deprecate-ghes-archive": "cross-env NODE_OPTIONS=--max-old-space-size=16384 tsx src/ghes-releases/scripts/deprecate/archive-version.ts",
+ "deprecate-ghes-archive": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=16384 tsx src/ghes-releases/scripts/deprecate/archive-version.ts",
"dev": "cross-env npm start",
"dev-toc": "tsx src/dev-toc/generate.ts",
"enable-automerge": "tsx src/workflows/enable-automerge.ts",
diff --git a/src/events/lib/schema.ts b/src/events/lib/schema.ts
index 01cca0ab460f..89447ebdf4b0 100644
--- a/src/events/lib/schema.ts
+++ b/src/events/lib/schema.ts
@@ -646,6 +646,39 @@ const preference = {
},
}
+const tableInteraction = {
+ type: 'object',
+ additionalProperties: false,
+ required: ['type', 'context', 'table_interaction_name', 'table_interaction_type'],
+ properties: {
+ context,
+ type: {
+ type: 'string',
+ pattern: '^tableInteraction$',
+ },
+ table_interaction_name: {
+ type: 'string',
+ description:
+ 'Identifier for the table being interacted with (e.g. "secret-scanning-patterns").',
+ },
+ table_interaction_type: {
+ type: 'string',
+ enum: ['search', 'filter', 'sort', 'paginate', 'reset'],
+ description: 'The kind of interaction the user performed with the table.',
+ },
+ table_interaction_field_name: {
+ type: 'string',
+ description:
+ 'The field/column the interaction targeted (e.g. "pushProtection"). Omitted for whole-table actions.',
+ },
+ table_interaction_field_value: {
+ type: 'string',
+ description:
+ 'The value applied to the field (e.g. the filter value, search query, sort direction, or page number).',
+ },
+ },
+}
+
const validation = {
type: 'object',
additionalProperties: false,
@@ -682,6 +715,7 @@ export const schemas = {
clipboard,
print,
preference,
+ tableInteraction,
validation,
}
@@ -699,6 +733,7 @@ export const hydroNames = {
clipboard: 'docs.v0.ClipboardEvent',
print: 'docs.v0.PrintEvent',
preference: 'docs.v0.PreferenceEvent',
+ tableInteraction: 'docs.v0.TableInteractionEvent',
validation: 'docs.v0.ValidationEvent',
} as Record
diff --git a/src/events/tests/middleware.ts b/src/events/tests/middleware.ts
index ebac4573d8c5..0dc9363197b5 100644
--- a/src/events/tests/middleware.ts
+++ b/src/events/tests/middleware.ts
@@ -210,4 +210,38 @@ describe('POST /events', () => {
})
expect(statusCode).toBe(400)
})
+
+ test('should accept a tableInteraction filter event', async () => {
+ const { statusCode } = await checkEvent({
+ type: 'tableInteraction',
+ context: pageExample.context,
+ table_interaction_name: 'secret-scanning-patterns',
+ table_interaction_type: 'filter',
+ table_interaction_field_name: 'pushProtection',
+ table_interaction_field_value: 'yes',
+ })
+ expect(statusCode).toBe(200)
+ })
+
+ test('should accept a tableInteraction event without optional fields', async () => {
+ const { statusCode } = await checkEvent({
+ type: 'tableInteraction',
+ context: pageExample.context,
+ table_interaction_name: 'secret-scanning-patterns',
+ table_interaction_type: 'reset',
+ })
+ expect(statusCode).toBe(200)
+ })
+
+ test('should reject a tableInteraction event with an invalid interaction type', async () => {
+ const { statusCode } = await checkEvent({
+ type: 'tableInteraction',
+ context: pageExample.context,
+ table_interaction_name: 'secret-scanning-patterns',
+ table_interaction_type: 'not-a-valid-type',
+ table_interaction_field_name: 'pushProtection',
+ table_interaction_field_value: 'yes',
+ })
+ expect(statusCode).toBe(400)
+ })
})
diff --git a/src/events/types.ts b/src/events/types.ts
index 41a323daaf88..84c423e975da 100644
--- a/src/events/types.ts
+++ b/src/events/types.ts
@@ -12,6 +12,7 @@ export enum EventType {
preference = 'preference',
clipboard = 'clipboard',
print = 'print',
+ tableInteraction = 'tableInteraction',
}
export type EventProps = {
@@ -135,4 +136,10 @@ export type EventPropsByType = {
survey_comment_language?: string
survey_connected_event_id?: string
}
+ [EventType.tableInteraction]: {
+ table_interaction_name: string
+ table_interaction_type: 'search' | 'filter' | 'sort' | 'paginate' | 'reset'
+ table_interaction_field_name?: string
+ table_interaction_field_value?: string
+ }
}
diff --git a/src/fixtures/fixtures/data/ui.yml b/src/fixtures/fixtures/data/ui.yml
index 7d6c90dbe7a7..ca5e6b94ca15 100644
--- a/src/fixtures/fixtures/data/ui.yml
+++ b/src/fixtures/fixtures/data/ui.yml
@@ -273,6 +273,8 @@ secret_scanning:
filter_all: All
filter_yes: 'Yes'
filter_no: 'No'
+ clear_filters: Clear filters
+ clear_filters_aria_label: Clear all filters and search
showing_patterns: 'Showing {filtered} of {total} patterns'
column_provider: Provider
column_secret: Secret
diff --git a/src/fixtures/tests/playwright-rendering.spec.ts b/src/fixtures/tests/playwright-rendering.spec.ts
index 3f83cb6787f8..c9dc604eacc0 100644
--- a/src/fixtures/tests/playwright-rendering.spec.ts
+++ b/src/fixtures/tests/playwright-rendering.spec.ts
@@ -1399,9 +1399,7 @@ test.describe('LandingArticleGridWithFilter component', () => {
// Should show "no articles found" message as well
const noResultsMessage = page.getByTestId('no-articles-message')
await expect(noResultsMessage).toBeVisible()
- await expect(page.locator('[aria-live="polite"][aria-atomic="true"]')).toHaveText(
- 'No articles found matching your criteria.',
- )
+ await expect(noResultsMessage).toHaveText('No articles found matching your criteria.')
})
test('responsive behavior on different screen sizes', async ({ page }) => {
diff --git a/src/ghes-releases/lib/deprecation-steps.md b/src/ghes-releases/lib/deprecation-steps.md
index c7a9cf2c5c00..bcf03f1507c7 100644
--- a/src/ghes-releases/lib/deprecation-steps.md
+++ b/src/ghes-releases/lib/deprecation-steps.md
@@ -7,321 +7,249 @@ labels:
- workflow-generated
---
-# Deprecation steps for GHES releases
+# Deprecate a GitHub Enterprise Server release
-The day after a GHES version's [deprecation date](https://github.com/github/docs-internal/tree/main/src/ghes-releases/lib/enterprise-dates.json), a banner on the docs will say: `This version was deprecated on .` This lets users know that the release is deprecated. However, until the release is fully deprecated, it will show up in the Versions dropdown on the docs.github.com site.
+This issue is the runbook for fully deprecating a GHES version on docs.github.com. Fully deprecate means: scrape the version's docs into a static archive repository, remove all Markdown, YAML, and JSON content versioned for that release or lower from `github/docs-internal`, and deprecate its OpenAPI description in `github/github`.
-When we fully deprecate the release, we remove all any content (YML, JSON, Markdown) versioned for that release or lower. Follow the steps in this issue to fully **deprecate** the docs.
+Human: A coding agent such as Copilot CLI drives this runbook while a human supervises. Point your agent at this issue and work through it one step at a time.
-**Note**: Each step below, except step 0, must be done in order. Only move on to the next step after successfully completing the previous step.
+Throughout, `{{ release-number }}` stands for the version being deprecated. When this runbook is generated as a deprecation issue, the placeholder is filled in for you.
-The following large repositories are used throughout this checklist, it may be useful to clone them before you begin:
+- Do the steps in order. One at a time. Tell the human each time you start a new step.
+- Only move on after the previous step succeeds.
+- Keep process notes as you go. The final step uses them.
+- A callout appears in the steps: 🛑 **HUMAN**. This means stop and get explicit human sign-off before continuing. These mark points where a mistake is expensive or hard to undo, or actions only a human can perform. Text on these lines are meant for the human to do; any text on a line that does not have the flag is for the agent.
+- When a step is ambiguous or a tool misbehaves, look at the two most recent deprecation pull requests in `github/docs-internal` for how it was handled. Search for pull requests titled "Deprecate GitHub Enterprise Server", and read the content team's review comments so you can fix the same problems before requesting review.
+- Clone `github/docs-internal` and `github/github` before you begin.
-- `github/github`
-- `github/docs-internal`
+## Step 1: Confirm the deprecation date
-## Step 0: Confirm the deprecation date
+Look up `{{ release-number }}` in the [release date list](https://github.com/github/enterprise-releases/blob/master/releases.json), find the corresponding `prp` owner, and draft a short Slack message asking them to confirm the deprecation date.
-Before beginning the deprecation, ensure the date of the deprecation is correctly defined:
+🛑 **HUMAN**: Send a Slack message. If there is no `prp` owner, ask in #docs-content-enterprise or #ghes-releases instead.
-1. Check that the deprecation date is correct by looking up the version you are deprecating in the [release date list](https://github.com/github/enterprise-releases/blob/master/releases.json) and finding the corresponding `prp` owner. Send them a slack message to confirm that the date is correct. If the date is being pushed out, you can ask the `prp` to update the date in the release date list. If the release date list does not get updated (it doesn't always) we have to prepare that our version of that file (`src/ghes-releases/lib/enterprise-dates.json`) will also be inaccurate.
+* If the date is being pushed out, ask the `prp` to update the release date list, update this issue's target date, and pause until the new date arrives.
+* The release date list is often not updated. If it isn't, our copy in `src/ghes-releases/lib/enterprise-dates.json` may also be wrong. You fix that in Step 6.
- If there is no `prp` defined, reach out to our content friends for help in the #docs-content-enterprise or #ghes-releases Slack channel.
+## Step 2: Remove the version from the github/docs-content release tracker
-1. If this release is being pushed out, update the target date of this issue and you can wait to proceed with any futher steps.
+In the `github/docs-content` repository, remove `{{ release-number }}` from the `options` list in [`release-tracking.yml`](https://github.com/github/docs-content/blob/main/.github/ISSUE_TEMPLATE/release-tracking.yml), ensure the list of versions matches the available versions, and open a pull request.
-1. In the `docs-content` repo, remove the deprecated GHES version number from the `options` list in [`release-tracking.yml`](https://github.com/github/docs-content/blob/main/.github/ISSUE_TEMPLATE/release-tracking.yml).
+🛑 **HUMAN**: Review the `github/docs-content` pull request. Acknowledge this will need to be merged in the next few days.
-1. When the PR is approved, merge it in.
+You can continue once the human reviews the `github/docs-content` pull request and acknowledges they are responsible for getting it merged.
-🤖 Copilot prompt for Step 0
+## Step 3: Clone the translation repositories
-> I'm preparing to deprecate GHES VERSION_NUMBER. Read `src/ghes-releases/lib/enterprise-dates.json` in docs-internal and find the deprecation date for this version. Then draft a Slack message I can send to the PRP owner to confirm the date is correct. Also check `github/enterprise-releases/releases.json` and identify the PRP owner for this version.
+The full scrape needs local clones of the eight translation repositories:
-
-
-## Step 1: Create the new archived repository
-
-All previously archived content lives in its own repository. For example, GHES 3.11 archived content is located in https://github.com/github/docs-ghes-3.11.
-
-1. Create a new repository that will store the scraped deprecated files:
-
- ```shell
- npm run deprecate-ghes -- create-repo --version
- ```
-
- For example, to deprecate GHES 3.11, you would run:
-
- ```shell
- npm run deprecate-ghes -- create-repo --version 3.11
- ```
-
-1. From the new repository's home page, click the gear icon next to the "About" section and deselect the "Releases", "Packages", and "Deployments" checkboxes. Click "Save changes".
-
-🤖 Copilot prompt for Step 1
-
-> Run `npm run deprecate-ghes -- create-repo --version VERSION_NUMBER` and show me the output. If the script fails (API errors, permission issues, path problems), diagnose the error and suggest a fix. The `create-docs-ghes-version-repo.sh` script may have hardcoded paths or assumptions that need updating.
-
-
-
-## Step 2: Dry run: Scrape the docs and archive the files
-
-**Note:** You may want to perform the following dry run steps on a new temporary branch that you can delete after the dry run is complete.
-
-1. If the release date documented in the [release date list](https://github.com/github/enterprise-releases/blob/master/releases.json) is incorrect or differs from what we have documented in `src/ghes-releases/lib/enterprise-dates.json`, update the date in `src/ghes-releases/lib/enterprise-dates.json` to the correct deprecation date before proceeding with the deprecation. A banner is displayed on each page with a version that will be deprecated soon. The banner uses the dates defined in `src/ghes-releases/lib/enterprise-dates.json`.
-
-1. Ensure you have local clones of the [translation repositories](#configuring-the-translation-repositories).
-
-1. Update all translation directories to the latest `main` branch.
-
-1. Hide search components temporarily while scraping docs by adding the `visually-hidden` class to the search components:
-
- **In `src/search/components/input/SearchBarButton.tsx`**, wrap the return statement content:
-
- ```javascript
- return (
-
- );
- }
- ```
-
-1. Ensure your build is up to date:
-
- ```shell
- npm run build
- ```
-
-1. Do a dry run by scraping a small amount of files to test locally on your machine. This command does not overwrite the references to asset files so they will render on your machine.
+```shell
+npm run clone-translations
+```
- ```shell
- npm run deprecate-ghes-archive -- --dry-run --local-dev
- ```
+This clones every language into `./translations/` inside your `github/docs-internal` checkout, which the scrape reads by default. No `.env` configuration is needed. It clones eight repositories and can take several minutes. To keep your clones elsewhere, use per-language `TRANSLATIONS_ROOT_*` variables instead, described in [the appendix](#reference-configuring-the-translation-repositories).
-1. Navigate to the scraped files directory (`tmpArchivalDir_`) inside your docs-internal checkout. Open a few HTML files and ensure they render and drop-down pickers work correctly.
+## Step 4: Create the archive repository
-1. If the dry-run looks good, scrape all content files. This will take about 20-30 minutes. **Note:** This will overwrite the directory that was previously generated with new files. You can also create a specific output directory using the `--output` flag.
+Each deprecated version's docs live in their own repository, for example `github/docs-ghes-3.11`. Create the new one:
- ```shell
- npm run deprecate-ghes-archive
- ```
+```shell
+npm run deprecate-ghes -- create-repo --version {{ release-number }}
+```
-1. Revert changes to `src/search/components/input/SearchBarButton.tsx` and `src/search/components/input/SearchOverlayContainer.tsx`.
+🛑 **HUMAN**: On the new repository's home page, click the gear next to "About" and clear the "Releases", "Packages", and "Deployments" checkboxes, then save. No public API covers these toggles.
-1. Check in any change to `src/ghes-releases/lib/enterprise-dates.json`.
+You can continue once the human has said they completed that step.
-🤖 Copilot prompt for Step 2
+## Step 5: Create the deprecation branch
-> I'm doing a dry run of the GHES VERSION_NUMBER deprecation archive scrape.
->
-> First, in `src/search/components/input/SearchBarButton.tsx`, wrap the return statement content in a `
` wrapper. Do the same in `src/search/components/input/SearchOverlayContainer.tsx` for the SearchOverlay component when `isSearchOpen` is true.
->
-> Then run `npm run build` and show me the output. If the build succeeds, run `npm run deprecate-ghes-archive -- --dry-run --local-dev` and show me the output. Tell me if any errors occurred.
->
-> After I've reviewed the dry run output, run the full scrape: `npm run deprecate-ghes-archive`. This will take 20-30 minutes.
->
-> When the scrape completes, revert the search component changes: `git checkout src/search/components/input/SearchBarButton.tsx src/search/components/input/SearchOverlayContainer.tsx`. Verify the files are reverted.
+Create the branch that holds every `github/docs-internal` change in this deprecation. Keep it through Step 13:
-
+```shell
+git checkout -b deprecate-{{ release-number }}
+```
-## Step 3: Commit the scraped docs to the new repository
+## Step 6: Fix the deprecation date if needed
-1. Copy the scraped files from the `tmpArchivalDir_` directory in `docs-internal` over to the new `github/docs-ghes-` repository.
+If the date in the [release date list](https://github.com/github/enterprise-releases/blob/master/releases.json) differs from `src/ghes-releases/lib/enterprise-dates.json`, update `enterprise-dates.json` to match and commit it on your branch. The pre-deprecation banner reads its dates from that file, so fix it before scraping.
-1. Commit the files. A GitHub Pages build should automatically begin, creating the static site that serves these docs.
+## Step 7: Dry-run the scrape
-1. Preview a few pages, by navigating to the full URL checked into the repo. For example, for GHES 3.11, you can view `https://github.github.com/docs-ghes-3.11/en/enterprise-server@3.11/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/about-notifications/index.html`.
+Update your translation clones to the latest `main`. Then hide the search components so they don't get scraped into the static archive: in `src/search/components/input/SearchBarButton.tsx`, wrap the returned content in a `
`, and do the same for the `SearchOverlay` in `src/search/components/input/SearchOverlayContainer.tsx`.
-1. Remove the `tmpArchivalDir_` directory from your `github/docs-internal` checkout.
+Build, then scrape a few pages locally:
-🤖 Copilot prompt for Step 3
+```shell
+npm run build
+npm run deprecate-ghes-archive -- --dry-run --local-dev
+```
-> Copy the scraped files from `tmpArchivalDir_` to the `github/docs-ghes-` repository. Commit all the files with the message "Archive GHES docs". Verify the commit succeeded and show me the file count. Then remove the `tmpArchivalDir_` directory from docs-internal.
+Open a few HTML files in `tmpArchivalDir_{{ release-number }}` and confirm they render, styles load, and the version dropdowns work.
-
+Offer to open the files for the human in their text editor or browser.
-## Step 4: Deprecate the GHES release in docs-internal
+🛑 **HUMAN**: Review the dry-run output before the full scrape.
-1. In your `docs-internal` checkout, create a new branch: `git checkout -b deprecate-`.
+You can continue after the human confirms they have reviewed the dry-run output.
-1. In your `docs-internal` checkout, edit `src/versions/lib/enterprise-server-releases.ts` by removing the version number to be deprecated from the `supported` array and move it to the `deprecatedWithFunctionalRedirects` array.
+## Step 8: Run the full scrape
-1. Deprecate the automated pipelines data files (including audit logs, REST, GraphQL, webhooks, GitHub Apps, CodeQL CLI, and secret scanning):
+Scrape every page. This takes 20-30 minutes and overwrites the dry-run output:
- ```shell
- npm run deprecate-ghes -- pipelines
- ```
+```shell
+npm run deprecate-ghes-archive
+```
-1. Remove deprecated content files and update the versions frontmatter:
+Revert the search component edits:
- ```shell
- npm run deprecate-ghes -- content
- ```
+```shell
+git checkout src/search/components/input/SearchBarButton.tsx src/search/components/input/SearchOverlayContainer.tsx
+```
-1. Remove deprecated Liquid from content and data files. **Note:** The previous step to update content file frontmatter must have run successfully for this step to work because the updated frontmatter is used to determine file versions.
+## Step 9: Publish the archive
- ```shell
- npm run lint-content -- --paths content data --rules liquid-ifversion-versions --fix
- ```
+The scrape writes the publishable site to an inner directory, `tmpArchivalDir_{{ release-number }}/{{ release-number }}/`, which holds `en/`, the other language directories, and the redirect files. Copy the contents of that inner directory into the root of the `github/docs-ghes-{{ release-number }}` repository, so pages land at `/en/...` with no nested `{{ release-number }}/` directory. If you are unsure, please look at some previous `github/docs-ghes-*` repos for the right organization.
-1. There are some `data/variables/*.yml` files that can't be autofixed. These will show up as errors. You can manually make the changes to these files. For example, this means open file data/variables/code-scanning and find the code_scanning_thread_model_support key. Edit the key’s value to remove the deprecated liquid:
+🛑 **HUMAN**: Confirm the file count, organization, and contents.
- 
+After the human confirms, commit and push. GitHub Pages builds the static site automatically. Wait a few minutes. Preview a few pages at the full Pages URL, for example `https://github.github.com/docs-ghes-{{ release-number }}/en/enterprise-server@{{ release-number }}/get-started/index.html`, across a couple of languages. Then remove `tmpArchivalDir_{{ release-number }}` from `github/docs-internal`.
-1. Deprecate any data files that are now empty, remove data resuables references that were deleted:
+## Step 10: Remove the version from github/docs-internal
- ```shell
- npm run deprecate-ghes -- data
- ```
+Back on your `deprecate-{{ release-number }}` branch, move the version out of the supported list. In `src/versions/lib/enterprise-server-releases.ts`, remove `'{{ release-number }}'` from `supported` and add it as the first element of `deprecatedWithFunctionalRedirects`.
-1. Run the linter again to remove whitespace and check for any other errors:
+Run the deprecation scripts in order. Each depends on the previous one succeeding:
- ```shell
- npm run lint-content -- --fix
- ```
+```shell
+npm run deprecate-ghes -- pipelines
+npm run deprecate-ghes -- content
+npm run lint-content -- --paths content data --rules liquid-ifversion-versions --fix
+```
-1. Use VSCode find/replace to remove any remaining table pipes after liquid has been removed. For example lines that only contain 1 or two pipes: ` |` or ` | |`. You can use the following regexes: `^\|\s*\|$` and `^\s?\|\s?$`.
+Some `data/variables/*.yml` files can't be autofixed and show as lint errors. Open each one, find the key named in the error, and remove the deprecated Liquid while keeping the content for supported versions.
-1. Test the changes by running the site locally:
+Then clean up empty data files and run the linter again:
- ```shell
- npm run start
- ```
+```shell
+npm run deprecate-ghes -- data
+npm run lint-content -- --fix
+```
-1. Poke around several deprecated pages by navigating to `docs.github.com/enterprise/`, and ensure that:
+## Step 11: Clean up content artifacts
- - Stylesheets are working properly
- - Images are rendering properly
- - The search functionality was disabled during scraping
- - Look at any console errors to ensure that no new unexpected errors were introduced. You can look at previous errors by viewing a previously completed deprecation page.
- - You should see a banner on the top of every deprecated page with the date that the version was deprecated.
- - You should see a banner at the top of every page for the oldes currently supported version with the date that it will be deprecated in the ~3 months.
+The content team flags issues on every deprecation, so fix them before requesting review:
-1. If everything looks good, check in all changes and create a pull request.
+* Collapse consecutive blank lines left behind by removed Liquid. Run `npm run deprecate-ghes -- collapse-blank-lines` to auto-fix any run of 2+ blank lines in the markdown files this deprecation changed. The linter doesn't catch these because MD012 is off. Use `--check` to list offenders without writing.
+* Review each Liquid-removal site for a stray single blank line the codemod introduced, one at a time. The auto-fix above only touches 2+ blank lines, because a lone blank line is often legitimate (for example, before a nested sub-list), so a human must judge each one.
+* Remove leftover table-pipe rows from removed Liquid, for example lines matching `^\|\s*\|$` or `^\s?\|\s?$`.
+* Fix paragraphs missing a leading space, and any `ifversion` left in the wrong place after a reusable was removed.
+* Revert codemod churn on autogenerated REST files. The codemod edits `content/rest/**` files marked `autogenerated: rest`, stripping the `# DO NOT MANUALLY EDIT` marker from their `versions:` block, rewriting `ghes: '>=X'` to `'*'`, and reflowing `intro: >-` scalars. The REST sync bot owns these files and will overwrite the edits, so restore the churned frontmatter to match `main` and keep only genuinely-removed pages. Alternatively, regenerate with `npm run sync-rest`, which needs a `github/github` checkout.
+* Close up double spaces left in inline `ifversion` chains. When the codemod drops a version from an inline chain it can leave a double space, for example `upgrading to {% ifversion ghes = 3.17 %}3.17{% endif %} {% ifversion ghes = 3.18 %}3.18{% endif %} with caution.` Move the trailing space inside each conditional (`3.17 {% endif %}`) so only the matched branch renders one space.
+* Prune empty `else` branches and dead conditionals. When the codemod removes the only content inside an `{% else %}`, often a now-unused reusable, delete the empty `{% else %}`. Also remove `{% ifversion ghes < %}` blocks, which are always false after the deprecation.
+* Add redirects for content removed on every version. When a deprecation fully removes a feature, not just one version of it, there's no auto-generated redirect, so add redirects to the current docs by hand and repoint version-pinned entries in `src/fixtures/fixtures/rest-redirects.json` at the current location. Expect a large list. There's no helper for this yet.
-1. Ensure that CI is passing or make any changes to content needed to get tests to pass.
+The list in this step should increase after each deprecation to improve the output of this process and reduce human effort.
-1. Add the PR to the [docs-content review board](https://github.com/orgs/github/projects/2936/views/2).
+## Step 12: Fix CI the codemod doesn't touch
-1. 🚢 Ship the change.
+The deprecation scripts only rewrite `content/` and `data/`, so version references in tests and fixtures can break CI. Run:
-🤖 Copilot prompt for Step 4 — initial setup
+```shell
+npm test -- src/versions/tests src/redirects/tests
+```
-> I'm deprecating GHES VERSION_NUMBER in docs-internal. In `src/versions/lib/enterprise-server-releases.ts`, remove `'VERSION_NUMBER'` from the `supported` array and add it as the first element of the `deprecatedWithFunctionalRedirects` array. Show me the diff.
+Fix any failures. For example, when a deprecation fully removes content rather than just a version of it, redirect fixtures in `src/fixtures/fixtures/rest-redirects.json` may still point at the removed version; repoint them at the current location.
-
+## Step 13: Review, smoke test, and open the pull request
-🤖 Copilot prompt for Step 4 — run deprecation scripts
+🛑 **HUMAN**: Review the full diff before the smoke test.
-> Run `npm run deprecate-ghes -- pipelines` and show me the output. If there are errors, diagnose and fix them.
->
-> Then run `npm run deprecate-ghes -- content` and show me the output. If there are errors, diagnose and fix them.
+After the human confirms they reviewed the full diff, smoke test locally with `npm run start` and visit a few pages at `docs.github.com/enterprise/{{ release-number }}`. Confirm stylesheets and images load, search is disabled, there are no new console errors, every deprecated page shows the "deprecated on " banner, and the new oldest supported version shows its upcoming-deprecation banner.
-
+Commit, push, and open a draft pull request labelled `llm-generated`. Use this body so the reviewer has context and a way to feed findings back into the runbook, filling in the placeholders:
-🤖 Copilot prompt for Step 4 — lint and fix Liquid
+````markdown
+```markdown
+Copilot generated this pull request.
-> Run `npm run lint-content -- --paths content data --rules liquid-ifversion-versions --fix`. Some `data/variables/*.yml` files can't be auto-fixed and will show as errors. For each error, open the file, find the key mentioned in the error, and remove the deprecated Liquid conditional for GHES VERSION_NUMBER while preserving the content for supported versions. Show me each change you make.
+This pull request deprecates GHES {{ release-number }} on docs.github.com.
-
+## What this does
-🤖 Copilot prompt for Step 4 — clean up data and remaining issues
+- Moves `{{ release-number }}` out of `supported` in `enterprise-server-releases.ts`.
+- Runs the deprecation codemods over content and pipeline data.
+- Removes the `{{ release-number }}` pipeline data and archives the version.
+- {Note any content removed entirely, with redirect counts.}
-> Run `npm run deprecate-ghes -- data` and show me the output.
->
-> Then run `npm run lint-content -- --fix` to remove whitespace and check for other errors.
->
-> Search the codebase for any remaining table pipe artifacts from removed Liquid conditionals. Look for lines matching `^\|\s*\|$` or `^\s?\|\s?$` across all content and data files. Remove any you find.
->
-> Show me a summary of all changes made.
+## For reviewers
-
+[Step 11 of the runbook](https://github.com/github/docs-internal/blob/main/src/ghes-releases/lib/deprecation-steps.md#step-11-clean-up-content-artifacts) lists the content problems that recur. If you spot issues this pull request missed, please add to Step 11 so the next deprecation catches it.
+```
+````
-🤖 Copilot prompt for Step 4 — final validation
+> [!NOTE]
+> The `dont-delete-features` check can fail when a deprecation removes more than one feature file. It guards against translations still referencing deleted features. This is expected on deprecations that fully remove features, and the docs-bot "Delete orphaned features" automation cleans them up. Don't block the pull request on it.
-> Run `npm run lint-content` and show me any remaining errors. For each error, fix it and show me the change.
->
-> Then run `npm run test -- src/versions` to check version-related tests still pass.
->
-> Summarize all changes made so far so I can review before committing.
+Offer to open the pull request in the human's browser.
-
+🛑 **HUMAN**: Review the draft pull request. Comment any changes needed.
-## Step 5: Create a tag
+Once the humans approves the pull request, mark as ready for review. Add the pull request to the [docs-content review board](https://github.com/orgs/github/projects/2936/views/2).
-1. Create a new tag for the most recent commit on the `main` branch so that we can keep track of where in commit history we removed the GHES release. Create a tag called `enterprise--deprecation`. On your local, `git checkout main`, `git pull`, `git tag enterprise--deprecation`, then `git push --tags --no-verify`.
+🛑 **HUMAN**: Get a content team review before merging the pull request. You should add any issues the content team finds to Step 11 of this runbook.
-🤖 Copilot prompt for Step 5
+You can proceed once the human acknowledges they will need to get a content team review and handle merging the pull request and adding any feedback. You do not need to wait for the pull request to get a content team review or to be merged.
-> Run `git checkout main && git pull` then `git tag enterprise-VERSION_NUMBER-deprecation && git push --tags --no-verify`. Show me the output.
+## Step 14: Tag the deprecation
-
+Tag `main` so we can find where in history the version was removed. You can tag now. There's no need to wait for the deprecation pull request to merge, which matches the recent deprecations.
-## Step 6: Deprecate the OpenAPI description in `github/github`
+```shell
+git checkout main && git pull
+git tag enterprise-{{ release-number }}-deprecation
+git push --tags --no-verify
+```
-1. In `github/github`, edit the release's config file in `app/api/description/config/releases/`, and change `deprecated: false` to `deprecated: true`.
+## Step 15: Deprecate the OpenAPI description in github/github
-1. Open a new PR, and get the required code owner approvals. A docs-content team member can approve it for the docs team.
+In `github/github`, edit `app/api/description/config/releases/ghes-{{ release-number }}.yaml` and change `deprecated: false` to `deprecated: true`. Open a pull request and get the required code owner approvals. A docs-content team member can approve for the docs team.
-1. When the PR is approved, [deploy the `github/github` PR](https://thehub.github.com/epd/engineering/devops/deployment/deploying-dotcom/). If you haven't deployed a `github/github` PR before, work with someone that has -- the process isn't too involved depending on how you deploy, but there are a lot of details that can potentially be confusing as you can see from the documentation.
+🛑 **HUMAN**: Once approved, [deploy the `github/github` pull request](https://thehub.github.com/epd/engineering/devops/deployment/deploying-dotcom/). If you haven't deployed `github/github` before, pair with someone who has.
-🤖 Copilot prompt for Step 6
+Continue on after the human acknowledges they will need to deploy the `github/github` pull request after the `github/docs-internal` pull request merges.
-> In the `github/github` repository, find the config file for GHES VERSION_NUMBER in `app/api/description/config/releases/`. Change `deprecated: false` to `deprecated: true`. Show me the diff and open a PR with the title "Deprecate GHES VERSION_NUMBER OpenAPI description".
+## Step 16: Capture process improvements
-
+Keep notes throughout the deprecation of anything wrong, slow, or confusing: runbook bugs, tooling failures, manual workarounds, and content-team feedback. When you finish, open a separate pull request, not part of the deprecation pull request, that fixes this runbook and the deprecation tooling for the next release. Write it for the next deprecation's agent.
-## Configuring the translation repositories
+🛑 **HUMAN**: Review the process improvement pull request.
-You can clone the translation repositories directly inside of your docs-internal checkout, but I'd recommend cloning them in a separate directory. For example, create a `translations` directory at the same level as your `docs-internal` directory. Inside of the `translations` directory, clone the following repoisitories (ensure this list includes all languages that we are supporting):
+After the human approves the process improvement pull request, you can continue.
-- [docs-internal.es-es](https://github.com/github/docs-internal.es-es)
-- [docs-internal.ja-jp](https://github.com/github/docs-internal.ja-jp)
-- [docs-internal.pt-br](https://github.com/github/docs-internal.pt-br)
-- [docs-internal.zh-cn](https://github.com/github/docs-internal.zh-cn)
-- [docs-internal.ru-ru](https://github.com/github/docs-internal.ru-ru)
-- [docs-internal.fr-fr](https://github.com/github/docs-internal.fr-fr)
-- [docs-internal.ko-kr](https://github.com/github/docs-internal.ko-kr)
-- [docs-internal.de-de](https://github.com/github/docs-internal.de-de)
+## Step 17: Summarize
-To map the location of each translation repository, edit your `.env` file with the mapping. For example, if following the locations suggested above, your `.env` file might look like this:
+Summarize the work completed in this workflow, and link to each of the pull requests with the next action needed from the human.
-```shell
-TRANSLATIONS=/Users/mona/repos/github-repos/translations
-TRANSLATIONS_ROOT_ES_ES=${TRANSLATIONS}/docs-internal.es-es
-TRANSLATIONS_ROOT_JA_JP=${TRANSLATIONS}/docs-internal.ja-jp
-TRANSLATIONS_ROOT_PT_BR=${TRANSLATIONS}/docs-internal.pt-br
-TRANSLATIONS_ROOT_ZH_CN=${TRANSLATIONS}/docs-internal.zh-cn
-TRANSLATIONS_ROOT_RU_RU=${TRANSLATIONS}/docs-internal.ru-ru
-TRANSLATIONS_ROOT_FR_FR=${TRANSLATIONS}/docs-internal.fr-fr
-TRANSLATIONS_ROOT_KO_KR=${TRANSLATIONS}/docs-internal.ko-kr
-TRANSLATIONS_ROOT_DE_DE=${TRANSLATIONS}/docs-internal.de-de
-```
+## Reference: Configuring the translation repositories
-## Re-scraping a page or all pages
+`npm run clone-translations` from Step 3 is the simplest setup: it clones every language into `./translations/`, which the scrape reads by default with no extra configuration.
-Occasionally, a change will need to be added to our archived enterprise versions. If this occurs, you can check out the `enterprise--release` branch and re-scrape the page or all pages using `npm run deprecate-ghes-archive`. To scrape a single page you can use the `—page ` option.
+To keep your clones elsewhere instead, clone the repositories below and map each one with a `TRANSLATIONS_ROOT_*` variable in your `.env` file:
-For each language, upload the new file to the `github/docs-ghes-` repo.
+* `docs-internal.es-es`
+* `docs-internal.ja-jp`
+* `docs-internal.pt-br`
+* `docs-internal.zh-cn`
+* `docs-internal.ru-ru`
+* `docs-internal.fr-fr`
+* `docs-internal.ko-kr`
+* `docs-internal.de-de`
-After uploading the new files, you will need to purge the Fastly cache for the single page. From Okta, go to Fastly and select `docs`. Click `Purge` then `Purge URL`. If you need to purge a whole path, just do a `Purge All`
+## Reference: Re-scraping a page or all pages
-
+Occasionally a change needs to land in an already-archived version. The archive script always scrapes the current oldest supported version, so check out the `enter
+ise-{{ release-number }}-deprecation` tag, which points at history from before the version left `supported`, and re-scrape with `npm run deprecate-ghes-archive`. To scrape a single page, use the `--page ` option, passing the path without a version or language prefix. Upload the new files to `github/docs-ghes-{{ release-number }}` for each language.
-Enter the URL or path and do a soft purge.
+Human: After uploading, purge the Fastly cache. From Okta, open Fastly, select `docs`, and click "Purge" then "Purge URL", or "Purge All" for a whole path. Enter the URL or path and do a soft purge.
-
+/cc @github/docs-engineering
diff --git a/src/ghes-releases/lib/variable-example.png b/src/ghes-releases/lib/variable-example.png
deleted file mode 100644
index ba7bc8f6068a..000000000000
Binary files a/src/ghes-releases/lib/variable-example.png and /dev/null differ
diff --git a/src/ghes-releases/scripts/create-enterprise-issue.ts b/src/ghes-releases/scripts/create-enterprise-issue.ts
index 9e48f4d5745a..33d194776220 100644
--- a/src/ghes-releases/scripts/create-enterprise-issue.ts
+++ b/src/ghes-releases/scripts/create-enterprise-issue.ts
@@ -103,12 +103,13 @@ async function createDeprecationIssue() {
const issueTemplate = readFileSync('src/ghes-releases/lib/deprecation-steps.md', 'utf8')
const { data, content } = matter(issueTemplate)
const { title, labels } = data
+ const renderedContent = content.replaceAll('{{ release-number }}', oldestSupported)
const body = `GHES ${oldestSupported} deprecation occurs on ${deprecationDate}.
- \n${content}
- '/cc @github/docs-engineering'`
+
+${renderedContent}`
await createIssue(
repo,
- title.replace('{{ release-number }}', oldestSupported),
+ title.replaceAll('{{ release-number }}', oldestSupported),
body,
labels,
oldestSupported,
diff --git a/src/ghes-releases/scripts/deprecate/archive-version.ts b/src/ghes-releases/scripts/deprecate/archive-version.ts
index 604247881eb7..179e2decacca 100755
--- a/src/ghes-releases/scripts/deprecate/archive-version.ts
+++ b/src/ghes-releases/scripts/deprecate/archive-version.ts
@@ -112,6 +112,8 @@ async function main() {
} catch (err) {
console.error('scraping error')
console.error(err)
+ server.close(() => process.exit(1))
+ return
}
fs.renameSync(
@@ -127,11 +129,11 @@ async function main() {
} else {
console.log('🏁 Scraping a single page is complete')
}
- server.close()
+ server.close(() => process.exit(0))
})
.on('error', (err) => {
console.log('error listening to port ', port, err)
- server.close()
+ server.close(() => process.exit(1))
})
}
diff --git a/src/ghes-releases/scripts/deprecate/collapse-blank-lines.ts b/src/ghes-releases/scripts/deprecate/collapse-blank-lines.ts
new file mode 100644
index 000000000000..52815b0b91d8
--- /dev/null
+++ b/src/ghes-releases/scripts/deprecate/collapse-blank-lines.ts
@@ -0,0 +1,84 @@
+import fs from 'fs'
+import { execSync } from 'child_process'
+
+// Removing deprecated Liquid conditionals leaves behind extra blank lines.
+// The content team flags these every deprecation, and the MD012 linter rule
+// is off so nothing catches them automatically. This collapses any run of
+// two or more consecutive blank lines down to one, but only in the markdown
+// files the deprecation actually changed. Single blank lines are left alone:
+// removed Liquid can introduce one in a place where it doesn't belong, so a
+// human still reviews each removal site one at a time.
+
+function getChangedMarkdownFiles(): string[] {
+ const commands = [
+ 'git diff --name-only HEAD',
+ 'git diff --name-only --cached',
+ 'git diff --name-only origin/main...HEAD',
+ ]
+ const files = new Set()
+ for (const command of commands) {
+ let output = ''
+ try {
+ output = execSync(command, { encoding: 'utf8' })
+ } catch {
+ // origin/main may not be fetched locally; skip that source.
+ continue
+ }
+ for (const line of output.split('\n')) {
+ const file = line.trim()
+ if (!file) continue
+ if (!file.endsWith('.md')) continue
+ if (!file.startsWith('content/') && !file.startsWith('data/')) continue
+ if (fs.existsSync(file)) files.add(file)
+ }
+ }
+ return [...files].sort()
+}
+
+// Collapses any run of 2+ blank lines into a single blank line.
+function collapse(contents: string): string {
+ const lines = contents.split('\n')
+ const result: string[] = []
+ let blankRun = 0
+ for (const line of lines) {
+ if (line.trim() === '') {
+ blankRun += 1
+ if (blankRun <= 1) result.push('')
+ } else {
+ blankRun = 0
+ result.push(line)
+ }
+ }
+ return result.join('\n')
+}
+
+export function collapseBlankLines(options: { check?: boolean } = {}) {
+ const files = getChangedMarkdownFiles()
+ const offenders: string[] = []
+
+ for (const file of files) {
+ const contents = fs.readFileSync(file, 'utf8')
+ const collapsed = collapse(contents)
+ if (collapsed === contents) continue
+ offenders.push(file)
+ if (!options.check) {
+ fs.writeFileSync(file, collapsed)
+ console.log('Collapsed blank lines in: ', file)
+ }
+ }
+
+ if (options.check) {
+ if (offenders.length) {
+ console.error('Found 2+ consecutive blank lines in:')
+ for (const file of offenders) console.error(` ${file}`)
+ console.error('Run `npm run deprecate-ghes -- collapse-blank-lines` to fix.')
+ process.exit(1)
+ }
+ console.log('No double blank lines found in changed markdown files.')
+ return
+ }
+
+ if (!offenders.length) {
+ console.log('No double blank lines found in changed markdown files.')
+ }
+}
diff --git a/src/ghes-releases/scripts/deprecate/create-docs-ghes-version-repo.sh b/src/ghes-releases/scripts/deprecate/create-docs-ghes-version-repo.sh
index eb8ec4c9d1a7..69ca2a54026d 100755
--- a/src/ghes-releases/scripts/deprecate/create-docs-ghes-version-repo.sh
+++ b/src/ghes-releases/scripts/deprecate/create-docs-ghes-version-repo.sh
@@ -46,7 +46,7 @@ mutation($repositoryId:ID!,$branch:String!,$requiredReviews:Int!) {
}' -f repositoryId="$repositoryId" -f branch=main -F requiredReviews=1 --silent
echo "--- Enable GitHub Pages, set source to main in root directory, and make the pages site public"
gh api -X POST "/repos/github/docs-ghes-$version/pages" \
- -f "source[branch]=main" -f "source[path]=/" -f "public=true" --silent
+ -f "source[branch]=main" -f "source[path]=/" -F "public=true" --silent
echo "--- Update custom properties"
gh api --method PATCH /repos/github/docs-ghes-$version/properties/values \
-f "properties[][property_name]=ownership-name" \
diff --git a/src/ghes-releases/scripts/deprecate/index.ts b/src/ghes-releases/scripts/deprecate/index.ts
index 4b9be2b51c89..b62542a2c179 100644
--- a/src/ghes-releases/scripts/deprecate/index.ts
+++ b/src/ghes-releases/scripts/deprecate/index.ts
@@ -3,31 +3,38 @@ import { execSync } from 'child_process'
import { updateContentFiles } from '@/ghes-releases/scripts/deprecate/update-content'
import { updateDataFiles } from '@/ghes-releases/scripts/deprecate/update-data'
import { updateAutomatedConfigFiles } from '@/ghes-releases/scripts/deprecate/update-automated-pipelines'
-
-program.option('-f, --foo', 'enable some foo')
+import { collapseBlankLines } from '@/ghes-releases/scripts/deprecate/collapse-blank-lines'
program
- .description('Update deprecated versions frontmatter and remove deprecated content files.')
.command('content')
+ .description('Update deprecated versions frontmatter and remove deprecated content files.')
.action(updateContentFiles)
program
+ .command('data')
.description(
'Update deprecated versions in data files, remove empty data files, and remove deleted reusables from content files.',
)
- .command('data')
.action(updateDataFiles)
program
+ .command('pipelines')
.description(
'Removes automated pipeline data files and updates the automated pipeline config files.',
)
- .command('pipelines')
.action(updateAutomatedConfigFiles)
program
+ .command('collapse-blank-lines')
+ .description(
+ 'Collapse 2+ consecutive blank lines left by removed Liquid into one, in changed markdown files only. Pass --check to report without writing.',
+ )
+ .option('--check', 'Report files with double blank lines and exit non-zero instead of fixing.')
+ .action((options) => collapseBlankLines({ check: options.check }))
+
+program
+ .command('create-repo')
.description('Create new `github/docs-ghes-` repository.')
- .command('repo')
.option('-v, --version ', 'The GHES version to create the repo for.')
.action((options) => {
if (!options.version) {
diff --git a/src/graphql/data/fpt/changelog.json b/src/graphql/data/fpt/changelog.json
index 1989655bb1bd..9ed8a823e089 100644
--- a/src/graphql/data/fpt/changelog.json
+++ b/src/graphql/data/fpt/changelog.json
@@ -1,4 +1,17 @@
[
+ {
+ "schemaChanges": [
+ {
+ "title": "The GraphQL schema includes these changes:",
+ "changes": [
+ "
Enum value THREADS was added to enum SocialAccountProvider
"
diff --git a/src/graphql/data/ghec/schema.docs.graphql b/src/graphql/data/ghec/schema.docs.graphql
index 134049b6a71c..410d11a9f537 100644
--- a/src/graphql/data/ghec/schema.docs.graphql
+++ b/src/graphql/data/ghec/schema.docs.graphql
@@ -59330,6 +59330,11 @@ enum SocialAccountProvider @docsCategory(name: "users") {
"""
REDDIT
+ """
+ Microblogging social platform.
+ """
+ THREADS
+
"""
Live-streaming service.
"""
diff --git a/src/graphql/scripts/sync.ts b/src/graphql/scripts/sync.ts
index 4d84455e9d06..2cb08cb86f0c 100755
--- a/src/graphql/scripts/sync.ts
+++ b/src/graphql/scripts/sync.ts
@@ -10,6 +10,8 @@ import processPreviews from './utils/process-previews'
import processUpcomingChanges from './utils/process-upcoming-changes'
import processSchemas from './utils/process-schemas'
import { bucketSchemaByCategory, writeCategoryFiles } from './utils/bucket-by-category'
+import { syncCategoryContentFiles, type CategoryPresence } from './utils/sync-category-content'
+import { ALL_KIND_KEYS } from '@/graphql/lib/categories'
import {
prependDatedEntry,
createChangelogEntry,
@@ -65,6 +67,12 @@ if (!process.env.GITHUB_TOKEN) {
const versionsToBuild = Object.keys(allVersions)
+// Tracks, per category, the set of docs versions in which the category has at
+// least one type. Populated inside the per-version loop and consumed after it
+// to manage the per-category content pages. Declared before `main()` runs so
+// the loop never reads it in the temporal dead zone.
+const categoryPresence: CategoryPresence = new Map()
+
main()
const allIgnoredChanges: IgnoredChange[] = []
@@ -145,6 +153,17 @@ async function main() {
const perCategoryFiles = bucketSchemaByCategory(schemaJsonPerVersion)
await writeCategoryFiles(path.join(graphqlStaticDir, graphqlVersion), perCategoryFiles)
+ // Record which categories have at least one type in this version so the
+ // content pages and their `versions` frontmatter can be managed after the
+ // loop. `version` is the docs version key (e.g. `enterprise-server@3.22`),
+ // which is the format `convertVersionsToFrontmatter` expects.
+ for (const [cat, bucket] of perCategoryFiles.entries()) {
+ const hasTypes = ALL_KIND_KEYS.some((kind) => (bucket[kind]?.length ?? 0) > 0)
+ if (!hasTypes) continue
+ if (!categoryPresence.has(cat)) categoryPresence.set(cat, new Set())
+ categoryPresence.get(cat)!.add(version)
+ }
+
// 4. UPDATE CHANGELOG
if (allVersions[version].nonEnterpriseDefault) {
// The changelog is only built for free-pro-team@latest
@@ -173,6 +192,11 @@ async function main() {
}
}
+ // Manage the per-category content pages (create new categories, delete
+ // emptied ones, narrow `versions` frontmatter) plus the reference index
+ // children and disappearance redirects, based on the presence collected above.
+ await syncCategoryContentFiles(categoryPresence)
+
// Ensure the YAML linter runs before checkinging in files
execSync('npx prettier -w "**/*.{yml,yaml}"')
diff --git a/src/graphql/scripts/utils/sync-category-content.ts b/src/graphql/scripts/utils/sync-category-content.ts
new file mode 100644
index 000000000000..02a58d150c5d
--- /dev/null
+++ b/src/graphql/scripts/utils/sync-category-content.ts
@@ -0,0 +1,177 @@
+import fs from 'fs/promises'
+import path from 'path'
+import walk from 'walk-sync'
+import matter from '@gr2m/gray-matter'
+import { isEqual } from 'lodash-es'
+
+import {
+ updateContentDirectory,
+ convertVersionsToFrontmatter,
+} from '@/automated-pipelines/lib/update-markdown'
+import { CATEGORIES, OTHER_CATEGORY, categoryTitle } from '@/graphql/lib/categories'
+
+// Default directory holding the per-category GraphQL reference content pages.
+// Overridable via options for tests; production always uses this path.
+const DEFAULT_CONTENT_DIR = path.join('content', 'graphql', 'reference')
+// Value of the `autogenerated` frontmatter on managed category pages. The
+// content-directory helper uses this to know which files it owns (and may
+// therefore delete when a category empties).
+const AUTOGENERATED_TYPE = 'graphql'
+// Breadcrumb category the reference pages sit under in the sidebar.
+const CATEGORY_BREADCRUMB = 'Explore the schema reference'
+
+// Maps a category slug to the set of docs version keys (e.g.
+// `free-pro-team@latest`, `enterprise-server@3.22`) in which the category has
+// at least one type. Built by sync.ts from the per-version buckets.
+export type CategoryPresence = Map>
+
+const categoryUrlPath = (cat: string) => `/graphql/reference/${cat}`
+
+// Matches a bare category reference URL (no fragment), e.g.
+// `/graphql/reference/code-scanning`. Kind pages like
+// `/graphql/reference/queries` also match this shape but are filtered out
+// because their slug is not in CATEGORIES.
+const CATEGORY_URL_RE = /^\/graphql\/reference\/([a-z][a-z0-9-]*)$/
+
+function isPresentInAnyVersion(presence: CategoryPresence, cat: string): boolean {
+ return (presence.get(cat)?.size ?? 0) > 0
+}
+
+// Read the `redirect_from` of every managed category page before the content
+// helper potentially deletes those files, so redirect chains aren't lost when a
+// category disappears. Returns a map of category slug -> redirect_from entries.
+async function captureCategoryRedirects(contentDir: string): Promise