Skip to content

fix(version): honor workspace selection for recursive version bumps#11425

Merged
zkochan merged 3 commits into
pnpm:mainfrom
aayushbaluni:fix/11348-version-recursive
Jun 17, 2026
Merged

fix(version): honor workspace selection for recursive version bumps#11425
zkochan merged 3 commits into
pnpm:mainfrom
aayushbaluni:fix/11348-version-recursive

Conversation

@aayushbaluni

@aayushbaluni aayushbaluni commented May 2, 2026

Copy link
Copy Markdown
Contributor

Problem

pnpm version --recursive did not bump the workspace packages the user selected. In recursive mode the command re-derived the workspace selection itself (via filterProjectsFromDir) using an incomplete set of options, so it could resolve a different set of packages than the CLI's actual --filter/--recursive resolution.

Fixes #11348.

Change

  • In recursive mode, bump the projects in selectedProjectsGraph — the selection the pnpm CLI (main.ts) already computes from the workspace filter, exactly the way pnpm publish --recursive works.
  • Remove the in-handler filterProjectsFromDir fallback. It duplicated the CLI's filtering logic and was unreachable in production (main.ts always passes selectedProjectsGraph for a recursive run). @pnpm/workspace.projects-filter becomes a dev-only dependency of @pnpm/releasing.commands.
  • No global/config plumbing is needed for --recursive: @pnpm/cli.parse-cli-args already recognizes recursive (and the -r shorthand) for every command, the config reader passes it through to the handler, and the version command already declares recursive in its own cliOptionsTypes.

Tests

  • pnpm/test/version.ts — a CLI e2e test that runs pnpm version --recursive and pnpm version --recursive --filter <pkg> end to end, exercising the real selection wiring (parse-cli-argsmain.ts → handler) rather than a hand-built graph.
  • Handler unit tests in @pnpm/releasing.commands cover the branches that are independent of the CLI selection wiring: an explicitly empty selection bumps nothing (guarding against re-introducing the fallback), skipping unnamed/unversioned packages, skipping the git commit/tag in recursive mode, and non-recursive behavior.

Reviewed and revised by Claude (Opus 4.8) at the request of @zkochan.

Summary by CodeRabbit

Bug Fixes

  • pnpm version --recursive now correctly honors workspace selection filters when determining which packages to bump. Previously, it would version bump all workspace packages regardless of selection. Now only packages matching the workspace filter are affected, bringing consistency with pnpm publish --recursive behavior.

@aayushbaluni aayushbaluni requested a review from zkochan as a code owner May 2, 2026 03:51
@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 43ce6bf0-876a-4122-a4ed-e5bd9d3f1dbc

📥 Commits

Reviewing files that changed from the base of the PR and between 2e2d177 and 6d895e9.

📒 Files selected for processing (1)
  • releasing/commands/test/version/index.test.ts

📝 Walkthrough

Walkthrough

Adds selectedProjectsGraph?: ProjectsGraph to VersionHandlerOptions. In recursive mode, if this field is provided, pkgDirs is derived directly from its keys; otherwise a filterProjectsFromDir call with expanded workspace configuration options is used. Test cases verify behavior with explicit workspace graphs, and a patch changeset documents the fix.

Changes

Recursive version workspace selection

Layer / File(s) Summary
VersionHandlerOptions contract and recursive selection logic
releasing/commands/src/version/index.ts
Imports ProjectsGraph, adds selectedProjectsGraph? to VersionHandlerOptions, and rewrites the recursive pkgDirs computation to use the precomputed graph when available or fall back to filterProjectsFromDir with full workspace config (engine/node constraints, lockfile, linking, test pattern, changed-files ignore, glob-dir mode).
Recursive mode test coverage
releasing/commands/test/version/index.test.ts
Git integration test and recursive mode unit tests now pass explicit selectedProjectsGraph maps; new test verifies empty graph rejection with "No packages to version" error, and existing skip test includes graph selection.
Changeset
.changeset/version-recursive-workspace-selection.md
Records patch releases for @pnpm/releasing.commands and pnpm with a note that pnpm version --recursive now respects workspace selection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • zkochan
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits specification with 'fix:' prefix and clearly describes the main change addressing recursive version bumps honoring workspace selection.
Linked Issues check ✅ Passed The PR successfully addresses the core objective from issue #11348: enabling pnpm version --recursive to apply version updates across workspace packages using workspace selection via selectedProjectsGraph.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the recursive version bump functionality. The modifications to handlers, tests, and changelog align with the stated objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


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 and usage tips.

