From 6d03bc009bcbe70aa9010e56d90d78358cfb9c13 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 7 May 2026 15:00:26 +0800 Subject: [PATCH 01/10] feat(cache): add output globs for cache restoration Adds an `output` field to cached tasks: archives files matching the configured globs after a successful run and restores them on cache hit. Supports glob patterns, negative patterns, and `{pattern, base}` form with explicit base directory. When `output` is omitted or empty, no output archiving happens (matches prior behavior). Schema version bumped to 12 (CacheEntryKey carries output_config, CacheEntryValue carries output_archive). Old caches are reset on upgrade. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 1 + Cargo.lock | 43 +++++++ Cargo.toml | 1 + crates/vite_task/Cargo.toml | 3 + crates/vite_task/src/session/cache/archive.rs | 61 ++++++++++ crates/vite_task/src/session/cache/display.rs | 1 + crates/vite_task/src/session/cache/mod.rs | 60 +++++++-- .../src/session/execute/glob_inputs.rs | 52 ++++++++ crates/vite_task/src/session/execute/mod.rs | 81 +++++++++++- crates/vite_task/src/session/mod.rs | 1 + .../vite_task/src/session/reporter/summary.rs | 4 +- crates/vite_task_bin/src/lib.rs | 1 + crates/vite_task_bin/src/vtt/write_file.rs | 6 +- .../fixtures/output_cache_test/package.json | 4 + .../fixtures/output_cache_test/snapshots.toml | 38 ++++++ ...put_globs___files_restored_on_cache_hit.md | 37 ++++++ ...___negative_excludes_files_from_archive.md | 49 ++++++++ .../fixtures/output_cache_test/src/main.ts | 1 + .../fixtures/output_cache_test/vite-task.json | 16 +++ crates/vite_task_graph/run-config.ts | 11 +- crates/vite_task_graph/src/config/mod.rs | 105 ++++++++++++---- crates/vite_task_graph/src/config/user.rs | 28 +++++ crates/vite_task_plan/src/cache_metadata.rs | 8 +- crates/vite_task_plan/src/plan.rs | 41 ++++++- ...ery_tool_synthetic_task_in_user_task.jsonc | 5 + .../additional_env/snapshots/task_graph.jsonc | 10 ++ ...query___cache_enables_script_caching.jsonc | 5 + ...ching_even_when_cache_tasks_is_false.jsonc | 5 + ..._per_task_cache_true_enables_caching.jsonc | 5 + .../snapshots/task_graph.jsonc | 20 +++ .../query_echo_and_lint_with_extra_args.jsonc | 5 + .../query_lint_and_echo_with_extra_args.jsonc | 5 + .../query_normal_task_with_extra_args.jsonc | 5 + .../query_synthetic_task_in_user_task.jsonc | 5 + ...synthetic_task_in_user_task_with_cwd.jsonc | 5 + ...ic_task_with_extra_args_in_user_task.jsonc | 5 + .../cache_keys/snapshots/task_graph.jsonc | 20 +++ .../snapshots/task_graph.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 10 ++ ...query_another_task_cached_by_default.jsonc | 5 + .../query_task_cached_by_default.jsonc | 5 + .../snapshots/task_graph.jsonc | 15 +++ .../cache_sharing/snapshots/task_graph.jsonc | 15 +++ .../snapshots/task_graph.jsonc | 5 + .../snapshots/task_graph.jsonc | 15 +++ ...script_cached_when_global_cache_true.jsonc | 5 + ...y_task_cached_when_global_cache_true.jsonc | 5 + .../snapshots/task_graph.jsonc | 10 ++ ..._should_put_synthetic_task_under_cwd.jsonc | 5 + ..._should_not_affect_expanded_task_cwd.jsonc | 5 + .../cd_in_scripts/snapshots/task_graph.jsonc | 15 +++ .../snapshots/task_graph.jsonc | 115 ++++++++++++++++++ .../conflict_test/snapshots/task_graph.jsonc | 15 +++ .../snapshots/task_graph.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 40 ++++++ .../snapshots/task_graph.jsonc | 50 ++++++++ ...extra_args_only_reach_requested_task.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 65 ++++++++++ .../snapshots/task_graph.jsonc | 5 + .../snapshots/task_graph.jsonc | 5 + ...d___cache_enables_inner_task_caching.jsonc | 5 + ...opagates_to_nested_run_without_flags.jsonc | 5 + ...es_not_propagate_into_nested___cache.jsonc | 5 + .../snapshots/task_graph.jsonc | 20 +++ .../nested_tasks/snapshots/task_graph.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 5 + .../snapshots/task_graph.jsonc | 25 ++++ .../snapshots/task_graph.jsonc | 5 + .../snapshots/task_graph.jsonc | 40 ++++++ .../script_hooks/snapshots/task_graph.jsonc | 30 +++++ .../snapshots/task_graph.jsonc | 15 +++ .../snapshots/task_graph.jsonc | 20 +++ .../snapshots/task_graph.jsonc | 10 ++ ...uery_shell_fallback_for_pipe_command.jsonc | 5 + .../shell_fallback/snapshots/task_graph.jsonc | 5 + ...does_not_affect_expanded_query_tasks.jsonc | 5 + ..._not_affect_expanded_synthetic_cache.jsonc | 5 + ..._untrackedEnv_inherited_by_synthetic.jsonc | 5 + ...h_cache_true_enables_synthetic_cache.jsonc | 5 + .../snapshots/task_graph.jsonc | 25 ++++ .../query_synthetic_in_subpackage.jsonc | 5 + .../snapshots/task_graph.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 15 +++ .../vpr_shorthand/snapshots/task_graph.jsonc | 10 ++ .../query_dev_filter_from_root.jsonc | 5 + .../snapshots/query_dev_in_subpackage.jsonc | 5 + .../snapshots/task_graph.jsonc | 5 + .../snapshots/task_graph.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 20 +++ .../snapshots/task_graph.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 20 +++ .../snapshots/task_graph.jsonc | 10 ++ .../snapshots/task_graph.jsonc | 15 +++ 96 files changed, 1570 insertions(+), 43 deletions(-) create mode 100644 crates/vite_task/src/session/cache/archive.rs create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/package.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/src/main.ts create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/vite-task.json diff --git a/CHANGELOG.md b/CHANGELOG.md index c437fce5a..8b9d75801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +- **Added** `output` field for cached tasks: archives output files matching the configured globs after a successful run and restores them on cache hit. Patterns are relative to the package directory; supports negative patterns (e.g. `"!dist/cache/**"`) and `{pattern, base}` form for explicit base. ([#321](https://github.com/voidzero-dev/vite-task/pull/321)) - **Fixed** Windows cached tasks can now run package shims rewritten through PowerShell; default env passthrough now preserves `PATHEXT` ([#366](https://github.com/voidzero-dev/vite-task/pull/366)) - **Added** Platform support for targets without `input` auto-inference (e.g. Android). Tasks still run; those relying on auto-inference run uncached, with the summary noting that `input` must be configured manually to enable caching ([#352](https://github.com/voidzero-dev/vite-task/pull/352)) - **Fixed** `vp run` no longer aborts with `failed to prepare the command for injection: Invalid argument` when the user environment already has `LD_PRELOAD` (Linux) or `DYLD_INSERT_LIBRARIES` (macOS) set. The tracer shim is now appended to any existing value and placed last, so user preloads keep their symbol-interposition precedence ([#340](https://github.com/voidzero-dev/vite-task/issues/340)) diff --git a/Cargo.lock b/Cargo.lock index 324f0a6ad..179a9470e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -421,6 +421,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -1686,6 +1688,16 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.85" @@ -4098,12 +4110,14 @@ dependencies = [ "rustc-hash", "serde", "serde_json", + "tar", "tempfile", "thiserror 2.0.18", "tokio", "tokio-util", "tracing", "twox-hash", + "uuid", "vite_path", "vite_select", "vite_str", @@ -4113,6 +4127,7 @@ dependencies = [ "wax", "winapi", "wincode", + "zstd", ] [[package]] @@ -4876,3 +4891,31 @@ name = "zmij" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 9e11f099b..5583f09b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,6 +163,7 @@ winsafe = { version = "0.0.27", features = ["kernel"] } xxhash-rust = { version = "0.8.15", features = ["const_xxh3"] } ntest = "0.9.5" terminal_size = "0.4" +zstd = "0.13" [workspace.metadata.cargo-shear] ignored = [ diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml index 2286b9dc7..f5d77780b 100644 --- a/crates/vite_task/Cargo.toml +++ b/crates/vite_task/Cargo.toml @@ -29,6 +29,7 @@ rustc-hash = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } thiserror = { workspace = true } +tar = { workspace = true } tokio = { workspace = true, features = [ "rt-multi-thread", "io-std", @@ -40,6 +41,7 @@ tokio = { workspace = true, features = [ tokio-util = { workspace = true } tracing = { workspace = true } twox-hash = { workspace = true } +uuid = { workspace = true, features = ["v4"] } vite_path = { workspace = true } vite_select = { workspace = true } vite_str = { workspace = true } @@ -47,6 +49,7 @@ vite_task_graph = { workspace = true } vite_task_plan = { workspace = true } vite_workspace = { workspace = true } wax = { workspace = true } +zstd = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/vite_task/src/session/cache/archive.rs b/crates/vite_task/src/session/cache/archive.rs new file mode 100644 index 000000000..9a4982ae2 --- /dev/null +++ b/crates/vite_task/src/session/cache/archive.rs @@ -0,0 +1,61 @@ +//! Output archive creation and extraction using tar + zstd compression. + +use std::fs::File; + +use vite_path::{AbsolutePath, RelativePathBuf}; + +/// Create a tar.zst archive from workspace-relative output file paths. +/// +/// Files that no longer exist are silently skipped (the task may delete +/// temporary files during execution). +/// +/// # Errors +/// +/// Returns an error if creating the archive file or adding entries fails. +pub fn create_output_archive( + workspace_root: &AbsolutePath, + output_files: &[RelativePathBuf], + archive_path: &AbsolutePath, +) -> anyhow::Result<()> { + let file = File::create(archive_path.as_path())?; + let encoder = zstd::Encoder::new(file, 0)?.auto_finish(); + let mut builder = tar::Builder::new(encoder); + + for rel_path in output_files { + let abs_path = workspace_root.join(rel_path); + // Skip files that no longer exist (task may delete temp files) + if !abs_path.as_path().exists() { + continue; + } + let metadata = std::fs::metadata(abs_path.as_path())?; + if metadata.is_file() { + let mut file = File::open(abs_path.as_path())?; + let mut header = tar::Header::new_gnu(); + header.set_metadata(&metadata); + header.set_cksum(); + builder.append_data(&mut header, rel_path.as_str(), &mut file)?; + } + } + + builder.finish()?; + Ok(()) +} + +/// Extract a tar.zst archive, restoring files relative to workspace root. +/// +/// Parent directories are created automatically. Existing files are overwritten. +/// +/// # Errors +/// +/// Returns an error if opening the archive or extracting entries fails. +pub fn extract_output_archive( + workspace_root: &AbsolutePath, + archive_path: &AbsolutePath, +) -> anyhow::Result<()> { + let file = File::open(archive_path.as_path())?; + let decoder = zstd::Decoder::new(file)?; + let mut archive = tar::Archive::new(decoder); + + archive.unpack(workspace_root.as_path())?; + Ok(()) +} diff --git a/crates/vite_task/src/session/cache/display.rs b/crates/vite_task/src/session/cache/display.rs index ec6bf3d3d..9dbcabd59 100644 --- a/crates/vite_task/src/session/cache/display.rs +++ b/crates/vite_task/src/session/cache/display.rs @@ -174,6 +174,7 @@ pub fn format_cache_status_inline(cache_status: &CacheStatus) -> Option { } } FingerprintMismatch::InputConfig => "input configuration changed", + FingerprintMismatch::OutputConfig => "output configuration changed", FingerprintMismatch::InputChanged { kind, path } => { let desc = format_input_change_str(*kind, path.as_str()); return Some(vite_str::format!("○ cache miss: {desc}, executing")); diff --git a/crates/vite_task/src/session/cache/mod.rs b/crates/vite_task/src/session/cache/mod.rs index 690d50d43..6d1ee6a9c 100644 --- a/crates/vite_task/src/session/cache/mod.rs +++ b/crates/vite_task/src/session/cache/mod.rs @@ -1,5 +1,6 @@ //! Execution cache for storing and retrieving cached command results. +pub mod archive; pub mod display; use std::{collections::BTreeMap, fmt::Display, fs::File, io::Write, sync::Arc, time::Duration}; @@ -14,7 +15,8 @@ use rusqlite::{Connection, OptionalExtension as _, config::DbConfig}; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; use vite_path::{AbsolutePath, RelativePathBuf}; -use vite_task_graph::config::ResolvedInputConfig; +use vite_str::Str; +use vite_task_graph::config::ResolvedGlobConfig; use vite_task_plan::cache_metadata::{CacheMetadata, ExecutionCacheKey, SpawnFingerprint}; use wincode::{ SchemaRead, SchemaReadOwned, SchemaWrite, @@ -43,7 +45,10 @@ pub struct CacheEntryKey { pub spawn_fingerprint: SpawnFingerprint, /// Resolved input configuration that affects cache behavior. /// Glob patterns are workspace-root-relative. - pub input_config: ResolvedInputConfig, + pub input_config: ResolvedGlobConfig, + /// Resolved output configuration that affects cache restoration. + /// Glob patterns are workspace-root-relative. + pub output_config: ResolvedGlobConfig, } impl CacheEntryKey { @@ -51,6 +56,7 @@ impl CacheEntryKey { Self { spawn_fingerprint: cache_metadata.spawn_fingerprint.clone(), input_config: cache_metadata.input_config.clone(), + output_config: cache_metadata.output_config.clone(), } } } @@ -103,6 +109,9 @@ pub struct CacheEntryValue { /// 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, + /// Filename of the output archive (e.g. `{uuid}.tar.zst`) stored alongside + /// `cache.db` in the cache directory. `None` if no output files were produced. + pub output_archive: Option, } #[derive(Debug)] @@ -142,6 +151,8 @@ pub enum FingerprintMismatch { }, /// Found a previous cache entry key for the same task, but `input_config` differs. InputConfig, + /// Found a previous cache entry key for the same task, but `output_config` differs. + OutputConfig, InputChanged { kind: InputChangeKind, @@ -158,6 +169,9 @@ impl Display for FingerprintMismatch { Self::InputConfig => { write!(f, "input configuration changed") } + Self::OutputConfig => { + write!(f, "output configuration changed") + } Self::InputChanged { kind, path } => { write!(f, "{}", display::format_input_change_str(*kind, path.as_str())) } @@ -201,16 +215,16 @@ impl ExecutionCache { "CREATE TABLE task_fingerprints (key BLOB PRIMARY KEY, value BLOB);", (), )?; - conn.execute("PRAGMA user_version = 11", ())?; + conn.execute("PRAGMA user_version = 12", ())?; } - 1..=10 => { + 1..=11 => { // 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)?; } - 11 => break, // current version - 12.. => { + 12 => break, // current version + 13.. => { return Err(anyhow::anyhow!( "Unrecognized database version: {user_version}. \ The cache may have been created by a newer version of Vite Task. \ @@ -270,11 +284,20 @@ impl ExecutionCache { self.get_cache_key_by_execution_key(execution_cache_key).await? { // 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 CacheEntryKey { + spawn_fingerprint: old_spawn_fingerprint, + input_config: old_input_config, + output_config: old_output_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 + // spawn fingerprint is the same but input_config or output_config changed + if old_input_config != cache_metadata.input_config { + FingerprintMismatch::InputConfig + } else if old_output_config != cache_metadata.output_config { + FingerprintMismatch::OutputConfig + } else { + FingerprintMismatch::InputConfig + } } else { FingerprintMismatch::SpawnFingerprint { old: old_spawn_fingerprint, @@ -288,16 +311,33 @@ impl ExecutionCache { } /// Update cache after successful execution. + /// + /// If a previous entry exists for the same cache key with a different + /// `output_archive`, the stale archive file in `cache_dir` is removed + /// (best-effort) so it doesn't accumulate on disk. #[tracing::instrument(level = "debug", skip_all)] pub async fn update( &self, cache_metadata: &CacheMetadata, cache_value: CacheEntryValue, + cache_dir: &AbsolutePath, ) -> anyhow::Result<()> { let execution_cache_key = &cache_metadata.execution_cache_key; let cache_key = CacheEntryKey::from_metadata(cache_metadata); + // If a previous entry exists with a stale output archive, delete the + // old file so the cache directory doesn't accumulate orphaned archives. + if let Some(old_value) = self.get_by_cache_key(&cache_key).await? + && let Some(old_archive) = old_value.output_archive + && cache_value.output_archive.as_ref() != Some(&old_archive) + { + let old_archive_path = cache_dir.join(old_archive.as_str()); + // Best-effort cleanup: a missing file (e.g. after a crash or manual + // cache clear) is fine, so we ignore the error. + let _ = std::fs::remove_file(old_archive_path.as_path()); + } + self.upsert_cache_entry(&cache_key, &cache_value).await?; self.upsert_task_fingerprint(execution_cache_key, &cache_key).await?; Ok(()) diff --git a/crates/vite_task/src/session/execute/glob_inputs.rs b/crates/vite_task/src/session/execute/glob_inputs.rs index a9bb1b20d..a3d966e11 100644 --- a/crates/vite_task/src/session/execute/glob_inputs.rs +++ b/crates/vite_task/src/session/execute/glob_inputs.rs @@ -109,6 +109,58 @@ pub fn compute_globbed_inputs( Ok(result) } +/// Collect file paths matching positive globs, filtered by negative globs. +/// +/// Like [`compute_globbed_inputs`] but only collects paths (no hashing). +/// Used for determining which output files to archive. +pub fn collect_glob_paths( + workspace_root: &AbsolutePath, + positive_globs: &std::collections::BTreeSet, + negative_globs: &std::collections::BTreeSet, +) -> anyhow::Result> { + if positive_globs.is_empty() { + return Ok(Vec::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 = Vec::new(); + + for pattern in positive_globs { + let glob = Glob::new(pattern.as_str())?.into_owned(); + let walk = glob.walk(workspace_root.as_path()); + for entry in walk.not(negation.clone())? { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + 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(); + let Some(stripped) = path.strip_prefix(workspace_root.as_path()).ok() else { + continue; + }; + let relative = RelativePathBuf::new(stripped)?; + result.push(relative); + } + } + + result.sort(); + result.dedup(); + Ok(result) +} + #[expect(clippy::disallowed_types, reason = "receives std::path::Path from wax glob walker")] fn hash_file_content(path: &std::path::Path) -> io::Result { super::hash::hash_content(io::BufReader::new(File::open(path)?)) diff --git a/crates/vite_task/src/session/execute/mod.rs b/crates/vite_task/src/session/execute/mod.rs index 812de5f65..29719a74f 100644 --- a/crates/vite_task/src/session/execute/mod.rs +++ b/crates/vite_task/src/session/execute/mod.rs @@ -16,6 +16,7 @@ use rustc_hash::FxHashMap; use tokio::sync::Semaphore; use tokio_util::sync::CancellationToken; use vite_path::{AbsolutePath, RelativePathBuf}; +use vite_str::Str; use vite_task_plan::{ ExecutionGraph, ExecutionItemDisplay, ExecutionItemKind, LeafExecutionKind, SpawnExecution, cache_metadata::CacheMetadata, execution_graph::ExecutionNodeIndex, @@ -30,7 +31,7 @@ use self::{ spawn::{SpawnStdio, spawn}, }; use super::{ - cache::{CacheEntryValue, ExecutionCache}, + cache::{CacheEntryValue, ExecutionCache, archive}, event::{ CacheDisabledReason, CacheErrorKind, CacheNotUpdatedReason, CacheStatus, CacheUpdateStatus, ExecutionError, @@ -75,6 +76,8 @@ struct ExecutionContext<'a> { /// Base path for resolving relative paths in cache entries. /// Typically the workspace root. cache_base_path: &'a Arc, + /// Directory where cache files (db, archives) are stored. + cache_dir: &'a AbsolutePath, /// Token cancelled when a task fails. Kills in-flight child processes /// (via `start_kill` in spawn.rs), prevents scheduling new tasks, and /// prevents caching results of concurrently-running tasks. @@ -236,6 +239,7 @@ impl ExecutionContext<'_> { spawn_execution, self.cache, self.cache_base_path, + self.cache_dir, self.fast_fail_token.clone(), self.interrupt_token.clone(), ) @@ -328,6 +332,7 @@ pub async fn execute_spawn( spawn_execution: &SpawnExecution, cache: &ExecutionCache, cache_base_path: &Arc, + cache_dir: &AbsolutePath, fast_fail_token: CancellationToken, interrupt_token: CancellationToken, ) -> SpawnOutcome { @@ -400,6 +405,18 @@ pub async fn execute_spawn( let _ = writer.write_all(&output.content); let _ = writer.flush(); } + // Restore output files from the cached archive + if let Some(ref archive_name) = cached.output_archive { + let archive_path = cache_dir.join(archive_name.as_str()); + if let Err(err) = archive::extract_output_archive(cache_base_path, &archive_path) { + leaf_reporter.finish( + None, + CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheHit), + Some(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }), + ); + return SpawnOutcome::Failed; + } + } leaf_reporter.finish( None, CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheHit), @@ -605,13 +622,38 @@ pub async fn execute_spawn( let path_reads = tracking.as_ref().map_or(&empty_path_reads, |t| &t.path_reads); match PostRunFingerprint::create(path_reads, cache_base_path, &globbed_inputs) { Ok(post_run_fingerprint) => { + // Collect output files and create archive + let output_archive = + match collect_and_archive_outputs(metadata, cache_base_path, cache_dir) + { + Ok(archive) => archive, + Err(err) => { + let result = ( + CacheUpdateStatus::NotUpdated( + CacheNotUpdatedReason::CacheDisabled, + ), + Some(ExecutionError::Cache { + kind: CacheErrorKind::Update, + source: err, + }), + ); + leaf_reporter.finish( + Some(outcome.exit_status), + result.0, + result.1, + ); + return SpawnOutcome::Spawned(outcome.exit_status); + } + }; + let new_cache_value = CacheEntryValue { post_run_fingerprint, std_outputs: std_outputs.into(), duration, globbed_inputs, + output_archive, }; - match cache.update(metadata, new_cache_value).await { + match cache.update(metadata, new_cache_value, cache_dir).await { Ok(()) => (CacheUpdateStatus::Updated, None), Err(err) => ( CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), @@ -645,6 +687,40 @@ pub async fn execute_spawn( SpawnOutcome::Spawned(outcome.exit_status) } +/// Collect output files matching the configured globs and create a tar.zst +/// archive in the cache directory. +/// +/// Returns `Some(archive_filename)` if files were archived, `None` if the +/// output config has no positive globs or no files matched. +fn collect_and_archive_outputs( + cache_metadata: &CacheMetadata, + workspace_root: &AbsolutePath, + cache_dir: &AbsolutePath, +) -> anyhow::Result> { + let output_config = &cache_metadata.output_config; + + if output_config.positive_globs.is_empty() { + return Ok(None); + } + + let output_files = glob_inputs::collect_glob_paths( + workspace_root, + &output_config.positive_globs, + &output_config.negative_globs, + )?; + + if output_files.is_empty() { + return Ok(None); + } + + let archive_name: Str = vite_str::format!("{}.tar.zst", uuid::Uuid::new_v4()); + let archive_path = cache_dir.join(archive_name.as_str()); + + archive::create_output_archive(workspace_root, &output_files, &archive_path)?; + + Ok(Some(archive_name)) +} + impl Session<'_> { /// Execute an execution graph, reporting events through the provided reporter builder. /// @@ -679,6 +755,7 @@ impl Session<'_> { reporter: &reporter, cache, cache_base_path: &self.workspace_path, + cache_dir: &self.cache_path, fast_fail_token: CancellationToken::new(), interrupt_token, }; diff --git a/crates/vite_task/src/session/mod.rs b/crates/vite_task/src/session/mod.rs index eb9ca6411..8edef289f 100644 --- a/crates/vite_task/src/session/mod.rs +++ b/crates/vite_task/src/session/mod.rs @@ -677,6 +677,7 @@ impl<'a> Session<'a> { &spawn_execution, cache, &self.workspace_path, + &self.cache_path, tokio_util::sync::CancellationToken::new(), tokio_util::sync::CancellationToken::new(), ) diff --git a/crates/vite_task/src/session/reporter/summary.rs b/crates/vite_task/src/session/reporter/summary.rs index 4eeceb684..f1a01168f 100644 --- a/crates/vite_task/src/session/reporter/summary.rs +++ b/crates/vite_task/src/session/reporter/summary.rs @@ -259,7 +259,9 @@ impl SavedCacheMissReason { FingerprintMismatch::SpawnFingerprint { old, new } => { Self::SpawnFingerprintChanged(detect_spawn_fingerprint_changes(old, new)) } - FingerprintMismatch::InputConfig => Self::ConfigChanged, + FingerprintMismatch::InputConfig | FingerprintMismatch::OutputConfig => { + Self::ConfigChanged + } FingerprintMismatch::InputChanged { kind, path } => { Self::InputChanged { kind: *kind, path: Str::from(path.as_str()) } } diff --git a/crates/vite_task_bin/src/lib.rs b/crates/vite_task_bin/src/lib.rs index 87a402a2a..43ccd08d1 100644 --- a/crates/vite_task_bin/src/lib.rs +++ b/crates/vite_task_bin/src/lib.rs @@ -98,6 +98,7 @@ impl vite_task::CommandHandler for CommandHandler { env: None, untracked_env: None, input: None, + output: None, }), envs: Arc::clone(&command.envs), })) diff --git a/crates/vite_task_bin/src/vtt/write_file.rs b/crates/vite_task_bin/src/vtt/write_file.rs index f5539146b..b1562bd79 100644 --- a/crates/vite_task_bin/src/vtt/write_file.rs +++ b/crates/vite_task_bin/src/vtt/write_file.rs @@ -2,6 +2,10 @@ pub fn run(args: &[String]) -> Result<(), Box> { if args.len() < 2 { return Err("Usage: vtt write-file ".into()); } - std::fs::write(&args[0], &args[1])?; + let path = std::path::Path::new(&args[0]); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(path, &args[1])?; Ok(()) } diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/package.json new file mode 100644 index 000000000..60b87e87f --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/package.json @@ -0,0 +1,4 @@ +{ + "name": "output-cache-test", + "private": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml new file mode 100644 index 000000000..d554d79b4 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml @@ -0,0 +1,38 @@ +[[e2e]] +name = "output_globs___files_restored_on_cache_hit" +comment = """ +With explicit output globs (`dist/**`), the first run writes a file to +`dist/`. After deleting `dist/`, a second run with no input changes is a +cache hit and the archived output file is restored. +""" +steps = [ + # First run - cache miss, writes dist/output.txt + ["vt", "run", "build"], + # Verify file was written + ["vtt", "print-file", "dist/output.txt"], + # Delete dist/ to prove restoration is real + ["vtt", "rm", "-rf", "dist"], + # Second run - cache hit, restores dist/output.txt from archive + ["vt", "run", "build"], + # File should be restored + ["vtt", "print-file", "dist/output.txt"], +] + +[[e2e]] +name = "output_globs___negative_excludes_files_from_archive" +comment = """ +A file matched by a negative output glob is not archived, so it is not +restored on cache hit. +""" +steps = [ + # First run - writes both dist/keep.txt and dist/skip.txt + ["vt", "run", "build-with-negative"], + # Both files exist after the run + ["vtt", "print-file", "dist/keep.txt"], + ["vtt", "print-file", "dist/skip.txt"], + # Delete dist/ to prove restoration is real + ["vtt", "rm", "-rf", "dist"], + # Second run - cache hit, only dist/keep.txt is restored + ["vt", "run", "build-with-negative"], + ["vtt", "print-file", "dist/keep.txt"], +] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md new file mode 100644 index 000000000..f10e226f1 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md @@ -0,0 +1,37 @@ +# output_globs___files_restored_on_cache_hit + +With explicit output globs (`dist/**`), the first run writes a file to +`dist/`. After deleting `dist/`, a second run with no input changes is a +cache hit and the archived output file is restored. + +## `vt run build` + +``` +$ vtt write-file dist/output.txt built +``` + +## `vtt print-file dist/output.txt` + +``` +built +``` + +## `vtt rm -rf dist` + +``` +``` + +## `vt run build` + +``` +$ vtt write-file dist/output.txt built ◉ cache hit, replaying + +--- +vt run: cache hit. +``` + +## `vtt print-file dist/output.txt` + +``` +built +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md new file mode 100644 index 000000000..2cb049158 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md @@ -0,0 +1,49 @@ +# output_globs___negative_excludes_files_from_archive + +A file matched by a negative output glob is not archived, so it is not +restored on cache hit. + +## `vt run build-with-negative` + +``` +$ vtt write-file dist/keep.txt keep + +$ vtt write-file dist/skip.txt skip + +--- +vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) +``` + +## `vtt print-file dist/keep.txt` + +``` +keep +``` + +## `vtt print-file dist/skip.txt` + +``` +skip +``` + +## `vtt rm -rf dist` + +``` +``` + +## `vt run build-with-negative` + +``` +$ vtt write-file dist/keep.txt keep ◉ cache hit, replaying + +$ vtt write-file dist/skip.txt skip ◉ cache hit, replaying + +--- +vt run: 2/2 cache hit (100%). (Run `vt run --last-details` for full details) +``` + +## `vtt print-file dist/keep.txt` + +``` +keep +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/src/main.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/src/main.ts new file mode 100644 index 000000000..38e000a1c --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/src/main.ts @@ -0,0 +1 @@ +export const main = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/vite-task.json new file mode 100644 index 000000000..6bd35a604 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/vite-task.json @@ -0,0 +1,16 @@ +{ + "tasks": { + "build": { + "command": "vtt write-file dist/output.txt built", + "input": ["src/**"], + "output": ["dist/**"], + "cache": true + }, + "build-with-negative": { + "command": "vtt write-file dist/keep.txt keep && vtt write-file dist/skip.txt skip", + "input": ["src/**"], + "output": ["dist/**", "!dist/skip.txt"], + "cache": true + } + } +} diff --git a/crates/vite_task_graph/run-config.ts b/crates/vite_task_graph/run-config.ts index 026664c39..1fa4ee868 100644 --- a/crates/vite_task_graph/run-config.ts +++ b/crates/vite_task_graph/run-config.ts @@ -53,7 +53,16 @@ untrackedEnv?: Array, * - `{auto: true}` enables automatic file tracking * - Negative patterns (e.g. `"!dist/**"`) exclude matched files */ -input?: Array, } | { +input?: Array, +/** + * Output files to archive after a successful run and restore on cache hit. + * + * - Omitted or `[]` (empty): no output archiving (default) + * - Glob patterns (e.g. `"dist/**"`) select specific output files, relative to the package directory + * - `{pattern: "...", base: "workspace" | "package"}` specifies a glob with an explicit base directory + * - Negative patterns (e.g. `"!dist/cache/**"`) exclude matched files + */ +output?: Array, } | { /** * Whether to cache the task */ diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index 916bc62a1..0e8c28f87 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -7,8 +7,8 @@ use rustc_hash::FxHashSet; use serde::Serialize; pub use user::{ AutoInput, EnabledCacheConfig, GlobWithBase, InputBase, ResolvedGlobalCacheConfig, - UserCacheConfig, UserGlobalCacheConfig, UserInputEntry, UserInputsConfig, UserRunConfig, - UserTaskConfig, + UserCacheConfig, UserGlobalCacheConfig, UserInputEntry, UserInputsConfig, UserOutputEntry, + UserRunConfig, UserTaskConfig, }; use vite_path::AbsolutePath; use vite_str::Str; @@ -69,12 +69,18 @@ impl ResolvedTaskOptions { enabled_cache_config.untracked_env.unwrap_or_default().into_iter().collect(); untracked_env.extend(DEFAULT_UNTRACKED_ENV.iter().copied().map(Str::from)); - let input_config = ResolvedInputConfig::from_user_config( + let input_config = ResolvedGlobConfig::from_user_config( enabled_cache_config.input.as_ref(), dir, workspace_root, )?; + let output_config = ResolvedGlobConfig::from_user_output_config( + enabled_cache_config.output.as_ref(), + dir, + workspace_root, + )?; + Some(CacheConfig { env_config: EnvConfig { fingerprinted_envs: enabled_cache_config @@ -84,6 +90,7 @@ impl ResolvedTaskOptions { untracked_env, }, input_config, + output_config, }) } }; @@ -92,9 +99,14 @@ impl ResolvedTaskOptions { } #[derive(Debug, Clone, Serialize)] +#[expect( + clippy::struct_field_names, + reason = "env_config, input_config, output_config are distinct config categories, not a naming smell" +)] pub struct CacheConfig { pub env_config: EnvConfig, - pub input_config: ResolvedInputConfig, + pub input_config: ResolvedGlobConfig, + pub output_config: ResolvedGlobConfig, } /// Resolved input configuration for cache fingerprinting. @@ -104,7 +116,7 @@ pub struct CacheConfig { /// - `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, SchemaWrite, SchemaRead)] -pub struct ResolvedInputConfig { +pub struct ResolvedGlobConfig { /// Whether automatic file tracking is enabled pub includes_auto: bool, @@ -117,7 +129,7 @@ pub struct ResolvedInputConfig { pub negative_globs: BTreeSet, } -impl ResolvedInputConfig { +impl ResolvedGlobConfig { /// Default configuration: auto-inference enabled, no explicit patterns #[must_use] pub const fn default_auto() -> Self { @@ -184,6 +196,57 @@ impl ResolvedInputConfig { Ok(Self { includes_auto, positive_globs, negative_globs }) } + /// Resolve from user output configuration, making glob patterns workspace-root-relative. + /// + /// Unlike [`Self::from_user_config`], `None` and `Some([])` both produce an empty config + /// with `includes_auto = false` (no output archiving). + /// + /// # Errors + /// + /// Returns [`ResolveTaskConfigError`] if a glob pattern is invalid or resolves + /// outside the workspace root. + pub fn from_user_output_config( + user_outputs: Option<&Vec>, + package_dir: &AbsolutePath, + workspace_root: &AbsolutePath, + ) -> Result { + let mut positive_globs = BTreeSet::new(); + let mut negative_globs = BTreeSet::new(); + + let Some(entries) = user_outputs else { + return Ok(Self { includes_auto: false, positive_globs, negative_globs }); + }; + + for entry in entries { + match entry { + UserOutputEntry::Glob(pattern) => { + Self::insert_glob( + pattern.as_str(), + package_dir, + workspace_root, + &mut positive_globs, + &mut negative_globs, + )?; + } + UserOutputEntry::GlobWithBase(GlobWithBase { pattern, base }) => { + let base_dir = match base { + InputBase::Package => package_dir, + InputBase::Workspace => workspace_root, + }; + Self::insert_glob( + pattern.as_str(), + base_dir, + workspace_root, + &mut positive_globs, + &mut negative_globs, + )?; + } + } + } + + Ok(Self { includes_auto: false, positive_globs, negative_globs }) + } + /// Insert a glob pattern into the appropriate set (positive or negative), /// resolving it relative to the given base directory. fn insert_glob( @@ -426,7 +489,7 @@ mod tests { #[test] fn test_resolved_input_config_default_auto() { - let config = ResolvedInputConfig::default_auto(); + let config = ResolvedGlobConfig::default_auto(); assert!(config.includes_auto); assert!(config.positive_globs.is_empty()); assert!(config.negative_globs.is_empty()); @@ -436,7 +499,7 @@ mod tests { 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(); + let config = ResolvedGlobConfig::from_user_config(None, &pkg, &ws).unwrap(); assert!(config.includes_auto); assert!(config.positive_globs.is_empty()); assert!(config.negative_globs.is_empty()); @@ -447,7 +510,7 @@ mod tests { 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(); + let config = ResolvedGlobConfig::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()); @@ -457,7 +520,7 @@ mod tests { fn test_resolved_input_config_auto_only() { let (pkg, ws) = test_paths(); let user_inputs = vec![UserInputEntry::Auto(AutoInput { auto: true })]; - let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + let config = ResolvedGlobConfig::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()); @@ -467,7 +530,7 @@ mod tests { fn test_resolved_input_config_auto_false_ignored() { let (pkg, ws) = test_paths(); let user_inputs = vec![UserInputEntry::Auto(AutoInput { auto: false })]; - let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + let config = ResolvedGlobConfig::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()); @@ -481,7 +544,7 @@ mod tests { UserInputEntry::Glob("src/**/*.ts".into()), UserInputEntry::Glob("package.json".into()), ]; - let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + let config = ResolvedGlobConfig::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 @@ -497,7 +560,7 @@ mod tests { UserInputEntry::Glob("src/**".into()), UserInputEntry::Glob("!src/**/*.test.ts".into()), ]; - let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + let config = ResolvedGlobConfig::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/**")); @@ -513,7 +576,7 @@ mod tests { UserInputEntry::Auto(AutoInput { auto: true }), UserInputEntry::Glob("!node_modules/**".into()), ]; - let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + let config = ResolvedGlobConfig::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")); @@ -529,7 +592,7 @@ mod tests { UserInputEntry::Glob("src/**/*.ts".into()), UserInputEntry::Auto(AutoInput { auto: true }), ]; - let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); assert!(config.includes_auto); } @@ -537,7 +600,7 @@ mod tests { 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(); + let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); assert_eq!(config.positive_globs.len(), 1); assert!( config.positive_globs.contains("packages/shared/src/**"), @@ -550,7 +613,7 @@ mod tests { 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); + let result = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), ResolveTaskConfigError::GlobOutsideWorkspace { .. })); } @@ -562,7 +625,7 @@ mod tests { pattern: "configs/tsconfig.json".into(), base: InputBase::Workspace, })]; - let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); assert!(!config.includes_auto); assert_eq!(config.positive_globs.len(), 1); // Workspace-base: should NOT have the package prefix @@ -580,7 +643,7 @@ mod tests { pattern: "!dist/**".into(), base: InputBase::Workspace, })]; - let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); assert_eq!(config.negative_globs.len(), 1); assert!( config.negative_globs.contains("dist/**"), @@ -597,7 +660,7 @@ mod tests { pattern: "src/**/*.ts".into(), base: InputBase::Package, })]; - let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); assert_eq!(config.positive_globs.len(), 1); assert!( config.positive_globs.contains("packages/my-pkg/src/**/*.ts"), @@ -621,7 +684,7 @@ mod tests { base: InputBase::Workspace, }), ]; - let config = ResolvedInputConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); + let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); assert!(config.includes_auto); assert_eq!(config.positive_globs.len(), 2); assert!(config.positive_globs.contains("packages/my-pkg/src/**")); diff --git a/crates/vite_task_graph/src/config/user.rs b/crates/vite_task_graph/src/config/user.rs index 79255ad63..aeee38608 100644 --- a/crates/vite_task_graph/src/config/user.rs +++ b/crates/vite_task_graph/src/config/user.rs @@ -65,6 +65,22 @@ pub enum UserInputEntry { /// Default (when field omitted): `[{auto: true}]` - infer from file accesses. pub type UserInputsConfig = Vec; +/// A single output entry in the `output` array. +/// +/// Outputs can be: +/// - Glob patterns as strings (resolved relative to the package directory) +/// - Object form with explicit base: `{ "pattern": "...", "base": "workspace" | "package" }` +#[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 UserOutputEntry { + /// Glob pattern (positive or negative starting with `!`), resolved relative to package dir + Glob(Str), + /// Glob pattern with explicit base directory + GlobWithBase(GlobWithBase), +} + /// 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 @@ -125,6 +141,16 @@ pub struct EnabledCacheConfig { #[serde(default)] #[cfg_attr(all(test, not(clippy)), ts(inline))] pub input: Option, + + /// Output files to archive after a successful run and restore on cache hit. + /// + /// - Omitted or `[]` (empty): no output archiving (default) + /// - Glob patterns (e.g. `"dist/**"`) select specific output files, relative to the package directory + /// - `{pattern: "...", base: "workspace" | "package"}` specifies a glob with an explicit base directory + /// - Negative patterns (e.g. `"!dist/cache/**"`) exclude matched files + #[serde(default)] + #[cfg_attr(all(test, not(clippy)), ts(inline))] + pub output: Option>, } /// Options for user-defined tasks in `vite.config.*`, excluding the command. @@ -160,6 +186,7 @@ impl Default for UserTaskOptions { env: None, untracked_env: None, input: None, + output: None, }, }, } @@ -428,6 +455,7 @@ mod tests { env: Some(std::iter::once("NODE_ENV".into()).collect()), untracked_env: Some(std::iter::once("FOO".into()).collect()), input: None, + output: None, } }, ); diff --git a/crates/vite_task_plan/src/cache_metadata.rs b/crates/vite_task_plan/src/cache_metadata.rs index c3435c345..11a1d957e 100644 --- a/crates/vite_task_plan/src/cache_metadata.rs +++ b/crates/vite_task_plan/src/cache_metadata.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use serde::{Deserialize, Serialize}; use vite_path::RelativePathBuf; use vite_str::{self, Str}; -use vite_task_graph::config::ResolvedInputConfig; +use vite_task_graph::config::ResolvedGlobConfig; use wincode::{SchemaRead, SchemaWrite}; use crate::envs::EnvFingerprints; @@ -45,7 +45,11 @@ pub struct CacheMetadata { /// Resolved input configuration for cache fingerprinting. /// Used at execution time to determine what files to track. - pub input_config: ResolvedInputConfig, + pub input_config: ResolvedGlobConfig, + + /// Resolved output configuration for cache restoration. + /// Used at execution time to determine what output files to archive. + pub output_config: ResolvedGlobConfig, } /// Fingerprint for spawn execution that affects caching. diff --git a/crates/vite_task_plan/src/plan.rs b/crates/vite_task_plan/src/plan.rs index 1a91d9165..89e892b48 100644 --- a/crates/vite_task_plan/src/plan.rs +++ b/crates/vite_task_plan/src/plan.rs @@ -20,7 +20,7 @@ use vite_str::Str; use vite_task_graph::{ TaskNodeIndex, TaskSource, config::{ - CacheConfig, EnabledCacheConfig, ResolvedGlobalCacheConfig, ResolvedInputConfig, + CacheConfig, EnabledCacheConfig, ResolvedGlobConfig, ResolvedGlobalCacheConfig, ResolvedTaskOptions, user::{UserCacheConfig, UserTaskOptions}, }, @@ -466,7 +466,8 @@ fn resolve_synthetic_cache_config( Ok(match synthetic_cache_config { UserCacheConfig::Disabled { .. } => Option::None, UserCacheConfig::Enabled { enabled_cache_config, .. } => { - let EnabledCacheConfig { env, untracked_env, input } = enabled_cache_config; + let EnabledCacheConfig { env, untracked_env, input, output } = + enabled_cache_config; parent_config.env_config.fingerprinted_envs.extend(env.unwrap_or_default()); parent_config .env_config @@ -474,7 +475,7 @@ fn resolve_synthetic_cache_config( .extend(untracked_env.unwrap_or_default()); if let Some(input) = input { - let synthetic_input = ResolvedInputConfig::from_user_config( + let synthetic_input = ResolvedGlobConfig::from_user_config( Some(&input), package_dir, workspace_path, @@ -493,6 +494,23 @@ fn resolve_synthetic_cache_config( .extend(synthetic_input.negative_globs); } + if let Some(output) = output { + let synthetic_output = ResolvedGlobConfig::from_user_output_config( + Some(&output), + package_dir, + workspace_path, + ) + .map_err(Error::ResolveTaskConfig)?; + parent_config + .output_config + .positive_globs + .extend(synthetic_output.positive_globs); + parent_config + .output_config + .negative_globs + .extend(synthetic_output.negative_globs); + } + Some(parent_config) } }) @@ -628,6 +646,7 @@ fn plan_spawn_execution( spawn_fingerprint, execution_cache_key, input_config: cache_config.input_config.clone(), + output_config: cache_config.output_config.clone(), }); } } @@ -853,7 +872,7 @@ mod tests { use vite_path::AbsolutePathBuf; use vite_str::Str; use vite_task_graph::config::{ - CacheConfig, EnabledCacheConfig, EnvConfig, ResolvedInputConfig, + CacheConfig, EnabledCacheConfig, EnvConfig, ResolvedGlobConfig, user::{UserCacheConfig, UserInputEntry}, }; @@ -879,11 +898,16 @@ mod tests { fingerprinted_envs: FxHashSet::default(), untracked_env: FxHashSet::default(), }, - input_config: ResolvedInputConfig { + input_config: ResolvedGlobConfig { includes_auto, positive_globs: positive_globs.iter().map(|s| Str::from(*s)).collect(), negative_globs: BTreeSet::new(), }, + output_config: ResolvedGlobConfig { + includes_auto: false, + positive_globs: BTreeSet::new(), + negative_globs: BTreeSet::new(), + }, } } @@ -901,6 +925,7 @@ mod tests { env: None, untracked_env: None, input: None, + output: None, }), &pkg, &ws, @@ -922,6 +947,7 @@ mod tests { env: None, untracked_env: None, input: Some(vec![UserInputEntry::Glob("config/**".into())]), + output: None, }), &pkg, &ws, @@ -943,6 +969,7 @@ mod tests { env: None, untracked_env: None, input: Some(vec![UserInputEntry::Glob("config/**".into())]), + output: None, }), &pkg, &ws, @@ -970,6 +997,7 @@ mod tests { UserInputEntry::Glob("config/**".into()), UserInputEntry::Auto(vite_task_graph::config::user::AutoInput { auto: true }), ]), + output: None, }), &pkg, &ws, @@ -995,6 +1023,7 @@ mod tests { env: None, untracked_env: None, input: Some(vec![UserInputEntry::Glob("config/**".into())]), + output: None, }), &pkg, &ws, @@ -1018,6 +1047,7 @@ mod tests { env: None, untracked_env: None, input: Some(vec![UserInputEntry::Glob("config/**".into())]), + output: None, }), &pkg, &ws, @@ -1050,6 +1080,7 @@ mod tests { env: None, untracked_env: None, input: Some(vec![UserInputEntry::Glob("!dist/**".into())]), + output: None, }), &pkg, &ws, diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/query_tool_synthetic_task_in_user_task.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/query_tool_synthetic_task_in_user_task.jsonc index 9f64c0760..63bad854a 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/query_tool_synthetic_task_in_user_task.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/query_tool_synthetic_task_in_user_task.jsonc @@ -60,6 +60,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/task_graph.jsonc index 253fae9f5..da3f2d593 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_script_caching.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_script_caching.jsonc index 67b1ae1c5..63272b8d8 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_script_caching.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_script_caching.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_task_caching_even_when_cache_tasks_is_false.jsonc index 4ceddbc22..af84a0ef1 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.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_task_caching_even_when_cache_tasks_is_false.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_on_task_with_per_task_cache_true_enables_caching.jsonc index cf1e3a1ea..44c82a701 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.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_on_task_with_per_task_cache_true_enables_caching.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/task_graph.jsonc index feb1a3c63..d98768c05 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -116,6 +126,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -150,6 +165,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_echo_and_lint_with_extra_args.jsonc index 6bcfb7bc0..f589579d4 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_echo_and_lint_with_extra_args.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_echo_and_lint_with_extra_args.jsonc @@ -87,6 +87,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_lint_and_echo_with_extra_args.jsonc index e1997caff..586844bfc 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_lint_and_echo_with_extra_args.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_lint_and_echo_with_extra_args.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_normal_task_with_extra_args.jsonc index e273f579d..07660f771 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_normal_task_with_extra_args.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_normal_task_with_extra_args.jsonc @@ -60,6 +60,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task.jsonc index 10fcc3e07..a75b9dfc1 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task_with_cwd.jsonc index 10fcc3e07..a75b9dfc1 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task_with_cwd.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task_with_cwd.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_with_extra_args_in_user_task.jsonc index 364b3e976..71683ee96 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_with_extra_args_in_user_task.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_with_extra_args_in_user_task.jsonc @@ -61,6 +61,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/task_graph.jsonc index 2ca6de9ad..13b2802fd 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -128,6 +143,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots/task_graph.jsonc index 876930785..5129800c3 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/snapshots/task_graph.jsonc index bc7667e73..8f084f7de 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_another_task_cached_by_default.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_another_task_cached_by_default.jsonc index 901475f0c..b344b082b 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_another_task_cached_by_default.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_another_task_cached_by_default.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_task_cached_by_default.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_task_cached_by_default.jsonc index d34e6f5ce..78a406fa1 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_task_cached_by_default.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_task_cached_by_default.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/task_graph.jsonc index a5632de79..cb6afda93 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/snapshots/task_graph.jsonc index 4688df733..ebfa2e8b3 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots/task_graph.jsonc index b8b926650..fb069d24d 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/task_graph.jsonc index 1d5d0edf9..55ebae45d 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_script_cached_when_global_cache_true.jsonc index 7d74b65d7..a82d5e2f9 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_script_cached_when_global_cache_true.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_script_cached_when_global_cache_true.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_task_cached_when_global_cache_true.jsonc index dbcb6a68e..a16181471 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_task_cached_when_global_cache_true.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_task_cached_when_global_cache_true.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/task_graph.jsonc index b19be3704..fff2e8ef4 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/task_graph.jsonc @@ -48,6 +48,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -82,6 +87,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_lint_should_put_synthetic_task_under_cwd.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_lint_should_put_synthetic_task_under_cwd.jsonc index 1f4636675..d226a413b 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_lint_should_put_synthetic_task_under_cwd.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_lint_should_put_synthetic_task_under_cwd.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_run_should_not_affect_expanded_task_cwd.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_run_should_not_affect_expanded_task_cwd.jsonc index 93c1d00ef..4142b0960 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_run_should_not_affect_expanded_task_cwd.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_run_should_not_affect_expanded_task_cwd.jsonc @@ -84,6 +84,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/task_graph.jsonc index 4fc7ccaad..a0ea080a3 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/snapshots/task_graph.jsonc index c02cdb6ed..9597c4b3a 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -128,6 +143,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -162,6 +182,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -196,6 +221,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -230,6 +260,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -264,6 +299,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -298,6 +338,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -332,6 +377,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -366,6 +416,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -400,6 +455,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -434,6 +494,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -468,6 +533,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -502,6 +572,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -536,6 +611,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -570,6 +650,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -604,6 +689,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -638,6 +728,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -672,6 +767,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -706,6 +806,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -740,6 +845,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -774,6 +884,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/snapshots/task_graph.jsonc index 9b82411d5..e246a1db3 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots/task_graph.jsonc index 6336fe8ae..2c07f9bd1 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -65,6 +70,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/snapshots/task_graph.jsonc index ae1236726..9b4779c26 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -65,6 +70,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/task_graph.jsonc index 2b1c82e45..b3d46d315 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/snapshots/task_graph.jsonc index 726f29388..0a88447df 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -100,6 +105,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -134,6 +144,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -168,6 +183,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -207,6 +227,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -241,6 +266,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -275,6 +305,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -309,6 +344,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/snapshots/task_graph.jsonc index 3d0fc4335..4c6c21e3b 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -95,6 +100,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -129,6 +139,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -163,6 +178,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -197,6 +217,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -231,6 +256,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -270,6 +300,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -304,6 +339,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -338,6 +378,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -381,6 +426,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/query_extra_args_only_reach_requested_task.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/query_extra_args_only_reach_requested_task.jsonc index a6b86de0d..da9501116 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/query_extra_args_only_reach_requested_task.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/query_extra_args_only_reach_requested_task.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { @@ -140,6 +145,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/task_graph.jsonc index a110c9d85..e483d70c4 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/task_graph.jsonc index 13011495a..27c09ea17 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -128,6 +143,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -162,6 +182,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -196,6 +221,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -230,6 +260,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -264,6 +299,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -298,6 +338,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -332,6 +377,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -366,6 +416,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -400,6 +455,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -434,6 +494,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/snapshots/task_graph.jsonc index a793212e7..11c015377 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/snapshots/task_graph.jsonc @@ -30,6 +30,11 @@ "negative_globs": [ "dist/**" ] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/snapshots/task_graph.jsonc index 03b8005dc..b82a6e8da 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/snapshots/task_graph.jsonc @@ -31,6 +31,11 @@ "negative_globs": [ "dist/**" ] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested___cache_enables_inner_task_caching.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested___cache_enables_inner_task_caching.jsonc index e1b6e4bb9..759b8f84e 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested___cache_enables_inner_task_caching.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested___cache_enables_inner_task_caching.jsonc @@ -84,6 +84,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___cache_propagates_to_nested_run_without_flags.jsonc index d3ba94f6a..a4f58176f 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___cache_propagates_to_nested_run_without_flags.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___cache_propagates_to_nested_run_without_flags.jsonc @@ -84,6 +84,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___no_cache_does_not_propagate_into_nested___cache.jsonc index bfd033588..6e384f70a 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___no_cache_does_not_propagate_into_nested___cache.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___no_cache_does_not_propagate_into_nested___cache.jsonc @@ -84,6 +84,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/task_graph.jsonc index 269edec05..1200a2faa 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -128,6 +143,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots/task_graph.jsonc index 8492756ac..eef8a1a13 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/task_graph.jsonc index 837fc00b2..599e3b086 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/task_graph.jsonc index 5c4ce9ebd..9c50b6a60 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -128,6 +143,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -162,6 +182,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots/task_graph.jsonc index 8c09f9552..aef412250 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/snapshots/task_graph.jsonc index 49781abe1..a902fcadd 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -128,6 +143,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -162,6 +182,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -196,6 +221,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -230,6 +260,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -264,6 +299,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/task_graph.jsonc index bb5913bb4..e5271222f 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -128,6 +143,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -162,6 +182,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -196,6 +221,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots/task_graph.jsonc index 43f2ac980..2961d7313 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots/task_graph.jsonc index b7db12108..36a3f67d0 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -128,6 +143,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots/task_graph.jsonc index a70ddee47..005d03240 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/query_shell_fallback_for_pipe_command.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/query_shell_fallback_for_pipe_command.jsonc index 738582f79..b3da6fda6 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/query_shell_fallback_for_pipe_command.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/query_shell_fallback_for_pipe_command.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/task_graph.jsonc index c0d1aba3d..51fd7c317 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_parent_cache_false_does_not_affect_expanded_query_tasks.jsonc index 06e4fc593..2df26b1a1 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_parent_cache_false_does_not_affect_expanded_query_tasks.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_parent_cache_false_does_not_affect_expanded_query_tasks.jsonc @@ -84,6 +84,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_script_cache_false_does_not_affect_expanded_synthetic_cache.jsonc index a6bbadeaf..d5e183324 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_script_cache_false_does_not_affect_expanded_synthetic_cache.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_script_cache_false_does_not_affect_expanded_synthetic_cache.jsonc @@ -84,6 +84,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_untrackedEnv_inherited_by_synthetic.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_untrackedEnv_inherited_by_synthetic.jsonc index b36b29e28..812734076 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_untrackedEnv_inherited_by_synthetic.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_untrackedEnv_inherited_by_synthetic.jsonc @@ -59,6 +59,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "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.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_with_cache_true_enables_synthetic_cache.jsonc index c3e7c5ffb..9ccca7a11 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_with_cache_true_enables_synthetic_cache.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_with_cache_true_enables_synthetic_cache.jsonc @@ -58,6 +58,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/task_graph.jsonc index b1cfddc17..b550e3faf 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -116,6 +126,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -151,6 +166,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -185,6 +205,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/query_synthetic_in_subpackage.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/query_synthetic_in_subpackage.jsonc index 2c60292b6..5a40fe048 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/query_synthetic_in_subpackage.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/query_synthetic_in_subpackage.jsonc @@ -84,6 +84,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/task_graph.jsonc index 554d3ab06..f45c1001a 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/task_graph.jsonc index 23a84412a..40ea1a092 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots/task_graph.jsonc index 0ae0ad7d5..00be69c6c 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_filter_from_root.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_filter_from_root.jsonc index e235a1f01..ba1f67e9e 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_filter_from_root.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_filter_from_root.jsonc @@ -64,6 +64,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_in_subpackage.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_in_subpackage.jsonc index a7e87dd0e..dc5f9a939 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_in_subpackage.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_in_subpackage.jsonc @@ -64,6 +64,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } }, "spawn_command": { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/task_graph.jsonc index 9eb638925..d8a3142d4 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots/task_graph.jsonc index 5a9820d52..f7ee35330 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots/task_graph.jsonc index 683023848..2a23ddc71 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -65,6 +70,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -99,6 +109,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -133,6 +148,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots/task_graph.jsonc index 1cd3ba456..f031c893d 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots/task_graph.jsonc index 1e0d1452d..5e3cee720 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -128,6 +143,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/snapshots/task_graph.jsonc index 56576d866..5a64e4c3a 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/task_graph.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/task_graph.jsonc index 0b4cbfe81..76f667459 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/task_graph.jsonc +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/task_graph.jsonc @@ -26,6 +26,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -60,6 +65,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } @@ -94,6 +104,11 @@ "includes_auto": true, "positive_globs": [], "negative_globs": [] + }, + "output_config": { + "includes_auto": false, + "positive_globs": [], + "negative_globs": [] } } } From eb4ec3753eef3dffbec8cb9b06588978ebd00a5d Mon Sep 17 00:00:00 2001 From: branchseer Date: Mon, 11 May 2026 14:18:59 +0800 Subject: [PATCH 02/10] docs(changelog): trim output field entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b9d75801..b702c12e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -- **Added** `output` field for cached tasks: archives output files matching the configured globs after a successful run and restores them on cache hit. Patterns are relative to the package directory; supports negative patterns (e.g. `"!dist/cache/**"`) and `{pattern, base}` form for explicit base. ([#321](https://github.com/voidzero-dev/vite-task/pull/321)) +- **Added** `output` field for cached tasks: archives matching files after a successful run and restores them on cache hit ([#375](https://github.com/voidzero-dev/vite-task/pull/375)) - **Fixed** Windows cached tasks can now run package shims rewritten through PowerShell; default env passthrough now preserves `PATHEXT` ([#366](https://github.com/voidzero-dev/vite-task/pull/366)) - **Added** Platform support for targets without `input` auto-inference (e.g. Android). Tasks still run; those relying on auto-inference run uncached, with the summary noting that `input` must be configured manually to enable caching ([#352](https://github.com/voidzero-dev/vite-task/pull/352)) - **Fixed** `vp run` no longer aborts with `failed to prepare the command for injection: Invalid argument` when the user environment already has `LD_PRELOAD` (Linux) or `DYLD_INSERT_LIBRARIES` (macOS) set. The tracer shim is now appended to any existing value and placed last, so user preloads keep their symbol-interposition precedence ([#340](https://github.com/voidzero-dev/vite-task/issues/340)) From 056bd4698b423c28e35b4cc131a30f9eb50f7dc9 Mon Sep 17 00:00:00 2001 From: branchseer Date: Mon, 11 May 2026 14:20:11 +0800 Subject: [PATCH 03/10] refactor(cache/archive): use metadata error for existence check Combines the prior `exists()` precheck with the subsequent metadata call into a single `metadata()` invocation. NotFound is the only expected miss (file deleted between glob walk and archive write); any other I/O error now propagates instead of being swallowed by `exists()` returning false. --- crates/vite_task/src/session/cache/archive.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/vite_task/src/session/cache/archive.rs b/crates/vite_task/src/session/cache/archive.rs index 9a4982ae2..26b28aca7 100644 --- a/crates/vite_task/src/session/cache/archive.rs +++ b/crates/vite_task/src/session/cache/archive.rs @@ -1,6 +1,6 @@ //! Output archive creation and extraction using tar + zstd compression. -use std::fs::File; +use std::{fs::File, io}; use vite_path::{AbsolutePath, RelativePathBuf}; @@ -23,11 +23,13 @@ pub fn create_output_archive( for rel_path in output_files { let abs_path = workspace_root.join(rel_path); - // Skip files that no longer exist (task may delete temp files) - if !abs_path.as_path().exists() { - continue; - } - let metadata = std::fs::metadata(abs_path.as_path())?; + // Skip files that no longer exist (task may delete temp files between + // glob walk and archiving). Any other error is propagated. + let metadata = match std::fs::metadata(abs_path.as_path()) { + Ok(m) => m, + Err(err) if err.kind() == io::ErrorKind::NotFound => continue, + Err(err) => return Err(err.into()), + }; if metadata.is_file() { let mut file = File::open(abs_path.as_path())?; let mut header = tar::Header::new_gnu(); From 008f50afa64542ab999c14c6932d2f0679c64e59 Mon Sep 17 00:00:00 2001 From: branchseer Date: Mon, 11 May 2026 14:20:46 +0800 Subject: [PATCH 04/10] refactor(cache): simplify fingerprint-mismatch detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old structure had a fallback `InputConfig` arm that was reachable only if all three fields (spawn, input, output) matched — but `get_by_cache_key` for the current key returned None earlier, so that state is contradictory. Flatten to a single if/else-if chain in priority order and assert the residual case with `debug_assert!`. --- crates/vite_task/src/session/cache/mod.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/vite_task/src/session/cache/mod.rs b/crates/vite_task/src/session/cache/mod.rs index 6d1ee6a9c..f310ddc00 100644 --- a/crates/vite_task/src/session/cache/mod.rs +++ b/crates/vite_task/src/session/cache/mod.rs @@ -283,26 +283,25 @@ impl ExecutionCache { if let Some(old_cache_key) = self.get_cache_key_by_execution_key(execution_cache_key).await? { - // Destructure to ensure we handle all fields when new ones are added + // Destructure to ensure we handle all fields when new ones are added. + // `get_by_cache_key` above returned None for the *current* cache key, + // so at least one field on `old_cache_key` must differ from the + // current metadata — checked in priority order (spawn → input → output). let CacheEntryKey { spawn_fingerprint: old_spawn_fingerprint, input_config: old_input_config, output_config: old_output_config, } = old_cache_key; - let mismatch = if old_spawn_fingerprint == *spawn_fingerprint { - // spawn fingerprint is the same but input_config or output_config changed - if old_input_config != cache_metadata.input_config { - FingerprintMismatch::InputConfig - } else if old_output_config != cache_metadata.output_config { - FingerprintMismatch::OutputConfig - } else { - FingerprintMismatch::InputConfig - } - } else { + let mismatch = if old_spawn_fingerprint != *spawn_fingerprint { FingerprintMismatch::SpawnFingerprint { old: old_spawn_fingerprint, new: spawn_fingerprint.clone(), } + } else if old_input_config != cache_metadata.input_config { + FingerprintMismatch::InputConfig + } else { + debug_assert!(old_output_config != cache_metadata.output_config); + FingerprintMismatch::OutputConfig }; return Ok(Err(CacheMiss::FingerprintMismatch(mismatch))); } From a7e4647afcb35ea46158fc0536af4855dbdbc51e Mon Sep 17 00:00:00 2001 From: branchseer Date: Mon, 11 May 2026 14:26:16 +0800 Subject: [PATCH 05/10] feat(cache): explain how to recover from corrupted output archive When archive restoration fails on cache hit (file missing, truncated, or unreadable), wrap the I/O error with a recovery hint telling the user to run ` cache clean`. This matches the message style already used for schema-version mismatches. --- crates/vite_task/src/session/execute/mod.rs | 21 ++++++++++++++++++++- crates/vite_task/src/session/mod.rs | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/vite_task/src/session/execute/mod.rs b/crates/vite_task/src/session/execute/mod.rs index 29719a74f..bcfe9000c 100644 --- a/crates/vite_task/src/session/execute/mod.rs +++ b/crates/vite_task/src/session/execute/mod.rs @@ -78,6 +78,9 @@ struct ExecutionContext<'a> { cache_base_path: &'a Arc, /// Directory where cache files (db, archives) are stored. cache_dir: &'a AbsolutePath, + /// Public-facing program name (e.g. `vp`), used in user-facing error + /// messages that suggest a CLI command (e.g. `cache clean`). + program_name: &'a str, /// Token cancelled when a task fails. Kills in-flight child processes /// (via `start_kill` in spawn.rs), prevents scheduling new tasks, and /// prevents caching results of concurrently-running tasks. @@ -240,6 +243,7 @@ impl ExecutionContext<'_> { self.cache, self.cache_base_path, self.cache_dir, + self.program_name, self.fast_fail_token.clone(), self.interrupt_token.clone(), ) @@ -327,12 +331,17 @@ struct TrackingOutcome { clippy::too_many_lines, reason = "sequential cache check, execute, and update steps are clearer in one function" )] +#[expect( + clippy::too_many_arguments, + reason = "these are the unavoidable inputs for a free-function cache-aware spawn" +)] pub async fn execute_spawn( mut leaf_reporter: Box, spawn_execution: &SpawnExecution, cache: &ExecutionCache, cache_base_path: &Arc, cache_dir: &AbsolutePath, + program_name: &str, fast_fail_token: CancellationToken, interrupt_token: CancellationToken, ) -> SpawnOutcome { @@ -405,10 +414,19 @@ pub async fn execute_spawn( let _ = writer.write_all(&output.content); let _ = writer.flush(); } - // Restore output files from the cached archive + // Restore output files from the cached archive. Failure here means the + // archive file is missing, truncated, or otherwise unreadable — the + // task can't proceed because the cache promised the outputs would be + // restored. Surface a recovery instruction rather than just the raw + // I/O error so users know to clear the cache. if let Some(ref archive_name) = cached.output_archive { let archive_path = cache_dir.join(archive_name.as_str()); if let Err(err) = archive::extract_output_archive(cache_base_path, &archive_path) { + let err = err.context(vite_str::format!( + "failed to restore cached outputs from {}; the archive may have been deleted \ + or corrupted. Run `{program_name} cache clean` to clear the cache.", + archive_path.as_path().display() + )); leaf_reporter.finish( None, CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheHit), @@ -756,6 +774,7 @@ impl Session<'_> { cache, cache_base_path: &self.workspace_path, cache_dir: &self.cache_path, + program_name: self.program_name.as_str(), fast_fail_token: CancellationToken::new(), interrupt_token, }; diff --git a/crates/vite_task/src/session/mod.rs b/crates/vite_task/src/session/mod.rs index 8edef289f..478421976 100644 --- a/crates/vite_task/src/session/mod.rs +++ b/crates/vite_task/src/session/mod.rs @@ -678,6 +678,7 @@ impl<'a> Session<'a> { cache, &self.workspace_path, &self.cache_path, + self.program_name.as_str(), tokio_util::sync::CancellationToken::new(), tokio_util::sync::CancellationToken::new(), ) From f6462825a9afe27101bbbcc5bff48ac79497eb38 Mon Sep 17 00:00:00 2001 From: branchseer Date: Mon, 11 May 2026 14:28:14 +0800 Subject: [PATCH 06/10] refactor(execute/glob_inputs): share walker between input hashing and output collection `compute_globbed_inputs` and `collect_glob_paths` had identical walk-and-filter logic, differing only in what they did per file (hash content vs. record path). Extract a common `walk_glob_files` helper that takes a closure; both public functions are now thin wrappers over it. --- .../src/session/execute/glob_inputs.rs | 169 +++++++----------- 1 file changed, 68 insertions(+), 101 deletions(-) diff --git a/crates/vite_task/src/session/execute/glob_inputs.rs b/crates/vite_task/src/session/execute/glob_inputs.rs index a3d966e11..29cac9fc1 100644 --- a/crates/vite_task/src/session/execute/glob_inputs.rs +++ b/crates/vite_task/src/session/execute/glob_inputs.rs @@ -1,9 +1,10 @@ -//! Glob-based input file discovery and fingerprinting. +//! Glob-based file discovery used by cache input fingerprinting and output +//! archiving. //! -//! 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). +//! Both rely on the same walker — positive globs collect candidate files, +//! negative globs filter them out, all workspace-root-relative. The input +//! path adds per-file content hashing on top; the output path only needs +//! the paths. use std::{collections::BTreeMap, fs::File, io}; @@ -16,55 +17,56 @@ use wax::{ walk::{Entry as _, FileIterator as _}, }; -/// Collect walk entries into the result map. +/// Walk positive globs, filtering with negative globs, and call `for_each_file` +/// with the workspace-relative path of each file entry. The absolute path is +/// also passed for callers that need to read file contents (e.g. hashing). /// /// Walk errors for non-existent directories are skipped gracefully. -fn collect_walk_entries( - walk: impl Iterator>, +/// Returning an error from `for_each_file` aborts the walk. +#[expect( + clippy::disallowed_types, + reason = "wax::walk::Entry::path returns std::path::Path; callers convert as needed" +)] +fn walk_glob_files( workspace_root: &AbsolutePath, - result: &mut BTreeMap, + positive_globs: &std::collections::BTreeSet, + negative_globs: &std::collections::BTreeSet, + mut for_each_file: impl FnMut(&std::path::Path, RelativePathBuf) -> anyhow::Result<()>, ) -> 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)?; + if positive_globs.is_empty() { + return Ok(()); + } - let std::collections::btree_map::Entry::Vacant(vacant) = - result.entry(relative_to_workspace) - else { - continue; // Already hashed by a previous glob pattern - }; + let negatives: Vec> = negative_globs + .iter() + .map(|p| Ok(Glob::new(p.as_str())?.into_owned())) + .collect::>()?; + let negation = wax::any(negatives)?; - // 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()); + for pattern in positive_globs { + let glob = Glob::new(pattern.as_str())?.into_owned(); + let walk = glob.walk(workspace_root.as_path()); + for entry in walk.not(negation.clone())? { + 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(); + let Some(stripped) = path.strip_prefix(workspace_root.as_path()).ok() else { + continue; // Skip if path is outside workspace_root + }; + let relative = RelativePathBuf::new(stripped)?; + for_each_file(path, relative)?; } } Ok(()) @@ -88,24 +90,22 @@ pub fn compute_globbed_inputs( 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)?; - } - + walk_glob_files(workspace_root, positive_globs, negative_globs, |path, relative| { + let std::collections::btree_map::Entry::Vacant(vacant) = result.entry(relative) else { + return Ok(()); // Already hashed by a previous glob pattern + }; + 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(()) + })?; Ok(result) } @@ -118,44 +118,11 @@ pub fn collect_glob_paths( positive_globs: &std::collections::BTreeSet, negative_globs: &std::collections::BTreeSet, ) -> anyhow::Result> { - if positive_globs.is_empty() { - return Ok(Vec::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 = Vec::new(); - - for pattern in positive_globs { - let glob = Glob::new(pattern.as_str())?.into_owned(); - let walk = glob.walk(workspace_root.as_path()); - for entry in walk.not(negation.clone())? { - let entry = match entry { - Ok(entry) => entry, - Err(err) => { - 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(); - let Some(stripped) = path.strip_prefix(workspace_root.as_path()).ok() else { - continue; - }; - let relative = RelativePathBuf::new(stripped)?; - result.push(relative); - } - } - + walk_glob_files(workspace_root, positive_globs, negative_globs, |_, relative| { + result.push(relative); + Ok(()) + })?; result.sort(); result.dedup(); Ok(result) From 404bcd95f52abb15958d2e84ed2683da690520b0 Mon Sep 17 00:00:00 2001 From: branchseer Date: Mon, 11 May 2026 14:31:57 +0800 Subject: [PATCH 07/10] test(e2e): verify old output archive is removed on re-run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `vtt list-dir [--ext ]` for inspecting cache state from e2e tests, plus a UUID redactor so archive filenames (which are random per run) stay stable across snapshot regenerations. The new `output_globs___old_archive_removed_on_rewrite` fixture asserts that after two cache-missing runs of the same task, the cache directory contains exactly one `.tar.zst` archive — proving the cleanup path in `ExecutionCache::update` actually runs. --- crates/vite_task_bin/src/vtt/list_dir.rs | 44 +++++++++++++++++++ crates/vite_task_bin/src/vtt/main.rs | 4 +- .../fixtures/output_cache_test/snapshots.toml | 21 +++++++++ ..._globs___old_archive_removed_on_rewrite.md | 35 +++++++++++++++ .../tests/e2e_snapshots/redact.rs | 5 +++ 5 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 crates/vite_task_bin/src/vtt/list_dir.rs create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md diff --git a/crates/vite_task_bin/src/vtt/list_dir.rs b/crates/vite_task_bin/src/vtt/list_dir.rs new file mode 100644 index 000000000..7a51297c6 --- /dev/null +++ b/crates/vite_task_bin/src/vtt/list_dir.rs @@ -0,0 +1,44 @@ +/// List entries in a directory, sorted by name, one per line. +/// +/// Usage: `vtt list-dir [--ext ]` +/// +/// With `--ext`, only entries whose filename ends with the given suffix are +/// printed (the leading `.` is part of the suffix you pass, e.g. `.tar.zst`). +/// +/// Used by e2e tests to assert on cache directory contents (e.g. exactly one +/// `.tar.zst` archive after a re-run that should have cleaned up the prior +/// archive). +pub fn run(args: &[String]) -> Result<(), Box> { + let mut dir: Option<&str> = None; + let mut ext: Option<&str> = None; + let mut i = 0; + while i < args.len() { + match args[i].as_str() { + "--ext" => { + i += 1; + ext = Some(args.get(i).ok_or("--ext requires a value")?.as_str()); + } + other if dir.is_none() => dir = Some(other), + other => return Err(format!("unexpected argument: {other}").into()), + } + i += 1; + } + let dir = dir.ok_or("Usage: vtt list-dir [--ext ]")?; + + let mut names: Vec = Vec::new(); + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let name = entry.file_name().to_string_lossy().into_owned(); + if let Some(suffix) = ext + && !name.ends_with(suffix) + { + continue; + } + names.push(name); + } + names.sort(); + for name in names { + println!("{name}"); + } + Ok(()) +} diff --git a/crates/vite_task_bin/src/vtt/main.rs b/crates/vite_task_bin/src/vtt/main.rs index 527e423b3..66fbcba0a 100644 --- a/crates/vite_task_bin/src/vtt/main.rs +++ b/crates/vite_task_bin/src/vtt/main.rs @@ -11,6 +11,7 @@ mod check_tty; mod cp; mod exit; mod exit_on_ctrlc; +mod list_dir; mod mkdir; mod pipe_stdin; mod print; @@ -28,7 +29,7 @@ fn main() { if args.len() < 2 { eprintln!("Usage: vtt [args...]"); eprintln!( - "Subcommands: barrier, check-tty, cp, exit, exit-on-ctrlc, mkdir, pipe-stdin, print, print-cwd, print-env, print-file, read-stdin, replace-file-content, rm, touch-file, write-file" + "Subcommands: barrier, check-tty, cp, exit, exit-on-ctrlc, list-dir, mkdir, pipe-stdin, print, print-cwd, print-env, print-file, read-stdin, replace-file-content, rm, touch-file, write-file" ); std::process::exit(1); } @@ -42,6 +43,7 @@ fn main() { "cp" => cp::run(&args[2..]), "exit" => exit::run(&args[2..]), "exit-on-ctrlc" => exit_on_ctrlc::run(), + "list-dir" => list_dir::run(&args[2..]), "mkdir" => mkdir::run(&args[2..]), "pipe-stdin" => pipe_stdin::run(&args[2..]), "print" => { diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml index d554d79b4..c0591bfb9 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml @@ -18,6 +18,27 @@ steps = [ ["vtt", "print-file", "dist/output.txt"], ] +[[e2e]] +name = "output_globs___old_archive_removed_on_rewrite" +comment = """ +When a cached task re-runs (cache miss because an input changed), it +writes a new archive and the previous archive file is cleaned up. After +two runs of the same task the cache directory still contains only one +\\\".tar.zst\\\" file. +""" +steps = [ + # First run - cache miss, produces archive A + ["vt", "run", "build"], + # Exactly one archive on disk + ["vtt", "list-dir", "node_modules/.vite/task-cache", "--ext", ".tar.zst"], + # Modify the input so the next run is a cache miss + ["vtt", "write-file", "src/main.ts", "changed"], + # Second run - cache miss, produces archive B; archive A is deleted + ["vt", "run", "build"], + # Still only one archive on disk (A removed, B written) + ["vtt", "list-dir", "node_modules/.vite/task-cache", "--ext", ".tar.zst"], +] + [[e2e]] name = "output_globs___negative_excludes_files_from_archive" comment = """ diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md new file mode 100644 index 000000000..fa99ee608 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md @@ -0,0 +1,35 @@ +# output_globs___old_archive_removed_on_rewrite + +When a cached task re-runs (cache miss because an input changed), it +writes a new archive and the previous archive file is cleaned up. After +two runs of the same task the cache directory still contains only one +\".tar.zst\" file. + +## `vt run build` + +``` +$ vtt write-file dist/output.txt built +``` + +## `vtt list-dir node_modules/.vite/task-cache --ext .tar.zst` + +``` +.tar.zst +``` + +## `vtt write-file src/main.ts changed` + +``` +``` + +## `vt run build` + +``` +$ vtt write-file dist/output.txt built ○ cache miss: 'src/main.ts' modified, executing +``` + +## `vtt list-dir node_modules/.vite/task-cache --ext .tar.zst` + +``` +.tar.zst +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/redact.rs b/crates/vite_task_bin/tests/e2e_snapshots/redact.rs index 4cc3fa64e..0137a1275 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/redact.rs +++ b/crates/vite_task_bin/tests/e2e_snapshots/redact.rs @@ -64,6 +64,11 @@ pub fn redact_e2e_output(mut output: String, workspace_root: &str) -> String { redact_string(&mut output, &redactions); + // Redact UUIDs (e.g. cache archive filenames `.tar.zst`) to "" + let uuid_regex = + regex::Regex::new(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}").unwrap(); + output = uuid_regex.replace_all(&output, "").into_owned(); + // Redact durations like "0ns", "123ms" or "1.23s" to "" let duration_regex = regex::Regex::new(r"\d+(\.\d+)?(ns|ms|s)").unwrap(); output = duration_regex.replace_all(&output, "").into_owned(); From 4bfa472e51c713bc6cb53a77dbe9826f263e56fe Mon Sep 17 00:00:00 2001 From: branchseer Date: Mon, 11 May 2026 15:04:58 +0800 Subject: [PATCH 08/10] refactor(execute/glob): rename module and tighten walker API - Rename crates/vite_task/src/session/execute/glob_inputs.rs to glob.rs to reflect its broader role (both input fingerprinting and output collection use it). - Change `walk_glob_files` to call the user closure with `AbsolutePathBuf` (was `(&Path, RelativePathBuf)`); callers strip the root themselves when they need a relative path. The closure now returns `anyhow::Result>` so a caller can stop the walk without producing an error. - Drop `workspace` from identifiers inside the module: positive globs are rooted at the caller-supplied `root`, which need not be a workspace root. --- .../execute/{glob_inputs.rs => glob.rs} | 94 +++++++++---------- crates/vite_task/src/session/execute/mod.rs | 6 +- 2 files changed, 50 insertions(+), 50 deletions(-) rename crates/vite_task/src/session/execute/{glob_inputs.rs => glob.rs} (86%) diff --git a/crates/vite_task/src/session/execute/glob_inputs.rs b/crates/vite_task/src/session/execute/glob.rs similarity index 86% rename from crates/vite_task/src/session/execute/glob_inputs.rs rename to crates/vite_task/src/session/execute/glob.rs index 29cac9fc1..39c92aa4e 100644 --- a/crates/vite_task/src/session/execute/glob_inputs.rs +++ b/crates/vite_task/src/session/execute/glob.rs @@ -2,36 +2,30 @@ //! archiving. //! //! Both rely on the same walker — positive globs collect candidate files, -//! negative globs filter them out, all workspace-root-relative. The input -//! path adds per-file content hashing on top; the output path only needs -//! the paths. +//! negative globs filter them out. The input path adds per-file content +//! hashing on top; the output path only needs the paths. -use std::{collections::BTreeMap, fs::File, io}; +use std::{collections::BTreeMap, fs::File, io, ops::ControlFlow}; -#[cfg(test)] -use vite_path::AbsolutePathBuf; -use vite_path::{AbsolutePath, RelativePathBuf}; +use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf}; use vite_str::Str; use wax::{ Glob, walk::{Entry as _, FileIterator as _}, }; -/// Walk positive globs, filtering with negative globs, and call `for_each_file` -/// with the workspace-relative path of each file entry. The absolute path is -/// also passed for callers that need to read file contents (e.g. hashing). +/// Walk positive globs (rooted at `root`), filtering with negative globs, and +/// call `for_each_file` with the absolute path of each file entry. Stripping +/// the root prefix (if needed) is the caller's responsibility. /// -/// Walk errors for non-existent directories are skipped gracefully. -/// Returning an error from `for_each_file` aborts the walk. -#[expect( - clippy::disallowed_types, - reason = "wax::walk::Entry::path returns std::path::Path; callers convert as needed" -)] +/// Walk errors for non-existent directories are skipped gracefully. Returning +/// `Err` from `for_each_file` aborts the walk with that error; returning +/// `Ok(ControlFlow::Break(()))` stops the walk cleanly. fn walk_glob_files( - workspace_root: &AbsolutePath, + root: &AbsolutePath, positive_globs: &std::collections::BTreeSet, negative_globs: &std::collections::BTreeSet, - mut for_each_file: impl FnMut(&std::path::Path, RelativePathBuf) -> anyhow::Result<()>, + mut for_each_file: impl FnMut(AbsolutePathBuf) -> anyhow::Result>, ) -> anyhow::Result<()> { if positive_globs.is_empty() { return Ok(()); @@ -45,7 +39,7 @@ fn walk_glob_files( for pattern in positive_globs { let glob = Glob::new(pattern.as_str())?.into_owned(); - let walk = glob.walk(workspace_root.as_path()); + let walk = glob.walk(root.as_path()); for entry in walk.not(negation.clone())? { let entry = match entry { Ok(entry) => entry, @@ -61,12 +55,12 @@ fn walk_glob_files( if !entry.file_type().is_file() { continue; } - let path = entry.path(); - let Some(stripped) = path.strip_prefix(workspace_root.as_path()).ok() else { - continue; // Skip if path is outside workspace_root + let Some(absolute) = AbsolutePathBuf::new(entry.path().to_path_buf()) else { + continue; // wax can hand back non-absolute paths in some cases }; - let relative = RelativePathBuf::new(stripped)?; - for_each_file(path, relative)?; + if for_each_file(absolute)?.is_break() { + return Ok(()); + } } } Ok(()) @@ -74,28 +68,23 @@ fn walk_glob_files( /// 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). +/// Globs are rooted at `root` (typically the workspace root, resolved at task +/// graph stage). Returns a sorted map of paths relative to `root` to their +/// content hashes. Only files are included (directories are skipped). pub fn compute_globbed_inputs( - workspace_root: &AbsolutePath, + root: &AbsolutePath, positive_globs: &std::collections::BTreeSet, negative_globs: &std::collections::BTreeSet, ) -> anyhow::Result> { let mut result = BTreeMap::new(); - walk_glob_files(workspace_root, positive_globs, negative_globs, |path, relative| { + walk_glob_files(root, positive_globs, negative_globs, |absolute| { + let Some(relative) = strip_root(root, &absolute)? else { + return Ok(ControlFlow::Continue(())); // outside root + }; let std::collections::btree_map::Entry::Vacant(vacant) = result.entry(relative) else { - return Ok(()); // Already hashed by a previous glob pattern + return Ok(ControlFlow::Continue(())); // already hashed via earlier pattern }; - match hash_file_content(path) { + match hash_file_content(&absolute) { Ok(hash) => { vacant.insert(hash); } @@ -104,7 +93,7 @@ pub fn compute_globbed_inputs( } Err(err) => return Err(err.into()), } - Ok(()) + Ok(ControlFlow::Continue(())) })?; Ok(result) } @@ -114,23 +103,34 @@ pub fn compute_globbed_inputs( /// Like [`compute_globbed_inputs`] but only collects paths (no hashing). /// Used for determining which output files to archive. pub fn collect_glob_paths( - workspace_root: &AbsolutePath, + root: &AbsolutePath, positive_globs: &std::collections::BTreeSet, negative_globs: &std::collections::BTreeSet, ) -> anyhow::Result> { let mut result = Vec::new(); - walk_glob_files(workspace_root, positive_globs, negative_globs, |_, relative| { - result.push(relative); - Ok(()) + walk_glob_files(root, positive_globs, negative_globs, |absolute| { + if let Some(relative) = strip_root(root, &absolute)? { + result.push(relative); + } + Ok(ControlFlow::Continue(())) })?; result.sort(); result.dedup(); Ok(result) } -#[expect(clippy::disallowed_types, reason = "receives std::path::Path from wax glob walker")] -fn hash_file_content(path: &std::path::Path) -> io::Result { - super::hash::hash_content(io::BufReader::new(File::open(path)?)) +/// Strip `root` from `absolute`, returning `None` when the path is outside it. +/// Wraps [`AbsolutePath::strip_prefix`] so callers can use `?` without +/// borrowing `absolute` past the call. +fn strip_root( + root: &AbsolutePath, + absolute: &AbsolutePath, +) -> anyhow::Result> { + absolute.strip_prefix(root).map_err(|err| anyhow::anyhow!("{err}")) +} + +fn hash_file_content(path: &AbsolutePath) -> io::Result { + super::hash::hash_content(io::BufReader::new(File::open(path.as_path())?)) } #[cfg(test)] diff --git a/crates/vite_task/src/session/execute/mod.rs b/crates/vite_task/src/session/execute/mod.rs index bcfe9000c..a0f88303e 100644 --- a/crates/vite_task/src/session/execute/mod.rs +++ b/crates/vite_task/src/session/execute/mod.rs @@ -1,5 +1,5 @@ pub mod fingerprint; -pub mod glob_inputs; +pub mod glob; mod hash; pub mod pipe; pub mod spawn; @@ -26,7 +26,7 @@ use vite_task_plan::{ use self::tracked_accesses::TrackedPathAccesses; use self::{ fingerprint::{PathRead, PostRunFingerprint}, - glob_inputs::compute_globbed_inputs, + glob::compute_globbed_inputs, pipe::{PipeSinks, StdOutput, pipe_stdio}, spawn::{SpawnStdio, spawn}, }; @@ -721,7 +721,7 @@ fn collect_and_archive_outputs( return Ok(None); } - let output_files = glob_inputs::collect_glob_paths( + let output_files = glob::collect_glob_paths( workspace_root, &output_config.positive_globs, &output_config.negative_globs, From 015a9a659677755d338c45cfa0b953f8f316faec Mon Sep 17 00:00:00 2001 From: branchseer Date: Mon, 11 May 2026 15:06:17 +0800 Subject: [PATCH 09/10] docs(config): flag from_user_output_config as a transient helper Adds a TODO note that this method will be folded into `from_user_config` once auto output inference lands and `output` becomes a `UserInputsConfig` like `input`. --- crates/vite_task_graph/src/config/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index 0e8c28f87..4ab8231bd 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -201,6 +201,10 @@ impl ResolvedGlobConfig { /// Unlike [`Self::from_user_config`], `None` and `Some([])` both produce an empty config /// with `includes_auto = false` (no output archiving). /// + /// TODO: remove this method once auto output inference lands; at that point + /// `output` becomes a `UserInputsConfig` and routes through + /// [`Self::from_user_config`] like inputs. + /// /// # Errors /// /// Returns [`ResolveTaskConfigError`] if a glob pattern is invalid or resolves From 90fdf919dc93415f7704e61d4c203a8e59078b45 Mon Sep 17 00:00:00 2001 From: branchseer Date: Mon, 11 May 2026 15:08:14 +0800 Subject: [PATCH 10/10] test(e2e): inline step comments + assert skip.txt is not restored MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move the descriptive comments out of TOML `#` lines (invisible to snapshots) and into per-step `comment =` fields, so the rendered snapshot describes what each step is doing. - In the negative-excludes scenario, follow the restoration step with a `vtt print-file dist/skip.txt` to assert that the excluded file is reported as "not found" — proving the negative glob keeps the file out of the archive, not just out of the initial walk. --- .../fixtures/output_cache_test/snapshots.toml | 126 +++++++++++++----- ...put_globs___files_restored_on_cache_hit.md | 10 ++ ...___negative_excludes_files_from_archive.md | 20 +++ ..._globs___old_archive_removed_on_rewrite.md | 14 +- 4 files changed, 136 insertions(+), 34 deletions(-) diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml index c0591bfb9..dc6c2fc0d 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml @@ -6,16 +6,32 @@ With explicit output globs (`dist/**`), the first run writes a file to cache hit and the archived output file is restored. """ steps = [ - # First run - cache miss, writes dist/output.txt - ["vt", "run", "build"], - # Verify file was written - ["vtt", "print-file", "dist/output.txt"], - # Delete dist/ to prove restoration is real - ["vtt", "rm", "-rf", "dist"], - # Second run - cache hit, restores dist/output.txt from archive - ["vt", "run", "build"], - # File should be restored - ["vtt", "print-file", "dist/output.txt"], + { argv = [ + "vt", + "run", + "build", + ], comment = "first run — cache miss, writes dist/output.txt" }, + { argv = [ + "vtt", + "print-file", + "dist/output.txt", + ], comment = "file is on disk after the run" }, + { argv = [ + "vtt", + "rm", + "-rf", + "dist", + ], comment = "delete dist/ to prove the restore is real" }, + { argv = [ + "vt", + "run", + "build", + ], comment = "second run — cache hit, restores from archive" }, + { argv = [ + "vtt", + "print-file", + "dist/output.txt", + ], comment = "file restored from archive" }, ] [[e2e]] @@ -23,20 +39,40 @@ name = "output_globs___old_archive_removed_on_rewrite" comment = """ When a cached task re-runs (cache miss because an input changed), it writes a new archive and the previous archive file is cleaned up. After -two runs of the same task the cache directory still contains only one -\\\".tar.zst\\\" file. +two cache-missing runs of the same task the cache directory still +contains only one `.tar.zst` archive. """ steps = [ - # First run - cache miss, produces archive A - ["vt", "run", "build"], - # Exactly one archive on disk - ["vtt", "list-dir", "node_modules/.vite/task-cache", "--ext", ".tar.zst"], - # Modify the input so the next run is a cache miss - ["vtt", "write-file", "src/main.ts", "changed"], - # Second run - cache miss, produces archive B; archive A is deleted - ["vt", "run", "build"], - # Still only one archive on disk (A removed, B written) - ["vtt", "list-dir", "node_modules/.vite/task-cache", "--ext", ".tar.zst"], + { argv = [ + "vt", + "run", + "build", + ], comment = "first run — cache miss, writes archive A" }, + { argv = [ + "vtt", + "list-dir", + "node_modules/.vite/task-cache", + "--ext", + ".tar.zst", + ], comment = "exactly one archive on disk" }, + { argv = [ + "vtt", + "write-file", + "src/main.ts", + "changed", + ], comment = "modify an input so the next run is a cache miss" }, + { argv = [ + "vt", + "run", + "build", + ], comment = "second run — cache miss, writes archive B and removes A" }, + { argv = [ + "vtt", + "list-dir", + "node_modules/.vite/task-cache", + "--ext", + ".tar.zst", + ], comment = "still exactly one archive — A was cleaned up" }, ] [[e2e]] @@ -46,14 +82,40 @@ A file matched by a negative output glob is not archived, so it is not restored on cache hit. """ steps = [ - # First run - writes both dist/keep.txt and dist/skip.txt - ["vt", "run", "build-with-negative"], - # Both files exist after the run - ["vtt", "print-file", "dist/keep.txt"], - ["vtt", "print-file", "dist/skip.txt"], - # Delete dist/ to prove restoration is real - ["vtt", "rm", "-rf", "dist"], - # Second run - cache hit, only dist/keep.txt is restored - ["vt", "run", "build-with-negative"], - ["vtt", "print-file", "dist/keep.txt"], + { argv = [ + "vt", + "run", + "build-with-negative", + ], comment = "first run — writes dist/keep.txt and dist/skip.txt" }, + { argv = [ + "vtt", + "print-file", + "dist/keep.txt", + ], comment = "keep.txt was written" }, + { argv = [ + "vtt", + "print-file", + "dist/skip.txt", + ], comment = "skip.txt was written" }, + { argv = [ + "vtt", + "rm", + "-rf", + "dist", + ], comment = "delete dist/ to prove the restore is real" }, + { argv = [ + "vt", + "run", + "build-with-negative", + ], comment = "second run — cache hit, restores from archive" }, + { argv = [ + "vtt", + "print-file", + "dist/keep.txt", + ], comment = "keep.txt restored from archive" }, + { argv = [ + "vtt", + "print-file", + "dist/skip.txt", + ], comment = "skip.txt is NOT restored — it was excluded by the negative glob" }, ] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md index f10e226f1..b36af12a7 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md @@ -6,23 +6,31 @@ cache hit and the archived output file is restored. ## `vt run build` +first run — cache miss, writes dist/output.txt + ``` $ vtt write-file dist/output.txt built ``` ## `vtt print-file dist/output.txt` +file is on disk after the run + ``` built ``` ## `vtt rm -rf dist` +delete dist/ to prove the restore is real + ``` ``` ## `vt run build` +second run — cache hit, restores from archive + ``` $ vtt write-file dist/output.txt built ◉ cache hit, replaying @@ -32,6 +40,8 @@ vt run: cache hit. ## `vtt print-file dist/output.txt` +file restored from archive + ``` built ``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md index 2cb049158..ae0a65c0f 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md @@ -5,6 +5,8 @@ restored on cache hit. ## `vt run build-with-negative` +first run — writes dist/keep.txt and dist/skip.txt + ``` $ vtt write-file dist/keep.txt keep @@ -16,23 +18,31 @@ vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) ## `vtt print-file dist/keep.txt` +keep.txt was written + ``` keep ``` ## `vtt print-file dist/skip.txt` +skip.txt was written + ``` skip ``` ## `vtt rm -rf dist` +delete dist/ to prove the restore is real + ``` ``` ## `vt run build-with-negative` +second run — cache hit, restores from archive + ``` $ vtt write-file dist/keep.txt keep ◉ cache hit, replaying @@ -44,6 +54,16 @@ vt run: 2/2 cache hit (100%). (Run `vt run --last-details` for full details) ## `vtt print-file dist/keep.txt` +keep.txt restored from archive + ``` keep ``` + +## `vtt print-file dist/skip.txt` + +skip.txt is NOT restored — it was excluded by the negative glob + +``` +dist/skip.txt: not found +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md index fa99ee608..9d3690178 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md @@ -2,34 +2,44 @@ When a cached task re-runs (cache miss because an input changed), it writes a new archive and the previous archive file is cleaned up. After -two runs of the same task the cache directory still contains only one -\".tar.zst\" file. +two cache-missing runs of the same task the cache directory still +contains only one `.tar.zst` archive. ## `vt run build` +first run — cache miss, writes archive A + ``` $ vtt write-file dist/output.txt built ``` ## `vtt list-dir node_modules/.vite/task-cache --ext .tar.zst` +exactly one archive on disk + ``` .tar.zst ``` ## `vtt write-file src/main.ts changed` +modify an input so the next run is a cache miss + ``` ``` ## `vt run build` +second run — cache miss, writes archive B and removes A + ``` $ vtt write-file dist/output.txt built ○ cache miss: 'src/main.ts' modified, executing ``` ## `vtt list-dir node_modules/.vite/task-cache --ext .tar.zst` +still exactly one archive — A was cleaned up + ``` .tar.zst ```