Skip to content

feat: filter packages with --filter#176

Merged
branchseer merged 35 commits intomainfrom
02-25-feat_filter_packages_with_--filter_
Mar 1, 2026
Merged

feat: filter packages with --filter#176
branchseer merged 35 commits intomainfrom
02-25-feat_filter_packages_with_--filter_

Conversation

@branchseer
Copy link
Copy Markdown
Member

@branchseer branchseer commented Feb 27, 2026

Summary

Add pnpm-compatible --filter flag to vp run for selecting which packages to run tasks in.

Supports:

  • Package name: exact (--filter @test/app) and glob (--filter @test/*)
  • Directory: exact (--filter ./packages/app), glob (--filter ./packages/*), relative (., ..)
  • Braced paths: {./path} for traversal on paths, name{./dir} for name + directory intersection
  • Dependency traversal: foo... (dependencies), ...foo (dependents), ^ to exclude self, ...foo... (both)
  • Exclusion: !foo to exclude packages from the result
  • Multiple filters: --filter a --filter b (union); --filter "a b" (whitespace split, pnpm compat)
  • -w / --workspace-root: select workspace root; additive with --filter, redundant with -r, supports -t for root + transitive deps
  • Nested expansion: vp run --filter .... build inside package scripts is expanded in the plan

Architecture: PackageQueryArgsPackageQueryFilterResolution

Package selection lives in vite_workspace, not in the task runner. Any command that needs to pick packages can reuse it.

Step 1 — Parse CLI flags into a query.

PackageQueryArgs is a clap struct with -r, -t, -w, and --filter. Embed it via #[clap(flatten)], then call into_package_query():

// in any command definition (e.g. `vp exec`, `vp run`)
#[derive(clap::Args)]
struct MyCommand {
    #[clap(flatten)]
    packages: PackageQueryArgs,
}

// at runtime — package_name comes from `pkg#task` syntax (e.g. "app" in `vp run app#build`).
// commands that don't use `pkg#task` (like `vp exec`) pass None.
let query: PackageQuery = args.packages.into_package_query(package_name, &cwd)?;

into_package_query validates flag combinations (e.g. --filter + -r is an error), splits whitespace, parses each --filter token, and returns an opaque PackageQuery.

Step 2 — Resolve the query against the package graph.

let resolution: FilterResolution = indexed_package_graph.resolve_query(&query);
// resolution.package_subgraph  — the selected packages + edges between them
// resolution.unmatched_selectors — filter strings that matched nothing (for warnings)

PackageQuery is opaque — callers don't inspect its internals. They just pass it to resolve_query and get back the selected subgraph.

Future commands like vp exec only need steps 1 and 2 — they get the selected packages without depending on the task graph at all.

Step 3 (task runner only) — Map packages to tasks.

The task-graph layer maps the package subgraph to task nodes, reconnecting across packages that lack the requested task. Only vp run needs this step.

unmatched_selectors tracks original filter strings

Each --filter token is stored as a source: Option<Str> inside the parsed filter. Synthetic filters (implicit cwd, -w) get source: None since the user didn't type them. When a filter matches nothing, resolve_query collects the original string into unmatched_selectors: Vec<Str> so the caller can show a warning like:

warn: --filter "typo-pkg" matched no packages

This works correctly even with whitespace splitting (--filter "a b" → two filters, each with its own source string).

Test plan

  • 48 unit tests in package_filter for parse_filter, into_package_query, resolve_directory_pattern, and source tracking — covering all selector types, traversal modes, flag combinations, path normalization, and error cases
  • 40 plan snapshot tests across filter-workspace (35) and transitive-skip-intermediate (5) fixtures
  • Existing plan and e2e snapshot tests still pass

🤖 Generated with Claude Code

@branchseer branchseer changed the title feat: add cwd and args info to snapshot headers feat: filter packages with --filter Feb 27, 2026
Copy link
Copy Markdown
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@branchseer branchseer marked this pull request as ready for review February 28, 2026 11:38
@fengmk2 fengmk2 force-pushed the 02-25-feat_filter_packages_with_--filter_ branch from e3ac798 to f013ff7 Compare February 28, 2026 16:45
branchseer and others added 25 commits March 1, 2026 11:12
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ensures snapshots fail when the info header (cwd/args) doesn't match,
preventing silently stale metadata after rebases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…stem

The CLI only accepts a single task specifier, but the underlying
TaskQueryKind still used FxHashSet for multiple specifiers. Simplify
to singular fields and delete the unused CLITaskQuery module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add pnpm-style --filter/-F flag for fine-grained package selection.
Topological ordering is now computed at query time from the package
subgraph rather than pre-computed in the task graph, which correctly
handles per-query skip-intermediate reconnection.

- Add package_filter module (types, parsing, unit tests) to vite_workspace
- Move IndexedPackageGraph to vite_workspace with resolve_query/resolve_filters
- Replace TaskQueryKind with two-stage model: PackageQuery → package subgraph → task mapping
- Simplify TaskDependencyType to explicit-only (topological edges removed from task graph)
- Add --filter/-F to CLI with conflict validation against -r/-t
- Add filter-workspace and transitive-skip-intermediate test fixtures
- Add task-query.md documenting the data flow and design decisions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SerializeByKey now serializes only neighbor keys, ignoring edge weights.
This removes the redundant `null` that appeared for every TaskDependencyType
edge in task graph snapshots.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Test `-t` combined with `package#task` syntax for both cases:
- package has the task (selects it + transitive deps)
- package lacks the task (skip-intermediate, only deps run)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement pnpm v7+ glob-dir semantics: plain directory paths are
exact-match only, `*`/`**` opt in to descendant matching. Adds
DirectoryPattern enum (Exact/Glob) and splits glob paths into a
resolved base + wax::Glob pattern for matching.

Also adds pnpm GitHub permalink references to all existing pnpm
comments and a test for `..` normalization in glob base paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`--filter "a b"` is now equivalent to `--filter a --filter b`,
matching pnpm's behavior of treating space-separated tokens within
a single --filter value as independent selectors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…patterns

Upgrade wax 0.6.0 → 0.7.0 and adapt to breaking changes: Pattern →
Program trait, WalkError moved to wax::walk, walk() takes Into<PathBuf>,
Entry trait methods require explicit import.

Replace manual base/glob splitting in resolve_directory_pattern with
wax::Glob::partition(), and use the Option<Glob> return to decide
between Exact and Glob variants instead of a manual metacharacter check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add plan snapshot tests for:
- directory filter with dependency expansion (./packages/app...)
- dot with deps from package cwd (.... from packages/app)
- dotdot with deps from subdir (..... from packages/app/src)
- nested vp run --filter in package script (deploy expands inline)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use `path_clean::clean()` (Plan 9 cleanname / Go path.Clean port) for
lexical `.`/`..` resolution instead of the manual `normalize_absolute_path`
function. Add direct unit tests for `resolve_directory_pattern`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… match

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused `vec1` and `clap` deps from vite_task_graph (cargo shear)
- Remove unfulfilled `#[expect(clippy::disallowed_types)]` on resolve_filter_path
- Replace wildcard match arms with explicit `DirectoryPattern::Exact` variant
- Make test `abs()` helper cross-platform by prepending `C:` on Windows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move package-query CLI fields (recursive, transitive, filter) and their
validation logic from vite_task::cli::RunFlags into a new
PackageQueryArgs struct in vite_workspace::package_filter. This makes
the package-query concept self-contained next to PackageQuery and
PackageFilter.

Also make PackageQuery opaque (pub struct wrapping pub(crate) enum) and
make PackageFilter, PackageSelector, and related sub-types pub(crate)
so they are no longer part of the public API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, and exclusion edge cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lters

Replace manual `contains(['*', '?', '['])` check with wax's own
partition method, consistent with how DirectoryPattern already works.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pnpm silently discards `...` traversal modifiers on unbraced path
selectors like `./packages/app...` due to syntactic ambiguity between
`..` (parent dir) and `...` (traversal). Only braced paths like
`{./packages/app}...` preserve traversal (pnpm issue #1651, PR #2254).

`parse_core_selector` now returns a `supports_traversal` flag that is
`false` for unbraced `.`-prefix paths, causing `parse_filter` to
discard any stripped `...`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a new PackageSelector::WorkspaceRoot variant that targets the
package with empty relative path (workspace root). The flag follows
pnpm's conflict rules:
- -w alone: selects only the workspace root
- -w --filter: additive (workspace root unioned with filter matches)
- -w -r: redundant (all packages already includes root)
- -w -t: workspace root with transitive dependencies
- -w with package name: error (conflicting target)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- mixed traversal filters: change @test/app... to @test/lib... so result
  ({lib, core, cli}) is distinct from recursive and shows cross-boundary
  cli→core edge
- workspace root with recursive: change build→check so root appears in
  output, proving -r already covers it

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`unmatched_selectors` was `Vec<usize>` (indices into the internal filter
list), but those indices don't map back to CLI args after whitespace
splitting and synthetic filter injection. Change to `Vec<Str>` by
storing the original `--filter` token as `source: Option<Str>` in each
`PackageFilter`. Synthetic filters (implicit cwd, `-w`) get `None` so
they are never reported as unmatched.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
branchseer and others added 4 commits March 1, 2026 11:12
Print "No packages matched the filter: <filter>" to stderr for each
inclusion filter that resolves to zero packages. Exclusion filters and
synthetic filters (implicit cwd, -w) are not reported.

Add e2e snapshot tests covering: partial match, multiple unmatched,
whitespace-split tokens, exclusion filters, glob filters, and directory
filters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace wildcard match arms in test code with explicit
`PackageQueryKind::All` to satisfy `match_wildcard_for_single_variants`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove `pub` from all fields. External crates interact only through
`#[clap(flatten)]` (parsing) and `into_package_query()` (conversion).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The flags are shared by `vp run` and future commands like `vp exec`,
so the help text should not mention "tasks".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@branchseer branchseer force-pushed the 02-25-feat_filter_packages_with_--filter_ branch 2 times, most recently from fa3c8ac to d5e4d4f Compare March 1, 2026 03:16
@branchseer branchseer force-pushed the 02-25-feat_filter_packages_with_--filter_ branch from d5e4d4f to 42c822f Compare March 1, 2026 03:26
branchseer and others added 5 commits March 1, 2026 16:06
…ty filters

Replace nested if-chains with a single match on (recursive, transitive,
workspace_root, Option<Vec1<Str>>) to ensure no flag combination is missed.
Rename `filter` field to `filters`, reject empty/whitespace-only --filter
values with EmptyFilter error, and add tests for the new validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add package_name as the 5th element of the match tuple, replacing
inner `if let Some(package_name)` checks with dedicated match arms.
Each arm is commented with its CLI args. The last combined arm is
split into three: `<pkg>#<task>`, `--transitive`, and bare invocation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Error arms now only match the conflicting fields (wildcards for the
rest); success arms explicitly match every field with no wildcards.
Adds section-header comments separating the two groups.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When `vp run pkg#task` resolves to multiple packages sharing the same
name, emit an `AmbiguousPackageName` error instead of silently running
all of them. `--filter` with the same name continues to match all
packages.

Adds a `unique: bool` flag to `PackageNamePattern::Exact` — true for
`pkg#task` specifiers, false for `--filter`. The resolution chain
(`resolve_query` → `resolve_filters` → `match_by_name_pattern`) is now
fallible, propagating through `query_tasks` to `plan_query_request`.

Also renames the CLI flag from `--filters` to `--filter` (field name
stays `filters`), and adds plan snapshot tests for the duplicate
package name scenario.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use `Path::display()` instead of `Debug` formatting for package paths
in the `AmbiguousPackageName` error message, avoiding platform-specific
quoting differences that break Windows CI snapshots.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@branchseer branchseer merged commit 9e1287e into main Mar 1, 2026
7 checks passed
@branchseer branchseer deleted the 02-25-feat_filter_packages_with_--filter_ branch March 1, 2026 11:06
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