diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/a.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/a.mjs new file mode 100644 index 00000000..647828c7 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/a.mjs @@ -0,0 +1 @@ +export const value = 'a'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/b.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/b.mjs new file mode 100644 index 00000000..cb16d940 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/b.mjs @@ -0,0 +1 @@ +export const value = 'b'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/c.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/c.mjs new file mode 100644 index 00000000..2a0fd0fb --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/c.mjs @@ -0,0 +1 @@ +export const value = 'c'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/package.json new file mode 100644 index 00000000..bc695cb8 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/package.json @@ -0,0 +1,5 @@ +{ + "name": "node-compile-cache-outside-workspace-fixture", + "private": true, + "type": "module" +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/run.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/run.mjs new file mode 100644 index 00000000..a361ddad --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/run.mjs @@ -0,0 +1,20 @@ +// Tiny Node script that turns on the v22 compile cache and imports a +// few sibling modules so the cache directory actually gets some files +// written to it. On a normally-configured machine the cache lives in +// the OS temp directory (outside the workspace), so the runner doesn't +// see those files when it decides whether the run can be cached. +// +// If the spawned process doesn't have LOCALAPPDATA (or TMP/TEMP/ +// USERPROFILE) set on Windows, Node ends up putting the cache inside +// the workspace, the same files are both written and read in this one +// run, and the runner refuses to cache it. That's the bug this fixture +// catches. +import { enableCompileCache } from 'node:module'; + +enableCompileCache(); + +await import('./a.mjs'); +await import('./b.mjs'); +await import('./c.mjs'); + +console.log('done'); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots.toml new file mode 100644 index 00000000..06c1507b --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots.toml @@ -0,0 +1,20 @@ +[[e2e]] +name = "node_compile_cache_does_not_poison_workspace" +comment = """ +Runs a small Node script that turns on Node's compile cache. The cache should land in the OS temp directory (outside the workspace), so two `vt run --cache build` calls should be a miss then a hit. On Windows, if the spawned task env doesn't have `LOCALAPPDATA`, Node puts the cache inside the workspace instead, the runner sees the same files both written and read, and refuses to cache the run — so the second call becomes another miss with a "not cached because it modified its input" message. +""" +ignore = true +steps = [ + { argv = [ + "vt", + "run", + "--cache", + "build", + ], comment = "first run: cache miss" }, + { argv = [ + "vt", + "run", + "--cache", + "build", + ], comment = "second run: cache hit" }, +] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots/node_compile_cache_does_not_poison_workspace.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots/node_compile_cache_does_not_poison_workspace.md new file mode 100644 index 00000000..e0f113f8 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots/node_compile_cache_does_not_poison_workspace.md @@ -0,0 +1,24 @@ +# node_compile_cache_does_not_poison_workspace + +Runs a small Node script that turns on Node's compile cache. The cache should land in the OS temp directory (outside the workspace), so two `vt run --cache build` calls should be a miss then a hit. On Windows, if the spawned task env doesn't have `LOCALAPPDATA`, Node puts the cache inside the workspace instead, the runner sees the same files both written and read, and refuses to cache the run — so the second call becomes another miss with a "not cached because it modified its input" message. + +## `vt run --cache build` + +first run: cache miss + +``` +$ node run.mjs +done +``` + +## `vt run --cache build` + +second run: cache hit + +``` +$ node run.mjs ◉ cache hit, replaying +done + +--- +vt run: cache hit. +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/vite-task.json new file mode 100644 index 00000000..8c247f01 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/vite-task.json @@ -0,0 +1,8 @@ +{ + "tasks": { + "build": { + "command": "node run.mjs", + "cache": true + } + } +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/main.rs b/crates/vite_task_bin/tests/e2e_snapshots/main.rs index d222696c..45adbada 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/main.rs +++ b/crates/vite_task_bin/tests/e2e_snapshots/main.rs @@ -392,6 +392,30 @@ fn run_case( // On Windows, ensure common executable extensions are included in PATHEXT for command resolution in subprocesses. if cfg!(windows) { cmd.env("PATHEXT", ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC"); + // Forward the Windows env vars Node needs to find a real + // temp directory. Without these, Node's compile cache and + // similar helpers fall back to a workspace-relative path + // and break cached runs. Values come from cargo-test's + // own env when present. + for name in [ + "TMP", + "TEMP", + "APPDATA", + "LOCALAPPDATA", + "PROGRAMDATA", + "USERPROFILE", + "HOMEDRIVE", + "HOMEPATH", + "WINDIR", + "SYSTEMROOT", + "SYSTEMDRIVE", + "ProgramFiles", + "ProgramFiles(x86)", + ] { + if let Some(value) = env::var_os(name) { + cmd.env(name, value); + } + } } for (k, v) in step.envs() { let resolved = resolve_env_placeholder(v.as_str()); diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index a68efbec..38e1db73 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -445,6 +445,11 @@ pub const DEFAULT_UNTRACKED_ENV: &[&str] = &[ "RUNNER_*", // Windows specific "APPDATA", + // Node's compile cache uses LOCALAPPDATA to pick its cache directory + // on Windows. Without it the cache lands inside the workspace and + // breaks every cached task that opts into the compile cache. See + // the `node_compile_cache_outside_workspace` e2e fixture. + "LOCALAPPDATA", "PROGRAMDATA", "SYSTEMROOT", "SYSTEMDRIVE",