consolidate npm publishing for trusted publishing (OIDC) - #26
Conversation
release.yml no longer publishes to npm directly; after the GitHub release is created it dispatches npm-publish.yml, which publishes from the release assets. A single publishing workflow file is a prerequisite for npm trusted publishing, whose per-package publisher config matches on workflow filename.
…y safely - release.yml now watches the dispatched npm-publish run to completion and propagates its result, so a failed npm publish fails the release run. - npm-publish.yml checks out the release tag instead of the default branch: the tag's tree is what gets published. This requires the tag to contain the npm packaging scripts. - Retrying a version older than the current npm latest publishes under a temporary dist-tag so it cannot steal the "latest" dist-tag from a newer release.
0bf6d10 to
9104822
Compare
There was a problem hiding this comment.
🟡 Not ready to approve
The release workflow’s dispatch polling window is short enough to be flaky under queue/API propagation delays, which can incorrectly fail releases even when the publish workflow was successfully dispatched.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR consolidates npm publishing into a single workflow (npm-publish.yml) to support npm Trusted Publishing (OIDC) constraints, and updates the release workflow to dispatch-and-watch that publishing workflow so npm publish failures correctly fail the release pipeline.
Changes:
- Move npm publishing out of
release.ymlinto a dispatchednpm-publish.ymlrun, and propagate its success/failure back to the release workflow. - Update
npm-publish.ymlto publish from the release tag’s tree (reproducible republish) and add a dist-tag guard to prevent older retries from stealinglatest.
File summaries
| File | Description |
|---|---|
| .github/workflows/release.yml | Replaces inline npm publish steps with dispatch + polling + watch of npm-publish.yml, so npm failures fail the release workflow. |
| .github/workflows/npm-publish.yml | Switches checkout to the release tag and adds a backfill dist-tag strategy when republishing versions older than current npm latest. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
…lation - Backdate the dispatch timestamp by a minute so clock skew between the runner and GitHub cannot hide the dispatched run from the --created filter, which would fail the release while the publish proceeds. - Compare versions with real semver (npx semver) instead of sort -V, which ranks prereleases above their release (0.3.0-rc.1 > 0.3.0) and would let an RC keep the "latest" dist-tag. - Pass the tag and repository into the dispatch script via env instead of expression interpolation; tag names may contain shell metacharacters.
…essing npm-publish.yml accepts an optional dispatch_id input and embeds it in its run name; release.yml passes its own run id and polls for the run whose name carries it. Discovery of the dispatched run is now exact: immune to clock skew and to concurrent releases matching each other's runs. The discovery window is also widened from 1 to 2 minutes to ride out API propagation delays.
There was a problem hiding this comment.
🟡 Human review recommended
It changes production release/publishing automation in ways that can only be fully validated by an end-to-end release run (dispatch + permissions + registry behavior).
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
- npm-publish.yml fails immediately for any tag that is not exactly vX.Y.Z; prerelease and other tag shapes must be published manually. release.yml skips the dispatch for such tags so they still get a GitHub release without failing the run. - Per package, publish only when the version is strictly newer than the package's current npm latest; skip otherwise. This replaces the temporary "backfill" dist-tag mechanism: the workflow can now never move `latest` backwards, at the cost that filling version gaps older than latest is a manual operation.
The per-package guard compared against each package's own npm latest, so retrying an old tag could still "forward-publish" a package that had missed several releases, leaving cross-package version gaps (a launcher whose exact-pinned platform packages do not exist). The workflow now refuses any tag that is not the repository's newest stable vX.Y.Z tag: retrying the newest tag fills in exactly the packages a partial run missed, and anything older is a manual operation.
There was a problem hiding this comment.
🟡 Not ready to approve
The publish guard uses an unpinned npx semver invocation, which can introduce nondeterministic release behavior if the semver CLI changes.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (1)
.github/workflows/npm-publish.yml:111
npx --yes semveris unpinned, so a futuresemverCLI release could change behavior and make publishing nondeterministic or break unexpectedly. Pin at least the major version (or an exact version) to keep the workflow stable.
if [ -n "$latest" ] && ! npx --yes semver -r ">${latest}" "$VERSION" >/dev/null 2>&1; then
- Files reviewed: 2/2 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
- Install the semver CLI in its own step instead of npx-on-demand in the publish loop: a transient fetch failure there was indistinguishable from "version not newer" and would silently skip publishing a package while the run stayed green. As a dedicated step, a fetch failure fails the run loudly. - Match the dispatched run by "dispatch <id>)" including the closing paren, so a run id that is a string-prefix of a concurrent release's run id cannot match the wrong run.
The publish job is the sole holder of npm publish rights for the @ProvableHQ packages, and semver is the only third-party code it fetches at run time. Pinning to an exact, immutable version closes off behavioral drift and package-takeover via a future semver release.
Motivation
Groundwork for moving npm publishing from a long-lived
NPM_TOKENsecret to npm trusted publishing (OIDC), which replaces the token with short-lived per-publish credentials and adds automatic provenance attestation.npm's trusted publisher config allows one publisher per package, matched by workflow filename — so all publishing must live in a single workflow file. This PR makes
npm-publish.ymlthat file and tightens the release flow:release.ymlno longer publishes to npm inline; after the GitHub release is published it dispatchesnpm-publish.yml, then watches the dispatched run to completion and propagates its result — a failed npm publish fails the release run instead of going unnoticed.npm-publish.ymlchecks out the release tag instead of the default branch: the tag's tree is what gets published. Consequence: it only works for tags that containscripts/build-npm.mjs, i.e. releases after this PR.vX.Y.Zand the repository's newest such tag — prereleases, malformed tags, and older releases must be published manually.release.ymlskips the npm dispatch for non-stable tags so they still get a GitHub release.latest, so retrying the newest tag publishes exactly the packages a partial earlier run missed, and the workflow can never movelatestbackwards or create cross-package version gaps.Publishing still uses
NPM_TOKENfor now — the packages must exist on npm before trusted publishers can be configured for them. A follow-up PR switchesnpm-publish.ymlto OIDC (id-token: write, npm ≥ 11.5.1, dropNODE_AUTH_TOKEN) once the bootstrap publish has happened and the trusted publisher config is in place on npmjs.com.Test Plan
v0.2.1,v10.20.30) and rejected (v0.3.0-rc.1,v0.3,v1.2.3.4,0.2.1,v0.2.1x) shapes.0.2.10>0.2.9), and a manually published prereleaselatestfollowed by its stable release (0.3.0-rc.1→0.3.0publishes).workflow_dispatchbeing one of the event typesGITHUB_TOKENis permitted to trigger; end-to-end verification happens with the next release tag.Related PRs
npm-publish.yml)