From 8e1444b14b88c65bc6d7f8bfde5c3d7da76f8e12 Mon Sep 17 00:00:00 2001 From: "Solace (Mark) Brannan" Date: Tue, 22 Sep 2026 11:42:09 -0700 Subject: [PATCH 1/2] ci: add publish.yml, wire release-please to dispatch it release-please cut tags and GitHub Releases but nothing published to npm. publish.yml uses OIDC trusted publishing (no stored token); release-please.yml dispatches it via workflow_dispatch after a release, matching mark-brannan/colregs's already-working pattern. Prerequisite before the first tagged release publishes: register the trusted publisher on npmjs.com (package settings -> Trusted publishers -> GitHub Actions, repo mark-brannan/wire-wright, workflow publish.yml). Co-Authored-By: Claude Sonnet 5 --- .github/workflows/publish.yml | 63 ++++++++++++++++++++++++++++ .github/workflows/release-please.yml | 5 --- 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..1c32e15 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,63 @@ +name: Publish + +# Publishes to npm using OIDC trusted publishing: npm verifies this workflow's +# short-lived, workflow-scoped identity, so there is no npm token to store, +# rotate, or leak. Provenance attestations are generated automatically. +# +# Prerequisite, done once on npmjs.com: package settings -> Trusted publishers +# -> GitHub Actions, with repository mark-brannan/wire-wright and workflow +# file `publish.yml`. Until that exists this workflow will fail +# authentication, which is the correct failure mode. + +on: + workflow_dispatch: + # Deliberately no `push: tags` trigger. release-please.yml tags through a + # GitHub App installation token (not the default GITHUB_TOKEN), so that + # push DOES fire workflows -- and a `push: tags` listener here would start + # a second, racing publish alongside the explicit `gh workflow run + # publish.yml --ref ` dispatch release-please.yml already does. + # workflow_dispatch is also the only trigger npm's OIDC trusted publisher + # is configured for on npmjs.com (this file, this repo). So a tag alone, + # pushed by hand, does not publish; run `gh workflow run publish.yml --ref + # vX.Y.Z` instead. + +permissions: + contents: read # release-please.yml creates the tag and the GitHub Release + id-token: write # required: this is what mints the OIDC token + +jobs: + publish: + runs-on: ubuntu-latest + # workflow_dispatch can target a branch, not just a tag. A branch named + # to match package.json's version (e.g. "v0.1.1") would pass the tag + # check below despite not being an actual release tag -- npm trusted + # publishing doesn't restrict which ref a workflow_dispatch can run on. + # Refusing anything but a real tag closes that. + if: github.ref_type == 'tag' + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + + # Trusted publishing requires npm >= 11.5.1, newer than what ships with + # Node 22. + - run: npm install -g npm@latest + + - name: Refuse to publish a tag that disagrees with package.json + run: | + TAG="${GITHUB_REF_NAME}" + PKG=$(node -p "require('./package.json').version") + if [ "${TAG#v}" != "$PKG" ]; then + echo "tag $TAG does not match package.json version $PKG" >&2 + exit 1 + fi + + # The one dependency is `ampacity`, same as test.yml. + - run: npm ci + - run: npm test + + # No NODE_AUTH_TOKEN. Authentication is the OIDC identity above. + - run: npm publish diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 8f72e28..ab1f255 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -6,9 +6,6 @@ name: Release # mark-brannan/.github/.github/workflows/release-please.yml for the full # rationale (batching, squash-merge requirement, tag format, version # policy). -# -# This repo has no publish.yml -- nothing to dispatch on release -- so the -# caller opts out with `publish: false`. on: push: @@ -23,8 +20,6 @@ permissions: jobs: release: uses: mark-brannan/.github/.github/workflows/release-please.yml@main - with: - publish: false secrets: RELEASE_PLEASE_APP_CLIENT_ID: ${{ secrets.RELEASE_PLEASE_APP_CLIENT_ID }} RELEASE_PLEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }} From 02292466985cee5a7b9b6e991377e731a4f87389 Mon Sep 17 00:00:00 2001 From: "Solace (Mark) Brannan" Date: Tue, 22 Sep 2026 13:14:11 -0700 Subject: [PATCH 2/2] ci: don't persist the checkout token in publish.yml Matches test.yml's checkout, which sets persist-credentials: false with the rationale that nothing in the job writes to the repository and the token should not outlive the step. The same holds here -- release-please owns the tag and the Release -- and permissions are already contents: read, so this closes the remaining gap rather than opening anything. Co-Authored-By: Claude Opus 5 --- .github/workflows/publish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1c32e15..543a4f9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -35,7 +35,11 @@ jobs: # Refusing anything but a real tag closes that. if: github.ref_type == 'tag' steps: + # Nothing here writes to the repository -- release-please.yml owns the + # tag and the Release -- and the checkout's token should not outlive + # the step. Same rationale as test.yml's checkout. - uses: actions/checkout@v7 + with: { persist-credentials: false } - uses: actions/setup-node@v7 with: