Skip to content

feat: add the pnpm/release action - #1

Open
zkochan wants to merge 2 commits into
mainfrom
feat/initial-action
Open

feat: add the pnpm/release action#1
zkochan wants to merge 2 commits into
mainfrom
feat/initial-action

Conversation

@zkochan

@zkochan zkochan commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Initial implementation of pnpm/release — a GitHub Action that automates releases for any pnpm workspace using pnpm's native versioning (pnpm change / pnpm version), the built-in replacement for @changesets/cli. It is to pnpm version what changesets/action is to @changesets/cli.

On each push to the release branch it runs in one of two modes:

  • Pending change intents → runs pnpm version -r, commits, and opens/force-updates a single release PR.
  • No intents pending (that PR was merged) → runs your publish command, then tags and drafts a GitHub release for the packages you choose to release.

Design notes

  • Composite action, no bundle. Steps are small zero-dependency Node scripts (stdlib + the preinstalled gh/pnpm/npm). No dist/ to build or keep in sync.
  • Registry-diff to decide what publishes. Uses npm view name@version (404 ⇒ unpublished), so a re-run resumes a partial release instead of double-publishing — the same gate pnpm/pnpm's own release workflow uses.
  • Subset releases. A filter input threads a pnpm --filter through both the version and publish commands; the ledger keeps the un-selected packages' intents pending for a later release.
  • Monorepo-safe tagging. Publishing and tagging are separate concerns: release-filter restricts which published packages get a git tag + release (or create-github-releases: false for none), so a workspace that republishes hundreds of internal packages doesn't flood the ref namespace.

Not yet done

  • No end-to-end run against a live workspace yet (no PR/publish exercised).
  • First release will be tagged v0.

See README.md for inputs/outputs and examples/release.yml for a drop-in consumer workflow.


Written by an agent (Claude Code, claude-opus-4-8).

A composite GitHub Action that automates releases with pnpm's native
versioning (`pnpm change` / `pnpm version`): it maintains a release PR
while change intents are pending, and publishes — tagging and drafting
GitHub releases — once that PR is merged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0f9b730f-25db-4c14-ab43-7991ae0b1e58

📥 Commits

Reviewing files that changed from the base of the PR and between 40b5d55 and c8e6890.

📒 Files selected for processing (9)
  • .github/workflows/ci.yml
  • LICENSE
  • README.md
  • action.yml
  • examples/release.yml
  • scripts/detect.mjs
  • scripts/lib.mjs
  • scripts/publish.mjs
  • scripts/version-pr.mjs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/initial-action

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.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add pnpm/release composite action for pnpm-native workspace releases

✨ Enhancement 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add a composite GitHub Action to manage pnpm version PRs from change intents.
• Publish unreleased workspace versions, then tag and draft GitHub releases.
• Document inputs/outputs and provide example release + CI workflows.
Diagram

