From 0eaa21e4ca5d1a61c6223eb14659e76f2668af53 Mon Sep 17 00:00:00 2001 From: Sophie <29382425+sophietheking@users.noreply.github.com> Date: Thu, 2 Jul 2026 19:44:21 +0200 Subject: [PATCH 1/5] [2026-06-04] Direct-org billing of Copilot CLI in GitHub Actions [GA] (#60462) Co-authored-by: cmuto09 Co-authored-by: Chris Muto --- .../copilot-cli-in-github-actions.md | 48 +++++++++++++ .../concepts/agents/copilot-cli/index.md | 1 + .../automate-with-actions.md | 18 ++++- content/copilot/how-tos/copilot-cli/index.md | 1 + .../copilot-cli/use-copilot-cli-in-actions.md | 68 +++++++++++++++++++ 5 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 content/copilot/concepts/agents/copilot-cli/copilot-cli-in-github-actions.md create mode 100644 content/copilot/how-tos/copilot-cli/use-copilot-cli-in-actions.md diff --git a/content/copilot/concepts/agents/copilot-cli/copilot-cli-in-github-actions.md b/content/copilot/concepts/agents/copilot-cli/copilot-cli-in-github-actions.md new file mode 100644 index 000000000000..9324ff168358 --- /dev/null +++ b/content/copilot/concepts/agents/copilot-cli/copilot-cli-in-github-actions.md @@ -0,0 +1,48 @@ +--- +title: About using Copilot CLI in GitHub Actions +shortTitle: Copilot CLI in Actions +allowTitleToDifferFromFilename: true +intro: 'You can run {% data variables.copilot.copilot_cli_short %} in a {% data variables.product.prodname_actions %} workflow using either a {% data variables.product.pat_generic %} or the built-in `GITHUB_TOKEN`. The two approaches differ in how {% data variables.product.prodname_ai_credits_short %} are billed and what setup is required.' +versions: + feature: copilot +contentType: concepts +category: + - Learn about Copilot CLI # Copilot CLI bespoke page +docsTeamMetrics: + - copilot-cli +--- + +## Authentication and billing options + +When you run {% data variables.copilot.copilot_cli_short %} in a {% data variables.product.prodname_actions %} workflow, you can authenticate using either a {% data variables.product.pat_generic %} (PAT) or the built-in `GITHUB_TOKEN`. + +* **Using a PAT**: The workflow authenticates as the user who created the PAT. {% data variables.product.prodname_ai_credits_short %} are drawn from that user's {% data variables.product.prodname_copilot_short %} seat entitlements, and their license determines which models and features are available. This works in any repository but introduces operational and security risks for organizations running automations at scale. +* **Using `GITHUB_TOKEN`**: The workflow authenticates as an installation, with no individual user associated with the request. How {% data variables.product.prodname_ai_credits_short %} are billed depends on where the workflow runs: + + * In a **personally-owned repository**, usage is billed to the repository owner's {% data variables.product.prodname_copilot_short %} seat. + * In an **organization-owned repository**, usage is metered directly to the organization. This requires the **"Allow use of {% data variables.copilot.copilot_cli_short %} billed to the organization"** policy to be enabled by an organization owner. + +Using `GITHUB_TOKEN` in an organization-owned repository is the recommended approach for automations. Each workflow run receives a short-lived, scoped token generated by {% data variables.product.prodname_actions %}, so no long-lived credentials need to be stored or rotated. + +Note that this policy is separate from your {% data variables.product.prodname_copilot_short %} licensing setup. Enterprises that issue licenses through a dedicated organization and do their work in other organizations do not need {% data variables.product.prodname_copilot_short %} licensing enabled in the working organization, only the policy. + +## Controlling cost + +When usage is billed directly to the organization, user-level {% data variables.product.prodname_copilot_short %} budgets are not considered, because the cost is not attributed to any individual user. To manage spend for {% data variables.copilot.copilot_cli_short %} usage billed this way, you can: + +* Configure cost centers for the relevant organizations. Cost centers allow cost attribution to groups of organizations, and budgets can be applied to cost centers. See [AUTOTITLE](/billing/concepts/cost-centers). +* Monitor {% data variables.product.prodname_copilot_short %} usage from your organization's billing and usage dashboards to track consumption over time. + +## Security considerations + +Running {% data variables.copilot.copilot_cli_short %} in automated workflows introduces security risks that are independent of which authentication method you use. Because {% data variables.copilot.copilot_cli_short %} is an agentic tool that can read and modify repository contents, a compromised or misconfigured workflow can cause unintended changes. + +To reduce risk: + +* Use [{% data variables.product.github %} Agentic Workflows](https://github.com/github/gh-aw) rather than invoking {% data variables.copilot.copilot_cli_short %} directly in `run` steps. Agentic Workflows are designed with guardrails for automated use. +* Follow the principle of least privilege when setting workflow permissions. +* Review workflow triggers carefully. Workflows that run on pull request events from forks are at higher risk of prompt injection. + +## Next steps + +To learn how to set up {% data variables.copilot.copilot_cli_short %} with `GITHUB_TOKEN` in a {% data variables.product.prodname_actions %} workflow, see [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli-in-actions). diff --git a/content/copilot/concepts/agents/copilot-cli/index.md b/content/copilot/concepts/agents/copilot-cli/index.md index 05782ff81ccb..e9eb3de15373 100644 --- a/content/copilot/concepts/agents/copilot-cli/index.md +++ b/content/copilot/concepts/agents/copilot-cli/index.md @@ -8,6 +8,7 @@ versions: children: - /about-copilot-cli - /comparing-cli-features + - /copilot-cli-in-github-actions - /cancel-and-roll-back - /about-remote-control - /about-custom-agents diff --git a/content/copilot/how-tos/copilot-cli/automate-copilot-cli/automate-with-actions.md b/content/copilot/how-tos/copilot-cli/automate-copilot-cli/automate-with-actions.md index 79017f210813..16a239cb79f6 100644 --- a/content/copilot/how-tos/copilot-cli/automate-copilot-cli/automate-with-actions.md +++ b/content/copilot/how-tos/copilot-cli/automate-copilot-cli/automate-with-actions.md @@ -17,11 +17,18 @@ docsTeamMetrics: You can run {% data variables.copilot.copilot_cli %} in a {% data variables.product.prodname_actions %} workflow to automate AI-powered tasks as part of your CI/CD process. For example, you can summarize recent repository activity, generate reports, or scaffold project content. {% data variables.copilot.copilot_cli %} runs on the Actions runner like any other CLI tool, so you can install it during a job and invoke it from workflow steps. +## Recommended approach: {% data variables.product.github %} Agentic Workflows + +For most automation use cases, we recommend using [{% data variables.product.github %} Agentic Workflows](https://github.com/github/gh-aw) rather than invoking `copilot` directly in workflow steps. Agentic workflows use `GITHUB_TOKEN` authentication by default and include additional guardrails suited for automated environments. + +For setup instructions, see [Quick Start](https://github.github.com/gh-aw/setup/quick-start/) in the {% data variables.product.github %} Agentic Workflows documentation. + ## Using {% data variables.copilot.copilot_cli_short %} in an Actions workflow You can define a job in a {% data variables.product.prodname_actions %} workflow that: installs {% data variables.copilot.copilot_cli_short %} on the runner, authenticates it, runs it in programmatic mode, and then handles the results. Programmatic mode is designed for scripts and automation and lets you pass a prompt non-interactively. Workflows can follow this pattern: + 1. **Trigger**: Start the workflow on a schedule, in response to repository events, or manually. 1. **Setup**: Checkout code, set up environment. 1. **Install**: Install {% data variables.copilot.copilot_cli %} on the runner. @@ -118,14 +125,19 @@ In this example, the workflow installs {% data variables.copilot.copilot_cli %} ## Authenticate -To allow {% data variables.copilot.copilot_cli_short %} to run on an Actions runner, you need to authenticate a {% data variables.product.github %} user account with a valid {% data variables.product.prodname_copilot_short %} license. +To allow {% data variables.copilot.copilot_cli_short %} to run on an Actions runner, you need to provide authentication credentials. There are two options: + +* **Using `GITHUB_TOKEN`** (recommended for organization-owned repositories): No PAT or stored secrets required. See [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli-in-actions). +* **Using a {% data variables.product.pat_generic %}**: An alternative to `GITHUB_TOKEN`, for example if your organization has not enabled the policy, or if you want usage billed to a specific user's {% data variables.product.prodname_copilot_short %} seat. Follow the steps below. + +**Step 1: Create a {% data variables.product.pat_generic %} (PAT) with the "{% data variables.product.prodname_copilot_short %} Requests" permission:** -**Step 1: Create a {% data variables.product.pat_generic %} (PAT) with the "Copilot Requests" permission:** 1. Go to your personal settings for creating a {% data variables.product.pat_v2 %}: [github.com/settings/personal-access-tokens/new](https://github.com/settings/personal-access-tokens/new?ref_product=copilot&ref_type=engagement&ref_style=text). -1. Create a new PAT with the "Copilot Requests" permission. +1. Create a new PAT with the "{% data variables.product.prodname_copilot_short %} Requests" permission. 1. Copy the token value. **Step 2: Store the PAT as an Actions repository secret:** + 1. In your repository, go to **Settings** > **Secrets and variables** > **Actions** and click **New repository secret**. 1. Give the secret a name that you will use in the workflow. In this example we're using `PERSONAL_ACCESS_TOKEN` as the name of the secret. 1. Paste the token value into the "Secret" field and click **Add secret**. diff --git a/content/copilot/how-tos/copilot-cli/index.md b/content/copilot/how-tos/copilot-cli/index.md index 324e9dc679b5..e33c5920f8e3 100644 --- a/content/copilot/how-tos/copilot-cli/index.md +++ b/content/copilot/how-tos/copilot-cli/index.md @@ -21,6 +21,7 @@ children: - /automate-copilot-cli - /customize-copilot - /administer-copilot-cli-for-your-enterprise + - /use-copilot-cli-in-actions - /automate-copilot-cli/automate-with-actions - /automate-copilot-cli/quickstart - /automate-copilot-cli/run-cli-programmatically diff --git a/content/copilot/how-tos/copilot-cli/use-copilot-cli-in-actions.md b/content/copilot/how-tos/copilot-cli/use-copilot-cli-in-actions.md new file mode 100644 index 000000000000..c2ceef3c9518 --- /dev/null +++ b/content/copilot/how-tos/copilot-cli/use-copilot-cli-in-actions.md @@ -0,0 +1,68 @@ +--- +title: Using Copilot CLI in GitHub Actions with GITHUB_TOKEN +shortTitle: Copilot CLI in Actions +intro: 'Run {% data variables.copilot.copilot_cli_short %} in a {% data variables.product.prodname_actions %} workflow using the built-in `GITHUB_TOKEN`, without a {% data variables.product.pat_generic %}.' +versions: + feature: copilot +contentType: how-tos +allowTitleToDifferFromFilename: true +category: + - Manage Copilot for a team # Copilot discovery page + - Administer Copilot CLI # Copilot CLI bespoke landing page +docsTeamMetrics: + - copilot-cli +--- + +For background on authentication options and how billing works when running {% data variables.copilot.copilot_cli_short %} in {% data variables.product.prodname_actions %}, see [AUTOTITLE](/copilot/concepts/agents/copilot-cli/copilot-cli-in-github-actions). + +## Enabling the policy + +For workflows in your organization to use {% data variables.copilot.copilot_cli_short %} with `GITHUB_TOKEN`, the policy must be enabled. This policy is enabled by default for organizations with {% data variables.copilot.copilot_cli_short %} turned on, but you can confirm or change this setting in your organization's policy settings. + +1. Navigate to the policy settings for your organization. See [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-organization/manage-policies). +1. Under "{% data variables.copilot.copilot_cli_short %}", confirm that **Allow use of {% data variables.copilot.copilot_cli_short %} billed to the organization** is selected. + +## Recommended approach: {% data variables.product.github %} Agentic Workflows + +For most automation use cases, we recommend using [{% data variables.product.github %} Agentic Workflows](https://github.com/github/gh-aw) rather than invoking `copilot` directly in workflow steps. Agentic workflows use `GITHUB_TOKEN` authentication by default and include additional guardrails suited for automated environments. + +For setup instructions, see [Quick Start](https://github.github.com/gh-aw/setup/quick-start/) in the {% data variables.product.github %} Agentic Workflows documentation. Your workflow must also grant the `copilot-requests: write` permission. See [Permissions](https://github.github.com/gh-aw/reference/permissions/) in the {% data variables.product.github %} Agentic Workflows documentation. + +## Using {% data variables.copilot.copilot_cli_short %} directly in a workflow + +If you need to invoke {% data variables.copilot.copilot_cli_short %} directly in a workflow step, install the CLI with npm. + +> [!WARNING] +> Invoking {% data variables.copilot.copilot_cli_short %} directly in workflow steps gives it broad access to your workflow environment. Review your workflow triggers and permissions carefully before using this approach. Workflows triggered by pull requests from forks are particularly at risk. + +### Example workflow + +```yaml +name: Copilot CLI example +on: [push] + +permissions: + contents: read + copilot-requests: write + +jobs: + copilot: + runs-on: ubuntu-latest + steps: + - uses: {% data reusables.actions.action-checkout %} + - name: Install Copilot CLI + run: npm install -g @github/copilot + - name: Run Copilot + run: copilot --yolo -p "Summarize the changes in this commit" + env: + GITHUB_TOKEN: ${{ github.token }} +``` + +Key details about this example: + +* The `--yolo` flag suppresses interactive prompts, which is required for non-interactive environments like {% data variables.product.prodname_actions %}. +* The `copilot-requests: write` permission is required for the workflow to make {% data variables.product.prodname_copilot_short %} requests. +* The `GITHUB_TOKEN` provided by {% data variables.product.prodname_actions %} handles authentication automatically, no additional secrets are needed. + +> [!NOTE] +> You must be on a recent version of {% data variables.copilot.copilot_cli_short %} to use `GITHUB_TOKEN` authentication. Update with `copilot update`, or reinstall the latest version with `npm install -g @github/copilot`. From f09d5d0a9dcc6d6c711a6b75e165039b8cc97323 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 2 Jul 2026 11:30:25 -0700 Subject: [PATCH 2/5] Remove unused files (dead code) (#62046) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../lib/helpers/schema-utils.ts | 79 ------------------- .../ui/BumpLink/BumpLink.module.scss | 13 --- src/frame/components/ui/BumpLink/BumpLink.tsx | 35 -------- src/frame/components/ui/BumpLink/index.ts | 1 - src/release-notes/components/PlainLink.tsx | 15 ---- src/search/components/hooks/useBreakpoint.ts | 21 ----- src/search/components/hooks/useMediaQuery.ts | 28 ------- src/search/components/hooks/usePage.ts | 16 ---- 8 files changed, 208 deletions(-) delete mode 100644 src/content-linter/lib/helpers/schema-utils.ts delete mode 100644 src/frame/components/ui/BumpLink/BumpLink.module.scss delete mode 100644 src/frame/components/ui/BumpLink/BumpLink.tsx delete mode 100644 src/frame/components/ui/BumpLink/index.ts delete mode 100644 src/release-notes/components/PlainLink.tsx delete mode 100644 src/search/components/hooks/useBreakpoint.ts delete mode 100644 src/search/components/hooks/useMediaQuery.ts delete mode 100644 src/search/components/hooks/usePage.ts diff --git a/src/content-linter/lib/helpers/schema-utils.ts b/src/content-linter/lib/helpers/schema-utils.ts deleted file mode 100644 index c95f27e3aabf..000000000000 --- a/src/content-linter/lib/helpers/schema-utils.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { getFrontmatter } from './utils' - -// AJV validation error object structure -interface AjvValidationError { - instancePath: string - keyword: string - message: string - params: { - additionalProperty?: string - missingProperty?: string - [key: string]: unknown - } -} - -// Processed error object for markdown linting -interface ProcessedValidationError { - instancePath: string - detail: string - context: string - errorProperty: string - searchProperty: string -} - -export function formatAjvErrors(errors: AjvValidationError[] = []): ProcessedValidationError[] { - const processedErrors: ProcessedValidationError[] = [] - - for (const errorObj of errors) { - const error: Partial = {} - - error.instancePath = - errorObj.instancePath === '' - ? errorObj.instancePath - : errorObj.instancePath.slice(1).replace('/', '.') - - if (errorObj.keyword === 'additionalProperties') { - error.detail = 'The frontmatter includes an unsupported property.' - const pathContext = error.instancePath ? ` from \`${error.instancePath}\`` : '' - error.context = `Remove the property \`${errorObj.params.additionalProperty}\`${pathContext}.` - error.errorProperty = errorObj.params.additionalProperty - error.searchProperty = error.errorProperty - } - - // required rule - if (errorObj.keyword === 'required') { - error.detail = 'The frontmatter has a missing required property' - const pathContext = error.instancePath ? ` from \`${error.instancePath}\`` : '' - error.context = `Add the missing property \`${errorObj.params.missingProperty}\`${pathContext}` - error.errorProperty = errorObj.params.missingProperty - error.searchProperty = error.instancePath.split('.').pop() - } - - // all other rules - if (!error.detail) { - error.detail = `Frontmatter ${errorObj.message}.` - error.context = Object.values(errorObj.params).join('') - error.errorProperty = error.context - error.searchProperty = error.errorProperty - } - - processedErrors.push(error as ProcessedValidationError) - } - - return processedErrors -} - -// Alias for backward compatibility -export const processSchemaValidationErrors = formatAjvErrors - -// Schema validator interface - generic due to different schema types (AJV, JSON Schema, etc.) -interface SchemaValidator { - validate(data: unknown): boolean -} - -export function getSchemaValidator( - frontmatterLines: string[], -): (schema: SchemaValidator) => boolean { - const frontmatter = getFrontmatter(frontmatterLines) - return (schema: SchemaValidator) => schema.validate(frontmatter) -} diff --git a/src/frame/components/ui/BumpLink/BumpLink.module.scss b/src/frame/components/ui/BumpLink/BumpLink.module.scss deleted file mode 100644 index ac7823769445..000000000000 --- a/src/frame/components/ui/BumpLink/BumpLink.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -.container:hover .symbol { - opacity: 1; - transform: translateX(3px); -} - -.symbol { - display: inline-block; - transform: translateX(0); - color: inherit; - opacity: 0; - transition: 200ms; - transform: translateX(0); -} diff --git a/src/frame/components/ui/BumpLink/BumpLink.tsx b/src/frame/components/ui/BumpLink/BumpLink.tsx deleted file mode 100644 index a354495aae25..000000000000 --- a/src/frame/components/ui/BumpLink/BumpLink.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { cloneElement, ReactNode, ReactElement, ElementType } from 'react' -import cx from 'classnames' - -import styles from './BumpLink.module.scss' - -export type BumpLinkPropsT = { - children?: ReactNode - title: ReactElement<{ children?: ReactNode }> | string - href: string - as?: ElementType<{ className?: string; href: string }> - className?: string -} - -export const BumpLink = ({ as, children, href, title, className }: BumpLinkPropsT) => { - const Component = as || 'a' - - let extendedTitle: ReactNode - if (typeof title === 'string') { - extendedTitle = {title} - } else { - extendedTitle = cloneElement(title, title.props, title.props.children) - } - - return ( - - {extendedTitle} - - {children} - - ) -} diff --git a/src/frame/components/ui/BumpLink/index.ts b/src/frame/components/ui/BumpLink/index.ts deleted file mode 100644 index e6ef713a25e5..000000000000 --- a/src/frame/components/ui/BumpLink/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { BumpLink } from './BumpLink' diff --git a/src/release-notes/components/PlainLink.tsx b/src/release-notes/components/PlainLink.tsx deleted file mode 100644 index c05191bc1d8d..000000000000 --- a/src/release-notes/components/PlainLink.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type { ReactNode } from 'react' - -type PlainLinkProps = { - href: string - children: ReactNode - className?: string -} - -export function PlainLink({ href, className, children }: PlainLinkProps) { - return ( - - {children} - - ) -} diff --git a/src/search/components/hooks/useBreakpoint.ts b/src/search/components/hooks/useBreakpoint.ts deleted file mode 100644 index b3b8651462e8..000000000000 --- a/src/search/components/hooks/useBreakpoint.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useTheme } from '@primer/react' - -import { useMediaQuery } from './useMediaQuery' - -type Size = 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' -export function useMinWidthBreakpoint(size: Size) { - const { theme } = useTheme() - // For some reason, xsmall isn't in theme for Primer: https://github.com/primer/react/blob/308fe82909f3d922be0a6582f83e96798678ec78/packages/react/src/utils/layout.ts#L6 - let sizePx = theme?.sizes[size] - if (size === 'xsmall') { - sizePx = '320px' - } - return useMediaQuery(`(min-width: ${sizePx})`) -} - -export function useMaxWidthBreakpoint(sizePx: string) { - if (!sizePx.endsWith('px')) { - sizePx = `${sizePx}px` - } - return useMediaQuery(`(max-width: ${sizePx})`) -} diff --git a/src/search/components/hooks/useMediaQuery.ts b/src/search/components/hooks/useMediaQuery.ts deleted file mode 100644 index 433f09bf2e66..000000000000 --- a/src/search/components/hooks/useMediaQuery.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useState, useEffect } from 'react' - -export function useMediaQuery(query: string) { - const [state, setState] = useState( - typeof window !== 'undefined' ? window.matchMedia(query).matches : false, - ) - - useEffect(() => { - let mounted = true - const mql = window.matchMedia(query) - const onChange = () => { - if (!mounted) { - return - } - setState(!!mql.matches) - } - - mql.addEventListener('change', onChange) - setState(mql.matches) - - return () => { - mounted = false - mql.removeEventListener('change', onChange) - } - }, [query]) - - return state -} diff --git a/src/search/components/hooks/usePage.ts b/src/search/components/hooks/usePage.ts deleted file mode 100644 index dbf3386732fd..000000000000 --- a/src/search/components/hooks/usePage.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useRouter } from 'next/router' - -type Info = { - page: number -} -export const usePage = (): Info => { - const router = useRouter() - const page = parseInt( - router.query.page && Array.isArray(router.query.page) - ? router.query.page[0] - : router.query.page || '', - ) - return { - page: !isNaN(page) && page >= 1 ? page : 1, - } -} From c7e89e6929e493822ecd45a185370e54ea3bc63f Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 2 Jul 2026 12:24:29 -0700 Subject: [PATCH 3/5] Remove unused dependencies (#62049) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- package-lock.json | 33 ---------------------- package.json | 5 ---- src/tools/components/scroll-anchoring.d.ts | 8 ------ 3 files changed, 46 deletions(-) delete mode 100644 src/tools/components/scroll-anchoring.d.ts diff --git a/package-lock.json b/package-lock.json index fa385cf86aa1..741a1b1f2efa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,6 @@ "remark-rehype": "^11.1.2", "remark-remove-comments": "^1.1.1", "remark-stringify": "^11.0.0", - "scroll-anchoring": "^0.1.0", "semver": "^7.7.4", "sharp": "0.33.5", "slash": "^5.1.0", @@ -127,7 +126,6 @@ "@types/cookie": "0.6.0", "@types/cookie-parser": "1.4.8", "@types/eslint-plugin-jsx-a11y": "^6.10.1", - "@types/event-to-promise": "^0.7.5", "@types/express": "^5.0.6", "@types/imurmurhash": "^0.1.4", "@types/js-cookie": "^3.0.6", @@ -152,7 +150,6 @@ "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-custom-rules": "file:src/eslint-rules", - "eslint-plugin-escompat": "^3.11.4", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-github": "^6.0.0", @@ -162,7 +159,6 @@ "eslint-plugin-no-only-tests": "^3.3.0", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-primer-react": "^9.0.0", - "event-to-promise": "^0.8.0", "globals": "^17.3.0", "gpt-tokenizer": "^3.4.0", "graphql": "^16.12.0", @@ -180,7 +176,6 @@ "patch-package": "^8.0.1", "prettier": "^3.8.1", "rimraf": "^6.1.3", - "robots-parser": "^3.0.1", "sass": "^1.97.3", "start-server-and-test": "^3.0.0", "unist-util-remove": "^4.0.0", @@ -5888,15 +5883,6 @@ "@types/estree": "*" } }, - "node_modules/@types/event-to-promise": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@types/event-to-promise/-/event-to-promise-0.7.5.tgz", - "integrity": "sha512-h10M3ybTySQFVP4N1uiEgPwbpHExNS8UMpCqRUJFkMhlpgSlWsyYsGMmkrJIKRnhGfYDOb4LD3U+SSPujoMHNA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/express": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", @@ -9727,11 +9713,6 @@ "node": ">= 0.6" } }, - "node_modules/event-to-promise": { - "version": "0.8.0", - "dev": true, - "license": "MIT" - }, "node_modules/eventemitter3": { "version": "4.0.7", "dev": true, @@ -15620,16 +15601,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/robots-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/robots-parser/-/robots-parser-3.0.1.tgz", - "integrity": "sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/rollup": { "version": "4.61.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.0.tgz", @@ -15847,10 +15818,6 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, - "node_modules/scroll-anchoring": { - "version": "0.1.0", - "license": "MIT" - }, "node_modules/section-matter": { "version": "1.0.0", "license": "MIT", diff --git a/package.json b/package.json index 7bf6cdf1e53d..f23bdff74747 100644 --- a/package.json +++ b/package.json @@ -259,7 +259,6 @@ "remark-rehype": "^11.1.2", "remark-remove-comments": "^1.1.1", "remark-stringify": "^11.0.0", - "scroll-anchoring": "^0.1.0", "semver": "^7.7.4", "sharp": "0.33.5", "slash": "^5.1.0", @@ -288,7 +287,6 @@ "@types/cookie": "0.6.0", "@types/cookie-parser": "1.4.8", "@types/eslint-plugin-jsx-a11y": "^6.10.1", - "@types/event-to-promise": "^0.7.5", "@types/express": "^5.0.6", "@types/imurmurhash": "^0.1.4", "@types/js-cookie": "^3.0.6", @@ -313,7 +311,6 @@ "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-custom-rules": "file:src/eslint-rules", - "eslint-plugin-escompat": "^3.11.4", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-github": "^6.0.0", @@ -323,7 +320,6 @@ "eslint-plugin-no-only-tests": "^3.3.0", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-primer-react": "^9.0.0", - "event-to-promise": "^0.8.0", "globals": "^17.3.0", "gpt-tokenizer": "^3.4.0", "graphql": "^16.12.0", @@ -341,7 +337,6 @@ "patch-package": "^8.0.1", "prettier": "^3.8.1", "rimraf": "^6.1.3", - "robots-parser": "^3.0.1", "sass": "^1.97.3", "start-server-and-test": "^3.0.0", "unist-util-remove": "^4.0.0", diff --git a/src/tools/components/scroll-anchoring.d.ts b/src/tools/components/scroll-anchoring.d.ts deleted file mode 100644 index bbd6f2f00671..000000000000 --- a/src/tools/components/scroll-anchoring.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare module 'scroll-anchoring' { - export function findAnchorNode(document: Document): Node | undefined - export function preserveAnchorNodePosition( - document: Document, - callback: () => Promise | T, - ): Promise - export function preservePosition(anchorNode: Node, callback: () => Promise | T): Promise -} From d10228d86487c9adcee7699c420db31a9d8959ab Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 2 Jul 2026 12:25:02 -0700 Subject: [PATCH 4/5] Remove unused exported symbols and one dead component (#62047) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../lib/helpers/liquid-utils.ts | 2 - src/content-linter/lib/helpers/utils.ts | 69 ------------------- src/frame/components/hooks/useFeatureFlags.ts | 7 -- src/landings/components/LandingSection.tsx | 27 -------- src/languages/components/useTranslation.ts | 8 --- src/links/lib/extract-links.ts | 16 ----- .../helpers/execute-search-actions.ts | 1 - src/search/components/hooks/useQuery.ts | 21 ------ .../lib/helpers/cse-copilot-docs-versions.ts | 6 -- .../lib/helpers/external-search-analytics.ts | 14 ---- 10 files changed, 171 deletions(-) delete mode 100644 src/landings/components/LandingSection.tsx diff --git a/src/content-linter/lib/helpers/liquid-utils.ts b/src/content-linter/lib/helpers/liquid-utils.ts index 79744a18346c..fb8ddc4dc5e5 100644 --- a/src/content-linter/lib/helpers/liquid-utils.ts +++ b/src/content-linter/lib/helpers/liquid-utils.ts @@ -30,8 +30,6 @@ export function getLiquidTokens( export const OUTPUT_OPEN = '{%' export const OUTPUT_CLOSE = '%}' -export const TAG_OPEN = '{{' -export const TAG_CLOSE = '}}' export const conditionalTags = ['if', 'elseif', 'unless', 'case', 'ifversion'] diff --git a/src/content-linter/lib/helpers/utils.ts b/src/content-linter/lib/helpers/utils.ts index 3e4d478542e1..d848cc98ec58 100644 --- a/src/content-linter/lib/helpers/utils.ts +++ b/src/content-linter/lib/helpers/utils.ts @@ -70,75 +70,6 @@ export function quotePrecedesLinkOpen(text: string | undefined): boolean { return text.endsWith('"') || text.endsWith("'") } -// Filters a list of tokens by token type only when they match -// a specific token type order. -// For example, if a list of tokens contains: -// -// [ -// { type: 'inline'}, -// { type: 'list_item_close'}, -// { type: 'list_item_open'}, -// { type: 'paragraph_open'}, -// { type: 'inline'}, -// { type: 'paragraph_close'}, -// ] -// -// And if the `tokenOrder` being looked for is: -// -// [ -// 'list_item_open', -// 'paragraph_open', -// 'inline' -// ] -// -// Then the return value would be the items that match that sequence: -// Index 2-4: -// [ -// { type: 'inline'}, <-- Index 0 - NOT INCLUDED -// { type: 'list_item_close'}, <-- Index 1 - NOT INCLUDED -// { type: 'list_item_open'}, <-- Index 2 - INCLUDED -// { type: 'paragraph_open'}, <-- Index 3 - INCLUDED -// { type: 'inline'}, <-- Index 4 - INCLUDED -// { type: 'paragraph_close'}, <-- Index 5 - NOT INCLUDED -// ] -// -export function filterTokensByOrder( - tokens: MarkdownToken[], - tokenOrder: string[], -): MarkdownToken[] { - const matches: MarkdownToken[] = [] - - // Get a list of token indexes that match the - // first token (root) in the tokenOrder array - const tokenRootIndexes: number[] = [] - const firstTokenOrderType = tokenOrder[0] - for (let index = 0; index < tokens.length; index++) { - const token = tokens[index] - if (token.type === firstTokenOrderType) { - tokenRootIndexes.push(index) - } - } - - // Loop through each root token index and check if - // the order matches the tokenOrder array - for (const tokenRootIndex of tokenRootIndexes) { - for (let i = 1; i < tokenOrder.length; i++) { - if (tokens[tokenRootIndex + i].type !== tokenOrder[i]) { - // This tokenRootIndex was a possible start, - // but doesn't match the tokenOrder perfectly, so break out - // of the inner loop before it reaches the end. - break - } - if (i === tokenOrder.length - 1) { - matches.push(...tokens.slice(tokenRootIndex, tokenRootIndex + i + 1)) - } - } - } - return matches -} - -export const docsDomains = ['docs.github.com', 'help.github.com', 'developer.github.com'] - // Lines is an array of strings read from a // Markdown file a split around new lines. // This is the format we get from Markdownlint. diff --git a/src/frame/components/hooks/useFeatureFlags.ts b/src/frame/components/hooks/useFeatureFlags.ts index 1cd834b6eb55..18e69617ccf2 100644 --- a/src/frame/components/hooks/useFeatureFlags.ts +++ b/src/frame/components/hooks/useFeatureFlags.ts @@ -1,8 +1 @@ -import { useMainContext } from '@/frame/components/context/MainContext' - export type FeatureFlags = Record - -export const useFeatureFlags = (): FeatureFlags => { - const { featureFlags } = useMainContext() - return featureFlags -} diff --git a/src/landings/components/LandingSection.tsx b/src/landings/components/LandingSection.tsx deleted file mode 100644 index 9a8917afe19f..000000000000 --- a/src/landings/components/LandingSection.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react' -import cx from 'classnames' -import { HeadingLink } from '@/frame/components/article/HeadingLink' -import { RenderedHTML } from '@/frame/components/ui/RenderedHTML/RenderedHTML' - -type Props = { - title?: string - sectionLink?: string - children?: React.ReactNode - className?: string - description?: string -} -export const LandingSection = ({ title, children, className, sectionLink, description }: Props) => { - return ( -
-
- {title && ( - - {title} - - )} - {description && } -
- {children} -
- ) -} diff --git a/src/languages/components/useTranslation.ts b/src/languages/components/useTranslation.ts index 02d359f2170b..68dbe33ca269 100644 --- a/src/languages/components/useTranslation.ts +++ b/src/languages/components/useTranslation.ts @@ -1,4 +1,3 @@ -import type { UIStrings } from '@/frame/components/context/MainContext' import { useMainContext } from '@/frame/components/context/MainContext' import { createTranslationFunctions } from '@/languages/lib/translation-utils' @@ -37,10 +36,3 @@ export const useTranslation = (namespaces: string | Array) => { return createTranslationFunctions(loadedData, namespaces) } - -/** - * Hook for App Router contexts that don't use MainContext - */ -export const useAppTranslation = (uiData: UIStrings, namespaces: string | Array) => { - return createTranslationFunctions(uiData, namespaces) -} diff --git a/src/links/lib/extract-links.ts b/src/links/lib/extract-links.ts index 8f91cb23eb66..2cf850669b12 100644 --- a/src/links/lib/extract-links.ts +++ b/src/links/lib/extract-links.ts @@ -355,22 +355,6 @@ export async function renderAndExtractLinks( } } -/** - * Read a file and extract links - */ -export async function extractLinksFromFile( - filePath: string, - context?: Context, -): Promise { - const content = fs.readFileSync(filePath, 'utf-8') - - if (context) { - return extractLinksWithLiquid(content, context) - } - - return extractLinksFromMarkdown(content) -} - /** * Get relative path from content root */ diff --git a/src/search/components/helpers/execute-search-actions.ts b/src/search/components/helpers/execute-search-actions.ts index 29834277ada2..cc4899fdab91 100644 --- a/src/search/components/helpers/execute-search-actions.ts +++ b/src/search/components/helpers/execute-search-actions.ts @@ -9,7 +9,6 @@ import { sanitizeSearchQuery } from '@/search/lib/sanitize-search-query' // Search context values for identifying each search event export const GENERAL_SEARCH_CONTEXT = 'general-search' export const AI_SEARCH_CONTEXT = 'ai-search' -export const COMBINED_SEARCH_CONTEXT = 'combined-search' // The logic that redirects to the /search page with the proper query params // The query params will be consumed in the general search middleware diff --git a/src/search/components/hooks/useQuery.ts b/src/search/components/hooks/useQuery.ts index 7e7a1631adaf..0cee0f3860b6 100644 --- a/src/search/components/hooks/useQuery.ts +++ b/src/search/components/hooks/useQuery.ts @@ -1,24 +1,3 @@ -import { useRouter } from 'next/router' - -type QueryInfo = { - query: string - debug: boolean -} -export const useQuery = (): QueryInfo => { - const router = useRouter() - const query = - router.query.query && Array.isArray(router.query.query) - ? router.query.query[0] - : router.query.query || '' - - const debug = parseDebug(router.query.debug) - - return { - query, - debug, - } -} - export function parseDebug(debug: string | Array | undefined) { if (debug === '') { // E.g. `?query=foo&debug` should be treated as truthy diff --git a/src/search/lib/helpers/cse-copilot-docs-versions.ts b/src/search/lib/helpers/cse-copilot-docs-versions.ts index eb2e96729785..486b2b5fb16e 100644 --- a/src/search/lib/helpers/cse-copilot-docs-versions.ts +++ b/src/search/lib/helpers/cse-copilot-docs-versions.ts @@ -2,12 +2,6 @@ import { versionToIndexVersionMap } from '../elasticsearch-versions' const CSE_COPILOT_DOCS_VERSIONS = ['dotcom', 'ghec', 'ghes'] -// Languages supported by cse-copilot -const DOCS_LANGUAGES = ['en'] -export function supportedCSECopilotLanguages() { - return DOCS_LANGUAGES -} - export function getCSECopilotSource(version: (typeof CSE_COPILOT_DOCS_VERSIONS)[number]) { if (!version) { throw new Error(`Missing required key 'version' in request body`) diff --git a/src/search/lib/helpers/external-search-analytics.ts b/src/search/lib/helpers/external-search-analytics.ts index 69da231f6bba..931c4f217012 100644 --- a/src/search/lib/helpers/external-search-analytics.ts +++ b/src/search/lib/helpers/external-search-analytics.ts @@ -124,20 +124,6 @@ function sanitizeUserAgent(userAgent: string | undefined): string { return 'other' } -/** - * Determines if a host should bypass client_name requirement for analytics - * Returns true if the host ends with github.net or githubapp.com - * (for internal staging environments) - * Note: docs.github.com is removed since normalizedHost will always be docs.github.com in production - * Note: localhost is NOT included here as it should send analytics with auto-set client_name - */ -export function shouldBypassClientNameRequirement(host: string | undefined): boolean { - if (!host) return false - - const normalizedHost = stripPort(host) - return normalizedHost.endsWith('.github.net') || normalizedHost.endsWith('.githubapp.com') -} - /** * Strips port number from host string */ From 8b47d602b6aa3b33564656b80864b057871f85a9 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 2 Jul 2026 12:25:08 -0700 Subject: [PATCH 5/5] Remove unused React imports (#62048) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx | 1 - src/graphql/components/BreakingChanges.tsx | 1 - src/graphql/components/GraphqlPage.tsx | 1 - src/graphql/components/Previews.tsx | 1 - src/landings/components/ProductSelections.tsx | 2 -- src/landings/components/TableOfContents.tsx | 2 -- src/landings/pages/home.tsx | 1 - src/search/components/input/SearchGroups.tsx | 1 - 8 files changed, 10 deletions(-) diff --git a/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx b/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx index c22dcfdabc52..c2ae7d829eec 100644 --- a/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx +++ b/src/frame/components/ui/MarkdownContent/UnrenderedMarkdownContent.tsx @@ -1,4 +1,3 @@ -import React from 'react' import ReactMarkdown from 'react-markdown' import type { Components } from 'react-markdown' import type { JSX } from 'react' diff --git a/src/graphql/components/BreakingChanges.tsx b/src/graphql/components/BreakingChanges.tsx index 2b6b71058dd0..657847082128 100644 --- a/src/graphql/components/BreakingChanges.tsx +++ b/src/graphql/components/BreakingChanges.tsx @@ -1,4 +1,3 @@ -import React from 'react' import cx from 'classnames' import { HeadingLink } from '@/frame/components/article/HeadingLink' diff --git a/src/graphql/components/GraphqlPage.tsx b/src/graphql/components/GraphqlPage.tsx index c22f772630b1..f6201f6d5488 100644 --- a/src/graphql/components/GraphqlPage.tsx +++ b/src/graphql/components/GraphqlPage.tsx @@ -1,4 +1,3 @@ -import React from 'react' import type { JSX } from 'react' import cx from 'classnames' diff --git a/src/graphql/components/Previews.tsx b/src/graphql/components/Previews.tsx index 029cf31ec3a5..6bea7d5690ed 100644 --- a/src/graphql/components/Previews.tsx +++ b/src/graphql/components/Previews.tsx @@ -1,4 +1,3 @@ -import React from 'react' import GithubSlugger from 'github-slugger' import cx from 'classnames' diff --git a/src/landings/components/ProductSelections.tsx b/src/landings/components/ProductSelections.tsx index 2a6836b0e62d..998fb5c9d622 100644 --- a/src/landings/components/ProductSelections.tsx +++ b/src/landings/components/ProductSelections.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import type { ProductT } from '@/frame/components/context/MainContext' import { ProductSelectionCard } from './ProductSelectionCard' diff --git a/src/landings/components/TableOfContents.tsx b/src/landings/components/TableOfContents.tsx index ba8d5a909c3b..d1de6e289d5a 100644 --- a/src/landings/components/TableOfContents.tsx +++ b/src/landings/components/TableOfContents.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import { Link } from '@/frame/components/Link' import type { TocItem } from '@/landings/types' import { RenderedHTML } from '@/frame/components/ui/RenderedHTML/RenderedHTML' diff --git a/src/landings/pages/home.tsx b/src/landings/pages/home.tsx index ee1c8af94287..ac959006c688 100644 --- a/src/landings/pages/home.tsx +++ b/src/landings/pages/home.tsx @@ -1,4 +1,3 @@ -import React from 'react' import type { GetServerSideProps } from 'next' import type { Response } from 'express' diff --git a/src/search/components/input/SearchGroups.tsx b/src/search/components/input/SearchGroups.tsx index 1f9adac0e07d..d3525bbc84c2 100644 --- a/src/search/components/input/SearchGroups.tsx +++ b/src/search/components/input/SearchGroups.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { ActionList, Spinner } from '@primer/react' import { SearchIcon,