-
Notifications
You must be signed in to change notification settings - Fork 0
186 lines (172 loc) · 8.84 KB
/
Copy pathrelease.yml
File metadata and controls
186 lines (172 loc) · 8.84 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
name: release
# Every push to main releases, if the commits since the last tag warrant one.
# No release PR, no approval step: semantic-release reads the conventional
# commits, works out the next version, tags it, writes the GitHub release notes
# and publishes to npm in a single run.
#
# A push with nothing releasable in it (docs:, chore:, ci:) is a no-op. That is
# the behaviour to expect rather than a failure — see .releaserc.json for which
# commit types earn which bump.
#
# PUBLISHES TO npmjs, PUBLICLY, WITH NO TOKEN. Authentication is npm trusted
# publishing: GitHub Actions mints a short-lived OIDC token, npm verifies it
# against the Trusted Publisher configured on the package, and no long-lived
# secret exists to leak or expire. `id-token: write` below is what makes that
# possible, and @semantic-release/npm@13 detects the OIDC context and skips its
# token check.
#
# Every release goes to BOTH registries: npmjs first (canonical), then mirrored
# to GitHub Packages. npm is what the consumer repos install from, because
# GitHub Packages requires an access token to install even for public packages —
# per GitHub's own docs — which would put a PAT back in every developer's
# ~/.npmrc and every CI job. The mirror exists for the org-internal listing, not
# for consumption.
#
# npm provenance is generated automatically under trusted publishing, so the
# published package carries a verifiable link back to this repo and this
# workflow run. It requires a public repo and a public package; both hold.
#
# ONE-TIME SETUP ON npmjs, which cannot be done from here:
# npmjs.com -> @engineio/ui -> Settings -> Trusted Publisher
# Organization or user: engineio
# Repository: ui
# Workflow filename: release.yml
# Until that exists the publish step fails with an auth error, which is correct
# — it means nothing can publish under this name without going through here.
#
# WHAT THIS DELIBERATELY DOES NOT DO: write anything back to main. The usual
# semantic-release setup adds @semantic-release/changelog and
# @semantic-release/git so the version bump and CHANGELOG land as a commit on
# the release branch. Both are omitted, because main requires pull requests and
# a bot pushing straight to it would need a bypass nobody should hand a CI job.
# So package.json's version stays the placeholder 0.0.0-development — tags are
# the source of truth, and the real version is written in the runner just
# before publishing — and the GitHub release notes are the changelog.
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: release
cancel-in-progress: false
permissions:
contents: write # create tags and releases
id-token: write # mint the OIDC token for npm trusted publishing
packages: write # mirror the release to GitHub Packages
issues: write # semantic-release/github verifies these even with
pull-requests: write # comments switched off
jobs:
release:
runs-on: ubuntu-latest
steps:
# fetch-depth: 0 is required, not tidiness — semantic-release works out
# the next version by reading every commit since the last tag, and a
# shallow clone has neither the tags nor the history to do it.
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.14
# Node 22.14+ is the floor for trusted publishing. registry-url points
# npm at npmjs and writes the .npmrc it reads; there is no token to put
# in it.
- uses: actions/setup-node@v6
with:
node-version: "22.14"
registry-url: https://registry.npmjs.org
# Node 22 bundles npm 10.x, and trusted publishing needs npm >= 11.5.1.
# Without this the publish fails asking for credentials, which looks like
# a misconfigured Trusted Publisher rather than an old CLI.
- name: Ensure npm supports trusted publishing
run: |
npm install -g npm@^11
npm --version
- run: bun install --frozen-lockfile
# Gate the release on the same checks CI runs. A broken build must not
# become a published version — once a version exists on the registry it
# cannot be replaced, only superseded.
- name: Verify before releasing
run: |
bun run check
bun run build
- name: Release
run: bunx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Bootstrap escape hatch, and normally empty.
#
# npm's docs do not say whether a Trusted Publisher can be registered
# for a package name that does not exist yet, and a Trusted Publisher
# is configured in a package's settings — which a package needs to
# exist to have. If pre-registering turns out to be impossible, add an
# npm automation token as the NPM_TOKEN secret, let the first publish
# use it, then configure the Trusted Publisher and DELETE the secret.
#
# Passing it unconditionally is safe either way: an unset secret
# arrives as an empty string, @semantic-release/npm only writes an
# authToken when the value is truthy, and with nothing written it
# falls through to the OIDC path. So this line needs no edit when the
# secret is added or removed.
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# BOTH names are required, and leaving this one out is why five
# release attempts failed with `401 GET /-/whoami`.
#
# `setup-node` with `registry-url` writes its own user-level .npmrc
# containing `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}`
# and points NPM_CONFIG_USERCONFIG at it. With NODE_AUTH_TOKEN unset,
# npm expands it to an empty string and every registry call is
# unauthenticated — the same shadowing bug that an empty
# ${NODE_AUTH_TOKEN} in a project .npmrc causes locally, except
# inflicted by the action rather than by a checked-in file.
#
# NPM_TOKEN is what @semantic-release/npm reads; NODE_AUTH_TOKEN is
# what setup-node's .npmrc dereferences. Both point at the same
# secret. Under trusted publishing this whole block becomes
# unnecessary and can go.
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# npm is the CANONICAL registry — it is what both consumer repos install
# from, and the only one that needs no credential. This mirrors the same
# version to GitHub Packages for the org-internal package listing and for
# anything still pointed there. If the two ever disagree, npm is right.
#
# Runs only when semantic-release actually released: its npm plugin writes
# the real version into package.json in the workspace during prepare, so a
# version other than the placeholder is the signal. A push with nothing
# releasable leaves the placeholder and this no-ops.
#
# --registry overrides publishConfig.registry on the command line, which
# is why package.json stays pinned to npmjs. GITHUB_TOKEN suffices —
# GitHub Packages accepts it for a same-repo publish, so there is no extra
# secret. Note this does NOT make GitHub Packages installable without a
# token: that is a GitHub restriction on reads, not on publishing.
- name: Mirror to GitHub Packages
run: |
set -euo pipefail
version=$(node -p "require('./package.json').version")
if [ "$version" = "0.0.0-development" ]; then
echo "no release this run — nothing to mirror"
exit 0
fi
# A DEDICATED npmrc, because setup-node's one only carries auth for
# registry.npmjs.org. Publishing with --registry=npm.pkg.github.com
# against it fails ENEEDAUTH: npm finds no credential for that host
# and does not fall back. NPM_CONFIG_USERCONFIG also means the
# runner's ~/.npmrc is ignored, so appending there does nothing.
rc=$(mktemp)
printf '//npm.pkg.github.com/:_authToken=%s\n' "$NODE_AUTH_TOKEN" > "$rc"
echo "mirroring @engineio/ui@$version to GitHub Packages"
# A version already present on the mirror is success, not failure —
# it means a previous run got this far. Anything else is a real error.
if NPM_CONFIG_USERCONFIG="$rc" npm publish \
--registry=https://npm.pkg.github.com \
--ignore-scripts --tag latest 2>&1 | tee /tmp/mirror.log; then
echo "mirrored"
elif grep -qE 'EPUBLISHCONFLICT|cannot publish over|already exists' /tmp/mirror.log; then
echo "$version already on GitHub Packages — nothing to do"
else
echo "::error::mirror to GitHub Packages failed"
exit 1
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}