Skip to content

Stage releases on npm from a version tag - #7

Merged
zuhairhalawa merged 2 commits into
mainfrom
ci/staged-npm-release
Nov 6, 2026
Merged

zuhairhalawa merged 2 commits into
mainfrom
ci/staged-npm-release

Conversation

@zuhairhalawa

Copy link
Copy Markdown
Owner

Adds a workflow that stages each release on npm from a version tag, with provenance. A maintainer still approves every release with 2FA. This changes nothing on npm until the trusted-publisher setting below is turned on, and nothing in the package itself.

How it works

Trigger What runs
Version tag such as v0.4.0 All checks, then npm stage publish --provenance. The package is not public until approved on npmjs.com
Pull request changing release.yml, or a manual run The same checks, then npm stage publish --dry-run. Nothing is uploaded

Before uploading, it stops if the tag doesn't match package.json, if the commit isn't on main, or if the version is already on npm.

Safety choices

  • No stored npm token. It uses trusted publishing through GitHub's OIDC.
  • Stage only. The setting allows only npm stage publish, so a compromised workflow can't publish directly, and approval still needs 2FA.
  • Actions pinned to full commits, since this workflow can upload a release. ci.yml is unchanged.
  • No build cache, no persisted Git credentials, and no ${{ }} expressions inside shell scripts.
  • A separate npm-release environment, used only by tag runs, so the npm setting can be tied to it.

What this PR proves, and what it doesn't

This PR's run tests the checks, the tarball test and a dry run of the upload on GitHub's runners. It can't test the real upload. Trusted publishing, staging through OIDC and provenance will only run for real at the next release, and the steps for that are in docs/RELEASING.md. Until then, npm trust and --provenance with npm stage publish are based on npm's docs, not on a run of this workflow.

Locally, I checked the file's structure and safety properties with a small script, ran the tag-matching logic by hand, and confirmed the integrity calculation matches npm's value for 0.3.0. actionlint was not available. There has been no outside review.

One npm setting, later

Trusted publishing needs a setting on npmjs.com, which only the owner can make, and it isn't made yet. npm removes a new setting if it has no successful publish within 2 days, so it should be added right before the next release, not now. docs/RELEASING.md lists the exact values.

A version tag now runs the checks and uploads the package to npm's stage queue
with provenance, using trusted publishing, so no npm token is stored. Nothing
becomes public until a maintainer approves it with 2FA.

Pull requests that change the workflow and manual runs do everything except the
upload. docs/RELEASING.md has the steps, including the npm setting this needs.
npm refuses even a dry run for a version it already has, and main is on one
between releases, so the dry run failed every time. It now uses a version that
can never exist. The change stays on the runner and is never committed.
@zuhairhalawa
zuhairhalawa merged commit 6804f25 into main Nov 6, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant