From e9d0aa5579c43a8ce210bec143108427f391f71e Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 5 Feb 2026 09:39:06 +0800 Subject: [PATCH 01/10] feat: unify UserConfigFile and UserConfigTasks into UserRunConfig - Merge UserConfigFile and UserConfigTasks into single UserRunConfig struct - Add cacheScripts field (Option, currently unused) - Rename config file from vite.config.json to vite-task.json - Rename task-config.ts to run-config.ts - Update TypeScript definition to export RunConfig type - Update all test fixtures and snapshots Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 8 +-- .../{vite.config.json => vite-task.json} | 0 .../cache-miss-reasons/snapshots.toml | 8 +-- .../snapshots/cwd changed.snap | 2 +- .../snapshots/pass-through env added.snap | 2 +- .../snapshots/pass-through env removed.snap | 4 +- .../{vite.config.json => vite-task.json} | 0 .../{vite.config.json => vite-task.json} | 0 .../{vite.config.json => vite-task.json} | 0 .../{vite.config.json => vite-task.json} | 0 crates/vite_task_graph/run-config.ts | 50 +++++++++++++++++++ crates/vite_task_graph/src/config/mod.rs | 2 +- crates/vite_task_graph/src/config/user.rs | 35 +++++++------ crates/vite_task_graph/src/lib.rs | 8 +-- crates/vite_task_graph/src/loader.rs | 16 +++--- crates/vite_task_graph/task-config.ts | 39 --------------- .../{vite.config.json => vite-task.json} | 0 .../a/{vite.config.json => vite-task.json} | 0 .../{vite.config.json => vite-task.json} | 0 .../{vite.config.json => vite-task.json} | 0 .../{vite.config.json => vite-task.json} | 0 .../app/{vite.config.json => vite-task.json} | 0 .../core/{vite.config.json => vite-task.json} | 0 .../{vite.config.json => vite-task.json} | 0 .../{vite.config.json => vite-task.json} | 0 25 files changed, 92 insertions(+), 82 deletions(-) rename crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-disabled/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-envs/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal-exit/{vite.config.json => vite-task.json} (100%) create mode 100644 crates/vite_task_graph/run-config.ts delete mode 100644 crates/vite_task_graph/task-config.ts rename crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/packages/test-package/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/packages/a/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/another-empty/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/empty-name/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/normal-package/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/app/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/core/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/utils/{vite.config.json => vite-task.json} (100%) rename crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/{vite.config.json => vite-task.json} (100%) diff --git a/CLAUDE.md b/CLAUDE.md index 03ebcd7d9..06b3de101 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,7 +39,7 @@ Test fixtures and snapshots: ## CLI Usage ```bash -# Run a task defined in vite.config.json +# Run a task defined in vite-task.json vite run # run task in current package vite run # # run task in specific package vite run -r # run task in all packages (recursive) @@ -67,7 +67,7 @@ vite lint [args...] # run oxlint ## Task Configuration -Tasks are defined in `vite.config.json`: +Tasks are defined in `vite-task.json`: ```json { @@ -83,7 +83,7 @@ Tasks are defined in `vite.config.json`: ## Task Dependencies -1. **Explicit**: Defined via `dependsOn` in `vite.config.json` (skip with `--ignore-depends-on`) +1. **Explicit**: Defined via `dependsOn` in `vite-task.json` (skip with `--ignore-depends-on`) 2. **Topological**: Based on package.json dependencies - With `-r/--recursive`: runs task across all packages in dependency order - With `-t/--transitive`: runs task in current package and its dependencies @@ -116,4 +116,4 @@ These patterns are enforced by `.clippy.toml`: ## Quick Reference - **Task Format**: `package#task` (e.g., `app#build`, `@test/utils#lint`) -- **Config File**: `vite.config.json` in each package +- **Config File**: `vite-task.json` in each package diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-disabled/vite.config.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-disabled/vite-task.json similarity index 100% rename from crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-disabled/vite.config.json rename to crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-disabled/vite-task.json diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots.toml index 1a2e321aa..93bd65931 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots.toml +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots.toml @@ -25,16 +25,16 @@ steps = [ name = "pass-through env added" steps = [ "vite run test # cache miss", - "json-edit vite.config.json \"_.tasks.test.passThroughEnvs = ['MY_PASSTHROUGH']\" # add pass-through env", + "json-edit vite-task.json \"_.tasks.test.passThroughEnvs = ['MY_PASSTHROUGH']\" # add pass-through env", "vite run test # cache miss: pass-through env added", ] [[e2e]] name = "pass-through env removed" steps = [ - "json-edit vite.config.json \"_.tasks.test.passThroughEnvs = ['MY_PASSTHROUGH']\" # setup", + "json-edit vite-task.json \"_.tasks.test.passThroughEnvs = ['MY_PASSTHROUGH']\" # setup", "vite run test # cache miss", - "json-edit vite.config.json \"delete _.tasks.test.passThroughEnvs\" # remove pass-through env", + "json-edit vite-task.json \"delete _.tasks.test.passThroughEnvs\" # remove pass-through env", "vite run test # cache miss: pass-through env removed", ] @@ -44,7 +44,7 @@ steps = [ "vite run test # cache miss", "mkdir -p subfolder", "cp test.txt subfolder/test.txt", - "json-edit vite.config.json \"_.tasks.test.cwd = 'subfolder'\" # change cwd", + "json-edit vite-task.json \"_.tasks.test.cwd = 'subfolder'\" # change cwd", "vite run test # cache miss: cwd changed", ] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/cwd changed.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/cwd changed.snap index fe831b276..02264c044 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/cwd changed.snap +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/cwd changed.snap @@ -26,7 +26,7 @@ Task Details: > cp test.txt subfolder/test.txt -> json-edit vite.config.json "_.tasks.test.cwd = 'subfolder'" # change cwd +> json-edit vite-task.json "_.tasks.test.cwd = 'subfolder'" # change cwd > vite run test # cache miss: cwd changed ~/subfolder$ print-file test.txt ✗ cache miss: working directory changed, executing diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/pass-through env added.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/pass-through env added.snap index 805fc64c7..fa63d424c 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/pass-through env added.snap +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/pass-through env added.snap @@ -22,7 +22,7 @@ Task Details: → Cache miss: no previous cache entry found ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -> json-edit vite.config.json "_.tasks.test.passThroughEnvs = ['MY_PASSTHROUGH']" # add pass-through env +> json-edit vite-task.json "_.tasks.test.passThroughEnvs = ['MY_PASSTHROUGH']" # add pass-through env > vite run test # cache miss: pass-through env added $ print-file test.txt ✗ cache miss: pass-through env config changed, executing diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/pass-through env removed.snap b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/pass-through env removed.snap index b5b4596f2..7de44e70e 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/pass-through env removed.snap +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/snapshots/pass-through env removed.snap @@ -4,7 +4,7 @@ assertion_line: 203 expression: e2e_outputs input_file: crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons --- -> json-edit vite.config.json "_.tasks.test.passThroughEnvs = ['MY_PASSTHROUGH']" # setup +> json-edit vite-task.json "_.tasks.test.passThroughEnvs = ['MY_PASSTHROUGH']" # setup > vite run test # cache miss $ print-file test.txt @@ -24,7 +24,7 @@ Task Details: → Cache miss: no previous cache entry found ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -> json-edit vite.config.json "delete _.tasks.test.passThroughEnvs" # remove pass-through env +> json-edit vite-task.json "delete _.tasks.test.passThroughEnvs" # remove pass-through env > vite run test # cache miss: pass-through env removed $ print-file test.txt ✗ cache miss: pass-through env config changed, executing diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/vite.config.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/vite-task.json similarity index 100% rename from crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/vite.config.json rename to crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/vite-task.json diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite.config.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite-task.json similarity index 100% rename from crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite.config.json rename to crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite-task.json diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-envs/vite.config.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-envs/vite-task.json similarity index 100% rename from crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-envs/vite.config.json rename to crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-envs/vite-task.json diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal-exit/vite.config.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal-exit/vite-task.json similarity index 100% rename from crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal-exit/vite.config.json rename to crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal-exit/vite-task.json diff --git a/crates/vite_task_graph/run-config.ts b/crates/vite_task_graph/run-config.ts new file mode 100644 index 000000000..446cc6d7e --- /dev/null +++ b/crates/vite_task_graph/run-config.ts @@ -0,0 +1,50 @@ +export type Task = + & { + /** + * The command to run for the task. + * + * If omitted, the script from `package.json` with the same name will be used + */ + command?: string; + /** + * The working directory for the task, relative to the package root (not workspace root). + */ + cwd?: string; + /** + * Dependencies of this task. Use `package-name#task-name` to refer to tasks in other packages. + */ + dependsOn?: Array; + } + & ( + | { + /** + * Whether to cache the task + */ + cache?: true; + /** + * Environment variable names to be fingerprinted and passed to the task. + */ + envs?: Array; + /** + * Environment variable names to be passed to the task without fingerprinting. + */ + passThroughEnvs?: Array; + } + | { + /** + * Whether to cache the task + */ + cache: false; + } + ); + +export type RunConfig = { + /** + * Cache scripts from package.json (currently unused) + */ + cacheScripts?: boolean; + /** + * Task definitions + */ + tasks?: { [key in string]?: Task }; +}; diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index 93fac6270..2639c43b5 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -4,7 +4,7 @@ use std::{collections::HashSet, sync::Arc}; use monostate::MustBe; use serde::Serialize; -pub use user::{UserCacheConfig, UserConfigFile, UserTaskConfig}; +pub use user::{UserCacheConfig, UserRunConfig, UserTaskConfig}; use vite_path::AbsolutePath; use vite_str::Str; diff --git a/crates/vite_task_graph/src/config/user.rs b/crates/vite_task_graph/src/config/user.rs index 394453fa1..5fd95360a 100644 --- a/crates/vite_task_graph/src/config/user.rs +++ b/crates/vite_task_graph/src/config/user.rs @@ -92,22 +92,21 @@ pub struct UserTaskConfig { pub options: UserTaskOptions, } -/// User configuration file structure for `vite.config.*` -#[derive(Debug, Deserialize)] -pub struct UserConfigFile { - pub tasks: UserConfigTasks, -} - -/// Type of the `tasks` field in `vite.config.*` +/// User configuration file structure for `vite-task.json` #[derive(Debug, Default, Deserialize)] -#[cfg_attr(test, derive(TS))] -#[serde(transparent)] -#[cfg_attr(test, ts(rename = "Tasks"))] -pub struct UserConfigTasks(pub HashMap); +#[cfg_attr(test, derive(TS), ts(optional_fields, rename = "RunConfig"))] +#[serde(rename_all = "camelCase")] +pub struct UserRunConfig { + /// Cache scripts from package.json (currently unused) + pub cache_scripts: Option, + + /// Task definitions + pub tasks: Option>, +} -impl UserConfigTasks { - /// TypeScript type definitions for user task configuration. - pub const TS_TYPE: &str = include_str!("../../task-config.ts"); +impl UserRunConfig { + /// TypeScript type definitions for user run configuration. + pub const TS_TYPE: &str = include_str!("../../run-config.ts"); /// Generates TypeScript type definitions for user task configuration. #[cfg(test)] @@ -155,7 +154,7 @@ impl UserConfigTasks { .expect("oxfmt not found in packages/tools"); let mut child = Command::new(oxfmt_path) - .arg("--stdin-filepath=task-config.ts") + .arg("--stdin-filepath=run-config.ts") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() @@ -179,13 +178,13 @@ impl UserConfigTasks { mod ts_tests { use std::{env, path::PathBuf}; - use super::UserConfigTasks; + use super::UserRunConfig; #[test] fn typescript_generation() { let file_path = - PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("task-config.ts"); - let ts = UserConfigTasks::generate_ts_definition().replace("\r", ""); + PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("run-config.ts"); + let ts = UserRunConfig::generate_ts_definition().replace("\r", ""); if env::var("VT_UPDATE_TS_TYPES").unwrap_or_default() == "1" { std::fs::write(&file_path, ts).unwrap(); diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 5190a571e..250f5da1c 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -11,7 +11,7 @@ use std::{ sync::Arc, }; -use config::{ResolvedTaskConfig, UserConfigFile}; +use config::{ResolvedTaskConfig, UserRunConfig}; use package_graph::IndexedPackageGraph; use petgraph::{ graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}, @@ -211,13 +211,13 @@ impl IndexedTaskGraph { .map(|(name, value)| (name.as_str(), value.as_str())) .collect(); - // Load vite.config.* for the package - let user_config: UserConfigFile = + // Load vite-task.json for the package + let user_config: UserRunConfig = config_loader.load_user_config_file(&package_dir).await.map_err(|error| { TaskGraphLoadError::ConfigLoadError { error, package_path: package_dir.clone() } })?; - for (task_name, task_user_config) in user_config.tasks.0 { + for (task_name, task_user_config) in user_config.tasks.unwrap_or_default() { // For each task defined in vite.config.*, look up the corresponding package.json script (if any) let package_json_script = package_json_scripts.remove(task_name.as_str()); diff --git a/crates/vite_task_graph/src/loader.rs b/crates/vite_task_graph/src/loader.rs index 0b699bb79..11eb569e7 100644 --- a/crates/vite_task_graph/src/loader.rs +++ b/crates/vite_task_graph/src/loader.rs @@ -2,18 +2,18 @@ use std::fmt::Debug; use vite_path::AbsolutePath; -use crate::config::UserConfigFile; +use crate::config::UserRunConfig; -/// Loader trait for loading user configuration files (vite.config.*). +/// Loader trait for loading user configuration files (vite-task.json). #[async_trait::async_trait(?Send)] pub trait UserConfigLoader: Debug + Send + Sync { async fn load_user_config_file( &self, package_path: &AbsolutePath, - ) -> anyhow::Result; + ) -> anyhow::Result; } -/// A `UserConfigLoader` implementation that only loads `vite.config.json`. +/// A `UserConfigLoader` implementation that only loads `vite-task.json`. /// /// This is mainly for examples and testing as it does not require Node.js environment. #[derive(Default, Debug)] @@ -24,16 +24,16 @@ impl UserConfigLoader for JsonUserConfigLoader { async fn load_user_config_file( &self, package_path: &AbsolutePath, - ) -> anyhow::Result { - let config_path = package_path.join("vite.config.json"); + ) -> anyhow::Result { + let config_path = package_path.join("vite-task.json"); let config_content = match tokio::fs::read_to_string(&config_path).await { Ok(content) => content, Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - return Ok(UserConfigFile { tasks: Default::default() }); + return Ok(UserRunConfig::default()); } Err(err) => return Err(err.into()), }; - let user_config: UserConfigFile = serde_json::from_str(&config_content)?; + let user_config: UserRunConfig = serde_json::from_str(&config_content)?; Ok(user_config) } } diff --git a/crates/vite_task_graph/task-config.ts b/crates/vite_task_graph/task-config.ts deleted file mode 100644 index 0c3db0443..000000000 --- a/crates/vite_task_graph/task-config.ts +++ /dev/null @@ -1,39 +0,0 @@ -export type Task = { - /** - * The command to run for the task. - * - * If omitted, the script from `package.json` with the same name will be used - */ - command?: string; - /** - * The working directory for the task, relative to the package root (not workspace root). - */ - cwd?: string; - /** - * Dependencies of this task. Use `package-name#task-name` to refer to tasks in other packages. - */ - dependsOn?: Array; -} & ( - | { - /** - * Whether to cache the task - */ - cache?: true; - /** - * Environment variable names to be fingerprinted and passed to the task. - */ - envs?: Array; - /** - * Environment variable names to be passed to the task without fingerprinting. - */ - passThroughEnvs?: Array; - } - | { - /** - * Whether to cache the task - */ - cache: false; - } -); - -export type Tasks = { [key in string]?: Task }; diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/packages/test-package/vite.config.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/packages/test-package/vite-task.json similarity index 100% rename from crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/packages/test-package/vite.config.json rename to crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/packages/test-package/vite-task.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/packages/a/vite-task.json similarity index 100% rename from crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json rename to crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/packages/a/vite-task.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/another-empty/vite.config.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/another-empty/vite-task.json similarity index 100% rename from crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/another-empty/vite.config.json rename to crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/another-empty/vite-task.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/empty-name/vite.config.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/empty-name/vite-task.json similarity index 100% rename from crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/empty-name/vite.config.json rename to crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/empty-name/vite-task.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/normal-package/vite.config.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/normal-package/vite-task.json similarity index 100% rename from crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/normal-package/vite.config.json rename to crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/packages/normal-package/vite-task.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/app/vite.config.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/app/vite-task.json similarity index 100% rename from crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/app/vite.config.json rename to crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/app/vite-task.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/core/vite.config.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/core/vite-task.json similarity index 100% rename from crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/core/vite.config.json rename to crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/core/vite-task.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/utils/vite.config.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/utils/vite-task.json similarity index 100% rename from crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/utils/vite.config.json rename to crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/packages/utils/vite-task.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/vite.config.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/vite-task.json similarity index 100% rename from crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/vite.config.json rename to crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/vite-task.json From 3cc277bc244d1d5390a23cd8396c8b2f3530c0c1 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 5 Feb 2026 09:42:14 +0800 Subject: [PATCH 02/10] update doc comments --- crates/vite_task_graph/run-config.ts | 2 +- crates/vite_task_graph/src/config/user.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/vite_task_graph/run-config.ts b/crates/vite_task_graph/run-config.ts index 446cc6d7e..f8df5f600 100644 --- a/crates/vite_task_graph/run-config.ts +++ b/crates/vite_task_graph/run-config.ts @@ -40,7 +40,7 @@ export type Task = export type RunConfig = { /** - * Cache scripts from package.json (currently unused) + * Enable cache for all scripts from package.json */ cacheScripts?: boolean; /** diff --git a/crates/vite_task_graph/src/config/user.rs b/crates/vite_task_graph/src/config/user.rs index 5fd95360a..aa51f2e8b 100644 --- a/crates/vite_task_graph/src/config/user.rs +++ b/crates/vite_task_graph/src/config/user.rs @@ -92,12 +92,12 @@ pub struct UserTaskConfig { pub options: UserTaskOptions, } -/// User configuration file structure for `vite-task.json` +/// User configuration structure for `run` field in `vite.config.*` #[derive(Debug, Default, Deserialize)] #[cfg_attr(test, derive(TS), ts(optional_fields, rename = "RunConfig"))] #[serde(rename_all = "camelCase")] pub struct UserRunConfig { - /// Cache scripts from package.json (currently unused) + /// Enable cache for all scripts from package.json pub cache_scripts: Option, /// Task definitions From 7cfaaa6af93d0a6662f97c8e955708cda92fcc31 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 5 Feb 2026 09:46:19 +0800 Subject: [PATCH 03/10] refactor: return Option from load_user_config_file Change UserConfigLoader::load_user_config_file to return anyhow::Result> instead of anyhow::Result. Returns None when config file doesn't exist. Co-Authored-By: Claude Opus 4.5 --- crates/vite_task_graph/src/lib.rs | 6 ++++-- crates/vite_task_graph/src/loader.rs | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 250f5da1c..9a1ad9adc 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -212,12 +212,14 @@ impl IndexedTaskGraph { .collect(); // Load vite-task.json for the package - let user_config: UserRunConfig = + let user_config: Option = config_loader.load_user_config_file(&package_dir).await.map_err(|error| { TaskGraphLoadError::ConfigLoadError { error, package_path: package_dir.clone() } })?; - for (task_name, task_user_config) in user_config.tasks.unwrap_or_default() { + for (task_name, task_user_config) in + user_config.and_then(|c| c.tasks).unwrap_or_default() + { // For each task defined in vite.config.*, look up the corresponding package.json script (if any) let package_json_script = package_json_scripts.remove(task_name.as_str()); diff --git a/crates/vite_task_graph/src/loader.rs b/crates/vite_task_graph/src/loader.rs index 11eb569e7..8a192fdcd 100644 --- a/crates/vite_task_graph/src/loader.rs +++ b/crates/vite_task_graph/src/loader.rs @@ -10,7 +10,7 @@ pub trait UserConfigLoader: Debug + Send + Sync { async fn load_user_config_file( &self, package_path: &AbsolutePath, - ) -> anyhow::Result; + ) -> anyhow::Result>; } /// A `UserConfigLoader` implementation that only loads `vite-task.json`. @@ -24,16 +24,16 @@ impl UserConfigLoader for JsonUserConfigLoader { async fn load_user_config_file( &self, package_path: &AbsolutePath, - ) -> anyhow::Result { + ) -> anyhow::Result> { let config_path = package_path.join("vite-task.json"); let config_content = match tokio::fs::read_to_string(&config_path).await { Ok(content) => content, Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - return Ok(UserRunConfig::default()); + return Ok(None); } Err(err) => return Err(err.into()), }; let user_config: UserRunConfig = serde_json::from_str(&config_content)?; - Ok(user_config) + Ok(Some(user_config)) } } From f11f7a1604a44bab4eab73a577e899df3baafc63 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 5 Feb 2026 09:47:20 +0800 Subject: [PATCH 04/10] update --- crates/vite_task_graph/src/lib.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 9a1ad9adc..36e0a8a23 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -211,15 +211,17 @@ impl IndexedTaskGraph { .map(|(name, value)| (name.as_str(), value.as_str())) .collect(); - // Load vite-task.json for the package - let user_config: Option = - config_loader.load_user_config_file(&package_dir).await.map_err(|error| { - TaskGraphLoadError::ConfigLoadError { error, package_path: package_dir.clone() } - })?; - - for (task_name, task_user_config) in - user_config.and_then(|c| c.tasks).unwrap_or_default() - { + // Load user config for the package + let user_config: UserRunConfig = config_loader + .load_user_config_file(&package_dir) + .await + .map_err(|error| TaskGraphLoadError::ConfigLoadError { + error, + package_path: package_dir.clone(), + })? + .unwrap_or_default(); + + for (task_name, task_user_config) in user_config.tasks.unwrap_or_default() { // For each task defined in vite.config.*, look up the corresponding package.json script (if any) let package_json_script = package_json_scripts.remove(task_name.as_str()); From e1feda508157310489d82a98039876b71dd3b96e Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 5 Feb 2026 10:24:59 +0800 Subject: [PATCH 05/10] feat: implement cacheScripts option for workspace-level script caching - Add `cacheScripts` field to enable cache for all package.json scripts - Validate that cacheScripts can only be set in workspace root config - Add CacheScriptsInNonRootPackage error for invalid usage - Refactor load() into two passes to extract and validate cacheScripts - Add test fixtures for cacheScripts behavior - Add cacheScripts: true to existing fixtures to maintain behavior Co-Authored-By: Claude Opus 4.5 --- .../associate-existing-cache/vite-task.json | 3 + .../builtin-different-cwd/vite-task.json | 3 + .../builtin-non-zero-exit/vite-task.json | 3 + .../fixtures/cache-disabled/vite-task.json | 1 + .../cache-miss-command-change/vite-task.json | 3 + .../cache-miss-reasons/vite-task.json | 1 + .../fixtures/colon-in-name/vite-task.json | 3 + .../fixtures/e2e-env-test/vite-task.json | 3 + .../fixtures/e2e-lint-cache/vite-task.json | 3 + .../error_cycle_dependency/vite-task.json | 1 + .../fixtures/exit-codes/vite-task.json | 3 + .../vite-task.json | 3 + .../individual-cache-for-envs/vite-task.json | 1 + .../fixtures/lint-dot-git/vite-task.json | 3 + .../vite-task.json | 3 + .../same-name-as-builtin/vite-task.json | 3 + .../shared-caching-inputs/vite-task.json | 3 + .../fixtures/signal-exit/vite-task.json | 1 + .../fixtures/stdin-passthrough/vite-task.json | 3 + .../task-no-trailing-newline/vite-task.json | 3 + .../fixtures/vite-task-smoke/vite-task.json | 3 + crates/vite_task_graph/run-config.ts | 5 +- crates/vite_task_graph/src/config/mod.rs | 20 ++++- crates/vite_task_graph/src/config/user.rs | 5 +- crates/vite_task_graph/src/lib.rs | 51 ++++++++--- .../fixtures/additional-envs/vite-task.json | 3 + .../fixtures/cache-keys/vite-task.json | 3 + .../cache-scripts-default/package.json | 7 ++ .../snapshots/task graph.snap | 49 +++++++++++ .../cache-scripts-enabled/package.json | 7 ++ .../snapshots/task graph.snap | 63 ++++++++++++++ .../cache-scripts-enabled/vite-task.json | 3 + .../cache-scripts-task-override/package.json | 7 ++ .../snapshots/task graph.snap | 84 +++++++++++++++++++ .../vite-task.json | 8 ++ .../fixtures/cache-sharing/vite-task.json | 3 + .../comprehensive-task-graph/vite-task.json | 3 + .../fixtures/conflict-test/vite-task.json | 3 + .../vite-task.json | 3 + .../empty-package-test/vite-task.json | 3 + .../explicit-deps-workspace/vite-task.json | 3 + .../fingerprint-ignore-test/vite-task.json | 1 + .../fixtures/nested-tasks/vite-task.json | 3 + .../vite-task.json | 3 + .../vite-task.json | 3 + .../fixtures/shell-fallback/vite-task.json | 3 + .../synthetic-in-subpackage/vite-task.json | 3 + 47 files changed, 385 insertions(+), 17 deletions(-) create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate-existing-cache/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin-different-cwd/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin-non-zero-exit/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-command-change/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon-in-name/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/e2e-env-test/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/e2e-lint-cache/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit-codes/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-adt-args/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/lint-dot-git/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/replay-logs-chronological-order/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/same-name-as-builtin/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared-caching-inputs/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/stdin-passthrough/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/task-no-trailing-newline/vite-task.json create mode 100644 crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite-task-smoke/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/package.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/snapshots/task graph.snap create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/package.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/snapshots/task graph.snap create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/package.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/task graph.snap create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-task-graph/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-tasks/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm-workspace-packages-optional/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topological-workspace/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/vite-task.json diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate-existing-cache/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate-existing-cache/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate-existing-cache/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin-different-cwd/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin-different-cwd/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin-different-cwd/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin-non-zero-exit/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin-non-zero-exit/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin-non-zero-exit/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-disabled/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-disabled/vite-task.json index 0de4fd889..e0b5cafa9 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-disabled/vite-task.json +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-disabled/vite-task.json @@ -1,4 +1,5 @@ { + "cacheScripts": true, "tasks": { "no-cache-task": { "command": "print-file test.txt", diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-command-change/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-command-change/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-command-change/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/vite-task.json index 73eb02696..81a1babf4 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/vite-task.json +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache-miss-reasons/vite-task.json @@ -1,4 +1,5 @@ { + "cacheScripts": true, "tasks": { "test": { "command": "print-file test.txt", diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon-in-name/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon-in-name/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon-in-name/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/e2e-env-test/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/e2e-env-test/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/e2e-env-test/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/e2e-lint-cache/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/e2e-lint-cache/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/e2e-lint-cache/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite-task.json index 82baa8300..46b744586 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite-task.json +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite-task.json @@ -1,4 +1,5 @@ { + "cacheScripts": true, "tasks": { "task-a": { "command": "echo a", diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit-codes/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit-codes/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit-codes/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-adt-args/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-adt-args/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-adt-args/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-envs/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-envs/vite-task.json index 8ef0caca1..419def71b 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-envs/vite-task.json +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual-cache-for-envs/vite-task.json @@ -1,4 +1,5 @@ { + "cacheScripts": true, "tasks": { "hello": { "command": "print-env FOO", diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/lint-dot-git/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/lint-dot-git/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/lint-dot-git/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/replay-logs-chronological-order/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/replay-logs-chronological-order/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/replay-logs-chronological-order/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/same-name-as-builtin/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/same-name-as-builtin/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/same-name-as-builtin/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared-caching-inputs/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared-caching-inputs/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared-caching-inputs/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal-exit/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal-exit/vite-task.json index 6407f2fbd..8c7ba7357 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal-exit/vite-task.json +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal-exit/vite-task.json @@ -1,4 +1,5 @@ { + "cacheScripts": true, "tasks": { "abort": { "command": "node -e \"process.kill(process.pid, 6)\"" diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/stdin-passthrough/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/stdin-passthrough/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/stdin-passthrough/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task-no-trailing-newline/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task-no-trailing-newline/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task-no-trailing-newline/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite-task-smoke/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite-task-smoke/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite-task-smoke/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_graph/run-config.ts b/crates/vite_task_graph/run-config.ts index f8df5f600..68787863d 100644 --- a/crates/vite_task_graph/run-config.ts +++ b/crates/vite_task_graph/run-config.ts @@ -40,7 +40,10 @@ export type Task = export type RunConfig = { /** - * Enable cache for all scripts from package.json + * Enable cache for all scripts from package.json. + * + * This option can only be set in the workspace root's config file. + * Setting it in a package's config will result in an error. */ cacheScripts?: boolean; /** diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index 2639c43b5..28157d088 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -4,7 +4,7 @@ use std::{collections::HashSet, sync::Arc}; use monostate::MustBe; use serde::Serialize; -pub use user::{UserCacheConfig, UserRunConfig, UserTaskConfig}; +pub use user::{EnabledCacheConfig, UserCacheConfig, UserRunConfig, UserTaskConfig}; use vite_path::AbsolutePath; use vite_str::Str; @@ -91,14 +91,28 @@ pub enum ResolveTaskConfigError { } impl ResolvedTaskConfig { - /// Resolve from package.json script only + /// Resolve from package.json script only (no vite-task.json config for this task) + /// + /// The `cache_scripts` parameter determines whether caching is enabled for the script. + /// When `true`, caching is enabled with default settings. + /// When `false`, caching is disabled. pub fn resolve_package_json_script( package_dir: &Arc, package_json_script: &str, + cache_scripts: bool, ) -> Self { + let cache_config = if cache_scripts { + UserCacheConfig::Enabled { + cache: None, + enabled_cache_config: EnabledCacheConfig { envs: None, pass_through_envs: None }, + } + } else { + UserCacheConfig::Disabled { cache: MustBe!(false) } + }; + let options = UserTaskOptions { cache_config, ..Default::default() }; Self { command: package_json_script.into(), - resolved_options: ResolvedTaskOptions::resolve(UserTaskOptions::default(), package_dir), + resolved_options: ResolvedTaskOptions::resolve(options, package_dir), } } diff --git a/crates/vite_task_graph/src/config/user.rs b/crates/vite_task_graph/src/config/user.rs index aa51f2e8b..d5292bda5 100644 --- a/crates/vite_task_graph/src/config/user.rs +++ b/crates/vite_task_graph/src/config/user.rs @@ -97,7 +97,10 @@ pub struct UserTaskConfig { #[cfg_attr(test, derive(TS), ts(optional_fields, rename = "RunConfig"))] #[serde(rename_all = "camelCase")] pub struct UserRunConfig { - /// Enable cache for all scripts from package.json + /// Enable cache for all scripts from package.json. + /// + /// This option can only be set in the workspace root's config file. + /// Setting it in a package's config will result in an error. pub cache_scripts: Option, /// Task definitions diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 36e0a8a23..1049a3cf9 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -112,6 +112,11 @@ pub enum TaskGraphLoadError { #[source] error: SpecifierLookupError, }, + + #[error( + "`cacheScripts` can only be set in the workspace root config, but found in {package_path:?}" + )] + CacheScriptsInNonRootPackage { package_path: Arc }, } /// Error when looking up a task by its specifier. @@ -198,10 +203,39 @@ impl IndexedTaskGraph { let mut node_indices_by_task_id: HashMap = HashMap::with_capacity(task_graph.node_count()); - // Load task nodes into `task_graph` + // First pass: load all configs, extract cacheScripts from root, validate + let mut cache_scripts = false; // Default: disabled + let mut package_configs: Vec<(PackageNodeIndex, Arc, Option)> = + Vec::with_capacity(package_graph.node_count()); + for package_index in package_graph.node_indices() { let package = &package_graph[package_index]; let package_dir: Arc = workspace_root.path.join(&package.path).into(); + let is_workspace_root = package.path.as_str().is_empty(); + + let user_config = + config_loader.load_user_config_file(&package_dir).await.map_err(|error| { + TaskGraphLoadError::ConfigLoadError { error, package_path: package_dir.clone() } + })?; + + if let Some(ref config) = user_config { + if config.cache_scripts.is_some() { + if is_workspace_root { + cache_scripts = config.cache_scripts.unwrap_or(false); + } else { + return Err(TaskGraphLoadError::CacheScriptsInNonRootPackage { + package_path: package_dir.clone(), + }); + } + } + } + + package_configs.push((package_index, package_dir, user_config)); + } + + // Second pass: create task nodes using cache_scripts value + for (package_index, package_dir, user_config) in package_configs { + let package = &package_graph[package_index]; // Collect package.json scripts into a mutable map for draining lookup. let mut package_json_scripts: HashMap<&str, &str> = package @@ -211,17 +245,9 @@ impl IndexedTaskGraph { .map(|(name, value)| (name.as_str(), value.as_str())) .collect(); - // Load user config for the package - let user_config: UserRunConfig = config_loader - .load_user_config_file(&package_dir) - .await - .map_err(|error| TaskGraphLoadError::ConfigLoadError { - error, - package_path: package_dir.clone(), - })? - .unwrap_or_default(); - - for (task_name, task_user_config) in user_config.tasks.unwrap_or_default() { + for (task_name, task_user_config) in + user_config.and_then(|c| c.tasks).unwrap_or_default() + { // For each task defined in vite.config.*, look up the corresponding package.json script (if any) let package_json_script = package_json_scripts.remove(task_name.as_str()); @@ -264,6 +290,7 @@ impl IndexedTaskGraph { let resolved_config = ResolvedTaskConfig::resolve_package_json_script( &package_dir, package_json_script, + cache_scripts, ); let node_index = task_graph.add_node(TaskNode { task_display: TaskDisplay { diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional-envs/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-keys/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/package.json new file mode 100644 index 000000000..6bfb25ccf --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/cache-scripts-default", + "scripts": { + "build": "echo building", + "test": "echo testing" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/snapshots/task graph.snap new file mode 100644 index 000000000..b20ad283c --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default/snapshots/task graph.snap @@ -0,0 +1,49 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: task_graph_json +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-default +--- +[ + { + "key": [ + "/", + "build" + ], + "node": { + "task_display": { + "package_name": "@test/cache-scripts-default", + "task_name": "build", + "package_path": "/" + }, + "resolved_config": { + "command": "echo building", + "resolved_options": { + "cwd": "/", + "cache_config": null + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/", + "test" + ], + "node": { + "task_display": { + "package_name": "@test/cache-scripts-default", + "task_name": "test", + "package_path": "/" + }, + "resolved_config": { + "command": "echo testing", + "resolved_options": { + "cwd": "/", + "cache_config": null + } + } + }, + "neighbors": [] + } +] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/package.json new file mode 100644 index 000000000..ee76392f1 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/cache-scripts-enabled", + "scripts": { + "build": "echo building", + "test": "echo testing" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/snapshots/task graph.snap new file mode 100644 index 000000000..457d41935 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/snapshots/task graph.snap @@ -0,0 +1,63 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: task_graph_json +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled +--- +[ + { + "key": [ + "/", + "build" + ], + "node": { + "task_display": { + "package_name": "@test/cache-scripts-enabled", + "task_name": "build", + "package_path": "/" + }, + "resolved_config": { + "command": "echo building", + "resolved_options": { + "cwd": "/", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "pass_through_envs": [ + "" + ] + } + } + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/", + "test" + ], + "node": { + "task_display": { + "package_name": "@test/cache-scripts-enabled", + "task_name": "test", + "package_path": "/" + }, + "resolved_config": { + "command": "echo testing", + "resolved_options": { + "cwd": "/", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "pass_through_envs": [ + "" + ] + } + } + } + } + }, + "neighbors": [] + } +] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-enabled/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/package.json new file mode 100644 index 000000000..2990ff19d --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/cache-scripts-task-override", + "scripts": { + "build": "echo building", + "test": "echo testing" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/task graph.snap new file mode 100644 index 000000000..fcf03317b --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/snapshots/task graph.snap @@ -0,0 +1,84 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: task_graph_json +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override +--- +[ + { + "key": [ + "/", + "build" + ], + "node": { + "task_display": { + "package_name": "@test/cache-scripts-task-override", + "task_name": "build", + "package_path": "/" + }, + "resolved_config": { + "command": "echo building", + "resolved_options": { + "cwd": "/", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "pass_through_envs": [ + "" + ] + } + } + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/", + "deploy" + ], + "node": { + "task_display": { + "package_name": "@test/cache-scripts-task-override", + "task_name": "deploy", + "package_path": "/" + }, + "resolved_config": { + "command": "echo deploying", + "resolved_options": { + "cwd": "/", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "pass_through_envs": [ + "" + ] + } + } + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/", + "test" + ], + "node": { + "task_display": { + "package_name": "@test/cache-scripts-task-override", + "task_name": "test", + "package_path": "/" + }, + "resolved_config": { + "command": "echo testing", + "resolved_options": { + "cwd": "/", + "cache_config": null + } + } + }, + "neighbors": [] + } +] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/vite-task.json new file mode 100644 index 000000000..5c71428c9 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-task-override/vite-task.json @@ -0,0 +1,8 @@ +{ + "tasks": { + "build": {}, + "deploy": { + "command": "echo deploying" + } + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-sharing/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-task-graph/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-task-graph/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive-task-graph/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict-test/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency-both-topo-and-explicit/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty-package-test/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit-deps-workspace/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/vite-task.json index effd7a428..f3583872e 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/vite-task.json +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/fingerprint-ignore-test/vite-task.json @@ -1,4 +1,5 @@ { + "cacheScripts": true, "tasks": { "create-files": { "command": "mkdir -p node_modules/pkg-a && echo '{\"name\":\"pkg-a\"}' > node_modules/pkg-a/package.json && echo 'content' > node_modules/pkg-a/index.js && mkdir -p dist && echo 'output' > dist/bundle.js", diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-tasks/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-tasks/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested-tasks/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm-workspace-packages-optional/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm-workspace-packages-optional/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm-workspace-packages-optional/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topological-workspace/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topological-workspace/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive-topological-workspace/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell-fallback/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic-in-subpackage/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} From 3f22e5c639591cb770b36b56b0fd4514aa56b7a5 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 5 Feb 2026 10:28:23 +0800 Subject: [PATCH 06/10] test: add snapshot test for cacheScripts error in non-root package - Add cache-scripts-error-non-root fixture to verify error message - Update plan snapshot test runner to handle task graph load errors Co-Authored-By: Claude Opus 4.5 --- .../cache-scripts-error-non-root/package.json | 6 ++++++ .../packages/pkg-a/package.json | 6 ++++++ .../packages/pkg-a/vite-task.json | 3 +++ .../pnpm-workspace.yaml | 2 ++ .../snapshots/task graph load error.snap | 6 ++++++ crates/vite_task_plan/tests/plan_snapshots/main.rs | 13 ++++++++++--- 6 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/package.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/packages/pkg-a/package.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/packages/pkg-a/vite-task.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/pnpm-workspace.yaml create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/snapshots/task graph load error.snap diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/package.json new file mode 100644 index 000000000..5c37b711d --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/cache-scripts-error-non-root", + "scripts": { + "build": "echo building" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/packages/pkg-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/packages/pkg-a/package.json new file mode 100644 index 000000000..3fadeff85 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/packages/pkg-a/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/pkg-a", + "scripts": { + "build": "echo building pkg-a" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/packages/pkg-a/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/packages/pkg-a/vite-task.json new file mode 100644 index 000000000..1d0fe9f20 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/packages/pkg-a/vite-task.json @@ -0,0 +1,3 @@ +{ + "cacheScripts": true +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/pnpm-workspace.yaml new file mode 100644 index 000000000..924b55f42 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - packages/* diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/snapshots/task graph load error.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/snapshots/task graph load error.snap new file mode 100644 index 000000000..b353f43df --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/snapshots/task graph load error.snap @@ -0,0 +1,6 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: err_str +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root +--- +`cacheScripts` can only be set in the workspace root config, but found in "/packages/pkg-a" diff --git a/crates/vite_task_plan/tests/plan_snapshots/main.rs b/crates/vite_task_plan/tests/plan_snapshots/main.rs index 4c94b4ab0..1cb7a7e8b 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/main.rs +++ b/crates/vite_task_plan/tests/plan_snapshots/main.rs @@ -97,10 +97,17 @@ fn run_case_inner( ) .unwrap(); + let task_graph_result = session.ensure_task_graph_loaded().await; + let task_graph = match task_graph_result { + Ok(task_graph) => task_graph, + Err(err) => { + let err_str = format!("{err:#}").replace(workspace_root_str, ""); + insta::assert_snapshot!("task graph load error", err_str); + return; + } + }; let task_graph_json = redact_snapshot( - &vite_graph_ser::SerializeByKey( - session.ensure_task_graph_loaded().await.unwrap().task_graph(), - ), + &vite_graph_ser::SerializeByKey(task_graph.task_graph()), workspace_root_str, ); insta::assert_json_snapshot!("task graph", task_graph_json); From f7b62420e49a1150c7c0b1b8ac4128d936f51793 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 5 Feb 2026 10:42:57 +0800 Subject: [PATCH 07/10] feat: allow vite-task.json at workspace root without package.json Previously, if a workspace root (with pnpm-workspace.yaml) had no package.json, the root vite-task.json was silently ignored. This change ensures a root package node is always added to the package graph, using PackageJson::default() when no package.json exists. This enables workspace-level config (like cacheScripts) and root-level tasks without requiring a root package.json. Co-Authored-By: Claude Opus 4.5 --- .../packages/pkg-a/package.json | 6 ++ .../pnpm-workspace.yaml | 2 + .../snapshots.toml | 1 + .../snapshots/task graph.snap | 63 +++++++++++++++++++ .../vite-task.json | 8 +++ crates/vite_workspace/src/lib.rs | 39 ++++++------ 6 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/packages/pkg-a/package.json create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/pnpm-workspace.yaml create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots.toml create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots/task graph.snap create mode 100644 crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/vite-task.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/packages/pkg-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/packages/pkg-a/package.json new file mode 100644 index 000000000..3fadeff85 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/packages/pkg-a/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/pkg-a", + "scripts": { + "build": "echo building pkg-a" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/pnpm-workspace.yaml new file mode 100644 index 000000000..924b55f42 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - packages/* diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots.toml new file mode 100644 index 000000000..432a4504a --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots.toml @@ -0,0 +1 @@ +# Test cases for workspace root without package.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots/task graph.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots/task graph.snap new file mode 100644 index 000000000..051e2c040 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/snapshots/task graph.snap @@ -0,0 +1,63 @@ +--- +source: crates/vite_task_plan/tests/plan_snapshots/main.rs +expression: task_graph_json +input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json +--- +[ + { + "key": [ + "/", + "deploy" + ], + "node": { + "task_display": { + "package_name": "", + "task_name": "deploy", + "package_path": "/" + }, + "resolved_config": { + "command": "echo deploying workspace", + "resolved_options": { + "cwd": "/", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "pass_through_envs": [ + "" + ] + } + } + } + } + }, + "neighbors": [] + }, + { + "key": [ + "/packages/pkg-a", + "build" + ], + "node": { + "task_display": { + "package_name": "@test/pkg-a", + "task_name": "build", + "package_path": "/packages/pkg-a" + }, + "resolved_config": { + "command": "echo building pkg-a", + "resolved_options": { + "cwd": "/packages/pkg-a", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "pass_through_envs": [ + "" + ] + } + } + } + } + }, + "neighbors": [] + } +] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/vite-task.json new file mode 100644 index 000000000..f07f1a9c6 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace-root-no-package-json/vite-task.json @@ -0,0 +1,8 @@ +{ + "cacheScripts": true, + "tasks": { + "deploy": { + "command": "echo deploying workspace" + } + } +} diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index db1d8e9bb..64965855e 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -257,27 +257,28 @@ pub fn load_package_graph( has_root_package = has_root_package || package_path.as_str().is_empty(); graph_builder.add_package(package_path, absolute_path.into(), package_json); } - // try add the root package anyway if the member globs do not include it. + // Always add the root package if member globs do not include it. if !has_root_package { let package_json_path = workspace_root.path.join("package.json"); - match fs::read(&package_json_path) { + let package_json = match fs::read(&package_json_path) { Ok(content) => { let package_json_path: Arc = package_json_path.into(); - let package_json: PackageJson = serde_json::from_slice(&content).map_err(|e| { - Error::SerdeJson { file_path: package_json_path, serde_json_error: e } - })?; - graph_builder.add_package( - RelativePathBuf::default(), - Arc::clone(&workspace_root.path), - package_json, - ); + serde_json::from_slice(&content).map_err(|e| Error::SerdeJson { + file_path: package_json_path, + serde_json_error: e, + })? } - Err(err) => { - if err.kind() != io::ErrorKind::NotFound { - return Err(err.into()); - } + Err(err) if err.kind() == io::ErrorKind::NotFound => { + // No package.json at root - use empty default + PackageJson::default() } - } + Err(err) => return Err(err.into()), + }; + graph_builder.add_package( + RelativePathBuf::default(), + Arc::clone(&workspace_root.path), + package_json, + ); } graph_builder.build() } @@ -616,8 +617,8 @@ mod tests { let graph = discover_package_graph(temp_dir_path).unwrap(); - // Should have 2 nodes but no edges (nameless package can't be referenced) - assert_eq!(graph.node_count(), 2); + // Should have 3 nodes (nameless, pkg-a, root) but no edges (nameless package can't be referenced) + assert_eq!(graph.node_count(), 3); assert_eq!(graph.edge_count(), 0); } @@ -703,8 +704,8 @@ mod tests { let graph = discover_package_graph(temp_dir_path).unwrap(); - // Should have 2 nodes and 2 edges (circular) - assert_eq!(graph.node_count(), 2); + // Should have 3 nodes (pkg-a, pkg-b, root) and 2 edges (circular between a and b) + assert_eq!(graph.node_count(), 3); assert_eq!(graph.edge_count(), 2); // Verify both edges exist From 1da7c67c68f3e7041397347846f1dab8c0d6669d Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 5 Feb 2026 11:00:34 +0800 Subject: [PATCH 08/10] regenerate run-config.ts --- crates/vite_task_graph/run-config.ts | 40 +++++++++++++--------------- dprint.json | 2 +- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/crates/vite_task_graph/run-config.ts b/crates/vite_task_graph/run-config.ts index 68787863d..0c68a8e11 100644 --- a/crates/vite_task_graph/run-config.ts +++ b/crates/vite_task_graph/run-config.ts @@ -1,22 +1,20 @@ -export type Task = - & { - /** - * The command to run for the task. - * - * If omitted, the script from `package.json` with the same name will be used - */ - command?: string; - /** - * The working directory for the task, relative to the package root (not workspace root). - */ - cwd?: string; - /** - * Dependencies of this task. Use `package-name#task-name` to refer to tasks in other packages. - */ - dependsOn?: Array; - } - & ( - | { +export type Task = { + /** + * The command to run for the task. + * + * If omitted, the script from `package.json` with the same name will be used + */ + command?: string; + /** + * The working directory for the task, relative to the package root (not workspace root). + */ + cwd?: string; + /** + * Dependencies of this task. Use `package-name#task-name` to refer to tasks in other packages. + */ + dependsOn?: Array; +} & ( + | { /** * Whether to cache the task */ @@ -30,13 +28,13 @@ export type Task = */ passThroughEnvs?: Array; } - | { + | { /** * Whether to cache the task */ cache: false; } - ); +); export type RunConfig = { /** diff --git a/dprint.json b/dprint.json index 0484a32cd..5dc442769 100644 --- a/dprint.json +++ b/dprint.json @@ -15,7 +15,7 @@ "excludes": [ "crates/fspy_detours_sys/detours", "pnpm-lock.yaml", - "crates/vite_task_graph/task-config.ts" + "crates/vite_task_graph/run-config.ts" ], "plugins": [ "https://plugins.dprint.dev/typescript-0.94.0.wasm", From 2ca9bc87814f4c374030fd2a10a2e1426f0b9c14 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 5 Feb 2026 11:05:58 +0800 Subject: [PATCH 09/10] simplify code --- crates/vite_task_graph/src/lib.rs | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 1049a3cf9..a2c9da0f5 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -205,7 +205,7 @@ impl IndexedTaskGraph { // First pass: load all configs, extract cacheScripts from root, validate let mut cache_scripts = false; // Default: disabled - let mut package_configs: Vec<(PackageNodeIndex, Arc, Option)> = + let mut package_configs: Vec<(PackageNodeIndex, Arc, UserRunConfig)> = Vec::with_capacity(package_graph.node_count()); for package_index in package_graph.node_indices() { @@ -213,20 +213,22 @@ impl IndexedTaskGraph { let package_dir: Arc = workspace_root.path.join(&package.path).into(); let is_workspace_root = package.path.as_str().is_empty(); - let user_config = - config_loader.load_user_config_file(&package_dir).await.map_err(|error| { - TaskGraphLoadError::ConfigLoadError { error, package_path: package_dir.clone() } - })?; - - if let Some(ref config) = user_config { - if config.cache_scripts.is_some() { - if is_workspace_root { - cache_scripts = config.cache_scripts.unwrap_or(false); - } else { - return Err(TaskGraphLoadError::CacheScriptsInNonRootPackage { - package_path: package_dir.clone(), - }); - } + let user_config = config_loader + .load_user_config_file(&package_dir) + .await + .map_err(|error| TaskGraphLoadError::ConfigLoadError { + error, + package_path: package_dir.clone(), + })? + .unwrap_or_default(); + + if let Some(current_cache_scripts) = user_config.cache_scripts { + if is_workspace_root { + cache_scripts = current_cache_scripts; + } else { + return Err(TaskGraphLoadError::CacheScriptsInNonRootPackage { + package_path: package_dir.clone(), + }); } } @@ -245,9 +247,7 @@ impl IndexedTaskGraph { .map(|(name, value)| (name.as_str(), value.as_str())) .collect(); - for (task_name, task_user_config) in - user_config.and_then(|c| c.tasks).unwrap_or_default() - { + for (task_name, task_user_config) in user_config.tasks.unwrap_or_default() { // For each task defined in vite.config.*, look up the corresponding package.json script (if any) let package_json_script = package_json_scripts.remove(task_name.as_str()); From b16e3cdc966edcbfab51fafd77deb80d7689a9ac Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 5 Feb 2026 11:21:58 +0800 Subject: [PATCH 10/10] fix: use Display instead of Debug for path in error message Debug formatting escapes backslashes on Windows, breaking the path replacement in snapshot tests. Switch to Display formatting via a new Display impl on AbsolutePath. Co-Authored-By: Claude Opus 4.5 --- crates/vite_path/src/absolute/mod.rs | 6 ++++++ crates/vite_task_graph/src/lib.rs | 2 +- .../snapshots/task graph load error.snap | 2 +- crates/vite_task_plan/tests/plan_snapshots/main.rs | 5 ++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/vite_path/src/absolute/mod.rs b/crates/vite_path/src/absolute/mod.rs index 98ffe860a..4e0ed98c5 100644 --- a/crates/vite_path/src/absolute/mod.rs +++ b/crates/vite_path/src/absolute/mod.rs @@ -31,6 +31,12 @@ impl Debug for AbsolutePath { } } +impl Display for AbsolutePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0.display(), f) + } +} + impl Serialize for AbsolutePath { fn serialize(&self, serializer: S) -> Result where diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index a2c9da0f5..e0e8fa74f 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -114,7 +114,7 @@ pub enum TaskGraphLoadError { }, #[error( - "`cacheScripts` can only be set in the workspace root config, but found in {package_path:?}" + "`cacheScripts` can only be set in the workspace root config, but found in {package_path}" )] CacheScriptsInNonRootPackage { package_path: Arc }, } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/snapshots/task graph load error.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/snapshots/task graph load error.snap index b353f43df..6807d8ccc 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/snapshots/task graph load error.snap +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root/snapshots/task graph load error.snap @@ -3,4 +3,4 @@ source: crates/vite_task_plan/tests/plan_snapshots/main.rs expression: err_str input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/cache-scripts-error-non-root --- -`cacheScripts` can only be set in the workspace root config, but found in "/packages/pkg-a" +`cacheScripts` can only be set in the workspace root config, but found in /packages/pkg-a diff --git a/crates/vite_task_plan/tests/plan_snapshots/main.rs b/crates/vite_task_plan/tests/plan_snapshots/main.rs index 1cb7a7e8b..0677eeacf 100644 --- a/crates/vite_task_plan/tests/plan_snapshots/main.rs +++ b/crates/vite_task_plan/tests/plan_snapshots/main.rs @@ -101,7 +101,10 @@ fn run_case_inner( let task_graph = match task_graph_result { Ok(task_graph) => task_graph, Err(err) => { - let err_str = format!("{err:#}").replace(workspace_root_str, ""); + let mut err_str = format!("{err:#}").replace(workspace_root_str, ""); + if cfg!(windows) { + err_str = err_str.replace('\\', "/"); + } insta::assert_snapshot!("task graph load error", err_str); return; }