diff --git a/Cargo.lock b/Cargo.lock index d79cc2f8..c60596b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3796,6 +3796,7 @@ name = "vite_glob" version = "0.0.0" dependencies = [ "thiserror 2.0.18", + "vite_path", "vite_str", "wax", ] @@ -3816,6 +3817,7 @@ dependencies = [ "assert2", "bincode", "diff-struct", + "path-clean", "ref-cast", "serde", "thiserror 2.0.18", @@ -3879,17 +3881,18 @@ dependencies = [ "rustc-hash", "serde", "serde_json", + "tempfile", "thiserror 2.0.18", "tokio", "tracing", "twox-hash", - "vite_glob", "vite_path", "vite_select", "vite_str", "vite_task_graph", "vite_task_plan", "vite_workspace", + "wax", ] [[package]] @@ -3926,6 +3929,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "bincode", "monostate", "petgraph", "pretty_assertions", @@ -3939,6 +3943,7 @@ dependencies = [ "vite_path", "vite_str", "vite_workspace", + "wax", "which", ] @@ -4002,7 +4007,6 @@ name = "vite_workspace" version = "0.0.0" dependencies = [ "clap", - "path-clean", "petgraph", "rustc-hash", "serde", diff --git a/crates/fspy/src/unix/mod.rs b/crates/fspy/src/unix/mod.rs index 72b5dac0..96583f04 100644 --- a/crates/fspy/src/unix/mod.rs +++ b/crates/fspy/src/unix/mod.rs @@ -100,6 +100,7 @@ impl SpyImpl { ) .map_err(|err| SpawnError::Injection(err.into()))?; command.set_exec(exec); + command.env("FSPY", "1"); let mut tokio_command = command.into_tokio_command(); diff --git a/crates/fspy/src/windows/mod.rs b/crates/fspy/src/windows/mod.rs index 03a4c899..b4f1c75e 100644 --- a/crates/fspy/src/windows/mod.rs +++ b/crates/fspy/src/windows/mod.rs @@ -73,8 +73,9 @@ impl SpyImpl { } #[expect(clippy::unused_async, reason = "async signature required by SpyImpl trait")] - pub(crate) async fn spawn(&self, command: Command) -> Result { + pub(crate) async fn spawn(&self, mut command: Command) -> Result { let ansi_dll_path_with_nul = Arc::clone(&self.ansi_dll_path_with_nul); + command.env("FSPY", "1"); let mut command = command.into_tokio_command(); command.creation_flags(CREATE_SUSPENDED); diff --git a/crates/fspy_preload_unix/src/interceptions/stat.rs b/crates/fspy_preload_unix/src/interceptions/stat.rs index c70ff270..fe05c2b0 100644 --- a/crates/fspy_preload_unix/src/interceptions/stat.rs +++ b/crates/fspy_preload_unix/src/interceptions/stat.rs @@ -2,10 +2,7 @@ use fspy_shared::ipc::AccessMode; use libc::{c_char, c_int, stat as stat_struct}; use crate::{ - client::{ - convert::{Fd, PathAt}, - handle_open, - }, + client::{convert::PathAt, handle_open}, macros::intercept, }; @@ -30,16 +27,6 @@ unsafe extern "C" fn lstat(path: *const c_char, buf: *mut stat_struct) -> c_int unsafe { lstat::original()(path, buf) } } -intercept!(fstat(64): unsafe extern "C" fn(fd: c_int, buf: *mut stat_struct) -> c_int); -unsafe extern "C" fn fstat(fd: c_int, buf: *mut stat_struct) -> c_int { - // SAFETY: fd is a valid file descriptor provided by the caller of the interposed function - unsafe { - handle_open(Fd(fd), AccessMode::READ); - } - // SAFETY: calling the original libc fstat() with the same arguments forwarded from the interposed function - unsafe { fstat::original()(fd, buf) } -} - intercept!(fstatat(64): unsafe extern "C" fn(dirfd: c_int, pathname: *const c_char, buf: *mut stat_struct, flags: c_int) -> c_int); unsafe extern "C" fn fstatat( dirfd: c_int, diff --git a/crates/vite_glob/Cargo.toml b/crates/vite_glob/Cargo.toml index d749d75f..90006a26 100644 --- a/crates/vite_glob/Cargo.toml +++ b/crates/vite_glob/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true [dependencies] thiserror = { workspace = true } +vite_path = { workspace = true } wax = { workspace = true } [dev-dependencies] diff --git a/crates/vite_glob/src/error.rs b/crates/vite_glob/src/error.rs index bf399dec..5d1b75f8 100644 --- a/crates/vite_glob/src/error.rs +++ b/crates/vite_glob/src/error.rs @@ -2,4 +2,8 @@ pub enum Error { #[error(transparent)] WaxBuild(#[from] wax::BuildError), + #[error(transparent)] + Walk(#[from] wax::walk::WalkError), + #[error(transparent)] + InvalidPathData(#[from] vite_path::relative::InvalidPathDataError), } diff --git a/crates/vite_path/Cargo.toml b/crates/vite_path/Cargo.toml index 207f05e0..7da9d22e 100644 --- a/crates/vite_path/Cargo.toml +++ b/crates/vite_path/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true [dependencies] bincode = { workspace = true } diff-struct = { workspace = true } +path-clean = { workspace = true } ref-cast = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } thiserror = { workspace = true } diff --git a/crates/vite_path/src/absolute/mod.rs b/crates/vite_path/src/absolute/mod.rs index 794eb93b..a7b2476e 100644 --- a/crates/vite_path/src/absolute/mod.rs +++ b/crates/vite_path/src/absolute/mod.rs @@ -200,6 +200,24 @@ impl AbsolutePath { pub fn ends_with>(&self, path: P) -> bool { self.0.ends_with(path.as_ref()) } + + /// Lexically normalizes the path by resolving `.` and `..` components + /// without accessing the filesystem. + /// + /// **Symlink limitation**: Because this is purely lexical, it can produce + /// incorrect results when symlinks are involved. For example, if + /// `/a/link` is a symlink to `/x/y`, then cleaning `/a/link/../c` + /// yields `/a/c` instead of the correct `/x/c`. Use + /// [`std::fs::canonicalize`] when you need symlink-correct resolution. + #[must_use] + pub fn clean(&self) -> AbsolutePathBuf { + use path_clean::PathClean as _; + + let cleaned = self.0.clean(); + // SAFETY: Lexical cleaning of an absolute path preserves absoluteness — + // it only removes `.`/`..` components and redundant separators. + unsafe { AbsolutePathBuf::assume_absolute(cleaned) } + } } /// An Error returned from [`AbsolutePath::strip_prefix`] if the stripped path is not a valid `RelativePath` diff --git a/crates/vite_path/src/relative.rs b/crates/vite_path/src/relative.rs index 548b8017..3d3bd8fe 100644 --- a/crates/vite_path/src/relative.rs +++ b/crates/vite_path/src/relative.rs @@ -62,6 +62,28 @@ impl RelativePath { relative_path_buf } + /// Lexically normalizes the path by resolving `..` components without + /// accessing the filesystem. (`.` components are already stripped by + /// [`RelativePathBuf::new`].) + /// + /// **Symlink limitation**: Because this is purely lexical, it can produce + /// incorrect results when symlinks are involved. For example, if + /// `a/link` is a symlink to `x/y`, then cleaning `a/link/../c` + /// yields `a/c` instead of the correct `x/c`. Use + /// [`std::fs::canonicalize`] when you need symlink-correct resolution. + /// + /// # Panics + /// + /// Panics if the cleaned path is no longer a valid relative path, which + /// should never happen in practice. + #[must_use] + pub fn clean(&self) -> RelativePathBuf { + use path_clean::PathClean as _; + + let cleaned = self.as_path().clean(); + RelativePathBuf::new(cleaned).expect("cleaning a relative path preserves relativity") + } + /// Returns a path that, when joined onto `base`, yields `self`. /// /// If `base` is not a prefix of `self`, returns [`None`]. diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml index 58f61818..183e32d1 100644 --- a/crates/vite_task/Cargo.toml +++ b/crates/vite_task/Cargo.toml @@ -33,13 +33,16 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "io-std", "io-util", "macros", "sync"] } tracing = { workspace = true } twox-hash = { workspace = true } -vite_glob = { workspace = true } vite_path = { workspace = true } vite_select = { workspace = true } vite_str = { workspace = true } vite_task_graph = { workspace = true } vite_task_plan = { workspace = true } vite_workspace = { workspace = true } +wax = { workspace = true } + +[dev-dependencies] +tempfile = { workspace = true } [target.'cfg(unix)'.dependencies] nix = { workspace = true } diff --git a/crates/vite_task/src/session/cache/display.rs b/crates/vite_task/src/session/cache/display.rs index 7bf46c96..9392d41f 100644 --- a/crates/vite_task/src/session/cache/display.rs +++ b/crates/vite_task/src/session/cache/display.rs @@ -39,12 +39,6 @@ pub enum SpawnFingerprintChange { // Working directory change /// Working directory changed CwdChanged, - - // Fingerprint ignores changes - /// Fingerprint ignore pattern added - FingerprintIgnoreAdded { pattern: Str }, - /// Fingerprint ignore pattern removed - FingerprintIgnoreRemoved { pattern: Str }, } /// Format a single spawn fingerprint change as human-readable text. @@ -70,12 +64,6 @@ pub fn format_spawn_change(change: &SpawnFingerprintChange) -> Str { SpawnFingerprintChange::ProgramChanged => Str::from("program changed"), SpawnFingerprintChange::ArgsChanged => Str::from("args changed"), SpawnFingerprintChange::CwdChanged => Str::from("working directory changed"), - SpawnFingerprintChange::FingerprintIgnoreAdded { pattern } => { - vite_str::format!("fingerprint ignore '{pattern}' added") - } - SpawnFingerprintChange::FingerprintIgnoreRemoved { pattern } => { - vite_str::format!("fingerprint ignore '{pattern}' removed") - } } } @@ -141,20 +129,6 @@ pub fn detect_spawn_fingerprint_changes( changes.push(SpawnFingerprintChange::CwdChanged); } - // Check fingerprint ignores changes - let old_ignores: FxHashSet<_> = - old.fingerprint_ignores().map(|v| v.iter().collect()).unwrap_or_default(); - let new_ignores: FxHashSet<_> = - new.fingerprint_ignores().map(|v| v.iter().collect()).unwrap_or_default(); - for pattern in old_ignores.difference(&new_ignores) { - changes - .push(SpawnFingerprintChange::FingerprintIgnoreRemoved { pattern: (*pattern).clone() }); - } - for pattern in new_ignores.difference(&old_ignores) { - changes - .push(SpawnFingerprintChange::FingerprintIgnoreAdded { pattern: (*pattern).clone() }); - } - changes } @@ -181,7 +155,7 @@ pub fn format_cache_status_inline(cache_status: &CacheStatus) -> Option { CacheStatus::Miss(CacheMiss::FingerprintMismatch(mismatch)) => { // Show "cache miss" with reason why cache couldn't be used let reason = match mismatch { - FingerprintMismatch::SpawnFingerprintMismatch { old, new } => { + FingerprintMismatch::SpawnFingerprint { old, new } => { let changes = detect_spawn_fingerprint_changes(old, new); match changes.first() { Some( @@ -196,14 +170,16 @@ pub fn format_cache_status_inline(cache_status: &CacheStatus) -> Option { Some(SpawnFingerprintChange::ProgramChanged) => "program changed", Some(SpawnFingerprintChange::ArgsChanged) => "args changed", Some(SpawnFingerprintChange::CwdChanged) => "working directory changed", - Some( - SpawnFingerprintChange::FingerprintIgnoreAdded { .. } - | SpawnFingerprintChange::FingerprintIgnoreRemoved { .. }, - ) => "fingerprint ignores changed", None => "configuration changed", } } - FingerprintMismatch::PostRunFingerprintMismatch(diff) => { + FingerprintMismatch::InputConfig => "inputs configuration changed", + FingerprintMismatch::GlobbedInput { path } => { + return Some(vite_str::format!( + "✗ cache miss: content of input '{path}' changed, executing" + )); + } + FingerprintMismatch::PostRunFingerprint(diff) => { use crate::session::execute::fingerprint::PostRunFingerprintMismatch; match diff { PostRunFingerprintMismatch::InputContentChanged { path } => { diff --git a/crates/vite_task/src/session/cache/mod.rs b/crates/vite_task/src/session/cache/mod.rs index 1b686bb5..a4b3f588 100644 --- a/crates/vite_task/src/session/cache/mod.rs +++ b/crates/vite_task/src/session/cache/mod.rs @@ -2,7 +2,7 @@ pub mod display; -use std::{fmt::Display, fs::File, io::Write, sync::Arc, time::Duration}; +use std::{collections::BTreeMap, fmt::Display, fs::File, io::Write, sync::Arc, time::Duration}; use bincode::{Decode, Encode, decode_from_slice, encode_to_vec}; // Re-export display functions for convenience @@ -11,7 +11,8 @@ pub use display::{SpawnFingerprintChange, detect_spawn_fingerprint_changes, form use rusqlite::{Connection, OptionalExtension as _, config::DbConfig}; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; -use vite_path::AbsolutePath; +use vite_path::{AbsolutePath, RelativePathBuf}; +use vite_task_graph::config::ResolvedInputConfig; use vite_task_plan::cache_metadata::{CacheMetadata, ExecutionCacheKey, SpawnFingerprint}; use super::execute::{ @@ -19,13 +20,50 @@ use super::execute::{ spawn::StdOutput, }; -/// Command cache value, for validating post-run fingerprint after the spawn fingerprint is matched, -/// and replaying the std outputs if validated. +/// Cache lookup key identifying a task's execution configuration. +/// +/// # Key vs value design +/// +/// Put a field in the **key** if each distinct value should have its own +/// cache entry (e.g., different env values → different entries, so +/// reverting an env change can still hit the old entry). +/// +/// Put a field in the **value** ([`CacheEntryValue`]) if changes should +/// overwrite the existing entry (e.g., input file hashes — there's no +/// reason to keep the old hash around, and storing them in the value +/// lets us report exactly *which file* changed). +#[derive(Debug, Encode, Decode, Serialize, PartialEq, Eq, Clone)] +pub struct CacheEntryKey { + /// The spawn fingerprint (command, args, cwd, envs) + pub spawn_fingerprint: SpawnFingerprint, + /// Resolved input configuration that affects cache behavior. + /// Glob patterns are workspace-root-relative. + pub input_config: ResolvedInputConfig, +} + +impl CacheEntryKey { + fn from_metadata(cache_metadata: &CacheMetadata) -> Self { + Self { + spawn_fingerprint: cache_metadata.spawn_fingerprint.clone(), + input_config: cache_metadata.input_config.clone(), + } + } +} + +/// Cached execution result for a task. +/// +/// Contains the post-run fingerprint (from fspy), captured outputs, +/// execution duration, and explicit input file hashes. #[derive(Debug, Encode, Decode, Serialize)] -pub struct CommandCacheValue { +pub struct CacheEntryValue { pub post_run_fingerprint: PostRunFingerprint, pub std_outputs: Arc<[StdOutput]>, pub duration: Duration, + /// Hashes of explicit input files computed from positive globs. + /// Files matching negative globs are already filtered out. + /// Path is relative to workspace root, value is `xxHash3_64` of file content. + /// Stored in the value (not the key) so changes can be detected and reported. + pub globbed_inputs: BTreeMap, } #[derive(Debug)] @@ -46,30 +84,36 @@ pub enum CacheMiss { } #[derive(Debug, Serialize, Deserialize)] -#[expect( - clippy::large_enum_variant, - reason = "SpawnFingerprintMismatch holds two SpawnFingerprints for comparison; boxing would add unnecessary indirection for a short-lived enum" -)] pub enum FingerprintMismatch { - /// Found the cache entry of the same task run, but the spawn fingerprint mismatches - /// this happens when the command itself or an env changes. - SpawnFingerprintMismatch { + /// Found a previous cache entry key for the same task, but the spawn fingerprint differs. + /// This happens when the command itself or an env changes. + SpawnFingerprint { /// The fingerprint from the cached entry old: SpawnFingerprint, /// The fingerprint of the current execution new: SpawnFingerprint, }, + /// Found a previous cache entry key for the same task, but `input_config` differs. + InputConfig, + /// Found the cache entry with the same spawn fingerprint, but an explicit globbed input changed + GlobbedInput { path: RelativePathBuf }, /// Found the cache entry with the same spawn fingerprint, but the post-run fingerprint mismatches - PostRunFingerprintMismatch(PostRunFingerprintMismatch), + PostRunFingerprint(PostRunFingerprintMismatch), } impl Display for FingerprintMismatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::SpawnFingerprintMismatch { old, new } => { + Self::SpawnFingerprint { old, new } => { write!(f, "Spawn fingerprint changed: old={old:?}, new={new:?}") } - Self::PostRunFingerprintMismatch(diff) => Display::fmt(diff, f), + Self::InputConfig => { + write!(f, "inputs configuration changed") + } + Self::GlobbedInput { path } => { + write!(f, "content of input '{path}' changed") + } + Self::PostRunFingerprint(diff) => Display::fmt(diff, f), } } } @@ -98,23 +142,23 @@ impl ExecutionCache { 0 => { // fresh new db conn.execute( - "CREATE TABLE spawn_fingerprint_cache (key BLOB PRIMARY KEY, value BLOB);", + "CREATE TABLE cache_entries (key BLOB PRIMARY KEY, value BLOB);", (), )?; conn.execute( - "CREATE TABLE execution_key_to_fingerprint (key BLOB PRIMARY KEY, value BLOB);", + "CREATE TABLE task_fingerprints (key BLOB PRIMARY KEY, value BLOB);", (), )?; - conn.execute("PRAGMA user_version = 6", ())?; + conn.execute("PRAGMA user_version = 10", ())?; } - 1..=5 => { + 1..=9 => { // old internal db version. reset conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, true)?; conn.execute("VACUUM", ())?; conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, false)?; } - 6 => break, // current version - 7.. => { + 10 => break, // current version + 11.. => { return Err(anyhow::anyhow!("Unrecognized database version: {user_version}")); } } @@ -129,48 +173,61 @@ impl ExecutionCache { Ok(()) } - /// Try to hit cache with spawn fingerprint. + /// Try to hit cache by looking up the cache entry key and validating inputs. /// Returns `Ok(Ok(cache_value))` on cache hit, `Ok(Err(cache_miss))` on miss. #[tracing::instrument(level = "debug", skip_all)] pub async fn try_hit( &self, cache_metadata: &CacheMetadata, - base_dir: &AbsolutePath, - ) -> anyhow::Result> { + globbed_inputs: &BTreeMap, + workspace_root: &AbsolutePath, + ) -> anyhow::Result> { let spawn_fingerprint = &cache_metadata.spawn_fingerprint; let execution_cache_key = &cache_metadata.execution_cache_key; - // Try to directly find the cache by spawn fingerprint first - if let Some(cache_value) = self.get_by_spawn_fingerprint(spawn_fingerprint).await? { - // Validate post-run fingerprint + let cache_key = CacheEntryKey::from_metadata(cache_metadata); + + // Try to find the cache entry by key (spawn fingerprint + input config) + if let Some(cache_value) = self.get_by_cache_key(&cache_key).await? { + // Validate explicit globbed inputs against the stored values + if let Some(mismatch) = + detect_globbed_input_change(&cache_value.globbed_inputs, globbed_inputs) + { + return Ok(Err(CacheMiss::FingerprintMismatch(mismatch))); + } + + // Validate post-run fingerprint (inferred inputs from fspy) if let Some(post_run_fingerprint_mismatch) = - cache_value.post_run_fingerprint.validate(base_dir)? + cache_value.post_run_fingerprint.validate(workspace_root)? { - // Found the cache with the same spawn fingerprint, but the post-run fingerprint mismatches return Ok(Err(CacheMiss::FingerprintMismatch( - FingerprintMismatch::PostRunFingerprintMismatch(post_run_fingerprint_mismatch), + FingerprintMismatch::PostRunFingerprint(post_run_fingerprint_mismatch), ))); } - // Associate the execution key to the spawn fingerprint if not already, - // so that next time we can find it and report spawn fingerprint mismatch - self.upsert_execution_key_to_fingerprint(execution_cache_key, spawn_fingerprint) - .await?; + // Associate the execution key to the cache entry key if not already, + // so that next time we can find it and report what changed + self.upsert_task_fingerprint(execution_cache_key, &cache_key).await?; return Ok(Ok(cache_value)); } - // No cache found with the current spawn fingerprint, - // check if execution key maps to different fingerprint - if let Some(old_spawn_fingerprint) = - self.get_fingerprint_by_execution_key(execution_cache_key).await? + // No cache found with the current cache entry key, + // check if execution key maps to a different cache entry key + if let Some(old_cache_key) = + self.get_cache_key_by_execution_key(execution_cache_key).await? { - // Found a spawn fingerprint associated with the same execution key, - // meaning the command or env has changed since last run - return Ok(Err(CacheMiss::FingerprintMismatch( - FingerprintMismatch::SpawnFingerprintMismatch { + // Destructure to ensure we handle all fields when new ones are added + let CacheEntryKey { spawn_fingerprint: old_spawn_fingerprint, input_config: _ } = + old_cache_key; + let mismatch = if old_spawn_fingerprint == *spawn_fingerprint { + // spawn fingerprint is the same but input_config or glob_base changed + FingerprintMismatch::InputConfig + } else { + FingerprintMismatch::SpawnFingerprint { old: old_spawn_fingerprint, new: spawn_fingerprint.clone(), - }, - ))); + } + }; + return Ok(Err(CacheMiss::FingerprintMismatch(mismatch))); } Ok(Err(CacheMiss::NotFound)) @@ -181,17 +238,54 @@ impl ExecutionCache { pub async fn update( &self, cache_metadata: &CacheMetadata, - cache_value: CommandCacheValue, + cache_value: CacheEntryValue, ) -> anyhow::Result<()> { - let spawn_fingerprint = &cache_metadata.spawn_fingerprint; let execution_cache_key = &cache_metadata.execution_cache_key; - self.upsert_spawn_fingerprint_cache(spawn_fingerprint, &cache_value).await?; - self.upsert_execution_key_to_fingerprint(execution_cache_key, spawn_fingerprint).await?; + let cache_key = CacheEntryKey::from_metadata(cache_metadata); + + self.upsert_cache_entry(&cache_key, &cache_value).await?; + self.upsert_task_fingerprint(execution_cache_key, &cache_key).await?; Ok(()) } } +/// Compare stored and current globbed inputs, returning the first changed path. +/// Both maps are `BTreeMap` so we iterate them in sorted lockstep. +fn detect_globbed_input_change( + stored: &BTreeMap, + current: &BTreeMap, +) -> Option { + let mut stored_iter = stored.iter(); + let mut current_iter = current.iter(); + let mut s = stored_iter.next(); + let mut c = current_iter.next(); + + loop { + match (s, c) { + (None, None) => return None, + (Some((path, _)), None) | (None, Some((path, _))) => { + return Some(FingerprintMismatch::GlobbedInput { path: path.clone() }); + } + (Some((sp, sh)), Some((cp, ch))) => match sp.cmp(cp) { + std::cmp::Ordering::Equal => { + if sh != ch { + return Some(FingerprintMismatch::GlobbedInput { path: sp.clone() }); + } + s = stored_iter.next(); + c = current_iter.next(); + } + std::cmp::Ordering::Less => { + return Some(FingerprintMismatch::GlobbedInput { path: sp.clone() }); + } + std::cmp::Ordering::Greater => { + return Some(FingerprintMismatch::GlobbedInput { path: cp.clone() }); + } + }, + } + } +} + // Basic database operations impl ExecutionCache { #[expect( @@ -227,18 +321,18 @@ impl ExecutionCache { Ok(Some(value)) } - async fn get_by_spawn_fingerprint( + async fn get_by_cache_key( &self, - spawn_fingerprint: &SpawnFingerprint, - ) -> anyhow::Result> { - self.get_key_by_value("spawn_fingerprint_cache", spawn_fingerprint).await + cache_key: &CacheEntryKey, + ) -> anyhow::Result> { + self.get_key_by_value("cache_entries", cache_key).await } - async fn get_fingerprint_by_execution_key( + async fn get_cache_key_by_execution_key( &self, execution_cache_key: &ExecutionCacheKey, - ) -> anyhow::Result> { - self.get_key_by_value("execution_key_to_fingerprint", execution_cache_key).await + ) -> anyhow::Result> { + self.get_key_by_value("task_fingerprints", execution_cache_key).await } #[expect( @@ -266,20 +360,20 @@ impl ExecutionCache { Ok(()) } - async fn upsert_spawn_fingerprint_cache( + async fn upsert_cache_entry( &self, - spawn_fingerprint: &SpawnFingerprint, - cache_value: &CommandCacheValue, + cache_key: &CacheEntryKey, + cache_value: &CacheEntryValue, ) -> anyhow::Result<()> { - self.upsert("spawn_fingerprint_cache", spawn_fingerprint, cache_value).await + self.upsert("cache_entries", cache_key, cache_value).await } - async fn upsert_execution_key_to_fingerprint( + async fn upsert_task_fingerprint( &self, execution_cache_key: &ExecutionCacheKey, - spawn_fingerprint: &SpawnFingerprint, + cache_entry_key: &CacheEntryKey, ) -> anyhow::Result<()> { - self.upsert("execution_key_to_fingerprint", execution_cache_key, spawn_fingerprint).await + self.upsert("task_fingerprints", execution_cache_key, cache_entry_key).await } #[expect( @@ -314,15 +408,10 @@ impl ExecutionCache { } pub async fn list(&self, mut out: impl Write) -> anyhow::Result<()> { - out.write_all(b"------- execution_key_to_fingerprint -------\n")?; - self.list_table::( - "execution_key_to_fingerprint", - &mut out, - ) - .await?; - out.write_all(b"------- spawn_fingerprint_cache -------\n")?; - self.list_table::("spawn_fingerprint_cache", &mut out) - .await?; + out.write_all(b"------- task_fingerprints -------\n")?; + self.list_table::("task_fingerprints", &mut out).await?; + out.write_all(b"------- cache_entries -------\n")?; + self.list_table::("cache_entries", &mut out).await?; Ok(()) } } diff --git a/crates/vite_task/src/session/execute/fingerprint.rs b/crates/vite_task/src/session/execute/fingerprint.rs index ccf81801..bbb28b9e 100644 --- a/crates/vite_task/src/session/execute/fingerprint.rs +++ b/crates/vite_task/src/session/execute/fingerprint.rs @@ -13,7 +13,6 @@ use std::{ use bincode::{Decode, Encode}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; -use vite_glob::GlobPatternSet; use vite_path::{AbsolutePath, RelativePathBuf}; use vite_str::Str; @@ -24,8 +23,9 @@ use crate::collections::HashMap; /// Used to validate whether cached outputs are still valid. #[derive(Encode, Decode, Debug, Serialize)] pub struct PostRunFingerprint { - /// Paths accessed during execution with their content fingerprints - pub inputs: HashMap, + /// Paths inferred from fspy during execution with their content fingerprints. + /// Only populated when `input_config.includes_auto` is true. + pub inferred_inputs: HashMap, } /// Fingerprint for a single path (file or directory) @@ -69,28 +69,20 @@ impl std::fmt::Display for PostRunFingerprintMismatch { impl PostRunFingerprint { /// Creates a new fingerprint from path accesses after task execution. /// + /// Negative glob filtering is done upstream in `spawn_with_tracking`. + /// Paths may contain `..` components from fspy, so this method cleans them + /// before fingerprinting. + /// /// # Arguments - /// * `path_reads` - Map of paths that were read during execution + /// * `inferred_path_reads` - Map of paths that were read during execution (from fspy) /// * `base_dir` - Workspace root for resolving relative paths - /// * `fingerprint_ignores` - Optional glob patterns to exclude from fingerprinting #[tracing::instrument(level = "debug", skip_all, name = "create_post_run_fingerprint")] pub fn create( - path_reads: &HashMap, + inferred_path_reads: &HashMap, base_dir: &AbsolutePath, - fingerprint_ignores: Option<&[Str]>, ) -> anyhow::Result { - // Build ignore matcher from patterns if provided - let ignore_matcher = fingerprint_ignores - .filter(|patterns| !patterns.is_empty()) - .map(GlobPatternSet::new) - .transpose()?; - - let inputs = path_reads + let inferred_inputs = inferred_path_reads .par_iter() - .filter(|(path, _)| { - // Apply ignore patterns if present - ignore_matcher.as_ref().is_none_or(|matcher| !matcher.is_match(path.as_str())) - }) .map(|(relative_path, path_read)| { let full_path = Arc::::from(base_dir.join(relative_path)); let fingerprint = fingerprint_path(&full_path, *path_read)?; @@ -98,7 +90,7 @@ impl PostRunFingerprint { }) .collect::>>()?; - Ok(Self { inputs }) + Ok(Self { inferred_inputs }) } /// Validates the fingerprint against current filesystem state. @@ -108,8 +100,8 @@ impl PostRunFingerprint { &self, base_dir: &AbsolutePath, ) -> anyhow::Result> { - let input_mismatch = - self.inputs.par_iter().find_map_any(|(input_relative_path, path_fingerprint)| { + let input_mismatch = self.inferred_inputs.par_iter().find_map_any( + |(input_relative_path, path_fingerprint)| { let input_full_path = Arc::::from(base_dir.join(input_relative_path)); let path_read = PathRead { read_dir_entries: matches!(path_fingerprint, PathFingerprint::Folder(Some(_))), @@ -125,7 +117,8 @@ impl PostRunFingerprint { path: input_relative_path.clone(), })) } - }); + }, + ); input_mismatch.transpose() } } diff --git a/crates/vite_task/src/session/execute/glob_inputs.rs b/crates/vite_task/src/session/execute/glob_inputs.rs new file mode 100644 index 00000000..c68f3bd0 --- /dev/null +++ b/crates/vite_task/src/session/execute/glob_inputs.rs @@ -0,0 +1,446 @@ +//! Glob-based input file discovery and fingerprinting. +//! +//! This module provides functions to walk glob patterns and compute file hashes +//! for cache invalidation based on explicit input patterns. +//! +//! All glob patterns are workspace-root-relative (resolved at task graph stage). + +use std::{ + collections::BTreeMap, + fs::File, + hash::Hasher as _, + io::{self, Read}, +}; + +#[cfg(test)] +use vite_path::AbsolutePathBuf; +use vite_path::{AbsolutePath, RelativePathBuf}; +use vite_str::Str; +use wax::{ + Glob, + walk::{Entry as _, FileIterator as _}, +}; + +/// Collect walk entries into the result map. +/// +/// Walk errors for non-existent directories are skipped gracefully. +fn collect_walk_entries( + walk: impl Iterator>, + workspace_root: &AbsolutePath, + result: &mut BTreeMap, +) -> anyhow::Result<()> { + for entry in walk { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + // WalkError -> io::Error preserves the error kind + let io_err: io::Error = err.into(); + if io_err.kind() == io::ErrorKind::NotFound { + continue; + } + return Err(io_err.into()); + } + }; + if !entry.file_type().is_file() { + continue; + } + + let path = entry.path(); + + // Compute path relative to workspace_root for the result + let Some(stripped) = path.strip_prefix(workspace_root.as_path()).ok() else { + continue; // Skip if path is outside workspace_root + }; + let relative_to_workspace = RelativePathBuf::new(stripped)?; + + let std::collections::btree_map::Entry::Vacant(vacant) = + result.entry(relative_to_workspace) + else { + continue; // Already hashed by a previous glob pattern + }; + + // Hash file content + match hash_file_content(path) { + Ok(hash) => { + vacant.insert(hash); + } + Err(err) if err.kind() == io::ErrorKind::NotFound => { + // File was deleted between walk and hash, skip it + } + Err(err) => { + return Err(err.into()); + } + } + } + Ok(()) +} + +/// Compute globbed inputs by walking positive glob patterns and filtering with negative patterns. +/// +/// All globs are workspace-root-relative (resolved at task graph stage). +/// Positive globs are walked from `workspace_root`, and negative globs filter the results. +/// +/// # Arguments +/// * `workspace_root` - The workspace root (globs are relative to this) +/// * `positive_globs` - Workspace-root-relative glob patterns for files to include +/// * `negative_globs` - Workspace-root-relative glob patterns for files to exclude +/// +/// # Returns +/// A sorted map of relative paths (from `workspace_root`) to their content hashes. +/// Only files are included (directories are skipped). +pub fn compute_globbed_inputs( + workspace_root: &AbsolutePath, + positive_globs: &std::collections::BTreeSet, + negative_globs: &std::collections::BTreeSet, +) -> anyhow::Result> { + if positive_globs.is_empty() { + return Ok(BTreeMap::new()); + } + + let negatives: Vec> = negative_globs + .iter() + .map(|p| Ok(Glob::new(p.as_str())?.into_owned())) + .collect::>()?; + let negation = wax::any(negatives)?; + + let mut result = BTreeMap::new(); + + for pattern in positive_globs { + let glob = Glob::new(pattern.as_str())?.into_owned(); + let walk = glob.walk(workspace_root.as_path()); + collect_walk_entries(walk.not(negation.clone())?, workspace_root, &mut result)?; + } + + Ok(result) +} + +/// Hash file content using `xxHash3_64`. +#[expect(clippy::disallowed_types, reason = "receives std::path::Path from wax glob walker")] +fn hash_file_content(path: &std::path::Path) -> io::Result { + let file = File::open(path)?; + let mut reader = io::BufReader::new(file); + let mut hasher = twox_hash::XxHash3_64::default(); + let mut buf = [0u8; 8192]; + loop { + let n = reader.read(&mut buf)?; + if n == 0 { + break; + } + hasher.write(&buf[..n]); + } + Ok(hasher.finish()) +} + +#[cfg(test)] +mod tests { + use std::fs; + + use tempfile::TempDir; + + use super::*; + + fn create_test_workspace() -> (TempDir, AbsolutePathBuf) { + let temp_dir = TempDir::new().unwrap(); + let workspace_root = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Create package directory structure + let package_dir = workspace_root.join("packages/my-pkg"); + fs::create_dir_all(&package_dir).unwrap(); + + // Create source files + fs::create_dir_all(package_dir.join("src")).unwrap(); + fs::write(package_dir.join("src/index.ts"), "export const a = 1;").unwrap(); + fs::write(package_dir.join("src/utils.ts"), "export const b = 2;").unwrap(); + fs::write(package_dir.join("src/utils.test.ts"), "test('a', () => {});").unwrap(); + + // Create nested directory + fs::create_dir_all(package_dir.join("src/lib")).unwrap(); + fs::write(package_dir.join("src/lib/helper.ts"), "export const c = 3;").unwrap(); + fs::write(package_dir.join("src/lib/helper.test.ts"), "test('c', () => {});").unwrap(); + + // Create other files + fs::write(package_dir.join("package.json"), "{}").unwrap(); + fs::write(package_dir.join("README.md"), "# Readme").unwrap(); + + (temp_dir, workspace_root) + } + + #[test] + fn test_empty_positive_globs_returns_empty() { + let (_temp, workspace) = create_test_workspace(); + let positive = std::collections::BTreeSet::new(); + let negative = std::collections::BTreeSet::new(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + assert!(result.is_empty()); + } + + #[test] + fn test_single_positive_glob() { + let (_temp, workspace) = create_test_workspace(); + // Globs are now workspace-root-relative + let positive: std::collections::BTreeSet = + std::iter::once("packages/my-pkg/src/**/*.ts".into()).collect(); + let negative = std::collections::BTreeSet::new(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + + // Should match all .ts files in src/ + assert_eq!(result.len(), 5); + assert!( + result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()) + ); + assert!( + result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.ts").unwrap()) + ); + assert!( + result + .contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.test.ts").unwrap()) + ); + assert!( + result + .contains_key(&RelativePathBuf::new("packages/my-pkg/src/lib/helper.ts").unwrap()) + ); + assert!(result.contains_key( + &RelativePathBuf::new("packages/my-pkg/src/lib/helper.test.ts").unwrap() + )); + } + + #[test] + fn test_positive_with_negative_exclusion() { + let (_temp, workspace) = create_test_workspace(); + let positive: std::collections::BTreeSet = + std::iter::once("packages/my-pkg/src/**/*.ts".into()).collect(); + let negative: std::collections::BTreeSet = + std::iter::once("**/*.test.ts".into()).collect(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + + // Should match only non-test .ts files + assert_eq!(result.len(), 3); + assert!( + result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()) + ); + assert!( + result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.ts").unwrap()) + ); + assert!( + result + .contains_key(&RelativePathBuf::new("packages/my-pkg/src/lib/helper.ts").unwrap()) + ); + // Test files should be excluded + assert!( + !result + .contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.test.ts").unwrap()) + ); + assert!(!result.contains_key( + &RelativePathBuf::new("packages/my-pkg/src/lib/helper.test.ts").unwrap() + )); + } + + #[test] + fn test_multiple_positive_globs() { + let (_temp, workspace) = create_test_workspace(); + let positive: std::collections::BTreeSet = + ["packages/my-pkg/src/**/*.ts".into(), "packages/my-pkg/package.json".into()] + .into_iter() + .collect(); + let negative: std::collections::BTreeSet = + std::iter::once("**/*.test.ts".into()).collect(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + + // Should include .ts files (excluding tests) plus package.json + assert_eq!(result.len(), 4); + assert!( + result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()) + ); + assert!( + result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.ts").unwrap()) + ); + assert!( + result + .contains_key(&RelativePathBuf::new("packages/my-pkg/src/lib/helper.ts").unwrap()) + ); + assert!( + result.contains_key(&RelativePathBuf::new("packages/my-pkg/package.json").unwrap()) + ); + } + + #[test] + fn test_multiple_negative_globs() { + let (_temp, workspace) = create_test_workspace(); + let positive: std::collections::BTreeSet = + ["packages/my-pkg/src/**/*.ts".into(), "packages/my-pkg/*.md".into()] + .into_iter() + .collect(); + let negative: std::collections::BTreeSet = + ["**/*.test.ts".into(), "**/*.md".into()].into_iter().collect(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + + // Should exclude both test files and markdown files + assert_eq!(result.len(), 3); + assert!( + result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()) + ); + assert!( + result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.ts").unwrap()) + ); + assert!( + result + .contains_key(&RelativePathBuf::new("packages/my-pkg/src/lib/helper.ts").unwrap()) + ); + assert!(!result.contains_key(&RelativePathBuf::new("packages/my-pkg/README.md").unwrap())); + } + + #[test] + fn test_negative_only_returns_empty() { + let (_temp, workspace) = create_test_workspace(); + let positive: std::collections::BTreeSet = std::collections::BTreeSet::new(); + let negative: std::collections::BTreeSet = + std::iter::once("**/*.test.ts".into()).collect(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + + // No positive globs means empty result (negative globs alone don't select anything) + assert!(result.is_empty()); + } + + #[test] + fn test_file_hashes_are_consistent() { + let (_temp, workspace) = create_test_workspace(); + let positive: std::collections::BTreeSet = + std::iter::once("packages/my-pkg/src/index.ts".into()).collect(); + let negative = std::collections::BTreeSet::new(); + + // Run twice and compare hashes + let result1 = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + let result2 = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + + assert_eq!(result1, result2); + } + + #[test] + fn test_file_hashes_change_with_content() { + let (temp, workspace) = create_test_workspace(); + let positive: std::collections::BTreeSet = + std::iter::once("packages/my-pkg/src/index.ts".into()).collect(); + let negative = std::collections::BTreeSet::new(); + + // Get initial hash + let result1 = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + let hash1 = + result1.get(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()).unwrap(); + + // Modify file content + let file_path = temp.path().join("packages/my-pkg/src/index.ts"); + fs::write(&file_path, "export const a = 999;").unwrap(); + + // Get new hash + let result2 = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + let hash2 = + result2.get(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()).unwrap(); + + assert_ne!(hash1, hash2); + } + + #[test] + fn test_skips_directories() { + let (_temp, workspace) = create_test_workspace(); + // This glob could match the `src/lib` directory if not filtered + let positive: std::collections::BTreeSet = + std::iter::once("packages/my-pkg/src/*".into()).collect(); + let negative = std::collections::BTreeSet::new(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + + // Should only have files, not directories + for path in result.keys() { + assert!(!path.as_str().ends_with("/lib")); + assert!(!path.as_str().ends_with("\\lib")); + } + } + + #[test] + fn test_no_matching_files_returns_empty() { + let (_temp, workspace) = create_test_workspace(); + let positive: std::collections::BTreeSet = + std::iter::once("packages/my-pkg/nonexistent/**/*.xyz".into()).collect(); + let negative = std::collections::BTreeSet::new(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + assert!(result.is_empty()); + } + + /// Creates a workspace with sibling packages for testing cross-package globs + fn create_workspace_with_sibling() -> (TempDir, AbsolutePathBuf) { + let temp_dir = TempDir::new().unwrap(); + let workspace_root = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Create sub-pkg + let sub_pkg = workspace_root.join("packages/sub-pkg"); + fs::create_dir_all(sub_pkg.join("src")).unwrap(); + fs::write(sub_pkg.join("src/main.ts"), "export const sub = 1;").unwrap(); + + // Create sibling shared package + let shared = workspace_root.join("packages/shared"); + fs::create_dir_all(shared.join("src")).unwrap(); + fs::create_dir_all(shared.join("dist")).unwrap(); + fs::write(shared.join("src/utils.ts"), "export const shared = 1;").unwrap(); + fs::write(shared.join("dist/output.js"), "// output").unwrap(); + + (temp_dir, workspace_root) + } + + #[test] + fn test_sibling_positive_glob_matches_sibling_package() { + let (_temp, workspace) = create_workspace_with_sibling(); + // Globs are already workspace-root-relative (resolved at task graph stage) + let positive: std::collections::BTreeSet = + std::iter::once("packages/shared/src/**".into()).collect(); + let negative = std::collections::BTreeSet::new(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + assert!( + result.contains_key(&RelativePathBuf::new("packages/shared/src/utils.ts").unwrap()), + "should find sibling package file via packages/shared/src/**" + ); + } + + #[test] + fn test_sibling_negative_glob_excludes_from_sibling() { + let (_temp, workspace) = create_workspace_with_sibling(); + let positive: std::collections::BTreeSet = + std::iter::once("packages/shared/**".into()).collect(); + let negative: std::collections::BTreeSet = + std::iter::once("packages/shared/dist/**".into()).collect(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + assert!( + result.contains_key(&RelativePathBuf::new("packages/shared/src/utils.ts").unwrap()), + "should include non-excluded sibling file" + ); + assert!( + !result.contains_key(&RelativePathBuf::new("packages/shared/dist/output.js").unwrap()), + "should exclude dist via packages/shared/dist/**" + ); + } + + #[test] + fn test_overlapping_positive_globs_deduplicates() { + let (_temp, workspace) = create_test_workspace(); + // Both patterns match src/index.ts + let positive: std::collections::BTreeSet = + ["packages/my-pkg/src/**/*.ts".into(), "packages/my-pkg/src/index.ts".into()] + .into_iter() + .collect(); + let negative: std::collections::BTreeSet = + std::iter::once("**/*.test.ts".into()).collect(); + + let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); + + // BTreeMap naturally deduplicates by key + assert_eq!(result.len(), 3); + } +} diff --git a/crates/vite_task/src/session/execute/mod.rs b/crates/vite_task/src/session/execute/mod.rs index 10467e22..9ec9cbe3 100644 --- a/crates/vite_task/src/session/execute/mod.rs +++ b/crates/vite_task/src/session/execute/mod.rs @@ -1,7 +1,8 @@ pub mod fingerprint; +pub mod glob_inputs; pub mod spawn; -use std::{process::Stdio, sync::Arc}; +use std::{collections::BTreeMap, process::Stdio, sync::Arc}; use futures_util::FutureExt; use tokio::io::AsyncWriteExt as _; @@ -13,10 +14,11 @@ use vite_task_plan::{ use self::{ fingerprint::PostRunFingerprint, - spawn::{SpawnResult, spawn_with_tracking}, + glob_inputs::compute_globbed_inputs, + spawn::{SpawnResult, TrackedPathAccesses, spawn_with_tracking}, }; use super::{ - cache::{CommandCacheValue, ExecutionCache}, + cache::{CacheEntryValue, ExecutionCache}, event::{ CacheDisabledReason, CacheErrorKind, CacheNotUpdatedReason, CacheStatus, CacheUpdateStatus, ExecutionError, @@ -26,7 +28,7 @@ use super::{ StdioSuggestion, }, }; -use crate::{Session, session::execute::spawn::SpawnTrackResult}; +use crate::{Session, collections::HashMap}; /// Outcome of a spawned execution. /// @@ -193,17 +195,40 @@ pub async fn execute_spawn( // 1. Determine cache status FIRST by trying cache hit. // We need to know the status before calling start() so the reporter // can display cache status immediately when execution begins. - let (cache_status, cached_value) = if let Some(cache_metadata) = cache_metadata { - match cache.try_hit(cache_metadata, cache_base_path).await { + let (cache_status, cached_value, globbed_inputs) = if let Some(cache_metadata) = cache_metadata + { + // Compute globbed inputs from positive globs at execution time + // Globs are already workspace-root-relative (resolved at task graph stage) + let globbed_inputs = match compute_globbed_inputs( + cache_base_path, + &cache_metadata.input_config.positive_globs, + &cache_metadata.input_config.negative_globs, + ) { + Ok(inputs) => inputs, + Err(err) => { + leaf_reporter + .finish( + None, + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), + Some(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }), + ) + .await; + return SpawnOutcome::Failed; + } + }; + + match cache.try_hit(cache_metadata, &globbed_inputs, cache_base_path).await { Ok(Ok(cached)) => ( // Cache hit — we can replay the cached outputs CacheStatus::Hit { replayed_duration: cached.duration }, Some(cached), + globbed_inputs, ), Ok(Err(cache_miss)) => ( // Cache miss — includes detailed reason (NotFound or FingerprintMismatch) CacheStatus::Miss(cache_miss), None, + globbed_inputs, ), Err(err) => { // Cache lookup error — report through finish. @@ -220,7 +245,7 @@ pub async fn execute_spawn( } } else { // No cache metadata provided — caching is disabled for this task - (CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata), None) + (CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata), None, BTreeMap::new()) }; // 2. Report execution start with the determined cache status. @@ -283,8 +308,43 @@ pub async fn execute_spawn( } // 5. Piped mode: execute spawn with tracking, streaming output to writers. - let mut track_result_with_cache_metadata = - cache_metadata.map(|cache_metadata| (SpawnTrackResult::default(), cache_metadata)); + // - std_outputs: always captured when caching is enabled (for cache replay) + // - path_accesses: only tracked when includes_auto is true (fspy inference) + let (mut std_outputs, mut path_accesses, cache_metadata_and_inputs) = + cache_metadata.map_or((None, None, None), |cache_metadata| { + let path_accesses = if cache_metadata.input_config.includes_auto { + Some(TrackedPathAccesses::default()) + } else { + None // Skip fspy when inference is disabled + }; + (Some(Vec::new()), path_accesses, Some((cache_metadata, globbed_inputs))) + }); + + // Build negative globs for fspy path filtering (already workspace-root-relative) + let resolved_negatives: Vec> = + if let Some((cache_metadata, _)) = &cache_metadata_and_inputs { + match cache_metadata + .input_config + .negative_globs + .iter() + .map(|p| Ok(wax::Glob::new(p.as_str())?.into_owned())) + .collect::>>() + { + Ok(negs) => negs, + Err(err) => { + leaf_reporter + .finish( + None, + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), + Some(ExecutionError::PostRunFingerprint(err)), + ) + .await; + return SpawnOutcome::Failed; + } + } + } else { + Vec::new() + }; #[expect( clippy::large_futures, @@ -295,7 +355,9 @@ pub async fn execute_spawn( cache_base_path, &mut stdio_config.stdout_writer, &mut stdio_config.stderr_writer, - track_result_with_cache_metadata.as_mut().map(|(track_result, _)| track_result), + std_outputs.as_mut(), + path_accesses.as_mut(), + &resolved_negatives, ) .await { @@ -314,23 +376,22 @@ pub async fn execute_spawn( // 6. Update cache if successful and determine cache update status. // Errors during cache update are terminal (reported through finish). - let (cache_update_status, cache_error) = if let Some((track_result, cache_metadata)) = - track_result_with_cache_metadata + let (cache_update_status, cache_error) = if let Some((cache_metadata, globbed_inputs)) = + cache_metadata_and_inputs { if result.exit_status.success() { + // path_reads is empty when inference is disabled (path_accesses is None) + let empty_path_reads = HashMap::default(); + let path_reads = path_accesses.as_ref().map_or(&empty_path_reads, |pa| &pa.path_reads); + // Execution succeeded — attempt to create fingerprint and update cache - let fingerprint_ignores = - cache_metadata.spawn_fingerprint.fingerprint_ignores().map(std::vec::Vec::as_slice); - match PostRunFingerprint::create( - &track_result.path_reads, - cache_base_path, - fingerprint_ignores, - ) { + match PostRunFingerprint::create(path_reads, cache_base_path) { Ok(post_run_fingerprint) => { - let new_cache_value = CommandCacheValue { + let new_cache_value = CacheEntryValue { post_run_fingerprint, - std_outputs: track_result.std_outputs.clone().into(), + std_outputs: std_outputs.unwrap_or_default().into(), duration: result.duration, + globbed_inputs, }; match cache.update(cache_metadata, new_cache_value).await { Ok(()) => (CacheUpdateStatus::Updated, None), diff --git a/crates/vite_task/src/session/execute/spawn.rs b/crates/vite_task/src/session/execute/spawn.rs index 33b8d2c7..7c78d621 100644 --- a/crates/vite_task/src/session/execute/spawn.rs +++ b/crates/vite_task/src/session/execute/spawn.rs @@ -13,6 +13,7 @@ use serde::Serialize; use tokio::io::{AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _}; use vite_path::{AbsolutePath, RelativePathBuf}; use vite_task_plan::SpawnCommand; +use wax::Program as _; use crate::collections::HashMap; @@ -43,12 +44,10 @@ pub struct SpawnResult { pub duration: Duration, } -/// Tracking result from a spawned process for caching +/// Tracked file accesses from fspy. +/// Only populated when fspy tracking is enabled (`includes_auto` is true). #[derive(Default, Debug)] -pub struct SpawnTrackResult { - /// captured stdout/stderr - pub std_outputs: Vec, - +pub struct TrackedPathAccesses { /// Tracked path reads pub path_reads: HashMap, @@ -56,14 +55,15 @@ pub struct SpawnTrackResult { pub path_writes: FxHashSet, } -/// Spawn a command with file system tracking via fspy, using piped stdio. +/// Spawn a command with optional file system tracking via fspy, using piped stdio. /// -/// Returns the execution result including captured outputs, exit status, -/// and tracked file accesses. +/// Returns the execution result including exit status and duration. /// /// - stdin is always `/dev/null` (piped mode is for non-interactive execution). /// - `stdout_writer`/`stderr_writer` receive the child's stdout/stderr output in real-time. -/// - `track_result` if provided, will be populated with captured outputs and path accesses for caching. If `None`, tracking is disabled. +/// - `std_outputs` if provided, will be populated with captured outputs for cache replay. +/// - `path_accesses` if provided, fspy will be used to track file accesses. If `None`, fspy is disabled. +/// - `resolved_negatives` - resolved negative glob patterns for filtering fspy-tracked paths. #[tracing::instrument(level = "debug", skip_all)] #[expect(clippy::future_not_send, reason = "uses !Send dyn AsyncWrite writers internally")] #[expect( @@ -75,15 +75,18 @@ pub async fn spawn_with_tracking( workspace_root: &AbsolutePath, stdout_writer: &mut (dyn AsyncWrite + Unpin), stderr_writer: &mut (dyn AsyncWrite + Unpin), - track_result: Option<&mut SpawnTrackResult>, + std_outputs: Option<&mut Vec>, + path_accesses: Option<&mut TrackedPathAccesses>, + resolved_negatives: &[wax::Glob<'static>], ) -> anyhow::Result { - /// The tracking state of the spawned process - enum TrackingState<'a> { - /// Tacking is enabled, with the tracked child and result reference - Enabled(fspy::TrackedChild, &'a mut SpawnTrackResult), - - /// Tracking is disabled, with the tokio child process - Disabled(tokio::process::Child), + /// The tracking state of the spawned process. + /// Determined by whether `path_accesses` is `Some` (fspy enabled) or `None` (fspy disabled). + enum TrackingState { + /// fspy tracking is enabled + FspyEnabled(fspy::TrackedChild), + + /// fspy tracking is disabled, using plain tokio process + FspyDisabled(tokio::process::Child), } let mut cmd = fspy::Command::new(spawn_command.program_path.as_path()); @@ -92,27 +95,25 @@ pub async fn spawn_with_tracking( cmd.current_dir(&*spawn_command.cwd); cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped()); - let mut tracking_state = if let Some(track_result) = track_result { - // track_result is Some. Spawn with tracking enabled - TrackingState::Enabled(cmd.spawn().await?, track_result) + let mut tracking_state = if path_accesses.is_some() { + // path_accesses is Some, spawn with fspy tracking enabled + TrackingState::FspyEnabled(cmd.spawn().await?) } else { - // Spawn without tracking - TrackingState::Disabled(cmd.into_tokio_command().spawn()?) + // path_accesses is None, spawn without fspy + TrackingState::FspyDisabled(cmd.into_tokio_command().spawn()?) }; let mut child_stdout = match &mut tracking_state { - TrackingState::Enabled(tracked_child, _) => tracked_child.stdout.take().unwrap(), - TrackingState::Disabled(tokio_child) => tokio_child.stdout.take().unwrap(), + TrackingState::FspyEnabled(tracked_child) => tracked_child.stdout.take().unwrap(), + TrackingState::FspyDisabled(tokio_child) => tokio_child.stdout.take().unwrap(), }; let mut child_stderr = match &mut tracking_state { - TrackingState::Enabled(tracked_child, _) => tracked_child.stderr.take().unwrap(), - TrackingState::Disabled(tokio_child) => tokio_child.stderr.take().unwrap(), + TrackingState::FspyEnabled(tracked_child) => tracked_child.stderr.take().unwrap(), + TrackingState::FspyDisabled(tokio_child) => tokio_child.stderr.take().unwrap(), }; - let mut outputs = match &mut tracking_state { - TrackingState::Enabled(_, track_result) => Some(&mut track_result.std_outputs), - TrackingState::Disabled(_) => None, - }; + // Output capturing is independent of fspy tracking + let mut outputs = std_outputs; let mut stdout_buf = [0u8; 8192]; let mut stderr_buf = [0u8; 8192]; let mut stdout_done = false; @@ -169,24 +170,28 @@ pub async fn spawn_with_tracking( } } - let (termination, track_result) = match tracking_state { - TrackingState::Enabled(tracked_child, track_result) => { - (tracked_child.wait_handle.await?, track_result) + // Wait for process termination and process path accesses if fspy was enabled + let (termination, path_accesses) = match tracking_state { + TrackingState::FspyEnabled(tracked_child) => { + let termination = tracked_child.wait_handle.await?; + // path_accesses must be Some when fspy is enabled (they're set together) + let path_accesses = path_accesses.ok_or_else(|| { + anyhow::anyhow!("internal error: fspy enabled but path_accesses is None") + })?; + (termination, path_accesses) } - TrackingState::Disabled(mut tokio_child) => { - return Ok(SpawnResult { - exit_status: tokio_child.wait().await?, - duration: start.elapsed(), - }); + TrackingState::FspyDisabled(mut tokio_child) => { + let exit_status = tokio_child.wait().await?; + return Ok(SpawnResult { exit_status, duration: start.elapsed() }); } }; let duration = start.elapsed(); - - // Process path accesses - let path_reads = &mut track_result.path_reads; - let path_writes = &mut track_result.path_writes; + let path_reads = &mut path_accesses.path_reads; + let path_writes = &mut path_accesses.path_writes; for access in termination.path_accesses.iter() { + // Strip workspace root, clean `..` components, and filter in one pass. + // fspy may report paths like `packages/sub-pkg/../shared/dist/output.js`. let relative_path = access.path.strip_path_prefix(workspace_root, |strip_result| { let Ok(stripped_path) = strip_result else { return None; @@ -194,19 +199,31 @@ pub async fn spawn_with_tracking( // On Windows, paths are possible to be still absolute after stripping the workspace root. // For example: c:\workspace\subdir\c:\workspace\subdir // Just ignore those accesses. - RelativePathBuf::new(stripped_path).ok() + let relative = RelativePathBuf::new(stripped_path).ok()?; + + // Clean `..` components — fspy may report paths like + // `packages/sub-pkg/../shared/dist/output.js`. Normalize them for + // consistent behavior across platforms and clean user-facing messages. + let relative = relative.clean(); + + // Skip .git directory accesses (workaround for tools like oxlint) + if relative.as_path().strip_prefix(".git").is_ok() { + return None; + } + + if !resolved_negatives.is_empty() + && resolved_negatives.iter().any(|neg| neg.is_match(relative.as_str())) + { + return None; + } + + Some(relative) }); let Some(relative_path) = relative_path else { - // Ignore accesses outside the workspace continue; }; - // Skip .git directory accesses (workaround for tools like oxlint) - if relative_path.as_path().strip_prefix(".git").is_ok() { - continue; - } - if access.mode.contains(AccessMode::READ) { path_reads.entry(relative_path.clone()).or_insert(PathRead { read_dir_entries: false }); } diff --git a/crates/vite_task/src/session/reporter/summary.rs b/crates/vite_task/src/session/reporter/summary.rs index 8178bd2b..39f5f9e5 100644 --- a/crates/vite_task/src/session/reporter/summary.rs +++ b/crates/vite_task/src/session/reporter/summary.rs @@ -112,6 +112,8 @@ pub enum SavedCacheMissReason { NotFound, /// Spawn fingerprint changed (command, envs, cwd, etc.). SpawnFingerprintChanged(Vec), + /// Task configuration changed (`input_config` or `glob_base`). + ConfigChanged, /// Content of an input file changed. InputContentChanged { path: Str }, } @@ -235,10 +237,14 @@ impl SavedCacheMissReason { match cache_miss { CacheMiss::NotFound => Self::NotFound, CacheMiss::FingerprintMismatch(mismatch) => match mismatch { - FingerprintMismatch::SpawnFingerprintMismatch { old, new } => { + FingerprintMismatch::SpawnFingerprint { old, new } => { Self::SpawnFingerprintChanged(detect_spawn_fingerprint_changes(old, new)) } - FingerprintMismatch::PostRunFingerprintMismatch(diff) => { + FingerprintMismatch::InputConfig => Self::ConfigChanged, + FingerprintMismatch::GlobbedInput { path } => { + Self::InputContentChanged { path: Str::from(path.as_str()) } + } + FingerprintMismatch::PostRunFingerprint(diff) => { use crate::session::execute::fingerprint::PostRunFingerprintMismatch; match diff { PostRunFingerprintMismatch::InputContentChanged { path } => { @@ -425,6 +431,9 @@ impl TaskResult { vite_str::format!("→ Cache miss: {joined}") } } + SavedCacheMissReason::ConfigChanged => { + Str::from("→ Cache miss: inputs configuration changed") + } SavedCacheMissReason::InputContentChanged { path } => { vite_str::format!("→ Cache miss: content of input '{path}' changed") } diff --git a/crates/vite_task_bin/src/lib.rs b/crates/vite_task_bin/src/lib.rs index c2560653..5b6c3adc 100644 --- a/crates/vite_task_bin/src/lib.rs +++ b/crates/vite_task_bin/src/lib.rs @@ -64,6 +64,7 @@ fn synthesize_node_modules_bin_task( cache_config: UserCacheConfig::with_config(EnabledCacheConfig { envs: None, pass_through_envs: None, + inputs: None, }), envs: Arc::clone(envs), }) @@ -125,7 +126,11 @@ impl vite_task::CommandHandler for CommandHandler { program: find_executable(get_path_env(&envs), &command.cwd, "print-env")?, args: [name.clone()].into(), cache_config: UserCacheConfig::with_config({ - EnabledCacheConfig { envs: None, pass_through_envs: Some(vec![name]) } + EnabledCacheConfig { + envs: None, + pass_through_envs: Some(vec![name]), + inputs: None, + } }), envs: Arc::new(envs), })) diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs index b4a1320b..2dfdd252 100644 --- a/crates/vite_task_bin/src/main.rs +++ b/crates/vite_task_bin/src/main.rs @@ -35,6 +35,7 @@ async fn run() -> anyhow::Result { EnabledCacheConfig { envs: Some(Box::from([Str::from("FOO")])), pass_through_envs: None, + inputs: None, } }), envs: Arc::clone(envs), diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots.toml index 150a6812..ead59c25 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots.toml +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots.toml @@ -55,3 +55,11 @@ steps = [ "replace-file-content test.txt initial modified # modify input", "vp run test # cache miss: input changed", ] + +[[e2e]] +name = "inputs config changed" +steps = [ + "vp run test # cache miss", + "json-edit vite-task.json \"_.tasks.test.inputs = ['test.txt']\" # change inputs config", + "vp run test # cache miss: configuration changed", +] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/inputs config changed.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/inputs config changed.snap new file mode 100644 index 00000000..fe83d450 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/inputs config changed.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run test # cache miss +$ print-file test.txt +initial content +> json-edit vite-task.json "_.tasks.test.inputs = ['test.txt']" # change inputs config + +> vp run test # cache miss: configuration changed +$ print-file test.txt ✗ cache miss: inputs configuration changed, executing +initial content diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/other/other.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/other/other.ts new file mode 100644 index 00000000..95b7e318 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/other/other.ts @@ -0,0 +1 @@ +export const rootOther = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/package.json new file mode 100644 index 00000000..62b9c6f6 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/package.json @@ -0,0 +1,4 @@ +{ + "name": "glob-base-test", + "private": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/other/other.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/other/other.ts new file mode 100644 index 00000000..bba04996 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/other/other.ts @@ -0,0 +1 @@ +export const other = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/package.json new file mode 100644 index 00000000..e0caa109 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/package.json @@ -0,0 +1,4 @@ +{ + "name": "sub-pkg", + "private": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/src/sub.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/src/sub.ts new file mode 100644 index 00000000..27a97907 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/src/sub.ts @@ -0,0 +1 @@ +export const sub = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/vite-task.json new file mode 100644 index 00000000..2343b213 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/packages/sub-pkg/vite-task.json @@ -0,0 +1,15 @@ +{ + "tasks": { + "sub-glob-test": { + "command": "print-file src/sub.ts", + "inputs": ["src/**/*.ts"], + "cache": true + }, + "sub-glob-with-cwd": { + "command": "print-file sub.ts", + "cwd": "src", + "inputs": ["src/**/*.ts"], + "cache": true + } + } +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/pnpm-workspace.yaml new file mode 100644 index 00000000..18ec407e --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots.toml new file mode 100644 index 00000000..3cb33454 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots.toml @@ -0,0 +1,87 @@ +# Test glob base directory behavior +# Globs are relative to PACKAGE directory, NOT task cwd +# No special cross-package filtering - just normal relative path matching + +# 1. Root package - glob matches files in root's src/ +[[e2e]] +name = "root glob - matches src files" +steps = [ + "vp run root-glob-test", + # Modify matched file + "replace-file-content src/root.ts initial modified", + # Cache miss: file matches glob + "vp run root-glob-test", +] + +[[e2e]] +name = "root glob - unmatched directory" +steps = [ + "vp run root-glob-test", + # Modify file outside glob pattern + "replace-file-content other/other.ts initial modified", + # Cache hit: file doesn't match glob + "vp run root-glob-test", +] + +[[e2e]] +name = "root glob - subpackage path unmatched by relative glob" +steps = [ + "vp run root-glob-test", + # Modify file in subpackage - relative glob src/** doesn't reach packages/sub-pkg/src/ + "replace-file-content packages/sub-pkg/src/sub.ts initial modified", + # Cache hit: path simply doesn't match the relative glob pattern + "vp run root-glob-test", +] + +# 2. Root package with custom cwd - glob still relative to package root +[[e2e]] +name = "root glob with cwd - glob relative to package not cwd" +steps = [ + "vp run root-glob-with-cwd", + # Modify file - glob is src/** relative to package root + "replace-file-content src/root.ts initial modified", + # Cache miss: file matches glob (relative to package, not cwd) + "vp run root-glob-with-cwd", +] + +# 3. Subpackage - glob matches files in subpackage's src/ +[[e2e]] +name = "subpackage glob - matches own src files" +steps = [ + "vp run sub-pkg#sub-glob-test", + # Modify matched file in subpackage + "replace-file-content packages/sub-pkg/src/sub.ts initial modified", + # Cache miss: file matches subpackage's glob + "vp run sub-pkg#sub-glob-test", +] + +[[e2e]] +name = "subpackage glob - unmatched directory in subpackage" +steps = [ + "vp run sub-pkg#sub-glob-test", + # Modify file outside glob pattern + "replace-file-content packages/sub-pkg/other/other.ts initial modified", + # Cache hit: file doesn't match glob + "vp run sub-pkg#sub-glob-test", +] + +[[e2e]] +name = "subpackage glob - root path unmatched by relative glob" +steps = [ + "vp run sub-pkg#sub-glob-test", + # Modify file in root - relative glob src/** is resolved from subpackage dir + "replace-file-content src/root.ts initial modified", + # Cache hit: path simply doesn't match the relative glob pattern + "vp run sub-pkg#sub-glob-test", +] + +# 4. Subpackage with custom cwd - glob still relative to subpackage root +[[e2e]] +name = "subpackage glob with cwd - glob relative to package not cwd" +steps = [ + "vp run sub-pkg#sub-glob-with-cwd", + # Modify file - glob is src/** relative to subpackage root + "replace-file-content packages/sub-pkg/src/sub.ts initial modified", + # Cache miss: file matches glob (relative to subpackage, not cwd) + "vp run sub-pkg#sub-glob-with-cwd", +] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob - matches src files.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob - matches src files.snap new file mode 100644 index 00000000..e33a40c0 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob - matches src files.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run root-glob-test +$ print-file src/root.ts +export const root = 'initial'; +> replace-file-content src/root.ts initial modified + +> vp run root-glob-test +$ print-file src/root.ts ✗ cache miss: content of input 'src/root.ts' changed, executing +export const root = 'modified'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob - subpackage path unmatched by relative glob.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob - subpackage path unmatched by relative glob.snap new file mode 100644 index 00000000..39a0e28d --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob - subpackage path unmatched by relative glob.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run root-glob-test +$ print-file src/root.ts +export const root = 'initial'; +> replace-file-content packages/sub-pkg/src/sub.ts initial modified + +> vp run root-glob-test +$ print-file src/root.ts ✓ cache hit, replaying +export const root = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob - unmatched directory.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob - unmatched directory.snap new file mode 100644 index 00000000..e3c88b7f --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob - unmatched directory.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run root-glob-test +$ print-file src/root.ts +export const root = 'initial'; +> replace-file-content other/other.ts initial modified + +> vp run root-glob-test +$ print-file src/root.ts ✓ cache hit, replaying +export const root = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob with cwd - glob relative to package not cwd.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob with cwd - glob relative to package not cwd.snap new file mode 100644 index 00000000..818540ad --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/root glob with cwd - glob relative to package not cwd.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run root-glob-with-cwd +~/src$ print-file root.ts +export const root = 'initial'; +> replace-file-content src/root.ts initial modified + +> vp run root-glob-with-cwd +~/src$ print-file root.ts ✗ cache miss: content of input 'src/root.ts' changed, executing +export const root = 'modified'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob - matches own src files.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob - matches own src files.snap new file mode 100644 index 00000000..ac5a5a62 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob - matches own src files.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#sub-glob-test +~/packages/sub-pkg$ print-file src/sub.ts +export const sub = 'initial'; +> replace-file-content packages/sub-pkg/src/sub.ts initial modified + +> vp run sub-pkg#sub-glob-test +~/packages/sub-pkg$ print-file src/sub.ts ✗ cache miss: content of input 'packages/sub-pkg/src/sub.ts' changed, executing +export const sub = 'modified'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob - root path unmatched by relative glob.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob - root path unmatched by relative glob.snap new file mode 100644 index 00000000..cf4db383 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob - root path unmatched by relative glob.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#sub-glob-test +~/packages/sub-pkg$ print-file src/sub.ts +export const sub = 'initial'; +> replace-file-content src/root.ts initial modified + +> vp run sub-pkg#sub-glob-test +~/packages/sub-pkg$ print-file src/sub.ts ✓ cache hit, replaying +export const sub = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob - unmatched directory in subpackage.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob - unmatched directory in subpackage.snap new file mode 100644 index 00000000..68793b67 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob - unmatched directory in subpackage.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#sub-glob-test +~/packages/sub-pkg$ print-file src/sub.ts +export const sub = 'initial'; +> replace-file-content packages/sub-pkg/other/other.ts initial modified + +> vp run sub-pkg#sub-glob-test +~/packages/sub-pkg$ print-file src/sub.ts ✓ cache hit, replaying +export const sub = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob with cwd - glob relative to package not cwd.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob with cwd - glob relative to package not cwd.snap new file mode 100644 index 00000000..30a09767 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/snapshots/subpackage glob with cwd - glob relative to package not cwd.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#sub-glob-with-cwd +~/packages/sub-pkg/src$ print-file sub.ts +export const sub = 'initial'; +> replace-file-content packages/sub-pkg/src/sub.ts initial modified + +> vp run sub-pkg#sub-glob-with-cwd +~/packages/sub-pkg/src$ print-file sub.ts ✗ cache miss: content of input 'packages/sub-pkg/src/sub.ts' changed, executing +export const sub = 'modified'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/src/root.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/src/root.ts new file mode 100644 index 00000000..96424f31 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/src/root.ts @@ -0,0 +1 @@ +export const root = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/vite-task.json new file mode 100644 index 00000000..be9583e2 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob-base-test/vite-task.json @@ -0,0 +1,15 @@ +{ + "tasks": { + "root-glob-test": { + "command": "print-file src/root.ts", + "inputs": ["src/**/*.ts"], + "cache": true + }, + "root-glob-with-cwd": { + "command": "print-file root.ts", + "cwd": "src", + "inputs": ["src/**/*.ts"], + "cache": true + } + } +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/dist/output.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/dist/output.js new file mode 100644 index 00000000..d8d5d210 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/dist/output.js @@ -0,0 +1 @@ +// initial output diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/package.json new file mode 100644 index 00000000..1d369f44 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/package.json @@ -0,0 +1,4 @@ +{ + "name": "inputs-cache-test", + "private": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots.toml new file mode 100644 index 00000000..36a7ffbd --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots.toml @@ -0,0 +1,250 @@ +# Test all input configuration combinations for cache behavior + +# 1. Positive globs only: inputs: ["src/**/*.ts"] +# - Files matching the glob trigger cache invalidation +# - Files outside the glob do NOT trigger cache invalidation +[[e2e]] +name = "positive globs only - cache hit on second run" +steps = [ + # First run - cache miss + "vp run positive-globs-only", + # Second run - cache hit + "vp run positive-globs-only", +] + +[[e2e]] +name = "positive globs only - miss on matched file change" +steps = [ + # Initial run + "vp run positive-globs-only", + # Modify a file that matches the glob + "replace-file-content src/main.ts initial modified", + # Cache miss: matched file changed + "vp run positive-globs-only", +] + +[[e2e]] +name = "positive globs only - hit on unmatched file change" +steps = [ + # Initial run + "vp run positive-globs-only", + # Modify a file that does NOT match the glob (test/ is outside src/) + "replace-file-content test/main.test.ts outside modified", + # Cache hit: file not in inputs + "vp run positive-globs-only", +] + +# 1b. Positive globs reads unmatched file: inputs: ["src/main.ts"], command reads src/utils.ts too +# - File read by command but NOT matched by glob should NOT be fingerprinted +# - This tests that inference is truly disabled when only explicit globs are used +[[e2e]] +name = "positive globs - hit on read but unmatched file" +steps = [ + # Initial run - reads both src/main.ts and src/utils.ts + "vp run positive-globs-reads-unmatched", + # Modify utils.ts - read by command but NOT in glob + "replace-file-content src/utils.ts initial modified", + # Cache hit: file was read but not matched by glob, inference is off + "vp run positive-globs-reads-unmatched", +] + +# 2. Positive + negative globs: inputs: ["src/**", "!src/**/*.test.ts"] +# - Files matching positive but NOT negative trigger invalidation +# - Files matching negative glob are excluded +[[e2e]] +name = "positive negative globs - miss on non-excluded file" +steps = [ + # Initial run + "vp run positive-negative-globs", + # Modify a file that matches positive but NOT negative + "replace-file-content src/main.ts initial modified", + # Cache miss: file changed + "vp run positive-negative-globs", +] + +[[e2e]] +name = "positive negative globs - hit on excluded file" +steps = [ + # Initial run + "vp run positive-negative-globs", + # Modify a file that matches the negative glob (excluded) + "replace-file-content src/main.test.ts main modified", + # Cache hit: file excluded by negative glob + "vp run positive-negative-globs", +] + +# 3. Auto only: inputs: [{ "auto": true }] +# - Files read by the command trigger invalidation (fspy inference) +# - Files NOT read by the command do NOT trigger invalidation +[[e2e]] +name = "auto only - miss on inferred file change" +steps = [ + # Initial run - reads src/main.ts + "vp run auto-only", + # Modify the file that was read + "replace-file-content src/main.ts initial modified", + # Cache miss: inferred input changed + "vp run auto-only", +] + +[[e2e]] +name = "auto only - hit on non-inferred file change" +steps = [ + # Initial run - reads src/main.ts (NOT utils.ts) + "vp run auto-only", + # Modify a file that was NOT read by the command + "replace-file-content src/utils.ts initial modified", + # Cache hit: file not in inferred inputs + "vp run auto-only", +] + +# 4. Auto + negative: inputs: [{ "auto": true }, "!dist/**"] +# - Inferred files are tracked, but negative globs filter them out +# - Files in excluded directories don't trigger invalidation even if read +[[e2e]] +name = "auto with negative - hit on excluded inferred file" +steps = [ + # Initial run - reads both src/main.ts and dist/output.js + "vp run auto-with-negative", + # Modify file in excluded directory (dist/) + "replace-file-content dist/output.js initial modified", + # Cache hit: dist/ is excluded by negative glob + "vp run auto-with-negative", +] + +[[e2e]] +name = "auto with negative - miss on non-excluded inferred file" +steps = [ + # Initial run + "vp run auto-with-negative", + # Modify file NOT in excluded directory + "replace-file-content src/main.ts initial modified", + # Cache miss: inferred input changed + "vp run auto-with-negative", +] + +# 5. Positive + auto + negative: inputs: ["package.json", { "auto": true }, "!src/**/*.test.ts"] +# - Explicit globs AND inferred files are both tracked +# - Negative globs apply to both explicit and inferred inputs +[[e2e]] +name = "positive auto negative - miss on explicit glob file" +steps = [ + # Initial run + "vp run positive-auto-negative", + # Modify explicit input file + "replace-file-content package.json inputs-cache-test modified-pkg", + # Cache miss: explicit input changed + "vp run positive-auto-negative", +] + +[[e2e]] +name = "positive auto negative - miss on inferred file" +steps = [ + # Initial run + "vp run positive-auto-negative", + # Modify inferred input file (read by command) + "replace-file-content src/main.ts initial modified", + # Cache miss: inferred input changed + "vp run positive-auto-negative", +] + +[[e2e]] +name = "positive auto negative - hit on excluded file" +steps = [ + # Initial run + "vp run positive-auto-negative", + # Modify file excluded by negative glob + "replace-file-content src/main.test.ts main modified", + # Cache hit: file excluded by negative glob + "vp run positive-auto-negative", +] + +# 6. Empty inputs: inputs: [] +# - No file changes affect the cache +# - Only command/env changes trigger cache invalidation +[[e2e]] +name = "empty inputs - hit despite file changes" +steps = [ + # Initial run + "vp run empty-inputs", + # Modify any file - should NOT affect cache + "replace-file-content src/main.ts initial modified", + # Cache hit: no inputs configured + "vp run empty-inputs", +] + +[[e2e]] +name = "empty inputs - miss on command change" +steps = [ + # Initial run + "vp run empty-inputs", + # Change the command + "json-edit vite-task.json \"_.tasks['empty-inputs'].command = 'print-file src/utils.ts'\"", + # Cache miss: command changed + "vp run empty-inputs", +] + +# 7. FSPY environment variable +# - FSPY=1 is set when fspy is enabled (includes_auto is true) +# - FSPY is NOT set when fspy is disabled (explicit globs only) +[[e2e]] +name = "fspy env - set when auto inference enabled" +steps = [ + # Run task with auto inference - should see FSPY=1 + "vp run check-fspy-env-with-auto", +] + +[[e2e]] +name = "fspy env - not set when auto inference disabled" +steps = [ + # Run task without auto inference - should see (undefined) + "vp run check-fspy-env-without-auto", +] + +# 8. Trailing slash input: inputs: ["src/"] +# - Trailing `/` is expanded to `/**`, matching all files under that directory +# - Direct and nested file changes trigger cache invalidation +# - Files outside the directory do NOT trigger cache invalidation +[[e2e]] +name = "folder slash input - miss on direct and nested file changes" +steps = [ + "vp run folder-slash-input", + # Modify a direct file in src/ + "replace-file-content src/main.ts initial modified", + # Cache miss: direct file changed + "vp run folder-slash-input", + # Reset and run again to re-establish cache + "replace-file-content src/main.ts modified initial", + "vp run folder-slash-input", + # Modify a nested file in src/sub/ + "replace-file-content src/sub/nested.ts initial modified", + # Cache miss: nested file changed + "vp run folder-slash-input", +] + +[[e2e]] +name = "folder slash input - hit on file outside directory" +steps = [ + "vp run folder-slash-input", + # Modify a file outside src/ + "replace-file-content test/main.test.ts outside modified", + # Cache hit: file not under src/ + "vp run folder-slash-input", +] + +# 9. Folder path as input: inputs: ["src"] +# - A bare directory name matches nothing (directories are not files) +# - File changes inside the folder should NOT trigger cache invalidation +[[e2e]] +name = "folder input - hit despite file changes and folder deletion" +steps = [ + "vp run folder-input", + # Modify a file inside the folder + "replace-file-content src/main.ts initial modified", + # Cache hit: "src" matches the directory itself, not files inside it + "vp run folder-input", + # Delete the entire folder + "rm -rf src", + # Cache hit: folder removal doesn't affect fingerprint + "vp run folder-input", +] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto only - hit on non-inferred file change.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto only - hit on non-inferred file change.snap new file mode 100644 index 00000000..a524b6b4 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto only - hit on non-inferred file change.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run auto-only +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content src/utils.ts initial modified + +> vp run auto-only +$ print-file src/main.ts ✓ cache hit, replaying +export const main = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto only - miss on inferred file change.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto only - miss on inferred file change.snap new file mode 100644 index 00000000..6f17df22 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto only - miss on inferred file change.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run auto-only +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content src/main.ts initial modified + +> vp run auto-only +$ print-file src/main.ts ✗ cache miss: content of input 'src/main.ts' changed, executing +export const main = 'modified'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto with negative - hit on excluded inferred file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto with negative - hit on excluded inferred file.snap new file mode 100644 index 00000000..68767f83 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto with negative - hit on excluded inferred file.snap @@ -0,0 +1,17 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run auto-with-negative +$ print-file src/main.ts dist/output.js +export const main = 'initial'; +// initial output +> replace-file-content dist/output.js initial modified + +> vp run auto-with-negative +$ print-file src/main.ts dist/output.js ✓ cache hit, replaying +export const main = 'initial'; +// initial output + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto with negative - miss on non-excluded inferred file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto with negative - miss on non-excluded inferred file.snap new file mode 100644 index 00000000..9a98e9e1 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/auto with negative - miss on non-excluded inferred file.snap @@ -0,0 +1,14 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run auto-with-negative +$ print-file src/main.ts dist/output.js +export const main = 'initial'; +// initial output +> replace-file-content src/main.ts initial modified + +> vp run auto-with-negative +$ print-file src/main.ts dist/output.js ✗ cache miss: content of input 'src/main.ts' changed, executing +export const main = 'modified'; +// initial output diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/empty inputs - hit despite file changes.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/empty inputs - hit despite file changes.snap new file mode 100644 index 00000000..4cc5f6f2 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/empty inputs - hit despite file changes.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run empty-inputs +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content src/main.ts initial modified + +> vp run empty-inputs +$ print-file src/main.ts ✓ cache hit, replaying +export const main = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/empty inputs - miss on command change.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/empty inputs - miss on command change.snap new file mode 100644 index 00000000..6ec2904b --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/empty inputs - miss on command change.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run empty-inputs +$ print-file src/main.ts +export const main = 'initial'; +> json-edit vite-task.json "_.tasks['empty-inputs'].command = 'print-file src/utils.ts'" + +> vp run empty-inputs +$ print-file src/utils.ts ✗ cache miss: args changed, executing +export const utils = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/folder input - hit despite file changes and folder deletion.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/folder input - hit despite file changes and folder deletion.snap new file mode 100644 index 00000000..b919a7a4 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/folder input - hit despite file changes and folder deletion.snap @@ -0,0 +1,23 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run folder-input +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content src/main.ts initial modified + +> vp run folder-input +$ print-file src/main.ts ✓ cache hit, replaying +export const main = 'initial'; + +--- +[vp run] cache hit, saved. +> rm -rf src + +> vp run folder-input +$ print-file src/main.ts ✓ cache hit, replaying +export const main = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/folder slash input - hit on file outside directory.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/folder slash input - hit on file outside directory.snap new file mode 100644 index 00000000..899ccfaa --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/folder slash input - hit on file outside directory.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run folder-slash-input +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content test/main.test.ts outside modified + +> vp run folder-slash-input +$ print-file src/main.ts ✓ cache hit, replaying +export const main = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/folder slash input - miss on direct and nested file changes.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/folder slash input - miss on direct and nested file changes.snap new file mode 100644 index 00000000..82f9346b --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/folder slash input - miss on direct and nested file changes.snap @@ -0,0 +1,22 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run folder-slash-input +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content src/main.ts initial modified + +> vp run folder-slash-input +$ print-file src/main.ts ✗ cache miss: content of input 'src/main.ts' changed, executing +export const main = 'modified'; +> replace-file-content src/main.ts modified initial + +> vp run folder-slash-input +$ print-file src/main.ts ✗ cache miss: content of input 'src/main.ts' changed, executing +export const main = 'initial'; +> replace-file-content src/sub/nested.ts initial modified + +> vp run folder-slash-input +$ print-file src/main.ts ✗ cache miss: content of input 'src/sub/nested.ts' changed, executing +export const main = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/fspy env - not set when auto inference disabled.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/fspy env - not set when auto inference disabled.snap new file mode 100644 index 00000000..2b726d21 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/fspy env - not set when auto inference disabled.snap @@ -0,0 +1,7 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run check-fspy-env-without-auto +$ print-env FSPY +(undefined) diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/fspy env - set when auto inference enabled.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/fspy env - set when auto inference enabled.snap new file mode 100644 index 00000000..5f42dce0 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/fspy env - set when auto inference enabled.snap @@ -0,0 +1,7 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run check-fspy-env-with-auto +$ print-env FSPY +1 diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive auto negative - hit on excluded file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive auto negative - hit on excluded file.snap new file mode 100644 index 00000000..9e245558 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive auto negative - hit on excluded file.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run positive-auto-negative +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content src/main.test.ts main modified + +> vp run positive-auto-negative +$ print-file src/main.ts ✓ cache hit, replaying +export const main = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive auto negative - miss on explicit glob file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive auto negative - miss on explicit glob file.snap new file mode 100644 index 00000000..3dcdee47 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive auto negative - miss on explicit glob file.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run positive-auto-negative +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content package.json inputs-cache-test modified-pkg + +> vp run positive-auto-negative +$ print-file src/main.ts ✗ cache miss: content of input 'package.json' changed, executing +export const main = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive auto negative - miss on inferred file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive auto negative - miss on inferred file.snap new file mode 100644 index 00000000..156d17f1 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive auto negative - miss on inferred file.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run positive-auto-negative +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content src/main.ts initial modified + +> vp run positive-auto-negative +$ print-file src/main.ts ✗ cache miss: content of input 'src/main.ts' changed, executing +export const main = 'modified'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs - hit on read but unmatched file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs - hit on read but unmatched file.snap new file mode 100644 index 00000000..6377b61a --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs - hit on read but unmatched file.snap @@ -0,0 +1,17 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run positive-globs-reads-unmatched +$ print-file src/main.ts src/utils.ts +export const main = 'initial'; +export const utils = 'initial'; +> replace-file-content src/utils.ts initial modified + +> vp run positive-globs-reads-unmatched +$ print-file src/main.ts src/utils.ts ✓ cache hit, replaying +export const main = 'initial'; +export const utils = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs only - cache hit on second run.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs only - cache hit on second run.snap new file mode 100644 index 00000000..b110416a --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs only - cache hit on second run.snap @@ -0,0 +1,13 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run positive-globs-only +$ print-file src/main.ts +export const main = 'initial'; +> vp run positive-globs-only +$ print-file src/main.ts ✓ cache hit, replaying +export const main = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs only - hit on unmatched file change.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs only - hit on unmatched file change.snap new file mode 100644 index 00000000..f257d2f1 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs only - hit on unmatched file change.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run positive-globs-only +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content test/main.test.ts outside modified + +> vp run positive-globs-only +$ print-file src/main.ts ✓ cache hit, replaying +export const main = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs only - miss on matched file change.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs only - miss on matched file change.snap new file mode 100644 index 00000000..d2ccf7bf --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive globs only - miss on matched file change.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run positive-globs-only +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content src/main.ts initial modified + +> vp run positive-globs-only +$ print-file src/main.ts ✗ cache miss: content of input 'src/main.ts' changed, executing +export const main = 'modified'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive negative globs - hit on excluded file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive negative globs - hit on excluded file.snap new file mode 100644 index 00000000..44f165f7 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive negative globs - hit on excluded file.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run positive-negative-globs +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content src/main.test.ts main modified + +> vp run positive-negative-globs +$ print-file src/main.ts ✓ cache hit, replaying +export const main = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive negative globs - miss on non-excluded file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive negative globs - miss on non-excluded file.snap new file mode 100644 index 00000000..b67d6810 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots/positive negative globs - miss on non-excluded file.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run positive-negative-globs +$ print-file src/main.ts +export const main = 'initial'; +> replace-file-content src/main.ts initial modified + +> vp run positive-negative-globs +$ print-file src/main.ts ✗ cache miss: content of input 'src/main.ts' changed, executing +export const main = 'modified'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/main.test.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/main.test.ts new file mode 100644 index 00000000..3a3ca296 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/main.test.ts @@ -0,0 +1 @@ +test('main', () => {}); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/main.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/main.ts new file mode 100644 index 00000000..38e000a1 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/main.ts @@ -0,0 +1 @@ +export const main = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/sub/nested.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/sub/nested.ts new file mode 100644 index 00000000..987cac05 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/sub/nested.ts @@ -0,0 +1 @@ +export const nested = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/utils.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/utils.ts new file mode 100644 index 00000000..e5b6e206 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/src/utils.ts @@ -0,0 +1 @@ +export const utils = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/test/main.test.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/test/main.test.ts new file mode 100644 index 00000000..12d25c9c --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/test/main.test.ts @@ -0,0 +1 @@ +// test file outside src/ diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/vite-task.json new file mode 100644 index 00000000..d608ac41 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/vite-task.json @@ -0,0 +1,59 @@ +{ + "tasks": { + "positive-globs-only": { + "command": "print-file src/main.ts", + "inputs": ["src/**/*.ts"], + "cache": true + }, + "positive-globs-reads-unmatched": { + "command": "print-file src/main.ts src/utils.ts", + "inputs": ["src/main.ts"], + "cache": true + }, + "positive-negative-globs": { + "command": "print-file src/main.ts", + "inputs": ["src/**", "!src/**/*.test.ts"], + "cache": true + }, + "auto-only": { + "command": "print-file src/main.ts", + "inputs": [{ "auto": true }], + "cache": true + }, + "auto-with-negative": { + "command": "print-file src/main.ts dist/output.js", + "inputs": [{ "auto": true }, "!dist/**"], + "cache": true + }, + "positive-auto-negative": { + "command": "print-file src/main.ts", + "inputs": ["package.json", { "auto": true }, "!src/**/*.test.ts"], + "cache": true + }, + "empty-inputs": { + "command": "print-file src/main.ts", + "inputs": [], + "cache": true + }, + "check-fspy-env-with-auto": { + "command": "print-env FSPY", + "inputs": [{ "auto": true }], + "cache": true + }, + "check-fspy-env-without-auto": { + "command": "print-env FSPY", + "inputs": ["src/**/*.ts"], + "cache": true + }, + "folder-input": { + "command": "print-file src/main.ts", + "inputs": ["src"], + "cache": true + }, + "folder-slash-input": { + "command": "print-file src/main.ts", + "inputs": ["src/"], + "cache": true + } + } +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/package.json new file mode 100644 index 00000000..9e8f0d50 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/package.json @@ -0,0 +1,4 @@ +{ + "name": "inputs-glob-meta-in-path", + "private": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/packages/[lib]/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/packages/[lib]/package.json new file mode 100644 index 00000000..9bd69c4c --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/packages/[lib]/package.json @@ -0,0 +1,4 @@ +{ + "name": "[lib]", + "private": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/packages/[lib]/src/main.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/packages/[lib]/src/main.ts new file mode 100644 index 00000000..834b5a5e --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/packages/[lib]/src/main.ts @@ -0,0 +1 @@ +export const lib = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/packages/[lib]/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/packages/[lib]/vite-task.json new file mode 100644 index 00000000..2a31e9ce --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/packages/[lib]/vite-task.json @@ -0,0 +1,9 @@ +{ + "tasks": { + "build": { + "command": "print-file src/main.ts", + "inputs": ["src/**/*.ts"], + "cache": true + } + } +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/pnpm-workspace.yaml new file mode 100644 index 00000000..18ec407e --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/snapshots.toml new file mode 100644 index 00000000..c42f9ee8 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/snapshots.toml @@ -0,0 +1,12 @@ +# Test that glob meta characters in package paths are correctly escaped by wax::escape. +# Without escaping, "packages/[lib]/src/**/*.ts" would interpret [lib] as a character +# class matching 'l', 'i', or 'b' instead of the literal directory name. + +[[e2e]] +name = "cache hit then miss on file change" +steps = [ + "vp run [lib]#build", + "vp run [lib]#build", + "replace-file-content packages/[lib]/src/main.ts initial modified", + "vp run [lib]#build", +] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/snapshots/cache hit then miss on file change.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/snapshots/cache hit then miss on file change.snap new file mode 100644 index 00000000..207f484e --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-glob-meta-in-path/snapshots/cache hit then miss on file change.snap @@ -0,0 +1,18 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run [lib]#build +~/packages/[lib]$ print-file src/main.ts +export const lib = 'initial'; +> vp run [lib]#build +~/packages/[lib]$ print-file src/main.ts ✓ cache hit, replaying +export const lib = 'initial'; + +--- +[vp run] cache hit, saved. +> replace-file-content packages/[lib]/src/main.ts initial modified + +> vp run [lib]#build +~/packages/[lib]$ print-file src/main.ts ✗ cache miss: content of input 'packages/[lib]/src/main.ts' changed, executing +export const lib = 'modified'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/package.json new file mode 100644 index 00000000..84faec77 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/package.json @@ -0,0 +1,4 @@ +{ + "name": "inputs-negative-glob-subpackage", + "private": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/shared/dist/output.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/shared/dist/output.js new file mode 100644 index 00000000..d8d5d210 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/shared/dist/output.js @@ -0,0 +1 @@ +// initial output diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/shared/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/shared/package.json new file mode 100644 index 00000000..590047bc --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/shared/package.json @@ -0,0 +1,4 @@ +{ + "name": "shared", + "private": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/shared/src/utils.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/shared/src/utils.ts new file mode 100644 index 00000000..34f12be3 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/shared/src/utils.ts @@ -0,0 +1 @@ +export const shared = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/dist/output.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/dist/output.js new file mode 100644 index 00000000..d8d5d210 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/dist/output.js @@ -0,0 +1 @@ +// initial output diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/package.json new file mode 100644 index 00000000..e0caa109 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/package.json @@ -0,0 +1,4 @@ +{ + "name": "sub-pkg", + "private": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/src/main.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/src/main.ts new file mode 100644 index 00000000..38e000a1 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/src/main.ts @@ -0,0 +1 @@ +export const main = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/vite-task.json new file mode 100644 index 00000000..91096c33 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/packages/sub-pkg/vite-task.json @@ -0,0 +1,24 @@ +{ + "tasks": { + "auto-with-negative": { + "command": "print-file src/main.ts dist/output.js", + "inputs": [{ "auto": true }, "!dist/**"], + "cache": true + }, + "dotdot-positive": { + "command": "print-file ../shared/src/utils.ts", + "inputs": ["../shared/src/**"], + "cache": true + }, + "dotdot-positive-negative": { + "command": "print-file ../shared/src/utils.ts ../shared/dist/output.js", + "inputs": ["../shared/**", "!../shared/dist/**"], + "cache": true + }, + "dotdot-auto-negative": { + "command": "print-file ../shared/src/utils.ts ../shared/dist/output.js", + "inputs": [{ "auto": true }, "!../shared/dist/**"], + "cache": true + } + } +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/pnpm-workspace.yaml new file mode 100644 index 00000000..18ec407e --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots.toml new file mode 100644 index 00000000..51bac65f --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots.toml @@ -0,0 +1,98 @@ +# Test that negative input globs work correctly for subpackages. +# Bug: negative globs were matched against workspace-relative paths +# instead of package-relative paths, so exclusions like !dist/** +# failed for subpackages. + +# Auto + negative in subpackage: inputs: [{ "auto": true }, "!dist/**"] +# - dist/ should be excluded even though the package is a subpackage +# - Modifying dist/output.js should be a cache hit +[[e2e]] +name = "subpackage auto with negative - hit on excluded inferred file" +steps = [ + # First run - reads both src/main.ts and dist/output.js + "vp run sub-pkg#auto-with-negative", + # Modify file in excluded directory (dist/) + "replace-file-content packages/sub-pkg/dist/output.js initial modified", + # Cache hit: dist/ is excluded by negative glob + "vp run sub-pkg#auto-with-negative", +] + +[[e2e]] +name = "subpackage auto with negative - miss on non-excluded inferred file" +steps = [ + # First run + "vp run sub-pkg#auto-with-negative", + # Modify file NOT in excluded directory + "replace-file-content packages/sub-pkg/src/main.ts initial modified", + # Cache miss: inferred input changed + "vp run sub-pkg#auto-with-negative", +] + +# .. prefix positive globs: inputs: ["../shared/src/**"] +# - Should find files in sibling package via .. +# - Cache miss when sibling file changes, hit when unrelated file changes +[[e2e]] +name = "dotdot positive glob - miss on sibling file change" +steps = [ + "vp run sub-pkg#dotdot-positive", + # Modify a file that matches ../shared/src/** + "replace-file-content packages/shared/src/utils.ts initial modified", + # Cache miss: matched file changed + "vp run sub-pkg#dotdot-positive", +] + +[[e2e]] +name = "dotdot positive glob - hit on unmatched file change" +steps = [ + "vp run sub-pkg#dotdot-positive", + # Modify a file NOT matched by ../shared/src/** + "replace-file-content packages/shared/dist/output.js initial modified", + # Cache hit: file not in inputs + "vp run sub-pkg#dotdot-positive", +] + +# .. prefix positive + negative globs: inputs: ["../shared/**", "!../shared/dist/**"] +# - Positive glob matches sibling package files via .. +# - Negative glob excludes sibling dist/ via .. +[[e2e]] +name = "dotdot positive negative - miss on non-excluded sibling file" +steps = [ + "vp run sub-pkg#dotdot-positive-negative", + # Modify file matching positive but NOT negative + "replace-file-content packages/shared/src/utils.ts initial modified", + # Cache miss: file changed + "vp run sub-pkg#dotdot-positive-negative", +] + +[[e2e]] +name = "dotdot positive negative - hit on excluded sibling file" +steps = [ + "vp run sub-pkg#dotdot-positive-negative", + # Modify file in excluded sibling dist/ + "replace-file-content packages/shared/dist/output.js initial modified", + # Cache hit: excluded by !../shared/dist/** + "vp run sub-pkg#dotdot-positive-negative", +] + +# .. prefix auto + negative: inputs: [{ "auto": true }, "!../shared/dist/**"] +# - Auto-inferred files from sibling package are tracked +# - Negative glob excludes sibling dist/ from inferred inputs +[[e2e]] +name = "dotdot auto negative - hit on excluded sibling inferred file" +steps = [ + "vp run sub-pkg#dotdot-auto-negative", + # Modify file in excluded sibling dist/ + "replace-file-content packages/shared/dist/output.js initial modified", + # Cache hit: excluded by !../shared/dist/** + "vp run sub-pkg#dotdot-auto-negative", +] + +[[e2e]] +name = "dotdot auto negative - miss on non-excluded sibling inferred file" +steps = [ + "vp run sub-pkg#dotdot-auto-negative", + # Modify non-excluded sibling file + "replace-file-content packages/shared/src/utils.ts initial modified", + # Cache miss: inferred input changed + "vp run sub-pkg#dotdot-auto-negative", +] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot auto negative - hit on excluded sibling inferred file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot auto negative - hit on excluded sibling inferred file.snap new file mode 100644 index 00000000..3a127e1f --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot auto negative - hit on excluded sibling inferred file.snap @@ -0,0 +1,17 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#dotdot-auto-negative +~/packages/sub-pkg$ print-file ../shared/src/utils.ts ../shared/dist/output.js +export const shared = 'initial'; +// initial output +> replace-file-content packages/shared/dist/output.js initial modified + +> vp run sub-pkg#dotdot-auto-negative +~/packages/sub-pkg$ print-file ../shared/src/utils.ts ../shared/dist/output.js ✓ cache hit, replaying +export const shared = 'initial'; +// initial output + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot auto negative - miss on non-excluded sibling inferred file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot auto negative - miss on non-excluded sibling inferred file.snap new file mode 100644 index 00000000..3a6b1ca1 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot auto negative - miss on non-excluded sibling inferred file.snap @@ -0,0 +1,14 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#dotdot-auto-negative +~/packages/sub-pkg$ print-file ../shared/src/utils.ts ../shared/dist/output.js +export const shared = 'initial'; +// initial output +> replace-file-content packages/shared/src/utils.ts initial modified + +> vp run sub-pkg#dotdot-auto-negative +~/packages/sub-pkg$ print-file ../shared/src/utils.ts ../shared/dist/output.js ✗ cache miss: content of input 'packages/shared/src/utils.ts' changed, executing +export const shared = 'modified'; +// initial output diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive glob - hit on unmatched file change.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive glob - hit on unmatched file change.snap new file mode 100644 index 00000000..4f32efef --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive glob - hit on unmatched file change.snap @@ -0,0 +1,15 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#dotdot-positive +~/packages/sub-pkg$ print-file ../shared/src/utils.ts +export const shared = 'initial'; +> replace-file-content packages/shared/dist/output.js initial modified + +> vp run sub-pkg#dotdot-positive +~/packages/sub-pkg$ print-file ../shared/src/utils.ts ✓ cache hit, replaying +export const shared = 'initial'; + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive glob - miss on sibling file change.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive glob - miss on sibling file change.snap new file mode 100644 index 00000000..385f2fce --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive glob - miss on sibling file change.snap @@ -0,0 +1,12 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#dotdot-positive +~/packages/sub-pkg$ print-file ../shared/src/utils.ts +export const shared = 'initial'; +> replace-file-content packages/shared/src/utils.ts initial modified + +> vp run sub-pkg#dotdot-positive +~/packages/sub-pkg$ print-file ../shared/src/utils.ts ✗ cache miss: content of input 'packages/shared/src/utils.ts' changed, executing +export const shared = 'modified'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive negative - hit on excluded sibling file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive negative - hit on excluded sibling file.snap new file mode 100644 index 00000000..08d6027c --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive negative - hit on excluded sibling file.snap @@ -0,0 +1,17 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#dotdot-positive-negative +~/packages/sub-pkg$ print-file ../shared/src/utils.ts ../shared/dist/output.js +export const shared = 'initial'; +// initial output +> replace-file-content packages/shared/dist/output.js initial modified + +> vp run sub-pkg#dotdot-positive-negative +~/packages/sub-pkg$ print-file ../shared/src/utils.ts ../shared/dist/output.js ✓ cache hit, replaying +export const shared = 'initial'; +// initial output + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive negative - miss on non-excluded sibling file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive negative - miss on non-excluded sibling file.snap new file mode 100644 index 00000000..735a8bdb --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/dotdot positive negative - miss on non-excluded sibling file.snap @@ -0,0 +1,14 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#dotdot-positive-negative +~/packages/sub-pkg$ print-file ../shared/src/utils.ts ../shared/dist/output.js +export const shared = 'initial'; +// initial output +> replace-file-content packages/shared/src/utils.ts initial modified + +> vp run sub-pkg#dotdot-positive-negative +~/packages/sub-pkg$ print-file ../shared/src/utils.ts ../shared/dist/output.js ✗ cache miss: content of input 'packages/shared/src/utils.ts' changed, executing +export const shared = 'modified'; +// initial output diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/subpackage auto with negative - hit on excluded inferred file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/subpackage auto with negative - hit on excluded inferred file.snap new file mode 100644 index 00000000..d0dc00e6 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/subpackage auto with negative - hit on excluded inferred file.snap @@ -0,0 +1,17 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#auto-with-negative +~/packages/sub-pkg$ print-file src/main.ts dist/output.js +export const main = 'initial'; +// initial output +> replace-file-content packages/sub-pkg/dist/output.js initial modified + +> vp run sub-pkg#auto-with-negative +~/packages/sub-pkg$ print-file src/main.ts dist/output.js ✓ cache hit, replaying +export const main = 'initial'; +// initial output + +--- +[vp run] cache hit, saved. diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/subpackage auto with negative - miss on non-excluded inferred file.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/subpackage auto with negative - miss on non-excluded inferred file.snap new file mode 100644 index 00000000..8d615517 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-negative-glob-subpackage/snapshots/subpackage auto with negative - miss on non-excluded inferred file.snap @@ -0,0 +1,14 @@ +--- +source: crates/vite_task_bin/tests/e2e_snapshots/main.rs +expression: e2e_outputs +--- +> vp run sub-pkg#auto-with-negative +~/packages/sub-pkg$ print-file src/main.ts dist/output.js +export const main = 'initial'; +// initial output +> replace-file-content packages/sub-pkg/src/main.ts initial modified + +> vp run sub-pkg#auto-with-negative +~/packages/sub-pkg$ print-file src/main.ts dist/output.js ✗ cache miss: content of input 'packages/sub-pkg/src/main.ts' changed, executing +export const main = 'modified'; +// initial output diff --git a/crates/vite_task_bin/tests/e2e_snapshots/main.rs b/crates/vite_task_bin/tests/e2e_snapshots/main.rs index 47ac04ee..77172b1c 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/main.rs +++ b/crates/vite_task_bin/tests/e2e_snapshots/main.rs @@ -488,4 +488,8 @@ fn main() { for case_path in &fixture_paths { run_case(&tmp_dir_path, case_path, filter.as_deref()); } + #[expect(clippy::print_stdout, reason = "test summary")] + { + println!("All cases passed."); + } } diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml index 41a53db6..e91a746a 100644 --- a/crates/vite_task_graph/Cargo.toml +++ b/crates/vite_task_graph/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } +bincode = { workspace = true } monostate = { workspace = true } petgraph = { workspace = true } rustc-hash = { workspace = true } @@ -20,6 +21,7 @@ vite_graph_ser = { workspace = true } vite_path = { workspace = true } vite_str = { workspace = true } vite_workspace = { workspace = true } +wax = { workspace = true } [dev-dependencies] pretty_assertions = { workspace = true } diff --git a/crates/vite_task_graph/run-config.ts b/crates/vite_task_graph/run-config.ts index 0ab58364..b79190c9 100644 --- a/crates/vite_task_graph/run-config.ts +++ b/crates/vite_task_graph/run-config.ts @@ -29,6 +29,26 @@ export type Task = { * Environment variable names to be passed to the task without fingerprinting. */ passThroughEnvs?: Array; + /** + * Files to include in the cache fingerprint. + * + * - Omitted: automatically tracks which files the task reads + * - `[]` (empty): disables file tracking entirely + * - Glob patterns (e.g. `"src/**"`) select specific files + * - `{auto: true}` enables automatic file tracking + * - Negative patterns (e.g. `"!dist/**"`) exclude matched files + * + * Patterns are relative to the package directory. + */ + inputs?: Array< + | string + | { + /** + * Automatically track which files the task reads + */ + auto: boolean; + } + >; } | { /** diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index 34ba3d8c..5a8fc11f 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -1,13 +1,14 @@ pub mod user; -use std::sync::Arc; +use std::{collections::BTreeSet, sync::Arc}; +use bincode::{Decode, Encode}; use monostate::MustBe; use rustc_hash::FxHashSet; use serde::Serialize; pub use user::{ EnabledCacheConfig, ResolvedGlobalCacheConfig, UserCacheConfig, UserGlobalCacheConfig, - UserRunConfig, UserTaskConfig, + UserInputEntry, UserInputsConfig, UserRunConfig, UserTaskConfig, }; use vite_path::AbsolutePath; use vite_str::Str; @@ -46,7 +47,16 @@ impl ResolvedTaskOptions { /// Resolves from user-defined options and the directory path where the options are defined. /// For user-defined tasks or scripts in package.json, `dir` is the package directory /// For synthetic tasks, `dir` is the cwd of the original command (e.g. the cwd of `vp lint`). - pub fn resolve(user_options: UserTaskOptions, dir: &Arc) -> Self { + /// + /// # Errors + /// + /// Returns [`ResolveTaskConfigError`] if a glob pattern is invalid or resolves + /// outside the workspace root. + pub fn resolve( + user_options: UserTaskOptions, + dir: &Arc, + workspace_root: &AbsolutePath, + ) -> Result { let cwd: Arc = match user_options.cwd_relative_to_package { Some(ref cwd) if !cwd.as_str().is_empty() => dir.join(cwd).into(), _ => Arc::clone(dir), @@ -60,6 +70,13 @@ impl ResolvedTaskOptions { .into_iter() .collect(); pass_through_envs.extend(DEFAULT_PASSTHROUGH_ENVS.iter().copied().map(Str::from)); + + let input_config = ResolvedInputConfig::from_user_config( + enabled_cache_config.inputs.as_ref(), + dir, + workspace_root, + )?; + Some(CacheConfig { env_config: EnvConfig { fingerprinted_envs: enabled_cache_config @@ -68,16 +85,152 @@ impl ResolvedTaskOptions { .unwrap_or_default(), pass_through_envs, }, + input_config, }) } }; - Self { cwd, cache_config } + Ok(Self { cwd, cache_config }) } } #[derive(Debug, Clone, Serialize)] pub struct CacheConfig { pub env_config: EnvConfig, + pub input_config: ResolvedInputConfig, +} + +/// Resolved input configuration for cache fingerprinting. +/// +/// This is the normalized form after parsing user config. +/// - `includes_auto`: Whether automatic file tracking is enabled +/// - `positive_globs`: Glob patterns for files to include (without `!` prefix) +/// - `negative_globs`: Glob patterns for files to exclude (without `!` prefix) +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Encode, Decode)] +pub struct ResolvedInputConfig { + /// Whether automatic file tracking is enabled + pub includes_auto: bool, + + /// Positive glob patterns (files to include), relative to the workspace root. + /// Sorted for deterministic cache keys. + pub positive_globs: BTreeSet, + + /// Negative glob patterns (files to exclude, without the `!` prefix), relative to the workspace root. + /// Sorted for deterministic cache keys. + pub negative_globs: BTreeSet, +} + +impl ResolvedInputConfig { + /// Default configuration: auto-inference enabled, no explicit patterns + #[must_use] + pub const fn default_auto() -> Self { + Self { + includes_auto: true, + positive_globs: BTreeSet::new(), + negative_globs: BTreeSet::new(), + } + } + + /// Resolve from user configuration, making glob patterns workspace-root-relative. + /// + /// - `None`: defaults to auto-inference (`[{auto: true}]`) + /// - `Some([])`: no inputs, inference disabled + /// - `Some([...])`: explicit patterns resolved to workspace-root-relative + /// + /// # Errors + /// + /// Returns [`ResolveTaskConfigError`] if a glob pattern is invalid or resolves + /// outside the workspace root. + pub fn from_user_config( + user_inputs: Option<&UserInputsConfig>, + package_dir: &AbsolutePath, + workspace_root: &AbsolutePath, + ) -> Result { + let Some(entries) = user_inputs else { + // None means default to auto-inference + return Ok(Self::default_auto()); + }; + + let mut includes_auto = false; + let mut positive_globs = BTreeSet::new(); + let mut negative_globs = BTreeSet::new(); + + for entry in entries { + match entry { + UserInputEntry::Auto { auto: true } => includes_auto = true, + UserInputEntry::Auto { auto: false } => {} // Ignore {auto: false} + UserInputEntry::Glob(pattern) => { + if let Some(negated) = pattern.strip_prefix('!') { + let resolved = resolve_glob_to_workspace_relative( + negated, + package_dir, + workspace_root, + )?; + negative_globs.insert(resolved); + } else { + let resolved = resolve_glob_to_workspace_relative( + pattern.as_str(), + package_dir, + workspace_root, + )?; + positive_globs.insert(resolved); + } + } + } + } + + Ok(Self { includes_auto, positive_globs, negative_globs }) + } +} + +/// Resolve a single glob pattern to be workspace-root-relative. +/// +/// The algorithm: +/// 1. Partition the glob into an invariant prefix and a variant part +/// 2. Join the invariant prefix with `package_dir` and clean the path +/// 3. Strip the `workspace_root` prefix from the cleaned path +/// 4. Re-escape the stripped prefix and rejoin with the variant +fn resolve_glob_to_workspace_relative( + pattern: &str, + package_dir: &AbsolutePath, + workspace_root: &AbsolutePath, +) -> Result { + // A trailing `/` is shorthand for all files under that directory + let expanded: Str; + let pattern = if pattern.ends_with('/') { + expanded = vite_str::format!("{pattern}**"); + expanded.as_str() + } else { + pattern + }; + + let glob = wax::Glob::new(pattern).map_err(|source| ResolveTaskConfigError::InvalidGlob { + pattern: Str::from(pattern), + source: Box::new(source), + })?; + let (invariant_prefix, variant) = glob.partition(); + + let joined = package_dir.join(&invariant_prefix).clean(); + let stripped = joined.strip_prefix(workspace_root).map_err(|_| { + ResolveTaskConfigError::GlobOutsideWorkspace { pattern: Str::from(pattern) } + })?; + + // Re-escape the prefix path for use in a glob pattern + let stripped = stripped.ok_or_else(|| ResolveTaskConfigError::GlobOutsideWorkspace { + pattern: Str::from(pattern), + })?; + + let escaped_prefix = wax::escape(stripped.as_str()); + + let result = match variant { + Some(variant_glob) if escaped_prefix.is_empty() => { + Str::from(variant_glob.to_string().as_str()) + } + Some(variant_glob) => vite_str::format!("{escaped_prefix}/{variant_glob}"), + None if escaped_prefix.is_empty() => Str::from("**"), + None => Str::from(escaped_prefix.as_ref()), + }; + + Ok(result) } #[derive(Debug, Clone, Serialize)] @@ -97,6 +250,18 @@ pub enum ResolveTaskConfigError { /// Neither package.json script nor vite.config.* task define a command for the task #[error("Neither package.json script nor vite.config.* task define a command for the task")] NoCommand, + + /// A glob pattern resolves to a path outside the workspace root + #[error("glob pattern '{pattern}' resolves outside the workspace root")] + GlobOutsideWorkspace { pattern: Str }, + + /// A glob pattern is invalid + #[error("invalid glob pattern '{pattern}'")] + InvalidGlob { + pattern: Str, + #[source] + source: Box, + }, } impl ResolvedTaskConfig { @@ -104,15 +269,23 @@ impl ResolvedTaskConfig { /// /// Always resolves with caching enabled (default settings). /// The global cache config is applied at plan time, not here. - #[must_use] + /// + /// # Errors + /// + /// Returns [`ResolveTaskConfigError`] if glob resolution fails. pub fn resolve_package_json_script( package_dir: &Arc, package_json_script: &str, - ) -> Self { - Self { + workspace_root: &AbsolutePath, + ) -> Result { + Ok(Self { command: package_json_script.into(), - resolved_options: ResolvedTaskOptions::resolve(UserTaskOptions::default(), package_dir), - } + resolved_options: ResolvedTaskOptions::resolve( + UserTaskOptions::default(), + package_dir, + workspace_root, + )?, + }) } /// Resolves from user config, package dir, and package.json script (if any). @@ -125,6 +298,7 @@ impl ResolvedTaskConfig { user_config: UserTaskConfig, package_dir: &Arc, package_json_script: Option<&str>, + workspace_root: &AbsolutePath, ) -> Result { let command = match (&user_config.command, package_json_script) { (Some(_), Some(_)) => return Err(ResolveTaskConfigError::CommandConflict), @@ -134,7 +308,11 @@ impl ResolvedTaskConfig { }; Ok(Self { command: command.into(), - resolved_options: ResolvedTaskOptions::resolve(user_config.options, package_dir), + resolved_options: ResolvedTaskOptions::resolve( + user_config.options, + package_dir, + workspace_root, + )?, }) } } @@ -203,3 +381,153 @@ pub const DEFAULT_PASSTHROUGH_ENVS: &[&str] = &[ // Token patterns "*_TOKEN", ]; + +#[cfg(test)] +mod tests { + use vite_path::AbsolutePathBuf; + + use super::*; + + fn test_paths() -> (AbsolutePathBuf, AbsolutePathBuf) { + if cfg!(windows) { + ( + AbsolutePathBuf::new("C:\\workspace\\packages\\my-pkg".into()).unwrap(), + AbsolutePathBuf::new("C:\\workspace".into()).unwrap(), + ) + } else { + ( + AbsolutePathBuf::new("/workspace/packages/my-pkg".into()).unwrap(), + AbsolutePathBuf::new("/workspace".into()).unwrap(), + ) + } + } + + #[test] + fn test_resolved_input_config_default_auto() { + let config = ResolvedInputConfig::default_auto(); + assert!(config.includes_auto); + assert!(config.positive_globs.is_empty()); + assert!(config.negative_globs.is_empty()); + } + + #[test] + fn test_resolved_input_config_from_none() { + let (pkg, ws) = test_paths(); + // None means default to auto-inference + let config = ResolvedInputConfig::from_user_config(None, &pkg, &ws).unwrap(); + assert!(config.includes_auto); + assert!(config.positive_globs.is_empty()); + assert!(config.negative_globs.is_empty()); + } + + #[test] + fn test_resolved_input_config_empty_array() { + let (pkg, ws) = test_paths(); + // Empty array means no inputs, inference disabled + let user_inputs = vec![]; + let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + assert!(!config.includes_auto); + assert!(config.positive_globs.is_empty()); + assert!(config.negative_globs.is_empty()); + } + + #[test] + fn test_resolved_input_config_auto_only() { + let (pkg, ws) = test_paths(); + let user_inputs = vec![UserInputEntry::Auto { auto: true }]; + let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + assert!(config.includes_auto); + assert!(config.positive_globs.is_empty()); + assert!(config.negative_globs.is_empty()); + } + + #[test] + fn test_resolved_input_config_auto_false_ignored() { + let (pkg, ws) = test_paths(); + let user_inputs = vec![UserInputEntry::Auto { auto: false }]; + let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + assert!(!config.includes_auto); + assert!(config.positive_globs.is_empty()); + assert!(config.negative_globs.is_empty()); + } + + #[test] + fn test_resolved_input_config_globs_only() { + let (pkg, ws) = test_paths(); + // Globs without auto means inference disabled + let user_inputs = vec![ + UserInputEntry::Glob("src/**/*.ts".into()), + UserInputEntry::Glob("package.json".into()), + ]; + let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + assert!(!config.includes_auto); + assert_eq!(config.positive_globs.len(), 2); + // Globs should now be workspace-root-relative + assert!(config.positive_globs.contains("packages/my-pkg/src/**/*.ts")); + assert!(config.positive_globs.contains("packages/my-pkg/package.json")); + assert!(config.negative_globs.is_empty()); + } + + #[test] + fn test_resolved_input_config_negative_globs() { + let (pkg, ws) = test_paths(); + let user_inputs = vec![ + UserInputEntry::Glob("src/**".into()), + UserInputEntry::Glob("!src/**/*.test.ts".into()), + ]; + let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + assert!(!config.includes_auto); + assert_eq!(config.positive_globs.len(), 1); + assert!(config.positive_globs.contains("packages/my-pkg/src/**")); + assert_eq!(config.negative_globs.len(), 1); + assert!(config.negative_globs.contains("packages/my-pkg/src/**/*.test.ts")); + } + + #[test] + fn test_resolved_input_config_mixed() { + let (pkg, ws) = test_paths(); + let user_inputs = vec![ + UserInputEntry::Glob("package.json".into()), + UserInputEntry::Auto { auto: true }, + UserInputEntry::Glob("!node_modules/**".into()), + ]; + let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + assert!(config.includes_auto); + assert_eq!(config.positive_globs.len(), 1); + assert!(config.positive_globs.contains("packages/my-pkg/package.json")); + assert_eq!(config.negative_globs.len(), 1); + assert!(config.negative_globs.contains("packages/my-pkg/node_modules/**")); + } + + #[test] + fn test_resolved_input_config_globs_with_auto() { + let (pkg, ws) = test_paths(); + // Globs with auto keeps inference enabled + let user_inputs = + vec![UserInputEntry::Glob("src/**/*.ts".into()), UserInputEntry::Auto { auto: true }]; + let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + assert!(config.includes_auto); + } + + #[test] + fn test_resolved_input_config_dotdot_resolution() { + let (pkg, ws) = test_paths(); + let user_inputs = vec![UserInputEntry::Glob("../shared/src/**".into())]; + let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + assert_eq!(config.positive_globs.len(), 1); + assert!( + config.positive_globs.contains("packages/shared/src/**"), + "expected 'packages/shared/src/**', got {:?}", + config.positive_globs + ); + } + + #[test] + fn test_resolved_input_config_outside_workspace_error() { + let (pkg, ws) = test_paths(); + let user_inputs = vec![UserInputEntry::Glob("../../../outside/**".into())]; + let result = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), ResolveTaskConfigError::GlobOutsideWorkspace { .. })); + } +} diff --git a/crates/vite_task_graph/src/config/user.rs b/crates/vite_task_graph/src/config/user.rs index 1e8c552a..6210fadf 100644 --- a/crates/vite_task_graph/src/config/user.rs +++ b/crates/vite_task_graph/src/config/user.rs @@ -10,6 +10,28 @@ use ts_rs::TS; use vite_path::RelativePathBuf; use vite_str::Str; +/// A single input entry in the `inputs` array. +/// +/// Inputs can be either glob patterns (strings) or auto-inference directives (`{auto: true}`). +#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] +// TS derive macro generates code using std types that clippy disallows; skip derive during linting +#[cfg_attr(all(test, not(clippy)), derive(TS))] +#[serde(untagged)] +pub enum UserInputEntry { + /// Glob pattern (positive or negative starting with `!`) + Glob(Str), + /// Auto-inference directive + Auto { + /// Automatically track which files the task reads + auto: bool, + }, +} + +/// The inputs configuration for cache fingerprinting. +/// +/// Default (when field omitted): `[{auto: true}]` - infer from file accesses. +pub type UserInputsConfig = Vec; + /// Cache-related fields of a task defined by user in `vite.config.*` #[derive(Debug, Deserialize, PartialEq, Eq)] // TS derive macro generates code using std types that clippy disallows; skip derive during linting @@ -58,6 +80,19 @@ pub struct EnabledCacheConfig { /// Environment variable names to be passed to the task without fingerprinting. pub pass_through_envs: Option>, + + /// Files to include in the cache fingerprint. + /// + /// - Omitted: automatically tracks which files the task reads + /// - `[]` (empty): disables file tracking entirely + /// - Glob patterns (e.g. `"src/**"`) select specific files + /// - `{auto: true}` enables automatic file tracking + /// - Negative patterns (e.g. `"!dist/**"`) exclude matched files + /// + /// Patterns are relative to the package directory. + #[serde(default)] + #[cfg_attr(all(test, not(clippy)), ts(inline))] + pub inputs: Option, } /// Options for user-defined tasks in `vite.config.*`, excluding the command. @@ -89,7 +124,11 @@ impl Default for UserTaskOptions { // Caching enabled with no fingerprinted envs cache_config: UserCacheConfig::Enabled { cache: None, - enabled_cache_config: EnabledCacheConfig { envs: None, pass_through_envs: None }, + enabled_cache_config: EnabledCacheConfig { + envs: None, + pass_through_envs: None, + inputs: None, + }, }, } } @@ -374,11 +413,95 @@ mod tests { enabled_cache_config: EnabledCacheConfig { envs: Some(std::iter::once("NODE_ENV".into()).collect()), pass_through_envs: Some(std::iter::once("FOO".into()).collect()), + inputs: None, } }, ); } + #[test] + fn test_inputs_empty_array() { + let user_config_json = json!({ + "inputs": [] + }); + let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!(config.inputs, Some(vec![])); + } + + #[test] + fn test_inputs_auto_true() { + let user_config_json = json!({ + "inputs": [{ "auto": true }] + }); + let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!(config.inputs, Some(vec![UserInputEntry::Auto { auto: true }])); + } + + #[test] + fn test_inputs_auto_false() { + let user_config_json = json!({ + "inputs": [{ "auto": false }] + }); + let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!(config.inputs, Some(vec![UserInputEntry::Auto { auto: false }])); + } + + #[test] + fn test_inputs_globs() { + let user_config_json = json!({ + "inputs": ["src/**/*.ts", "package.json"] + }); + let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!( + config.inputs, + Some(vec![ + UserInputEntry::Glob("src/**/*.ts".into()), + UserInputEntry::Glob("package.json".into()), + ]) + ); + } + + #[test] + fn test_inputs_negative_globs() { + let user_config_json = json!({ + "inputs": ["src/**", "!src/**/*.test.ts"] + }); + let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!( + config.inputs, + Some(vec![ + UserInputEntry::Glob("src/**".into()), + UserInputEntry::Glob("!src/**/*.test.ts".into()), + ]) + ); + } + + #[test] + fn test_inputs_mixed() { + let user_config_json = json!({ + "inputs": ["package.json", { "auto": true }, "!node_modules/**"] + }); + let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!( + config.inputs, + Some(vec![ + UserInputEntry::Glob("package.json".into()), + UserInputEntry::Auto { auto: true }, + UserInputEntry::Glob("!node_modules/**".into()), + ]) + ); + } + + #[test] + fn test_inputs_with_cache_false_error() { + // inputs with cache: false should produce a serde error due to deny_unknown_fields + let user_config_json = json!({ + "cache": false, + "inputs": ["src/**"] + }); + assert!(serde_json::from_value::(user_config_json).is_err()); + } + #[test] fn test_cache_disabled_but_with_fields() { let user_config_json = json!({ diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index d0f91a81..91016619 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -276,6 +276,7 @@ impl IndexedTaskGraph { task_user_config, &package_dir, package_json_script, + &workspace_root.path, ) .map_err(|err| TaskGraphLoadError::ResolveConfigError { error: err, @@ -307,7 +308,16 @@ impl IndexedTaskGraph { let resolved_config = ResolvedTaskConfig::resolve_package_json_script( &package_dir, package_json_script, - ); + &workspace_root.path, + ) + .map_err(|err| TaskGraphLoadError::ResolveConfigError { + error: err, + task_display: TaskDisplay { + package_name: package.package_json.name.clone(), + task_name: script_name.into(), + package_path: Arc::clone(&package_dir), + }, + })?; let node_index = task_graph.add_node(TaskNode { task_display: TaskDisplay { package_name: package.package_json.name.clone(), diff --git a/crates/vite_task_plan/src/cache_metadata.rs b/crates/vite_task_plan/src/cache_metadata.rs index 9fcb3215..15a0b85b 100644 --- a/crates/vite_task_plan/src/cache_metadata.rs +++ b/crates/vite_task_plan/src/cache_metadata.rs @@ -4,6 +4,7 @@ use bincode::{Decode, Encode}; use serde::{Deserialize, Serialize}; use vite_path::RelativePathBuf; use vite_str::{self, Str}; +use vite_task_graph::config::ResolvedInputConfig; use crate::envs::EnvFingerprints; @@ -34,13 +35,17 @@ pub enum ExecutionCacheKey { /// It only contains information needed for hitting existing cache entries pre-execution. /// It doesn't contain any post-execution information like file fingerprints /// (which needs actual execution and is out of scope for planning). -#[derive(Debug, Encode, Decode, Serialize)] +#[derive(Debug, Serialize)] pub struct CacheMetadata { /// Fingerprint for spawn execution that affects caching. pub spawn_fingerprint: SpawnFingerprint, /// Key to identify an execution across sessions. pub execution_cache_key: ExecutionCacheKey, + + /// Resolved input configuration for cache fingerprinting. + /// Used at execution time to determine what files to track. + pub input_config: ResolvedInputConfig, } /// Fingerprint for spawn execution that affects caching. @@ -62,32 +67,15 @@ pub struct CacheMetadata { /// - The resolver provides envs which become part of the fingerprint /// - If resolver provides different envs between runs, cache breaks /// - Each built-in task type must have unique task name to avoid cache collision -/// -/// # Fingerprint Ignores Impact on Cache -/// -/// The `fingerprint_ignores` field controls which files are tracked in `PostRunFingerprint`: -/// - Changes to this config must invalidate the cache -/// - Vec maintains insertion order (pattern order matters for last-match-wins semantics) -/// - Even though ignore patterns only affect `PostRunFingerprint`, the config itself is part of the cache key #[derive(Encode, Decode, Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct SpawnFingerprint { pub(crate) cwd: RelativePathBuf, pub(crate) program_fingerprint: ProgramFingerprint, pub(crate) args: Arc<[Str]>, pub(crate) env_fingerprints: EnvFingerprints, - - /// Glob patterns for fingerprint filtering. Order matters (last match wins). - /// Changes to this config invalidate the cache to ensure correct fingerprint tracking. - pub(crate) fingerprint_ignores: Option>, } impl SpawnFingerprint { - /// Get the fingerprint ignores patterns. - #[must_use] - pub const fn fingerprint_ignores(&self) -> Option<&Vec> { - self.fingerprint_ignores.as_ref() - } - /// Get the environment fingerprints. #[must_use] pub const fn env_fingerprints(&self) -> &EnvFingerprints { diff --git a/crates/vite_task_plan/src/error.rs b/crates/vite_task_plan/src/error.rs index c2dd9090..fea79d03 100644 --- a/crates/vite_task_plan/src/error.rs +++ b/crates/vite_task_plan/src/error.rs @@ -137,6 +137,13 @@ pub enum Error { #[error("Failed to resolve environment variables")] ResolveEnv(#[source] ResolveEnvError), + #[error("Failed to resolve task configuration")] + ResolveTaskConfig( + #[source] + #[from] + vite_task_graph::config::ResolveTaskConfigError, + ), + #[error("No task specifier provided for 'run' command")] MissingTaskSpecifier, diff --git a/crates/vite_task_plan/src/plan.rs b/crates/vite_task_plan/src/plan.rs index 0311f6e4..76efc362 100644 --- a/crates/vite_task_plan/src/plan.rs +++ b/crates/vite_task_plan/src/plan.rs @@ -391,29 +391,33 @@ pub enum ParentCacheConfig { /// env config and merges in any additional envs the synthetic command needs. /// - If there is no parent (top-level invocation), the synthetic task's own /// [`UserCacheConfig`] is resolved with defaults. +#[expect(clippy::result_large_err, reason = "Error is large for diagnostics")] fn resolve_synthetic_cache_config( parent: ParentCacheConfig, synthetic_cache_config: UserCacheConfig, cwd: &Arc, -) -> Option { + workspace_path: &AbsolutePath, +) -> Result, Error> { match parent { ParentCacheConfig::None => { // Top-level: resolve from synthetic's own config - ResolvedTaskOptions::resolve( + Ok(ResolvedTaskOptions::resolve( UserTaskOptions { cache_config: synthetic_cache_config, cwd_relative_to_package: None, depends_on: None, }, cwd, + workspace_path, ) - .cache_config + .map_err(Error::ResolveTaskConfig)? + .cache_config) } - ParentCacheConfig::Disabled => Option::None, + ParentCacheConfig::Disabled => Ok(Option::None), ParentCacheConfig::Inherited(mut parent_config) => { // Cache is enabled only if both parent and synthetic want it. // Merge synthetic's additions into parent's config. - match synthetic_cache_config { + Ok(match synthetic_cache_config { UserCacheConfig::Disabled { .. } => Option::None, UserCacheConfig::Enabled { enabled_cache_config, .. } => { if let Some(extra_envs) = enabled_cache_config.envs { @@ -424,7 +428,7 @@ fn resolve_synthetic_cache_config( } Some(parent_config) } - } + }) } } } @@ -442,7 +446,7 @@ pub fn plan_synthetic_request( let program_path = which(&program, &envs, cwd)?; let resolved_cache_config = - resolve_synthetic_cache_config(parent_cache_config, cache_config, cwd); + resolve_synthetic_cache_config(parent_cache_config, cache_config, cwd, workspace_path)?; let resolved_options = ResolvedTaskOptions { cwd: Arc::clone(cwd), cache_config: resolved_cache_config }; @@ -544,11 +548,13 @@ fn plan_spawn_execution( program_fingerprint, args: Arc::clone(&args), env_fingerprints, - fingerprint_ignores: None, }; if let Some(execution_cache_key) = execution_cache_key { - resolved_cache_metadata = - Some(CacheMetadata { spawn_fingerprint, execution_cache_key }); + resolved_cache_metadata = Some(CacheMetadata { + spawn_fingerprint, + execution_cache_key, + input_config: cache_config.input_config.clone(), + }); } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/snapshots/query - env-test synthetic task in user task.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/snapshots/query - env-test synthetic task in user task.snap index 94d675ad..ab8ef29f 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/snapshots/query - env-test synthetic task in user task.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/snapshots/query - env-test synthetic task in user task.snap @@ -51,8 +51,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs "TEST_VAR", "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -61,6 +60,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/snapshots/task graph.snap index 19119cb4..d98da2ef 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache enables script caching.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache enables script caching.snap index 06a1e725..efb6c31e 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache enables script caching.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache enables script caching.snap @@ -51,8 +51,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-overri "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -61,6 +60,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-overri "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache enables task caching even when cache.tasks is false.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache enables task caching even when cache.tasks is false.snap index 0b09b38f..f555e0db 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache enables task caching even when cache.tasks is false.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache enables task caching even when cache.tasks is false.snap @@ -51,8 +51,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-overri "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -61,6 +60,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-overri "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache on task with per-task cache true enables caching.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache on task with per-task cache true enables caching.snap index 517c6f69..b4d5824e 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache on task with per-task cache true enables caching.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/query - --cache on task with per-task cache true enables caching.snap @@ -51,8 +51,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-overri "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -61,6 +60,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-overri "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/task graph.snap index 59d13b75..afa12573 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-override/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-overri "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-overri "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -105,6 +115,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-overri "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -134,6 +149,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-cli-overri "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - echo and lint with extra args.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - echo and lint with extra args.snap index 47472c37..97c9ecb4 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - echo and lint with extra args.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - echo and lint with extra args.snap @@ -77,8 +77,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -89,6 +88,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys ], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - lint and echo with extra args.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - lint and echo with extra args.snap index 9e967e68..59e7604a 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - lint and echo with extra args.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - lint and echo with extra args.snap @@ -49,8 +49,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -59,6 +58,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - normal task with extra args.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - normal task with extra args.snap index 85be7c84..0120e12d 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - normal task with extra args.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - normal task with extra args.snap @@ -51,8 +51,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -63,6 +62,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys ], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task in user task with cwd.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task in user task with cwd.snap index 2463a68c..0cd398a9 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task in user task with cwd.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task in user task with cwd.snap @@ -49,8 +49,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -59,6 +58,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task in user task.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task in user task.snap index 0ea7bfa0..8e413cc9 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task in user task.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task in user task.snap @@ -48,8 +48,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -58,6 +57,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task with extra args in user task.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task with extra args in user task.snap index b2b5c3db..2de49ed2 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task with extra args in user task.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/query - synthetic task with extra args in user task.snap @@ -51,8 +51,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -63,6 +62,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys ], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/task graph.snap index 1159aa12..71d9d8f8 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -112,6 +127,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/snapshots/task graph.snap index 852aa4cf..0baec88d 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-de "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-de "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/snapshots/task graph.snap index fcaa05d4..5874e5ed 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-en "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-en "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/query - task cached by default.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/query - task cached by default.snap index 54a470d9..53cb5467 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/query - task cached by default.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/query - task cached by default.snap @@ -50,8 +50,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-ta "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -60,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-ta "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/query - task with command cached by default.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/query - task with command cached by default.snap index b816a161..56d84f3e 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/query - task with command cached by default.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/query - task with command cached by default.snap @@ -50,8 +50,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-ta "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -60,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-ta "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/task graph.snap index 4c99d0c5..59a60df8 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing/snapshots/task graph.snap index 75c12f88..21f30663 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-subcommand/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-subcommand/snapshots/task graph.snap index bc3ce966..0551f120 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-subcommand/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-subcommand/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-subcommand "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-tasks-disabled/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-tasks-disabled/snapshots/task graph.snap index 370cea7b..6d344ca2 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-tasks-disabled/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-tasks-disabled/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-tasks-disa "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-tasks-disa "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-tasks-disa "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/query - script cached when global cache true.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/query - script cached when global cache true.snap index 2e7fc39b..a002c6f8 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/query - script cached when global cache true.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/query - script cached when global cache true.snap @@ -50,8 +50,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-fo "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -60,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-fo "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/query - task cached when global cache true.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/query - task cached when global cache true.snap index 50210a10..d3a055e9 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/query - task cached when global cache true.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/query - task cached when global cache true.snap @@ -50,8 +50,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-fo "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -60,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-fo "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/task graph.snap index a865f485..b5fe5033 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-force-enable/snapshots/task graph.snap @@ -47,6 +47,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-fo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -76,6 +81,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-true-no-fo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/query - cd before vp lint should put synthetic task under cwd.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/query - cd before vp lint should put synthetic task under cwd.snap index 7da67793..756a1801 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/query - cd before vp lint should put synthetic task under cwd.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/query - cd before vp lint should put synthetic task under cwd.snap @@ -48,8 +48,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -58,6 +57,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/query - cd before vp run should not affect expanded task cwd.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/query - cd before vp run should not affect expanded task cwd.snap index 4c6f8fe4..8e8428c2 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/query - cd before vp run should not affect expanded task cwd.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/query - cd before vp run should not affect expanded task cwd.snap @@ -75,8 +75,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -85,6 +84,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/task graph.snap index 09efb044..df0d8a20 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cd-in-scripts "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-task-graph/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-task-graph/snapshots/task graph.snap index a4a0ceda..8c6d3935 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-task-graph/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-task-graph/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -112,6 +127,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -141,6 +161,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -170,6 +195,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -199,6 +229,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -228,6 +263,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -257,6 +297,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -286,6 +331,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -315,6 +365,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -344,6 +399,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -373,6 +433,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -402,6 +467,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -431,6 +501,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -460,6 +535,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -489,6 +569,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -518,6 +603,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -547,6 +637,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -576,6 +671,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -605,6 +705,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -634,6 +739,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -663,6 +773,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-ta "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/snapshots/task graph.snap index 441061e3..8a7028d3 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle-dependency/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle-dependency/snapshots/task graph.snap index 8e578194..defb4cb8 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle-dependency/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle-dependency/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle-dependency "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -59,6 +64,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle-dependency "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/snapshots/task graph.snap index 659a3440..cb2ab333 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both- "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -59,6 +64,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both- "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate-package-names/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate-package-names/snapshots/task graph.snap index 3df2b750..8e9555c8 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate-package-names/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate-package-names/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate-packag "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate-packag "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/snapshots/task graph.snap index f33ccfee..680f38d2 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-te "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +99,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-te "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -123,6 +133,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-te "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -152,6 +167,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-te "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -186,6 +206,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-te "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -215,6 +240,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-te "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -244,6 +274,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-te "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -273,6 +308,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-te "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/snapshots/task graph.snap index 316a0ad9..caeb9f8a 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-wo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -89,6 +94,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-wo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -118,6 +128,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-wo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -147,6 +162,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-wo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -176,6 +196,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-wo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -205,6 +230,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-wo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -239,6 +269,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-wo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -268,6 +303,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-wo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -297,6 +337,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-wo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -335,6 +380,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-wo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace/snapshots/task graph.snap index 16271d36..c0311c55 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -112,6 +127,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -141,6 +161,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -170,6 +195,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -199,6 +229,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -228,6 +263,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -257,6 +297,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -286,6 +331,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -315,6 +365,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -344,6 +399,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -373,6 +433,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/filter-workspace "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/snapshots/task graph.snap index 0036eca1..9b1922dc 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-igno "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/package.json new file mode 100644 index 00000000..600e5f1a --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/package.json @@ -0,0 +1,3 @@ +{ + "name": "test" +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/snapshots.toml new file mode 100644 index 00000000..8370d8ce --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/snapshots.toml @@ -0,0 +1 @@ +# Test that trailing `/` in glob patterns is expanded to `/**` diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/snapshots/task graph.snap new file mode 100644 index 00000000..071c92e6 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/snapshots/task graph.snap @@ -0,0 +1,45 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: task_graph_json +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash +--- +[ + { + "key": [ + "/", + "build" + ], + "node": { + "task_display": { + "package_name": "test", + "task_name": "build", + "package_path": "/" + }, + "resolved_config": { + "command": "echo build", + "resolved_options": { + "cwd": "/", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "pass_through_envs": [ + "" + ] + }, + "input_config": { + "includes_auto": false, + "positive_globs": [ + "src/**" + ], + "negative_globs": [ + "dist/**" + ] + } + } + } + }, + "source": "TaskConfig" + }, + "neighbors": [] + } +] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/vite-task.json new file mode 100644 index 00000000..563fc807 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/inputs-trailing-slash/vite-task.json @@ -0,0 +1,9 @@ +{ + "tasks": { + "build": { + "command": "echo build", + "inputs": ["src/", "!dist/"], + "cache": true + } + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - nested --cache enables inner task caching.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - nested --cache enables inner task caching.snap index f80fb7d7..3af88331 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - nested --cache enables inner task caching.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - nested --cache enables inner task caching.snap @@ -75,8 +75,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-ove "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -85,6 +84,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-ove "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - outer --cache propagates to nested run without flags.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - outer --cache propagates to nested run without flags.snap index 89d3c5aa..42044a23 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - outer --cache propagates to nested run without flags.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - outer --cache propagates to nested run without flags.snap @@ -76,8 +76,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-ove "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -86,6 +85,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-ove "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - outer --no-cache does not propagate into nested --cache.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - outer --no-cache does not propagate into nested --cache.snap index 51d15e09..81d55603 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - outer --no-cache does not propagate into nested --cache.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/query - outer --no-cache does not propagate into nested --cache.snap @@ -76,8 +76,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-ove "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -86,6 +85,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-ove "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/task graph.snap index 72e7589a..a414fcad 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-override/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-ove "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-ove "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-ove "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -112,6 +127,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-cache-ove "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-tasks/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-tasks/snapshots/task graph.snap index f70f89a4..cc63812a 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-tasks/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-tasks/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-tasks "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-tasks "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm-workspace-packages-optional/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm-workspace-packages-optional/snapshots/task graph.snap index 728b66a7..7dcef729 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm-workspace-packages-optional/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm-workspace-packages-optional/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm-workspace-p "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topological-workspace/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topological-workspace/snapshots/task graph.snap index 5a9fd1c4..5f1b9f51 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topological-workspace/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topological-workspace/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topolo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topolo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topolo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -112,6 +127,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topolo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -141,6 +161,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topolo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -170,6 +195,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topolo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -199,6 +229,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topolo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -228,6 +263,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topolo "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/snapshots/query - shell fallback for pipe command.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/snapshots/query - shell fallback for pipe command.snap index 98ff790d..056a81e0 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/snapshots/query - shell fallback for pipe command.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/snapshots/query - shell fallback for pipe command.snap @@ -51,8 +51,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -61,6 +60,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/snapshots/task graph.snap index 79b113c1..a37823a4 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - parent cache false does not affect expanded query tasks.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - parent cache false does not affect expanded query tasks.snap index be161143..c0715ca4 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - parent cache false does not affect expanded query tasks.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - parent cache false does not affect expanded query tasks.snap @@ -73,8 +73,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -83,6 +82,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - script cache false does not affect expanded synthetic cache.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - script cache false does not affect expanded synthetic cache.snap index 403abd2f..dd59626d 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - script cache false does not affect expanded synthetic cache.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - script cache false does not affect expanded synthetic cache.snap @@ -73,8 +73,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -83,6 +82,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task passThroughEnvs inherited by synthetic.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task passThroughEnvs inherited by synthetic.snap index a6bdf4a7..b4d5d242 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task passThroughEnvs inherited by synthetic.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task passThroughEnvs inherited by synthetic.snap @@ -49,8 +49,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "CUSTOM_VAR", "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -59,6 +58,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task with cache true enables synthetic cache.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task with cache true enables synthetic cache.snap index 03de80b4..782d7ce9 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task with cache true enables synthetic cache.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task with cache true enables synthetic cache.snap @@ -48,8 +48,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -58,6 +57,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "extra_args": [], "package_path": "" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/task graph.snap index 7be0e02e..dc55b774 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -105,6 +115,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -135,6 +150,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "CUSTOM_VAR", "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -164,6 +184,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache- "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/snapshots/query - synthetic-in-subpackage.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/snapshots/query - synthetic-in-subpackage.snap index 411dd5e4..34cf09c1 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/snapshots/query - synthetic-in-subpackage.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/snapshots/query - synthetic-in-subpackage.snap @@ -73,8 +73,7 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-sub "pass_through_env_config": [ "" ] - }, - "fingerprint_ignores": null + } }, "execution_cache_key": { "UserTask": { @@ -83,6 +82,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-sub "extra_args": [], "package_path": "packages/a" } + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/snapshots/task graph.snap index 07ed52b1..20a50f36 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-sub "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-sub "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive-skip-intermediate/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive-skip-intermediate/snapshots/task graph.snap index 30771c36..949e3f91 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive-skip-intermediate/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive-skip-intermediate/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive-skip- "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive-skip- "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive-skip- "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr-shorthand/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr-shorthand/snapshots/task graph.snap index 5aeff92d..50d674f8 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr-shorthand/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr-shorthand/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr-shorthand "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr-shorthand "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-cd-no-skip/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-cd-no-skip/snapshots/task graph.snap index 49028ec3..1e34a621 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-cd-no-skip/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-cd-no-skip/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-c "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-c "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-depends-on-passthrough/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-depends-on-passthrough/snapshots/task graph.snap index 045c90df..f59e82f9 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-depends-on-passthrough/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-depends-on-passthrough/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-d "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -59,6 +64,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-d "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -88,6 +98,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-d "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -117,6 +132,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-d "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-multi-command/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-multi-command/snapshots/task graph.snap index 778e1be9..60af8782 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-multi-command/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-multi-command/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-m "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-m "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-mutual-recursion/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-mutual-recursion/snapshots/task graph.snap index 9afec801..deb8564a 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-mutual-recursion/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-mutual-recursion/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-m "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-m "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-m "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -112,6 +127,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-m "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots/task graph.snap index 520f4db9..151c7785 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-n "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-n "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-self-reference/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-self-reference/snapshots/task graph.snap index e8b45c72..317b13b3 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-self-reference/snapshots/task graph.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-self-reference/snapshots/task graph.snap @@ -25,6 +25,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-s "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -54,6 +59,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-s "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } @@ -83,6 +93,11 @@ input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-s "pass_through_envs": [ "" ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/main.rs b/crates/vite_task_plan/tests/plan_snapshots/main.rs index 133ca597..1ea6877f 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/main.rs +++ b/crates/vite_task_plan/tests/plan_snapshots/main.rs @@ -316,10 +316,12 @@ fn main() { let tests_dir = std::env::current_dir().unwrap().join("tests"); - insta::glob!(tests_dir, "plan_snapshots/fixtures/*", |case_path| run_case( - &tokio_runtime, - &tmp_dir_path, - case_path, - filter.as_deref() - )); + insta::glob!(tests_dir, "plan_snapshots/fixtures/*", |case_path| { + run_case(&tokio_runtime, &tmp_dir_path, case_path, filter.as_deref()); + }); + + #[expect(clippy::print_stdout, reason = "test summary")] + { + println!("All cases passed."); + } } diff --git a/crates/vite_workspace/Cargo.toml b/crates/vite_workspace/Cargo.toml index da803569..9dd53ad8 100644 --- a/crates/vite_workspace/Cargo.toml +++ b/crates/vite_workspace/Cargo.toml @@ -9,7 +9,6 @@ rust-version.workspace = true [dependencies] clap = { workspace = true, features = ["derive"] } -path-clean = { workspace = true } petgraph = { workspace = true, features = ["serde-1"] } rustc-hash = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/vite_workspace/src/package_filter.rs b/crates/vite_workspace/src/package_filter.rs index e5c3a02e..07fc7fde 100644 --- a/crates/vite_workspace/src/package_filter.rs +++ b/crates/vite_workspace/src/package_filter.rs @@ -32,7 +32,7 @@ use std::{ops::Deref, sync::Arc}; use vec1::Vec1; -use vite_path::{AbsolutePath, AbsolutePathBuf}; +use vite_path::AbsolutePath; use vite_str::Str; use crate::package_graph::PackageQuery; @@ -590,10 +590,7 @@ fn resolve_directory_pattern( /// results when symlinks are involved (e.g. `/a/symlink/../b` → `/a/b`). This /// matches pnpm's behaviour. fn resolve_filter_path(path_str: &str, cwd: &AbsolutePath) -> Arc { - let cleaned = path_clean::clean(cwd.join(path_str).as_path()); - let normalized = AbsolutePathBuf::new(cleaned) - .expect("invariant: cleaning an absolute path preserves absoluteness"); - normalized.into() + cwd.join(path_str).clean().into() } /// Build a [`PackageNamePattern`] from a name or glob string. diff --git a/docs/inputs.md b/docs/inputs.md new file mode 100644 index 00000000..ddd10772 --- /dev/null +++ b/docs/inputs.md @@ -0,0 +1,221 @@ +# Task Inputs Configuration + +The `inputs` field controls which files are tracked for cache invalidation. When any tracked input file changes, the task's cache is invalidated and the task will re-run. + +## Default Behavior + +When `inputs` is omitted, vite-task automatically tracks files that the command reads during execution using fspy (file system spy): + +```json +{ + "tasks": { + "build": { + "command": "tsc" + } + } +} +``` + +Files read by `tsc` are automatically tracked. **In most cases, you don't need to configure `inputs` at all.** + +## Input Types + +### Glob Patterns + +Specify files using glob patterns relative to the package directory: + +```json +{ + "inputs": ["src/**/*.ts", "package.json"] +} +``` + +Supported glob syntax: + +- `*` - matches any characters except `/` +- `**` - matches any characters including `/` +- `?` - matches a single character +- `[abc]` - matches any character in the brackets +- `[!abc]` - matches any character not in the brackets + +### Auto-Inference + +Enable automatic input detection using fspy (file system spy): + +```json +{ + "inputs": [{ "auto": true }] +} +``` + +When enabled, vite-task automatically tracks files that the command actually reads during execution. This is the default behavior when `inputs` is omitted. + +### Negative Patterns + +Exclude files from tracking using `!` prefix: + +```json +{ + "inputs": ["src/**", "!src/**/*.test.ts"] +} +``` + +Negative patterns filter out files that would otherwise be matched by positive patterns or auto-inference. + +## Configuration Examples + +### Explicit Globs Only + +Specify exact files to track, disabling auto-inference: + +```json +{ + "tasks": { + "build": { + "command": "tsc", + "inputs": ["src/**/*.ts", "tsconfig.json"] + } + } +} +``` + +Only files matching the globs are tracked. Files read by the command but not matching the globs are ignored. + +### Auto-Inference with Exclusions + +Track inferred files but exclude certain patterns: + +```json +{ + "tasks": { + "build": { + "command": "tsc", + "inputs": [{ "auto": true }, "!dist/**", "!node_modules/**"] + } + } +} +``` + +Files in `dist/` and `node_modules/` won't trigger cache invalidation even if the command reads them. + +### Mixed Mode + +Combine explicit globs with auto-inference: + +```json +{ + "tasks": { + "build": { + "command": "tsc", + "inputs": ["package.json", { "auto": true }, "!**/*.test.ts"] + } + } +} +``` + +- `package.json` is always tracked (explicit) +- Files read by the command are tracked (auto) +- Test files are excluded from both (negative pattern) + +### No File Inputs + +Disable all file tracking (cache only on command/env changes): + +```json +{ + "tasks": { + "echo": { + "command": "echo hello", + "inputs": [] + } + } +} +``` + +The cache will only invalidate when the command itself or environment variables change. + +## Behavior Summary + +| Configuration | Auto-Inference | File Tracking | +| ---------------------------------------- | -------------- | ----------------------------- | +| `inputs` omitted | Enabled | Inferred files | +| `inputs: [{ "auto": true }]` | Enabled | Inferred files | +| `inputs: ["src/**"]` | Disabled | Matched files only | +| `inputs: [{ "auto": true }, "!dist/**"]` | Enabled | Inferred files except `dist/` | +| `inputs: ["pkg.json", { "auto": true }]` | Enabled | `pkg.json` + inferred files | +| `inputs: []` | Disabled | No files tracked | + +## Important Notes + +### Glob Base Directory + +Glob patterns are resolved relative to the **package directory** (where `package.json` is located), not the task's working directory (`cwd`). + +```json +{ + "tasks": { + "build": { + "command": "tsc", + "cwd": "src", + "inputs": ["src/**/*.ts"] // Still relative to package root + } + } +} +``` + +### Negative Patterns Apply to Both Modes + +When using mixed mode, negative patterns filter both explicit globs AND auto-inferred files: + +```json +{ + "inputs": ["src/**", { "auto": true }, "!**/*.generated.ts"] +} +``` + +Files matching `*.generated.ts` are excluded whether they come from the `src/**` glob or from auto-inference. + +### Auto-Inference Behavior + +The auto-inference (fspy) is intentionally **cautious** - it tracks all files that a command reads, even auxiliary files. This means **negative patterns are expected to be useful** for filtering out files you don't want to trigger cache invalidation. + +Common files you might want to exclude: + +```json +{ + "inputs": [ + { "auto": true }, + "!**/*.tsbuildinfo", // TypeScript incremental build info + "!**/tsconfig.tsbuildinfo", + "!dist/**" // Build outputs that get read during builds + ] +} +``` + +**When to use positive patterns vs negative patterns:** + +- **Negative patterns (expected)**: Use these to exclude files that fspy correctly detected but you don't want tracked (like `.tsbuildinfo`, cache files, build outputs) +- **Positive patterns (usually indicates a bug)**: If you find yourself adding explicit positive patterns because fspy missed files that your command actually reads, this likely indicates a bug in fspy + +If you encounter a case where fspy fails to detect a file read, please [report the issue](https://github.com/voidzero-dev/vite-task/issues) with: + +1. The command being run +2. The file(s) that weren't detected +3. Steps to reproduce + +### Cache Disabled + +The `inputs` field cannot be used with `cache: false`: + +```json +// ERROR: inputs cannot be specified when cache is disabled +{ + "tasks": { + "dev": { + "command": "vite dev", + "cache": false, + "inputs": ["src/**"] // This will cause a parse error + } + } +} +``` diff --git a/packages/tools/src/print-file.ts b/packages/tools/src/print-file.ts index 32cbf849..792f2d67 100755 --- a/packages/tools/src/print-file.ts +++ b/packages/tools/src/print-file.ts @@ -2,5 +2,7 @@ import { readFileSync } from 'node:fs'; -const content = readFileSync(process.argv[2]); -process.stdout.write(content); +for (const file of process.argv.slice(2)) { + const content = readFileSync(file); + process.stdout.write(content); +}