@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 the current code and only fix it if needed.

Inline comments:
In `@releasing/commands/src/version/index.ts`:
- Around line 163-170: The fallback selection builds WorkspaceFilter entries
only from opts.filter and treats opts.filterProd as a boolean, so pnpm's
recursive --filter-prod path can ignore patterns in opts.filterProd; update the
logic that constructs the filters array (the WorkspaceFilter push loop) to also
iterate over opts.filterProd when present and push a WorkspaceFilter for each
opts.filterProd pattern (setting filter to the pattern and followProdDepsOnly to
true/!!opts.filterProd), ensuring the fallback selection/pnpm version
--recursive --filter-prod path preserves the requested --filter-prod patterns.
- Around line 155-162: The current logic treats an explicit empty
selectedProjectsGraph ({}) the same as "no graph provided" because it switches
based on graphDirs.length > 0, which causes pkgDirs to fall back to
filterProjectsFromDir() and expand the selection; change the branch to test
opts.selectedProjectsGraph != null (or typeof !== 'undefined') instead of
Object.keys(opts.selectedProjectsGraph).length > 0 so that when
selectedProjectsGraph is explicitly an empty object you still assign pkgDirs =
Object.keys(opts.selectedProjectsGraph) (i.e., honor an intentionally empty
selection) and only call filterProjectsFromDir() when selectedProjectsGraph is
actually null/undefined; update the code around variables graphDirs, pkgDirs and
the call to filterProjectsFromDir() in the same block to reflect this condition.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f699c69a-3d59-44b7-aca6-9e15a969b53e

📥 Commits

Reviewing files that changed from the base of the PR and between 6ac06cb and 5f603b6.

📒 Files selected for processing (5)
  • config/reader/src/configFileKey.ts
  • config/reader/src/types.ts
  • pnpm/src/cmd/index.ts
  • releasing/commands/src/version/index.ts
  • releasing/commands/test/version/index.test.ts

Comment thread releasing/commands/src/version/index.ts Outdated
Comment thread releasing/commands/src/version/index.ts Outdated
Register --recursive as a global CLI option and reuse selectedProjectsGraph
from the CLI workspace filter pass (same idea as publish --recursive).
Fall back to filterProjectsFromDir with full workspace options when the graph
is absent.

Fixes pnpm#11348.
@zkochan zkochan force-pushed the fix/11348-version-recursive branch from 5f603b6 to 840fa9b Compare June 17, 2026 08:26
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 17, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. No graph fallback in version 🐞 Bug ≡ Correctness
Description
releasing/commands’s version handler now derives recursive targets exclusively from
opts.selectedProjectsGraph ?? {}, so calling handler() with recursive: true but without a
pre-populated graph will bump nothing and throw NO_PACKAGES_TO_VERSION. This is a behavioral
regression vs. the previous implementation (which computed the graph) and contradicts the PR
description’s stated fallback behavior.
Code

releasing/commands/src/version/index.ts[R151-156]

