Skip to content

Commit adad876

Browse files
authored
Merge pull request #270 from knockout/bmh/sc-trusted-publishing
release: adopt npm trusted publishing
2 parents e613af9 + 200ef14 commit adad876

File tree

5 files changed

+150
-35
lines changed

5 files changed

+150
-35
lines changed

.github/workflows/release.yml

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,100 @@
11
# Automated release via Changesets.
22
#
33
# On push to main:
4-
# - If there are unreleased changesets, opens/updates a "Version Packages"
4+
# - If there are unreleased changesets, opens/updates a release version
55
# PR that bumps versions and updates changelogs.
6-
# - If that PR is merged (no remaining changesets), builds and publishes
7-
# to npm.
6+
# - If there are no unreleased changesets, builds, tests, and publishes
7+
# to npm from a separate least-privilege job.
88
#
9-
# Requires an NPM_TOKEN secret with publish access to the @tko scope.
9+
# Requires npm trusted publisher configuration for the @tko packages.
10+
# Publish auth comes from GitHub Actions OIDC, not a long-lived npm token.
1011
name: Release
1112

1213
on:
1314
push:
1415
branches: [main]
15-
workflow_dispatch:
1616

1717
concurrency: ${{ github.workflow }}-${{ github.ref }}
1818

1919
jobs:
20-
release:
20+
prepare-release:
21+
name: Prepare Release
2122
runs-on: ubuntu-latest
2223

2324
permissions:
2425
contents: write
2526
pull-requests: write
27+
28+
outputs:
29+
has_changesets: ${{ steps.changesets.outputs.hasChangesets }}
30+
31+
steps:
32+
- name: Checkout code
33+
# actions/checkout v6.0.2
34+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
35+
36+
- name: Setup Node.js
37+
# actions/setup-node v6.3.0
38+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
39+
with:
40+
# npm trusted publishing requires npm CLI 11.5.1+, which is available
41+
# on the current Node 24 line.
42+
node-version: 24.x
43+
cache: 'npm'
44+
registry-url: 'https://registry.npmjs.org'
45+
46+
- name: Install dependencies
47+
run: npm ci
48+
49+
- name: Create or update version PR
50+
id: changesets
51+
# changesets/action v1.5.3
52+
uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba
53+
with:
54+
version: npx changeset version
55+
title: 'chore: version packages'
56+
commit: 'chore: version packages'
57+
# Signed GitHub API commits are preferable to git-cli pushes for the
58+
# automated version PR branch.
59+
commitMode: github-api
60+
env:
61+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62+
63+
publish:
64+
name: Publish to npm
65+
needs: prepare-release
66+
if: needs.prepare-release.outputs.has_changesets == 'false'
67+
runs-on: ubuntu-latest
68+
69+
permissions:
70+
contents: read
2671
id-token: write
2772

2873
steps:
2974
- name: Checkout code
30-
uses: actions/checkout@v6
75+
# actions/checkout v6.0.2
76+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
3177

3278
- name: Setup Node.js
33-
uses: actions/setup-node@v4
79+
# actions/setup-node v6.3.0
80+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
3481
with:
35-
node-version: 22.x
82+
# npm trusted publishing requires npm CLI 11.5.1+, which is available
83+
# on the current Node 24 line.
84+
node-version: 24.x
3685
cache: 'npm'
3786
registry-url: 'https://registry.npmjs.org'
3887

3988
- name: Install dependencies
40-
run: npm install
89+
run: npm ci
4190

4291
- name: Build all packages
4392
run: make
4493

4594
- name: Run tests
4695
run: make test-headless
4796

48-
- name: Create release PR or publish
49-
uses: changesets/action@v1
50-
with:
51-
version: npx changeset version
52-
publish: npx changeset publish --provenance
53-
title: 'chore: version packages'
54-
commit: 'chore: version packages'
55-
env:
56-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
58-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
97+
- name: Publish packages
98+
# npm generates provenance attestations automatically during trusted
99+
# publishing from GitHub Actions.
100+
run: npx changeset publish

AGENTS.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,11 @@ This creates a changeset file in `.changeset/` that gets committed with your PR.
126126
1. Push to main triggers `.github/workflows/release.yml`
127127
2. If unreleased changesets exist, the action opens a "Version Packages" PR
128128
3. Review the PR (it bumps versions and updates changelogs)
129-
4. Merge it to publish to npm
129+
4. Merge it to publish to npm via GitHub Actions OIDC trusted publishing
130130

131-
**Manual release** (fallback):
132-
```bash
133-
make repackage # Sync package.json metadata across packages
134-
lerna version # Bump versions (interactive)
135-
make # Rebuild everything
136-
lerna publish from-package # Publish to npm
137-
```
131+
Avoid manual workstation publishes. If release CI is unavailable, fix the
132+
workflow or npm trusted publisher configuration rather than bypassing it with a
133+
long-lived publish token.
138134

