Skip to content

Commit 72c426f

Browse files
branchseerclaude
andcommitted
refactor: simplify CLI and Session API by removing generic type parameters
Remove generic CustomSubcommand type parameter from Session, SessionCallbacks, PlanRequestParser, and TaskSynthesizer. The CLI only supports `run` as a top-level subcommand; synthetic tasks (lint, test, env-test) are now handled purely by TaskSynthesizer via string matching on program name and args. Key changes: - TaskSynthesizer trait takes (program, args) directly instead of a typed subcommand - Session/SessionCallbacks/PlanRequestParser no longer parameterized by subcommand type - plan_from_cli accepts BuiltInCommand directly (removed CLIArgs, TaskCLIArgs, ParsedTaskCLIArgs) - Binary crate owns its top-level Cli parser wrapping BuiltInCommand - E2E test fixtures converted from `vite lint`/`vite env-test` to `vite run <task>` - Removed same-name-as-builtin fixture (tested now-removed CLI lint vs run distinction) - Added diagnostic block sorting in E2E output redaction for deterministic snapshots Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e3afc2f commit 72c426f

File tree

53 files changed

+696
-652
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+696
-652
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/vite_task/docs/task-cache.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -611,10 +611,10 @@ CommandFingerprint {
611611
}
612612
```
613613

614-
### Example: Built-in Task Cache Key
614+
### Example: Synthetic Task Cache Key
615615

616616
```rust
617-
// Built-in task: vite lint
617+
// Synthetic task (e.g., "vite lint" in a task script)
618618
TaskRunKey {
619619
task_id: TaskId {
620620
task_group_id: TaskGroupId {
@@ -830,20 +830,20 @@ Cache hit, replaying
830830
b
831831
```
832832

833-
### Example 3: Built-in Task Caching by Working Directory
833+
### Example 3: Task Caching by Working Directory
834834

835835
```bash
836-
# Different directories create separate caches for built-in tasks
837-
> cd folder1 && vite lint
836+
# Different directories create separate caches for tasks
837+
> cd folder1 && vite run lint
838838
Cache not found
839839
Found 0 warnings and 0 errors.
840840

841-
> cd folder2 && vite lint
841+
> cd folder2 && vite run lint
842842
Cache not found # Different cwd = different cache
843843
Found 0 warnings and 0 errors.
844844

845845
# Each directory maintains its own cache
846-
> cd folder1 && vite lint
846+
> cd folder1 && vite run lint
847847
Cache hit, replaying
848848
Found 0 warnings and 0 errors.
849849
```

crates/vite_task/src/cli/mod.rs

Lines changed: 4 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,14 @@
1-
use std::{ffi::OsStr, sync::Arc};
1+
use std::sync::Arc;
22

3-
use clap::{Parser, Subcommand};
3+
use clap::Subcommand;
44
use vite_path::AbsolutePath;
55
use vite_str::Str;
66
use vite_task_graph::{TaskSpecifier, query::TaskQueryKind};
77
use vite_task_plan::plan_request::{PlanOptions, PlanRequest, QueryPlanRequest};
88

9-
/// Represents the CLI arguments handled by vite-task, including both built-in (like run) and custom subcommands (like lint).
10-
#[derive(Debug)]
11-
pub struct TaskCLIArgs<CustomSubcommand: Subcommand> {
12-
pub(crate) original: Arc<[Str]>,
13-
pub(crate) parsed: ParsedTaskCLIArgs<CustomSubcommand>,
14-
}
15-
16-
impl<CustomSubcommand: Subcommand> TaskCLIArgs<CustomSubcommand> {
17-
/// Inspect the custom subcommand (like lint/install). Returns `None` if it's built-in subcommand
18-
/// The caller should not use this method to actually handle the custom subcommand. Instead, it should
19-
/// private TaskSynthesizer to Session so that vite-task can handle custom subcommands consistently from
20-
/// both direct CLI invocations and invocations in task scripts.
21-
///
22-
/// This method is provided only to make it possible for the caller to behave differently BEFORE and AFTER the session.
23-
/// For example, vite+ needs this method to skip auto-install when the custom subcommand is already `install`.
24-
pub fn custom_subcommand(&self) -> Option<&CustomSubcommand> {
25-
match &self.parsed {
26-
ParsedTaskCLIArgs::BuiltIn(_) => None,
27-
ParsedTaskCLIArgs::Custom(custom) => Some(custom),
28-
}
29-
}
30-
}
31-
32-
/// Represents the overall CLI arguments, containing three kinds of subcommands:
33-
/// 1. Built-in subcommands handled by vite-task (like run)
34-
/// 2. Custom subcommands handled by vite-task with the help of TaskSyntheizer (like lint)
35-
/// 3. Custom subcommands not handled by vite-task (like vite+ commands without cache)
36-
pub enum CLIArgs<CustomSubcommand: Subcommand, NonTaskSubcommand: Subcommand> {
37-
/// Subcommands handled by vite task, including built-in (like run) and custom (like lint)
38-
Task(TaskCLIArgs<CustomSubcommand>),
39-
/// Custom subcommands not handled by vite task (like vite+ commands without cache)
40-
NonTask(NonTaskSubcommand),
41-
}
42-
43-
impl<CustomSubcommand: Subcommand, NonTaskSubcommand: Subcommand>
44-
CLIArgs<CustomSubcommand, NonTaskSubcommand>
45-
{
46-
/// Get the original CLI arguments
47-
pub fn try_parse_from(
48-
args: impl Iterator<Item = impl AsRef<str>>,
49-
) -> Result<Self, clap::Error> {
50-
#[derive(Debug, clap::Parser)]
51-
enum ParsedCLIArgs<CustomSubcommand: Subcommand, NonTaskSubcommand: Subcommand> {
52-
/// subcommands handled by vite task
53-
#[command(flatten)]
54-
Task(ParsedTaskCLIArgs<CustomSubcommand>),
55-
56-
/// subcommands that are not handled by vite task
57-
#[command(flatten)]
58-
NonTask(NonTaskSubcommand),
59-
}
60-
61-
let args = args.map(|arg| Str::from(arg.as_ref())).collect::<Arc<[Str]>>();
62-
let parsed_cli_args = ParsedCLIArgs::<CustomSubcommand, NonTaskSubcommand>::try_parse_from(
63-
args.iter().map(|s| OsStr::new(s.as_str())),
64-
)?;
65-
66-
Ok(match parsed_cli_args {
67-
ParsedCLIArgs::Task(parsed_task_cli_args) => {
68-
Self::Task(TaskCLIArgs { original: args, parsed: parsed_task_cli_args })
69-
}
70-
ParsedCLIArgs::NonTask(non_task_subcommand) => Self::NonTask(non_task_subcommand),
71-
})
72-
}
73-
}
74-
75-
#[derive(Debug, Parser)]
76-
pub(crate) enum ParsedTaskCLIArgs<CustomSubcommand: Subcommand> {
77-
/// subcommands provided by vite task, like `run`
78-
#[clap(flatten)]
79-
BuiltIn(BuiltInCommand),
80-
/// custom subcommands provided by vite+, like `lint`
81-
#[clap(flatten)]
82-
Custom(CustomSubcommand),
83-
}
84-
859
/// vite task CLI subcommands
8610
#[derive(Debug, Subcommand)]
87-
pub(crate) enum BuiltInCommand {
11+
pub enum BuiltInCommand {
8812
/// Run tasks
8913
Run {
9014
/// `packageName#taskName` or `taskName`.
@@ -118,7 +42,7 @@ pub enum CLITaskQueryError {
11842
}
11943

12044
impl BuiltInCommand {
121-
/// Convert to `TaskQuery`, or return an error if invalid.
45+
/// Convert to `PlanRequest`, or return an error if invalid.
12246
pub fn into_plan_request(
12347
self,
12448
cwd: &Arc<AbsolutePath>,

crates/vite_task/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod maybe_str;
44
pub mod session;
55

66
// Public exports for vite_task_bin
7-
pub use cli::CLIArgs;
7+
pub use cli::BuiltInCommand;
88
pub use session::{LabeledReporter, Reporter, Session, SessionCallbacks, TaskSynthesizer};
99
pub use vite_task_graph::{
1010
config::{

crates/vite_task/src/session/execute/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ impl ExecutionContext<'_> {
348348
}
349349
}
350350

351-
impl<'a, CustomSubcommand> Session<'a, CustomSubcommand> {
351+
impl<'a> Session<'a> {
352352
/// Execute an execution plan, reporting events to the provided reporter.
353353
///
354354
/// Returns Err(ExitStatus) to suggest the caller to abort and exit the process with the given exit status.

crates/vite_task/src/session/mod.rs

Lines changed: 55 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{ffi::OsStr, fmt::Debug, sync::Arc};
88

99
use cache::ExecutionCache;
1010
pub use cache::{CacheMiss, FingerprintMismatch};
11-
use clap::{Parser, Subcommand};
11+
use clap::Parser;
1212
pub use event::ExecutionEvent;
1313
use once_cell::sync::OnceCell;
1414
pub use reporter::{LabeledReporter, Reporter};
@@ -22,10 +22,7 @@ use vite_task_plan::{
2222
};
2323
use vite_workspace::{WorkspaceRoot, find_workspace_root};
2424

25-
use crate::{
26-
cli::{ParsedTaskCLIArgs, TaskCLIArgs},
27-
collections::HashMap,
28-
};
25+
use crate::{cli::BuiltInCommand, collections::HashMap};
2926

3027
#[derive(Debug)]
3128
enum LazyTaskGraph<'a> {
@@ -52,87 +49,76 @@ impl TaskGraphLoader for LazyTaskGraph<'_> {
5249
}
5350
}
5451

55-
pub struct SessionCallbacks<'a, CustomSubcommand> {
56-
pub task_synthesizer: &'a mut (dyn TaskSynthesizer<CustomSubcommand> + 'a),
52+
pub struct SessionCallbacks<'a> {
53+
pub task_synthesizer: &'a mut (dyn TaskSynthesizer + 'a),
5754
pub user_config_loader: &'a mut (dyn UserConfigLoader + 'a),
5855
}
5956

57+
/// Handles synthesizing task plan requests from commands found in task scripts.
58+
///
59+
/// When a task's command references a known program (e.g., `vite lint` in a script),
60+
/// the synthesizer converts it into a `SyntheticPlanRequest` for execution.
6061
#[async_trait::async_trait(?Send)]
61-
pub trait TaskSynthesizer<CustomSubcommand>: Debug {
62-
fn should_synthesize_for_program(&self, program: &str) -> bool;
63-
64-
/// Synthesize a synthetic task plan request for the given parsed custom subcommand.
62+
pub trait TaskSynthesizer: Debug {
63+
/// Called for every command in task scripts to determine if it should be synthesized.
6564
///
65+
/// - `program` is the program name (e.g., `"vite"`).
66+
/// - `args` is all arguments after the program (e.g., `["lint", "--fix"]`).
6667
/// - `envs` is the current environment variables where the task is being planned.
6768
/// - `cwd` is the current working directory where the task is being planned.
6869
///
69-
/// The implementor can return a different `envs` in `SyntheticPlanRequest` to customize
70-
/// environment variables for the synthetic task.
70+
/// Returns `Ok(Some(request))` if the command is recognized and should be synthesized,
71+
/// `Ok(None)` if the command should be executed as a normal process.
7172
async fn synthesize_task(
7273
&mut self,
73-
subcommand: CustomSubcommand,
74+
program: &str,
75+
args: &[Str],
7476
envs: &Arc<HashMap<Arc<OsStr>, Arc<OsStr>>>,
7577
cwd: &Arc<AbsolutePath>,
76-
) -> anyhow::Result<SyntheticPlanRequest>;
78+
) -> anyhow::Result<Option<SyntheticPlanRequest>>;
7779
}
7880

7981
#[derive(derive_more::Debug)]
80-
#[debug(bound())] // Avoid requiring CustomSubcommand: Debug
81-
struct PlanRequestParser<'a, CustomSubcommand> {
82-
task_synthesizer: &'a mut (dyn TaskSynthesizer<CustomSubcommand> + 'a),
83-
}
84-
85-
impl<CustomSubcommand: clap::Subcommand> PlanRequestParser<'_, CustomSubcommand> {
86-
async fn get_plan_request_from_cli_args(
87-
&mut self,
88-
cli_args: ParsedTaskCLIArgs<CustomSubcommand>,
89-
envs: &Arc<HashMap<Arc<OsStr>, Arc<OsStr>>>,
90-
cwd: &Arc<AbsolutePath>,
91-
) -> anyhow::Result<PlanRequest> {
92-
match cli_args {
93-
ParsedTaskCLIArgs::BuiltIn(vite_task_subcommand) => {
94-
Ok(vite_task_subcommand.into_plan_request(cwd)?)
95-
}
96-
ParsedTaskCLIArgs::Custom(custom_subcommand) => {
97-
let synthetic_plan_request =
98-
self.task_synthesizer.synthesize_task(custom_subcommand, envs, cwd).await?;
99-
Ok(PlanRequest::Synthetic(synthetic_plan_request))
100-
}
101-
}
102-
}
82+
struct PlanRequestParser<'a> {
83+
task_synthesizer: &'a mut (dyn TaskSynthesizer + 'a),
10384
}
10485

