Skip to content

Commit 12cea1c

Browse files
claudebranchseer
authored andcommitted
feat: add --cache/--no-cache CLI flags and move cache resolution to plan stage
Move global cache config resolution from task graph load time to plan time, enabling CLI overrides and proper nested vp run support. Key changes: - Add TaskSource enum to distinguish TaskConfig vs PackageJsonScript - Store ResolvedGlobalCacheConfig on IndexedTaskGraph without applying the global kill switch at graph load time - Add CacheOverride enum (None/ForceEnabled/ForceDisabled) to PlanOptions - Add --cache and --no-cache flags to RunFlags with conflicts_with - Compute final ResolvedGlobalCacheConfig in PlanContext by combining graph config with CLI override - Apply effective_cache_config per task at plan time using TaskSource - Nested vp run --cache/--no-cache overrides parent's resolved config https://claude.ai/code/session_01AYbt3E5j8Adk9NB7Sprkah
1 parent d2b8f83 commit 12cea1c

36 files changed

Lines changed: 516 additions & 177 deletions

File tree

crates/vite_task/src/cli/mod.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use clap::Parser;
44
use vite_path::AbsolutePath;
55
use vite_str::Str;
66
use vite_task_graph::{TaskSpecifier, query::TaskQuery};
7-
use vite_task_plan::plan_request::{PlanOptions, QueryPlanRequest};
7+
use vite_task_plan::plan_request::{CacheOverride, PlanOptions, QueryPlanRequest};
88
use vite_workspace::package_filter::{PackageQueryArgs, PackageQueryError};
99

