Skip to content

Commit 0fefc1b

Browse files
Merge branch 'main' into codex/add-copilot-cli-auth-next-steps
2 parents 8a2901b + 16cfd4d commit 0fefc1b

14 files changed

Lines changed: 803 additions & 14 deletions

File tree

.github/workflows/codeql.yml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,6 @@ jobs:
3030
runs-on: ubuntu-latest
3131
steps:
3232
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
33-
- name: Generate GitHub App token
34-
id: app-token
35-
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
36-
with:
37-
app-id: ${{ secrets.DOCS_BOT_APP_ID }}
38-
private-key: ${{ secrets.DOCS_BOT_APP_PRIVATE_KEY }}
39-
owner: github
40-
repositories: docs-engineering
4133

4234
- uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7
4335
with:
@@ -50,6 +42,16 @@ jobs:
5042
with:
5143
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
5244

45+
- name: Generate GitHub App token
46+
if: ${{ failure() && github.event_name != 'pull_request' }}
47+
id: app-token
48+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
49+
with:
50+
app-id: ${{ secrets.DOCS_BOT_APP_ID }}
51+
private-key: ${{ secrets.DOCS_BOT_APP_PRIVATE_KEY }}
52+
owner: github
53+
repositories: docs-engineering
54+
5355
- uses: ./.github/actions/create-workflow-failure-issue
5456
if: ${{ failure() && github.event_name != 'pull_request' }}
5557
with:

.github/workflows/purge-fastly.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ on:
2727

2828
permissions:
2929
contents: read
30+
deployments: read
3031

3132
# Serialize full-cache purges so two can't overlap and leave the cache in an
3233
# unknown state. Every other run (per-deploy, per-language) gets a unique group
@@ -104,6 +105,20 @@ jobs:
104105
fi
105106
npm run purge-fastly -- "${args[@]}"
106107
108+
- name: Hard-purge changed English content URLs
109+
# Prod deploys only. The soft purge above just marks `language:en` stale,
110+
# so stale-while-revalidate can keep serving the pre-deploy copy of a
111+
# just-changed page for a while. This evicts the specific English URLs
112+
# whose content/ files changed in this deploy, so the next request fetches
113+
# fresh. By the time the deploy succeeds the old pods are already gone, so
114+
# the refill is deterministically the new content. data/ changes stay
115+
# covered by the soft purge above (too many URLs to enumerate cheaply).
116+
if: ${{ github.event_name == 'deployment_status' }}
117+
env:
118+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
119+
HEAD_SHA: ${{ github.event.deployment.sha }}
120+
run: npm run purge-fastly-changed-content
121+
107122
- uses: ./.github/actions/slack-alert
108123
if: ${{ failure() && github.event_name != 'workflow_dispatch' }}
109124
with:

