diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index cf7b4103..7a615b97 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -51,8 +51,11 @@ impl ResolvedTaskOptions { let cache_config = match user_options.cache_config { UserCacheConfig::Disabled { cache: MustBe!(false) } => None, UserCacheConfig::Enabled { cache: _, enabled_cache_config } => { - let mut pass_through_envs = - enabled_cache_config.pass_through_envs.unwrap_or_default(); + let mut pass_through_envs: FxHashSet = enabled_cache_config + .pass_through_envs + .unwrap_or_default() + .into_iter() + .collect(); pass_through_envs.extend(DEFAULT_PASSTHROUGH_ENVS.iter().copied().map(Str::from)); Some(CacheConfig { env_config: EnvConfig { @@ -60,7 +63,7 @@ impl ResolvedTaskOptions { .envs .map(|e| e.into_vec().into_iter().collect()) .unwrap_or_default(), - pass_through_envs: pass_through_envs.into(), + pass_through_envs, }, }) } @@ -69,17 +72,17 @@ impl ResolvedTaskOptions { } } -#[derive(Debug, Serialize)] +#[derive(Debug, Clone, Serialize)] pub struct CacheConfig { pub env_config: EnvConfig, } -#[derive(Debug, Serialize)] +#[derive(Debug, Clone, Serialize)] pub struct EnvConfig { /// environment variable names to be fingerprinted and passed to the task, with defaults populated pub fingerprinted_envs: FxHashSet, /// environment variable names to be passed to the task without fingerprinting, with defaults populated - pub pass_through_envs: Arc<[Str]>, + pub pass_through_envs: FxHashSet, } #[derive(Debug, thiserror::Error)] diff --git a/crates/vite_task_plan/src/envs.rs b/crates/vite_task_plan/src/envs.rs index 8d537db8..5e176220 100644 --- a/crates/vite_task_plan/src/envs.rs +++ b/crates/vite_task_plan/src/envs.rs @@ -121,8 +121,12 @@ impl EnvFingerprints { Ok(Self { fingerprinted_envs, - // Save pass_through_envs names as-is, so any changes to it will invalidate the cache - pass_through_env_config: Arc::clone(&env_config.pass_through_envs), + // Save pass_through_envs names sorted for deterministic cache fingerprinting + pass_through_env_config: { + let mut sorted: Vec = env_config.pass_through_envs.iter().cloned().collect(); + sorted.sort(); + sorted.into() + }, }) } } @@ -194,9 +198,7 @@ mod tests { fn create_env_config(fingerprinted: &[&str], pass_through: &[&str]) -> EnvConfig { EnvConfig { fingerprinted_envs: fingerprinted.iter().map(|s| Str::from(*s)).collect(), - pass_through_envs: Arc::from( - pass_through.iter().map(|s| Str::from(*s)).collect::>(), - ), + pass_through_envs: pass_through.iter().map(|s| Str::from(*s)).collect(), } } diff --git a/crates/vite_task_plan/src/lib.rs b/crates/vite_task_plan/src/lib.rs index 2090cc6f..8a383f7d 100644 --- a/crates/vite_task_plan/src/lib.rs +++ b/crates/vite_task_plan/src/lib.rs @@ -16,7 +16,7 @@ pub use error::{Error, TaskPlanErrorKind}; use execution_graph::ExecutionGraph; use in_process::InProcessExecution; pub use path_env::{get_path_env, prepend_path_env}; -use plan::{plan_query_request, plan_synthetic_request}; +use plan::{ParentCacheConfig, plan_query_request, plan_synthetic_request}; use plan_request::{PlanRequest, SyntheticPlanRequest}; use rustc_hash::FxHashMap; use serde::{Serialize, ser::SerializeMap as _}; @@ -230,6 +230,7 @@ impl ExecutionPlan { synthetic_plan_request, None, cwd, + ParentCacheConfig::None, ) .with_empty_call_stack()?; ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(execution)) @@ -256,6 +257,7 @@ impl ExecutionPlan { synthetic_plan_request, Some(execution_cache_key), cwd, + ParentCacheConfig::None, ) .with_empty_call_stack()?; Ok(Self { root_node: ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(execution)) }) diff --git a/crates/vite_task_plan/src/plan.rs b/crates/vite_task_plan/src/plan.rs index 48ed3567..d173759e 100644 --- a/crates/vite_task_plan/src/plan.rs +++ b/crates/vite_task_plan/src/plan.rs @@ -18,7 +18,10 @@ use vite_shell::try_parse_as_and_list; use vite_str::Str; use vite_task_graph::{ TaskNodeIndex, - config::{ResolvedTaskOptions, user::UserTaskOptions}, + config::{ + CacheConfig, ResolvedTaskOptions, + user::{UserCacheConfig, UserTaskOptions}, + }, }; use crate::{ @@ -199,12 +202,21 @@ async fn plan_task_as_execution_node( } // Synthetic task (from CommandHandler) Some(PlanRequest::Synthetic(synthetic_plan_request)) => { + let parent_cache_config = task_node + .resolved_config + .resolved_options + .cache_config + .as_ref() + .map_or(ParentCacheConfig::Disabled, |config| { + ParentCacheConfig::Inherited(config.clone()) + }); let spawn_execution = plan_synthetic_request( context.workspace_path(), &and_item.envs, synthetic_plan_request, Some(task_execution_cache_key), &cwd, + parent_cache_config, ) .with_plan_context(&context)?; ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(spawn_execution)) @@ -298,6 +310,75 @@ async fn plan_task_as_execution_node( Ok(TaskExecution { task_display: task_node.task_display.clone(), items }) } +/// Cache configuration inherited from the parent task that contains a synthetic command. +/// +/// When a synthetic task (e.g., `vp lint` expanding to `oxlint`) appears inside a +/// user-defined task's script, the parent task's cache configuration should constrain +/// the synthetic task's caching behavior. +pub enum ParentCacheConfig { + /// No parent task (top-level synthetic command like `vp lint` run directly). + /// The synthetic task uses its own default cache configuration. + None, + + /// Parent task has caching disabled (`cache: false` or `cacheScripts` not enabled). + /// The synthetic task should also have caching disabled. + Disabled, + + /// Parent task has caching enabled with this configuration. + /// The synthetic task inherits this config, merged with its own additions. + Inherited(CacheConfig), +} + +/// Resolves the effective cache configuration for a synthetic task by combining +/// the parent task's cache config with the synthetic command's own additions. +/// +/// Synthetic tasks (e.g., `vp lint` → `oxlint`) may declare their own cache-related +/// env requirements (e.g., `pass_through_envs` for env-test). When a parent task +/// exists, its cache config takes precedence: +/// - If the parent disables caching, the synthetic task is also uncached. +/// - If the parent enables caching but the synthetic disables it, caching is disabled. +/// - If both parent and synthetic enable caching, the synthetic inherits the parent's +/// env config and merges in any additional envs the synthetic command needs. +/// - If there is no parent (top-level invocation), the synthetic task's own +/// [`UserCacheConfig`] is resolved with defaults. +fn resolve_synthetic_cache_config( + parent: ParentCacheConfig, + synthetic_cache_config: UserCacheConfig, + cwd: &Arc, +) -> Option { + match parent { + ParentCacheConfig::None => { + // Top-level: resolve from synthetic's own config + ResolvedTaskOptions::resolve( + UserTaskOptions { + cache_config: synthetic_cache_config, + cwd_relative_to_package: None, + depends_on: None, + }, + cwd, + ) + .cache_config + } + ParentCacheConfig::Disabled => Option::None, + ParentCacheConfig::Inherited(mut parent_config) => { + // Cache is enabled only if both parent and synthetic want it. + // Merge synthetic's additions into parent's config. + match synthetic_cache_config { + UserCacheConfig::Disabled { .. } => Option::None, + UserCacheConfig::Enabled { enabled_cache_config, .. } => { + if let Some(extra_envs) = enabled_cache_config.envs { + parent_config.env_config.fingerprinted_envs.extend(extra_envs.into_vec()); + } + if let Some(extra_pts) = enabled_cache_config.pass_through_envs { + parent_config.env_config.pass_through_envs.extend(extra_pts); + } + Some(parent_config) + } + } + } + } +} + #[expect(clippy::result_large_err, reason = "TaskPlanErrorKind is large for diagnostics")] pub fn plan_synthetic_request( workspace_path: &Arc, @@ -305,19 +386,15 @@ pub fn plan_synthetic_request( synthetic_plan_request: SyntheticPlanRequest, execution_cache_key: Option, cwd: &Arc, + parent_cache_config: ParentCacheConfig, ) -> Result { let SyntheticPlanRequest { program, args, cache_config, envs } = synthetic_plan_request; let program_path = which(&program, &envs, cwd).map_err(TaskPlanErrorKind::ProgramNotFound)?; - let resolved_options = ResolvedTaskOptions::resolve( - UserTaskOptions { - cache_config, - // cwd_relative_to_package and depends_on don't make sense for synthetic tasks. - cwd_relative_to_package: None, - depends_on: None, - }, - cwd, - ); + let resolved_cache_config = + resolve_synthetic_cache_config(parent_cache_config, cache_config, cwd); + let resolved_options = + ResolvedTaskOptions { cwd: Arc::clone(cwd), cache_config: resolved_cache_config }; plan_spawn_execution( workspace_path, diff --git a/crates/vite_task_plan/src/plan_request.rs b/crates/vite_task_plan/src/plan_request.rs index 33b06dea..cd85f651 100644 --- a/crates/vite_task_plan/src/plan_request.rs +++ b/crates/vite_task_plan/src/plan_request.rs @@ -18,6 +18,19 @@ pub struct ScriptCommand { pub cwd: Arc, } +impl ScriptCommand { + /// Convert this `ScriptCommand` to a `SyntheticPlanRequest` with the given `cache_config`. + #[must_use] + pub fn to_synthetic_plan_request(&self, cache_config: UserCacheConfig) -> SyntheticPlanRequest { + SyntheticPlanRequest { + program: Arc::from(OsStr::new(&self.program)), + args: self.args.clone(), + cache_config, + envs: self.envs.clone(), + } + } +} + #[derive(Debug)] pub struct PlanOptions { pub extra_args: Arc<[Str]>, diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/package.json new file mode 100644 index 00000000..17ccddc2 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/synthetic-cache-disabled", + "scripts": { + "lint": "vp lint", + "run-build-cache-false": "vp run build" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots.toml new file mode 100644 index 00000000..5184b7f4 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots.toml @@ -0,0 +1,23 @@ +[[plan]] +name = "script without cacheScripts defaults to no cache" +args = ["run", "lint"] + +[[plan]] +name = "task with cache false disables synthetic cache" +args = ["run", "lint-no-cache"] + +[[plan]] +name = "task with cache true enables synthetic cache" +args = ["run", "lint-with-cache"] + +[[plan]] +name = "task passThroughEnvs inherited by synthetic" +args = ["run", "lint-with-pass-through-envs"] + +[[plan]] +name = "parent cache false does not affect expanded query tasks" +args = ["run", "run-build-no-cache"] + +[[plan]] +name = "script cache false does not affect expanded synthetic cache" +args = ["run", "run-build-cache-false"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - parent cache false does not affect expanded query tasks.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - parent cache false does not affect expanded query tasks.snap new file mode 100644 index 00000000..60326ff8 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - parent cache false does not affect expanded query tasks.snap @@ -0,0 +1,112 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: "&plan_json" +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled +--- +{ + "root_node": { + "Expanded": [ + { + "key": [ + "/", + "run-build-no-cache" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "run-build-no-cache", + "package_path": "/" + }, + "items": [ + { + "execution_item_display": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "run-build-no-cache", + "package_path": "/" + }, + "command": "vp run build", + "and_item_index": null, + "cwd": "/" + }, + "kind": { + "Expanded": [ + { + "key": [ + "/", + "build" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "build", + "package_path": "/" + }, + "items": [ + { + "execution_item_display": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "build", + "package_path": "/" + }, + "command": "vp lint", + "and_item_index": null, + "cwd": "/" + }, + "kind": { + "Leaf": { + "Spawn": { + "cache_metadata": { + "spawn_fingerprint": { + "cwd": "", + "program_fingerprint": { + "OutsideWorkspace": { + "program_name": "oxlint" + } + }, + "args": [], + "env_fingerprints": { + "fingerprinted_envs": {}, + "pass_through_env_config": [ + "" + ] + }, + "fingerprint_ignores": null + }, + "execution_cache_key": { + "UserTask": { + "task_name": "build", + "and_item_index": 0, + "extra_args": [], + "package_path": "" + } + } + }, + "spawn_command": { + "program_path": "/node_modules/.bin/oxlint", + "args": [], + "all_envs": { + "NO_COLOR": "1", + "PATH": "/node_modules/.bin:/node_modules/.bin" + }, + "cwd": "/" + } + } + } + } + } + ] + }, + "neighbors": [] + } + ] + } + } + ] + }, + "neighbors": [] + } + ] + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - script cache false does not affect expanded synthetic cache.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - script cache false does not affect expanded synthetic cache.snap new file mode 100644 index 00000000..5fca264a --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - script cache false does not affect expanded synthetic cache.snap @@ -0,0 +1,112 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: "&plan_json" +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled +--- +{ + "root_node": { + "Expanded": [ + { + "key": [ + "/", + "run-build-cache-false" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "run-build-cache-false", + "package_path": "/" + }, + "items": [ + { + "execution_item_display": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "run-build-cache-false", + "package_path": "/" + }, + "command": "vp run build", + "and_item_index": null, + "cwd": "/" + }, + "kind": { + "Expanded": [ + { + "key": [ + "/", + "build" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "build", + "package_path": "/" + }, + "items": [ + { + "execution_item_display": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "build", + "package_path": "/" + }, + "command": "vp lint", + "and_item_index": null, + "cwd": "/" + }, + "kind": { + "Leaf": { + "Spawn": { + "cache_metadata": { + "spawn_fingerprint": { + "cwd": "", + "program_fingerprint": { + "OutsideWorkspace": { + "program_name": "oxlint" + } + }, + "args": [], + "env_fingerprints": { + "fingerprinted_envs": {}, + "pass_through_env_config": [ + "" + ] + }, + "fingerprint_ignores": null + }, + "execution_cache_key": { + "UserTask": { + "task_name": "build", + "and_item_index": 0, + "extra_args": [], + "package_path": "" + } + } + }, + "spawn_command": { + "program_path": "/node_modules/.bin/oxlint", + "args": [], + "all_envs": { + "NO_COLOR": "1", + "PATH": "/node_modules/.bin:/node_modules/.bin" + }, + "cwd": "/" + } + } + } + } + } + ] + }, + "neighbors": [] + } + ] + } + } + ] + }, + "neighbors": [] + } + ] + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - script without cacheScripts defaults to no cache.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - script without cacheScripts defaults to no cache.snap new file mode 100644 index 00000000..714efb30 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - script without cacheScripts defaults to no cache.snap @@ -0,0 +1,55 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: "&plan_json" +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled +--- +{ + "root_node": { + "Expanded": [ + { + "key": [ + "/", + "lint" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint", + "package_path": "/" + }, + "items": [ + { + "execution_item_display": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint", + "package_path": "/" + }, + "command": "vp lint", + "and_item_index": null, + "cwd": "/" + }, + "kind": { + "Leaf": { + "Spawn": { + "cache_metadata": null, + "spawn_command": { + "program_path": "/node_modules/.bin/oxlint", + "args": [], + "all_envs": { + "NO_COLOR": "1", + "PATH": "/node_modules/.bin:/node_modules/.bin" + }, + "cwd": "/" + } + } + } + } + } + ] + }, + "neighbors": [] + } + ] + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task passThroughEnvs inherited by synthetic.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task passThroughEnvs inherited by synthetic.snap new file mode 100644 index 00000000..c8ba0b5e --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task passThroughEnvs inherited by synthetic.snap @@ -0,0 +1,81 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: "&plan_json" +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled +--- +{ + "root_node": { + "Expanded": [ + { + "key": [ + "/", + "lint-with-pass-through-envs" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint-with-pass-through-envs", + "package_path": "/" + }, + "items": [ + { + "execution_item_display": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint-with-pass-through-envs", + "package_path": "/" + }, + "command": "vp lint", + "and_item_index": null, + "cwd": "/" + }, + "kind": { + "Leaf": { + "Spawn": { + "cache_metadata": { + "spawn_fingerprint": { + "cwd": "", + "program_fingerprint": { + "OutsideWorkspace": { + "program_name": "oxlint" + } + }, + "args": [], + "env_fingerprints": { + "fingerprinted_envs": {}, + "pass_through_env_config": [ + "CUSTOM_VAR", + "" + ] + }, + "fingerprint_ignores": null + }, + "execution_cache_key": { + "UserTask": { + "task_name": "lint-with-pass-through-envs", + "and_item_index": 0, + "extra_args": [], + "package_path": "" + } + } + }, + "spawn_command": { + "program_path": "/node_modules/.bin/oxlint", + "args": [], + "all_envs": { + "NO_COLOR": "1", + "PATH": "/node_modules/.bin:/node_modules/.bin" + }, + "cwd": "/" + } + } + } + } + } + ] + }, + "neighbors": [] + } + ] + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task with cache false disables synthetic cache.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task with cache false disables synthetic cache.snap new file mode 100644 index 00000000..967bf632 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task with cache false disables synthetic cache.snap @@ -0,0 +1,55 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: "&plan_json" +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled +--- +{ + "root_node": { + "Expanded": [ + { + "key": [ + "/", + "lint-no-cache" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint-no-cache", + "package_path": "/" + }, + "items": [ + { + "execution_item_display": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint-no-cache", + "package_path": "/" + }, + "command": "vp lint", + "and_item_index": null, + "cwd": "/" + }, + "kind": { + "Leaf": { + "Spawn": { + "cache_metadata": null, + "spawn_command": { + "program_path": "/node_modules/.bin/oxlint", + "args": [], + "all_envs": { + "NO_COLOR": "1", + "PATH": "/node_modules/.bin:/node_modules/.bin" + }, + "cwd": "/" + } + } + } + } + } + ] + }, + "neighbors": [] + } + ] + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task with cache true enables synthetic cache.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task with cache true enables synthetic cache.snap new file mode 100644 index 00000000..106590da --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/query - task with cache true enables synthetic cache.snap @@ -0,0 +1,80 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: "&plan_json" +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled +--- +{ + "root_node": { + "Expanded": [ + { + "key": [ + "/", + "lint-with-cache" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint-with-cache", + "package_path": "/" + }, + "items": [ + { + "execution_item_display": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint-with-cache", + "package_path": "/" + }, + "command": "vp lint", + "and_item_index": null, + "cwd": "/" + }, + "kind": { + "Leaf": { + "Spawn": { + "cache_metadata": { + "spawn_fingerprint": { + "cwd": "", + "program_fingerprint": { + "OutsideWorkspace": { + "program_name": "oxlint" + } + }, + "args": [], + "env_fingerprints": { + "fingerprinted_envs": {}, + "pass_through_env_config": [ + "" + ] + }, + "fingerprint_ignores": null + }, + "execution_cache_key": { + "UserTask": { + "task_name": "lint-with-cache", + "and_item_index": 0, + "extra_args": [], + "package_path": "" + } + } + }, + "spawn_command": { + "program_path": "/node_modules/.bin/oxlint", + "args": [], + "all_envs": { + "NO_COLOR": "1", + "PATH": "/node_modules/.bin:/node_modules/.bin" + }, + "cwd": "/" + } + } + } + } + } + ] + }, + "neighbors": [] + } + ] + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/task graph.snap new file mode 100644 index 00000000..ffe498b2 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/snapshots/task graph.snap @@ -0,0 +1,176 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: task_graph_json +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled +--- +[ + { + "key": [ + "/", + "build" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "build", + "package_path": "/" + }, + "resolved_config": { + "command": "vp lint", + "resolved_options": { + "cwd": "/", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "pass_through_envs": [ + "" + ] + } + } + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/", + "lint" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint", + "package_path": "/" + }, + "resolved_config": { + "command": "vp lint", + "resolved_options": { + "cwd": "/", + "cache_config": null + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/", + "lint-no-cache" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint-no-cache", + "package_path": "/" + }, + "resolved_config": { + "command": "vp lint", + "resolved_options": { + "cwd": "/", + "cache_config": null + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/", + "lint-with-cache" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint-with-cache", + "package_path": "/" + }, + "resolved_config": { + "command": "vp lint", + "resolved_options": { + "cwd": "/", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "pass_through_envs": [ + "" + ] + } + } + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/", + "lint-with-pass-through-envs" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "lint-with-pass-through-envs", + "package_path": "/" + }, + "resolved_config": { + "command": "vp lint", + "resolved_options": { + "cwd": "/", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "pass_through_envs": [ + "CUSTOM_VAR", + "" + ] + } + } + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/", + "run-build-cache-false" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "run-build-cache-false", + "package_path": "/" + }, + "resolved_config": { + "command": "vp run build", + "resolved_options": { + "cwd": "/", + "cache_config": null + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/", + "run-build-no-cache" + ], + "node": { + "task_display": { + "package_name": "@test/synthetic-cache-disabled", + "task_name": "run-build-no-cache", + "package_path": "/" + }, + "resolved_config": { + "command": "vp run build", + "resolved_options": { + "cwd": "/", + "cache_config": null + } + } + }, + "neighbors": [] + } +] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/vite-task.json new file mode 100644 index 00000000..754a54b7 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-cache-disabled/vite-task.json @@ -0,0 +1,24 @@ +{ + "tasks": { + "lint-no-cache": { + "command": "vp lint", + "cache": false + }, + "lint-with-cache": { + "command": "vp lint", + "cache": true + }, + "lint-with-pass-through-envs": { + "command": "vp lint", + "passThroughEnvs": ["CUSTOM_VAR"] + }, + "build": { + "command": "vp lint", + "cache": true + }, + "run-build-no-cache": { + "command": "vp run build", + "cache": false + } + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/redact.rs b/crates/vite_task_plan/tests/plan_snapshots/redact.rs index caf4742a..ad6f285a 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/redact.rs +++ b/crates/vite_task_plan/tests/plan_snapshots/redact.rs @@ -64,6 +64,10 @@ fn redact_string(s: &mut String, redactions: &[(&str, &str)]) { clippy::disallowed_types, reason = "String required by std::env::var return type and serde_json Value manipulation; Path required for CARGO_MANIFEST_DIR path manipulation" )] +#[expect( + clippy::too_many_lines, + reason = "redaction logic is sequential and reads better in one function" +)] pub fn redact_snapshot(value: &impl Serialize, workspace_root: &str) -> serde_json::Value { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); // Get the packages/tools directory path @@ -177,6 +181,12 @@ pub fn redact_snapshot(value: &impl Serialize, workspace_root: &str) -> serde_js true } }); + // Sort remaining entries for deterministic snapshots (FxHashSet has non-deterministic order) + array.sort_by(|a, b| { + let a_str = if let serde_json::Value::String(s) = a { s.as_str() } else { "" }; + let b_str = if let serde_json::Value::String(s) = b { s.as_str() } else { "" }; + a_str.cmp(b_str) + }); array.push(serde_json::Value::String("".to_string())); } });