-
Notifications
You must be signed in to change notification settings - Fork 0
287 lines (275 loc) · 13.8 KB
/
Copy pathci.yml
File metadata and controls
287 lines (275 loc) · 13.8 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
name: CI
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
commitlint:
name: Commitlint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Validate the last commit with commitlint
if: github.event_name == 'push'
run: pnpm exec commitlint --last --verbose
- name: Validate every PR commit with commitlint
if: github.event_name == 'pull_request'
run: pnpm exec commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
typecheck:
name: Typecheck
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
# attw (are-the-types-wrong) checks the built .d.ts emit against the package's exports map for resolution/shape errors the rest of CI cannot catch. It needs the built dist/ to analyse, so build first -- matching the prepublishOnly gate that previously was the only place attw ran.
- run: pnpm build
- run: pnpm exec attw --pack
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
code-quality: write # to upload the cobertura coverage report below
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test:coverage
# Cloudflare Workers (workerd) runtime test -- pnpm install above already built the workerd binary (allowBuilds in pnpm-workspace.yaml), enforcing zero Node-only API usage on the tested paths at runtime.
- run: pnpm test:workers
- name: Upload coverage report
# Code Quality requires the org on GitHub Team/Enterprise Cloud, which ExaDev is not yet on, so the upload call itself will fail until that changes -- fail-on-error: false keeps that failure a log annotation instead of gating the release job below on a feature we can't turn on yet. Also guarded against fork PRs, which never hold the code-quality: write permission to upload.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: actions/upload-code-coverage@v1
with:
file: coverage/cobertura-coverage.xml
language: typescript
label: unit
fail-on-error: false
test-smoke:
name: Smoke test
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test:smoke
release:
name: Release
needs: [commitlint, lint, typecheck, test, test-smoke]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write # to push the release commit/tag and create the GitHub Release
issues: write # to comment on released issues
pull-requests: write # to comment on released pull requests
id-token: write # OIDC identity for npm trusted publishing (no NPM_TOKEN)
outputs:
published: ${{ steps.before.outputs.version != steps.after.outputs.version }}
version: ${{ steps.after.outputs.version }}
steps:
# main requires status checks to merge, and the default GITHUB_TOKEN has no bypass for that -- @semantic-release/git's own push of the release commit is a direct push to main, so it needs a token from an actor the branch ruleset explicitly allows to bypass (see ExaDev/.github and each repo's own ruleset). Everything else in this job (release notes, GitHub Release creation, issue/PR comments, OIDC npm publish) keeps using secrets.GITHUB_TOKEN below, unaffected -- only checkout's own git credentials need the elevated token, since that's what the later `git push` inherits.
- name: Generate a token for the release push
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: "4473709"
private-key: ${{ secrets.AUTOMERGE_APP_PRIVATE_KEY }}
- uses: actions/checkout@v7
with:
# semantic-release analyses the full commit history since the last release.
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: pnpm
# registry-url is deliberately absent. Setting it makes setup-node write an .npmrc containing an _authToken line, and that line wins over the OIDC token exchange -- so the setting that looks like it configures the registry is exactly the one that would stop trusted publishing working.
- run: pnpm install --frozen-lockfile
- name: Read pre-release version
id: before
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
- name: Upgrade npm for OIDC trusted publishing (needs npm CLI >=11.5.1)
run: npm install -g npm@latest
- name: Release
# HUSKY=0 so the commit-msg hook never fires against the automated release commit.
run: HUSKY=0 pnpm exec semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Blanked, not omitted -- an inherited NPM_TOKEN/NODE_AUTH_TOKEN from a workflow-level env block, reusable workflow, or composite action would otherwise be used in preference to the OIDC exchange.
NPM_TOKEN: ''
NODE_AUTH_TOKEN: ''
- name: Read post-release version
id: after
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
notify-downstream:
name: Notify downstream repositories
needs: release
if: needs.release.outputs.published == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- name: Generate a token for cross-repo dispatch
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: "4473709"
private-key: ${{ secrets.AUTOMERGE_APP_PRIVATE_KEY }}
repositories: |
documents.js
documents
- name: Dispatch sibling-released event to downstream repos
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
version="${{ needs.release.outputs.version }}"
for repo in documents.js documents; do
gh api "repos/ExaDev/$repo/dispatches" \
-f event_type=sibling-released \
-F "client_payload[package]=markdown-codec" \
-F "client_payload[version]=$version"
done
publish-github-packages:
name: Publish alias to GitHub Packages
needs: release
if: needs.release.outputs.published == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
packages: write
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v7
with:
ref: main # the release commit semantic-release just pushed
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: pnpm
# Deliberately NOT setup-node's own registry-url/scope inputs (see ExaDev/documents.js#309): those write an @exadev:registry=https://npm.pkg.github.com/ *install-time* scope-to-registry mapping into .npmrc, and per setup-node's own docs an empty/omitted `scope` still falls back to the repository owner (ExaDev) when the target is GitHub Packages -- there is no way to get just the auth-token line out of that mechanism without also redirecting every @exadev-scoped *install* through it, which breaks `pnpm install` below: @exadev/eslint-config is a real devDependency published only to the default registry, never mirrored to GitHub Packages. publishConfig.registry (set explicitly below) already fully determines pnpm publish's *target* registry on its own; only the host-scoped auth token below is genuinely needed, added after install so install never sees any @exadev scope mapping at all.
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Rewrite package name and registry for the GitHub Packages scope
# GitHub Packages requires the npm package name to be scoped to the repo owner. Rewriting the fields rather than keeping a second package.json means this alias cannot drift away from the real package's metadata. publishConfig.registry has to be overridden too: it takes precedence over the .npmrc registry-url set by setup-node above, so without this the publish silently targets registry.npmjs.org instead -- confirmed failure mode, not a hypothetical (404 on the npmjs.org registry, since GITHUB_TOKEN isn't a credential it recognises).
run: |
npm pkg set name="@exadev/markdown-codec"
npm pkg set publishConfig.registry="https://npm.pkg.github.com"
- name: Configure the GitHub Packages auth token for publish only
run: echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
- run: pnpm publish --access public --no-git-checks
publish-aliases:
name: Publish aliases (${{ matrix.name }})
needs: release
if: needs.release.outputs.published == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
id-token: write # OIDC identity for npm trusted publishing -- every leg in this job targets npmjs.org. The GitHub Packages alias is a separate job above specifically so it never holds this permission: pnpm attempts an OIDC exchange whenever id-token: write is available regardless of target registry, and GitHub Packages has no such exchange to attempt -- confirmed the hard way in a sibling repo when this leg used to share that job and failed with "401 Unauthorized - authentication token not provided" even with GITHUB_TOKEN correctly set, because the failed OIDC attempt never fell through to it.
strategy:
fail-fast: false
matrix:
include:
- { name: mrkdwn.js, registry: 'https://registry.npmjs.org' }
steps:
- uses: actions/checkout@v7
with:
ref: main # the release commit semantic-release just pushed
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: pnpm
# registry-url is deliberately absent. Setting it makes setup-node write an .npmrc containing an _authToken line, and that line wins over the OIDC exchange -- so the setting that looks like it configures the registry is exactly the one that would break trusted publishing.
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Rewrite package name for this alias
# Rewriting the checked-out package.json rather than keeping a package.json per alias means no alias can drift away from the real package's metadata -- each matrix leg gets its own fresh runner and checkout, so there is no cross-contamination between legs.
run: npm pkg set name="${{ matrix.name }}"
- run: pnpm publish --access public --no-git-checks
env:
# Blanked, not omitted -- an inherited NODE_AUTH_TOKEN would otherwise be used in preference to the OIDC exchange, matching the release job's own convention above.
NODE_AUTH_TOKEN: ''
attest:
name: Attest SBOM and build provenance
needs: release
if: needs.release.outputs.published == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@v7
with:
ref: main # the release commit semantic-release just pushed
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
# Pack into a directory of its own, separate from dist/ (tsdown's raw build output). The attestation subject has to be the artefact that actually ships -- attesting dist/ itself would mix in files that never leave the repo, producing digests that match nothing a consumer can download.
- run: pnpm pack --pack-destination release-artifact
- run: pnpm sbom --sbom-format spdx --prod > release-artifact/sbom.spdx.json
# attest-sbom/attest-build-provenance are deprecated wrappers around actions/attest; called as two steps (sbom-path present vs. absent) rather than one, since actions/attest's docs don't clearly state whether passing both together attests SBOM and provenance in a single call or SBOM only -- two steps is the unambiguous equivalent of what this replaced.
- name: Attest SBOM
uses: actions/attest@v4
with:
subject-path: release-artifact/*.tgz
sbom-path: release-artifact/sbom.spdx.json
- name: Attest build provenance
uses: actions/attest@v4
with:
subject-path: release-artifact/*.tgz