diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e2ea8f4..51fd99cf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - **Perf** When tasks miss the cache, `vp run` no longer waits for the disk after saving each task's result, so the run finishes sooner. If the computer loses power or the operating system crashes during `vp run`, the cache entries that the run saved can be lost, and those tasks run again next time instead of hitting the cache ([#871](https://github.com/voidzero-dev/vite-task/pull/871)). - **Fixed** Running `vp run` in a package that sits inside a workspace folder but isn't listed in its `pnpm-workspace.yaml` `packages` or `package.json` `workspaces` now runs that package's own tasks, instead of treating the enclosing workspace as its root. When both configurations exist, `pnpm-workspace.yaml` takes precedence ([#859](https://github.com/voidzero-dev/vite-task/pull/859)). +- **Fixed** In a script like `cd ui && vpr hello`, where `ui` is a separate project outside the workspace, the nested `vp run` now runs `ui`'s `hello` instead of the workspace root's ([vite-plus#3129](https://github.com/voidzero-dev/vite-plus/issues/3129), [#867](https://github.com/voidzero-dev/vite-task/pull/867)). - **Fixed** A mistyped or misplaced setting in a task now gets an error that names it. For example, `cache: { outputs: [...] }` now fails with an `unknown field` error that names `outputs` and lists the accepted settings, instead of `data did not match any variant of untagged enum UserTaskDefinition` ([vite-plus#2635](https://github.com/voidzero-dev/vite-plus/issues/2635), [#815](https://github.com/voidzero-dev/vite-task/pull/815)). - **Fixed** In cached tasks on Linux and macOS, programs that pass `-1` as a directory descriptor to calls such as `openat` no longer abort. For example, `ps -p ` from procps-ng 4.0.6 or later aborted when the process had already exited ([#862](https://github.com/voidzero-dev/vite-task/pull/862)). - **Fixed** `vp run` no longer panics before running a script that contains non-ASCII characters, such as `echo こんにちは`, and no longer cuts off such commands where it displays them ([#861](https://github.com/voidzero-dev/vite-task/pull/861)). diff --git a/crates/vt_plan/Cargo.toml b/crates/vt_plan/Cargo.toml index 1af0dfc92..8cec85f8b 100644 --- a/crates/vt_plan/Cargo.toml +++ b/crates/vt_plan/Cargo.toml @@ -32,6 +32,7 @@ vt_powershell = { workspace = true } vt_shell = { workspace = true } vt_str = { workspace = true } vt_graph = { workspace = true } +vt_workspace = { workspace = true } which = { workspace = true } [dev-dependencies] @@ -46,7 +47,6 @@ tokio = { workspace = true, features = ["rt", "macros"] } toml = { workspace = true } vt = { workspace = true } vt_bin = { workspace = true } -vt_workspace = { workspace = true } [[test]] name = "plan_snapshots" diff --git a/crates/vt_plan/src/plan.rs b/crates/vt_plan/src/plan.rs index c76ba93f8..10dd88117 100644 --- a/crates/vt_plan/src/plan.rs +++ b/crates/vt_plan/src/plan.rs @@ -27,6 +27,7 @@ use vt_graph::{ use vt_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf, relative::InvalidPathDataError}; use vt_shell::try_parse_as_and_list; use vt_str::Str; +use vt_workspace::find_workspace_root; use crate::{ ExecutionItem, ExecutionItemDisplay, ExecutionItemKind, LeafExecutionKind, PlanContext, @@ -45,6 +46,13 @@ use crate::{ resolve_cache_with_override, }; +/// Whether `cwd` belongs to a workspace other than the one rooted at `workspace_path`. +/// +/// Directories without any discoverable workspace root are treated as part of `workspace_path`. +fn is_in_other_workspace(cwd: &AbsolutePath, workspace_path: &AbsolutePath) -> bool { + find_workspace_root(cwd).is_ok_and(|(root, _)| &*root.path != workspace_path) +} + /// Locate the executable path for a given program name in the provided envs and cwd. fn which( program: &Arc, @@ -229,7 +237,10 @@ async fn plan_task_as_execution_node( envs, cwd: Arc::clone(&cwd), }; - let plan_request = + // The command as written, before the callback rewrites it (e.g. `vpr` -> `vt run`). + let original_program = script_command.program.clone(); + let original_args = Arc::clone(&script_command.args); + let mut plan_request = context.callbacks().get_plan_request(&mut script_command).await.map_err( |error| Error::ParsePlanRequest { program: script_command.program.clone(), @@ -239,6 +250,19 @@ async fn plan_task_as_execution_node( }, )?; + // After a `cd` into a project that is not part of this workspace (it has its own + // workspace root), this workspace's task graph cannot resolve the nested run: + // it would fall back to the enclosing package. Run the command as written + // instead, so it discovers its own workspace like it does in a shell. + if matches!(plan_request, Some(PlanRequest::Query(_))) + && *cwd != *task_node.resolved_config.resolved_options.cwd + && is_in_other_workspace(&cwd, context.workspace_path()) + { + plan_request = None; + script_command.program = original_program; + script_command.args = original_args; + } + let execution_item_kind: ExecutionItemKind = match plan_request { // Expand task query like `vp run -r build` Some(PlanRequest::Query(query_plan_request)) => { diff --git a/crates/vt_plan/tests/plan_snapshots/fake-bin/README.md b/crates/vt_plan/tests/plan_snapshots/fake-bin/README.md index 0ecd751a7..fb649da6d 100644 --- a/crates/vt_plan/tests/plan_snapshots/fake-bin/README.md +++ b/crates/vt_plan/tests/plan_snapshots/fake-bin/README.md @@ -8,3 +8,4 @@ never actually executed during plan tests — only its resolved path appears in - `vtt` — Unix stub (executable shell script) - `vtt.cmd` — Windows stub (looked up via `PATHEXT`) +- `vpr`, `vpr.cmd` — the same stubs for `vpr`, which is spawned as written when a script `cd`s into a project outside the workspace diff --git a/crates/vt_plan/tests/plan_snapshots/fake-bin/vpr b/crates/vt_plan/tests/plan_snapshots/fake-bin/vpr new file mode 100755 index 000000000..39a3b707f --- /dev/null +++ b/crates/vt_plan/tests/plan_snapshots/fake-bin/vpr @@ -0,0 +1 @@ +#\!/bin/sh diff --git a/crates/vt_plan/tests/plan_snapshots/fake-bin/vpr.cmd b/crates/vt_plan/tests/plan_snapshots/fake-bin/vpr.cmd new file mode 100644 index 000000000..e69de29bb diff --git a/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/package.json b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/package.json new file mode 100644 index 000000000..147168f19 --- /dev/null +++ b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/package.json @@ -0,0 +1,8 @@ +{ + "name": "root", + "private": true, + "scripts": { + "hello": "echo ROOT", + "plain": "cd ui && vpr hello" + } +} diff --git a/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/snapshots.toml b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/snapshots.toml new file mode 100644 index 000000000..780ca37c9 --- /dev/null +++ b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/snapshots.toml @@ -0,0 +1,11 @@ +# Tests that after `cd` into a nested project outside this workspace (no workspace file lists it), +# a nested `vpr` runs as written instead of planning the enclosing package's task. + +[fields] +key = true +execution_item_display = { command = true, cwd = true } +spawn_command = { program_path = true, args = true, cwd = true } + +[[plan]] +name = "cd_into_other_project_then_vpr" +args = ["run", "plain"] diff --git a/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/snapshots/query_cd_into_other_project_then_vpr.jsonc b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/snapshots/query_cd_into_other_project_then_vpr.jsonc new file mode 100644 index 000000000..96cb8f879 --- /dev/null +++ b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/snapshots/query_cd_into_other_project_then_vpr.jsonc @@ -0,0 +1,34 @@ +// run plain +{ + "graph": [ + { + "key": [ + "/", + "plain" + ], + "node": { + "items": [ + { + "execution_item_display": { + "command": "vpr hello", + "cwd": "/ui" + }, + "kind": { + "Leaf": { + "Spawn": { + "spawn_command": { + "program_path": "/vpr", + "args": [ + "hello" + ], + "cwd": "/ui" + } + } + } + } + } + ] + } + } + ] +} diff --git a/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/snapshots/task_graph.md b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/snapshots/task_graph.md new file mode 100644 index 000000000..e74958662 --- /dev/null +++ b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/snapshots/task_graph.md @@ -0,0 +1,88 @@ +# task graph + +```mermaid +flowchart TD + task_0["/#hello"] + task_1["/#plain"] +``` + +## `/#hello` + +```json +{ + "task_display": { + "package_name": "root", + "task_name": "hello", + "package_path": "/" + }, + "resolved_config": { + "commands": [ + "echo ROOT" + ], + "resolved_options": { + "cwd": "/", + "cache_config": { + "remote_cache_allowed": true, + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "PackageJsonScript" +} +``` + +## `/#plain` + +```json +{ + "task_display": { + "package_name": "root", + "task_name": "plain", + "package_path": "/" + }, + "resolved_config": { + "commands": [ + "cd ui && vpr hello" + ], + "resolved_options": { + "cwd": "/", + "cache_config": { + "remote_cache_allowed": true, + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "PackageJsonScript" +} +``` + diff --git a/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/ui/package.json b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/ui/package.json new file mode 100644 index 000000000..0082b4638 --- /dev/null +++ b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/ui/package.json @@ -0,0 +1 @@ +{"name":"ui","private":true,"scripts":{"hello":"echo UI"}} diff --git a/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/vite-task.json b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/vite-task.json new file mode 100644 index 000000000..d548edfac --- /dev/null +++ b/crates/vt_plan/tests/plan_snapshots/fixtures/cd_nested_run/vite-task.json @@ -0,0 +1,3 @@ +{ + "cache": true +}