From b4e6527f543f954c4bf68979fb28fadc1dc3ab0b Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 12 Mar 2026 14:33:31 +0800 Subject: [PATCH] fix: skip self-referential package dependency edges in workspace graph A package that lists itself as a workspace dependency (e.g. rolldown depending on rolldown for self-testing) caused a self-loop edge in the package dependency graph. This loop propagated to the task execution graph, triggering a false "cycle dependency detected" error when planning any task in that package. Fix by silently skipping self-loop edges when building the package graph. Adds a plan snapshot test covering this scenario. Co-Authored-By: Claude Sonnet 4.6 --- .../package-self-dependency/package.json | 4 ++ .../packages/self-dep/package.json | 10 +++++ .../pnpm-workspace.yaml | 2 + .../package-self-dependency/snapshots.toml | 13 ++++++ ...y - build in self-referential package.snap | 13 ++++++ ...ry - recursive build across workspace.snap | 13 ++++++ .../snapshots/task graph.snap | 41 +++++++++++++++++++ crates/vite_workspace/src/lib.rs | 6 ++- 8 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/package.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/packages/self-dep/package.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/pnpm-workspace.yaml create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots.toml create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/query - build in self-referential package.snap create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/query - recursive build across workspace.snap create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/task graph.snap diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/package.json new file mode 100644 index 00000000..b65cafad --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/package.json @@ -0,0 +1,4 @@ +{ + "name": "test-workspace", + "private": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/packages/self-dep/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/packages/self-dep/package.json new file mode 100644 index 00000000..4642c145 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/packages/self-dep/package.json @@ -0,0 +1,10 @@ +{ + "name": "self-dep", + "version": "1.0.0", + "dependencies": { + "self-dep": "workspace:*" + }, + "scripts": { + "build": "echo building" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/pnpm-workspace.yaml new file mode 100644 index 00000000..18ec407e --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots.toml new file mode 100644 index 00000000..8015ef10 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots.toml @@ -0,0 +1,13 @@ +# Tests that a package with a self-referential workspace dependency (e.g. for +# testing purposes) does not produce a false cycle-dependency error. + +[[plan]] +name = "build in self-referential package" +args = ["run", "build"] +cwd = "packages/self-dep" +compact = true + +[[plan]] +name = "recursive build across workspace" +args = ["run", "-r", "build"] +compact = true diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/query - build in self-referential package.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/query - build in self-referential package.snap new file mode 100644 index 00000000..fd0c9e54 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/query - build in self-referential package.snap @@ -0,0 +1,13 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: "&compact_plan" +info: + args: + - run + - build + cwd: packages/self-dep +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency +--- +{ + "packages/self-dep#build": [] +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/query - recursive build across workspace.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/query - recursive build across workspace.snap new file mode 100644 index 00000000..4021dcd7 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/query - recursive build across workspace.snap @@ -0,0 +1,13 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: "&compact_plan" +info: + args: + - run + - "-r" + - build +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency +--- +{ + "packages/self-dep#build": [] +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/task graph.snap new file mode 100644 index 00000000..253579bb --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency/snapshots/task graph.snap @@ -0,0 +1,41 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: task_graph_json +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency +--- +[ + { + "key": [ + "/packages/self-dep", + "build" + ], + "node": { + "task_display": { + "package_name": "self-dep", + "task_name": "build", + "package_path": "/packages/self-dep" + }, + "resolved_config": { + "command": "echo building", + "resolved_options": { + "cwd": "/packages/self-dep", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "PackageJsonScript" + }, + "neighbors": [] + } +] diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index ed7f2a08..fd26cf9d 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -164,7 +164,11 @@ impl PackageGraphBuilder { path2: dep_path2.clone(), }); } - self.graph.add_edge(*id, *dep_id, *dep_type); + // Skip self-referential edges: a package listing itself as a dependency + // (e.g. for testing purposes) must not create a cycle in the task graph. + if *id != *dep_id { + self.graph.add_edge(*id, *dep_id, *dep_type); + } } // Silently skip if dependency not found - it might be an external package }