Skip to content

Stage npm releases from GitHub Actions - #24

Merged
boatmeme merged 1 commit into
masterfrom
npm-publish
Sep 24, 2026
Merged

boatmeme merged 1 commit into
masterfrom
npm-publish

Conversation

@boatmeme

@boatmeme boatmeme commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Sets up npm publishing the way growombud/omnesiac#17 did.

Publishing a GitHub release whose tag matches package.json's version runs a new Publish workflow. The workflow stages the package on npm. Nothing goes live until an npm maintainer approves it with 2FA.

  • No npm token. npm authenticates the workflow by OIDC (trusted publishing). The trusted publisher is bound to publish.yml and the npm environment, and is limited to npm stage publish.
  • A human gate on npm itself. With staged publishing, a compromised GitHub account, workflow edit or dependency can at most stage a version, never publish one. npm is also removing direct publishing with bypass-2FA tokens in January 2027.

.github/workflows/publish.yml

  • Build job (no npm credentials):
    • Checks that the release tag equals the version, and that the tagged commit is on master.
    • Installs with --ignore-scripts. No dependency has an install script, so this costs nothing.
    • Runs yarn ci-test, packs the tarball and uploads it as an artifact.
  • Stage job:
    • The only job with id-token: write, and it runs in the npm environment.
    • It never checks out or installs anything. It downloads the tarball and runs npm stage publish --provenance.
    • Prerelease versions such as 0.5.5-rc.0 go to the next dist-tag instead of latest.
  • Actions use the same SHA pins as build.yml. Credentials aren't persisted on checkout, and no dependency cache is used.

README

  • Added a Releasing section: bump the version in a PR, gh release create <version>, then approve on npm.

package.json

  • Bumps the version to 0.5.5 for the first release through this workflow.

Repo settings (already applied)

  • The npm environment deploys only from tags matching *.*.*.
  • The default GITHUB_TOKEN is read-only, and Actions can't create or approve PRs.
  • A master ruleset requires a PR and a passing Build check, and blocks force-pushes and deletion. Admins can bypass.
  • The classic branch protection on master is deleted, because the ruleset replaces it. The one thing dropped is "require branches to be up to date", which matches omnesiac.
  • Actions must be pinned to full commit SHAs.

Still to do on npmjs.com (package owner)

  • Confirm 2FA is on for the owning account. Approving staged versions requires it.
  • Add the trusted publisher: GitHub Actions, growombud / prolly / publish.yml / environment npm, allowed actions stage only. Do this before the first release. These fields can't be edited later.
  • After the first release, set Publishing access to "Require two-factor authentication and disallow tokens", and revoke old tokens.
  • Add a second npm maintainer. boatmeme is currently the only one, so approvals rest on one account.

Test plan

  • actionlint is clean on both workflows
  • Tag and master checks run locally: a matching tag passes. A mismatched tag, a v-prefixed tag and a commit not on master are each rejected. A prerelease version maps to next.
  • npm pack output contains only index.js, LICENSE, README.md and package.json
  • npm stage publish <tarball> --dry-run --tag next accepts the packed tarball (npm 11.19)
  • yarn ci-test passes locally
  • First run after merge: release 0.5.5, confirm it stages under latest with provenance attached, then approve or reject it

🤖 Generated with Claude Code

Publishing a GitHub release whose tag equals package.json's version runs
the new Publish workflow. It builds, tests and packs, then stages the
tarball on npm through OIDC trusted publishing, with provenance. Nothing
goes live until an npm maintainer approves it with 2FA.

Bumps the version to 0.5.5 for the first release through this path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@boatmeme
boatmeme merged commit ace679f into master Sep 24, 2026
2 checks passed
@boatmeme
boatmeme deleted the npm-publish branch September 24, 2026 06:15
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