Skip to content

Commit f8120b9

Browse files
lidge-junclaude
andcommitted
ci(release): switch npm publish to Trusted Publishing (OIDC) — no NPM_TOKEN
npm now flags long-lived automation tokens as a security risk and recommends Trusted Publishing for CI. Rework the release workflow to publish tokenlessly via OIDC: - drop the NODE_AUTH_TOKEN env (and the NPM_TOKEN secret dependency) - drop --provenance — provenance is automatic under trusted publishing - upgrade npm to latest in CI (OIDC needs npm >= 11.5.1) - keep id-token: write (now powers OIDC auth + provenance) Runbook updated: first publish is a one-time local 'npm publish' (a trusted publisher can only be configured after v1 exists), then configure the trusted publisher on npmjs.com and all future releases are tokenless. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent cb6241f commit f8120b9

2 files changed

Lines changed: 53 additions & 36 deletions

File tree

.github/workflows/release.yml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ on:
2727

2828
permissions:
2929
contents: read
30-
id-token: write # npm provenance attestation
30+
id-token: write # OIDC auth for Trusted Publishing + automatic provenance attestation
3131

3232
concurrency:
3333
group: release
@@ -47,13 +47,17 @@ jobs:
4747
with:
4848
bun-version: latest
4949

50-
# node + npm perform the actual publish (with provenance); registry-url wires NODE_AUTH_TOKEN.
50+
# node + npm perform the actual publish. registry-url points npm at the public registry.
5151
- name: Setup Node
5252
uses: actions/setup-node@v4
5353
with:
5454
node-version: 24
5555
registry-url: "https://registry.npmjs.org"
5656

57+
# Trusted Publishing (OIDC) needs npm >= 11.5.1 — the runner's bundled npm may be older.
58+
- name: Use latest npm
59+
run: npm install -g npm@latest
60+
5761
- name: Install dependencies
5862
run: bun install
5963

@@ -66,18 +70,20 @@ jobs:
6670
exit 1;
6771
}
6872
69-
# `npm publish` runs prepublishOnly (typecheck + build the GUI into gui/dist) first, so even a
70-
# dry-run fully verifies the build. --provenance attests the tarball was built in this CI run.
73+
# Tokenless publish via Trusted Publishing (OIDC) — NO NPM_TOKEN secret. npm auto-detects the
74+
# OIDC environment (`id-token: write` above) and generates provenance automatically, so neither a
75+
# token nor `--provenance` is needed. `npm publish` runs prepublishOnly first (typecheck + build
76+
# the GUI into gui/dist), so even a dry-run fully verifies the build.
77+
# PREREQUISITE: configure the Trusted Publisher for this repo + workflow on npmjs.com — possible
78+
# only AFTER the package's first version exists (do the first publish locally, see the runbook).
7179
- name: Publish (or dry-run)
7280
run: |
7381
if [ "${{ inputs.dry-run }}" = "true" ]; then
7482
echo "::notice::DRY RUN — building + packing, not publishing"
75-
npm publish --dry-run --tag "${{ inputs.tag }}" --access public --provenance
83+
npm publish --dry-run --tag "${{ inputs.tag }}" --access public
7684
else
77-
npm publish --tag "${{ inputs.tag }}" --access public --provenance
85+
npm publish --tag "${{ inputs.tag }}" --access public
7886
fi
79-
env:
80-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
8187
8288
# Confirm the registry actually has the new version (real publishes only).
8389
- name: Post-publish registry smoke
Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,49 @@
1-
# npm release CI + runbook (jawcode-style)
1+
# npm release CI + runbook (jawcode-style, Trusted Publishing / OIDC)
22

33
## Why
44
`bun/npm install -g opencodex` install from the **npm registry**, not GitHub — so opencodex must be
5-
**published to npm**. Modeled on jawcode's release flow (minus its monorepo/native-binary bits).
5+
published to npm. Modeled on jawcode's release flow, but using **npm Trusted Publishing (OIDC)** so CI
6+
publishes **without a long-lived `NPM_TOKEN` secret** (npm's recommended approach — short-lived OIDC
7+
credentials, provenance generated automatically, nothing to leak).
68

