Repository navigation
328 lines (320 loc) · 13.4 KB
/
Copy pathci.yml
File metadata and controls
328 lines (320 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
name: Validate repository
on:
pull_request:
merge_group:
workflow_dispatch:
permissions:
contents: read
# Superseding pushes to the same PR cancel the stale run instead of letting it
# race to completion; merge_group runs never get a duplicate ref, so they
# always finish undisturbed.
concurrency:
group:
ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref
}}
cancel-in-progress: true
jobs:
classify:
name: Classify changed paths
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
document_only: ${{ steps.classify.outputs.document_only }}
documents_b64: ${{ steps.classify.outputs.documents_b64 }}
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js
# The classifier gates every other job and imports only `node:`
# builtins, so it needs a runtime but no dependencies. Without this it
# inherits whatever `node` the runner image happens to ship, and a
# self-hosted runner claiming the `ubuntu-24.04` label ships none
# (#2096).
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
- name: Classify changed documents
id: classify
run:
node --input-type=module -e "import { runClassifier } from
'./scripts/lib/content-only-ci.mjs'; runClassifier()"
env:
GITHUB_EVENT_NAME: ${{ github.event_name }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
MERGE_BASE_SHA: ${{ github.event.merge_group.base_sha }}
validate:
name: Validate repository
needs: classify
outputs:
document_only:
${{ needs.classify.result == 'success' &&
needs.classify.outputs.document_only == 'true' }}
if: ${{ !cancelled() }}
env:
FULL_VALIDATION:
${{ needs.classify.result != 'success' ||
needs.classify.outputs.document_only != 'true' }}
CONTENT_ONLY:
${{ needs.classify.result == 'success' &&
needs.classify.outputs.document_only == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Node.js and install dependencies
uses: ./.github/actions/setup-node-deps
- name: Audit dependencies against the advisory allowlist
if: ${{ !cancelled() && env.FULL_VALIDATION == 'true' }}
run: npm run check:audit
- name: Run unit tests with coverage
if: ${{ !cancelled() && env.FULL_VALIDATION == 'true' }}
# A zone fourteen hours ahead of UTC, so a date formatted in the
# host zone instead of UTC lands on a different calendar day for most
# of the day; the suite used to pin TZ=UTC, which hid exactly that.
env:
TZ: Pacific/Kiritimati
run: npm run test:unit:coverage:check | tee coverage-summary.txt
shell: bash -o pipefail {0}
- name: Publish coverage summary
if: ${{ !cancelled() && env.FULL_VALIDATION == 'true' }}
run: |
if [ -f coverage-summary.txt ]; then
{ echo '## Unit test coverage'; echo '```'; cat coverage-summary.txt; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload coverage report
if: ${{ !cancelled() && env.FULL_VALIDATION == 'true' }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: coverage-summary
path: coverage-summary.txt
retention-days: 90
- name: Validate repository data
if: ${{ !cancelled() && env.FULL_VALIDATION == 'true' }}
run: |
npm run validate:metrics
npm run validate:architectures
npm run validate:architecture-assets
npm run validate:awards
npm run validate:community-people
npm run validate:community-groups
npm run validate:case-studies
npm run validate:radar-reports
npm run validate:projects-born
npm run validate:end-user-talks
npm run validate:button-contrast
npm run validate:enduser-members
npm run validate:members
- name: Check content contracts
if: ${{ !cancelled() && env.CONTENT_ONLY == 'true' }}
# Every unit suite that reads an authored document (docs/, blog/,
# adr/ or a root document). tests/content-only-ci-workflow.test.mjs
# scans tests/ for suites that read those trees and fails when one is
# missing here without a stated reason. tests/audit-gate.test.mjs
# also reads SECURITY.md, but only in the register checks that now
# live in tests/security-advisory-register.test.mjs; what stays
# behind there spends ~40s on the CLI retry and deadline arms, so it
# stays on the full path.
run: |
node --test \
tests/docs-contract.test.mjs \
tests/markdown-references.test.mjs \
tests/community-group-coverage.test.mjs \
tests/adr-contract.test.mjs \
tests/architectures-sidebar-contract.test.mjs \
tests/blog-frontmatter.test.mjs \
tests/community-people-data.test.mjs \
tests/content-only-ci-workflow.test.mjs \
tests/content-security-contract.test.mjs \
tests/contributing-e2e-setup.test.mjs \
tests/contributing-just-build-parity.test.mjs \
tests/dev-environment.test.mjs \
tests/docs-threshold-parity.test.mjs \
tests/e2e-docs-threshold-parity.test.mjs \
tests/enduser-directory-copy.test.mjs \
tests/events-catalog.test.mjs \
tests/events-index.test.mjs \
tests/events-page.test.mjs \
tests/events-sidebar-contract.test.mjs \
tests/governance-hygiene-parity.test.mjs \
tests/mdx-components.test.mjs \
tests/mdx-paragraph-nesting.test.mjs \
tests/root-docs-links.test.mjs \
tests/security-advisory-register.test.mjs \
tests/site-config.test.mjs \
tests/sitemap-exclusions.test.mjs \
tests/static-assets.test.mjs \
tests/static-orphans.test.mjs
- name: Build production site
run: npm run build:production
lint:
name: Lint repository
needs: classify
if: ${{ !cancelled() }}
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Node.js and install dependencies
uses: ./.github/actions/setup-node-deps
- name: Check changed-document formatting, spelling, and Markdown
if:
${{ !cancelled() && needs.classify.result == 'success' &&
needs.classify.outputs.document_only == 'true' }}
run:
node --input-type=module -e "import { runDocumentLint } from
'./scripts/lib/content-only-ci.mjs';
runDocumentLint(process.env.CHANGED_DOCUMENTS_B64)"
env:
CHANGED_DOCUMENTS_B64: ${{ needs.classify.outputs.documents_b64 }}
- name: Run full repository lint
if:
${{ !cancelled() && (needs.classify.result != 'success' ||
needs.classify.outputs.document_only != 'true') }}
run: |
npm run check:format
npm run check:spelling
npm run check:markdown
npm run check:duplication
npm run check:lint
npm run check:unused
e2e:
name: End-to-end tests
# Depends on classify alone so the browser suites start alongside validate
# and lint instead of after them; the three jobs no longer share a critical
# path. A document-only change skips the browsers on the classification
# itself (ADR 0011, amendment of 2026-11-01). A failed classify leaves
# document_only unset, so the comparison below runs the browsers then too.
needs: classify
if: ${{ !cancelled() && needs.classify.outputs.document_only != 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Node.js and install dependencies
uses: ./.github/actions/setup-node-deps
- name: Install Playwright browsers
uses: ./.github/actions/setup-playwright
- name: Build production site
# Deliberately built without SITE_URL/BASE_URL: the suite navigates
# with paths relative to the site root (playwright.config.js sets
# baseURL to the bare localhost origin), which only matches a build
# whose baseUrl is the default "/".
run: npm run build:production
- name: Run end-to-end tests
run: npm run test:e2e
- name: Upload Playwright traces
# The CI reporter (playwright.config.js) is 'github', which only
# annotates the run; it writes no HTML report. Failure traces
# (trace: 'retain-on-failure') land in test-results/ instead.
# Runs on green jobs too: with retries: 1 a flaky spec fails, retries,
# passes, and the job exits 0 — under `if: failure()` the trace of
# that first failing attempt was discarded, leaving no evidence to
# debug the flake (#1179). `!cancelled()` keeps the upload out of
# cancelled runs, and if-no-files-found: ignore keeps fully green
# runs quiet instead of warning about an empty test-results/.
if: ${{ !cancelled() }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: playwright-traces
path: test-results/
retention-days: 7
if-no-files-found: ignore
e2e-coverage:
name: End-to-end coverage
# Same gate as e2e, for the same reasons.
needs: classify
if: ${{ !cancelled() && needs.classify.outputs.document_only != 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 20
env:
RUN_DIR: coverage/e2e/${{ github.run_id }}-${{ github.run_attempt }}
RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Node.js and install dependencies
uses: ./.github/actions/setup-node-deps
- name: Install Playwright browsers
uses: ./.github/actions/setup-playwright
- name: Initialize coverage run
run: >-
node tests/tools/e2e-coverage-run.mjs init --dir "$RUN_DIR" --run-id
"$RUN_ID"
- name: Build source-mapped e2e site
run: npm run build:e2e:coverage
- name: Run e2e tests with coverage
id: coverage_tests
run: npm run test:e2e:coverage
env:
E2E_COVERAGE_DIR: ${{ env.RUN_DIR }}
E2E_COVERAGE_RUN_ID: ${{ env.RUN_ID }}
- name: Seal coverage run
if: always()
run: |
status=failed
if [ "${{ steps.coverage_tests.outcome }}" = "success" ]; then
status=passed
fi
node tests/tools/e2e-coverage-run.mjs seal --dir "$RUN_DIR" --status "$status"
- name: Render e2e coverage report
if: always()
run: |
mkdir -p coverage/e2e/report
npm run report:e2e:coverage -- \
--input "$RUN_DIR" \
--build build \
--check-source 100 \
--check-source-regions 96 \
--require-source-files \
--json coverage/e2e/report.json \
--text coverage/e2e/report.txt
- name: Publish e2e coverage summary
if: always()
run: |
if [ -f coverage/e2e/report.txt ]; then
{
echo '## E2E browser coverage'
echo '```'
cat coverage/e2e/report.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
else
echo '::error::E2E coverage report was not produced.'
echo '## E2E browser coverage' >> "$GITHUB_STEP_SUMMARY"
echo 'Coverage tooling failed before producing a report; inspect the artifact.' >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload e2e coverage artifacts
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: e2e-coverage
path: coverage/e2e/
if-no-files-found: warn
retention-days: 90
- name: Upload Playwright traces
# The describeCoverage blocks (E2E_COVERAGE=1) run in this job and
# nowhere else, so a flake in one of them cannot be diagnosed from the
# gating e2e job's traces (#1412). Same contract as that job's upload:
# survives a green run so a pass-on-retry keeps its first attempt, and
# stays quiet when no spec failed.
if: ${{ !cancelled() }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: playwright-traces-coverage
path: test-results/
retention-days: 7
if-no-files-found: ignore