What this issue is (and isn't)
This tracks one shared CLI-argument behavior that pacquet is missing: passing --filter (or --filter-prod) on its own must put the command into recursive mode, even without -r/--recursive. It is not about publish logic, and not about applying the filter once recursive (that was #12711, already done). Like #12711, this belongs outside the publish PR (#12691) because it governs every recursive-capable command, not just publish.
Why
pnpm's own deployment CI (.github/workflows/release.yml) drives publishing entirely through --filter, without -r:
pn --filter=@pnpm/exe publish --tag=next-11 --access=public --provenance
pn publish --filter=!pnpm --filter=!@pnpm/exe --access=public --provenance
pn publish --filter=pnpm --tag=next-11 --access=public --provenance
For pacquet to be a drop-in pn in this workflow, pn publish --filter=pnpm must behave as a recursive publish over the filtered set. Today it does not — it falls through to the single-package path and would try to publish the current directory.
Upstream behavior to match
pnpm promotes recursive in cli/parse-cli-args, for all commands, when a filter is present:
// pnpm11/cli/parse-cli-args/src/index.ts (around line 219)
if (options['recursive'] !== true && (options['filter'] || options['filter-prod'] || recursiveCommandUsed)) {
options['recursive'] = true
// ...
}
pnpm/src/main.ts then builds the filter set from config.filter/config.filterProd, and — when every selector is an exclusion (!pnpm, !@pnpm/exe) and the command is not run at the workspace root — additionally pushes !{.} so the root project is excluded too.
Current pacquet state
recursive is taken straight from the -r/--recursive flag and threaded through RunCtx.recursive; there is no promotion from --filter.
- The only place
--filter affects routing is the install fast-path bail in pacquet/crates/cli/src/cli_args/dispatch.rs (if self.recursive || !self.filter.is_empty() || !self.filter_prod.is_empty()), which merely disables a fast path — it does not enter the recursive command dispatch.
- Consequence:
run, exec, pack, and publish all require an explicit -r to go recursive, diverging from pnpm whenever only --filter is passed.
(Permalinks pinned at a70a996e88: dispatch routing in pacquet/crates/cli/src/cli_args/dispatch.rs, publish entry in pacquet/crates/cli/src/cli_args/publish.rs where if recursive || self.recursive selects the recursive path.)
Proposed change
Promote recursive to true when filter or filter_prod is non-empty, at the same single point where the raw -r flag is read (so it applies CLI-wide, matching parse-cli-args rather than being special-cased per command). Two sub-points to port faithfully:
- The all-exclusion augmentation (
!{.}) from main.ts so --filter=!pnpm --filter=!@pnpm/exe also excludes the workspace root. Note: in this repo the root manifest is private: true, so recursive publish's existing eligibility check already drops it; the augmentation still matters for non-private-root workspaces and for non-publish commands.
- Confirm no already-ported
run/exec/pack test asserts the current (non-promoting) behavior; update any that encode the divergence.
Acceptance
pn <recursive-capable-cmd> --filter=<sel> (no -r) enters recursive mode and operates on the filtered set.
pn publish --filter=pnpm and pn publish --filter=!pnpm --filter=!@pnpm/exe select the intended projects, matching release.yml.
- Behavior parity verified for
run/exec/pack/publish.
Not part of this issue
- Building
@pnpm/exe artifacts and the root release scripts (copy-artifacts, make-release-description) — TS/Node-specific release steps, tracked under the Rust Roadmap (#11633).
--batch publishing — intentionally unported; not used by release.yml.
Written by an agent (Claude Code, claude-opus-4-8).
What this issue is (and isn't)
This tracks one shared CLI-argument behavior that pacquet is missing: passing
--filter(or--filter-prod) on its own must put the command into recursive mode, even without-r/--recursive. It is not about publish logic, and not about applying the filter once recursive (that was #12711, already done). Like #12711, this belongs outside the publish PR (#12691) because it governs every recursive-capable command, not justpublish.Why
pnpm's own deployment CI (
.github/workflows/release.yml) drives publishing entirely through--filter, without-r:For pacquet to be a drop-in
pnin this workflow,pn publish --filter=pnpmmust behave as a recursive publish over the filtered set. Today it does not — it falls through to the single-package path and would try to publish the current directory.Upstream behavior to match
pnpm promotes recursive in
cli/parse-cli-args, for all commands, when a filter is present:pnpm/src/main.tsthen builds the filter set fromconfig.filter/config.filterProd, and — when every selector is an exclusion (!pnpm,!@pnpm/exe) and the command is not run at the workspace root — additionally pushes!{.}so the root project is excluded too.Current pacquet state
recursiveis taken straight from the-r/--recursiveflag and threaded throughRunCtx.recursive; there is no promotion from--filter.--filteraffects routing is the install fast-path bail inpacquet/crates/cli/src/cli_args/dispatch.rs(if self.recursive || !self.filter.is_empty() || !self.filter_prod.is_empty()), which merely disables a fast path — it does not enter the recursive command dispatch.run,exec,pack, andpublishall require an explicit-rto go recursive, diverging from pnpm whenever only--filteris passed.(Permalinks pinned at
a70a996e88: dispatch routing inpacquet/crates/cli/src/cli_args/dispatch.rs, publish entry inpacquet/crates/cli/src/cli_args/publish.rswhereif recursive || self.recursiveselects the recursive path.)Proposed change
Promote
recursivetotruewhenfilterorfilter_prodis non-empty, at the same single point where the raw-rflag is read (so it applies CLI-wide, matchingparse-cli-argsrather than being special-cased per command). Two sub-points to port faithfully:!{.}) frommain.tsso--filter=!pnpm --filter=!@pnpm/exealso excludes the workspace root. Note: in this repo the root manifest isprivate: true, so recursive publish's existing eligibility check already drops it; the augmentation still matters for non-private-root workspaces and for non-publish commands.run/exec/packtest asserts the current (non-promoting) behavior; update any that encode the divergence.Acceptance
pn <recursive-capable-cmd> --filter=<sel>(no-r) enters recursive mode and operates on the filtered set.pn publish --filter=pnpmandpn publish --filter=!pnpm --filter=!@pnpm/exeselect the intended projects, matchingrelease.yml.run/exec/pack/publish.Not part of this issue
@pnpm/exeartifacts and the root release scripts (copy-artifacts,make-release-description) — TS/Node-specific release steps, tracked under the Rust Roadmap (#11633).--batchpublishing — intentionally unported; not used byrelease.yml.Written by an agent (Claude Code, claude-opus-4-8).