10586
#[async_trait::async_trait(?Send)]
106-
impl<CustomSubcommand: clap::Subcommand> vite_task_plan::PlanRequestParser
107-
for PlanRequestParser<'_, CustomSubcommand>
108-
{
87+
impl vite_task_plan::PlanRequestParser for PlanRequestParser<'_> {
10988
async fn get_plan_request(
11089
&mut self,
11190
program: &str,
11291
args: &[Str],
11392
envs: &Arc<HashMap<Arc<OsStr>, Arc<OsStr>>>,
11493
cwd: &Arc<AbsolutePath>,
11594
) -> anyhow::Result<Option<PlanRequest>> {
116-
Ok(
117-
if self.task_synthesizer.should_synthesize_for_program(program)
118-
&& let Some(subcommand) = args.first()
119-
&& ParsedTaskCLIArgs::<CustomSubcommand>::has_subcommand(subcommand)
120-
{
121-
let cli_args = ParsedTaskCLIArgs::<CustomSubcommand>::try_parse_from(
122-
std::iter::once(program).chain(args.iter().map(Str::as_str)),
123-
)?;
124-
Some(self.get_plan_request_from_cli_args(cli_args, envs, cwd).await?)
125-
} else {
126-
None
127-
},
128-
)
95+
// Try task synthesizer first (handles e.g. "vite lint" in scripts)
96+
if let Some(synthetic) =
97+
self.task_synthesizer.synthesize_task(program, args, envs, cwd).await?
98+
{
99+
return Ok(Some(PlanRequest::Synthetic(synthetic)));
100+
}
101+
102+
// Try built-in "run" command (handles "vite run build" in scripts)
103+
#[derive(Parser)]
104+
enum BuiltInParser {
105+
#[clap(flatten)]
106+
Command(BuiltInCommand),
107+
}
108+
if let Ok(BuiltInParser::Command(built_in)) = BuiltInParser::try_parse_from(
109+
std::iter::once(program).chain(args.iter().map(Str::as_str)),
110+
) {
111+
return Ok(Some(built_in.into_plan_request(cwd)?));
112+
}
113+
114+
Ok(None)
129115
}
130116
}
131117

