Skip to content

[DO NOT MERGE] ci: stage npm releases via OIDC instead of publishing directly - #239

Draft
evenstensberg wants to merge 1 commit into
mainfrom
ci/npm-staged-publishing
Draft

evenstensberg wants to merge 1 commit into
mainfrom
ci/npm-staged-publishing

Conversation

@evenstensberg

@evenstensberg evenstensberg commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Releases now go to the npm stage queue through the existing OIDC trusted publisher, instead of being published directly. A maintainer has to approve each version with 2FA before it goes live.

changeset publish always runs a plain npm publish and has no staging option. So Changesets still handles versioning and the release PR, and the publish step now runs a small script.

Changes

  • scripts/stage-publish.mjs (new): replaces changeset publish.
    • Skips if the version is already on npm, or if the v<version> tag already exists on origin (meaning it's staged and waiting for approval). This keeps later pushes to main from trying to stage the same version again.
    • Otherwise runs npm stage publish, creates the v<version> tag locally and prints New tag: v<version>. changesets/action looks for that line in single-package repos, then pushes the tag and creates the GitHub release as before.
  • package.json: release now runs the script.
  • release.yml: comments and a step name only. Staging needs npm 11.15.0 or later and Node 22.14.0 or later; the workflow already uses Node lts/* and updates to npm@latest.

Reviewer notes / follow-ups

  • npmjs.com setting: restrict this package's trusted publisher to stage only, so a direct npm publish from CI is rejected.
  • Approving: after a release PR merges, run npm stage list coffee-loader, then npm stage approve <stage-id>, or use the Staged Packages tab on npmjs.com. Approval stays blocked until npm's malware scan finishes.
  • Trade-off: the tag and GitHub release are created when the version is staged, not when it's approved. If you reject a staged version, delete its tag and release by hand.

Testing

  • Ran ESLint, Prettier and cspell on the changed files. All pass.
  • Ran the script locally on the published 5.0.4: it skips as expected.
  • Ran it with npm and git stubbed and an unpublished version: it calls npm stage publish and git tag, then prints New tag:.
  • Not yet tested end to end on CI.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Package releases are now staged on npm before becoming publicly available, adding an approval step before live publication. Already-published or tagged versions are skipped. The release workflow also documents the minimum npm and Node.js versions required for staging and clarifies that staged releases need approval before publication.

Replace `changeset publish` with a script that runs `npm stage publish`
through the OIDC trusted publisher, so every release needs a maintainer's
2FA approval before it goes live. The script still prints "New tag:" so
changesets/action pushes the tag and creates the GitHub release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@evenstensberg
evenstensberg marked this pull request as draft October 14, 2026 16:00
@evenstensberg evenstensberg changed the title ci: stage npm releases via OIDC instead of publishing directly [DO NOT MERGE] ci: stage npm releases via OIDC instead of publishing directly Oct 14, 2026
@codecov

codecov Bot commented Oct 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (a388010) to head (f2d2a5b).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #239   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            3         3           
  Lines           18        18           
  Branches         2         2           
=========================================
  Hits            18        18           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Walkthrough

The release command now runs a staging script instead of changeset publish. The script checks whether the exact package version is already published or its tag exists on origin. If neither check finds an existing release, it stages the package, creates a local version tag, and prints approval instructions and a release-detection marker. Workflow comments describe the npm and Node.js requirements and staging restrictions.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to f2d2a

Releases now go to an npm staging area for maintainer approval. However, prerelease versions may fail to stage. A failed tag push after staging can also leave the release stuck, because a rerun cannot reconcile the pending stage. In addition, GitHub tags and releases appear before npm approves the version. Address these release-workflow gaps, or explicitly accept them, before merging.

Pre-merge checks: Passed 5
Passed Passed checks: 5 passed
Check name Status Explanation
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly describes the main change: staging npm releases through OIDC instead of publishing directly. The [DO NOT MERGE] marker is appropriate for the pull request state.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ac33b2d3-9467-42a7-b0a0-130fcdf526b4
📥 Commits

Reviewing files that changed from the base of the PR and between a388010 and f2d2a5b.

📒 Files selected for processing: 3
  • .github/workflows/release.yml
  • package.json
  • scripts/stage-publish.mjs

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.

Comment thread scripts/stage-publish.mjs
} else if (isTagged()) {
log(`${tag} already exists, ${name}@${version} is staged, skipping.`);
} else {
run("npm", ["stage", "publish"], { stdio: "inherit" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass an npm dist-tag for prerelease versions.

If Changesets produces a version such as 5.1.0-beta.1, this command uses npm’s default latest tag. npm stage publish rejects prerelease versions without an explicit tag. Select the Changesets prerelease tag and pass --tag when staging that version. Otherwise, the prerelease workflow stops before it creates a stage. (docs.npmjs.com)

Comment thread scripts/stage-publish.mjs
Comment on lines +40 to +41
run("npm", ["stage", "publish"], { stdio: "inherit" });
run("git", ["tag", tag]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Recover a stage when tag publication fails.

If staging succeeds but git tag or changesets/action’s tag push fails, npm retains the staged version. A fresh run then finds no published version and no remote tag. It attempts to stage the same version again, which npm rejects. Reconcile an existing pending stage before staging; when the remote tag is absent, let the run create the tag and emit New tag: without another upload. (docs.npmjs.com)

Comment thread scripts/stage-publish.mjs
run("git", ["tag", tag]);

log(`Staged ${name}@${version}, approve it with 2FA to publish.`);
log(`New tag: ${tag}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Delay the public release signal until approval.

If a maintainer has not approved the stage, this New tag: line still makes changesets/action push v<version>, create a GitHub release, and report published=true. npm does not make the version installable until approval. A rejected stage therefore leaves a public release for a version that never went live. Use a separate staging signal, then create the release after approval. (raw.githubusercontent.com)

This branch has not been deployed

No deployments
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