From 3065cf9e054c1176a0bed7efc2327ceb24b29120 Mon Sep 17 00:00:00 2001 From: branchseer Date: Tue, 19 May 2026 16:18:58 +0800 Subject: [PATCH 1/2] test(e2e): add Node compile-cache fixture (fails on Windows) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A small Node script turns on Node's compile cache and imports a few sibling modules so the cache directory gets some files. Two `vt run --cache build` calls expect a cache miss followed by a cache hit — that's how it should look when the cache lives outside the workspace. The test passes on Linux and macOS but is expected to fail on Windows CI. The e2e harness clears the spawned process env for snapshot determinism, so on Windows the task ends up without `LOCALAPPDATA`, Node falls back to a workspace-relative cache path, and the runner refuses to cache the run. The next commit makes both this fixture and real-world Windows tasks pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../a.mjs | 1 + .../b.mjs | 1 + .../c.mjs | 1 + .../package.json | 5 ++++ .../run.mjs | 20 ++++++++++++++++ .../snapshots.toml | 20 ++++++++++++++++ ...compile_cache_does_not_poison_workspace.md | 24 +++++++++++++++++++ .../vite-task.json | 8 +++++++ 8 files changed, 80 insertions(+) create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/a.mjs create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/b.mjs create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/c.mjs create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/package.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/run.mjs create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots.toml create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots/node_compile_cache_does_not_poison_workspace.md create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/vite-task.json 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 + } + } +} From c96a35c1e76a6b29e9bf30287690ab4cd9bdcdcf Mon Sep 17 00:00:00 2001 From: branchseer Date: Tue, 19 May 2026 16:19:07 +0800 Subject: [PATCH 2/2] fix(plan): forward LOCALAPPDATA so Node's compile cache stays outside the workspace `DEFAULT_UNTRACKED_ENV` already forwards most Windows env vars to cached tasks, but `LOCALAPPDATA` was missing. Node's compile cache uses it to decide where to put cache files on Windows. Without it, the cache lands inside the workspace, the runner sees the same files both written and read in one run, and refuses to cache anything with `not cached because it modified its input`. Add `LOCALAPPDATA` to the list, and mirror it in the e2e harness so the previous commit's Node compile-cache fixture goes from failing on Windows to passing on every platform. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../vite_task_bin/tests/e2e_snapshots/main.rs | 24 +++++++++++++++++++ crates/vite_task_graph/src/config/mod.rs | 5 ++++ 2 files changed, 29 insertions(+) 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",