From 2dc0fdba352b0671d9591f342850831e7a05fd5a Mon Sep 17 00:00:00 2001 From: wan9chi Date: Fri, 30 Oct 2026 11:57:46 +0900 Subject: [PATCH 1/2] test(e2e): show that a dist folder appearing in a listed folder goes unseen Two cases for a task that lists a folder in which a `dist` folder it didn't write appears later. In `dist_appears_in_listed_folder`, the task lists the package root with `vtt list-dir .` and prints its entries. In `oxlint_dist`, `oxlint -D no-debugger` lints a package whose `.gitignore` lists `node_modules` but not `dist/`, like a GitHub Action that commits its built `dist/`: it lists the package root, `src` and, once there, `dist`, and reads the files in them. Today every listing skips an entry named `dist`, so a new `dist` folder in the listed root goes unseen and the next run is a cache hit: the first case replays the old listing without `dist`, and the second passes although `dist/bad.js` holds a `debugger` statement. The oxlint fixture ignores `node_modules` as a real package would; otherwise oxlint walks into vp's task cache in `node_modules/.vite`, whose files change between runs. The oxlint steps redact oxlint's rule count, which changes between oxlint releases. Co-Authored-By: Claude Opus 5.5 --- .../package.json | 4 +++ .../snapshots.toml | 22 ++++++++++++ .../dist_appears_in_listed_folder.md | 35 +++++++++++++++++++ .../vite-task.json | 8 +++++ .../fixtures/oxlint_dist/.gitignore | 1 + .../fixtures/oxlint_dist/package.json | 4 +++ .../fixtures/oxlint_dist/snapshots.toml | 24 +++++++++++++ .../oxlint_dist/snapshots/oxlint_dist.md | 29 +++++++++++++++ .../fixtures/oxlint_dist/src/main.js | 1 + .../fixtures/oxlint_dist/vite-task.json | 8 +++++ 10 files changed, 136 insertions(+) create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/package.json create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots.toml create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots/dist_appears_in_listed_folder.md create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/vite-task.json create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/.gitignore create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/package.json create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots.toml create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots/oxlint_dist.md create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/src/main.js create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/vite-task.json diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/package.json b/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/package.json new file mode 100644 index 000000000..b9c66d940 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/package.json @@ -0,0 +1,4 @@ +{ + "name": "dist-appears-in-listed-folder", + "private": true +} diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots.toml new file mode 100644 index 000000000..7aaeeddf7 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots.toml @@ -0,0 +1,22 @@ +[[e2e]] +name = "dist_appears_in_listed_folder" +comment = """ +The task lists the package root and prints its entries, like a tool looking for the files to process. +""" +steps = [ + [ + "vt", + "run", + "build", + ], + { argv = [ + "vtt", + "mkdir", + "dist", + ], comment = "a `dist` folder the task didn't make appears in the listed folder" }, + [ + "vt", + "run", + "build", + ], +] diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots/dist_appears_in_listed_folder.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots/dist_appears_in_listed_folder.md new file mode 100644 index 000000000..6490c60bf --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots/dist_appears_in_listed_folder.md @@ -0,0 +1,35 @@ +# dist_appears_in_listed_folder + +The task lists the package root and prints its entries, like a tool looking for the files to process. + +## `vt run build` + +``` +$ vtt list-dir . +node_modules +package.json +snapshots +snapshots.toml +vite-task.json +``` + +## `vtt mkdir dist` + +a `dist` folder the task didn't make appears in the listed folder + +``` +``` + +## `vt run build` + +``` +$ vtt list-dir . ◉ cache hit, replaying +node_modules +package.json +snapshots +snapshots.toml +vite-task.json + +--- +vt run: cache hit. +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/vite-task.json b/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/vite-task.json new file mode 100644 index 000000000..41f87302a --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/vite-task.json @@ -0,0 +1,8 @@ +{ + "tasks": { + "build": { + "command": "vtt list-dir .", + "cache": true + } + } +} diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/.gitignore b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/package.json b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/package.json new file mode 100644 index 000000000..a424b34b8 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/package.json @@ -0,0 +1,4 @@ +{ + "name": "oxlint-dist", + "private": true +} diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots.toml new file mode 100644 index 000000000..0b1b547f9 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots.toml @@ -0,0 +1,24 @@ +[[e2e]] +name = "oxlint_dist" +comment = """ +`oxlint -D no-debugger` lints the package, whose `.gitignore` lists `node_modules` but not `dist/`, like a GitHub Action that commits its built `dist/`. It lists the package root and each folder it doesn't ignore, and reads the files in them. +""" +ignore = true +steps = [ + { argv = [ + "vt", + "run", + "lint", + ], redact = { 'with \d+ rules' = 'with rules' } }, + { argv = [ + "vtt", + "write-file", + "dist/bad.js", + "debugger;", + ], comment = "a `dist` folder the task didn't write appears, holding code oxlint rejects" }, + { argv = [ + "vt", + "run", + "lint", + ], redact = { 'with \d+ rules' = 'with rules' } }, +] diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots/oxlint_dist.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots/oxlint_dist.md new file mode 100644 index 000000000..2df3d053e --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots/oxlint_dist.md @@ -0,0 +1,29 @@ +# oxlint_dist + +`oxlint -D no-debugger` lints the package, whose `.gitignore` lists `node_modules` but not `dist/`, like a GitHub Action that commits its built `dist/`. It lists the package root and each folder it doesn't ignore, and reads the files in them. + +## `vt run lint` + +``` +$ oxlint -D no-debugger +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vtt write-file dist/bad.js debugger;` + +a `dist` folder the task didn't write appears, holding code oxlint rejects + +``` +``` + +## `vt run lint` + +``` +$ oxlint -D no-debugger ◉ cache hit, replaying +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. + +--- +vt run: cache hit. +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/src/main.js b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/src/main.js new file mode 100644 index 000000000..efeee5db1 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/src/main.js @@ -0,0 +1 @@ +export const value = 1; diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/vite-task.json b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/vite-task.json new file mode 100644 index 000000000..b84e71f3a --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/vite-task.json @@ -0,0 +1,8 @@ +{ + "tasks": { + "lint": { + "command": "oxlint -D no-debugger", + "cache": true + } + } +} From 625af30a4c9aff0985dff6e884c8be290dc223fa Mon Sep 17 00:00:00 2001 From: wan9chi Date: Fri, 30 Oct 2026 12:01:11 +0900 Subject: [PATCH 2/2] fix(cache): stop skipping dist in directory listings Every directory listing fingerprint skipped an entry named `dist`. It was added in #181 so that a lint task listing a folder, like the package root, stayed a cache hit when a `dist` folder it didn't write, such as another task's build output, appeared there. It helped no other folder name and hid a `dist` that does matter: a `dist` folder appearing in or disappearing from a folder the task listed went unseen, and the next run was a cache hit replaying stale output, like `oxlint` passing after a `dist` folder with lint errors appeared in a package that commits its built `dist/`. Listings now skip only `.`, `..` and `.DS_Store`. A task that should ignore another task's `dist` gets that back with `"!dist/**"` in its `input`, since input negatives now keep matching entries out of listings. A task's own `dist` output folder doesn't need the skip: a folder holding only what the task can rebuild is an ignored entry of the listing, so it may be missing. No other snapshot changes, including `typescript_out_dir` and `astro_empty_collections`, whose tasks list the folder holding their own `dist`. The cache schema version is bumped: an entry recorded with the skip holds listings without `dist`, which would otherwise be a hit after a `dist` folder the task listed went away. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + crates/vt/src/session/cache/mod.rs | 2 +- crates/vt/src/session/execute/fingerprint.rs | 2 +- .../snapshots/dist_appears_in_listed_folder.md | 6 ++---- .../oxlint_dist/snapshots/oxlint_dist.md | 17 ++++++++++++----- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a06712477..79f92b185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +- **Changed** When a task lists a folder, an entry named `dist` now counts like any other: a `dist` folder appearing in or disappearing from a folder the task lists makes the next run a cache miss. Every listing used to leave `dist` out, so that a lint task listing the package root still hit after another task's build made `dist` there. That also hid a `dist` that matters, and the next run replayed stale output: `oxlint` in a package that commits its built `dist/` passed on a cache hit after a `dist` folder with lint errors appeared. To keep `dist` out of a task's listings, add `"!dist/**"` to its `input`. A `dist` folder holding only files the task wrote still doesn't count, like any such folder ([#919](https://github.com/voidzero-dev/vite-task/pull/919)). - **Changed** Exclusions in a task's `input`, like `"!build/**"`, now also keep matching entries out of the folders the task lists, and so do the paths a tool reports with `ignoreInput`. For example, a lint task with `"!build/**"` in its `input` that lists the package root is now a cache hit when another task's `build` folder appears there. Previously an exclusion only left out the files the task read, so the new folder was a cache miss. A folder entry is left out when the pattern matches the folder itself, as `build/**` does, but not `build/*` ([#941](https://github.com/voidzero-dev/vite-task/pull/941)). - **Fixed** A task's automatically tracked outputs now include the symbolic links it creates, like the links Nitro's dependency tracer makes in `.output/server/node_modules`, so they no longer need an `output` glob that matches them. The next run is a cache hit if they are missing, as on a fresh clone, and the hit restores them as links if they're among the task's outputs. Previously a hit left them out, and removing one that the task had checked for was a cache miss ([#918](https://github.com/voidzero-dev/vite-task/pull/918)). - **Fixed** A cache hit now restores the empty folders a task made if they're among its outputs, like the `.astro/collections` folder that `astro build` leaves empty: with automatic output tracking that's every such folder, and with `output` globs, the ones they match. A folder holding only such folders and files the task wrote, like `.astro`, may now be missing, as on a fresh clone, and the next run is still a cache hit, which restores it if what it holds is among the task's outputs. Previously a hit left empty folders missing, and removing a folder holding one was a cache miss ([#917](https://github.com/voidzero-dev/vite-task/pull/917)). diff --git a/crates/vt/src/session/cache/mod.rs b/crates/vt/src/session/cache/mod.rs index 31a3cf391..179d94dda 100644 --- a/crates/vt/src/session/cache/mod.rs +++ b/crates/vt/src/session/cache/mod.rs @@ -277,7 +277,7 @@ pub fn split_path(path: &str) -> (Option<&str>, &str) { /// its own cache warm across branch switches, and a cache from a different /// version is simply ignored (it lives in a directory this build never looks /// at) rather than aborting the run. Bumping the version starts a fresh cache. -const CACHE_SCHEMA_VERSION: u32 = 22; +const CACHE_SCHEMA_VERSION: u32 = 23; /// Name of the per-version subdirectory (e.g. `v14`) under the task-cache /// directory that holds the database and output archives for the current diff --git a/crates/vt/src/session/execute/fingerprint.rs b/crates/vt/src/session/execute/fingerprint.rs index f04833876..35c0cdb6f 100644 --- a/crates/vt/src/session/execute/fingerprint.rs +++ b/crates/vt/src/session/execute/fingerprint.rs @@ -483,7 +483,7 @@ fn leave_out_excluded_entries( /// Check if a directory entry should be ignored in fingerprinting const fn should_ignore_entry(name: &[u8]) -> bool { - matches!(name, b"." | b".." | b".DS_Store") || name.eq_ignore_ascii_case(b"dist") + matches!(name, b"." | b".." | b".DS_Store") } /// Fingerprint a single path diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots/dist_appears_in_listed_folder.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots/dist_appears_in_listed_folder.md index 6490c60bf..55a531762 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots/dist_appears_in_listed_folder.md +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/dist_appears_in_listed_folder/snapshots/dist_appears_in_listed_folder.md @@ -23,13 +23,11 @@ a `dist` folder the task didn't make appears in the listed folder ## `vt run build` ``` -$ vtt list-dir . ◉ cache hit, replaying +$ vtt list-dir . ○ cache miss: 'dist' added in workspace root, executing +dist node_modules package.json snapshots snapshots.toml vite-task.json - ---- -vt run: cache hit. ``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots/oxlint_dist.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots/oxlint_dist.md index 2df3d053e..847c5a5f9 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots/oxlint_dist.md +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/oxlint_dist/snapshots/oxlint_dist.md @@ -19,11 +19,18 @@ a `dist` folder the task didn't write appears, holding code oxlint rejects ## `vt run lint` +**Exit code:** 1 + ``` -$ oxlint -D no-debugger ◉ cache hit, replaying -Found 0 warnings and 0 errors. -Finished in on 1 file with rules using threads. +$ oxlint -D no-debugger ○ cache miss: 'dist' added in workspace root, executing + + × eslint(no-debugger): `debugger` statement is not allowed + ╭─[dist/bad.js:1:1] + 1 │ debugger; + · ───────── + ╰──── + help: Remove the debugger statement ---- -vt run: cache hit. +Found 0 warnings and 1 error. +Finished in on 2 files with rules using threads. ```