content/actions/get-started/understand-github-actions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ For a complete list of events that can be used to trigger workflows, see [Events
7171

7272
A **job** is a set of **steps** in a workflow that is executed on the same **runner**. Each step is either a shell script that will be executed, or an **action** that will be run. Steps are executed in order and are dependent on each other. Since each step is executed on the same runner, you can share data from one step to another. For example, you can have a step that builds your application followed by a step that tests the application that was built.
7373

74+
{% ifversion actions-nga %}
75+
Steps run in order by default, but you can also run selected steps concurrently when your workflow benefits from parallel execution, such as starting a long-running service while later steps continue. For more information, see [AUTOTITLE](/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstepsbackground).
76+
{% endif %}
77+
7478
You can configure a job's dependencies with other jobs; by default, jobs have no dependencies and run in parallel. When a job takes a dependency on another job, it waits for the dependent job to complete before running.
7579

7680
You can also use a **matrix** to run the same job multiple times, each with a different combination of variables—like operating systems or language versions.

content/actions/reference/workflows-and-actions/workflow-syntax.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,143 @@ The maximum number of minutes to run the step before killing the process. Maximu
903903

904904
Fractional values are not supported. `timeout-minutes` must be a positive integer.
905905

906+
{% ifversion actions-nga %}
907+
908+
## `jobs.<job_id>.steps[*].background`
909+
910+
Runs a step asynchronously so the job continues to the next step without waiting for it to finish. Use `background: true` for long-running processes, such as databases, servers, or monitoring tasks, that need to run alongside other steps. You synchronize with background steps later using [`wait`](#jobsjob_idstepswait) or [`wait-all`](#jobsjob_idstepswait-all) or stop them with [`cancel`](#jobsjob_idstepscancel).
911+
912+
You can use `background` on steps that use `run` or `uses`. To reference a background step from [`wait`](#jobsjob_idstepswait) or [`cancel`](#jobsjob_idstepscancel), give it an [`id`](#jobsjob_idstepsid). A maximum of 10 background steps can run concurrently in a single job; additional background steps are queued until a slot is free.
913+
914+
Outputs and environment changes from a background step are only available after you run a `wait` or `wait-all` step that includes it. If a background step fails, the job fails at the next `wait` or `wait-all` that includes it (unless [`continue-on-error`](#jobsjob_idstepscontinue-on-error) is set on that step). An implicit `wait-all` runs before any post-job cleanup.
915+
916+
Use `background` when you need fine-grained control: starting a long-running process (like a server or database) that stays up while later steps run, referencing a specific step with [`wait`](#jobsjob_idstepswait) or [`cancel`](#jobsjob_idstepscancel), or interleaving background work with other steps. If you instead have a self-contained group of steps that should all finish before the job continues, [`parallel`](#jobsjob_idstepsparallel) is a more convenient shorthand.
917+
918+
### Example: Running a step in the background
919+
920+
```yaml
921+
steps:
922+
- name: Start server
923+
id: server
924+
run: npm start
925+
background: true
926+
927+
- name: Run tests against the server
928+
run: npm test
929+
930+
- name: Wait for the server step to finish
931+
wait: server
932+
```
933+
934+
## `jobs.<job_id>.steps[*].wait`
935+
936+
Pauses the job until one or more background steps complete. A `wait` step performs no work itself, it only blocks until the referenced background steps finish. Provide a single step `id` as a string, or multiple step `id`s as an array.
937+
938+
After a `wait` step completes, the outputs of the referenced background steps become available to subsequent steps. If a referenced background step failed, the `wait` step fails too.
939+
940+
### Example: Waiting for specific background steps
941+
942+
```yaml
943+
steps:
944+
- name: Build frontend
945+
id: build-frontend
946+
run: npm run build:frontend
947+
background: true
948+
949+
- name: Build backend
950+
id: build-backend
951+
run: npm run build:backend
952+
background: true
953+
954+
- name: Run linter while builds run
955+
run: npm run lint
956+
957+
- name: Wait for both builds to finish
958+
wait: [build-frontend, build-backend]
959+
960+
- name: Run tests
961+
run: npm test
962+
```
963+
964+
## `jobs.<job_id>.steps[*].wait-all`
965+
966+
Pauses the job until all active background steps complete. This is useful when several background steps are running and you want them all to finish before continuing. Like `wait`, the `wait-all` step fails if any of the background steps it waits on failed, unless you set [`continue-on-error`](#jobsjob_idstepscontinue-on-error) to `true`.
967+
968+
The `wait-all` keyword takes no arguments.
969+
970+
### Example: Waiting for all background steps
971+
972+
```yaml
973+
steps:
974+
- name: Start database
975+
id: db
976+
run: docker run -d postgres:15
977+
background: true
978+
979+
- name: Start cache
980+
id: cache
981+
run: docker run -d redis:7
982+
background: true
983+
984+
- name: Run integration tests
985+
run: npm run test:integration
986+
987+
- name: Wait for all services to stop
988+
wait-all:
989+
```
990+
991+
## `jobs.<job_id>.steps[*].cancel`
992+
993+
Gracefully terminates a running background step. The runner sends the step's process a termination signal (`SIGTERM`) so it can clean up, and forcibly stops it (`SIGKILL`) if it does not exit within a short grace period. The `cancel` keyword targets a single background step by its `id`.
994+
995+
### Example: Canceling a background step
996+
997+
```yaml
998+
steps:
999+
- name: Start long-running monitor
1000+
id: monitor
1001+
run: ./scripts/monitor.sh
1002+
background: true
1003+
1004+
- name: Run the main task
1005+
run: npm test
1006+
1007+
- name: Stop the monitor
1008+
cancel: monitor
1009+
```
1010+
1011+
## `jobs.<job_id>.steps[*].parallel`
1012+
1013+
Runs a group of steps concurrently, then waits for all of them to finish before continuing. The `parallel` keyword is shorthand: every step in the group runs as a background step, with an implicit `wait` at the end of the group. Use it when you have an independent group of steps that can run at the same time and you don't need to reference them individually.
1014+
1015+
Use `parallel` when you have a self-contained group of steps that should all finish before the job moves on, such as building several components at once. Use [`background`](#jobsjob_idstepsbackground) when you need finer control: starting a long-running process (like a server or database) that stays up while later steps run, referencing a specific step with [`wait`](#jobsjob_idstepswait) or [`cancel`](#jobsjob_idstepscancel), or interleaving background work with other steps. In short, `parallel` is more limited but more convenient for the "run this group at once" case, while `background` is the general-purpose primitive.
1016+
1017+
Each step in the group is subject to the same 10-step concurrency limit as other background steps.
1018+
1019+
### Example: Running steps in parallel
1020+
1021+
```yaml
1022+
steps:
1023+
- uses: {% data reusables.actions.action-checkout %}
1024+
1025+
- parallel:
1026+
- name: Build frontend
1027+
run: npm run build:frontend
1028+
1029+
- name: Build backend
1030+
run: npm run build:backend
1031+
1032+
- name: Build docs
1033+
run: npm run build:docs
1034+
1035+
- name: Run tests after all builds complete
1036+
run: npm test
1037+
```
1038+
1039+
The group above is equivalent to declaring each step with `background: true` followed by a `wait` step.
1040+
1041+
{% endif %}
1042+
9061043
## `jobs.<job_id>.timeout-minutes`
9071044

9081045
The maximum number of minutes to let a job run before {% data variables.product.prodname_dotcom %} automatically cancels it. Default: 360

content/actions/tutorials/migrate-to-github-actions/manual-migrations/migrate-from-jenkins.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Jenkins uses directives to manage _Declarative Pipelines_. These directives defi
7676

7777
### Parallel job processing
7878

79-
Jenkins can run the `stages` and `steps` in parallel, while {% data variables.product.prodname_actions %} currently only runs jobs in parallel.
79+
{% ifversion actions-nga %}Jenkins can run the `stages` and `steps` in parallel. {% data variables.product.prodname_actions %} runs jobs in parallel and can also run steps concurrently within a job using step-level syntax. For more information, see [AUTOTITLE](/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstepsbackground).{% else %}Jenkins can run the `stages` and `steps` in parallel, while {% data variables.product.prodname_actions %} currently only runs jobs in parallel.{% endif %}
8080

8181
| Jenkins Parallel | {% data variables.product.prodname_actions %} |
8282
| ------------- | ------------- |

data/reusables/copilot/copilot-cli-pat-steps.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
1. Under **Repository access**, select the level of access appropriate for your use case:
44
* **Public repositories** if you only need to work with public repos.
55
* **All repositories** if you need access across all your current and future repos.
6-
* **Only select repositories** f you want to restrict access to specific repos.
6+
* **Only select repositories** if you want to restrict access to specific repos.
77
1. Under **Permissions**, select the **Account** tab.
88
1. Click **Add permissions** and select **{% data variables.product.prodname_copilot_short %} Requests**.
99
1. Click **Generate token**.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"prettier-check": "prettier -c \"**/*.{ts,tsx,scss,yml,yaml}\"",
8181
"prevent-pushes-to-main": "tsx src/workflows/prevent-pushes-to-main.ts",
8282
"purge-fastly": "tsx src/workflows/purge-fastly.ts",
83+
"purge-fastly-changed-content": "tsx src/workflows/purge-fastly-changed-content.ts",
8384
"readability-report": "tsx src/workflows/experimental/readability-report.ts",
8485
"ready-for-docs-review": "tsx src/workflows/ready-for-docs-review.ts",
8586
"release-banner": "tsx src/ghes-releases/scripts/release-banner.ts",
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { describe, expect, test, vi, beforeEach } from 'vitest'
2+
3+
import { SecretScanningTransformer } from '@/article-api/transformers/secret-scanning-transformer'
4+
import shortVersionsMiddleware from '@/versions/middleware/short-versions'
5+
import { allVersions } from '@/versions/lib/all-versions'
6+
import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases'
7+
import { getSecretScanningData } from '@/secret-scanning/lib/get-secret-scanning-data'
8+
import type { Context, ExtendedRequest, Page, SecretScanningData } from '@/types'
9+
10+
vi.mock('@/secret-scanning/lib/get-secret-scanning-data')
11+
vi.mock('@/article-api/lib/load-template', () => ({
12+
loadTemplate: () => '{{ content }}',
13+
}))
14+
15+
const ghesConditional = '{% ifversion ghes %}false{% else %}true{% endif %}'
16+
17+
const makeEntry = (): SecretScanningData =>
18+
({
19+
provider: 'Example',
20+
supportedSecret: 'Example Token',
21+
secretType: 'example_token',
22+
versions: {},
23+
isPublic: true,
24+
isPrivateWithGhas: true,
25+
hasPushProtection: true,
26+
hasValidityCheck: ghesConditional,
27+
hasExtendedMetadata: ghesConditional,
28+
base64Supported: false,
29+
isduplicate: false,
30+
}) as SecretScanningData
31+
32+
const stubPage = {
33+
autogenerated: 'secret-scanning',
34+
title: 'Test',
35+
intro: '',
36+
render: vi.fn().mockResolvedValue(''),
37+
renderProp: vi.fn().mockResolvedValue(''),
38+
} as unknown as Page
39+
40+
const buildContext = async (currentVersion: string): Promise<Context> => {
41+
const req = { language: 'en', query: {} } as ExtendedRequest
42+
req.context = { currentVersion, allVersions, enterpriseServerReleases } as Context
43+
req.context.currentVersionObj = allVersions[currentVersion]
44+
await shortVersionsMiddleware(req, null, () => {})
45+
return req.context
46+
}
47+
48+
describe('SecretScanningTransformer Liquid evaluation', () => {
49+
const transformer = new SecretScanningTransformer()
50+
51+
beforeEach(() => {
52+
vi.clearAllMocks()
53+
})
54+
55+
const oldestGhes = enterpriseServerReleases.oldestSupported
56+
57+
test('resolves GHES conditionals to false on enterprise-server', async () => {
58+
vi.mocked(getSecretScanningData).mockResolvedValue([makeEntry()])
59+
const context = await buildContext(`enterprise-server@${oldestGhes}`)
60+
61+
await transformer.transform(stubPage, '/test', context)
62+
63+
expect(context.secretScanningData).toBeDefined()
64+
expect(context.secretScanningData![0].hasValidityCheck).toBe(false)
65+
expect(context.secretScanningData![0].hasExtendedMetadata).toBe(false)
66+
})
67+
68+
test('resolves GHES conditionals to true on free-pro-team', async () => {
69+
vi.mocked(getSecretScanningData).mockResolvedValue([makeEntry()])
70+
const context = await buildContext('free-pro-team@latest')
71+
72+
await transformer.transform(stubPage, '/test', context)
73+
74+
expect(context.secretScanningData).toBeDefined()
75+
expect(context.secretScanningData![0].hasValidityCheck).toBe(true)
76+
expect(context.secretScanningData![0].hasExtendedMetadata).toBe(true)
77+
})
78+
})

src/article-api/transformers/secret-scanning-transformer.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,24 @@ export class SecretScanningTransformer implements PageTransformer {
4040

4141
// Process Liquid in values
4242
for (const entry of data) {
43-
// Only process Liquid for the hasValidityCheck field, as in the middleware
43+
// Process Liquid for the hasValidityCheck field, as in the middleware
4444
if (typeof entry.hasValidityCheck === 'string' && entry.hasValidityCheck.includes('{%')) {
4545
// Render Liquid and parse as YAML to get correct boolean type
4646
entry.hasValidityCheck = load(
4747
await liquid.parseAndRender(entry.hasValidityCheck, context),
4848
) as boolean
4949
}
5050

51+
// Process Liquid for the hasExtendedMetadata field, as in the middleware
52+
if (
53+
typeof entry.hasExtendedMetadata === 'string' &&
54+
entry.hasExtendedMetadata.includes('{%')
55+
) {
56+
entry.hasExtendedMetadata = load(
57+
await liquid.parseAndRender(entry.hasExtendedMetadata, context),
58+
) as boolean
59+
}
60+
5161
if (entry.isduplicate) {
5262
entry.secretType += ' <br/><a href="#token-versions">Token versions</a>'
5363
}

src/secret-scanning/middleware/secret-scanning.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,13 @@ export default async function secretScanning(
4848
// to execute that Liquid to get the actual value.
4949
for (const entry of req.context.secretScanningData) {
5050
for (const [key, value] of Object.entries(entry)) {
51-
if (key === 'hasValidityCheck' && typeof value === 'string' && value.includes('{%')) {
51+
if (
52+
(key === 'hasValidityCheck' || key === 'hasExtendedMetadata') &&
53+
typeof value === 'string' &&
54+
value.includes('{%')
55+
) {
5256
const evaluated = yaml.load(await liquid.parseAndRender(value, req.context))
53-
entry[key] = evaluated as string
57+
entry[key] = evaluated as boolean | string
5458
}
5559
}
5660
if (entry.isduplicate) {

0 commit comments

Comments
 (0)