7-
## How jawcode does it (reference)
8-
`jawcode/.github/workflows/release.yml` is a **`workflow_dispatch`** job with `version` + `tag`
9-
(latest/preview) + `dry-run` (default **true**) inputs: it verifies package.json matches the input,
10-
then `npm publish --tag … --access public --provenance`, then a post-publish registry smoke. A local
11-
`bun scripts/release.ts <version>` does preflight → bump → commit → push → dispatch → watch.
9+
## What shipped
10+
- `.github/workflows/release.yml``workflow_dispatch` (inputs: `version` / `tag` latest|preview /
11+
`dry-run` default true). Verifies `tag == package.json`, upgrades npm (OIDC needs **npm ≥ 11.5.1**),
12+
then **tokenless** `npm publish` via OIDC (`id-token: write`) — no `NPM_TOKEN`, no `--provenance`
13+
(automatic). Post-publish `npm view` smoke.
14+
- `scripts/release.ts` (+ `bun run release` / `release:watch`) — preflight → bump → commit → push →
15+
dispatch → watch. Not shipped in the tarball.
1216

13-
## What shipped (opencodex)
14-
- **`.github/workflows/release.yml`**`workflow_dispatch` with `version` / `tag` (latest|preview) /
15-
`dry-run` (default true). Verifies the tag input == package.json, then `npm publish` (prepublishOnly
16-
builds the GUI into gui/dist first) with `--provenance`, then `npm view` smoke on real publishes.
17-
Replaces the old tag-push `publish-npm.yml`.
18-
- **`scripts/release.ts`** (+ `bun run release` / `release:watch`) — single-package version of
19-
jawcode's helper: clean-main + typecheck preflight → `npm version --no-git-tag-version` → commit →
20-
push → `gh workflow run release.yml` (dry-run unless `--publish`) → watch. Not shipped in the tarball.
21-
- Package already publish-ready (`files`, bin `#!/usr/bin/env bun`, `engines.bun`, `prepublishOnly`).
17+
## One-time setup (owner) — Trusted Publishing
18+
⚠️ A Trusted Publisher can only be configured **after** the package has ≥1 published version. So the
19+
very first publish of the new name is done locally; everything after is tokenless CI.
2220

23-
## One-time setup (owner)
24-
1. npm **Automation** (or Granular all-packages Read+Write) token: npmjs.com → Access Tokens.
25-
2. Add it as the repo secret **`NPM_TOKEN`**: GitHub → Settings → Secrets and variables → Actions.
21+
1. **First publish (local, one-time)** — claims `opencodex`, no token stored anywhere:
22+
```bash
23+
npm login # browser auth
24+
npm version 0.1.0 --no-git-tag-version # set the first version
25+
git commit -am "release: v0.1.0" && git push origin main
26+
npm publish --access public # prepublishOnly builds the GUI first
27+
```
28+
2. **Configure the Trusted Publisher** — npmjs.com → the `opencodex` package → **Settings → Trusted
29+
Publisher → GitHub Actions**:
30+
- Organization or user: `lidge-jun`
31+
- Repository: `opencodex`
32+
- Workflow filename: `release.yml`
33+
- Environment name: *(leave blank)*
34+
3. Done — every future release publishes tokenlessly via OIDC. (No `NPM_TOKEN` secret anywhere.)
2635

27-
## Releasing
28-
**Easiest (local helper):**
36+
## Releasing (after setup)
2937
```bash
30-
bun run release 0.1.0 # preflight → bump commit push DRY-RUN dispatch watch
31-
bun run release 0.1.0 --publish # …and actually publish (after the dry-run looks good)
38+
bun run release 0.2.0 # bump + commit + push + DRY-RUN dispatch + watch
39+
bun run release 0.2.0 --publish # …and actually publish (tokenless OIDC)
3240
```
33-
**Manual (GitHub UI):** bump `version` in package.json on main, commit/push, then Actions → **Release**
34-
→ Run workflow → enter the version, pick the dist-tag, leave **dry-run on** first, re-run with dry-run
35-
off to publish.
41+
Or: Actions → **Release** → Run workflow → version + dist-tag, dry-run on first, then off to publish.
3642

37-
After a real publish: `bun install -g opencodex` / `npm install -g opencodex` work (package page:
38-
https://www.npmjs.com/package/opencodex). opencodex is bun-native, so installers still need **bun** on PATH.
43+
After a real publish, `bun install -g opencodex` / `npm install -g opencodex` (and `ocx update`) work.
44+
opencodex is bun-native, so installers still need **bun** on PATH. Page: https://www.npmjs.com/package/opencodex
45+
46+
## Why not an automation token?
47+
npm now warns that classic automation/granular tokens for CI carry security risk (a leaked write token
48+
publishes arbitrary versions). Trusted Publishing replaces it with per-run OIDC credentials scoped to
49+
this exact repo+workflow — nothing long-lived to store or steal.

0 commit comments

Comments
 (0)