if (opts.recursive) {
-    const workspaceDir = opts.workspaceDir || opts.dir
-    const filters: WorkspaceFilter[] = []
-
-    if (opts.filter && opts.filter.length > 0) {
-      opts.filter.forEach(filterPattern => {
-        filters.push({
-          filter: filterPattern,
-          followProdDepsOnly: !!opts.filterProd && opts.filterProd.length > 0,
-        })
-      })
-    }
-
-    const result = await filterProjectsFromDir(
-      workspaceDir,
-      filters,
-      {
-        workspaceDir,
-        prefix: opts.dir,
-      }
-    )
-
-    const pkgDirs = Object.keys(result.selectedProjectsGraph)
+    const pkgDirs = Object.keys(opts.selectedProjectsGraph ?? {})
const bumpResults = await Promise.all(
pkgDirs.map(pkgDir => bumpPackageVersion(pkgDir, rawBump, explicitVersion, opts))
)
Evidence
The handler now exclusively uses opts.selectedProjectsGraph to determine which package dirs to
bump; if it is missing, no bumps occur and an error is thrown. selectedProjectsGraph is only
populated by the pnpm CLI harness during recursive execution, so direct/programmatic invocations of
the exported handler do not get this field unless they set it themselves.

releasing/commands/src/version/index.ts[127-171]
pnpm/src/main.ts[260-324]
releasing/commands/src/version/index.ts[315-321]

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

## Issue description
`releasing/commands/src/version/index.ts` uses `Object.keys(opts.selectedProjectsGraph ?? {})` in recursive mode. When `selectedProjectsGraph` is not provided (e.g. calling `version.handler()` programmatically or in unit tests without going through `pnpm/src/main.ts`), the handler bumps no manifests and throws `NO_PACKAGES_TO_VERSION`.
### Issue Context
The pnpm CLI (`pnpm/src/main.ts`) computes `context.selectedProjectsGraph` only on the recursive CLI path and then spreads it into the options passed to command handlers. That means the handler currently depends on CLI bootstrapping.
### Fix Focus Areas
- releasing/commands/src/version/index.ts[149-171]
### Suggested fix
1. In the `if (opts.recursive)` branch, implement a fallback when `opts.selectedProjectsGraph == null`:
- Compute `workspaceDir` as `opts.workspaceDir ?? opts.dir`.
- Build the same `filters` array pattern used by the CLI (`opts.filter`, `opts.filterProd`).
- Call `filterProjectsFromDir(workspaceDir, filters, { workspaceDir, prefix: opts.dir, patterns: opts.workspacePackagePatterns, engineStrict: opts.engineStrict, nodeVersion: opts.nodeVersion, linkWorkspacePackages: !!opts.linkWorkspacePackages, testPattern: opts.testPattern, changedFilesIgnorePattern: opts.changedFilesIgnorePattern, useGlobDirFiltering: !opts.legacyDirFiltering, sharedWorkspaceLockfile: opts.sharedWorkspaceLockfile })`.
- Use the returned `selectedProjectsGraph` keys as `pkgDirs`.
2. If you reintroduce `filterProjectsFromDir` at runtime, ensure `@pnpm/workspace.projects-filter` is a runtime dependency again (or dynamically import it inside the fallback so the common CLI path does not pay for it).
3. Add/adjust tests to cover the fallback path explicitly (a direct `handler({ recursive: true, dir, workspaceDir, filter: [...] })` call without `selectedProjectsGraph` should still bump the expected packages).

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


Grey Divider

Previous review results

Review updated until commit 6d895e9

Results up to commit N/A


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


Remediation recommended
1. No graph fallback in version 🐞 Bug ≡ Correctness
Description
releasing/commands’s version handler now derives recursive targets exclusively from
opts.selectedProjectsGraph ?? {}, so calling handler() with recursive: true but without a
pre-populated graph will bump nothing and throw NO_PACKAGES_TO_VERSION. This is a behavioral
regression vs. the previous implementation (which computed the graph) and contradicts the PR
description’s stated fallback behavior.
Code

releasing/commands/src/version/index.ts[R151-156]

if (opts.recursive) {
-    const workspaceDir = opts.workspaceDir || opts.dir
-    const filters: WorkspaceFilter[] = []
-
-    if (opts.filter && opts.filter.length > 0) {
-      opts.filter.forEach(filterPattern => {
-        filters.push({
-          filter: filterPattern,
-          followProdDepsOnly: !!opts.filterProd && opts.filterProd.length > 0,
-        })
-      })
-    }
-
-    const result = await filterProjectsFromDir(
-      workspaceDir,
-      filters,
-      {
-        workspaceDir,
-        prefix: opts.dir,
-      }
-    )
-
-    const pkgDirs = Object.keys(result.selectedProjectsGraph)
+    const pkgDirs = Object.keys(opts.selectedProjectsGraph ?? {})
const bumpResults = await Promise.all(
  pkgDirs.map(pkgDir => bumpPackageVersion(pkgDir, rawBump, explicitVersion, opts))
)
Evidence
The handler now exclusively uses opts.selectedProjectsGraph to determine which package dirs to
bump; if it is missing, no bumps occur and an error is thrown. selectedProjectsGraph is only
populated by the pnpm CLI harness during recursive execution, so direct/programmatic invocations of
the exported handler do not get this field unless they set it themselves.

releasing/commands/src/version/index.ts[127-171]
pnpm/src/main.ts[260-324]
releasing/commands/src/version/index.ts[315-321]

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

## Issue description
`releasing/commands/src/version/index.ts` uses `Object.keys(opts.selectedProjectsGraph ?? {})` in recursive mode. When `selectedProjectsGraph` is not provided (e.g. calling `version.handler()` programmatically or in unit tests without going through `pnpm/src/main.ts`), the handler bumps no manifests and throws `NO_PACKAGES_TO_VERSION`.
### Issue Context
The pnpm CLI (`pnpm/src/main.ts`) computes `context.selectedProjectsGraph` only on the recursive CLI path and then spreads it into the options passed to command handlers. That means the handler currently depends on CLI bootstrapping.
### Fix Focus Areas
- releasing/commands/src/version/index.ts[149-171]
### Suggested fix
1. In the `if (opts.recursive)` branch, implement a fallback when `opts.selectedProjectsGraph == null`:
- Compute `workspaceDir` as `opts.workspaceDir ?? opts.dir`.
- Build the same `filters` array pattern used by the CLI (`opts.filter`, `opts.filterProd`).
- Call `filterProjectsFromDir(workspaceDir, filters, { workspaceDir, prefix: opts.dir, patterns: opts.workspacePackagePatterns, engineStrict: opts.engineStrict, nodeVersion: opts.nodeVersion, linkWorkspacePackages: !!opts.linkWorkspacePackages, testPattern: opts.testPattern, changedFilesIgnorePattern: opts.changedFilesIgnorePattern, useGlobDirFiltering: !opts.legacyDirFiltering, sharedWorkspaceLockfile: opts.sharedWorkspaceLockfile })`.
- Use the returned `selectedProjectsGraph` keys as `pkgDirs`.
2. If you reintroduce `filterProjectsFromDir` at runtime, ensure `@pnpm/workspace.projects-filter` is a runtime dependency again (or dynamically import it inside the fallback so the common CLI path does not pay for it).
3. Add/adjust tests to cover the fallback path explicitly (a direct `handler({ recursive: true, dir, workspaceDir, filter: [...] })` call without `selectedProjectsGraph` should still bump the expected packages).

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


Qodo Logo

@zkochan zkochan force-pushed the fix/11348-version-recursive branch from 840fa9b to 73818f7 Compare June 17, 2026 08:48
Comment thread releasing/commands/src/version/index.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 73818f7

@zkochan zkochan force-pushed the fix/11348-version-recursive branch from 73818f7 to eaa8eab Compare June 17, 2026 08:59
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit eaa8eab

Workspace selection for a recursive run is already computed by the pnpm
CLI (main.ts) and handed to the command as selectedProjectsGraph, the same
way publish --recursive works. The version handler now bumps exactly those
projects instead of re-deriving the selection itself.

This removes the in-handler filterProjectsFromDir fallback, which
duplicated the CLI's filtering with a different (and incomplete) set of
options and was unreachable in production. `@pnpm/workspace.projects-filter`
becomes a dev-only dependency (still used by tests).

The --recursive flag does not need to be registered as a global option:
`@pnpm/cli.parse-cli-args` already recognizes `recursive` (and the `-r`
shorthand) for every command, the config reader copies it through to the
handler, and the version command already declares it in its own
cliOptionsTypes. So the additions to GLOBAL_OPTIONS, pnpmTypes and
excludedPnpmKeys are reverted, which also avoids a duplicate-property type
error in pack's cliOptionsTypes.

Add a pnpm CLI e2e test (test/version.ts) that runs `pnpm version
--recursive` and `--recursive --filter` end to end, exercising the real
selection wiring rather than a hand-built selectedProjectsGraph.
@zkochan zkochan force-pushed the fix/11348-version-recursive branch from eaa8eab to 2e2d177 Compare June 17, 2026 09:12
The recursive happy path and JSON output are now exercised end to end in
pnpm/test/version.ts and by the non-recursive JSON test, so the duplicated
handler cases are removed. The remaining recursive handler tests cover
branches independent of the CLI selection wiring (empty selection, skipping
unnamed packages, flag gating).
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@zkochan zkochan merged commit 9d0a300 into pnpm:main Jun 17, 2026
6 of 7 checks passed
@welcome

welcome Bot commented Jun 17, 2026

Copy link
Copy Markdown

Congrats on merging your first pull request! 🎉🎉🎉

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6d895e9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pnpm version with --recursive does not work as expected

2 participants