Skip to content

vp run --filter reports a false cycle when sibling packages are linked via peer/devDependencies #411

@semimikoh

Description

@semimikoh

Summary

vp run --filter ... derives its workspace ordering from the package graph. Currently, that graph treats dependencies, devDependencies, and peerDependencies as ordering edges.

As a result, when two workspace packages reference each other through a peerDependencies or devDependencies link, the back-edge is rejected by the task planner as a cyclic dependency, even though there is no real build-order cycle.

pnpm and turbo do not treat a peerDependencies link as a build-order edge, so the same workspace builds successfully there.

Reported downstream at voidzero-dev/vite-plus#1610.

Reproduction

Reproduction: https://github.com/danielroe/vp-cycle-repro

Workspace shape:

  • app → builder via dependencies
  • builder → app via peerDependencies

Commands:

pnpm install
pnpm --filter './packages/' build
vp run --filter './packages/
' build

pnpm succeeds in topological order, while vp run fails with:

Cycle dependency detected: app#build -> builder#build -> app#build

pnpm install warns about the cyclic workspace links but completes, and the build itself runs fine.

Root cause

As of rev 5833b37, the package graph adds an edge for every dependency kind: Normal, Dev, and Peer.

In crates/vite_workspace/src/package.rs:49-54:

  • dependencies → DependencyType::Normal
  • dev_dependencies → DependencyType::Dev
  • peer_dependencies → DependencyType::Peer

Then, in crates/vite_workspace/src/lib.rs:194, add_edge(id, dep_id, dep_type) adds all three dependency types to the graph.

Topological ordering for vp run is derived from that package subgraph without filtering by dependency type. In crates/vite_task_graph/src/lib.rs:26-27, the code notes that topological ordering is handled at query time via the package subgraph rather than by pre-computing edges in the task graph.

However, crates/vite_workspace/src/package_graph.rs builds the induced subgraph with () edge weights through full_subgraph / build_induced_subgraph. This drops the original DependencyType weight without filtering.

The comment there already anticipates type-aware filtering:

Future --filter-prod support would skip DependencyType::Dev edges at this point.

Finally, the execution graph hard-errors on any cycle, unlike vp exec, which tolerates cycles through a Tarjan SCC fallback. That is why only vp run surfaces this issue.

Relevant locations:

  • crates/vite_task_plan/src/execution_graph.rs — AcyclicGraph::try_from_graph
  • crates/vite_task_plan/src/error.rs:176 — Cycle dependency detected: ...

In short, the peerDependencies back-edge from builder to app is promoted into the build-order graph, and the planner aborts even though peerDependencies should not impose build order.

Proposed fix

The package graph already carries DependencyType on each edge, but that information is discarded when the induced subgraph is built in package_graph.rs.

Filtering at the subgraph-construction point would keep the fix localized.

The main change should be:

  • Exclude peerDependencies edges from the build-order subgraph.

A peer dependency does not impose a build or install order. This matches pnpm and turbo behavior, and removes the false cycle directly.

devDependencies may need to be discussed separately. They may still be useful for ordering in some cases, but they should probably not create a hard build cycle by themselves. This also aligns with the existing --filter-prod direction already noted in the code.

Suggested test

A plan snapshot fixture should be enough, since this issue is about task graph ordering and cycle detection rather than task execution.

Suggested fixture location:

crates/vite_task_plan/tests/plan_snapshots/fixtures/

The fixture can use a two-package workspace:

  • app depends on builder through dependencies
  • builder references app through peerDependencies

Expected result:

vp run should plan cleanly instead of failing with a cycle error.

System info

  • vp 0.1.21
  • pnpm 11.0.9
  • macOS arm64

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions