Repository navigation
334 lines (319 loc) · 13.4 KB
/
Copy pathrelease.yml
File metadata and controls
334 lines (319 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
329
330
331
332
333
334
name: Release
on:
push:
tags:
- 'v[0-9]*.[0-9]*.[0-9]*'
# Runs the npm-oidc-check job only; the release path below is gated on push.
workflow_dispatch:
jobs:
build:
if: github.event_name == 'push'
strategy:
matrix:
include:
- runner: macos-latest
target: aarch64-apple-darwin
node_target: darwin-arm64
- runner: macos-15-intel
target: x86_64-apple-darwin
node_target: darwin-x64
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
node_target: linux-x64
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
node_target: linux-arm64
- runner: windows-2022
target: x86_64-pc-windows-gnu
node_target: win32-x64
runs-on: ${{ matrix.runner }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
# Windows cgo build needs a GCC toolchain (pg_query_go uses -std=gnu99)
- name: Set up MinGW (Windows)
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
install: mingw-w64-x86_64-gcc
path-type: inherit
- name: Build
run: |
ext=""
[ "${{ runner.os }}" = "Windows" ] && ext=".exe"
CGO_ENABLED=1 go build \
-ldflags "-X github.com/boringsql/dryrun/internal/buildinfo.Version=${GITHUB_REF_NAME}" \
-o "dryrun${ext}" \
./cmd/dryrun
- name: Archive (tar.xz)
if: runner.os != 'Windows'
run: tar -cJf dry_run_cli-${{ matrix.target }}.tar.xz dryrun
- name: Archive (zip)
if: runner.os == 'Windows'
run: 7z a dry_run_cli-${{ matrix.target }}.zip dryrun.exe
- uses: actions/upload-artifact@v4
with:
name: tar-${{ matrix.target }}
path: dry_run_cli-${{ matrix.target }}.*
retention-days: 1
# raw binary for the npm platform packages
- uses: actions/upload-artifact@v4
with:
name: bin-${{ matrix.node_target }}
path: dryrun${{ runner.os == 'Windows' && '.exe' || '' }}
retention-days: 1
release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: tar-*
merge-multiple: true
path: dist/
- name: Checksums
run: cd dist && shasum -a 256 *.tar.xz *.zip > checksums.txt
- name: Publish release
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--generate-notes \
dist/*.tar.xz dist/*.zip dist/checksums.txt
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
npm-publish:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 20
# shell: bash gives -eo pipefail; the default bash -e lets a failing
# curl in a pipeline be masked by the next command's exit 0.
defaults:
run:
shell: bash
permissions:
id-token: write # OIDC for npm trusted publishing; no stored credential
contents: read
# HARDENING, once publishing works: npm matches a trusted publisher on
# org + repo + workflow filename (+ optional environment) -- the git ref is
# NOT part of the match. With no environment set, anyone who can push a
# branch and dispatch a workflow can mint a real npm publish token from
# that branch. Bind the six packages to a GitHub Environment with required
# reviewers and add `environment:` here and on npm-oidc-check.
steps:
- uses: actions/checkout@v4
# No registry-url on purpose: setup-node would still write an unresolved
# _authToken=${NODE_AUTH_TOKEN} into .npmrc, turning a failed OIDC
# exchange into a misleading "404 Not Found - PUT" (v0.16.0/v0.16.1)
# instead of ENEEDAUTH. registry.npmjs.org is npm's default anyway.
- uses: actions/setup-node@v7
with:
node-version: 24
# off on publishing jobs: a poisoned cache could leak the OIDC token
package-manager-cache: false
# Node 24 ships npm 11.x; this just pins the floor (^11 keeps npm 12 out).
- name: Ensure npm supports trusted publishing
run: npm install -g npm@^11.5.1
- uses: actions/download-artifact@v4
with:
pattern: bin-*
path: bins/
- name: Stage binaries
run: |
for t in darwin-arm64 darwin-x64 linux-x64 linux-arm64; do
mkdir -p npm/packages/$t/bin
cp bins/bin-$t/dryrun npm/packages/$t/bin/dryrun
chmod +x npm/packages/$t/bin/dryrun
done
mkdir -p npm/packages/win32-x64/bin
cp bins/bin-win32-x64/dryrun.exe npm/packages/win32-x64/bin/dryrun.exe
- name: Set versions
run: node npm/scripts/set-version.mjs "${GITHUB_REF_NAME#v}"
# No NODE_AUTH_TOKEN: auth is the OIDC exchange. Each of the six packages
# needs a trusted publisher on npmjs.com naming repo dryrun, workflow
# release.yml, no environment; a mismatch fails as E404. Provenance is
# attested automatically, so --provenance is not needed.
#
# One step so the helper stays in scope. Launcher last: its platform
# optionalDependencies must exist first. Skipping published versions
# keeps the job re-runnable after a partial publish.
- name: Publish packages
run: |
publish_if_new() {
local dir="$1" name version
name=$(node -p "require('./$dir/package.json').name")
version=$(node -p "require('./$dir/package.json').version")
if npm view "$name@$version" version >/dev/null 2>&1; then
echo "$name@$version already published, skipping"
return 0
fi
# ./ is load-bearing: npm parses a bare one-slash argument as the
# GitHub shorthand <org>/<repo>, so `npm publish npm/dryrun` goes
# looking for github.com/npm/dryrun.git instead of the directory.
# npm/packages/<t> has two slashes and escapes it by accident.
#
# verbose: OIDC failures never throw and log at verbose only
# (lib/utils/oidc.js), so a misconfigured publisher is silent otherwise.
npm publish --access public --loglevel verbose "./$dir"
}
for t in darwin-arm64 darwin-x64 linux-x64 linux-arm64 win32-x64; do
publish_if_new "npm/packages/$t"
done
publish_if_new npm/dryrun
# Metadata only: the registry resolves @boringsql/dryrun on npm and checks its
# mcpName against server.json's name, so npm-publish must land first.
mcp-registry:
needs: npm-publish
runs-on: ubuntu-latest
timeout-minutes: 20
# shell: bash gives -eo pipefail; the default bash -e lets a failing
# curl in a pipeline be masked by the next command's exit 0.
defaults:
run:
shell: bash
permissions:
id-token: write # OIDC; no stored registry credential
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Stamp version into server.json
run: node npm/scripts/set-version.mjs "${GITHUB_REF_NAME#v}"
- name: Install mcp-publisher
run: |
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
- name: Validate server.json
run: ./mcp-publisher validate
- name: Authenticate to MCP registry
run: ./mcp-publisher login github-oidc
- name: Publish to MCP registry
# npm needs a moment to serve the version the registry is about to fetch
run: |
for attempt in 1 2 3; do
./mcp-publisher publish && exit 0
echo "publish attempt $attempt failed; retrying in 30s"
sleep 30
done
exit 1
# Diagnostic, publishes nothing. Run from the Actions tab. It lives in THIS
# file because npm binds a trusted publisher to the workflow filename -- a
# probe anywhere else is a different identity and would fail even when
# release.yml is configured correctly.
npm-oidc-check:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
run:
shell: bash
permissions:
id-token: write
contents: read
concurrency: npm-oidc-check
steps:
- name: Show the OIDC claims npm will match on
run: |
tok=$(curl -sS --get --data-urlencode "audience=npm:registry.npmjs.org" \
-H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value)
# payload only; never echo the token itself
payload=$(printf '%s' "$tok" | cut -d. -f2)
case $(( ${#payload} % 4 )) in 2) payload="$payload==";; 3) payload="$payload=";; esac
printf '%s' "$payload" | tr '_-' '/+' | base64 -d 2>/dev/null \
| jq '{aud, iss, repository, repository_owner, repository_id, repository_owner_id, workflow, workflow_ref, job_workflow_ref, ref, environment}'
- name: Probe the trusted-publisher exchange for each package
run: |
audience="npm:registry.npmjs.org"
# One token per package, deliberately NOT hoisted out of the loop:
# npm mints a fresh token per publish, and if the exchange treats the
# JWT as single-use a reused token would fail packages 2..6 and send
# you auditing npm settings that are fine. The extra call is cheap;
# a false FAIL from the tool meant to end false FAILs is not.
# --get --data-urlencode matches npm's URLSearchParams (encodes ':').
mint_token() {
local out code
out=$(mktemp)
code=$(curl -sS --max-time 30 -o "$out" -w '%{http_code}' \
--get --data-urlencode "audience=$audience" \
-H "Accept: application/json" \
-H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL" || true)
if [ "$code" != "200" ]; then
echo "::error::minting the OIDC token failed (HTTP ${code:-000}) -- id-token: write missing, or a transport error" >&2
rm -f "$out"
return 1
fi
jq -r '.value // empty' "$out"
rm -f "$out"
}
fail=0
results=""
for pkg in \
"@boringsql/dryrun" \
"@boringsql/dryrun-darwin-arm64" \
"@boringsql/dryrun-darwin-x64" \
"@boringsql/dryrun-linux-x64" \
"@boringsql/dryrun-linux-arm64" \
"@boringsql/dryrun-win32-x64"
do
token=$(mint_token) || exit 1
if [ -z "$token" ]; then
echo "::error::OIDC token response had no value"
exit 1
fi
# npm-package-arg's escapedName: a single lowercase %2f, no other escaping.
escaped=$(printf '%s' "$pkg" | sed 's|/|%2f|')
body=$(mktemp)
# No Content-Type: npm posts no body. || true: transport errors
# report as 000 instead of killing the loop under set -e.
code=$(curl -sS --max-time 30 -o "$body" -w '%{http_code}' -X POST \
-H "Authorization: Bearer $token" \
"https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/${escaped}" \
|| true)
if [ "$code" = "200" ]; then
line="PASS $pkg"
else
fail=1
msg=$(jq -r '.message // .error // empty' "$body" 2>/dev/null || true)
[ -n "$msg" ] || msg=$(head -c 200 "$body")
[ -n "$msg" ] || msg="<empty body>"
line="FAIL $pkg (HTTP ${code:-000}) -- $msg"
fi
echo "$line"
results="$results$line"$'\n'
rm -f "$body"
done
{
echo "## npm trusted-publisher probe"
echo
echo '```'
printf '%s' "$results"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
[ "$fail" -eq 0 ] || {
printf '%s\n' \
"" \
"A FAIL means npm rejected the exchange for that package. On npmjs.com," \
"under the package's Settings -> Trusted Publisher, check that:" \
" - owner is boringSQL and repository is dryrun (case-sensitive)" \
" - workflow is release.yml -- filename only, no path, no ref" \
" - Environment matches: npm-publish enters none today, so an" \
" environment configured on npm can never match. See the note" \
" on the npm-publish job about binding one once publishing works." \
" - publishing access is not set to require 2FA in a way that" \
" excludes trusted publishing" \
"Note a reusable/called workflow changes job_workflow_ref and so" \
"changes the identity npm matches on."
exit 1
}
echo "All six packages accept an OIDC exchange from release.yml."