graph TD
  A["GitHub workflow run"] --> B["pnpm/release (composite)"] --> C["detect.mjs"] --> D{{"Pending intents?"}}
  D -->|"yes"| E["version-pr.mjs"] --> G(["GitHub PRs/tags/releases"])
  D -->|"no"| F["publish.mjs"] --> H[("npm registry")]
  F --> G

  subgraph Legend
    direction LR
    _proc["Step/script"] ~~~ _dec{{"Decision"}} ~~~ _ext(["External system"]) ~~~ _db[("Registry")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. JavaScript action with bundled dist (toolkit-based)
  • ➕ More robust input handling (quoted commands, JSON inputs, structured configs)
  • ➕ Richer GitHub API usage via Octokit without depending on gh
  • ➕ Easier to unit test internal logic
  • ➖ Requires build pipeline and keeping dist/ in sync
  • ➖ Increases dependency surface and supply-chain footprint
  • ➖ More maintenance overhead than small stdlib scripts
2. Use changesets/action + @changesets/cli instead of pnpm-native versioning
  • ➕ Battle-tested release PR + publish automation with wide adoption
  • ➕ Less custom logic to maintain
  • ➖ Does not leverage pnpm’s native intent/ledger flow
  • ➖ Doesn’t directly support pnpm’s subset-release semantics via ledger the same way
3. Rely on publish output parsing (instead of registry-diff gating)
  • ➕ Avoids per-package registry lookups
  • ➕ Potentially faster for large workspaces
  • ➖ Fragile across pnpm versions and different publish flags
  • ➖ Harder to guarantee idempotent re-runs and partial-release recovery

Recommendation: Keep the current composite + stdlib-scripts approach: it minimizes dependencies and matches the stated goal of “no bundle”. The registry-diff gate is the right strategy for idempotency and partial-failure recovery. If input flexibility becomes a priority (e.g., quoted publish commands or richer config), consider migrating to a bundled JS action later rather than expanding ad-hoc shell parsing.

Files changed (9) +693 / -0

Enhancement (4) +319 / -0
detect.mjsImplement change-intent detection for mode selection +18/-0

Implement change-intent detection for mode selection

• Adds a script that checks for pending '.changeset/*.md' intents (excluding README) and sets the 'has-changesets' output. This output drives whether the action manages a release PR or proceeds to publishing.

scripts/detect.mjs

lib.mjsAdd shared helpers for pnpm/git/gh/npm operations +122/-0

Add shared helpers for pnpm/git/gh/npm operations

• Introduces shared utilities for running commands, handling filters, enumerating releasable packages, checking registry publication status, formatting tags, extracting changelog bodies, and writing GitHub Action outputs. Encapsulates the action’s “registry diff” logic and changelog-to-release-notes mapping.

scripts/lib.mjs

publish.mjsPublish missing versions and optionally tag + draft GitHub releases +94/-0

Publish missing versions and optionally tag + draft GitHub releases

• Implements publish mode by computing which workspace versions are not yet on the registry, running the configured publish command, then confirming publication per package. Optionally creates git tags and drafts GitHub releases (with changelog-derived notes) while remaining tolerant to re-runs and pre-existing tags/releases.

scripts/publish.mjs

version-pr.mjsConsume intents, commit versioning output, and manage a single release PR +85/-0

Consume intents, commit versioning output, and manage a single release PR

• Runs the configured version command (optionally filtered), commits the resulting bumps/changelogs/ledger, force-pushes a stable head branch, and opens or updates a single release PR. Generates a PR body listing releases based on newly added changelog files.

scripts/version-pr.mjs

Documentation (3) +229 / -0
LICENSEAdd MIT license +21/-0

Add MIT license

• Adds an MIT license file attributing pnpm contributors.

LICENSE

README.mdDocument pnpm/release usage, behavior, and inputs/outputs +164/-0

Document pnpm/release usage, behavior, and inputs/outputs

• Adds end-user documentation describing the two-mode workflow (version PR vs publish), monorepo tagging strategy, subset releases via '--filter', and required permissions. Includes usage snippets and guidance for gating downstream steps on action outputs.

README.md

release.ymlProvide example consumer release workflow +44/-0

Provide example consumer release workflow

• Adds a drop-in GitHub workflow showing how to run the action on pushes to main with appropriate permissions, pnpm/node setup, and publish configuration. Demonstrates concurrency settings and token/env wiring.

examples/release.yml

Other (2) +145 / -0
ci.ymlAdd CI workflow to syntax-check action scripts +20/-0

Add CI workflow to syntax-check action scripts

• Introduces a CI workflow on pushes to main and PRs. Validates all 'scripts/*.mjs' files via 'node --check' to ensure the action ships without a build step.

.github/workflows/ci.yml

action.ymlDefine composite action interface and step wiring +125/-0

Define composite action interface and step wiring

• Adds action metadata (branding, inputs/outputs) and composes three Node-script steps: detect pending intents, maintain the release PR via versioning, and publish/tag/release when ready. Wires inputs through environment variables for the scripts to consume.

action.yml

@qodo-code-review

qodo-code-review Bot commented Jul 24, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. pnpm invoked twice ✓ Resolved 🐞 Bug ≡ Correctness
Description
The scripts hardcode the executable as pnpm but also tokenize inputs/defaults that already start
with pnpm, resulting in pnpm pnpm ... and failing the action’s core version/publish flows. This
breaks the default inputs.version and the documented publish command format.
Code

scripts/version-pr.mjs[26]

+run('pnpm', withFilter(tokenize(process.env.INPUT_VERSION)), { cwd })
Evidence
action.yml and the README/examples include input strings starting with pnpm, but the scripts
invoke pnpm as the executable and pass the tokenized string as argv, duplicating pnpm and
causing command failure.

action.yml[12-19]
scripts/lib.mjs[27-32]
scripts/version-pr.mjs[23-27]
scripts/publish.mjs[42-46]
README.md[23-31]
examples/release.yml[38-42]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The action currently runs `run('pnpm', tokenize(INPUT_*))` while the documented/default input strings include a leading `pnpm`. This produces `pnpm pnpm ...` (e.g., `pnpm pnpm version -r ...`, `pnpm pnpm -r publish ...`) and will fail deterministically.

### Issue Context
- `action.yml` default for `inputs.version` includes `pnpm ...`.
- README/examples instruct users to set `publish: pnpm ...`.
- `scripts/version-pr.mjs` and `scripts/publish.mjs` both hardcode `run('pnpm', ...)` and pass the fully tokenized input.

### Fix Focus Areas
Pick **one** consistent contract and implement it end-to-end:

**Option A (recommended): accept full commands**
- Parse `INPUT_VERSION` / `INPUT_PUBLISH` as `[cmd, ...args] = tokenize(...)` and run `run(cmd, withFilterIfPnpm(args))`.
- Only apply `withFilter(...)` when `cmd === 'pnpm'` (or when you explicitly decide to support `corepack pnpm` etc.).

**Option B: accept pnpm args only**
- Change `action.yml` defaults and README/examples so inputs do **not** include the `pnpm` prefix (e.g., `version -r --no-git-checks`, `-r publish ...`).

Update docs/examples to match the chosen contract.

Fix focus areas (edit these regions):
- action.yml[12-25]
- scripts/lib.mjs[27-41]
- scripts/version-pr.mjs[23-27]
- scripts/publish.mjs[42-46]
- README.md[23-31]
- examples/release.yml[38-42]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Token required unnecessarily ✓ Resolved 🐞 Bug ☼ Reliability
Description
scripts/publish.mjs throws if GITHUB_TOKEN is missing before checking create-github-releases, so
runs fail even when tagging/releases are disabled and no GitHub operations would occur. This breaks
the documented publish-only / least-privilege mode (create-github-releases: false).
Code

scripts/publish.mjs[R23-27]

+const token = process.env.GITHUB_TOKEN
+if (!token) throw new Error('GITHUB_TOKEN is required to create tags and releases')
+
+const createReleases = process.env.INPUT_CREATE_GITHUB_RELEASES === 'true'
+const releaseFilter = process.env.INPUT_RELEASE_FILTER?.trim()
Evidence
The script throws on missing GITHUB_TOKEN before evaluating createReleases, yet when releases
are disabled it sets taggable to an empty set and never calls the tag/release code path (so the
token is not actually needed in that mode).

scripts/publish.mjs[22-25]
scripts/publish.mjs[26-55]
action.yml[45-51]
README.md[66-69]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`publish.mjs` unconditionally requires `GITHUB_TOKEN` even when `INPUT_CREATE_GITHUB_RELEASES` is `false`. In that mode, `taggable` becomes an empty set and `tagAndRelease()` is never called, so the token is unused but the step still aborts.

### Issue Context
This prevents workflows that only want to publish to npm (and explicitly disable tag/release creation) from running without a GitHub token, and it undermines least-privilege configurations.

### Fix Focus Areas
- Compute `createReleases` first.
- Only require `GITHUB_TOKEN` when tags/releases might be created (e.g., `if (createReleases && !token) throw ...`).
- Ensure any later git/gh operations that truly need the token are guarded accordingly.

Fix focus areas:
- scripts/publish.mjs[22-55]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread scripts/version-pr.mjs Outdated
Comment thread scripts/publish.mjs Outdated
- Treat the `version`/`publish` inputs as full command lines instead of
  prepending `pnpm`, which produced `pnpm pnpm version …` and failed every
  run. `--filter` is appended only when the command is `pnpm`.
- Require `GITHUB_TOKEN` only when `create-github-releases` is enabled, so a
  publish-only run works without it (least privilege).
- Use `pnpm/setup` (installs pnpm + Node) in the example instead of
  `pnpm/action-setup` + `actions/setup-node`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zkochan

zkochan commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Addressed the review in c8e6890:

Bug 1 — pnpm invoked twice (Qodo). Confirmed and fixed. The scripts ran pnpm as the executable while the input already started with pnpm, yielding pnpm pnpm version …. Adopted Qodo's Option A: the version/publish inputs are now parsed as full command lines ([cmd, ...args]), and --filter is appended only when cmd === 'pnpm', so corepack pnpm … or a custom publish script also works. Added a small assertion test covering the argv construction.

Bug 2 — token required in publish-only mode (Qodo). Confirmed and fixed. GITHUB_TOKEN is now required only when create-github-releases is enabled; a publish-only run (create-github-releases: false) does no GitHub writes and runs without it.

Also: switched the example workflow to pnpm/setup@v1 (installs pnpm + Node via runtime:) instead of pnpm/action-setup + actions/setup-node, and updated the README registry-auth note to match.

CodeRabbit was rate-limited and didn't review — I'll re-request it once the window resets.


Written by an agent (Claude Code, claude-opus-4-8).

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