Skip to content

Commit 6e3aa68

Browse files
branchseerclaude
andauthored
refactor: simplify CLI and Session API by removing generic type parameters (#137)
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> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d4407b3 commit 6e3aa68

File tree

79 files changed

+1431
-908
lines changed

Some content is hidden

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

79 files changed

+1431
-908
lines changed

Cargo.lock

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

crates/vite_task/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ derive_more = { workspace = true, features = ["from"] }
2121
diff-struct = { workspace = true }
2222
fspy = { workspace = true }
2323
futures-util = { workspace = true }
24+
monostate = { workspace = true }
2425
once_cell = { workspace = true }
2526
owo-colors = { workspace = true }
2627
petgraph = { workspace = true }

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: 65 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,49 @@
1-
use std::{ffi::OsStr, sync::Arc};
1+
use std::sync::Arc;
22

3-
use clap::{Parser, Subcommand};
3+
use clap::Parser;
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>,
9+
#[derive(Debug, Clone, clap::Subcommand)]
10+
pub enum CacheSubcommand {
11+
/// Clean up all the cache
12+
Clean,
1413
}
1514

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-
}
15+
/// Arguments for the `run` subcommand.
16+
#[derive(Debug, clap::Args)]
17+
pub struct RunCommand {
18+
/// `packageName#taskName` or `taskName`.
19+
pub task_specifier: TaskSpecifier,
4220

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>),
21+
/// Run tasks found in all packages in the workspace, in topological order based on package dependencies.
22+
#[clap(default_value = "false", short, long)]
23+
pub recursive: bool,
5524

56-
/// subcommands that are not handled by vite task
57-
#[command(flatten)]
58-
NonTask(NonTaskSubcommand),
59-
}
25+
/// Run tasks found in the current package and all its transitive dependencies, in topological order based on package dependencies.
26+
#[clap(default_value = "false", short, long)]
27+
pub transitive: bool,
6028

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-
}
29+
/// Do not run dependencies specified in `dependsOn` fields.
30+
#[clap(default_value = "false", long)]
31+
pub ignore_depends_on: bool,
7432

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),
33+
/// Additional arguments to pass to the tasks
34+
#[clap(trailing_var_arg = true, allow_hyphen_values = true)]
35+
pub additional_args: Vec<Str>,
8336
}
8437

