Skip to content

Use stage releases - #18

Merged
zemd merged 6 commits into
mainfrom
zemd/stage-releases
Aug 9, 2026
Merged

Use stage releases#18
zemd merged 6 commits into
mainfrom
zemd/stage-releases

Conversation

@zemd

@zemd zemd commented Aug 8, 2026

Copy link
Copy Markdown
Owner

This pull request updates the release automation to use npm staged publishing by default, with an option for consumers to opt into direct publishing. It also introduces support for advancing a private shared-workflow contract version during release pull requests and updates documentation and workflow files to reflect these changes.

Release process improvements:

  • Default behavior is now npm staged publishing; maintainers must approve staged packages with 2FA before they are available from npm. Consumers can opt out by setting staged-publishing: false for direct publishing. (.changeset/hip-memes-rhyme.md, .github/workflows-examples/README.md, .github/workflows-examples/release.yml, .github/workflows/shared-release.yml, [1] [2] [3] [4] [5] [6]

  • The release scripts and workflows now advance the version of a private contract package (e.g., internal workflow/tooling contracts) if specified via the new contract-version-package input. (.github/workflows/shared-release.yml, .github/workflows/release.yml, [1] [2]

Script and workflow changes:

  • The .github/scripts/gha.mjs script introduces new logic for contract version management, staged publishing, and improved release note generation, including clear messaging when packages are staged vs. published. (.github/scripts/gha.mjs, [1] [2] [3] [4] [5] [6] [7]

Documentation updates:

  • The release workflow documentation and usage examples have been updated to describe staged publishing, the new contract package input, and trusted publisher requirements. (.github/workflows-examples/README.md, [1] [2]
  • Workflow descriptions and comments now consistently refer to "submitting" packages to npm, not just publishing, to clarify the staged process. (.github/workflows/shared-release.yml, [1]; .github/workflows-examples/release.yml, [2]

These changes modernize the release pipeline for improved security and flexibility, and make it easier for consumers to manage both staged and direct publishing workflows.

@zemd zemd self-assigned this Aug 8, 2026
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@zemd, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 7 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e2a879b4-f03e-4d36-b8e6-26ebd753f10d

📥 Commits

Reviewing files that changed from the base of the PR and between cfcdd1b and 342a09e.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .github/workflows/shared-ci.yml
  • .github/workflows/shared-codeql.yml
  • .github/workflows/shared-release.yml
  • .github/workflows/shared-scorecard.yml
  • internal/gha/README.md
  • internal/gha/src/workflows.test.ts
📝 Walkthrough

Walkthrough

The release tooling adds private contract-version planning and finalization. The reusable workflow submits packages to npm in staged mode by default or publishes them directly. GitHub release notes identify published and staged packages.

Changes

Release pipeline updates

Layer / File(s) Summary
Contract-version planning and reconciliation
internal/gha/src/contract-version.ts, internal/gha/src/contract-version.test.ts, internal/gha/src/commands/contract-version.ts, internal/gha/src/commands/contract-version.test.ts
The tooling parses package-specific intents, calculates the highest bump, updates private manifests, persists plans, and reconciles pnpm release output.
Contract-version workflow integration
.github/workflows/shared-release.yml, .github/workflows/release.yml, .github/scripts/gha.mjs, internal/gha/src/commands/index.ts, internal/gha/src/commands/index.test.ts, internal/gha/src/workflows.test.ts
The workflows expose the contract package input, check out shared tooling, and run contract-version preparation and finalization around release processing.
npm submission mode planning
internal/gha/src/npm-publishing.ts, internal/gha/src/npm-publishing.test.ts, internal/gha/src/commands/npm-publishing-mode.ts, .github/scripts/gha.mjs
The tooling checks package existence on npm and classifies packages for direct, mixed, or staged submission.
Staged npm submission and release state
.github/workflows/shared-release.yml, .github/scripts/gha.mjs, internal/gha/src/github-releases.ts, internal/gha/src/commands/github-releases.ts, internal/gha/src/github-releases.test.ts, internal/gha/src/github.ts, internal/gha/src/release-tags.ts, internal/gha/src/workflows.test.ts, .changeset/hip-memes-rhyme.md
The workflow submits package groups in the selected mode. Release generation receives separate published and staged summaries and renders staged-package approval instructions. Tag checks now distinguish missing tags from API failures.
Release contract and configuration documentation
.github/workflows-examples/README.md, .github/workflows-examples/release.yml, internal/gha/README.md
The documentation describes contract-version inputs, staged submission defaults, direct submission, trusted publishing, and release-state arguments.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SharedReleaseWorkflow
  participant ContractVersionCommand
  participant NpmPublishingMode
  participant Npm
  participant GithubReleases
  SharedReleaseWorkflow->>ContractVersionCommand: prepare contract version
  ContractVersionCommand->>SharedReleaseWorkflow: persist version plan
  SharedReleaseWorkflow->>NpmPublishingMode: classify workspace packages
  NpmPublishingMode->>Npm: check package existence
  SharedReleaseWorkflow->>Npm: stage or publish package groups
  SharedReleaseWorkflow->>GithubReleases: pass published and staged summaries
  GithubReleases->>SharedReleaseWorkflow: generate state-specific release notes
Loading

Possibly related PRs

  • zemd/js#11: Introduced the reusable release workflow and shared tooling extended by this change.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title identifies the main change to use staged releases, although the wording is slightly awkward.
Description check ✅ Passed The description clearly explains staged publishing, contract-version support, workflow changes, and documentation updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

Comment thread internal/gha/src/workflows.test.ts Fixed
Comment thread internal/gha/src/workflows.test.ts Fixed

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows-examples/README.md:
- Around line 48-52: Update the shared-release workflow summary in README.md to
describe both submission paths: staged publishing via pnpm stage publish when
staged-publishing is true, and direct pnpm publish when it is false. Make the
maintainer review and 2FA requirement explicitly conditional on staged
publishing, while preserving the existing release, tagging, and npm availability
details.

In @.github/workflows/shared-release.yml:
- Around line 235-241: Remove the NODE_AUTH_TOKEN environment variable and its
NPM_TOKEN reference from the “Stage packages on npm” step when
inputs.staged-publishing is enabled, so pnpm stage publish uses only the
stage-only OIDC trusted publisher. Do not alter authentication for other publish
steps; only retain a token here if package-level token restrictions explicitly
prevent direct publishing.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e7d4a095-6415-451e-af69-7d140c4be9e2

📥 Commits

Reviewing files that changed from the base of the PR and between b92586a and fea163d.

📒 Files selected for processing (17)
  • .changeset/hip-memes-rhyme.md
  • .github/scripts/gha.mjs
  • .github/workflows-examples/README.md
  • .github/workflows-examples/release.yml
  • .github/workflows/release.yml
  • .github/workflows/shared-release.yml
  • internal/gha/README.md
  • internal/gha/src/commands/contract-version.test.ts
  • internal/gha/src/commands/contract-version.ts
  • internal/gha/src/commands/github-releases.ts
  • internal/gha/src/commands/index.test.ts
  • internal/gha/src/commands/index.ts
  • internal/gha/src/contract-version.test.ts
  • internal/gha/src/contract-version.ts
  • internal/gha/src/github-releases.test.ts
  • internal/gha/src/github-releases.ts
  • internal/gha/src/workflows.test.ts

Comment thread .github/workflows-examples/README.md Outdated
Comment thread .github/workflows/shared-release.yml Outdated

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/gha/src/github-releases.ts (1)

140-151: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Tags are created for staged versions before approval.

The releases list combines published and staged packages, and every entry receives a git tag at line 150. A maintainer can reject a staged version during the npm approval step. The repository then keeps a tag for a version that never becomes available on npm, and a later re-release of the same version cannot reuse that tag. Confirm that this is the intended behavior, or defer tag creation for staged packages until approval.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/gha/src/github-releases.ts` around lines 140 - 151, Update the
release flow around createTag so staged packages are not tagged before npm
approval; create tags only for published releases, or move staged-package
tagging into the post-approval path. Preserve tagging for approved/published
versions and ensure rejected staged versions do not leave reusable version tags
behind.
🧹 Nitpick comments (4)
internal/gha/src/commands/npm-publishing-mode.ts (2)

60-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the parsed boolean for the output flags.

Lines 63 and 64 re-derive the staged-publishing decision from the raw string, although parseBoolean already produced that value at line 36. The two code paths can diverge if the parser later accepts more inputs, for example "1" or "TRUE".

♻️ Proposed refactor
+    const stagedPublishing = parseBoolean(rawStagedPublishing);
     const plan = await planNpmPublishing(
       parseWorkspacePackages(readFileSync(workspacePath, "utf8")),
-      parseBoolean(rawStagedPublishing),
+      stagedPublishing,
@@
-        `direct=${rawStagedPublishing === "false" || plan.firstReleasePackages.length > 0}`,
-        `direct_all=${rawStagedPublishing === "false"}`,
+        `direct=${!stagedPublishing || plan.firstReleasePackages.length > 0}`,
+        `direct_all=${!stagedPublishing}`,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/gha/src/commands/npm-publishing-mode.ts` around lines 60 - 69,
Update the output flag construction in the publishing command to reuse the
boolean produced by parseBoolean instead of comparing rawStagedPublishing to the
string "false"; apply the parsed value consistently for direct and direct_all
while preserving the first-release package logic for direct.

37-41: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a timeout to the registry lookup.

npm-publishing-mode runs with the caller-supplied node-version input, which can be an LTS older than Node 17 where AbortSignal.timeout is unavailable; use an explicit AbortController timeout instead so the release step fails quickly when the registry stops responding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/gha/src/commands/npm-publishing-mode.ts` around lines 37 - 41, Add
an explicit AbortController-based timeout to the fetch callback inside
packageExistsOnRegistry, avoiding AbortSignal.timeout for compatibility with
older Node versions. Start a timer that aborts the request after the chosen
timeout and clear it when the fetch completes, while preserving the existing
headers and registry lookup behavior.
internal/gha/src/npm-publishing.test.ts (1)

46-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for disabled staging with an existing package.

The current tests never cover stagedPublishing = false combined with an already published package. That path must produce mode: "direct" with empty firstReleasePackages, which is what drives the direct_all workflow output.

♻️ Proposed test
+test("uses direct publishing for existing packages when staging is disabled", async () => {
+  const packageExists = vi.fn(async () => true);
+
+  await expect(
+    planNpmPublishing([workspacePackage("public")], false, packageExists),
+  ).resolves.toEqual({
+    mode: "direct",
+    firstReleasePackages: [],
+    stagedPackages: [],
+  });
+});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/gha/src/npm-publishing.test.ts` around lines 46 - 57, Add a test
alongside the existing explicit regular-publishing case that calls
planNpmPublishing with stagedPublishing false and a packageExists mock returning
true. Assert it resolves to mode "direct" with empty firstReleasePackages and
the existing package listed in stagedPackages, and verify packageExists is
called once with the package name.
.github/workflows/shared-release.yml (1)

260-296: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Publishing runs no verification that the summary file exists.

Both steps run mv pnpm-publish-summary.json right after the publish command. If pnpm writes the summary to a different location, or writes nothing when no package matches the filters, mv fails and the job stops after packages were already submitted to npm. Consider guarding the move.

♻️ Proposed guard
           pnpm publish -r "${filters[@]}" --access public --no-git-checks --report-summary
-          mv pnpm-publish-summary.json "${RUNNER_TEMP}/published-summary.json"
+          if [ -f pnpm-publish-summary.json ]; then
+            mv pnpm-publish-summary.json "${RUNNER_TEMP}/published-summary.json"
+          fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/shared-release.yml around lines 260 - 296, Guard the
summary-file moves in the “Publish packages to npm directly” and “Stage packages
on npm” steps by checking that pnpm-publish-summary.json exists before moving it
to the corresponding runner-temp destination; preserve successful publishing
when no summary is produced.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/gha/src/github-releases.ts`:
- Around line 140-151: Update the release flow around createTag so staged
packages are not tagged before npm approval; create tags only for published
releases, or move staged-package tagging into the post-approval path. Preserve
tagging for approved/published versions and ensure rejected staged versions do
not leave reusable version tags behind.

---

Nitpick comments:
In @.github/workflows/shared-release.yml:
- Around line 260-296: Guard the summary-file moves in the “Publish packages to
npm directly” and “Stage packages on npm” steps by checking that
pnpm-publish-summary.json exists before moving it to the corresponding
runner-temp destination; preserve successful publishing when no summary is
produced.

In `@internal/gha/src/commands/npm-publishing-mode.ts`:
- Around line 60-69: Update the output flag construction in the publishing
command to reuse the boolean produced by parseBoolean instead of comparing
rawStagedPublishing to the string "false"; apply the parsed value consistently
for direct and direct_all while preserving the first-release package logic for
direct.
- Around line 37-41: Add an explicit AbortController-based timeout to the fetch
callback inside packageExistsOnRegistry, avoiding AbortSignal.timeout for
compatibility with older Node versions. Start a timer that aborts the request
after the chosen timeout and clear it when the fetch completes, while preserving
the existing headers and registry lookup behavior.

In `@internal/gha/src/npm-publishing.test.ts`:
- Around line 46-57: Add a test alongside the existing explicit
regular-publishing case that calls planNpmPublishing with stagedPublishing false
and a packageExists mock returning true. Assert it resolves to mode "direct"
with empty firstReleasePackages and the existing package listed in
stagedPackages, and verify packageExists is called once with the package name.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 165d0c2b-c615-4742-bf06-a000d3e92201

📥 Commits

Reviewing files that changed from the base of the PR and between fea163d and abde9c3.

📒 Files selected for processing (14)
  • .changeset/hip-memes-rhyme.md
  • .github/scripts/gha.mjs
  • .github/workflows-examples/README.md
  • .github/workflows-examples/release.yml
  • .github/workflows/shared-release.yml
  • internal/gha/src/commands/github-releases.ts
  • internal/gha/src/commands/index.test.ts
  • internal/gha/src/commands/index.ts
  • internal/gha/src/commands/npm-publishing-mode.ts
  • internal/gha/src/github-releases.test.ts
  • internal/gha/src/github-releases.ts
  • internal/gha/src/npm-publishing.test.ts
  • internal/gha/src/npm-publishing.ts
  • internal/gha/src/workflows.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/gha/src/commands/index.test.ts
  • .github/workflows-examples/release.yml
  • .changeset/hip-memes-rhyme.md
  • .github/workflows-examples/README.md

@zemd

zemd commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/gha/README.md`:
- Line 24: Add the text language identifier to the fenced code block in the
README so the CLI syntax block uses a language-tagged fence and satisfies
markdownlint MD040.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0351218d-f750-4e3c-8003-f46ce0793ee3

📥 Commits

Reviewing files that changed from the base of the PR and between abde9c3 and cfcdd1b.

📒 Files selected for processing (14)
  • .changeset/hip-memes-rhyme.md
  • .github/scripts/gha.mjs
  • .github/workflows-examples/README.md
  • .github/workflows/shared-release.yml
  • internal/gha/README.md
  • internal/gha/src/commands/npm-publishing-mode.ts
  • internal/gha/src/github-releases.test.ts
  • internal/gha/src/github-releases.ts
  • internal/gha/src/github.test.ts
  • internal/gha/src/github.ts
  • internal/gha/src/npm-publishing.test.ts
  • internal/gha/src/npm-publishing.ts
  • internal/gha/src/release-tags.ts
  • internal/gha/src/workflows.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • internal/gha/src/github-releases.test.ts
  • .changeset/hip-memes-rhyme.md
  • .github/workflows-examples/README.md
  • internal/gha/src/github-releases.ts
  • .github/workflows/shared-release.yml
  • internal/gha/src/workflows.test.ts
  • .github/scripts/gha.mjs

Comment thread internal/gha/README.md Outdated
@zemd
zemd merged commit 9043153 into main Aug 9, 2026
15 checks passed
@zemd
zemd deleted the zemd/stage-releases branch August 9, 2026 18:25
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.

2 participants