Skip to content

Commit 3154a3a

Browse files
sinelawclaude
andcommitted
Switch npm publish to OIDC trusted publishing
Use a custom npm-publish workflow instead of cargo-dist's built-in npm job to support OIDC trusted publishing with provenance attestations. This approach survives `dist generate` because: - Custom workflow is in a separate file cargo-dist doesn't manage - dist-workspace.toml references it via "./npm-publish" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ce6ce6d commit 3154a3a

3 files changed

Lines changed: 52 additions & 27 deletions

File tree

.github/workflows/npm-publish.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Custom npm publish job with OIDC trusted publishing
2+
# This replaces the built-in cargo-dist npm job to support provenance attestations
3+
4+
name: npm-publish
5+
6+
on:
7+
workflow_call:
8+
inputs:
9+
plan:
10+
required: true
11+
type: string
12+
13+
jobs:
14+
publish-npm:
15+
runs-on: ubuntu-22.04
16+
permissions:
17+
contents: read
18+
id-token: write
19+
env:
20+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21+
PLAN: ${{ inputs.plan }}
22+
if: ${{ !fromJson(inputs.plan).announcement_is_prerelease || fromJson(inputs.plan).publish_prereleases }}
23+
steps:
24+
- name: Fetch npm packages
25+
uses: actions/download-artifact@v4
26+
with:
27+
pattern: artifacts-*
28+
path: npm/
29+
merge-multiple: true
30+
- uses: actions/setup-node@v4
31+
with:
32+
node-version: '20.x'
33+
registry-url: 'https://registry.npmjs.org'
34+
- name: Publish to npm with provenance
35+
run: |
36+
for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith("-npm-package.tar.gz")] | any)'); do
37+
pkg=$(echo "$release" | jq '.artifacts[] | select(endswith("-npm-package.tar.gz"))' --raw-output)
38+
npm publish --access public --provenance "./npm/${pkg}"
39+
done

.github/workflows/release.yml

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -346,45 +346,30 @@ jobs:
346346
done
347347
git push
348348
349-
publish-npm:
349+
custom-npm-publish:
350350
needs:
351351
- plan
352352
- host
353-
runs-on: "ubuntu-22.04"
354-
permissions:
355-
contents: read
356-
id-token: write
357-
env:
358-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
359-
PLAN: ${{ needs.plan.outputs.val }}
360353
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
361-
steps:
362-
- name: Fetch npm packages
363-
uses: actions/download-artifact@v4
364-
with:
365-
pattern: artifacts-*
366-
path: npm/
367-
merge-multiple: true
368-
- uses: actions/setup-node@v4
369-
with:
370-
node-version: '20.x'
371-
registry-url: 'https://registry.npmjs.org'
372-
- run: |
373-
for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith("-npm-package.tar.gz")] | any)'); do
374-
pkg=$(echo "$release" | jq '.artifacts[] | select(endswith("-npm-package.tar.gz"))' --raw-output)
375-
npm publish --access public --provenance "./npm/${pkg}"
376-
done
354+
uses: ./.github/workflows/npm-publish.yml
355+
with:
356+
plan: ${{ needs.plan.outputs.val }}
357+
secrets: inherit
358+
# publish jobs get escalated permissions
359+
permissions:
360+
"id-token": "write"
361+
"packages": "write"
377362

378363
announce:
379364
needs:
380365
- plan
381366
- host
382367
- publish-homebrew-formula
383-
- publish-npm
368+
- custom-npm-publish
384369
# use "always() && ..." to allow us to wait for all publish jobs while
385370
# still allowing individual publish jobs to skip themselves (for prereleases).
386371
# "host" however must run to completion, no skipping allowed!
387-
if: ${{ always() && needs.host.result == 'success' && (needs.publish-homebrew-formula.result == 'skipped' || needs.publish-homebrew-formula.result == 'success') && (needs.publish-npm.result == 'skipped' || needs.publish-npm.result == 'success') }}
372+
if: ${{ always() && needs.host.result == 'success' && (needs.publish-homebrew-formula.result == 'skipped' || needs.publish-homebrew-formula.result == 'success') && (needs.custom-npm-publish.result == 'skipped' || needs.custom-npm-publish.result == 'success') }}
388373
runs-on: "ubuntu-22.04"
389374
env:
390375
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

dist-workspace.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ local-artifacts-jobs = ["./musl-builds"]
2626
# A GitHub repo to push Homebrew formulas to
2727
tap = "sinelaw/homebrew-fresh"
2828
# Publish jobs to run in CI
29-
publish-jobs = ["homebrew", "npm"]
29+
# Note: Using custom npm-publish job instead of built-in "npm" for OIDC trusted publishing
30+
publish-jobs = ["homebrew", "./npm-publish"]
3031
# Post-announce jobs to run in CI
3132
post-announce-jobs = ["./aur-publish", "./cargo-publish"]

0 commit comments

Comments
 (0)