1010
#[derive(Debug, Clone, clap::Subcommand)]
@@ -15,6 +15,7 @@ pub enum CacheSubcommand {
1515

1616
/// Flags that control how a `run` command selects tasks.
1717
#[derive(Debug, Clone, PartialEq, Eq, clap::Args)]
18+
#[expect(clippy::struct_excessive_bools, reason = "CLI flags are naturally boolean")]
1819
pub struct RunFlags {
1920
#[clap(flatten)]
2021
pub package_query: PackageQueryArgs,
@@ -26,6 +27,27 @@ pub struct RunFlags {
2627
/// Show full detailed summary after execution.
2728
#[clap(default_value = "false", short = 'v', long)]
2829
pub verbose: bool,
30+
31+
/// Force caching on for all tasks and scripts.
32+
#[clap(long, conflicts_with = "no_cache")]
33+
pub cache: bool,
34+
35+
/// Force caching off for all tasks and scripts.
36+
#[clap(long, conflicts_with = "cache")]
37+
pub no_cache: bool,
38+
}
39+
40+
impl RunFlags {
41+
#[must_use]
42+
pub const fn cache_override(&self) -> CacheOverride {
43+
if self.cache {
44+
CacheOverride::ForceEnabled
45+
} else if self.no_cache {
46+
CacheOverride::ForceDisabled
47+
} else {
48+
CacheOverride::None
49+
}
50+
}
2951
}
3052

3153
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -155,19 +177,23 @@ impl ResolvedRunCommand {
155177
let raw_specifier = self.task_specifier.ok_or(CLITaskQueryError::MissingTaskSpecifier)?;
156178
let task_specifier = TaskSpecifier::parse_raw(&raw_specifier);
157179

180+
let cache_override = self.flags.cache_override();
181+
let include_explicit_deps = !self.flags.ignore_depends_on;
182+
158183
let (package_query, is_cwd_only) =
159184
self.flags.package_query.into_package_query(task_specifier.package_name, cwd)?;
160185

161-
let include_explicit_deps = !self.flags.ignore_depends_on;
162-
163186
Ok((
164187
QueryPlanRequest {
165188
query: TaskQuery {
166189
package_query,
167190
task_name: task_specifier.task_name,
168191
include_explicit_deps,
169192
},
170-
plan_options: PlanOptions { extra_args: self.additional_args.into() },
193+
plan_options: PlanOptions {
194+
extra_args: self.additional_args.into(),
195+
cache_override,
196+
},
171197
},
172198
is_cwd_only,
173199
))

crates/vite_task_graph/src/config/mod.rs

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,29 +100,18 @@ pub enum ResolveTaskConfigError {
100100
}
101101

102102
impl ResolvedTaskConfig {
103-
/// Resolve from package.json script only (no vite-task.json config for this task)
103+
/// Resolve from package.json script only (no config entry for this task).
104104
///
105-
/// The `cache_scripts` parameter determines whether caching is enabled for the script.
106-
/// When `true`, caching is enabled with default settings.
107-
/// When `false`, caching is disabled.
105+
/// Always resolves with caching enabled (default settings).
106+
/// The global cache config is applied at plan time, not here.
108107
#[must_use]
109108
pub fn resolve_package_json_script(
110109
package_dir: &Arc<AbsolutePath>,
111110
package_json_script: &str,
112-
cache_scripts: bool,
113111
) -> Self {
114-
let cache_config = if cache_scripts {
115-
UserCacheConfig::Enabled {
116-
cache: None,
117-
enabled_cache_config: EnabledCacheConfig { envs: None, pass_through_envs: None },
118-
}
119-
} else {
120-
UserCacheConfig::Disabled { cache: MustBe!(false) }
121-
};
122-
let options = UserTaskOptions { cache_config, ..Default::default() };
123112
Self {
124113
command: package_json_script.into(),
125-
resolved_options: ResolvedTaskOptions::resolve(options, package_dir),
114+
resolved_options: ResolvedTaskOptions::resolve(UserTaskOptions::default(), package_dir),
126115
}
127116
}
128117

crates/vite_task_graph/src/config/user.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ pub enum UserGlobalCacheConfig {
150150
}
151151

152152
/// Resolved global cache configuration with concrete boolean values.
153+
#[derive(Debug, Clone, Copy)]
153154
pub struct ResolvedGlobalCacheConfig {
154155
pub scripts: bool,
155156
pub tasks: bool,

crates/vite_task_graph/src/lib.rs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod specifier;
66

77
use std::{convert::Infallible, sync::Arc};
88

9-
use config::{ResolvedTaskConfig, UserRunConfig};
9+
use config::{ResolvedGlobalCacheConfig, ResolvedTaskConfig, UserRunConfig};
1010
use petgraph::graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex};
1111
use rustc_hash::{FxBuildHasher, FxHashMap};
1212
use serde::Serialize;
@@ -47,6 +47,15 @@ pub(crate) struct TaskId {
4747
pub task_name: Str,
4848
}
4949

50+
/// Whether a task originates from the `tasks` map or from a package.json script.
51+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
52+
pub enum TaskSource {
53+
/// Defined in the `tasks` map in the workspace config.
54+
TaskConfig,
55+
/// Pure package.json script (not in the tasks map).
56+
PackageJsonScript,
57+
}
58+
5059
/// A node in the task graph, representing a task with its resolved configuration.
5160
#[derive(Debug, Serialize)]
5261
pub struct TaskNode {
@@ -60,6 +69,9 @@ pub struct TaskNode {
6069
///
6170
/// However, it does not contain external factors like additional args from cli and env vars.
6271
pub resolved_config: ResolvedTaskConfig,
72+
73+
/// Whether this task comes from the tasks map or a package.json script.
74+
pub source: TaskSource,
6375
}
6476

6577
impl vite_graph_ser::GetKey for TaskNode {
@@ -166,6 +178,9 @@ pub struct IndexedTaskGraph {
166178

167179
/// task indices by task id for quick lookup
168180
pub(crate) node_indices_by_task_id: FxHashMap<TaskId, TaskNodeIndex>,
181+
182+
/// Global cache configuration resolved from the workspace root config.
183+
resolved_global_cache: ResolvedGlobalCacheConfig,
169184
}
170185

171186
pub type TaskGraph = DiGraph<TaskNode, TaskDependencyType, TaskIx>;
@@ -234,11 +249,9 @@ impl IndexedTaskGraph {
234249
package_configs.push((package_index, package_dir, user_config));
235250
}
236251

237-
let resolved_cache = config::ResolvedGlobalCacheConfig::resolve_from(root_cache.as_ref());
238-
let cache_scripts = resolved_cache.scripts;
239-
let cache_tasks = resolved_cache.tasks;
252+
let resolved_global_cache = ResolvedGlobalCacheConfig::resolve_from(root_cache.as_ref());
240253

241-
// Second pass: create task nodes using resolved cache config
254+
// Second pass: create task nodes (cache is NOT applied here; it's applied at plan time)
242255
for (package_index, package_dir, user_config) in package_configs {
243256
let package = &package_graph[package_index];
244257

@@ -250,19 +263,15 @@ impl IndexedTaskGraph {
250263
.map(|(name, value)| (name.as_str(), value.as_str()))
251264
.collect();
252265

253-
for (task_name, mut task_user_config) in user_config.tasks.unwrap_or_default() {
254-
// Apply cache.tasks kill switch: when false, override all tasks to disable caching
255-
if !cache_tasks {
256-
task_user_config.options.cache_config = config::UserCacheConfig::disabled();
257-
}
258-
// For each task defined in vite.config.*, look up the corresponding package.json script (if any)
266+
for (task_name, task_user_config) in user_config.tasks.unwrap_or_default() {
267+
// For each task defined in the config, look up the corresponding package.json script (if any)
259268
let package_json_script = package_json_scripts.remove(task_name.as_str());
260269

261270
let task_id = TaskId { task_name: task_name.clone(), package_index };
262271

263272
let dependency_specifiers = task_user_config.options.depends_on.clone();
264273

265-
// Resolve the task configuration combining vite.config.* and package.json script
274+
// Resolve the task configuration combining config and package.json script
266275
let resolved_config = ResolvedTaskConfig::resolve(
267276
task_user_config,
268277
&package_dir,
@@ -284,20 +293,20 @@ impl IndexedTaskGraph {
284293
package_path: Arc::clone(&package_dir),
285294
},
286295
resolved_config,
296+
source: TaskSource::TaskConfig,
287297
};
288298

289299
let node_index = task_graph.add_node(task_node);
290300
task_ids_with_dependency_specifiers.push((task_id.clone(), dependency_specifiers));
291301
node_indices_by_task_id.insert(task_id, node_index);
292302
}
293303

294-
// For remaining package.json scripts not defined in vite.config.*, create tasks with default config
304+
// For remaining package.json scripts not in the tasks map, create tasks with default config
295305
for (script_name, package_json_script) in package_json_scripts {
296306
let task_id = TaskId { task_name: Str::from(script_name), package_index };
297307
let resolved_config = ResolvedTaskConfig::resolve_package_json_script(
298308
&package_dir,
299309
package_json_script,
300-
cache_scripts,
301310
);
302311
let node_index = task_graph.add_node(TaskNode {
303312
task_display: TaskDisplay {
@@ -306,6 +315,7 @@ impl IndexedTaskGraph {
306315
package_path: Arc::clone(&package_dir),
307316
},
308317
resolved_config,
318+
source: TaskSource::PackageJsonScript,
309319
});
310320
node_indices_by_task_id.insert(task_id, node_index);
311321
}
@@ -316,6 +326,7 @@ impl IndexedTaskGraph {
316326
task_graph,
317327
indexed_package_graph: IndexedPackageGraph::index(package_graph),
318328
node_indices_by_task_id,
329+
resolved_global_cache,
319330
};
320331

321332
// Add explicit dependencies
@@ -420,4 +431,9 @@ impl IndexedTaskGraph {
420431
let index = self.indexed_package_graph.get_package_index_from_cwd(cwd)?;
421432
Some(self.get_package_path(index))
422433
}
434+
435+
#[must_use]
436+
pub const fn global_cache_config(&self) -> &ResolvedGlobalCacheConfig {
437+
&self.resolved_global_cache
438+
}
423439
}

crates/vite_task_plan/src/context.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{env::JoinPathsError, ffi::OsStr, ops::Range, sync::Arc};
33
use rustc_hash::FxHashMap;
44
use vite_path::AbsolutePath;
55
use vite_str::Str;
6-
use vite_task_graph::{IndexedTaskGraph, TaskNodeIndex};
6+
use vite_task_graph::{IndexedTaskGraph, TaskNodeIndex, config::ResolvedGlobalCacheConfig};
77

88
use crate::{PlanRequestParser, path_env::prepend_path_env};
99

@@ -39,6 +39,9 @@ pub struct PlanContext<'a> {
3939
extra_args: Arc<[Str]>,
4040

4141
indexed_task_graph: &'a IndexedTaskGraph,
42+
43+
/// Final resolved global cache config, combining the graph's config with any CLI override.
44+
resolved_global_cache: ResolvedGlobalCacheConfig,
4245
}
4346

4447
impl<'a> PlanContext<'a> {
@@ -48,6 +51,7 @@ impl<'a> PlanContext<'a> {
4851
envs: FxHashMap<Arc<OsStr>, Arc<OsStr>>,
4952
callbacks: &'a mut (dyn PlanRequestParser + 'a),
5053
indexed_task_graph: &'a IndexedTaskGraph,
54+
resolved_global_cache: ResolvedGlobalCacheConfig,
5155
) -> Self {
5256
Self {
5357
workspace_path,
@@ -57,6 +61,7 @@ impl<'a> PlanContext<'a> {
5761
task_call_stack: Vec::new(),
5862
indexed_task_graph,
5963
extra_args: Arc::default(),
64+
resolved_global_cache,
6065
}
6166
}
6267

@@ -115,6 +120,14 @@ impl<'a> PlanContext<'a> {
115120
self.extra_args = extra_args;
116121
}
117122

123+
pub const fn resolved_global_cache(&self) -> &ResolvedGlobalCacheConfig {
124+
&self.resolved_global_cache
125+
}
126+
127+
pub const fn set_resolved_global_cache(&mut self, config: ResolvedGlobalCacheConfig) {
128+
self.resolved_global_cache = config;
129+
}
130+
118131
pub fn duplicate(&mut self) -> PlanContext<'_> {
119132
PlanContext {
120133
workspace_path: self.workspace_path,
@@ -124,6 +137,7 @@ impl<'a> PlanContext<'a> {
124137
task_call_stack: self.task_call_stack.clone(),
125138
indexed_task_graph: self.indexed_task_graph,
126139
extra_args: Arc::clone(&self.extra_args),
140+
resolved_global_cache: self.resolved_global_cache,
127141
}
128142
}
129143
}

crates/vite_task_plan/src/lib.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub use execution_graph::ExecutionGraph;
1616
pub use in_process::InProcessExecution;
1717
pub use path_env::{get_path_env, prepend_path_env};
1818
use plan::{ParentCacheConfig, plan_query_request, plan_synthetic_request};
19-
use plan_request::{PlanRequest, QueryPlanRequest, SyntheticPlanRequest};
19+
use plan_request::{CacheOverride, PlanRequest, QueryPlanRequest, SyntheticPlanRequest};
2020
use rustc_hash::FxHashMap;
2121
use serde::{Serialize, ser::SerializeMap as _};
2222
use vite_path::AbsolutePath;
@@ -200,16 +200,37 @@ pub async fn plan_query(
200200
) -> Result<ExecutionGraph, Error> {
201201
let indexed_task_graph = task_graph_loader.load_task_graph().await?;
202202

203+
let resolved_global_cache = resolve_cache_with_override(
204+
*indexed_task_graph.global_cache_config(),
205+
query_plan_request.plan_options.cache_override,
206+
);
207+
203208
let context = PlanContext::new(
204209
workspace_path,
205210
Arc::clone(cwd),
206211
envs.clone(),
207212
plan_request_parser,
208213
indexed_task_graph,
214+
resolved_global_cache,
209215
);
210216
plan_query_request(query_plan_request, context).await
211217
}
212218

219+
const fn resolve_cache_with_override(
220+
graph_cache: vite_task_graph::config::ResolvedGlobalCacheConfig,
221+
cache_override: CacheOverride,
222+
) -> vite_task_graph::config::ResolvedGlobalCacheConfig {
223+
match cache_override {
224+
CacheOverride::ForceEnabled => {
225+
vite_task_graph::config::ResolvedGlobalCacheConfig { scripts: true, tasks: true }
226+
}
227+
CacheOverride::ForceDisabled => {
228+
vite_task_graph::config::ResolvedGlobalCacheConfig { scripts: false, tasks: false }
229+
}
230+
CacheOverride::None => graph_cache,
231+
}
232+
}
233+
213234
/// Plan a synthetic task execution, returning the resolved [`SpawnExecution`] directly.
214235
///
215236
/// Unlike [`plan_query`] which returns a full execution graph, synthetic executions

0 commit comments

Comments
 (0)