Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,19 @@ Test fixtures and snapshots:

```bash
# Run a task defined in vite-task.json
vp run <task> # run task in current package
vp run <package>#<task> # run task in specific package
vp run <task> -r # run task in all packages (recursive)
vp run <task> -t # run task in current package + transitive deps
vp run <task> -- --extra --args # pass extra args to the task command
vp run # interactive task selector (fuzzy search)
vt run <task> # run task in current package
vt run <package>#<task> # run task in specific package
vt run <task> -r # run task in all packages (recursive)
vt run <task> -t # run task in current package + transitive deps
vt run <task> -- --extra --args # pass extra args to the task command
vt run # interactive task selector (fuzzy search)

# Built-in commands (run tools from node_modules/.bin)
vp test [args...] # run vitest
vp lint [args...] # run oxlint
vt test [args...] # run vitest
vt lint [args...] # run oxlint

# Cache management
vp cache clean # remove the cache database
vt cache clean # remove the cache database

# Package selection flags
-r, --recursive # select all packages in the workspace
Expand All @@ -113,7 +113,7 @@ vp cache clean # remove the cache database
## Key Architecture

- **vite_task** - Main task runner with caching and session management
- **vite_task_bin** - CLI binary (`vp` command) and task synthesizer
- **vite_task_bin** - CLI binary (`vt` command) and task synthesizer
- **vite_task_graph** - Task dependency graph construction and config loading
- **vite_task_plan** - Execution planning (resolves env vars, working dirs, commands)
- **vite_workspace** - Workspace detection and package dependency graph
Expand Down
2 changes: 1 addition & 1 deletion crates/vite_task/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod session;

// Public exports for vite_task_bin
pub use cli::{CacheSubcommand, Command, RunCommand, RunFlags};
pub use session::{CommandHandler, ExitStatus, HandledCommand, Session, SessionCallbacks};
pub use session::{CommandHandler, ExitStatus, HandledCommand, Session, SessionConfig};
pub use vite_task_graph::{
config::{
self,
Expand Down
25 changes: 13 additions & 12 deletions crates/vite_task/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ impl TaskGraphLoader for LazyTaskGraph<'_> {
}
}

pub struct SessionCallbacks<'a> {
pub struct SessionConfig<'a> {
pub command_handler: &'a mut (dyn CommandHandler + 'a),
pub user_config_loader: &'a mut (dyn UserConfigLoader + 'a),
pub program_name: Str,
}

/// The result of a [`CommandHandler::handle_command`] call.
Expand Down Expand Up @@ -162,8 +163,10 @@ pub struct Session<'a> {

plan_request_parser: PlanRequestParser<'a>,

program_name: Str,

/// Cache is lazily initialized to avoid `SQLite` race conditions when multiple
/// processes (e.g., parallel `vp lib` commands) start simultaneously.
/// processes (e.g., parallel `vt lib` commands) start simultaneously.
cache: OnceCell<ExecutionCache>,
cache_path: AbsolutePathBuf,
}
Expand All @@ -185,11 +188,11 @@ impl<'a> Session<'a> {
/// Returns an error if the current directory cannot be determined or
/// if workspace initialization fails.
#[tracing::instrument(level = "debug", skip_all)]
pub fn init(callbacks: SessionCallbacks<'a>) -> anyhow::Result<Self> {
pub fn init(config: SessionConfig<'a>) -> anyhow::Result<Self> {
let envs = std::env::vars_os()
.map(|(k, v)| (Arc::<OsStr>::from(k.as_os_str()), Arc::<OsStr>::from(v.as_os_str())))
.collect();
Self::init_with(envs, vite_path::current_dir()?.into(), callbacks)
Self::init_with(envs, vite_path::current_dir()?.into(), config)
}

/// Ensures the task graph is loaded, loading it if necessary.
Expand All @@ -214,14 +217,10 @@ impl<'a> Session<'a> {
///
/// Returns an error if workspace root cannot be found or PATH env cannot be prepended.
#[tracing::instrument(level = "debug", skip_all)]
#[expect(
clippy::needless_pass_by_value,
reason = "cwd is an Arc that gets cloned internally, pass by value is intentional"
)]
pub fn init_with(
mut envs: FxHashMap<Arc<OsStr>, Arc<OsStr>>,
cwd: Arc<AbsolutePath>,
callbacks: SessionCallbacks<'a>,
config: SessionConfig<'a>,
) -> anyhow::Result<Self> {
let (workspace_root, _) = find_workspace_root(&cwd)?;
let cache_path = get_cache_path_of_workspace(&workspace_root.path);
Expand All @@ -235,11 +234,12 @@ impl<'a> Session<'a> {
workspace_path: Arc::clone(&workspace_root.path),
lazy_task_graph: LazyTaskGraph::Uninitialized {
workspace_root,
config_loader: callbacks.user_config_loader,
config_loader: config.user_config_loader,
},
envs: Arc::new(envs),
cwd,
plan_request_parser: PlanRequestParser { command_handler: callbacks.command_handler },
plan_request_parser: PlanRequestParser { command_handler: config.command_handler },
program_name: config.program_name,
cache: OnceCell::new(),
cache_path,
})
Expand Down Expand Up @@ -317,6 +317,7 @@ impl<'a> Session<'a> {
Box::new(tokio::io::stdout()),
run_command.flags.verbose,
Some(self.make_summary_writer()),
self.program_name.clone(),
);
self.execute_graph(graph, Box::new(builder)).await.map_err(SessionError::EarlyExit)
}
Expand Down Expand Up @@ -688,7 +689,7 @@ impl<'a> Session<'a> {
Err(error) => {
return Err(vite_task_plan::Error::ParsePlanRequest {
error: error.into(),
program: Str::from("vp"),
program: self.program_name.clone(),
args: Arc::default(),
cwd: Arc::clone(&cwd),
});
Expand Down
10 changes: 8 additions & 2 deletions crates/vite_task/src/session/reporter/labeled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub struct LabeledReporterBuilder {
/// Callback to persist the summary (e.g., write `last-summary.json`).
/// `None` when persistence is not needed (e.g., nested script execution, tests).
write_summary: Option<WriteSummaryFn>,
program_name: Str,
}

impl LabeledReporterBuilder {
Expand All @@ -68,13 +69,15 @@ impl LabeledReporterBuilder {
/// - `writer`: Async writer for reporter display output.
/// - `show_details`: Whether to render the full detailed summary.
/// - `write_summary`: Callback to persist the summary, or `None` to skip.
/// - `program_name`: The CLI binary name (e.g. `"vt"`) used in summary output.
pub fn new(
workspace_path: Arc<AbsolutePath>,
writer: Box<dyn AsyncWrite + Unpin>,
show_details: bool,
write_summary: Option<WriteSummaryFn>,
program_name: Str,
) -> Self {
Self { workspace_path, writer, show_details, write_summary }
Self { workspace_path, writer, show_details, write_summary, program_name }
}
}

Expand All @@ -87,6 +90,7 @@ impl GraphExecutionReporterBuilder for LabeledReporterBuilder {
workspace_path: self.workspace_path,
show_details: self.show_details,
write_summary: self.write_summary,
program_name: self.program_name,
})
}
}
Expand All @@ -101,6 +105,7 @@ pub struct LabeledGraphReporter {
workspace_path: Arc<AbsolutePath>,
show_details: bool,
write_summary: Option<WriteSummaryFn>,
program_name: Str,
}

#[async_trait::async_trait(?Send)]
Expand Down Expand Up @@ -177,7 +182,7 @@ impl GraphExecutionReporter for LabeledGraphReporter {
let summary_buf = if self.show_details {
format_full_summary(&summary)
} else {
format_compact_summary(&summary)
format_compact_summary(&summary, &self.program_name)
};

// Persist summary via callback (best-effort, callback handles errors).
Expand Down Expand Up @@ -331,6 +336,7 @@ mod tests {
Box::new(tokio::io::sink()),
false,
None,
Str::from("vt"),
));
let mut reporter = builder.build();
reporter.new_leaf_execution(display, leaf_kind, all_ancestors_single_node)
Expand Down
10 changes: 6 additions & 4 deletions crates/vite_task/src/session/reporter/summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ pub fn format_full_summary(summary: &LastRunSummary) -> Vec<u8> {
/// - Single task + cache hit → thin line + "vp run: cache hit, {duration} saved."
/// - Multi-task → thin line + "vp run: {hits}/{total} cache hit ({rate}%), {duration} saved."
/// with optional failure count and `--verbose` hint.
pub fn format_compact_summary(summary: &LastRunSummary) -> Vec<u8> {
pub fn format_compact_summary(summary: &LastRunSummary, program_name: &str) -> Vec<u8> {
let stats = SummaryStats::compute(&summary.tasks);

let is_single_task = summary.tasks.len() == 1;
Expand All @@ -681,13 +681,14 @@ pub fn format_compact_summary(summary: &LastRunSummary) -> Vec<u8> {
// Thin line separator
let _ = writeln!(buf, "{}", "---".style(Style::new().bright_black()));

let run_label = vite_str::format!("{program_name} run:");
if is_single_task {
// Single task cache hit
let formatted_total_saved = format_summary_duration(stats.total_saved);
let _ = writeln!(
buf,
"{} cache hit, {} saved.",
"vp run:".style(Style::new().blue().bold()),
run_label.as_str().style(Style::new().blue().bold()),
formatted_total_saved.style(Style::new().green().bold()),
);
} else {
Expand All @@ -709,7 +710,7 @@ pub fn format_compact_summary(summary: &LastRunSummary) -> Vec<u8> {
let _ = write!(
buf,
"{} {hits}/{total} cache hit ({rate}%)",
"vp run:".style(Style::new().blue().bold()),
run_label.as_str().style(Style::new().blue().bold()),
);

if stats.total_saved > Duration::ZERO {
Expand All @@ -726,8 +727,9 @@ pub fn format_compact_summary(summary: &LastRunSummary) -> Vec<u8> {
let _ = write!(buf, ", {} failed", n.style(Style::new().red()));
}

let last_details_cmd = vite_str::format!("`{program_name} run --last-details`");
let _ = write!(buf, ". {}", "(Run ".style(Style::new().bright_black()));
let _ = write!(buf, "{}", "`vp run --last-details`".style(COMMAND_STYLE));
let _ = write!(buf, "{}", last_details_cmd.as_str().style(COMMAND_STYLE));
let _ = write!(buf, "{}", " for full details)".style(Style::new().bright_black()));
let _ = writeln!(buf);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/vite_task_bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license.workspace = true
rust-version.workspace = true

[[bin]]
name = "vp"
name = "vt"
path = "src/main.rs"

[dependencies]
Expand Down
19 changes: 10 additions & 9 deletions crates/vite_task_bin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use rustc_hash::FxHashMap;
use vite_path::AbsolutePath;
use vite_str::Str;
use vite_task::{
Command, EnabledCacheConfig, HandledCommand, ScriptCommand, SessionCallbacks, UserCacheConfig,
Command, EnabledCacheConfig, HandledCommand, ScriptCommand, SessionConfig, UserCacheConfig,
get_path_env, plan_request::SyntheticPlanRequest,
};

Expand Down Expand Up @@ -71,7 +71,7 @@ fn synthesize_node_modules_bin_task(
}

#[derive(Debug, Parser)]
#[command(name = "vp", version)]
#[command(name = "vt", version)]
pub enum Args {
Lint {
#[clap(trailing_var_arg = true, allow_hyphen_values = true)]
Expand All @@ -96,10 +96,10 @@ impl vite_task::CommandHandler for CommandHandler {
command: &mut ScriptCommand,
) -> anyhow::Result<HandledCommand> {
match command.program.as_str() {
"vp" => {}
// `vpr <args>` is shorthand for `vp run <args>`
"vt" | "vp" => {}
// `vpr <args>` is shorthand for `vt run <args>`
"vpr" => {
command.program = Str::from("vp");
command.program = Str::from("vt");
command.args =
iter::once(Str::from("run")).chain(command.args.iter().cloned()).collect();
}
Expand Down Expand Up @@ -171,16 +171,17 @@ impl vite_task::loader::UserConfigLoader for JsonUserConfigLoader {
}

#[derive(Default)]
pub struct OwnedSessionCallbacks {
pub struct OwnedSessionConfig {
command_handler: CommandHandler,
user_config_loader: JsonUserConfigLoader,
}

impl OwnedSessionCallbacks {
pub fn as_callbacks(&mut self) -> SessionCallbacks<'_> {
SessionCallbacks {
impl OwnedSessionConfig {
pub fn as_config(&mut self) -> SessionConfig<'_> {
SessionConfig {
command_handler: &mut self.command_handler,
user_config_loader: &mut self.user_config_loader,
program_name: Str::from("vt"),
}
}
}
6 changes: 3 additions & 3 deletions crates/vite_task_bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use vite_task::{
EnabledCacheConfig, ExitStatus, Session, UserCacheConfig, get_path_env,
plan_request::SyntheticPlanRequest,
};
use vite_task_bin::{Args, OwnedSessionCallbacks, find_executable};
use vite_task_bin::{Args, OwnedSessionConfig, find_executable};

#[tokio::main]
async fn main() -> anyhow::Result<ExitCode> {
Expand All @@ -18,8 +18,8 @@ async fn main() -> anyhow::Result<ExitCode> {
#[expect(clippy::future_not_send, reason = "Session contains !Send types; single-threaded runtime")]
async fn run() -> anyhow::Result<ExitStatus> {
let args = Args::parse();
let mut owned_callbacks = OwnedSessionCallbacks::default();
let session = Session::init(owned_callbacks.as_callbacks())?;
let mut owned_config = OwnedSessionConfig::default();
let session = Session::init(owned_config.as_config())?;
match args {
Args::Task(parsed) => session.main(parsed).await,
args => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
[[e2e]]
name = "associate existing cache"
steps = [
"vp run script1 # cache miss",
"vp run script2 # cache hit, same command as script1",
"vt run script1 # cache miss",
"vt run script2 # cache hit, same command as script1",
"json-edit package.json '_.scripts.script2 = \"print world\"' # change script2",
"vp run script2 # cache miss",
"vt run script2 # cache miss",
]
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
source: crates/vite_task_bin/tests/e2e_snapshots/main.rs
expression: e2e_outputs
---
> vp run script1 # cache miss
> vt run script1 # cache miss
$ print hello
hello
> vp run script2 # cache hit, same command as script1
> vt run script2 # cache hit, same command as script1
$ print hello ✓ cache hit, replaying
hello

---
vp run: cache hit, <duration> saved.
vt run: cache hit, <duration> saved.
> json-edit package.json '_.scripts.script2 = "print world"' # change script2

> vp run script2 # cache miss
> vt run script2 # cache miss
$ print world ✗ cache miss: args changed, executing
world
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"scripts": {
"lint": "vp lint"
"lint": "vt lint"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
[[e2e]]
name = "builtin different cwd"
steps = [
"cd folder1 && vp run lint # cache miss in folder1",
"cd folder2 && vp run lint # cache miss in folder2",
"cd folder1 && vt run lint # cache miss in folder1",
"cd folder2 && vt run lint # cache miss in folder2",
"echo 'console.log(1);' > folder2/a.js # modify folder2",
"cd folder1 && vp run lint # cache hit",
"cd folder2 && vp run lint # cache miss",
"cd folder1 && vt run lint # cache hit",
"cd folder2 && vt run lint # cache miss",
]
Loading
Loading