From a86bc9db13a1e907d63a6ce4ea0066b3d89682e2 Mon Sep 17 00:00:00 2001 From: branchseer Date: Wed, 17 Dec 2025 18:40:15 +0800 Subject: [PATCH 01/10] feat: intergrate vite_task_plan and vite_task_graph --- Cargo.lock | 3 ++ Cargo.toml | 1 + crates/vite_task/Cargo.toml | 3 ++ crates/vite_task/src/cli/mod.rs | 45 ++++++++++++++++++++++++++++ crates/vite_task/src/lib.rs | 1 + crates/vite_task_graph/src/lib.rs | 2 +- crates/vite_task_plan/src/context.rs | 19 ++++++++---- crates/vite_task_plan/src/lib.rs | 11 ++----- crates/vite_task_plan/src/plan.rs | 3 +- 9 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 crates/vite_task/src/cli/mod.rs diff --git a/Cargo.lock b/Cargo.lock index c4fb365b..68653736 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3166,6 +3166,7 @@ dependencies = [ "anyhow", "bincode", "bstr", + "clap", "compact_str 0.9.0", "dashmap", "diff-struct", @@ -3193,6 +3194,8 @@ dependencies = [ "vite_path", "vite_shell", "vite_str", + "vite_task_graph", + "vite_task_plan", "vite_workspace", "wax", ] diff --git a/Cargo.toml b/Cargo.toml index f0b19e44..133e52fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,6 +122,7 @@ vite_path = { path = "crates/vite_path" } vite_shell = { path = "crates/vite_shell" } vite_str = { path = "crates/vite_str" } vite_task_graph = { path = "crates/vite_task_graph" } +vite_task_plan = { path = "crates/vite_task_plan" } vite_workspace = { path = "crates/vite_workspace" } wax = "0.6.0" which = "8.0.0" diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml index 60396af1..08e8e590 100644 --- a/crates/vite_task/Cargo.toml +++ b/crates/vite_task/Cargo.toml @@ -15,6 +15,7 @@ workspace = true anyhow = { workspace = true } bincode = { workspace = true, features = ["derive"] } bstr = { workspace = true } +clap = { workspace = true, features = ["derive"] } compact_str = { workspace = true, features = ["serde"] } dashmap = { workspace = true } diff-struct = { workspace = true } @@ -41,6 +42,8 @@ vite_glob = { workspace = true } vite_path = { workspace = true } vite_shell = { workspace = true } vite_str = { workspace = true } +vite_task_graph = { workspace = true } +vite_task_plan = { workspace = true } vite_workspace = { workspace = true } wax = { workspace = true } diff --git a/crates/vite_task/src/cli/mod.rs b/crates/vite_task/src/cli/mod.rs new file mode 100644 index 00000000..3b448191 --- /dev/null +++ b/crates/vite_task/src/cli/mod.rs @@ -0,0 +1,45 @@ +use clap::{Parser, Subcommand}; +use vite_str::Str; +use vite_task_graph::TaskSpecifier; + +/// The CLI arguments for vite task, with customizable subcommands. +#[derive(Debug, clap::Parser)] +pub struct CLIArgs { + inner: CLIArgsInner, +} + +#[derive(Debug, clap::Parser)] +pub enum CLIArgsInner { + /// subcommands provided by vite task + #[clap(flatten)] + ViteTask(ViteTaskSubCommand), + + /// custom subcommands provided by vite+ + #[clap(flatten)] + Custom(CustomSubCommand), +} + +/// vite task CLI subcommands +#[derive(Debug, Parser)] +pub enum ViteTaskSubCommand { + Run { + /// `packageName#taskName` or `taskName`. + task_specifier: TaskSpecifier, + + /// Run tasks found in all packages in the workspace, in topological order based on package dependencies. + #[clap(default_value = "false", short, long)] + recursive: bool, + + /// Run tasks found in the current package and all its transitive dependencies, in topological order based on package dependencies. + #[clap(default_value = "false", short, long)] + transitive: bool, + + /// Do not run dependencies specified in `dependsOn` fields. + #[clap(default_value = "false", long)] + ignore_depends_on: bool, + + /// Additional arguments to pass to the tasks + #[clap(trailing_var_arg = true)] + args: Vec, + }, +} diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 67a0fa25..2f9735ca 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -1,4 +1,5 @@ mod cache; +mod cli; mod collections; mod config; mod error; diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index a159cd3f..59b1631f 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -18,7 +18,7 @@ use petgraph::{ visit::{Control, DfsEvent, depth_first_search}, }; use serde::Serialize; -use specifier::TaskSpecifier; +pub use specifier::TaskSpecifier; use vec1::smallvec_v1::SmallVec1; use vite_path::AbsolutePath; use vite_str::Str; diff --git a/crates/vite_task_plan/src/context.rs b/crates/vite_task_plan/src/context.rs index a15462bc..14df835a 100644 --- a/crates/vite_task_plan/src/context.rs +++ b/crates/vite_task_plan/src/context.rs @@ -20,18 +20,18 @@ pub struct TaskRecursionError { #[derive(Debug)] pub struct PlanContext<'a> { /// The current working directory. - pub cwd: Arc, + cwd: Arc, /// The environment variables for the current execution context. - pub envs: HashMap, Arc>, + envs: HashMap, Arc>, /// The callbacks for loading task graphs and parsing commands. - pub callbacks: &'a mut (dyn PlanCallbacks + 'a), + callbacks: &'a mut (dyn PlanCallbacks + 'a), /// The current call stack of task index nodes being planned. - pub task_call_stack: Vec<(TaskNodeIndex, Range)>, + task_call_stack: Vec<(TaskNodeIndex, Range)>, - pub indexed_task_graph: &'a IndexedTaskGraph, + indexed_task_graph: &'a IndexedTaskGraph, } /// A human-readable frame in the task call stack. @@ -69,6 +69,15 @@ impl Display for TaskCallStackDisplay { } impl<'a> PlanContext<'a> { + pub fn new( + cwd: Arc, + envs: HashMap, Arc>, + callbacks: &'a mut (dyn PlanCallbacks + 'a), + indexed_task_graph: &'a IndexedTaskGraph, + ) -> Self { + Self { cwd, envs, callbacks, task_call_stack: Vec::new(), indexed_task_graph } + } + pub fn cwd(&self) -> &Arc { &self.cwd } diff --git a/crates/vite_task_plan/src/lib.rs b/crates/vite_task_plan/src/lib.rs index 9247b55f..14a48d2b 100644 --- a/crates/vite_task_plan/src/lib.rs +++ b/crates/vite_task_plan/src/lib.rs @@ -118,6 +118,7 @@ pub trait PlanCallbacks: Debug { &self, program: &str, args: &[Str], + cwd: &AbsolutePath, ) -> BoxFuture<'_, anyhow::Result>>; } @@ -149,14 +150,8 @@ impl ExecutionPlan { .map_err(|load_error| TaskPlanErrorKind::TaskGraphLoadError(load_error)) .with_empty_call_stack()?; - let context = PlanContext { - cwd: Arc::clone(cwd), - envs: envs.clone(), - callbacks, - task_call_stack: Vec::new(), - indexed_task_graph: &indexed_task_graph, - }; - + let context = + PlanContext::new(Arc::clone(cwd), envs.clone(), callbacks, &indexed_task_graph); let execution_graph = plan_query_request(query_plan_request, context).await?; ExecutionItemKind::Expanded(execution_graph) } diff --git a/crates/vite_task_plan/src/plan.rs b/crates/vite_task_plan/src/plan.rs index 5c6bf44f..c9e618f4 100644 --- a/crates/vite_task_plan/src/plan.rs +++ b/crates/vite_task_plan/src/plan.rs @@ -70,9 +70,10 @@ async fn plan_task_as_execution_node( } // Try to parse the args of an and_item to a task request like `run -r build` + let cwd = Arc::clone(context.cwd()); let task_request = context .callbacks() - .get_plan_request(&and_item.program, &and_item.args) + .get_plan_request(&and_item.program, &and_item.args, &cwd) .await .map_err(|error| TaskPlanErrorKind::ParsePlanRequestError { error }) .with_plan_context(&context)?; From 1cff667b4f1d3afd3208f0c91716cee1fc493f93 Mon Sep 17 00:00:00 2001 From: branchseer Date: Wed, 17 Dec 2025 21:00:12 +0800 Subject: [PATCH 02/10] update --- Cargo.lock | 9 ---- crates/vite_task/src/bin/vite.rs | 13 +++++ crates/vite_task/src/cli/mod.rs | 84 ++++++++++++++++++++++++++------ crates/vite_task/src/lib.rs | 2 + crates/vite_task/src/session.rs | 14 ++++++ crates/vite_task_bin/Cargo.toml | 19 -------- crates/vite_task_bin/src/vite.rs | 19 -------- 7 files changed, 99 insertions(+), 61 deletions(-) create mode 100644 crates/vite_task/src/bin/vite.rs create mode 100644 crates/vite_task/src/session.rs delete mode 100644 crates/vite_task_bin/Cargo.toml delete mode 100644 crates/vite_task_bin/src/vite.rs diff --git a/Cargo.lock b/Cargo.lock index 68653736..0acfe3f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3200,15 +3200,6 @@ dependencies = [ "wax", ] -[[package]] -name = "vite_task_bin" -version = "0.1.0" -dependencies = [ - "clap", - "vite_str", - "vite_task_graph", -] - [[package]] name = "vite_task_graph" version = "0.1.0" diff --git a/crates/vite_task/src/bin/vite.rs b/crates/vite_task/src/bin/vite.rs new file mode 100644 index 00000000..42acd1ac --- /dev/null +++ b/crates/vite_task/src/bin/vite.rs @@ -0,0 +1,13 @@ +use clap::Parser; +use vite_str::Str; +use vite_task::CLIArgs; + +#[derive(Debug, Parser)] +enum CustomTaskSubCommand { + /// oxlint + Lint { args: Vec }, +} + +fn main() { + let _subcommand = CLIArgs::::parse(); +} diff --git a/crates/vite_task/src/cli/mod.rs b/crates/vite_task/src/cli/mod.rs index 3b448191..62b5736f 100644 --- a/crates/vite_task/src/cli/mod.rs +++ b/crates/vite_task/src/cli/mod.rs @@ -1,27 +1,26 @@ -use clap::{Parser, Subcommand}; -use vite_str::Str; -use vite_task_graph::TaskSpecifier; +use std::sync::Arc; -/// The CLI arguments for vite task, with customizable subcommands. -#[derive(Debug, clap::Parser)] -pub struct CLIArgs { - inner: CLIArgsInner, -} +use clap::Subcommand; +use vite_path::AbsolutePath; +use vite_str::Str; +use vite_task_graph::{TaskSpecifier, query::TaskQueryKind}; +use vite_task_plan::plan_request::{PlanOptions, PlanRequest, QueryPlanRequest}; #[derive(Debug, clap::Parser)] -pub enum CLIArgsInner { +pub enum CLIArgs { /// subcommands provided by vite task - #[clap(flatten)] - ViteTask(ViteTaskSubCommand), + #[command(flatten)] + ViteTaskSubCommand(ViteTaskSubCommand), /// custom subcommands provided by vite+ - #[clap(flatten)] + #[command(flatten)] Custom(CustomSubCommand), } /// vite task CLI subcommands -#[derive(Debug, Parser)] +#[derive(Debug, Subcommand)] pub enum ViteTaskSubCommand { + /// Run tasks Run { /// `packageName#taskName` or `taskName`. task_specifier: TaskSpecifier, @@ -40,6 +39,63 @@ pub enum ViteTaskSubCommand { /// Additional arguments to pass to the tasks #[clap(trailing_var_arg = true)] - args: Vec, + additional_args: Vec, }, } + +#[derive(thiserror::Error, Debug)] +pub enum CLITaskQueryError { + #[error("--recursive and --transitive cannot be used together")] + RecursiveTransitiveConflict, + + #[error("cannot specify package '{package_name}' for task '{task_name}' with --recursive")] + PackageNameSpecifiedWithRecursive { package_name: Str, task_name: Str }, +} + +impl ViteTaskSubCommand { + /// Convert to `TaskQuery`, or return an error if invalid. + pub fn into_plan_request( + self, + cwd: &Arc, + ) -> Result { + match self { + Self::Run { + task_specifier, + recursive, + transitive, + ignore_depends_on, + additional_args, + } => { + let include_explicit_deps = !ignore_depends_on; + + let query_kind = if recursive { + if transitive { + return Err(CLITaskQueryError::RecursiveTransitiveConflict); + } + let task_name = if let Some(package_name) = task_specifier.package_name { + return Err(CLITaskQueryError::PackageNameSpecifiedWithRecursive { + package_name, + task_name: task_specifier.task_name, + }); + } else { + task_specifier.task_name + }; + TaskQueryKind::Recursive { task_names: [task_name].into() } + } else { + TaskQueryKind::Normal { + task_specifiers: [task_specifier].into(), + cwd: Arc::clone(cwd), + include_topological_deps: transitive, + } + }; + Ok(PlanRequest::Query(QueryPlanRequest { + query: vite_task_graph::query::TaskQuery { + kind: query_kind, + include_explicit_deps, + }, + plan_options: PlanOptions { extra_args: additional_args.into() }, + })) + } + } + } +} diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 2f9735ca..a58658ba 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -8,11 +8,13 @@ mod fingerprint; mod fs; mod maybe_str; mod schedule; +mod session; mod types; mod ui; // Public exports for vite-plus-cli to use pub use cache::TaskCache; +pub use cli::CLIArgs; pub use config::{ResolvedTask, Workspace}; pub use error::Error; pub use execute::{CURRENT_EXECUTION_ID, EXECUTION_SUMMARY_DIR}; diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs new file mode 100644 index 00000000..67e3c9bd --- /dev/null +++ b/crates/vite_task/src/session.rs @@ -0,0 +1,14 @@ +// use std::sync::Arc; + +// use vite_path::AbsolutePath; +// use vite_task_graph::IndexedTaskGraph; + +// pub struct Session {} + +// impl Session { +// pub fn init(cwd: &Arc) -> Self { +// // Initialize session state here +// // IndexedTaskGraph::load(workspace_root, config_loader) +// Session {} +// } +// } diff --git a/crates/vite_task_bin/Cargo.toml b/crates/vite_task_bin/Cargo.toml deleted file mode 100644 index 58b13455..00000000 --- a/crates/vite_task_bin/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "vite_task_bin" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true - -[[bin]] -name = "vite" -path = "src/vite.rs" - -[dependencies] -clap = { workspace = true, features = ["derive"] } -vite_str = { workspace = true } -vite_task_graph = { path = "../vite_task_graph" } - -[lints] -workspace = true diff --git a/crates/vite_task_bin/src/vite.rs b/crates/vite_task_bin/src/vite.rs deleted file mode 100644 index 9b675bfa..00000000 --- a/crates/vite_task_bin/src/vite.rs +++ /dev/null @@ -1,19 +0,0 @@ -use clap::Parser; -use vite_str::Str; - -#[derive(Parser)] -enum SubCommand { - /// Run tasks - Run { - #[clap(flatten)] - query: vite_task_graph::query::cli::CLITaskQuery, - - /// Additional arguments to pass to the tasks - #[clap(last = true)] - args: Vec, - }, -} - -fn main() { - let _subcommand = SubCommand::parse(); -} From ebfafe1bb27c4f563308f77d9d5cb29686b67efa Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 18 Dec 2025 07:43:42 +0800 Subject: [PATCH 03/10] add workspace .bin path --- crates/vite_task/src/session.rs | 121 ++++++++++++++++++++-- crates/vite_task_graph/src/lib.rs | 6 +- crates/vite_task_graph/tests/snapshots.rs | 4 +- crates/vite_task_plan/src/context.rs | 8 +- crates/vite_task_plan/src/error.rs | 4 - crates/vite_task_plan/src/lib.rs | 47 ++++++--- crates/vite_task_plan/src/plan.rs | 22 ++-- 7 files changed, 161 insertions(+), 51 deletions(-) diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs index 67e3c9bd..afb8d6bb 100644 --- a/crates/vite_task/src/session.rs +++ b/crates/vite_task/src/session.rs @@ -1,14 +1,113 @@ -// use std::sync::Arc; +use std::{ffi::OsStr, sync::Arc}; -// use vite_path::AbsolutePath; -// use vite_task_graph::IndexedTaskGraph; +use futures_core::future::BoxFuture; +use vite_path::AbsolutePath; +use vite_task_graph::{IndexedTaskGraph, TaskGraphLoadError, loader::UserConfigLoader}; +use vite_task_plan::{ExecutionPlan, plan_request::PlanRequest}; +use vite_workspace::{WorkspaceRoot, find_workspace_root}; -// pub struct Session {} +use crate::collections::HashMap; -// impl Session { -// pub fn init(cwd: &Arc) -> Self { -// // Initialize session state here -// // IndexedTaskGraph::load(workspace_root, config_loader) -// Session {} -// } -// } +#[derive(Debug)] +enum LazyTaskGraph { + Uninitialized(WorkspaceRoot), + Initialized(Arc), +} +impl LazyTaskGraph { + async fn get( + &mut self, + config_loader: &(impl UserConfigLoader + '_), + ) -> Result, TaskGraphLoadError> { + Ok(match self { + Self::Uninitialized(workspace_root) => { + let graph = IndexedTaskGraph::load(workspace_root, config_loader).await?; + let graph = Arc::new(graph); + *self = Self::Initialized(Arc::clone(&graph)); + graph + } + Self::Initialized(graph) => Arc::clone(&graph), + }) + } +} + +pub trait SessionCallbacks: UserConfigLoader { + /// Get a plan request for the given program and args in the given cwd. + /// + /// - If it returns `Ok(None)`, the command will be spawned as a normal process. + /// - If it returns `Ok(Some(PlanRequest::Query)`, the command will be expanded as a `ExecutionPlan` with a task graph queried from the returned `TaskQuery`. + /// - If it returns `Ok(Some(PlanRequest::Synthetic)`, the command will become a `ExecutionPlan` with the synthetic task. + fn get_plan_request( + &self, + program: &str, + args: &[vite_str::Str], + cwd: &AbsolutePath, + ) -> BoxFuture<'_, anyhow::Result>>; +} + +pub struct Session { + workspace_path: Arc, + /// A session doesn't necessarily load the task graph immediately. + /// The task graph is loaded on-demand and cached for future use. + lazy_task_graph: LazyTaskGraph, + + envs: HashMap, Arc>, + cwd: Arc, +} + +impl Session { + /// Initialize a session with real environment variables and cwd + pub fn init() -> anyhow::Result { + let envs = std::env::vars_os() + .map(|(k, v)| (Arc::::from(k.as_os_str()), Arc::::from(v.as_os_str()))) + .collect(); + Self::init_with(envs, vite_path::current_dir()?.into()) + } + + /// Initialize a session with custom cwd, environment variables. Useful for testing. + pub fn init_with( + envs: HashMap, Arc>, + cwd: Arc, + ) -> anyhow::Result { + let (workspace_root, _) = find_workspace_root(&cwd)?; + Ok(Self { + workspace_path: Arc::clone(&workspace_root.path), + lazy_task_graph: LazyTaskGraph::Uninitialized(workspace_root), + envs, + cwd, + }) + } + + pub async fn plan( + &mut self, + plan_request: PlanRequest, + ) -> Result { + // ExecutionPlan::plan(plan_request, &self.workspace_path, &self.cwd, &self.envs, todo!()) + // .await + todo!() + } +} + +#[derive(Debug)] +struct PlanCallbacks<'a> { + lazy_task_graph: &'a mut LazyTaskGraph, +} +impl<'a> vite_task_plan::PlanRequestParser for PlanCallbacks<'a> { + // fn load_task_graph( + // &mut self, + // cwd: &AbsolutePath, + // ) -> BoxFuture<'_, Result, TaskGraphLoadError>> { + // Box::pin(async move { + // let config_loader = vite_task_graph::loader::JsonUserConfigLoader::new(cwd); + // self.lazy_task_graph.get(&config_loader).await + // }) + // } + + fn get_plan_request( + &self, + program: &str, + args: &[vite_str::Str], + cwd: &AbsolutePath, + ) -> BoxFuture<'_, anyhow::Result>> { + todo!() + } +} diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 59b1631f..67c65890 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -175,12 +175,12 @@ pub type TaskGraph = DiGraph; impl IndexedTaskGraph { /// Load the task graph from a discovered workspace using the provided config loader. pub async fn load( - workspace_root: WorkspaceRoot, - config_loader: impl loader::UserConfigLoader, + workspace_root: &WorkspaceRoot, + config_loader: &(impl loader::UserConfigLoader + '_), ) -> Result { let mut task_graph = DiGraph::::default(); - let package_graph = vite_workspace::load_package_graph(&workspace_root)?; + let package_graph = vite_workspace::load_package_graph(workspace_root)?; // Record dependency specifiers for each task node to add explicit dependencies later let mut dependency_specifiers_with_task_node_indices: Vec<(Arc<[Str]>, TaskNodeIndex)> = diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index 6f29798f..7a860627 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -181,8 +181,8 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { runtime.block_on(async { let indexed_task_graph = vite_task_graph::IndexedTaskGraph::load( - workspace_root, - JsonUserConfigLoader::default(), + &workspace_root, + &JsonUserConfigLoader::default(), ) .await .expect(&format!("Failed to load task graph for case {case_name}")); diff --git a/crates/vite_task_plan/src/context.rs b/crates/vite_task_plan/src/context.rs index 14df835a..d50803a5 100644 --- a/crates/vite_task_plan/src/context.rs +++ b/crates/vite_task_plan/src/context.rs @@ -5,7 +5,7 @@ use std::{ use vite_path::AbsolutePath; use vite_task_graph::{IndexedTaskGraph, TaskNodeIndex, display::TaskDisplay}; -use crate::{PlanCallbacks, path_env::prepend_path_env}; +use crate::{PlanRequestParser, path_env::prepend_path_env}; #[derive(Debug, thiserror::Error)] #[error( @@ -26,7 +26,7 @@ pub struct PlanContext<'a> { envs: HashMap, Arc>, /// The callbacks for loading task graphs and parsing commands. - callbacks: &'a mut (dyn PlanCallbacks + 'a), + callbacks: &'a mut (dyn PlanRequestParser + 'a), /// The current call stack of task index nodes being planned. task_call_stack: Vec<(TaskNodeIndex, Range)>, @@ -72,7 +72,7 @@ impl<'a> PlanContext<'a> { pub fn new( cwd: Arc, envs: HashMap, Arc>, - callbacks: &'a mut (dyn PlanCallbacks + 'a), + callbacks: &'a mut (dyn PlanRequestParser + 'a), indexed_task_graph: &'a IndexedTaskGraph, ) -> Self { Self { cwd, envs, callbacks, task_call_stack: Vec::new(), indexed_task_graph } @@ -122,7 +122,7 @@ impl<'a> PlanContext<'a> { self.task_call_stack.push((task_node_index, command_span)); } - pub fn callbacks(&mut self) -> &mut (dyn PlanCallbacks + '_) { + pub fn callbacks(&mut self) -> &mut (dyn PlanRequestParser + '_) { self.callbacks } diff --git a/crates/vite_task_plan/src/error.rs b/crates/vite_task_plan/src/error.rs index b656c232..5e3dda4d 100644 --- a/crates/vite_task_plan/src/error.rs +++ b/crates/vite_task_plan/src/error.rs @@ -35,10 +35,6 @@ pub enum TaskPlanErrorKind { #[error("Failed to add node_modules/.bin to PATH environment variable")] AddNodeModulesBinPathError { - /// This error occurred before parse the command of the task, - /// so the task call stack doesn't contain the current task (no command_span yet). - /// This field is where the error occurred, while the task call stack is the stack leading to it.s - task_display: TaskDisplay, #[source] join_paths_error: JoinPathsError, }, diff --git a/crates/vite_task_plan/src/lib.rs b/crates/vite_task_plan/src/lib.rs index 14a48d2b..6920d9c1 100644 --- a/crates/vite_task_plan/src/lib.rs +++ b/crates/vite_task_plan/src/lib.rs @@ -11,7 +11,8 @@ use std::{collections::HashMap, ffi::OsStr, fmt::Debug, ops::Range, sync::Arc}; use context::PlanContext; use envs::ResolvedEnvs; -use error::{Error, TaskPlanErrorKind, TaskPlanErrorKindResultExt}; +use error::TaskPlanErrorKindResultExt; +pub use error::{Error, TaskPlanErrorKind}; use execution_graph::ExecutionGraph; use futures_core::future::BoxFuture; use in_process::InProcessExecution; @@ -21,6 +22,8 @@ use vite_path::AbsolutePath; use vite_str::Str; use vite_task_graph::{TaskGraphLoadError, TaskNodeIndex, query::TaskQuery}; +use crate::path_env::prepend_path_env; + /// Resolved cache configuration for a spawn execution. #[derive(Debug)] pub struct ResolvedCacheConfig { @@ -98,14 +101,9 @@ pub enum ExecutionItemKind { Leaf(LeafExecutionKind), } -/// Callbackes needed during planning. -/// See each method for details. -pub trait PlanCallbacks: Debug { - fn load_task_graph( - &mut self, - cwd: &AbsolutePath, - ) -> BoxFuture<'_, Result, TaskGraphLoadError>>; - +/// The callback trait for parsing plan requests from cli args. +/// See the method for details. +pub trait PlanRequestParser: Debug { /// This is called for every parsable command in the task graph in order to determine how to execute it. /// /// `vite_task_plan` doesn't have the knowledge of how cli args should be parsed. It relies on this callback. @@ -122,6 +120,12 @@ pub trait PlanCallbacks: Debug { ) -> BoxFuture<'_, anyhow::Result>>; } +pub trait TaskGraphLoader { + fn load_task_graph( + &mut self, + ) -> BoxFuture<'_, Result, TaskGraphLoadError>>; +} + #[derive(Debug)] pub struct ExecutionPlan { root_node: ExecutionItemKind, @@ -138,26 +142,39 @@ impl ExecutionPlan { pub async fn plan( plan_request: PlanRequest, + workspace_path: &AbsolutePath, cwd: &Arc, envs: &HashMap, Arc>, - callbacks: &mut (dyn PlanCallbacks + '_), + plan_request_parser: &mut (dyn PlanRequestParser + '_), + task_graph_loader: &mut (dyn TaskGraphLoader + '_), ) -> Result { + let workspace_node_modules_bin = workspace_path.join("node_modules").join(".bin"); + let mut envs = envs.clone(); + prepend_path_env(&mut envs, &workspace_node_modules_bin) + .map_err(|join_paths_error| TaskPlanErrorKind::AddNodeModulesBinPathError { + join_paths_error, + }) + .with_empty_call_stack()?; let root_node = match plan_request { PlanRequest::Query(query_plan_request) => { - let indexed_task_graph = callbacks - .load_task_graph(cwd) + let indexed_task_graph = task_graph_loader + .load_task_graph() .await .map_err(|load_error| TaskPlanErrorKind::TaskGraphLoadError(load_error)) .with_empty_call_stack()?; - let context = - PlanContext::new(Arc::clone(cwd), envs.clone(), callbacks, &indexed_task_graph); + let context = PlanContext::new( + Arc::clone(cwd), + envs.clone(), + plan_request_parser, + &indexed_task_graph, + ); let execution_graph = plan_query_request(query_plan_request, context).await?; ExecutionItemKind::Expanded(execution_graph) } PlanRequest::Synthetic(synthetic_plan_request) => { let execution = - plan_synthetic_request(&Default::default(), synthetic_plan_request, cwd, envs) + plan_synthetic_request(&Default::default(), synthetic_plan_request, cwd, &envs) .with_empty_call_stack()?; ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(execution)) diff --git a/crates/vite_task_plan/src/plan.rs b/crates/vite_task_plan/src/plan.rs index c9e618f4..b2f095df 100644 --- a/crates/vite_task_plan/src/plan.rs +++ b/crates/vite_task_plan/src/plan.rs @@ -30,27 +30,25 @@ async fn plan_task_as_execution_node( .map_err(TaskPlanErrorKind::TaskRecursionDetected) .with_plan_context(&context)?; + let task_node = &context.indexed_task_graph().task_graph()[task_node_index]; + let command_str = task_node.resolved_config.command.as_str(); + // Prepend {package_path}/node_modules/.bin to PATH let package_node_modules_bin_path = context .indexed_task_graph() .get_package_path_for_task(task_node_index) .join("node_modules") .join(".bin"); - context - .prepend_path(&package_node_modules_bin_path) - .map_err(|join_paths_error| TaskPlanErrorKind::AddNodeModulesBinPathError { - task_display: context.indexed_task_graph().display_task(task_node_index), - join_paths_error, - }) - .with_plan_context(&context)?; - - let task_node = &context.indexed_task_graph().task_graph()[task_node_index]; - - // TODO: variable expansion (https://crates.io/crates/shellexpand) BEFORE parsing - let command_str = task_node.resolved_config.command.as_str(); + if let Err(join_paths_error) = context.prepend_path(&package_node_modules_bin_path) { + // Push the current task frame with full command span (the path was added for every and_item of the command) before returning the error + context.push_stack_frame(task_node_index, 0..command_str.len()); + return Err(TaskPlanErrorKind::AddNodeModulesBinPathError { join_paths_error }) + .with_plan_context(&context); + } let mut items = Vec::::new(); + // TODO: variable expansion (https://crates.io/crates/shellexpand) BEFORE parsing // Try to parse the command string as a list of subcommands separated by `&&` if let Some(parsed_subcommands) = try_parse_as_and_list(command_str) { for (and_item, add_item_span) in parsed_subcommands { From 900920ce50231b045135c5071ec60597ee81af6f Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 18 Dec 2025 09:38:03 +0800 Subject: [PATCH 04/10] update --- Cargo.lock | 14 ++++ Cargo.toml | 1 + crates/vite_task/Cargo.toml | 1 + crates/vite_task/src/lib.rs | 1 + crates/vite_task/src/session.rs | 105 +++++++++++---------------- crates/vite_task_graph/Cargo.toml | 1 + crates/vite_task_graph/src/lib.rs | 2 +- crates/vite_task_graph/src/loader.rs | 36 ++++----- crates/vite_task_plan/Cargo.toml | 1 + crates/vite_task_plan/src/error.rs | 2 - crates/vite_task_plan/src/lib.rs | 11 +-- 11 files changed, 89 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0acfe3f3..a8325bac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,17 @@ version = "9.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59051ec02907378a67b0ba1b8631121f5388c8dbbb3cec8c749d8f93c2c3c211" +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -3164,6 +3175,7 @@ name = "vite_task" version = "0.0.0" dependencies = [ "anyhow", + "async-trait", "bincode", "bstr", "clap", @@ -3205,6 +3217,7 @@ name = "vite_task_graph" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "clap", "copy_dir", "insta", @@ -3227,6 +3240,7 @@ name = "vite_task_plan" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "futures-core", "futures-util", "petgraph", diff --git a/Cargo.toml b/Cargo.toml index 133e52fb..814b4921 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ allocator-api2 = { version = "0.2.21", default-features = false, features = ["al anyhow = "1.0.98" assert2 = "0.3.16" assertables = "9.8.1" +async-trait = "0.1.89" base64 = "0.22.1" bincode = "2.0.1" bindgen = "0.72.1" diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml index 08e8e590..ab238d76 100644 --- a/crates/vite_task/Cargo.toml +++ b/crates/vite_task/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } bincode = { workspace = true, features = ["derive"] } bstr = { workspace = true } clap = { workspace = true, features = ["derive"] } diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index a58658ba..88eb2f20 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -19,4 +19,5 @@ pub use config::{ResolvedTask, Workspace}; pub use error::Error; pub use execute::{CURRENT_EXECUTION_ID, EXECUTION_SUMMARY_DIR}; pub use schedule::{ExecutionPlan, ExecutionStatus, ExecutionSummary}; +pub use session::{Session, SessionCallbacks}; pub use types::ResolveCommandResult; diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs index afb8d6bb..25354671 100644 --- a/crates/vite_task/src/session.rs +++ b/crates/vite_task/src/session.rs @@ -1,79 +1,81 @@ use std::{ffi::OsStr, sync::Arc}; -use futures_core::future::BoxFuture; use vite_path::AbsolutePath; use vite_task_graph::{IndexedTaskGraph, TaskGraphLoadError, loader::UserConfigLoader}; -use vite_task_plan::{ExecutionPlan, plan_request::PlanRequest}; +use vite_task_plan::{ + ExecutionPlan, PlanRequestParser, TaskGraphLoader, plan_request::PlanRequest, +}; use vite_workspace::{WorkspaceRoot, find_workspace_root}; use crate::collections::HashMap; #[derive(Debug)] -enum LazyTaskGraph { - Uninitialized(WorkspaceRoot), - Initialized(Arc), +enum LazyTaskGraph<'a> { + Uninitialized { workspace_root: WorkspaceRoot, config_loader: &'a dyn UserConfigLoader }, + Initialized(IndexedTaskGraph), } -impl LazyTaskGraph { - async fn get( + +#[async_trait::async_trait] +impl TaskGraphLoader for LazyTaskGraph<'_> { + async fn load_task_graph( &mut self, - config_loader: &(impl UserConfigLoader + '_), - ) -> Result, TaskGraphLoadError> { + ) -> Result<&vite_task_graph::IndexedTaskGraph, TaskGraphLoadError> { Ok(match self { - Self::Uninitialized(workspace_root) => { - let graph = IndexedTaskGraph::load(workspace_root, config_loader).await?; - let graph = Arc::new(graph); - *self = Self::Initialized(Arc::clone(&graph)); - graph + Self::Uninitialized { workspace_root, config_loader } => { + let graph = IndexedTaskGraph::load(workspace_root, *config_loader).await?; + *self = Self::Initialized(graph); + match self { + Self::Initialized(graph) => &*graph, + _ => unreachable!(), + } } - Self::Initialized(graph) => Arc::clone(&graph), + Self::Initialized(graph) => &*graph, }) } } -pub trait SessionCallbacks: UserConfigLoader { - /// Get a plan request for the given program and args in the given cwd. - /// - /// - If it returns `Ok(None)`, the command will be spawned as a normal process. - /// - If it returns `Ok(Some(PlanRequest::Query)`, the command will be expanded as a `ExecutionPlan` with a task graph queried from the returned `TaskQuery`. - /// - If it returns `Ok(Some(PlanRequest::Synthetic)`, the command will become a `ExecutionPlan` with the synthetic task. - fn get_plan_request( - &self, - program: &str, - args: &[vite_str::Str], - cwd: &AbsolutePath, - ) -> BoxFuture<'_, anyhow::Result>>; +pub struct SessionCallbacks<'a> { + plan_request_parser: &'a mut (dyn PlanRequestParser + 'a), + user_config_loader: &'a mut (dyn UserConfigLoader + 'a), } -pub struct Session { +pub struct Session<'a> { workspace_path: Arc, /// A session doesn't necessarily load the task graph immediately. /// The task graph is loaded on-demand and cached for future use. - lazy_task_graph: LazyTaskGraph, + lazy_task_graph: LazyTaskGraph<'a>, envs: HashMap, Arc>, cwd: Arc, + + plan_request_parser: &'a mut (dyn PlanRequestParser + 'a), } -impl Session { +impl<'a> Session<'a> { /// Initialize a session with real environment variables and cwd - pub fn init() -> anyhow::Result { + pub fn init(callbacks: SessionCallbacks<'a>) -> anyhow::Result { let envs = std::env::vars_os() .map(|(k, v)| (Arc::::from(k.as_os_str()), Arc::::from(v.as_os_str()))) .collect(); - Self::init_with(envs, vite_path::current_dir()?.into()) + Self::init_with(envs, vite_path::current_dir()?.into(), callbacks) } /// Initialize a session with custom cwd, environment variables. Useful for testing. pub fn init_with( envs: HashMap, Arc>, cwd: Arc, + callbacks: SessionCallbacks<'a>, ) -> anyhow::Result { let (workspace_root, _) = find_workspace_root(&cwd)?; Ok(Self { workspace_path: Arc::clone(&workspace_root.path), - lazy_task_graph: LazyTaskGraph::Uninitialized(workspace_root), + lazy_task_graph: LazyTaskGraph::Uninitialized { + workspace_root, + config_loader: callbacks.user_config_loader, + }, envs, cwd, + plan_request_parser: callbacks.plan_request_parser, }) } @@ -81,33 +83,14 @@ impl Session { &mut self, plan_request: PlanRequest, ) -> Result { - // ExecutionPlan::plan(plan_request, &self.workspace_path, &self.cwd, &self.envs, todo!()) - // .await - todo!() - } -} - -#[derive(Debug)] -struct PlanCallbacks<'a> { - lazy_task_graph: &'a mut LazyTaskGraph, -} -impl<'a> vite_task_plan::PlanRequestParser for PlanCallbacks<'a> { - // fn load_task_graph( - // &mut self, - // cwd: &AbsolutePath, - // ) -> BoxFuture<'_, Result, TaskGraphLoadError>> { - // Box::pin(async move { - // let config_loader = vite_task_graph::loader::JsonUserConfigLoader::new(cwd); - // self.lazy_task_graph.get(&config_loader).await - // }) - // } - - fn get_plan_request( - &self, - program: &str, - args: &[vite_str::Str], - cwd: &AbsolutePath, - ) -> BoxFuture<'_, anyhow::Result>> { - todo!() + ExecutionPlan::plan( + plan_request, + &self.workspace_path, + &self.cwd, + &self.envs, + self.plan_request_parser, + &mut self.lazy_task_graph, + ) + .await } } diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml index 15ebfb2a..775af226 100644 --- a/crates/vite_task_graph/Cargo.toml +++ b/crates/vite_task_graph/Cargo.toml @@ -8,6 +8,7 @@ rust-version.workspace = true [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } clap = { workspace = true, features = ["derive"] } monostate = "1.0.2" petgraph = { workspace = true } diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 67c65890..7b8ce4b1 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -176,7 +176,7 @@ impl IndexedTaskGraph { /// Load the task graph from a discovered workspace using the provided config loader. pub async fn load( workspace_root: &WorkspaceRoot, - config_loader: &(impl loader::UserConfigLoader + '_), + config_loader: &dyn loader::UserConfigLoader, ) -> Result { let mut task_graph = DiGraph::::default(); diff --git a/crates/vite_task_graph/src/loader.rs b/crates/vite_task_graph/src/loader.rs index 7a3c932b..99ba1714 100644 --- a/crates/vite_task_graph/src/loader.rs +++ b/crates/vite_task_graph/src/loader.rs @@ -1,13 +1,16 @@ +use std::fmt::Debug; + use vite_path::AbsolutePath; use crate::config::UserConfigFile; /// Loader trait for loading user configuration files (vite.config.*). -pub trait UserConfigLoader { - fn load_user_config_file( +#[async_trait::async_trait] +pub trait UserConfigLoader: Debug + Send + Sync { + async fn load_user_config_file( &self, package_path: &AbsolutePath, - ) -> impl std::future::Future> + Send; + ) -> anyhow::Result; } /// A `UserConfigLoader` implementation that only loads `vite.config.json`. @@ -16,22 +19,21 @@ pub trait UserConfigLoader { #[derive(Default, Debug)] pub struct JsonUserConfigLoader(()); +#[async_trait::async_trait] impl UserConfigLoader for JsonUserConfigLoader { - fn load_user_config_file( + async fn load_user_config_file( &self, package_path: &AbsolutePath, - ) -> impl std::future::Future> + Send { - async move { - let config_path = package_path.join("vite.config.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() }); - } - Err(err) => return Err(err.into()), - }; - let user_config: UserConfigFile = serde_json::from_str(&config_content)?; - Ok(user_config) - } + ) -> anyhow::Result { + let config_path = package_path.join("vite.config.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() }); + } + Err(err) => return Err(err.into()), + }; + let user_config: UserConfigFile = serde_json::from_str(&config_content)?; + Ok(user_config) } } diff --git a/crates/vite_task_plan/Cargo.toml b/crates/vite_task_plan/Cargo.toml index ccbc124b..c5cf51ba 100644 --- a/crates/vite_task_plan/Cargo.toml +++ b/crates/vite_task_plan/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } futures-core = { workspace = true } futures-util = { workspace = true } petgraph = { workspace = true } diff --git a/crates/vite_task_plan/src/error.rs b/crates/vite_task_plan/src/error.rs index 5e3dda4d..4bcb077c 100644 --- a/crates/vite_task_plan/src/error.rs +++ b/crates/vite_task_plan/src/error.rs @@ -1,7 +1,5 @@ use std::env::JoinPathsError; -use vite_task_graph::display::TaskDisplay; - use crate::{ context::{PlanContext, TaskCallStackDisplay, TaskRecursionError}, envs::ResolveEnvError, diff --git a/crates/vite_task_plan/src/lib.rs b/crates/vite_task_plan/src/lib.rs index 6920d9c1..3b92507d 100644 --- a/crates/vite_task_plan/src/lib.rs +++ b/crates/vite_task_plan/src/lib.rs @@ -14,7 +14,6 @@ use envs::ResolvedEnvs; use error::TaskPlanErrorKindResultExt; pub use error::{Error, TaskPlanErrorKind}; use execution_graph::ExecutionGraph; -use futures_core::future::BoxFuture; use in_process::InProcessExecution; use plan::{plan_query_request, plan_synthetic_request}; use plan_request::PlanRequest; @@ -103,6 +102,7 @@ pub enum ExecutionItemKind { /// The callback trait for parsing plan requests from cli args. /// See the method for details. +#[async_trait::async_trait] pub trait PlanRequestParser: Debug { /// This is called for every parsable command in the task graph in order to determine how to execute it. /// @@ -112,18 +112,19 @@ pub trait PlanRequestParser: Debug { /// - If it returns `Ok(None)`, the command will be spawned as a normal process. /// - If it returns `Ok(Some(ParsedArgs::TaskQuery)`, the command will be expanded as a `ExpandedExecution` with a task graph queried from the returned `TaskQuery`. /// - If it returns `Ok(Some(ParsedArgs::Synthetic)`, the command will become a `SpawnExecution` with the synthetic task. - fn get_plan_request( + async fn get_plan_request( &self, program: &str, args: &[Str], cwd: &AbsolutePath, - ) -> BoxFuture<'_, anyhow::Result>>; + ) -> anyhow::Result>; } +#[async_trait::async_trait] pub trait TaskGraphLoader { - fn load_task_graph( + async fn load_task_graph( &mut self, - ) -> BoxFuture<'_, Result, TaskGraphLoadError>>; + ) -> Result<&vite_task_graph::IndexedTaskGraph, TaskGraphLoadError>; } #[derive(Debug)] From 05345077bcc45ad254ec92161c50993c4bcd5486 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 18 Dec 2025 11:16:47 +0800 Subject: [PATCH 05/10] wip --- Cargo.lock | 4 + crates/vite_task/Cargo.toml | 4 + crates/vite_task/src/session.rs | 107 ++++++-- crates/vite_task/tests/plan_snapshots/main.rs | 240 ++++++++++++++++++ crates/vite_task_graph/src/loader.rs | 4 +- crates/vite_task_plan/src/error.rs | 11 +- crates/vite_task_plan/src/lib.rs | 8 +- 7 files changed, 353 insertions(+), 25 deletions(-) create mode 100644 crates/vite_task/tests/plan_snapshots/main.rs diff --git a/Cargo.lock b/Cargo.lock index a8325bac..1af46c99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3180,11 +3180,14 @@ dependencies = [ "bstr", "clap", "compact_str 0.9.0", + "copy_dir", "dashmap", + "derive_more", "diff-struct", "fspy", "futures-core", "futures-util", + "insta", "itertools 0.14.0", "nix 0.30.1", "owo-colors", @@ -3199,6 +3202,7 @@ dependencies = [ "tempfile", "thiserror 2.0.17", "tokio", + "toml", "tracing", "twox-hash", "uuid", diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml index ab238d76..1808a68a 100644 --- a/crates/vite_task/Cargo.toml +++ b/crates/vite_task/Cargo.toml @@ -19,6 +19,7 @@ bstr = { workspace = true } clap = { workspace = true, features = ["derive"] } compact_str = { workspace = true, features = ["serde"] } dashmap = { workspace = true } +derive_more = { workspace = true } diff-struct = { workspace = true } fspy = { workspace = true } futures-core = { workspace = true } @@ -52,4 +53,7 @@ wax = { workspace = true } nix = { workspace = true } [dev-dependencies] +copy_dir = { workspace = true } +insta = { workspace = true, features = ["glob", "json"] } tempfile = { workspace = true } +toml = { workspace = true } diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs index 25354671..207547a5 100644 --- a/crates/vite_task/src/session.rs +++ b/crates/vite_task/src/session.rs @@ -1,13 +1,16 @@ -use std::{ffi::OsStr, sync::Arc}; +use std::{ffi::OsStr, fmt::Debug, sync::Arc}; +use clap::Parser; use vite_path::AbsolutePath; -use vite_task_graph::{IndexedTaskGraph, TaskGraphLoadError, loader::UserConfigLoader}; +use vite_str::Str; +use vite_task_graph::{IndexedTaskGraph, TaskGraph, TaskGraphLoadError, loader::UserConfigLoader}; use vite_task_plan::{ - ExecutionPlan, PlanRequestParser, TaskGraphLoader, plan_request::PlanRequest, + ExecutionPlan, TaskGraphLoader, TaskPlanErrorKind, + plan_request::{PlanRequest, SyntheticPlanRequest}, }; use vite_workspace::{WorkspaceRoot, find_workspace_root}; -use crate::collections::HashMap; +use crate::{CLIArgs, collections::HashMap}; #[derive(Debug)] enum LazyTaskGraph<'a> { @@ -15,7 +18,7 @@ enum LazyTaskGraph<'a> { Initialized(IndexedTaskGraph), } -#[async_trait::async_trait] +#[async_trait::async_trait(?Send)] impl TaskGraphLoader for LazyTaskGraph<'_> { async fn load_task_graph( &mut self, @@ -34,12 +37,70 @@ impl TaskGraphLoader for LazyTaskGraph<'_> { } } -pub struct SessionCallbacks<'a> { - plan_request_parser: &'a mut (dyn PlanRequestParser + 'a), +pub struct SessionCallbacks<'a, CustomSubCommand> { + task_synthesizer: &'a mut (dyn TaskSynthesizer + 'a), user_config_loader: &'a mut (dyn UserConfigLoader + 'a), } -pub struct Session<'a> { +#[async_trait::async_trait(?Send)] +pub trait TaskSynthesizer: Debug { + fn should_synthesize_for_program(&self, program: &str) -> bool; + async fn synthesize_task( + &mut self, + subcommand: CustomSubCommand, + cwd: &Arc, + ) -> anyhow::Result; +} + +#[derive(derive_more::Debug)] +#[debug(bound())] // Avoid requiring CustomSubCommand: Debug +struct PlanRequestParser<'a, CustomSubCommand> { + task_synthesizer: &'a mut (dyn TaskSynthesizer + 'a), +} + +impl PlanRequestParser<'_, CustomSubCommand> { + async fn get_plan_request_from_cli_args( + &mut self, + cli_args: CLIArgs, + cwd: &Arc, + ) -> anyhow::Result { + match cli_args { + CLIArgs::ViteTaskSubCommand(vite_task_subcommand) => { + Ok(vite_task_subcommand.into_plan_request(cwd)?) + } + CLIArgs::Custom(custom_subcommand) => { + let synthetic_plan_request = + self.task_synthesizer.synthesize_task(custom_subcommand, cwd).await?; + Ok(PlanRequest::Synthetic(synthetic_plan_request)) + } + } + } +} + +#[async_trait::async_trait(?Send)] +impl vite_task_plan::PlanRequestParser + for PlanRequestParser<'_, CustomSubCommand> +{ + async fn get_plan_request( + &mut self, + program: &str, + args: &[Str], + cwd: &Arc, + ) -> anyhow::Result> { + if !self.task_synthesizer.should_synthesize_for_program(program) { + return Ok(None); + } + let cli_args = CLIArgs::::try_parse_from( + std::iter::once(program).chain(args.iter().map(Str::as_str)), + )?; + Ok(Some(self.get_plan_request_from_cli_args(cli_args, cwd).await?)) + } +} + +/// Represents a vite task session for planning and executing tasks. A process typically has one session. +/// +/// A session manages task graph loading internally and provides non-consuming methods to plan and/or execute tasks (allows multiple plans/executions per session). +pub struct Session<'a, CustomSubCommand> { workspace_path: Arc, /// A session doesn't necessarily load the task graph immediately. /// The task graph is loaded on-demand and cached for future use. @@ -48,12 +109,12 @@ pub struct Session<'a> { envs: HashMap, Arc>, cwd: Arc, - plan_request_parser: &'a mut (dyn PlanRequestParser + 'a), + plan_request_parser: PlanRequestParser<'a, CustomSubCommand>, } -impl<'a> Session<'a> { +impl<'a, CustomSubCommand> Session<'a, CustomSubCommand> { /// Initialize a session with real environment variables and cwd - pub fn init(callbacks: SessionCallbacks<'a>) -> anyhow::Result { + pub fn init(callbacks: SessionCallbacks<'a, CustomSubCommand>) -> anyhow::Result { let envs = std::env::vars_os() .map(|(k, v)| (Arc::::from(k.as_os_str()), Arc::::from(v.as_os_str()))) .collect(); @@ -64,7 +125,7 @@ impl<'a> Session<'a> { pub fn init_with( envs: HashMap, Arc>, cwd: Arc, - callbacks: SessionCallbacks<'a>, + callbacks: SessionCallbacks<'a, CustomSubCommand>, ) -> anyhow::Result { let (workspace_root, _) = find_workspace_root(&cwd)?; Ok(Self { @@ -75,20 +136,36 @@ impl<'a> Session<'a> { }, envs, cwd, - plan_request_parser: callbacks.plan_request_parser, + plan_request_parser: PlanRequestParser { task_synthesizer: callbacks.task_synthesizer }, }) } + pub fn task_graph(&self) -> Option<&TaskGraph> { + match &self.lazy_task_graph { + LazyTaskGraph::Initialized(graph) => Some(graph.task_graph()), + _ => None, + } + } +} + +impl<'a, CustomSubCommand: clap::Subcommand> Session<'a, CustomSubCommand> { pub async fn plan( &mut self, - plan_request: PlanRequest, + cli_args: CLIArgs, ) -> Result { + let plan_request = self + .plan_request_parser + .get_plan_request_from_cli_args(cli_args, &self.cwd) + .await + .map_err(|error| { + TaskPlanErrorKind::ParsePlanRequestError { error }.with_empty_call_stack() + })?; ExecutionPlan::plan( plan_request, &self.workspace_path, &self.cwd, &self.envs, - self.plan_request_parser, + &mut self.plan_request_parser, &mut self.lazy_task_graph, ) .await diff --git a/crates/vite_task/tests/plan_snapshots/main.rs b/crates/vite_task/tests/plan_snapshots/main.rs new file mode 100644 index 00000000..7a860627 --- /dev/null +++ b/crates/vite_task/tests/plan_snapshots/main.rs @@ -0,0 +1,240 @@ +use core::panic; +use std::{path::Path, sync::Arc}; + +use clap::Parser; +use copy_dir::copy_dir; +use petgraph::visit::EdgeRef as _; +use tokio::runtime::Runtime; +use vite_path::{AbsolutePath, RelativePathBuf}; +use vite_str::Str; +use vite_task_graph::{ + IndexedTaskGraph, SpecifierLookupError, TaskDependencyType, TaskNodeIndex, + loader::JsonUserConfigLoader, + query::{PackageUnknownError, TaskExecutionGraph, TaskQueryError, cli::CLITaskQuery}, +}; +use vite_workspace::find_workspace_root; + +#[derive(serde::Serialize, PartialEq, PartialOrd, Eq, Ord)] +struct TaskIdSnapshot { + package_dir: RelativePathBuf, + task_name: Str, +} +impl TaskIdSnapshot { + fn new( + task_index: TaskNodeIndex, + base_dir: &AbsolutePath, + indexed_task_graph: &IndexedTaskGraph, + ) -> Self { + let task_id = &indexed_task_graph.task_graph()[task_index].task_id; + Self { + task_name: task_id.task_name.clone(), + package_dir: indexed_task_graph + .get_package_path(task_id.package_index) + .strip_prefix(base_dir) + .unwrap() + .unwrap(), + } + } +} + +/// Create a stable json representation of the task graph for snapshot testing. +/// +/// All paths are relative to `base_dir`. +fn snapshot_task_graph( + indexed_task_graph: &IndexedTaskGraph, + base_dir: &AbsolutePath, +) -> impl serde::Serialize { + #[derive(serde::Serialize)] + struct TaskNodeSnapshot { + id: TaskIdSnapshot, + command: Str, + cwd: RelativePathBuf, + depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)>, + } + + let task_graph = indexed_task_graph.task_graph(); + let mut node_snapshots = Vec::::with_capacity(task_graph.node_count()); + for task_index in task_graph.node_indices() { + let task_node = &task_graph[task_index]; + let mut depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)> = task_graph + .edges_directed(task_index, petgraph::Direction::Outgoing) + .map(|edge| { + (TaskIdSnapshot::new(edge.target(), base_dir, indexed_task_graph), *edge.weight()) + }) + .collect(); + depends_on.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + node_snapshots.push(TaskNodeSnapshot { + id: TaskIdSnapshot::new(task_index, base_dir, indexed_task_graph), + command: task_node.resolved_config.command.clone(), + cwd: task_node + .resolved_config + .resolved_options + .cwd + .strip_prefix(base_dir) + .unwrap() + .unwrap(), + depends_on, + }); + } + node_snapshots.sort_unstable_by(|a, b| a.id.cmp(&b.id)); + + node_snapshots +} + +/// Create a stable json representation of the task graph for snapshot testing. +/// +/// All paths are relative to `base_dir`. +fn snapshot_execution_graph( + execution_graph: &TaskExecutionGraph, + indexed_task_graph: &IndexedTaskGraph, + base_dir: &AbsolutePath, +) -> impl serde::Serialize { + #[derive(serde::Serialize, PartialEq)] + struct ExecutionNodeSnapshot { + task: TaskIdSnapshot, + deps: Vec, + } + + let mut execution_node_snapshots = Vec::::new(); + for task_index in execution_graph.nodes() { + let mut deps = execution_graph + .neighbors(task_index) + .map(|dep_index| TaskIdSnapshot::new(dep_index, base_dir, indexed_task_graph)) + .collect::>(); + deps.sort_unstable(); + + execution_node_snapshots.push(ExecutionNodeSnapshot { + task: TaskIdSnapshot::new(task_index, base_dir, indexed_task_graph), + deps, + }); + } + execution_node_snapshots.sort_unstable_by(|a, b| a.task.cmp(&b.task)); + execution_node_snapshots +} + +/// Modify absolute paths to be stable across different tmpdir locations. +fn stabilize_absolute_path(path: &mut Arc, base_dir: &AbsolutePath) { + let relative_path = path.strip_prefix(base_dir).unwrap().unwrap(); + // this path is considered absolute on all platforms + let new_base_dir = AbsolutePath::new("//?/workspace/").unwrap(); + *path = new_base_dir.join(relative_path).into(); +} + +/// Modify absolute paths in the SpecifierLookupError to be stable across different tmpdir locations. +fn stabilize_specifier_lookup_error( + err: &mut SpecifierLookupError, + base_dir: &AbsolutePath, +) { + match err { + SpecifierLookupError::AmbiguousPackageName { package_paths, .. } => { + for path in package_paths.iter_mut() { + stabilize_absolute_path(path, base_dir); + } + } + SpecifierLookupError::PackageNameNotFound { .. } => {} + SpecifierLookupError::TaskNameNotFound { package_index, .. } => { + *package_index = Default::default() + } + SpecifierLookupError::PackageUnknown { unspecifier_package_error, .. } => { + stabilize_absolute_path(&mut unspecifier_package_error.cwd, base_dir); + } + } +} + +#[derive(serde::Deserialize)] +struct CLIQuery { + pub name: Str, + pub args: Vec, + pub cwd: RelativePathBuf, +} + +#[derive(serde::Deserialize, Default)] +struct CLIQueriesFile { + #[serde(rename = "query")] // toml usually uses singular for arrays + pub queries: Vec, +} + +fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { + let case_name = case_path.file_name().unwrap().to_str().unwrap(); + if case_name.starts_with(".") { + return; // skip hidden files like .DS_Store + } + + // Copy the case directory to a temporary directory to avoid discovering workspace outside of the test case. + let case_stage_path = tmpdir.join(case_name); + copy_dir(case_path, &case_stage_path).unwrap(); + + let (workspace_root, _cwd) = find_workspace_root(&case_stage_path).unwrap(); + + assert_eq!( + &case_stage_path, &*workspace_root.path, + "folder '{}' should be a workspace root", + case_name + ); + + let cli_queries_toml_path = case_path.join("cli-queries.toml"); + let cli_queries_file: CLIQueriesFile = match std::fs::read(&cli_queries_toml_path) { + Ok(content) => toml::from_slice(&content).unwrap(), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Default::default(), + Err(err) => panic!("Failed to read cli-queries.toml for case {}: {}", case_name, err), + }; + + runtime.block_on(async { + let indexed_task_graph = vite_task_graph::IndexedTaskGraph::load( + &workspace_root, + &JsonUserConfigLoader::default(), + ) + .await + .expect(&format!("Failed to load task graph for case {case_name}")); + + let task_graph_snapshot = snapshot_task_graph(&indexed_task_graph, &case_stage_path); + insta::assert_json_snapshot!("task graph", task_graph_snapshot); + + for cli_query in cli_queries_file.queries { + let snapshot_name = format!("query - {}", cli_query.name); + + let cli_task_query = CLITaskQuery::try_parse_from( + std::iter::once("vite-run") // dummy program name + .chain(cli_query.args.iter().map(|s| s.as_str())), + ) + .expect(&format!( + "Failed to parse CLI args for query '{}' in case '{}'", + cli_query.name, case_name + )); + + let cwd: Arc = case_stage_path.join(&cli_query.cwd).into(); + let task_query = match cli_task_query.into_task_query(&cwd) { + Ok(ok) => ok, + Err(err) => { + insta::assert_debug_snapshot!(snapshot_name, err); + continue; + } + }; + + let execution_graph = match indexed_task_graph.query_tasks(task_query) { + Ok(ok) => ok, + Err(mut err) => { + match &mut err { + TaskQueryError::SpecifierLookupError { lookup_error, .. } => { + stabilize_specifier_lookup_error(lookup_error, &case_stage_path); + } + } + insta::assert_debug_snapshot!(snapshot_name, err); + continue; + } + }; + + let execution_graph_snapshot = + snapshot_execution_graph(&execution_graph, &indexed_task_graph, &case_stage_path); + insta::assert_json_snapshot!(snapshot_name, execution_graph_snapshot); + } + }); +} + +#[test] +fn test_snapshots() { + let tokio_runtime = Runtime::new().unwrap(); + let tmp_dir = tempfile::tempdir().unwrap(); + let tmp_dir_path = AbsolutePath::new(tmp_dir.path()).unwrap(); + insta::glob!("fixtures/*", |case_path| run_case(&tokio_runtime, tmp_dir_path, case_path)); +} diff --git a/crates/vite_task_graph/src/loader.rs b/crates/vite_task_graph/src/loader.rs index 99ba1714..0b699bb7 100644 --- a/crates/vite_task_graph/src/loader.rs +++ b/crates/vite_task_graph/src/loader.rs @@ -5,7 +5,7 @@ use vite_path::AbsolutePath; use crate::config::UserConfigFile; /// Loader trait for loading user configuration files (vite.config.*). -#[async_trait::async_trait] +#[async_trait::async_trait(?Send)] pub trait UserConfigLoader: Debug + Send + Sync { async fn load_user_config_file( &self, @@ -19,7 +19,7 @@ pub trait UserConfigLoader: Debug + Send + Sync { #[derive(Default, Debug)] pub struct JsonUserConfigLoader(()); -#[async_trait::async_trait] +#[async_trait::async_trait(?Send)] impl UserConfigLoader for JsonUserConfigLoader { async fn load_user_config_file( &self, diff --git a/crates/vite_task_plan/src/error.rs b/crates/vite_task_plan/src/error.rs index 4bcb077c..45e00a30 100644 --- a/crates/vite_task_plan/src/error.rs +++ b/crates/vite_task_plan/src/error.rs @@ -50,6 +50,12 @@ pub struct Error { kind: TaskPlanErrorKind, } +impl TaskPlanErrorKind { + pub fn with_empty_call_stack(self) -> Error { + Error { task_call_stack: TaskCallStackDisplay::default(), kind: self } + } +} + pub(crate) trait TaskPlanErrorKindResultExt { type Ok; /// Attach the current task call stack from the planning context to the error. @@ -76,10 +82,7 @@ impl TaskPlanErrorKindResultExt for Result { fn with_empty_call_stack(self) -> Result { match self { Ok(value) => Ok(value), - Err(kind) => { - let task_call_stack = TaskCallStackDisplay::default(); - Err(Error { task_call_stack, kind }) - } + Err(kind) => Err(kind.with_empty_call_stack()), } } } diff --git a/crates/vite_task_plan/src/lib.rs b/crates/vite_task_plan/src/lib.rs index 3b92507d..bf8eb508 100644 --- a/crates/vite_task_plan/src/lib.rs +++ b/crates/vite_task_plan/src/lib.rs @@ -102,7 +102,7 @@ pub enum ExecutionItemKind { /// The callback trait for parsing plan requests from cli args. /// See the method for details. -#[async_trait::async_trait] +#[async_trait::async_trait(?Send)] pub trait PlanRequestParser: Debug { /// This is called for every parsable command in the task graph in order to determine how to execute it. /// @@ -113,14 +113,14 @@ pub trait PlanRequestParser: Debug { /// - If it returns `Ok(Some(ParsedArgs::TaskQuery)`, the command will be expanded as a `ExpandedExecution` with a task graph queried from the returned `TaskQuery`. /// - If it returns `Ok(Some(ParsedArgs::Synthetic)`, the command will become a `SpawnExecution` with the synthetic task. async fn get_plan_request( - &self, + &mut self, program: &str, args: &[Str], - cwd: &AbsolutePath, + cwd: &Arc, ) -> anyhow::Result>; } -#[async_trait::async_trait] +#[async_trait::async_trait(?Send)] pub trait TaskGraphLoader { async fn load_task_graph( &mut self, From 6ea434ce1a459dd90181877122c0e261be65f899 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 18 Dec 2025 11:23:56 +0800 Subject: [PATCH 06/10] move snapshot test to vite_task --- .../tests/fixtures/cache-sharing/package.json | 0 .../fixtures/cache-sharing/pnpm-lock.yaml | 0 .../cache-sharing/pnpm-workspace.yaml | 0 .../comprehensive-task-graph/package.json | 0 .../packages/api/package.json | 0 .../packages/app/package.json | 0 .../packages/config/package.json | 0 .../packages/pkg#special/package.json | 0 .../packages/shared/package.json | 0 .../packages/tools/package.json | 0 .../packages/ui/package.json | 0 .../pnpm-workspace.yaml | 0 .../tests/fixtures/conflict-test/package.json | 0 .../packages/scope-a-b/package.json | 0 .../packages/scope-a/package.json | 0 .../packages/test-package/package.json | 0 .../packages/test-package/vite.config.json | 0 .../conflict-test/pnpm-workspace.yaml | 0 .../package.json | 0 .../packages/a/package.json | 0 .../packages/a/vite.config.json | 0 .../packages/b/package.json | 0 .../pnpm-workspace.yaml | 0 .../fixtures/empty-package-test/package.json | 0 .../packages/another-empty/package.json | 0 .../packages/another-empty/vite.config.json | 0 .../packages/empty-name/package.json | 0 .../packages/empty-name/vite.config.json | 0 .../packages/normal-package/package.json | 0 .../packages/normal-package/vite.config.json | 0 .../empty-package-test/pnpm-workspace.yaml | 0 .../explicit-deps-workspace/package.json | 0 .../packages/app/package.json | 0 .../packages/app/vite.config.json | 0 .../packages/core/package.json | 0 .../packages/core/vite.config.json | 0 .../packages/utils/package.json | 0 .../packages/utils/vite.config.json | 0 .../pnpm-workspace.yaml | 0 .../fingerprint-ignore-test/README.md | 0 .../fingerprint-ignore-test/package.json | 0 .../fingerprint-ignore-test/vite.config.json | 0 .../apps/web/package.json | 0 .../package.json | 0 .../packages/app/package.json | 0 .../packages/core/package.json | 0 .../packages/utils/package.json | 0 .../pnpm-workspace.yaml | 0 .../cli-queries.toml | 0 .../packages/a/package.json | 0 .../packages/a/src/.gitkeep | 0 .../packages/a/vite.config.json | 0 .../packages/another-a/package.json | 0 .../packages/b1/package.json | 0 .../packages/b2/package.json | 0 .../packages/c/package.json | 0 .../pnpm-workspace.yaml | 0 crates/vite_task/tests/plan_snapshots/main.rs | 240 ------------------ .../tests/snapshots.rs | 0 ... name@transitive-dependency-workspace.snap | 0 ...ckage@transitive-dependency-workspace.snap | 0 ...e cwd@transitive-dependency-workspace.snap | 0 ...ds on@transitive-dependency-workspace.snap | 0 ...itive@transitive-dependency-workspace.snap | 0 ...rsive@transitive-dependency-workspace.snap | 0 ... name@transitive-dependency-workspace.snap | 0 ... task@transitive-dependency-workspace.snap | 0 ... task@transitive-dependency-workspace.snap | 0 ...itive@transitive-dependency-workspace.snap | 0 ...ckage@transitive-dependency-workspace.snap | 0 .../snapshots__task graph@cache-sharing.snap | 0 ...__task graph@comprehensive-task-graph.snap | 0 .../snapshots__task graph@conflict-test.snap | 0 ...aph@dependency-both-topo-and-explicit.snap | 0 ...pshots__task graph@empty-package-test.snap | 0 ...s__task graph@explicit-deps-workspace.snap | 0 ...s__task graph@fingerprint-ignore-test.snap | 0 ...graph@recursive-topological-workspace.snap | 0 ...graph@transitive-dependency-workspace.snap | 0 79 files changed, 240 deletions(-) rename crates/{vite_task_graph => vite_task}/tests/fixtures/cache-sharing/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/cache-sharing/pnpm-lock.yaml (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/cache-sharing/pnpm-workspace.yaml (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/comprehensive-task-graph/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/comprehensive-task-graph/packages/api/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/comprehensive-task-graph/packages/app/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/comprehensive-task-graph/packages/config/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/comprehensive-task-graph/packages/pkg#special/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/comprehensive-task-graph/packages/shared/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/comprehensive-task-graph/packages/tools/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/comprehensive-task-graph/packages/ui/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/comprehensive-task-graph/pnpm-workspace.yaml (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/conflict-test/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/conflict-test/packages/scope-a-b/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/conflict-test/packages/scope-a/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/conflict-test/packages/test-package/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/conflict-test/packages/test-package/vite.config.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/conflict-test/pnpm-workspace.yaml (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/dependency-both-topo-and-explicit/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/dependency-both-topo-and-explicit/packages/a/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/dependency-both-topo-and-explicit/packages/b/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/dependency-both-topo-and-explicit/pnpm-workspace.yaml (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/empty-package-test/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/empty-package-test/packages/another-empty/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/empty-package-test/packages/empty-name/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/empty-package-test/packages/normal-package/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/empty-package-test/pnpm-workspace.yaml (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/explicit-deps-workspace/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/explicit-deps-workspace/packages/app/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/explicit-deps-workspace/packages/core/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/explicit-deps-workspace/packages/utils/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/explicit-deps-workspace/pnpm-workspace.yaml (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/fingerprint-ignore-test/README.md (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/fingerprint-ignore-test/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/fingerprint-ignore-test/vite.config.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/recursive-topological-workspace/apps/web/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/recursive-topological-workspace/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/recursive-topological-workspace/packages/app/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/recursive-topological-workspace/packages/core/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/recursive-topological-workspace/packages/utils/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/recursive-topological-workspace/pnpm-workspace.yaml (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/transitive-dependency-workspace/cli-queries.toml (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/transitive-dependency-workspace/packages/a/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/transitive-dependency-workspace/packages/a/src/.gitkeep (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/transitive-dependency-workspace/packages/a/vite.config.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/transitive-dependency-workspace/packages/another-a/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/transitive-dependency-workspace/packages/b2/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/transitive-dependency-workspace/packages/c/package.json (100%) rename crates/{vite_task_graph => vite_task}/tests/fixtures/transitive-dependency-workspace/pnpm-workspace.yaml (100%) delete mode 100644 crates/vite_task/tests/plan_snapshots/main.rs rename crates/{vite_task_graph => vite_task}/tests/snapshots.rs (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__task graph@cache-sharing.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__task graph@conflict-test.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__task graph@empty-package-test.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap (100%) rename crates/{vite_task_graph => vite_task}/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap (100%) diff --git a/crates/vite_task_graph/tests/fixtures/cache-sharing/package.json b/crates/vite_task/tests/fixtures/cache-sharing/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/cache-sharing/package.json rename to crates/vite_task/tests/fixtures/cache-sharing/package.json diff --git a/crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-lock.yaml b/crates/vite_task/tests/fixtures/cache-sharing/pnpm-lock.yaml similarity index 100% rename from crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-lock.yaml rename to crates/vite_task/tests/fixtures/cache-sharing/pnpm-lock.yaml diff --git a/crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-workspace.yaml b/crates/vite_task/tests/fixtures/cache-sharing/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-workspace.yaml rename to crates/vite_task/tests/fixtures/cache-sharing/pnpm-workspace.yaml diff --git a/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/package.json b/crates/vite_task/tests/fixtures/comprehensive-task-graph/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/package.json rename to crates/vite_task/tests/fixtures/comprehensive-task-graph/package.json diff --git a/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/api/package.json b/crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/api/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/api/package.json rename to crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/api/package.json diff --git a/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/app/package.json b/crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/app/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/app/package.json rename to crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/app/package.json diff --git a/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/config/package.json b/crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/config/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/config/package.json rename to crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/config/package.json diff --git a/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/pkg#special/package.json b/crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/pkg#special/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/pkg#special/package.json rename to crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/pkg#special/package.json diff --git a/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/shared/package.json b/crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/shared/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/shared/package.json rename to crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/shared/package.json diff --git a/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/tools/package.json b/crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/tools/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/tools/package.json rename to crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/tools/package.json diff --git a/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/ui/package.json b/crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/ui/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/ui/package.json rename to crates/vite_task/tests/fixtures/comprehensive-task-graph/packages/ui/package.json diff --git a/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/pnpm-workspace.yaml b/crates/vite_task/tests/fixtures/comprehensive-task-graph/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/pnpm-workspace.yaml rename to crates/vite_task/tests/fixtures/comprehensive-task-graph/pnpm-workspace.yaml diff --git a/crates/vite_task_graph/tests/fixtures/conflict-test/package.json b/crates/vite_task/tests/fixtures/conflict-test/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/conflict-test/package.json rename to crates/vite_task/tests/fixtures/conflict-test/package.json diff --git a/crates/vite_task_graph/tests/fixtures/conflict-test/packages/scope-a-b/package.json b/crates/vite_task/tests/fixtures/conflict-test/packages/scope-a-b/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/conflict-test/packages/scope-a-b/package.json rename to crates/vite_task/tests/fixtures/conflict-test/packages/scope-a-b/package.json diff --git a/crates/vite_task_graph/tests/fixtures/conflict-test/packages/scope-a/package.json b/crates/vite_task/tests/fixtures/conflict-test/packages/scope-a/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/conflict-test/packages/scope-a/package.json rename to crates/vite_task/tests/fixtures/conflict-test/packages/scope-a/package.json diff --git a/crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/package.json b/crates/vite_task/tests/fixtures/conflict-test/packages/test-package/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/package.json rename to crates/vite_task/tests/fixtures/conflict-test/packages/test-package/package.json diff --git a/crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite.config.json b/crates/vite_task/tests/fixtures/conflict-test/packages/test-package/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite.config.json rename to crates/vite_task/tests/fixtures/conflict-test/packages/test-package/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/conflict-test/pnpm-workspace.yaml b/crates/vite_task/tests/fixtures/conflict-test/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task_graph/tests/fixtures/conflict-test/pnpm-workspace.yaml rename to crates/vite_task/tests/fixtures/conflict-test/pnpm-workspace.yaml diff --git a/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/package.json b/crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/package.json rename to crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit/package.json diff --git a/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/package.json b/crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit/packages/a/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/package.json rename to crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit/packages/a/package.json diff --git a/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json b/crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json rename to crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/b/package.json b/crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit/packages/b/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/b/package.json rename to crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit/packages/b/package.json diff --git a/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/pnpm-workspace.yaml b/crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/pnpm-workspace.yaml rename to crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit/pnpm-workspace.yaml diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/package.json b/crates/vite_task/tests/fixtures/empty-package-test/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/package.json rename to crates/vite_task/tests/fixtures/empty-package-test/package.json diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/package.json b/crates/vite_task/tests/fixtures/empty-package-test/packages/another-empty/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/package.json rename to crates/vite_task/tests/fixtures/empty-package-test/packages/another-empty/package.json diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json b/crates/vite_task/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json rename to crates/vite_task/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/package.json b/crates/vite_task/tests/fixtures/empty-package-test/packages/empty-name/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/package.json rename to crates/vite_task/tests/fixtures/empty-package-test/packages/empty-name/package.json diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json b/crates/vite_task/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json rename to crates/vite_task/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/package.json b/crates/vite_task/tests/fixtures/empty-package-test/packages/normal-package/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/package.json rename to crates/vite_task/tests/fixtures/empty-package-test/packages/normal-package/package.json diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json b/crates/vite_task/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json rename to crates/vite_task/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/pnpm-workspace.yaml b/crates/vite_task/tests/fixtures/empty-package-test/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/pnpm-workspace.yaml rename to crates/vite_task/tests/fixtures/empty-package-test/pnpm-workspace.yaml diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/package.json b/crates/vite_task/tests/fixtures/explicit-deps-workspace/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/package.json rename to crates/vite_task/tests/fixtures/explicit-deps-workspace/package.json diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/package.json b/crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/app/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/package.json rename to crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/app/package.json diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json b/crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json rename to crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/package.json b/crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/core/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/package.json rename to crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/core/package.json diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json b/crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json rename to crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/package.json b/crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/utils/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/package.json rename to crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/utils/package.json diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json b/crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json rename to crates/vite_task/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/pnpm-workspace.yaml b/crates/vite_task/tests/fixtures/explicit-deps-workspace/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/pnpm-workspace.yaml rename to crates/vite_task/tests/fixtures/explicit-deps-workspace/pnpm-workspace.yaml diff --git a/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/README.md b/crates/vite_task/tests/fixtures/fingerprint-ignore-test/README.md similarity index 100% rename from crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/README.md rename to crates/vite_task/tests/fixtures/fingerprint-ignore-test/README.md diff --git a/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/package.json b/crates/vite_task/tests/fixtures/fingerprint-ignore-test/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/package.json rename to crates/vite_task/tests/fixtures/fingerprint-ignore-test/package.json diff --git a/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json b/crates/vite_task/tests/fixtures/fingerprint-ignore-test/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json rename to crates/vite_task/tests/fixtures/fingerprint-ignore-test/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/apps/web/package.json b/crates/vite_task/tests/fixtures/recursive-topological-workspace/apps/web/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/apps/web/package.json rename to crates/vite_task/tests/fixtures/recursive-topological-workspace/apps/web/package.json diff --git a/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/package.json b/crates/vite_task/tests/fixtures/recursive-topological-workspace/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/package.json rename to crates/vite_task/tests/fixtures/recursive-topological-workspace/package.json diff --git a/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/app/package.json b/crates/vite_task/tests/fixtures/recursive-topological-workspace/packages/app/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/app/package.json rename to crates/vite_task/tests/fixtures/recursive-topological-workspace/packages/app/package.json diff --git a/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/core/package.json b/crates/vite_task/tests/fixtures/recursive-topological-workspace/packages/core/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/core/package.json rename to crates/vite_task/tests/fixtures/recursive-topological-workspace/packages/core/package.json diff --git a/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/utils/package.json b/crates/vite_task/tests/fixtures/recursive-topological-workspace/packages/utils/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/utils/package.json rename to crates/vite_task/tests/fixtures/recursive-topological-workspace/packages/utils/package.json diff --git a/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/pnpm-workspace.yaml b/crates/vite_task/tests/fixtures/recursive-topological-workspace/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/pnpm-workspace.yaml rename to crates/vite_task/tests/fixtures/recursive-topological-workspace/pnpm-workspace.yaml diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml b/crates/vite_task/tests/fixtures/transitive-dependency-workspace/cli-queries.toml similarity index 100% rename from crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml rename to crates/vite_task/tests/fixtures/transitive-dependency-workspace/cli-queries.toml diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/package.json b/crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/a/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/package.json rename to crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/a/package.json diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/src/.gitkeep b/crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/a/src/.gitkeep similarity index 100% rename from crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/src/.gitkeep rename to crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/a/src/.gitkeep diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/vite.config.json b/crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/a/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/vite.config.json rename to crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/a/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-a/package.json b/crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/another-a/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-a/package.json rename to crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/another-a/package.json diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json b/crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json rename to crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b2/package.json b/crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/b2/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b2/package.json rename to crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/b2/package.json diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/c/package.json b/crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/c/package.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/c/package.json rename to crates/vite_task/tests/fixtures/transitive-dependency-workspace/packages/c/package.json diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/pnpm-workspace.yaml b/crates/vite_task/tests/fixtures/transitive-dependency-workspace/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/pnpm-workspace.yaml rename to crates/vite_task/tests/fixtures/transitive-dependency-workspace/pnpm-workspace.yaml diff --git a/crates/vite_task/tests/plan_snapshots/main.rs b/crates/vite_task/tests/plan_snapshots/main.rs deleted file mode 100644 index 7a860627..00000000 --- a/crates/vite_task/tests/plan_snapshots/main.rs +++ /dev/null @@ -1,240 +0,0 @@ -use core::panic; -use std::{path::Path, sync::Arc}; - -use clap::Parser; -use copy_dir::copy_dir; -use petgraph::visit::EdgeRef as _; -use tokio::runtime::Runtime; -use vite_path::{AbsolutePath, RelativePathBuf}; -use vite_str::Str; -use vite_task_graph::{ - IndexedTaskGraph, SpecifierLookupError, TaskDependencyType, TaskNodeIndex, - loader::JsonUserConfigLoader, - query::{PackageUnknownError, TaskExecutionGraph, TaskQueryError, cli::CLITaskQuery}, -}; -use vite_workspace::find_workspace_root; - -#[derive(serde::Serialize, PartialEq, PartialOrd, Eq, Ord)] -struct TaskIdSnapshot { - package_dir: RelativePathBuf, - task_name: Str, -} -impl TaskIdSnapshot { - fn new( - task_index: TaskNodeIndex, - base_dir: &AbsolutePath, - indexed_task_graph: &IndexedTaskGraph, - ) -> Self { - let task_id = &indexed_task_graph.task_graph()[task_index].task_id; - Self { - task_name: task_id.task_name.clone(), - package_dir: indexed_task_graph - .get_package_path(task_id.package_index) - .strip_prefix(base_dir) - .unwrap() - .unwrap(), - } - } -} - -/// Create a stable json representation of the task graph for snapshot testing. -/// -/// All paths are relative to `base_dir`. -fn snapshot_task_graph( - indexed_task_graph: &IndexedTaskGraph, - base_dir: &AbsolutePath, -) -> impl serde::Serialize { - #[derive(serde::Serialize)] - struct TaskNodeSnapshot { - id: TaskIdSnapshot, - command: Str, - cwd: RelativePathBuf, - depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)>, - } - - let task_graph = indexed_task_graph.task_graph(); - let mut node_snapshots = Vec::::with_capacity(task_graph.node_count()); - for task_index in task_graph.node_indices() { - let task_node = &task_graph[task_index]; - let mut depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)> = task_graph - .edges_directed(task_index, petgraph::Direction::Outgoing) - .map(|edge| { - (TaskIdSnapshot::new(edge.target(), base_dir, indexed_task_graph), *edge.weight()) - }) - .collect(); - depends_on.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - node_snapshots.push(TaskNodeSnapshot { - id: TaskIdSnapshot::new(task_index, base_dir, indexed_task_graph), - command: task_node.resolved_config.command.clone(), - cwd: task_node - .resolved_config - .resolved_options - .cwd - .strip_prefix(base_dir) - .unwrap() - .unwrap(), - depends_on, - }); - } - node_snapshots.sort_unstable_by(|a, b| a.id.cmp(&b.id)); - - node_snapshots -} - -/// Create a stable json representation of the task graph for snapshot testing. -/// -/// All paths are relative to `base_dir`. -fn snapshot_execution_graph( - execution_graph: &TaskExecutionGraph, - indexed_task_graph: &IndexedTaskGraph, - base_dir: &AbsolutePath, -) -> impl serde::Serialize { - #[derive(serde::Serialize, PartialEq)] - struct ExecutionNodeSnapshot { - task: TaskIdSnapshot, - deps: Vec, - } - - let mut execution_node_snapshots = Vec::::new(); - for task_index in execution_graph.nodes() { - let mut deps = execution_graph - .neighbors(task_index) - .map(|dep_index| TaskIdSnapshot::new(dep_index, base_dir, indexed_task_graph)) - .collect::>(); - deps.sort_unstable(); - - execution_node_snapshots.push(ExecutionNodeSnapshot { - task: TaskIdSnapshot::new(task_index, base_dir, indexed_task_graph), - deps, - }); - } - execution_node_snapshots.sort_unstable_by(|a, b| a.task.cmp(&b.task)); - execution_node_snapshots -} - -/// Modify absolute paths to be stable across different tmpdir locations. -fn stabilize_absolute_path(path: &mut Arc, base_dir: &AbsolutePath) { - let relative_path = path.strip_prefix(base_dir).unwrap().unwrap(); - // this path is considered absolute on all platforms - let new_base_dir = AbsolutePath::new("//?/workspace/").unwrap(); - *path = new_base_dir.join(relative_path).into(); -} - -/// Modify absolute paths in the SpecifierLookupError to be stable across different tmpdir locations. -fn stabilize_specifier_lookup_error( - err: &mut SpecifierLookupError, - base_dir: &AbsolutePath, -) { - match err { - SpecifierLookupError::AmbiguousPackageName { package_paths, .. } => { - for path in package_paths.iter_mut() { - stabilize_absolute_path(path, base_dir); - } - } - SpecifierLookupError::PackageNameNotFound { .. } => {} - SpecifierLookupError::TaskNameNotFound { package_index, .. } => { - *package_index = Default::default() - } - SpecifierLookupError::PackageUnknown { unspecifier_package_error, .. } => { - stabilize_absolute_path(&mut unspecifier_package_error.cwd, base_dir); - } - } -} - -#[derive(serde::Deserialize)] -struct CLIQuery { - pub name: Str, - pub args: Vec, - pub cwd: RelativePathBuf, -} - -#[derive(serde::Deserialize, Default)] -struct CLIQueriesFile { - #[serde(rename = "query")] // toml usually uses singular for arrays - pub queries: Vec, -} - -fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { - let case_name = case_path.file_name().unwrap().to_str().unwrap(); - if case_name.starts_with(".") { - return; // skip hidden files like .DS_Store - } - - // Copy the case directory to a temporary directory to avoid discovering workspace outside of the test case. - let case_stage_path = tmpdir.join(case_name); - copy_dir(case_path, &case_stage_path).unwrap(); - - let (workspace_root, _cwd) = find_workspace_root(&case_stage_path).unwrap(); - - assert_eq!( - &case_stage_path, &*workspace_root.path, - "folder '{}' should be a workspace root", - case_name - ); - - let cli_queries_toml_path = case_path.join("cli-queries.toml"); - let cli_queries_file: CLIQueriesFile = match std::fs::read(&cli_queries_toml_path) { - Ok(content) => toml::from_slice(&content).unwrap(), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => Default::default(), - Err(err) => panic!("Failed to read cli-queries.toml for case {}: {}", case_name, err), - }; - - runtime.block_on(async { - let indexed_task_graph = vite_task_graph::IndexedTaskGraph::load( - &workspace_root, - &JsonUserConfigLoader::default(), - ) - .await - .expect(&format!("Failed to load task graph for case {case_name}")); - - let task_graph_snapshot = snapshot_task_graph(&indexed_task_graph, &case_stage_path); - insta::assert_json_snapshot!("task graph", task_graph_snapshot); - - for cli_query in cli_queries_file.queries { - let snapshot_name = format!("query - {}", cli_query.name); - - let cli_task_query = CLITaskQuery::try_parse_from( - std::iter::once("vite-run") // dummy program name - .chain(cli_query.args.iter().map(|s| s.as_str())), - ) - .expect(&format!( - "Failed to parse CLI args for query '{}' in case '{}'", - cli_query.name, case_name - )); - - let cwd: Arc = case_stage_path.join(&cli_query.cwd).into(); - let task_query = match cli_task_query.into_task_query(&cwd) { - Ok(ok) => ok, - Err(err) => { - insta::assert_debug_snapshot!(snapshot_name, err); - continue; - } - }; - - let execution_graph = match indexed_task_graph.query_tasks(task_query) { - Ok(ok) => ok, - Err(mut err) => { - match &mut err { - TaskQueryError::SpecifierLookupError { lookup_error, .. } => { - stabilize_specifier_lookup_error(lookup_error, &case_stage_path); - } - } - insta::assert_debug_snapshot!(snapshot_name, err); - continue; - } - }; - - let execution_graph_snapshot = - snapshot_execution_graph(&execution_graph, &indexed_task_graph, &case_stage_path); - insta::assert_json_snapshot!(snapshot_name, execution_graph_snapshot); - } - }); -} - -#[test] -fn test_snapshots() { - let tokio_runtime = Runtime::new().unwrap(); - let tmp_dir = tempfile::tempdir().unwrap(); - let tmp_dir_path = AbsolutePath::new(tmp_dir.path()).unwrap(); - insta::glob!("fixtures/*", |case_path| run_case(&tokio_runtime, tmp_dir_path, case_path)); -} diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task/tests/snapshots.rs similarity index 100% rename from crates/vite_task_graph/tests/snapshots.rs rename to crates/vite_task/tests/snapshots.rs diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@cache-sharing.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap rename to crates/vite_task/tests/snapshots/snapshots__task graph@cache-sharing.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap rename to crates/vite_task/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@conflict-test.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap rename to crates/vite_task/tests/snapshots/snapshots__task graph@conflict-test.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap rename to crates/vite_task/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@empty-package-test.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap rename to crates/vite_task/tests/snapshots/snapshots__task graph@empty-package-test.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap rename to crates/vite_task/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap rename to crates/vite_task/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap From b45f6f537129b35477b57f250775f0e28c8a97ad Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 18 Dec 2025 12:41:15 +0800 Subject: [PATCH 07/10] redact absolute paths --- Cargo.lock | 51 +++++++++++ crates/vite_path/Cargo.toml | 3 + .../src/{absolute.rs => absolute/mod.rs} | 35 ++++++++ crates/vite_path/src/absolute/redaction.rs | 23 +++++ crates/vite_path/src/lib.rs | 2 + crates/vite_task/Cargo.toml | 3 +- crates/vite_task/tests/snapshots.rs | 85 +++++-------------- ... name@transitive-dependency-workspace.snap | 36 ++++---- ...ckage@transitive-dependency-workspace.snap | 4 +- ...e cwd@transitive-dependency-workspace.snap | 4 +- ...ds on@transitive-dependency-workspace.snap | 4 +- ...itive@transitive-dependency-workspace.snap | 6 +- ...rsive@transitive-dependency-workspace.snap | 4 +- ... name@transitive-dependency-workspace.snap | 4 +- ... task@transitive-dependency-workspace.snap | 4 +- ... task@transitive-dependency-workspace.snap | 25 +++--- ...itive@transitive-dependency-workspace.snap | 4 +- ...ckage@transitive-dependency-workspace.snap | 4 +- .../snapshots__task graph@cache-sharing.snap | 4 +- ...__task graph@comprehensive-task-graph.snap | 4 +- .../snapshots__task graph@conflict-test.snap | 4 +- ...aph@dependency-both-topo-and-explicit.snap | 4 +- ...pshots__task graph@empty-package-test.snap | 4 +- ...s__task graph@explicit-deps-workspace.snap | 4 +- ...s__task graph@fingerprint-ignore-test.snap | 4 +- ...graph@recursive-topological-workspace.snap | 4 +- ...graph@transitive-dependency-workspace.snap | 4 +- crates/vite_task_graph/src/lib.rs | 9 +- crates/vite_task_graph/src/query/cli.rs | 3 +- crates/vite_task_graph/src/query/mod.rs | 5 +- crates/vite_task_graph/src/specifier.rs | 3 +- 31 files changed, 218 insertions(+), 139 deletions(-) rename crates/vite_path/src/{absolute.rs => absolute/mod.rs} (89%) create mode 100644 crates/vite_path/src/absolute/redaction.rs diff --git a/Cargo.lock b/Cargo.lock index 1af46c99..64e2ee18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1444,6 +1444,8 @@ dependencies = [ "console", "globset", "once_cell", + "pest", + "pest_derive", "serde", "similar", "walkdir", @@ -1991,6 +1993,49 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" +[[package]] +name = "pest" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pest_meta" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" +dependencies = [ + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.8.3" @@ -3020,6 +3065,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-ident" version = "1.0.19" diff --git a/crates/vite_path/Cargo.toml b/crates/vite_path/Cargo.toml index 01d16110..5184a749 100644 --- a/crates/vite_path/Cargo.toml +++ b/crates/vite_path/Cargo.toml @@ -14,6 +14,9 @@ serde = { workspace = true, features = ["derive", "rc"] } thiserror = { workspace = true } vite_str = { workspace = true } +[features] +absolute-redaction = [] + [lints] workspace = true diff --git a/crates/vite_path/src/absolute.rs b/crates/vite_path/src/absolute/mod.rs similarity index 89% rename from crates/vite_path/src/absolute.rs rename to crates/vite_path/src/absolute/mod.rs index 89beb90c..fde8b89c 100644 --- a/crates/vite_path/src/absolute.rs +++ b/crates/vite_path/src/absolute/mod.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "absolute-redaction")] +pub mod redaction; + use std::{ ffi::OsStr, fmt::Display, @@ -8,6 +11,7 @@ use std::{ }; use ref_cast::{RefCastCustom, ref_cast_custom}; +use serde::Serialize; use crate::relative::{FromPathError, InvalidPathDataError, RelativePathBuf}; @@ -21,6 +25,29 @@ impl AsRef for AbsolutePath { } } +impl Serialize for AbsolutePath { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self.as_path().to_str() { + Some(s) => { + #[cfg(feature = "absolute-redaction")] + let s = redaction::REDACTION_PREFIX.with_borrow(|redaction_prefix| { + if let Some(redaction_prefix) = redaction_prefix + && let Some(stripped_path) = s.strip_prefix(redaction_prefix.as_ref()) + { + return stripped_path.strip_prefix("/").unwrap_or(stripped_path); + } + s + }); + s.serialize(serializer) + } + None => Err(serde::ser::Error::custom("path contains invalid UTF-8 characters")), + } + } +} + impl PartialEq for AbsolutePath { fn eq(&self, other: &AbsolutePathBuf) -> bool { self.0 == other.0 @@ -46,6 +73,14 @@ impl From<&AbsolutePath> for Arc { } } +impl From<&AbsolutePath> for Box { + fn from(path: &AbsolutePath) -> Self { + let path_box: Box = path.0.into(); + let path_box_raw = Box::into_raw(path_box) as *mut AbsolutePath; + unsafe { Self::from_raw(path_box_raw) } + } +} + impl AbsolutePath { /// Creates a [`AbsolutePath`] if the give path is absolute. pub fn new + ?Sized>(path: &P) -> Option<&Self> { diff --git a/crates/vite_path/src/absolute/redaction.rs b/crates/vite_path/src/absolute/redaction.rs new file mode 100644 index 00000000..7773daa4 --- /dev/null +++ b/crates/vite_path/src/absolute/redaction.rs @@ -0,0 +1,23 @@ +use super::AbsolutePath; + +thread_local! { + pub(crate) static REDACTION_PREFIX: std::cell::RefCell>> = std::cell::RefCell::new(None); +} + +#[derive(Debug)] +pub struct RedactionGuard(()); + +impl Drop for RedactionGuard { + fn drop(&mut self) { + REDACTION_PREFIX.set(None); + } +} + +pub fn redact_absolute_paths(prefix: &str) -> RedactionGuard { + REDACTION_PREFIX.with(|redaction_prefix| { + let mut redaction_prefix = redaction_prefix.borrow_mut(); + assert!(redaction_prefix.is_none(), "RedactionGuard already active"); + *redaction_prefix = Some(prefix.into()); + }); + RedactionGuard(()) +} diff --git a/crates/vite_path/src/lib.rs b/crates/vite_path/src/lib.rs index 9014e4ef..87a3808f 100644 --- a/crates/vite_path/src/lib.rs +++ b/crates/vite_path/src/lib.rs @@ -3,6 +3,8 @@ pub mod relative; use std::io; +#[cfg(feature = "absolute-redaction")] +pub use absolute::redaction; pub use absolute::{AbsolutePath, AbsolutePathBuf}; pub use relative::{RelativePath, RelativePathBuf}; diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml index 1808a68a..25b22fd0 100644 --- a/crates/vite_task/Cargo.toml +++ b/crates/vite_task/Cargo.toml @@ -54,6 +54,7 @@ nix = { workspace = true } [dev-dependencies] copy_dir = { workspace = true } -insta = { workspace = true, features = ["glob", "json"] } +insta = { workspace = true, features = ["glob", "json", "redactions"] } tempfile = { workspace = true } toml = { workspace = true } +vite_path = { workspace = true, features = ["absolute-redaction"] } diff --git a/crates/vite_task/tests/snapshots.rs b/crates/vite_task/tests/snapshots.rs index 7a860627..4159d6ca 100644 --- a/crates/vite_task/tests/snapshots.rs +++ b/crates/vite_task/tests/snapshots.rs @@ -3,9 +3,11 @@ use std::{path::Path, sync::Arc}; use clap::Parser; use copy_dir::copy_dir; +use insta::internals::Content; use petgraph::visit::EdgeRef as _; +use serde::Serializer; use tokio::runtime::Runtime; -use vite_path::{AbsolutePath, RelativePathBuf}; +use vite_path::{AbsolutePath, RelativePathBuf, redaction::redact_absolute_paths}; use vite_str::Str; use vite_task_graph::{ IndexedTaskGraph, SpecifierLookupError, TaskDependencyType, TaskNodeIndex, @@ -16,23 +18,15 @@ use vite_workspace::find_workspace_root; #[derive(serde::Serialize, PartialEq, PartialOrd, Eq, Ord)] struct TaskIdSnapshot { - package_dir: RelativePathBuf, + package_dir: Arc, task_name: Str, } impl TaskIdSnapshot { - fn new( - task_index: TaskNodeIndex, - base_dir: &AbsolutePath, - indexed_task_graph: &IndexedTaskGraph, - ) -> Self { + fn new(task_index: TaskNodeIndex, indexed_task_graph: &IndexedTaskGraph) -> Self { let task_id = &indexed_task_graph.task_graph()[task_index].task_id; Self { task_name: task_id.task_name.clone(), - package_dir: indexed_task_graph - .get_package_path(task_id.package_index) - .strip_prefix(base_dir) - .unwrap() - .unwrap(), + package_dir: Arc::clone(&indexed_task_graph.get_package_path(task_id.package_index)), } } } @@ -48,7 +42,7 @@ fn snapshot_task_graph( struct TaskNodeSnapshot { id: TaskIdSnapshot, command: Str, - cwd: RelativePathBuf, + cwd: Arc, depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)>, } @@ -58,21 +52,13 @@ fn snapshot_task_graph( let task_node = &task_graph[task_index]; let mut depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)> = task_graph .edges_directed(task_index, petgraph::Direction::Outgoing) - .map(|edge| { - (TaskIdSnapshot::new(edge.target(), base_dir, indexed_task_graph), *edge.weight()) - }) + .map(|edge| (TaskIdSnapshot::new(edge.target(), indexed_task_graph), *edge.weight())) .collect(); depends_on.sort_unstable_by(|a, b| a.0.cmp(&b.0)); node_snapshots.push(TaskNodeSnapshot { - id: TaskIdSnapshot::new(task_index, base_dir, indexed_task_graph), + id: TaskIdSnapshot::new(task_index, indexed_task_graph), command: task_node.resolved_config.command.clone(), - cwd: task_node - .resolved_config - .resolved_options - .cwd - .strip_prefix(base_dir) - .unwrap() - .unwrap(), + cwd: Arc::clone(&task_node.resolved_config.resolved_options.cwd), depends_on, }); } @@ -87,7 +73,6 @@ fn snapshot_task_graph( fn snapshot_execution_graph( execution_graph: &TaskExecutionGraph, indexed_task_graph: &IndexedTaskGraph, - base_dir: &AbsolutePath, ) -> impl serde::Serialize { #[derive(serde::Serialize, PartialEq)] struct ExecutionNodeSnapshot { @@ -99,12 +84,12 @@ fn snapshot_execution_graph( for task_index in execution_graph.nodes() { let mut deps = execution_graph .neighbors(task_index) - .map(|dep_index| TaskIdSnapshot::new(dep_index, base_dir, indexed_task_graph)) + .map(|dep_index| TaskIdSnapshot::new(dep_index, indexed_task_graph)) .collect::>(); deps.sort_unstable(); execution_node_snapshots.push(ExecutionNodeSnapshot { - task: TaskIdSnapshot::new(task_index, base_dir, indexed_task_graph), + task: TaskIdSnapshot::new(task_index, indexed_task_graph), deps, }); } @@ -112,35 +97,6 @@ fn snapshot_execution_graph( execution_node_snapshots } -/// Modify absolute paths to be stable across different tmpdir locations. -fn stabilize_absolute_path(path: &mut Arc, base_dir: &AbsolutePath) { - let relative_path = path.strip_prefix(base_dir).unwrap().unwrap(); - // this path is considered absolute on all platforms - let new_base_dir = AbsolutePath::new("//?/workspace/").unwrap(); - *path = new_base_dir.join(relative_path).into(); -} - -/// Modify absolute paths in the SpecifierLookupError to be stable across different tmpdir locations. -fn stabilize_specifier_lookup_error( - err: &mut SpecifierLookupError, - base_dir: &AbsolutePath, -) { - match err { - SpecifierLookupError::AmbiguousPackageName { package_paths, .. } => { - for path in package_paths.iter_mut() { - stabilize_absolute_path(path, base_dir); - } - } - SpecifierLookupError::PackageNameNotFound { .. } => {} - SpecifierLookupError::TaskNameNotFound { package_index, .. } => { - *package_index = Default::default() - } - SpecifierLookupError::PackageUnknown { unspecifier_package_error, .. } => { - stabilize_absolute_path(&mut unspecifier_package_error.cwd, base_dir); - } - } -} - #[derive(serde::Deserialize)] struct CLIQuery { pub name: Str, @@ -180,6 +136,9 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { }; runtime.block_on(async { + let _redaction_guard = + redact_absolute_paths(workspace_root.path.as_path().to_str().unwrap()); + let indexed_task_graph = vite_task_graph::IndexedTaskGraph::load( &workspace_root, &JsonUserConfigLoader::default(), @@ -206,26 +165,21 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { let task_query = match cli_task_query.into_task_query(&cwd) { Ok(ok) => ok, Err(err) => { - insta::assert_debug_snapshot!(snapshot_name, err); + insta::assert_json_snapshot!(snapshot_name, err); continue; } }; let execution_graph = match indexed_task_graph.query_tasks(task_query) { Ok(ok) => ok, - Err(mut err) => { - match &mut err { - TaskQueryError::SpecifierLookupError { lookup_error, .. } => { - stabilize_specifier_lookup_error(lookup_error, &case_stage_path); - } - } - insta::assert_debug_snapshot!(snapshot_name, err); + Err(err) => { + insta::assert_json_snapshot!(snapshot_name, err); continue; } }; let execution_graph_snapshot = - snapshot_execution_graph(&execution_graph, &indexed_task_graph, &case_stage_path); + snapshot_execution_graph(&execution_graph, &indexed_task_graph); insta::assert_json_snapshot!(snapshot_name, execution_graph_snapshot); } }); @@ -236,5 +190,6 @@ fn test_snapshots() { let tokio_runtime = Runtime::new().unwrap(); let tmp_dir = tempfile::tempdir().unwrap(); let tmp_dir_path = AbsolutePath::new(tmp_dir.path()).unwrap(); + insta::glob!("fixtures/*", |case_path| run_case(&tokio_runtime, tmp_dir_path, case_path)); } diff --git a/crates/vite_task/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap index 0fab07dd..eafd86a2 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap @@ -1,24 +1,22 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: err -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- -SpecifierLookupError { - specifier: TaskSpecifier { - package_name: Some( - "@test/a", - ), - task_name: "build", - }, - lookup_error: AmbiguousPackageName { - package_name: "@test/a", - package_paths: [ - AbsolutePath( - "//?/workspace/packages/a", - ), - AbsolutePath( - "//?/workspace/packages/another-a", - ), - ], +{ + "SpecifierLookupError": { + "specifier": { + "package_name": "@test/a", + "task_name": "build" }, + "lookup_error": { + "AmbiguousPackageName": { + "package_name": "@test/a", + "package_paths": [ + "packages/a", + "packages/another-a" + ] + } + } + } } diff --git a/crates/vite_task/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap index f5e5cacc..3ff92394 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: execution_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap index f5e5cacc..3ff92394 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: execution_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap index 32511355..5a65e8f5 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: execution_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap index 662f385a..c267178d 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap @@ -1,6 +1,6 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: err -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- -RecursiveTransitiveConflict +"RecursiveTransitiveConflict" diff --git a/crates/vite_task/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap index b438fcb0..fdc5c1a5 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: execution_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap index 71d68865..41fc332c 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: execution_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap index e01dc8dd..95c858b1 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: execution_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap index c2d9b7b7..3e61cf81 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap @@ -1,16 +1,19 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: err -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- -SpecifierLookupError { - specifier: TaskSpecifier { - package_name: None, - task_name: "non-existent-task", - }, - lookup_error: TaskNameNotFound { - package_name: "@test/a", - task_name: "non-existent-task", - package_index: NodeIndex(PackageIx(0)), +{ + "SpecifierLookupError": { + "specifier": { + "package_name": null, + "task_name": "non-existent-task" }, + "lookup_error": { + "TaskNameNotFound": { + "package_name": "@test/a", + "task_name": "non-existent-task" + } + } + } } diff --git a/crates/vite_task/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap index 5036dfa5..826bebad 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: execution_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap index 71d68865..41fc332c 100644 --- a/crates/vite_task/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: execution_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__task graph@cache-sharing.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@cache-sharing.snap index 684053b5..2f23f391 100644 --- a/crates/vite_task/tests/snapshots/snapshots__task graph@cache-sharing.snap +++ b/crates/vite_task/tests/snapshots/snapshots__task graph@cache-sharing.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: task_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/cache-sharing +input_file: crates/vite_task/tests/fixtures/cache-sharing --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap index 18d8e441..d0e44840 100644 --- a/crates/vite_task/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap +++ b/crates/vite_task/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: task_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph +input_file: crates/vite_task/tests/fixtures/comprehensive-task-graph --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__task graph@conflict-test.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@conflict-test.snap index ad54a4dd..80e5603f 100644 --- a/crates/vite_task/tests/snapshots/snapshots__task graph@conflict-test.snap +++ b/crates/vite_task/tests/snapshots/snapshots__task graph@conflict-test.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: task_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/conflict-test +input_file: crates/vite_task/tests/fixtures/conflict-test --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap index 71a4231f..b609a8d2 100644 --- a/crates/vite_task/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap +++ b/crates/vite_task/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: task_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit +input_file: crates/vite_task/tests/fixtures/dependency-both-topo-and-explicit --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__task graph@empty-package-test.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@empty-package-test.snap index b1414a3a..5508e88e 100644 --- a/crates/vite_task/tests/snapshots/snapshots__task graph@empty-package-test.snap +++ b/crates/vite_task/tests/snapshots/snapshots__task graph@empty-package-test.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: task_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/empty-package-test +input_file: crates/vite_task/tests/fixtures/empty-package-test --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap index a4c34726..f2e12dd0 100644 --- a/crates/vite_task/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: task_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace +input_file: crates/vite_task/tests/fixtures/explicit-deps-workspace --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap index 6e6714cd..67c9f908 100644 --- a/crates/vite_task/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap +++ b/crates/vite_task/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: task_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test +input_file: crates/vite_task/tests/fixtures/fingerprint-ignore-test --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap index 941231f8..fef43080 100644 --- a/crates/vite_task/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: task_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspace +input_file: crates/vite_task/tests/fixtures/recursive-topological-workspace --- [ { diff --git a/crates/vite_task/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap b/crates/vite_task/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap index 62952fda..98b23222 100644 --- a/crates/vite_task/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap +++ b/crates/vite_task/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap @@ -1,7 +1,7 @@ --- -source: crates/vite_task_graph/tests/snapshots.rs +source: crates/vite_task/tests/snapshots.rs expression: task_graph_snapshot -input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +input_file: crates/vite_task/tests/fixtures/transitive-dependency-workspace --- [ { diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 7b8ce4b1..755929a2 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -116,7 +116,7 @@ pub enum TaskGraphLoadError { /// /// - When the specifier is from `dependOn` of a known task, `UnknownPackageError` is `Infallible` because the origin package is always known. /// - When the specifier is from a CLI command, `UnknownPackageError` can be a real error type in case cwd is not in any package. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Serialize)] pub enum SpecifierLookupError { #[error("Package '{package_name}' is ambiguous among multiple packages: {package_paths:?}")] AmbiguousPackageName { package_name: Str, package_paths: Box<[Arc]> }, @@ -125,7 +125,12 @@ pub enum SpecifierLookupError { PackageNameNotFound { package_name: Str }, #[error("Task '{task_name}' not found in package {package_name}")] - TaskNameNotFound { package_name: Str, task_name: Str, package_index: PackageNodeIndex }, + TaskNameNotFound { + package_name: Str, + task_name: Str, + #[serde(skip)] + package_index: PackageNodeIndex, + }, #[error( "Nowhere to look for task '{task_name}' because the package is unknown: {unspecifier_package_error}" diff --git a/crates/vite_task_graph/src/query/cli.rs b/crates/vite_task_graph/src/query/cli.rs index cb5e6e41..6824a5f9 100644 --- a/crates/vite_task_graph/src/query/cli.rs +++ b/crates/vite_task_graph/src/query/cli.rs @@ -1,5 +1,6 @@ use std::{collections::HashSet, sync::Arc}; +use serde::Serialize; use vite_path::AbsolutePath; use vite_str::Str; @@ -27,7 +28,7 @@ pub struct CLITaskQuery { ignore_depends_on: bool, } -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Serialize)] pub enum CLITaskQueryError { #[error("--recursive and --transitive cannot be used together")] RecursiveTransitiveConflict, diff --git a/crates/vite_task_graph/src/query/mod.rs b/crates/vite_task_graph/src/query/mod.rs index eebcc9cc..b5c41519 100644 --- a/crates/vite_task_graph/src/query/mod.rs +++ b/crates/vite_task_graph/src/query/mod.rs @@ -3,6 +3,7 @@ pub mod cli; use std::{collections::HashSet, sync::Arc}; use petgraph::{prelude::DiGraphMap, visit::EdgeRef}; +use serde::Serialize; use vite_path::AbsolutePath; use vite_str::Str; @@ -45,13 +46,13 @@ pub struct TaskQuery { /// The edges represent the final dependency relationships between tasks. No edge weights. pub type TaskExecutionGraph = DiGraphMap; -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Serialize)] #[error("The current working directory {cwd:?} is in not any package")] pub struct PackageUnknownError { pub cwd: Arc, } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Serialize)] pub enum TaskQueryError { #[error("Failed to look up task from specifier: {specifier}")] SpecifierLookupError { diff --git a/crates/vite_task_graph/src/specifier.rs b/crates/vite_task_graph/src/specifier.rs index 5bb2d586..c69241ff 100644 --- a/crates/vite_task_graph/src/specifier.rs +++ b/crates/vite_task_graph/src/specifier.rs @@ -1,12 +1,13 @@ use std::{convert::Infallible, fmt::Display, str::FromStr}; +use serde::Serialize; use vite_str::Str; /// Parsed task specifier (`"packageName#taskName"` or `"taskName"`) /// /// For `taskName`, `package_name` will be `None`. /// For `#taskName`, `package_name` will be `Some("")`. It's valid to have an empty package name. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] pub struct TaskSpecifier { pub package_name: Option, pub task_name: Str, From 3aa9c6eda379fb24393cf142a954e87d4883717a Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 18 Dec 2025 13:08:15 +0800 Subject: [PATCH 08/10] update --- crates/vite_path/src/absolute/redaction.rs | 2 -- crates/vite_task/tests/snapshots.rs | 13 ++++--------- crates/vite_task_plan/src/lib.rs | 6 +++--- crates/vite_task_plan/src/plan.rs | 5 ++++- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/crates/vite_path/src/absolute/redaction.rs b/crates/vite_path/src/absolute/redaction.rs index 7773daa4..dc1287c1 100644 --- a/crates/vite_path/src/absolute/redaction.rs +++ b/crates/vite_path/src/absolute/redaction.rs @@ -1,5 +1,3 @@ -use super::AbsolutePath; - thread_local! { pub(crate) static REDACTION_PREFIX: std::cell::RefCell>> = std::cell::RefCell::new(None); } diff --git a/crates/vite_task/tests/snapshots.rs b/crates/vite_task/tests/snapshots.rs index 4159d6ca..a7b81156 100644 --- a/crates/vite_task/tests/snapshots.rs +++ b/crates/vite_task/tests/snapshots.rs @@ -3,16 +3,14 @@ use std::{path::Path, sync::Arc}; use clap::Parser; use copy_dir::copy_dir; -use insta::internals::Content; use petgraph::visit::EdgeRef as _; -use serde::Serializer; use tokio::runtime::Runtime; use vite_path::{AbsolutePath, RelativePathBuf, redaction::redact_absolute_paths}; use vite_str::Str; use vite_task_graph::{ - IndexedTaskGraph, SpecifierLookupError, TaskDependencyType, TaskNodeIndex, + IndexedTaskGraph, TaskDependencyType, TaskNodeIndex, loader::JsonUserConfigLoader, - query::{PackageUnknownError, TaskExecutionGraph, TaskQueryError, cli::CLITaskQuery}, + query::{TaskExecutionGraph, cli::CLITaskQuery}, }; use vite_workspace::find_workspace_root; @@ -34,10 +32,7 @@ impl TaskIdSnapshot { /// Create a stable json representation of the task graph for snapshot testing. /// /// All paths are relative to `base_dir`. -fn snapshot_task_graph( - indexed_task_graph: &IndexedTaskGraph, - base_dir: &AbsolutePath, -) -> impl serde::Serialize { +fn snapshot_task_graph(indexed_task_graph: &IndexedTaskGraph) -> impl serde::Serialize { #[derive(serde::Serialize)] struct TaskNodeSnapshot { id: TaskIdSnapshot, @@ -146,7 +141,7 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { .await .expect(&format!("Failed to load task graph for case {case_name}")); - let task_graph_snapshot = snapshot_task_graph(&indexed_task_graph, &case_stage_path); + let task_graph_snapshot = snapshot_task_graph(&indexed_task_graph); insta::assert_json_snapshot!("task graph", task_graph_snapshot); for cli_query in cli_queries_file.queries { diff --git a/crates/vite_task_plan/src/lib.rs b/crates/vite_task_plan/src/lib.rs index bf8eb508..15aef1c8 100644 --- a/crates/vite_task_plan/src/lib.rs +++ b/crates/vite_task_plan/src/lib.rs @@ -19,7 +19,7 @@ use plan::{plan_query_request, plan_synthetic_request}; use plan_request::PlanRequest; use vite_path::AbsolutePath; use vite_str::Str; -use vite_task_graph::{TaskGraphLoadError, TaskNodeIndex, query::TaskQuery}; +use vite_task_graph::{TaskGraphLoadError, display::TaskDisplay, query::TaskQuery}; use crate::path_env::prepend_path_env; @@ -60,8 +60,8 @@ pub enum SpawnCommandKind { /// Represents how a task should be executed. It's the node type for the execution graph. Each node corresponds to a task. #[derive(Debug)] pub struct TaskExecution { - /// The task index in the task graph - pub task_node_index: TaskNodeIndex, + /// The task this execution corresponds to + pub task_display: TaskDisplay, /// A task's command is split by `&&` and expanded into multiple execution items. /// diff --git a/crates/vite_task_plan/src/plan.rs b/crates/vite_task_plan/src/plan.rs index b2f095df..4f83a709 100644 --- a/crates/vite_task_plan/src/plan.rs +++ b/crates/vite_task_plan/src/plan.rs @@ -126,7 +126,10 @@ async fn plan_task_as_execution_node( }); } - Ok(TaskExecution { task_node_index, items }) + Ok(TaskExecution { + task_display: context.indexed_task_graph().display_task(task_node_index), + items, + }) } pub fn plan_synthetic_request( From 9c7902affb6a5cfb9d33127d34ff444547bc0aee Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 18 Dec 2025 13:09:16 +0800 Subject: [PATCH 09/10] shear fix --- Cargo.lock | 5 ----- crates/vite_task_graph/Cargo.toml | 4 ---- crates/vite_task_plan/Cargo.toml | 1 - 3 files changed, 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64e2ee18..58bddf31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3274,16 +3274,12 @@ dependencies = [ "anyhow", "async-trait", "clap", - "copy_dir", - "insta", "monostate", "petgraph", "serde", "serde_json", - "tempfile", "thiserror 2.0.17", "tokio", - "toml", "vec1", "vite_path", "vite_str", @@ -3296,7 +3292,6 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "futures-core", "futures-util", "petgraph", "sha2", diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml index 775af226..9df01208 100644 --- a/crates/vite_task_graph/Cargo.toml +++ b/crates/vite_task_graph/Cargo.toml @@ -22,11 +22,7 @@ vite_str = { workspace = true } vite_workspace = { workspace = true } [dev-dependencies] -copy_dir = { workspace = true } -insta = { workspace = true, features = ["glob", "json"] } -tempfile = { workspace = true } tokio = { workspace = true, features = ["fs", "rt-multi-thread"] } -toml = { workspace = true } [lints] workspace = true diff --git a/crates/vite_task_plan/Cargo.toml b/crates/vite_task_plan/Cargo.toml index c5cf51ba..b3ebad6a 100644 --- a/crates/vite_task_plan/Cargo.toml +++ b/crates/vite_task_plan/Cargo.toml @@ -13,7 +13,6 @@ workspace = true [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } -futures-core = { workspace = true } futures-util = { workspace = true } petgraph = { workspace = true } sha2 = { workspace = true } From 309fb9f856af304e3ad8a2f1649e4831362d2c0f Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 18 Dec 2025 13:32:05 +0800 Subject: [PATCH 10/10] fix path redaction --- crates/vite_path/src/absolute/mod.rs | 30 +++++++++++++--------- crates/vite_path/src/absolute/redaction.rs | 10 +++++--- crates/vite_task/tests/snapshots.rs | 3 +-- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/crates/vite_path/src/absolute/mod.rs b/crates/vite_path/src/absolute/mod.rs index fde8b89c..928327cc 100644 --- a/crates/vite_path/src/absolute/mod.rs +++ b/crates/vite_path/src/absolute/mod.rs @@ -30,21 +30,27 @@ impl Serialize for AbsolutePath { where S: serde::Serializer, { - match self.as_path().to_str() { - Some(s) => { - #[cfg(feature = "absolute-redaction")] - let s = redaction::REDACTION_PREFIX.with_borrow(|redaction_prefix| { - if let Some(redaction_prefix) = redaction_prefix - && let Some(stripped_path) = s.strip_prefix(redaction_prefix.as_ref()) - { - return stripped_path.strip_prefix("/").unwrap_or(stripped_path); + #[cfg(feature = "absolute-redaction")] + { + use redaction::REDACTION_PREFIX; + + if let Some(redaction_prefix) = REDACTION_PREFIX + .with(|redaction_prefix| redaction_prefix.borrow().as_ref().map(Arc::clone)) + { + match self.strip_prefix(redaction_prefix) { + Ok(Some(stripped_path)) => return stripped_path.serialize(serializer), + Err(strip_error) => { + return Err(serde::ser::Error::custom(format!( + "Failed to redact absolute path '{}': {}", + self.as_path().display(), + strip_error + ))); } - s - }); - s.serialize(serializer) + Ok(None) => { /* continue to serialize full path */ } + } } - None => Err(serde::ser::Error::custom("path contains invalid UTF-8 characters")), } + self.as_path().serialize(serializer) } } diff --git a/crates/vite_path/src/absolute/redaction.rs b/crates/vite_path/src/absolute/redaction.rs index dc1287c1..5bc81281 100644 --- a/crates/vite_path/src/absolute/redaction.rs +++ b/crates/vite_path/src/absolute/redaction.rs @@ -1,5 +1,9 @@ +use std::sync::Arc; + +use super::AbsolutePath; + thread_local! { - pub(crate) static REDACTION_PREFIX: std::cell::RefCell>> = std::cell::RefCell::new(None); + pub(crate) static REDACTION_PREFIX: std::cell::RefCell>> = std::cell::RefCell::new(None); } #[derive(Debug)] @@ -11,11 +15,11 @@ impl Drop for RedactionGuard { } } -pub fn redact_absolute_paths(prefix: &str) -> RedactionGuard { +pub fn redact_absolute_paths(prefix: &Arc) -> RedactionGuard { REDACTION_PREFIX.with(|redaction_prefix| { let mut redaction_prefix = redaction_prefix.borrow_mut(); assert!(redaction_prefix.is_none(), "RedactionGuard already active"); - *redaction_prefix = Some(prefix.into()); + *redaction_prefix = Some(Arc::clone(&prefix)); }); RedactionGuard(()) } diff --git a/crates/vite_task/tests/snapshots.rs b/crates/vite_task/tests/snapshots.rs index a7b81156..e003ca7d 100644 --- a/crates/vite_task/tests/snapshots.rs +++ b/crates/vite_task/tests/snapshots.rs @@ -131,8 +131,7 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { }; runtime.block_on(async { - let _redaction_guard = - redact_absolute_paths(workspace_root.path.as_path().to_str().unwrap()); + let _redaction_guard = redact_absolute_paths(&workspace_root.path); let indexed_task_graph = vite_task_graph::IndexedTaskGraph::load( &workspace_root,