139135
## Plans
140136

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ It's available as `@tko/build.knockout`, and over CDN:
5454
| $ `make` | **Currently TKO use a make file** / no scripts at package.json |
5555
| $ `make test` | Run all tests with electron. See below. |
5656
| $ `make test-headless` | Run all tests with chromium. See below. |
57-
| $ `lerna publish` | Bump versions and publish to npm registry |
57+
| $ `npx changeset add` | Add a changeset for package behavior changes in your PR |
58+
| Release workflow | On merge to `main`, CI opens or updates a version PR; when that version PR is merged and there are no remaining changesets, CI publishes from GitHub Actions via npm trusted publishing |
5859
| $ `make test-coverage` | Run all tests and create a code coverage report |
5960

6061
Checkout the `Makefile` for more commands that can be executed with `make {command}`.

plans/build-and-release-certainty.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ releases without manual verification of every detail.
66

77
---
88

9-
## Current State
9+
## Original State
1010

1111
- 25-package Lerna monorepo, fully TypeScript, built with esbuild + Make
1212
- ~89% test coverage across 143 test files with Karma
1313
- 7 GitHub Actions workflows (build, test, lint, format, TypeScript, CodeQL, docs deploy)
1414
- ESLint + Prettier enforced on PRs
1515
- Manual release via `lerna version` + `lerna publish`
1616

17-
## Gaps
17+
## Original Gaps
1818

1919
| Area | Risk |
2020
|------|------|
@@ -71,7 +71,8 @@ to npm.
7171
- Builds all packages and runs tests
7272
- If unreleased changesets exist, opens/updates a version PR
7373
- If version PR is merged, publishes to npm
74-
- Requires `NPM_TOKEN` secret in GitHub repo settings
74+
- Uses npm trusted publishing via GitHub Actions OIDC
75+
- Requires trusted publisher configuration for the public `@tko/*` packages on npm
7576

7677
---
7778

@@ -86,8 +87,8 @@ to npm.
8687

8788
### 3.2 Add npm provenance to publishing ✅ DONE
8889

89-
Release workflow publishes with `--provenance` flag so users can verify
90-
packages were built in CI.
90+
When publishing through npm trusted publishing from GitHub Actions, npm
91+
automatically emits provenance attestations for public packages.
9192

9293
### 3.3 Add `npm audit` to CI ✅ DONE
9394

plans/trusted-publishing.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Plan: Trusted Publishing for npm Releases
2+
3+
**Goal**: Move TKO npm publishing from long-lived `NPM_TOKEN` authentication to
4+
npm trusted publishing via GitHub Actions OIDC.
5+
6+
---
7+
8+
## Current State
9+
10+
- `.github/workflows/release.yml` already grants `id-token: write`
11+
- Releases still document and use `NPM_TOKEN`
12+
- Workflow publishes through `changesets/action`
13+
- Current workflow uses Node 22, but npm trusted publishing now requires
14+
npm CLI `11.5.1+`
15+
16+
## Desired State
17+
18+
- Release publishing works from GitHub-hosted runners without a write token
19+
- The workflow uses a Node/npm combination compatible with npm trusted publishing
20+
- Release docs no longer tell maintainers to configure `NPM_TOKEN`
21+
- The migration path notes the remaining manual npm-side setup:
22+
trusted publisher registration and token revocation after verification
23+
24+
## Implementation
25+
26+
### 1. Workflow
27+
28+
- Update the release workflow to use a trusted-publisher-compatible Node version
29+
- Split version-PR automation from npm publishing so the publish job can run
30+
with read-only repository permissions plus OIDC
31+
- Switch CI dependency installation to `npm ci` for lockfile-enforced releases
32+
- Pin third-party GitHub Actions by commit SHA
33+
- Remove publish-time `NPM_TOKEN` / `NODE_AUTH_TOKEN` env wiring
34+
- Keep `id-token: write`
35+
- Keep Changesets for release PR creation
36+
- Drop explicit `--provenance` because npm generates provenance automatically
37+
during trusted publishing
38+
39+
### Why split prepare vs publish
40+
41+
Many Changesets-based repositories keep release-PR creation and npm publishing
42+
in one job. That is simpler, but it forces the publish path to carry both
43+
repository write access and package-publish credentials at the same time.
44+
45+
For TKO, the split is intentional:
46+
47+
- `prepare-release` needs repository write permissions to open/update the
48+
version PR branch
49+
- `publish` only needs `contents: read` plus `id-token: write` for npm OIDC
50+
- this keeps the npm publish path on least privilege while preserving the
51+
standard Changesets PR flow on `main`
52+
53+
This is a better fit for trusted publishing than the default single-job
54+
Changesets pattern.
55+
56+
### 2. Repository docs
57+
58+
- Update the build/release plan to describe trusted publishing instead of
59+
token-based publishing
60+
- Update the README release guidance so maintainers do not follow the old
61+
`lerna publish` path
62+
63+
### 3. Follow-up outside the repo
64+
65+
- Configure trusted publishers for the public `@tko/*` packages on npm
66+
- Verify one publish from GitHub Actions
67+
- After verification, disable token-based publishing for those packages and
68+
revoke unneeded automation tokens
69+
70+
## Verification
71+
72+
- Review `.github/workflows/release.yml` for OIDC-only publish auth
73+
- Confirm no remaining repo docs require `NPM_TOKEN` for publishing
74+
- Confirm the workflow now targets a Node/npm version that meets npm trusted
75+
publishing requirements

0 commit comments

Comments
 (0)