Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Changelog

- **Fixed** Reinstalling dependencies with pnpm no longer causes cache misses in tasks that run Vite or Vitest ([#823](https://github.com/voidzero-dev/vite-task/pull/823)).
- **Fixed** Reinstalling dependencies with pnpm, or installing them at another path, no longer causes cache misses ([#823](https://github.com/voidzero-dev/vite-task/pull/823), [#824](https://github.com/voidzero-dev/vite-task/pull/824)).
- **Added** Vite+ now builds and runs on platforms without file-access tracking support, such as FreeBSD. On those platforms, automatic input and output tracking is unavailable, so tasks that rely on it report that tracking is unsupported and must configure their `input` and `output` manually to be cached ([#829](https://github.com/voidzero-dev/vite-task/pull/829)).
- **Changed** The `vp run` summary now reads like `3/4 cache hit (75%), 9/10 successful, 2s saved.` The cache hit count leaves out tasks with caching disabled, and the count of successful tasks out of all tasks replaces the count of failed tasks. When no task has caching enabled, the summary shows only the successful count. In `vp run --verbose` and `vp run --last-details`, the cache hit rate also leaves out tasks with caching disabled, and says `no task has caching enabled` when there are none ([#821](https://github.com/voidzero-dev/vite-task/pull/821)).
- **Perf** On Linux, tasks with automatic file-access tracking start faster ([#817](https://github.com/voidzero-dev/vite-task/pull/817)).
Expand Down
5 changes: 4 additions & 1 deletion crates/vt/src/session/execute/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{hash::Hasher as _, io};
use twox_hash::XxHash3_64;
use vt_path::AbsolutePath;

use super::redact::{UnexpectedFormat, pnpm_modules_manifest};
use super::redact::{UnexpectedFormat, node_modules_bin, pnpm_modules_manifest};

/// Hash content using 8 KiB buffered `xxHash3_64`.
pub(super) fn hash_content(mut stream: impl io::Read) -> io::Result<u64> {
Expand All @@ -25,6 +25,9 @@ pub(super) fn hash_file_content(path: &AbsolutePath, stream: impl io::Read) -> i
if pnpm_modules_manifest::matches(path) {
return hash_redacted(stream, pnpm_modules_manifest::redact);
}
if node_modules_bin::matches(path) {
return hash_redacted(stream, node_modules_bin::redact);
}
hash_content(stream)
}

Expand Down
1 change: 1 addition & 0 deletions crates/vt/src/session/execute/redact/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! `redact` action, and call them from
//! [`hash_file_content`](super::hash::hash_file_content).

pub(super) mod node_modules_bin;
pub(super) mod pnpm_modules_manifest;

use std::io;
Expand Down
40 changes: 40 additions & 0 deletions crates/vt/src/session/execute/redact/node_modules_bin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! Executables in `node_modules/.bin`. pnpm writes them as shell, `.cmd`, and
//! `.ps1` shims that embed absolute paths of the workspace or pnpm's store;
//! npm and Yarn create symlinks to the packages' bin files instead.
//!
//! Only whether one exists is fingerprinted. What it runs is tracked through
//! the package files the task reads.

use std::io;

use vt_path::AbsolutePath;

use super::UnexpectedFormat;

/// Whether `path` is directly inside a `node_modules/.bin` directory.
pub fn matches(path: &AbsolutePath) -> bool {
path.parent().is_some_and(|dir| dir.ends_with("node_modules/.bin"))
}

/// Writes nothing, so every executable in `node_modules/.bin` hashes the same.
#[expect(clippy::unnecessary_wraps, reason = "matches the signature of the other redactions")]
pub const fn redact<W: io::Write>(_content: &[u8], _out: &mut W) -> Result<(), UnexpectedFormat> {
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn matches_only_directly_inside_node_modules_bin() {
let cwd = vt_path::current_dir().unwrap();
assert!(matches(&cwd.join("node_modules/.bin/tsc")));
assert!(matches(&cwd.join("packages/a/node_modules/.bin/tsc.cmd")));
assert!(!matches(&cwd.join("node_modules/typescript/bin/tsc")));
assert!(!matches(&cwd.join("node_modules/.bin")));
assert!(!matches(&cwd.join(".bin/tsc")));
#[cfg(windows)]
assert!(matches(&cwd.join(r"packages\a\node_modules\.bin\tsc.ps1")));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "node-modules-bin",
"private": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
export NODE_PATH="/home/alice/project/node_modules/.pnpm/tool@1.0.0/node_modules"
exec node "$basedir/../tool/bin/tool" "$@"
# cmd-shim-target=/home/alice/project/node_modules/tool/bin/tool
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[[e2e]]
name = "node_modules_bin_content_ignored"
comment = """
Only whether a file in `node_modules/.bin` exists is part of the fingerprint, so a shim rewritten with the workspace at another path keeps the cache hit. Removing it is a cache miss.
"""
steps = [
[
"vtt",
"mkdir",
"-p",
"node_modules/.bin",
],
[
"vtt",
"cp",
"shim.sh",
"node_modules/.bin/tool",
],
{ argv = [
"vt",
"run",
"read-shim",
], comment = "cache miss" },
{ argv = [
"vtt",
"replace-file-content",
"node_modules/.bin/tool",
"/home/alice/project",
"/home/bob/project",
], comment = "reinstall with the workspace at another path" },
{ argv = [
"vt",
"run",
"read-shim",
], comment = "cache hit" },
{ argv = [
"vtt",
"rm",
"node_modules/.bin/tool",
], comment = "remove the shim" },
{ argv = [
"vt",
"run",
"read-shim",
], comment = "cache miss: input removed" },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# node_modules_bin_content_ignored

Only whether a file in `node_modules/.bin` exists is part of the fingerprint, so a shim rewritten with the workspace at another path keeps the cache hit. Removing it is a cache miss.

## `vtt mkdir -p node_modules/.bin`

```
```

## `vtt cp shim.sh node_modules/.bin/tool`

```
```

## `vt run read-shim`

cache miss

```
$ vtt print-file node_modules/.bin/tool
#!/bin/sh
export NODE_PATH="/home/alice/project/node_modules/.pnpm/tool@1.0.0/node_modules"
exec node "$basedir/../tool/bin/tool" "$@"
# cmd-shim-target=/home/alice/project/node_modules/tool/bin/tool
```

## `vtt replace-file-content node_modules/.bin/tool /home/alice/project /home/bob/project`

reinstall with the workspace at another path

```
```

## `vt run read-shim`

cache hit

```
$ vtt print-file node_modules/.bin/tool ◉ cache hit, replaying
#!/bin/sh
export NODE_PATH="/home/alice/project/node_modules/.pnpm/tool@1.0.0/node_modules"
exec node "$basedir/../tool/bin/tool" "$@"
# cmd-shim-target=/home/alice/project/node_modules/tool/bin/tool

---
vt run: cache hit.
```

## `vtt rm node_modules/.bin/tool`

remove the shim

```
```

## `vt run read-shim`

cache miss: input removed

```
$ vtt print-file node_modules/.bin/tool ○ cache miss: 'tool' removed from 'node_modules/.bin', executing
node_modules/.bin/tool: not found
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"tasks": {
"read-shim": {
"command": "vtt print-file node_modules/.bin/tool",
"cache": true
}
}
}
Loading