132118
/// Represents a vite task session for planning and executing tasks. A process typically has one session.
133119
///
134120
/// A session manages task graph loading internally and provides non-consuming methods to plan and/or execute tasks (allows multiple plans/executions per session).
135-
pub struct Session<'a, CustomSubcommand> {
121+
pub struct Session<'a> {
136122
workspace_path: Arc<AbsolutePath>,
137123
/// A session doesn't necessarily load the task graph immediately.
138124
/// The task graph is loaded on-demand and cached for future use.
@@ -141,7 +127,7 @@ pub struct Session<'a, CustomSubcommand> {
141127
envs: Arc<HashMap<Arc<OsStr>, Arc<OsStr>>>,
142128
cwd: Arc<AbsolutePath>,
143129

144-
plan_request_parser: PlanRequestParser<'a, CustomSubcommand>,
130+
plan_request_parser: PlanRequestParser<'a>,
145131

146132
/// Cache is lazily initialized to avoid SQLite race conditions when multiple
147133
/// processes (e.g., parallel `vite lib` commands) start simultaneously.
@@ -157,9 +143,9 @@ fn get_cache_path_of_workspace(workspace_root: &AbsolutePath) -> AbsolutePathBuf
157143
}
158144
}
159145

160-
impl<'a, CustomSubcommand> Session<'a, CustomSubcommand> {
146+
impl<'a> Session<'a> {
161147
/// Initialize a session with real environment variables and cwd
162-
pub fn init(callbacks: SessionCallbacks<'a, CustomSubcommand>) -> anyhow::Result<Self> {
148+
pub fn init(callbacks: SessionCallbacks<'a>) -> anyhow::Result<Self> {
163149
let envs = std::env::vars_os()
164150
.map(|(k, v)| (Arc::<OsStr>::from(k.as_os_str()), Arc::<OsStr>::from(v.as_os_str())))
165151
.collect();
@@ -176,7 +162,7 @@ impl<'a, CustomSubcommand> Session<'a, CustomSubcommand> {
176162
pub fn init_with(
177163
mut envs: HashMap<Arc<OsStr>, Arc<OsStr>>,
178164
cwd: Arc<AbsolutePath>,
179-
callbacks: SessionCallbacks<'a, CustomSubcommand>,
165+
callbacks: SessionCallbacks<'a>,
180166
) -> anyhow::Result<Self> {
181167
let (workspace_root, _) = find_workspace_root(&cwd)?;
182168
let cache_path = get_cache_path_of_workspace(&workspace_root.path);
@@ -217,9 +203,7 @@ impl<'a, CustomSubcommand> Session<'a, CustomSubcommand> {
217203
_ => None,
218204
}
219205
}
220-
}
221206

222-
impl<'a, CustomSubcommand: clap::Subcommand> Session<'a, CustomSubcommand> {
223207
pub async fn plan_synthetic_task(
224208
&mut self,
225209
synthetic_plan_request: SyntheticPlanRequest,
@@ -239,21 +223,17 @@ impl<'a, CustomSubcommand: clap::Subcommand> Session<'a, CustomSubcommand> {
239223
pub async fn plan_from_cli(
240224
&mut self,
241225
cwd: Arc<AbsolutePath>,
242-
cli_args: TaskCLIArgs<CustomSubcommand>,
226+
command: BuiltInCommand,
243227
) -> Result<ExecutionPlan, vite_task_plan::Error> {
244-
let plan_request = self
245-
.plan_request_parser
246-
.get_plan_request_from_cli_args(cli_args.parsed, &self.envs, &cwd)
247-
.await
248-
.map_err(|error| {
249-
TaskPlanErrorKind::ParsePlanRequestError {
250-
error,
251-
program: cli_args.original[0].clone(),
252-
args: cli_args.original.iter().skip(1).cloned().collect(),
253-
cwd: Arc::clone(&cwd),
254-
}
255-
.with_empty_call_stack()
256-
})?;
228+
let plan_request = command.into_plan_request(&cwd).map_err(|error| {
229+
TaskPlanErrorKind::ParsePlanRequestError {
230+
error: error.into(),
231+
program: Str::from("vite"),
232+
args: Default::default(),
233+
cwd: Arc::clone(&cwd),
234+
}
235+
.with_empty_call_stack()
236+
})?;
257237
let plan = ExecutionPlan::plan(
258238
plan_request,
259239
&self.workspace_path,

0 commit comments

Comments
 (0)