8538
/// vite task CLI subcommands
86-
#[derive(Debug, Subcommand)]
87-
pub(crate) enum BuiltInCommand {
39+
#[derive(Debug, Parser)]
40+
pub enum Command {
8841
/// Run tasks
89-
Run {
90-
/// `packageName#taskName` or `taskName`.
91-
task_specifier: TaskSpecifier,
92-
93-
/// Run tasks found in all packages in the workspace, in topological order based on package dependencies.
94-
#[clap(default_value = "false", short, long)]
95-
recursive: bool,
96-
97-
/// Run tasks found in the current package and all its transitive dependencies, in topological order based on package dependencies.
98-
#[clap(default_value = "false", short, long)]
99-
transitive: bool,
100-
101-
/// Do not run dependencies specified in `dependsOn` fields.
102-
#[clap(default_value = "false", long)]
103-
ignore_depends_on: bool,
104-
105-
/// Additional arguments to pass to the tasks
106-
#[clap(trailing_var_arg = true, allow_hyphen_values = true)]
107-
additional_args: Vec<Str>,
42+
Run(RunCommand),
43+
/// Manage the task cache
44+
Cache {
45+
#[clap(subcommand)]
46+
subcmd: CacheSubcommand,
10847
},
10948
}
11049

@@ -117,50 +56,45 @@ pub enum CLITaskQueryError {
11756
PackageNameSpecifiedWithRecursive { package_name: Str, task_name: Str },
11857
}
11958

120-
impl BuiltInCommand {
121-
/// Convert to `TaskQuery`, or return an error if invalid.
59+
impl RunCommand {
60+
/// Convert to `PlanRequest`, or return an error if invalid.
12261
pub fn into_plan_request(
12362
self,
12463
cwd: &Arc<AbsolutePath>,
12564
) -> Result<PlanRequest, CLITaskQueryError> {
126-
match self {
127-
Self::Run {
128-
task_specifier,
129-
recursive,
130-
transitive,
131-
ignore_depends_on,
132-
additional_args,
133-
} => {
134-
let include_explicit_deps = !ignore_depends_on;
135-
136-
let query_kind = if recursive {
137-
if transitive {
138-
return Err(CLITaskQueryError::RecursiveTransitiveConflict);
139-
}
140-
let task_name = if let Some(package_name) = task_specifier.package_name {
141-
return Err(CLITaskQueryError::PackageNameSpecifiedWithRecursive {
142-
package_name,
143-
task_name: task_specifier.task_name,
144-
});
145-
} else {
146-
task_specifier.task_name
147-
};
148-
TaskQueryKind::Recursive { task_names: [task_name].into() }
149-
} else {
150-
TaskQueryKind::Normal {
151-
task_specifiers: [task_specifier].into(),
152-
cwd: Arc::clone(cwd),
153-
include_topological_deps: transitive,
154-
}
155-
};
156-
Ok(PlanRequest::Query(QueryPlanRequest {
157-
query: vite_task_graph::query::TaskQuery {
158-
kind: query_kind,
159-
include_explicit_deps,
160-
},
161-
plan_options: PlanOptions { extra_args: additional_args.into() },
162-
}))
65+
let RunCommand {
66+
task_specifier,
67+
recursive,
68+
transitive,
69+
ignore_depends_on,
70+
additional_args,
71+
} = self;
72+
73+
let include_explicit_deps = !ignore_depends_on;
74+
75+
let query_kind = if recursive {
76+
if transitive {
77+
return Err(CLITaskQueryError::RecursiveTransitiveConflict);
78+
}
79+
let task_name = if let Some(package_name) = task_specifier.package_name {
80+
return Err(CLITaskQueryError::PackageNameSpecifiedWithRecursive {
81+
package_name,
82+
task_name: task_specifier.task_name,
83+
});
84+
} else {
85+
task_specifier.task_name
86+
};
87+
TaskQueryKind::Recursive { task_names: [task_name].into() }
88+
} else {
89+
TaskQueryKind::Normal {
90+
task_specifiers: [task_specifier].into(),
91+
cwd: Arc::clone(cwd),
92+
include_topological_deps: transitive,
16393
}
164-
}
94+
};
95+
Ok(PlanRequest::Query(QueryPlanRequest {
96+
query: vite_task_graph::query::TaskQuery { kind: query_kind, include_explicit_deps },
97+
plan_options: PlanOptions { extra_args: additional_args.into() },
98+
}))
16599
}
166100
}

crates/vite_task/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ mod maybe_str;
44
pub mod session;
55

66
// Public exports for vite_task_bin
7-
pub use cli::CLIArgs;
8-
pub use session::{LabeledReporter, Reporter, Session, SessionCallbacks, TaskSynthesizer};
7+
pub use cli::{CacheSubcommand, Command, RunCommand};
8+
pub use session::{CommandHandler, ExitStatus, HandledCommand, Session, SessionCallbacks};
99
pub use vite_task_graph::{
1010
config::{
1111
self,
1212
user::{EnabledCacheConfig, UserCacheConfig, UserTaskConfig, UserTaskOptions},
1313
},
1414
loader,
1515
};
16-
/// get_path_env is useful for TaskSynthesizer implementations. Re-export it here.
16+
/// Re-exports useful for CommandHandler implementations.
1717
pub use vite_task_plan::get_path_env;
18-
pub use vite_task_plan::plan_request;
18+
pub use vite_task_plan::{plan_request, plan_request::ScriptCommand};

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,16 @@ impl ExecutionCache {
9292
"CREATE TABLE execution_key_to_fingerprint (key BLOB PRIMARY KEY, value BLOB);",
9393
(),
9494
)?;
95-
conn.execute("PRAGMA user_version = 4", ())?;
95+
conn.execute("PRAGMA user_version = 6", ())?;
9696
}
97-
1..=3 => {
97+
1..=5 => {
9898
// old internal db version. reset
9999
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, true)?;
100100
conn.execute("VACUUM", ())?;
101101
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, false)?;
102102
}
103-
4 => break, // current version
104-
5.. => {
103+
6 => break, // current version
104+
6.. => {
105105
return Err(anyhow::anyhow!("Unrecognized database version: {}", user_version));
106106
}
107107
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,14 +348,14 @@ 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.
355355
///
356356
/// The return type isn't just ExitStatus because we want to distinguish between normal successful execution,
357357
/// and execution that failed and needs to exit with a specific code which can be zero.
358-
pub async fn execute(
358+
pub(crate) async fn execute(
359359
&self,
360360
plan: ExecutionPlan,
361361
mut reporter: Box<dyn Reporter>,

0 commit comments

Comments
 (0)