From 4df07c05d038b0bce0cf86035d896ae578dee864 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 05:48:27 +0800 Subject: [PATCH 01/16] feat: task query # Conflicts: # crates/vite_workspace/src/lib.rs --- Cargo.lock | 50 ++++ Cargo.toml | 1 + crates/vite_path/src/absolute.rs | 8 + crates/vite_task_bin/Cargo.toml | 19 ++ crates/vite_task_bin/src/vite.rs | 18 ++ crates/vite_task_graph/Cargo.toml | 1 + crates/vite_task_graph/src/lib.rs | 278 ++++++++++++-------- crates/vite_task_graph/src/package_graph.rs | 75 ++++++ crates/vite_task_graph/src/query.rs | 197 ++++++++++++++ crates/vite_task_graph/src/specifier.rs | 34 +++ crates/vite_workspace/src/lib.rs | 13 +- 11 files changed, 586 insertions(+), 108 deletions(-) create mode 100644 crates/vite_task_bin/Cargo.toml create mode 100644 crates/vite_task_bin/src/vite.rs create mode 100644 crates/vite_task_graph/src/package_graph.rs create mode 100644 crates/vite_task_graph/src/query.rs create mode 100644 crates/vite_task_graph/src/specifier.rs diff --git a/Cargo.lock b/Cargo.lock index b0bc7079..5df9b957 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -411,6 +411,46 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + [[package]] name = "color-eyre" version = "0.6.5" @@ -3170,11 +3210,21 @@ 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" dependencies = [ "anyhow", + "clap", "copy_dir", "insta", "monostate", diff --git a/Cargo.toml b/Cargo.toml index cc1061af..824adf82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ bstr = { version = "1.12.0", default-features = false, features = ["alloc", "std bumpalo = { version = "3.17.0", features = ["allocator-api2"] } bytemuck = { version = "1.23.0", features = ["extern_crate_alloc", "must_cast"] } cc = "1.2.39" +clap = "4.5.53" color-eyre = "0.6.5" compact_str = "0.9.0" const_format = "0.2.34" diff --git a/crates/vite_path/src/absolute.rs b/crates/vite_path/src/absolute.rs index fc85af8e..89beb90c 100644 --- a/crates/vite_path/src/absolute.rs +++ b/crates/vite_path/src/absolute.rs @@ -38,6 +38,14 @@ impl Hash for AbsolutePath { } } +impl From<&AbsolutePath> for Arc { + fn from(path: &AbsolutePath) -> Self { + let arc: Arc = path.0.into(); + let arc_raw = Arc::into_raw(arc) as *const AbsolutePath; + unsafe { Self::from_raw(arc_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_task_bin/Cargo.toml b/crates/vite_task_bin/Cargo.toml new file mode 100644 index 00000000..58b13455 --- /dev/null +++ b/crates/vite_task_bin/Cargo.toml @@ -0,0 +1,19 @@ +[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 new file mode 100644 index 00000000..b84c2778 --- /dev/null +++ b/crates/vite_task_bin/src/vite.rs @@ -0,0 +1,18 @@ +use clap::Parser; +use vite_str::Str; + +#[derive(Parser)] +enum SubCommand { + Run { + #[clap(flatten)] + query: vite_task_graph::query::CLITaskQuery, + + /// Additional arguments to pass to the tasks + #[clap(last = true)] + args: Vec, + }, +} + +fn main() { + let _subcommand = SubCommand::parse(); +} diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml index 03cee953..42e0ae0f 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 } +clap = { workspace = true, features = ["derive"] } monostate = "1.0.2" petgraph = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index d1537ae2..19e9949d 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -1,21 +1,30 @@ pub mod config; pub mod loader; +mod package_graph; +pub mod query; +mod specifier; use std::{ collections::{HashMap, hash_map::Entry}, + convert::Infallible, sync::Arc, }; use config::{ResolvedUserTaskConfig, UserConfigFile}; +use package_graph::IndexedPackageGraph; use petgraph::{ graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}, visit::{Control, DfsEvent, depth_first_search}, }; use serde::Serialize; +use serde_json::value::Index; +use specifier::TaskSpecifier; use vec1::smallvec_v1::SmallVec1; use vite_path::{AbsolutePath, RelativePathBuf}; use vite_str::Str; -use vite_workspace::{DependencyType, PackageInfo, PackageIx, PackageNodeIndex, WorkspaceRoot}; +use vite_workspace::{ + DependencyType, PackageInfo, PackageIx, PackageNodeIndex, WorkspaceRoot, package, +}; #[derive(Debug, Clone, Copy, Serialize)] enum TaskDependencyTypeInner { @@ -64,16 +73,10 @@ pub struct TaskNode { /// The unique id of this task pub task_id: TaskId, - /// The name of the package where this task is defined. - /// It's used for matching task specifiers ('packageName#taskName') - /// - /// If package.json doesn't have a name field, this will be "", so the task can be matched by `#taskName`. - pub package_name: Str, - /// The resolved configuration of this task. /// /// This contains information affecting how the task is spawn, - /// whereas `task_id` and `package_name` are for looking up the task. + /// whereas `task_id` is for looking up the task. /// /// However, it does not contain external factors like additional args from cli and env vars. pub resolved_config: ResolvedUserTaskConfig, @@ -109,19 +112,30 @@ pub enum TaskGraphLoadError { }, } +/// Error when looking up a task by its specifier. +/// +/// It's generic over `UnknownPackageError`, which is the error type when looking up a task without a package name and without a package origin. +/// +/// - 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)] -pub enum SpecifierLookupError { +pub enum SpecifierLookupError { #[error("Package '{package_name}' is ambiguous among multiple packages: {package_paths:?}")] - AmbiguousPackageName { package_name: Str, package_paths: Box<[RelativePathBuf]> }, + AmbiguousPackageName { package_name: Str, package_paths: Box<[Arc]> }, #[error("Package '{package_name}' not found")] PackageNameNotFound { package_name: Str }, #[error("Task '{task_name}' not found in package {package_name}")] - TaskNameNotFound { package_name: Str, task_name: Str }, + TaskNameNotFound { package_name: Str, task_name: Str, package_index: PackageNodeIndex }, + + #[error( + "Nowhere to look for task '{task_name}' because the package is unknown: {unspecifier_package_error}" + )] + PackageUnknown { unspecifier_package_error: UnknownPackageError, task_name: Str }, } -/// newtype of `DefaultIx` for indices in package graphs +/// newtype of `DefaultIx` for indices in task graphs #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TaskIx(DefaultIx); unsafe impl IndexType for TaskIx { @@ -148,13 +162,10 @@ pub type TaskEdgeIndex = EdgeIndex; pub struct TaskGraph { task_graph: DiGraph, - /// Preserve the package graph for displaying purpose: - /// `self.task_graph` refers packages via PackageNodeIndex. To display package names and paths, we need to lookup them in package_graph. - package_graph: DiGraph, - - /// Grouping package indices by their package names. - /// Due to rare but possible name conflicts in monorepos, we use `SmallVec1` to store multiple dirs for same name. - package_indices_by_name: HashMap>, + /// Preserve the package graph for two purposes: + /// - `self.task_graph` refers packages via PackageNodeIndex. To display package names and paths, we need to lookup them in package_graph. + /// - To find nearest topological tasks when the starting package itself doesn't contain the task with the given name. + indexed_package_graph: IndexedPackageGraph, /// task indices by task id for quick lookup node_indices_by_task_id: HashMap, @@ -169,6 +180,15 @@ impl TaskGraph { let mut task_graph = DiGraph::::default(); let package_graph = vite_workspace::load_package_graph(&workspace_root)?; + // Index package indices by their absolute paths for quick lookup based on cwd + let package_indices_by_paths = package_graph + .node_indices() + .map(|package_index| { + let absolute_path: Arc = + Arc::clone(&package_graph[package_index].absolute_path); + (absolute_path, package_index) + }) + .collect::, PackageNodeIndex>>(); // Record dependency specifiers for each task node to add explicit dependencies later let mut dependency_specifiers_with_task_node_indices: Vec<(Arc<[Str]>, TaskNodeIndex)> = @@ -213,11 +233,7 @@ impl TaskGraph { task_name: task_name.clone(), })?; - let task_node = TaskNode { - task_id, - package_name: package.package_json.name.clone(), - resolved_config, - }; + let task_node = TaskNode { task_id, resolved_config }; let node_index = task_graph.add_node(task_node); dependency_specifiers_with_task_node_indices @@ -231,11 +247,7 @@ impl TaskGraph { &package_dir, package_json_script, ); - task_graph.add_node(TaskNode { - task_id, - package_name: package.package_json.name.clone(), - resolved_config, - }); + task_graph.add_node(TaskNode { task_id, resolved_config }); } } @@ -253,12 +265,12 @@ impl TaskGraph { } } - // Grouping package dirs by their package names. - let mut package_dirs_by_name: HashMap> = + // Grouping package indices by their package names. + let mut package_indices_by_name: HashMap> = HashMap::new(); for package_index in package_graph.node_indices() { let package = &package_graph[package_index]; - match package_dirs_by_name.entry(package.package_json.name.clone()) { + match package_indices_by_name.entry(package.package_json.name.clone()) { Entry::Vacant(vacant) => { vacant.insert(SmallVec1::new(package_index)); } @@ -271,9 +283,8 @@ impl TaskGraph { // Construct `Self` with task_graph with all task nodes ready and indexed, but no edges. let mut me = Self { task_graph, - package_graph, + indexed_package_graph: IndexedPackageGraph::index(package_graph), node_indices_by_task_id, - package_indices_by_name: package_dirs_by_name, }; // Add explicit dependencies @@ -283,7 +294,10 @@ impl TaskGraph { for specifier in dependency_specifiers.iter().cloned() { let to_node_index = me - .get_task_index_by_specifier(&specifier, from_task_id.package_index) + .get_task_index_by_specifier::( + TaskSpecifier::parse_raw(&specifier), + || Ok(from_task_id.package_index), + ) .map_err(|error| TaskGraphLoadError::DependencySpecifierLookupError { error, specifier, @@ -298,93 +312,143 @@ impl TaskGraph { } // Add topological dependencies based on package dependencies - let mut topological_edges = Vec::<(TaskNodeIndex, TaskNodeIndex)>::new(); + let mut nearest_topological_tasks = Vec::::new(); for task_index in me.task_graph.node_indices() { let task_id = &me.task_graph[task_index].task_id; - let task_name = &task_id.task_name; + let task_name = task_id.task_name.as_str(); let package_index = task_id.package_index; - // For every task, - // DFS starting from the package where it's defined. - depth_first_search(&me.package_graph, Some(package_index), |event| { + + // For every task, find nearest tasks with the same name. + // If there is a task with the same name in a deeper dependency, it will be connected via that nearer dependency's task. + me.find_nearest_topological_tasks( + task_name, + package_index, + &mut nearest_topological_tasks, + ); + for nearest_task_index in nearest_topological_tasks.drain(..) { + if let Some(existing_edge_index) = + me.task_graph.find_edge(task_index, nearest_task_index) + { + // If an edge already exists, + let existing_edge = &mut me.task_graph[existing_edge_index]; + match existing_edge.0 { + TaskDependencyTypeInner::Explicit => { + // upgrade from Explicit to Both + existing_edge.0 = TaskDependencyTypeInner::Both; + } + TaskDependencyTypeInner::Topological | TaskDependencyTypeInner::Both => { + // already has topological dependency, do nothing + } + } + } else { + // add new topological edge if not exists + me.task_graph.add_edge( + task_index, + nearest_task_index, + TaskDependencyType(TaskDependencyTypeInner::Topological), + ); + } + } + } + Ok(me) + } + + /// Find the nearest tasks with the given name starting from the given package node index. + /// This method only considers the package graph topology. It doesn't rely on existing topological edges of + /// the task graph, because the starting package itself may not contain a task with the given name + /// + /// The task with the given name in the starting package won't be included in the result even if there's one. + /// + /// This performs a BFS on the package graph starting from `starting_from`, + /// and collects the first found tasks with the given name in each branch. + /// + /// For example, if the package graph is A -> B -> C and A -> D -> C, + /// and we are looking for task "build" starting from A, + /// + /// - No matter A contains "build" or not, it's not included in the result. + /// - If B and D both have a "build" task, both will be returned, but C's "build" task will not be returned + /// because it's not the nearest in either branch. + /// - If B or D doesn't have a "build" task, then C's "build" task will be returned. + fn find_nearest_topological_tasks( + &self, + task_name: &str, + starting_from: PackageNodeIndex, + out: &mut Vec, + ) { + // DFS the package graph starting from `starting_from`, + depth_first_search( + self.indexed_package_graph.package_graph(), + Some(starting_from), + |event| { let DfsEvent::TreeEdge(_, dependency_package_index) = event else { return Control::<()>::Continue; }; - // If a direct or indirect dependency has a task with the same name - if let Some(dependency_task_index) = me.node_indices_by_task_id.get(&TaskId { + + if let Some(dependency_task_index) = self.node_indices_by_task_id.get(&TaskId { package_index: dependency_package_index, - task_name: task_name.clone(), + task_name: task_name.into(), }) { - // form an edge from the current task to that task - topological_edges.push((task_index, *dependency_task_index)); + // Encountered a package containing the task with the same name + // collect the task index + out.push(*dependency_task_index); + // And stop searching further down this branch - // If there is a task with the same name in a deeper dependency, it will be connected via that nearer dependency's task. Control::Prune } else { Control::Continue } - }); - } - for (from_node_index, to_node_index) in topological_edges { - if let Some(existing_edge_index) = - me.task_graph.find_edge(from_node_index, to_node_index) - { - let existing_edge = &mut me.task_graph[existing_edge_index]; - match existing_edge.0 { - TaskDependencyTypeInner::Explicit => { - // upgrade to Both - existing_edge.0 = TaskDependencyTypeInner::Both; - } - TaskDependencyTypeInner::Topological | TaskDependencyTypeInner::Both => { - // already has topological dependency, do nothing - } - } - } else { - // add new topological edge if not exists - me.task_graph.add_edge( - from_node_index, - to_node_index, - TaskDependencyType(TaskDependencyTypeInner::Topological), - ); - } - } - Ok(me) + }, + ); } - /// Lookup the node index of a task by its specifier. + /// Lookup the node index of a task by a specifier. /// /// The specifier can be either 'packageName#taskName' or just 'taskName' (in which case the task in the origin package is looked up). - fn get_task_index_by_specifier( + fn get_task_index_by_specifier( &self, - specifier: &str, - package_origin: PackageNodeIndex, - ) -> Result { - let (package_index, task_name): (PackageNodeIndex, Str) = - if let Some((package_name, task_name)) = specifier.rsplit_once('#') { - // Lookup package path by the package name from '#' - let Some(package_indices) = self.package_indices_by_name.get(package_name) else { - return Err(SpecifierLookupError::PackageNameNotFound { - package_name: package_name.into(), - }); - }; - if package_indices.len() > 1 { - return Err(SpecifierLookupError::AmbiguousPackageName { - package_name: package_name.into(), - package_paths: package_indices - .iter() - .map(|package_index| self.package_graph[*package_index].path.clone()) - .collect(), - }); - }; - (*package_indices.first(), task_name.into()) - } else { - // No '#', so the specifier only contains task name, look up in the origin path package - (package_origin, specifier.into()) + specifier: TaskSpecifier, + get_package_origin: impl FnOnce() -> Result, + ) -> Result> { + let package_index = if let Some(package_name) = specifier.package_name { + // Lookup package path by the package name from '#' + let Some(package_indices) = + self.indexed_package_graph.get_package_indices_by_name(&package_name) + else { + return Err(SpecifierLookupError::PackageNameNotFound { + package_name: package_name.into(), + }); }; - let task_id = TaskId { task_name, package_index }; - let Some(node_index) = self.node_indices_by_task_id.get(&task_id) else { + if package_indices.len() > 1 { + return Err(SpecifierLookupError::AmbiguousPackageName { + package_name: package_name.into(), + package_paths: package_indices + .iter() + .map(|package_index| { + Arc::clone( + &self.indexed_package_graph.package_graph()[*package_index] + .absolute_path, + ) + }) + .collect(), + }); + }; + *package_indices.first() + } else { + // No '#', so the specifier only contains task name, look up in the origin path package + get_package_origin().map_err(|err| SpecifierLookupError::PackageUnknown { + unspecifier_package_error: err, + task_name: specifier.task_name.clone(), + })? + }; + let task_id_to_lookup = TaskId { task_name: specifier.task_name, package_index }; + let Some(node_index) = self.node_indices_by_task_id.get(&task_id_to_lookup) else { return Err(SpecifierLookupError::TaskNameNotFound { - package_name: self.package_graph[package_index].package_json.name.clone(), - task_name: task_id.task_name.clone(), + package_name: self.indexed_package_graph.package_graph()[package_index] + .package_json + .name + .clone(), + task_name: task_id_to_lookup.task_name.clone(), + package_index, }); }; Ok(*node_index) @@ -432,14 +496,20 @@ impl TaskGraph { use petgraph::visit::EdgeRef as _; let target_node = &self.task_graph[edge.target()]; ( - TaskIdSnapshot::from_task_id(&target_node.task_id, &self.package_graph), + TaskIdSnapshot::from_task_id( + &target_node.task_id, + self.indexed_package_graph.package_graph(), + ), *edge.weight(), ) }) .collect(); depends_on.sort_unstable_by(|a, b| a.0.cmp(&b.0)); node_snapshots.push(TaskNodeSnapshot { - id: TaskIdSnapshot::from_task_id(&node.task_id, &self.package_graph), + id: TaskIdSnapshot::from_task_id( + &node.task_id, + self.indexed_package_graph.package_graph(), + ), command: node.resolved_config.command.clone(), cwd: node.resolved_config.cwd.strip_prefix(base_dir).unwrap().unwrap(), depends_on, diff --git a/crates/vite_task_graph/src/package_graph.rs b/crates/vite_task_graph/src/package_graph.rs new file mode 100644 index 00000000..1c41dfe3 --- /dev/null +++ b/crates/vite_task_graph/src/package_graph.rs @@ -0,0 +1,75 @@ +use std::{ + collections::{HashMap, hash_map::Entry}, + sync::Arc, +}; + +use petgraph::graph::DiGraph; +use vec1::smallvec_v1::SmallVec1; +use vite_path::AbsolutePath; +use vite_str::Str; +use vite_workspace::{DependencyType, PackageInfo, PackageIx, PackageNodeIndex}; + +/// Package graph with additional HashMaps for quick task lookup +pub struct IndexedPackageGraph { + package_graph: DiGraph, + + /// Grouping package indices by their package names. + /// Due to rare but possible name conflicts in monorepos, we use `SmallVec1` to store multiple dirs for same name. + package_indices_by_name: HashMap>, + + /// package indices by their absolute paths for quick lookup based on cwd + package_indices_by_paths: HashMap, PackageNodeIndex>, +} + +impl IndexedPackageGraph { + pub fn index(package_graph: DiGraph) -> Self { + // Index package indices by their absolute paths for quick lookup based on cwd + let package_indices_by_paths = package_graph + .node_indices() + .map(|package_index| { + let absolute_path: Arc = + Arc::clone(&package_graph[package_index].absolute_path); + (absolute_path, package_index) + }) + .collect::, PackageNodeIndex>>(); + + // Grouping package indices by their package names. + let mut package_indices_by_name: HashMap> = + HashMap::new(); + for package_index in package_graph.node_indices() { + let package = &package_graph[package_index]; + match package_indices_by_name.entry(package.package_json.name.clone()) { + Entry::Vacant(vacant) => { + vacant.insert(SmallVec1::new(package_index)); + } + Entry::Occupied(occupied) => { + occupied.into_mut().push(package_index); + } + } + } + Self { package_graph, package_indices_by_name, package_indices_by_paths } + } + + /// Get package index from a given current working directory by traversing up the directory tree. + pub fn get_package_index_from_cwd(&self, cwd: &AbsolutePath) -> Option { + let mut cur_path = cwd; + loop { + if let Some(package_index) = self.package_indices_by_paths.get(cur_path) { + return Some(*package_index); + } + cur_path = cur_path.parent()?; + } + } + + /// Get package indices by package name. + pub fn get_package_indices_by_name( + &self, + package_name: &Str, + ) -> Option<&SmallVec1<[PackageNodeIndex; 1]>> { + self.package_indices_by_name.get(package_name) + } + + pub fn package_graph(&self) -> &DiGraph { + &self.package_graph + } +} diff --git a/crates/vite_task_graph/src/query.rs b/crates/vite_task_graph/src/query.rs new file mode 100644 index 00000000..29996806 --- /dev/null +++ b/crates/vite_task_graph/src/query.rs @@ -0,0 +1,197 @@ +use core::task; +use std::{collections::HashSet, sync::Arc}; + +use clap::{Parser, error}; +use petgraph::{prelude::DiGraphMap, visit::EdgeRef}; +use vite_path::AbsolutePath; +use vite_str::Str; + +use crate::{SpecifierLookupError, TaskGraph, TaskNodeIndex, specifier::TaskSpecifier}; + +/// Different kinds of task queries. +pub enum TaskQueryKind { + /// A normal task query specifying task specifiers and options. + Normal { + task_specifiers: HashSet, + /// Where the query is being run from. + cwd: Arc, + /// Whether to include topological dependencies + include_topological_deps: bool, + }, + /// A recursive task query specifying only a task name. + /// It will match all tasks with the given names across all packages with topological ordering. + Resursive { task_names: HashSet }, +} + +/// Represents a valid query for a task and its dependencies, usually issued from a CLI command `vite run ...`. +/// A query represented by this struct is always valid, but still may result in no tasks found. +pub struct TaskQuery { + /// The kind of task query + pub kind: TaskQueryKind, + /// Whether to include explicit dependencies + pub include_explicit_deps: bool, +} + +/// A task execution graph queried from a `TaskQuery`. +/// +/// The nodes are task indices for `TaskGraph`. +/// The edges represent the final dependency relationships between tasks. No edge weights. +pub type TaskExecutionGraph = DiGraphMap; + +#[derive(Debug, thiserror::Error)] +#[error("The current working directory {cwd:?} is in not any package")] +pub struct PackageUnknownError { + cwd: Arc, +} + +impl TaskGraph { + pub fn query_tasks( + &self, + query: TaskQuery, + ) -> Result> { + let mut execution_graph = TaskExecutionGraph::default(); + match query.kind { + TaskQueryKind::Normal { task_specifiers, cwd, include_topological_deps } => { + let package_index_from_cwd = + self.indexed_package_graph.get_package_index_from_cwd(&cwd); + + let mut nearest_topological_tasks = Vec::::new(); + + // For every task specifier + for specifier in task_specifiers { + // Find the starting task + let starting_task_result = + self.get_task_index_by_specifier(specifier.clone(), || { + package_index_from_cwd + .ok_or_else(|| PackageUnknownError { cwd: Arc::clone(&cwd) }) + }); + + match starting_task_result { + Ok(starting_task) => { + // Found it, add to execution graph + execution_graph.add_node(starting_task); + } + // Task not found, but package located, and the query requests topological deps + // This happens when running `vite run --transitive taskName` in a package without `taskName`, but its dependencies have it. + Err(err @ SpecifierLookupError::TaskNameNotFound { package_index, .. }) + if include_topological_deps => + { + // try to find nearest task + self.find_nearest_topological_tasks( + &specifier.task_name, + package_index, + &mut nearest_topological_tasks, + ); + if nearest_topological_tasks.is_empty() { + // No nearest task found, return original error + return Err(err); + } + // Add nearest tasks to execution graph + for nearest_task in nearest_topological_tasks.drain(..) { + execution_graph.add_node(nearest_task); + } + } + Err(err) => { + // Not recoverable by finding nearest package, return error + return Err(err); + } + } + } + todo!() + } + TaskQueryKind::Resursive { task_names } => { + // Add all tasks matching the names + for task_index in self.task_graph.node_indices() { + let current_task_name = self.task_graph[task_index].task_id.task_name.as_str(); + if task_names.contains(current_task_name) { + execution_graph.add_node(task_index); + } + } + // Add topological edges + let mut topo_edges = Vec::<(TaskNodeIndex, TaskNodeIndex)>::with_capacity( + execution_graph.node_count(), + ); + for task_index in execution_graph.nodes() { + // for each added task + // Go through its dependencies + for edge_ref in self.task_graph.edges(task_index) { + let dep_index = edge_ref.target(); + if edge_ref.weight().is_topological() + && execution_graph.contains_node(dep_index) + { + // only add edge if it's topological and the dependency is also in the execution graph + topo_edges.push((task_index, dep_index)); + } + } + } + for (source, target) in topo_edges { + execution_graph.add_edge(source, target, ()); + } + } + } + Ok(execution_graph) + } +} + +/// Represents task query args of `vite run` +/// It will be converted to `TaskQuery`, but may be invalid, if so the error is returned early before loading the task graph. +#[derive(Debug, clap::Parser)] +pub struct CLITaskQuery { + /// Specifies one or multiple tasks to run, in form of `packageName#taskName` or `taskName`. + tasks: Vec, + + /// 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, +} + +#[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 CLITaskQuery { + /// Convert to `TaskQuery`, or return an error if invalid. + pub fn into_task_query(self, cwd: &Arc) -> Result { + let include_explicit_deps = !self.ignore_depends_on; + + let kind = if self.recursive { + if self.transitive { + return Err(CLITaskQueryError::RecursiveTransitiveConflict); + } + let task_names: HashSet = self + .tasks + .into_iter() + .map(|s| { + if let Some(package_name) = s.package_name { + return Err(CLITaskQueryError::PackageNameSpecifiedWithRecursive { + package_name, + task_name: s.task_name, + }); + } + Ok(s.task_name) + }) + .collect::>()?; + TaskQueryKind::Resursive { task_names } + } else { + TaskQueryKind::Normal { + task_specifiers: self.tasks.into_iter().collect(), + cwd: Arc::clone(cwd), + include_topological_deps: self.transitive, + } + }; + Ok(TaskQuery { kind, include_explicit_deps }) + } +} diff --git a/crates/vite_task_graph/src/specifier.rs b/crates/vite_task_graph/src/specifier.rs new file mode 100644 index 00000000..3d25de32 --- /dev/null +++ b/crates/vite_task_graph/src/specifier.rs @@ -0,0 +1,34 @@ +use std::{convert::Infallible, str::FromStr}; + +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)] +pub struct TaskSpecifier { + pub package_name: Option, + pub task_name: Str, +} + +impl TaskSpecifier { + pub fn parse_raw(raw_specifier: &str) -> Self { + if let Some((package_name, task_name)) = raw_specifier.rsplit_once('#') { + TaskSpecifier { + package_name: Some(Str::from(package_name)), + task_name: Str::from(task_name), + } + } else { + TaskSpecifier { package_name: None, task_name: Str::from(raw_specifier) } + } + } +} + +impl FromStr for TaskSpecifier { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(TaskSpecifier::parse_raw(s)) + } +} diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index 36ebe03f..d8dd9c75 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -96,10 +96,11 @@ impl WorkspaceMemberGlobs { } } -#[derive(Debug, Deserialize, Serialize, Clone, Default)] +#[derive(Debug, Clone)] pub struct PackageInfo { pub package_json: PackageJson, pub path: RelativePathBuf, + pub absolute_path: Arc, } #[derive(Default)] @@ -113,7 +114,11 @@ impl PackageGraphBuilder { fn add_package(&mut self, package_path: RelativePathBuf, package_json: PackageJson) { let deps = package_json.get_workspace_dependencies().collect::>(); let package_name = package_json.name.clone(); - let id = self.graph.add_node(PackageInfo { package_json, path: package_path.clone() }); + let id = self.graph.add_node(PackageInfo { + package_json, + path: package_path.clone(), + absolute_path, + }); // Always store by path self.id_and_deps_by_path.insert(package_path.clone(), (id, deps)); @@ -213,8 +218,8 @@ pub fn load_package_graph( let mut has_root_package = false; for package_json_path in member_globs.get_package_json_paths(workspace_root.path)? { let package_json: PackageJson = serde_json::from_slice(&fs::read(&package_json_path)?)?; - let package_path = package_json_path.parent().unwrap(); - let Some(package_path) = package_path.strip_prefix(workspace_root.path)? else { + let absolute_path = package_json_path.parent().unwrap(); + let Some(package_path) = absolute_path.strip_prefix(workspace_root.path)? else { return Err(Error::PackageOutsideWorkspace { package_path: package_json_path, workspace_root: workspace_root.path.to_absolute_path_buf(), From d085999813a23d3af65e56b8b886b16588228111 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 06:42:16 +0800 Subject: [PATCH 02/16] update --- crates/vite_task_bin/src/vite.rs | 3 +- crates/vite_task_graph/src/lib.rs | 4 +- crates/vite_task_graph/src/query/cli.rs | 71 ++++++++ .../src/{query.rs => query/mod.rs} | 158 ++++++++---------- 4 files changed, 146 insertions(+), 90 deletions(-) create mode 100644 crates/vite_task_graph/src/query/cli.rs rename crates/vite_task_graph/src/{query.rs => query/mod.rs} (55%) diff --git a/crates/vite_task_bin/src/vite.rs b/crates/vite_task_bin/src/vite.rs index b84c2778..9b675bfa 100644 --- a/crates/vite_task_bin/src/vite.rs +++ b/crates/vite_task_bin/src/vite.rs @@ -3,9 +3,10 @@ use vite_str::Str; #[derive(Parser)] enum SubCommand { + /// Run tasks Run { #[clap(flatten)] - query: vite_task_graph::query::CLITaskQuery, + query: vite_task_graph::query::cli::CLITaskQuery, /// Additional arguments to pass to the tasks #[clap(last = true)] diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 19e9949d..0a9218cf 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -44,11 +44,11 @@ pub struct TaskDependencyType(TaskDependencyTypeInner); // It hides `TaskDependencyTypeInner` and only expose `is_explicit`/`is_topological` // to avoid incorrectly matching only Explicit variant to check if it's explicit. impl TaskDependencyType { - pub fn is_explicit(&self) -> bool { + pub fn is_explicit(self) -> bool { matches!(self.0, TaskDependencyTypeInner::Explicit | TaskDependencyTypeInner::Both) } - pub fn is_topological(&self) -> bool { + pub fn is_topological(self) -> bool { matches!(self.0, TaskDependencyTypeInner::Topological | TaskDependencyTypeInner::Both) } } diff --git a/crates/vite_task_graph/src/query/cli.rs b/crates/vite_task_graph/src/query/cli.rs new file mode 100644 index 00000000..6336fb9f --- /dev/null +++ b/crates/vite_task_graph/src/query/cli.rs @@ -0,0 +1,71 @@ +use std::{collections::HashSet, sync::Arc}; + +use vite_path::AbsolutePath; +use vite_str::Str; + +use super::TaskQueryKind; +use crate::{query::TaskQuery, specifier::TaskSpecifier}; + +/// Represents task query args of `vite run` +/// It will be converted to `TaskQuery`, but may be invalid (contains conflicting options), +/// if so the error is returned early before loading the task graph. +#[derive(Debug, clap::Parser)] +pub struct CLITaskQuery { + /// Specifies one or multiple tasks to run, in form of `packageName#taskName` or `taskName`. + tasks: Vec, + + /// 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, +} + +#[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 CLITaskQuery { + /// Convert to `TaskQuery`, or return an error if invalid. + pub fn into_task_query(self, cwd: &Arc) -> Result { + let include_explicit_deps = !self.ignore_depends_on; + + let kind = if self.recursive { + if self.transitive { + return Err(CLITaskQueryError::RecursiveTransitiveConflict); + } + let task_names: HashSet = self + .tasks + .into_iter() + .map(|s| { + if let Some(package_name) = s.package_name { + return Err(CLITaskQueryError::PackageNameSpecifiedWithRecursive { + package_name, + task_name: s.task_name, + }); + } + Ok(s.task_name) + }) + .collect::>()?; + TaskQueryKind::Resursive { task_names } + } else { + TaskQueryKind::Normal { + task_specifiers: self.tasks.into_iter().collect(), + cwd: Arc::clone(cwd), + include_topological_deps: self.transitive, + } + }; + Ok(TaskQuery { kind, include_explicit_deps }) + } +} diff --git a/crates/vite_task_graph/src/query.rs b/crates/vite_task_graph/src/query/mod.rs similarity index 55% rename from crates/vite_task_graph/src/query.rs rename to crates/vite_task_graph/src/query/mod.rs index 29996806..dfff38a1 100644 --- a/crates/vite_task_graph/src/query.rs +++ b/crates/vite_task_graph/src/query/mod.rs @@ -1,16 +1,23 @@ -use core::task; +pub mod cli; + use std::{collections::HashSet, sync::Arc}; -use clap::{Parser, error}; -use petgraph::{prelude::DiGraphMap, visit::EdgeRef}; +use petgraph::{ + prelude::DiGraphMap, + visit::{Control, DfsEvent, EdgeRef, depth_first_search}, +}; use vite_path::AbsolutePath; use vite_str::Str; -use crate::{SpecifierLookupError, TaskGraph, TaskNodeIndex, specifier::TaskSpecifier}; +use crate::{ + SpecifierLookupError, TaskDependencyType, TaskGraph, TaskNodeIndex, specifier::TaskSpecifier, +}; /// Different kinds of task queries. +#[derive(Debug)] pub enum TaskQueryKind { /// A normal task query specifying task specifiers and options. + /// The tasks will be searched in packages in task specifiers, or located from cwd. Normal { task_specifiers: HashSet, /// Where the query is being run from. @@ -18,13 +25,15 @@ pub enum TaskQueryKind { /// Whether to include topological dependencies include_topological_deps: bool, }, - /// A recursive task query specifying only a task name. + /// A recursive task query specifying one or multiple task names. /// It will match all tasks with the given names across all packages with topological ordering. + /// The whole workspace will be searched, so cwd is not relevant. Resursive { task_names: HashSet }, } /// Represents a valid query for a task and its dependencies, usually issued from a CLI command `vite run ...`. /// A query represented by this struct is always valid, but still may result in no tasks found. +#[derive(Debug)] pub struct TaskQuery { /// The kind of task query pub kind: TaskQueryKind, @@ -50,6 +59,13 @@ impl TaskGraph { query: TaskQuery, ) -> Result> { let mut execution_graph = TaskExecutionGraph::default(); + + let include_topologicial_deps = match &query.kind { + TaskQueryKind::Normal { include_topological_deps, .. } => *include_topological_deps, + TaskQueryKind::Resursive { .. } => true, // recursive means topological across all packages + }; + + // Add starting tasks without dependencies match query.kind { TaskQueryKind::Normal { task_specifiers, cwd, include_topological_deps } => { let package_index_from_cwd = @@ -57,7 +73,7 @@ impl TaskGraph { let mut nearest_topological_tasks = Vec::::new(); - // For every task specifier + // For every task specifier, add matching tasks for specifier in task_specifiers { // Find the starting task let starting_task_result = @@ -87,6 +103,7 @@ impl TaskGraph { return Err(err); } // Add nearest tasks to execution graph + // Topoplogical dependencies of nearest tasks will be added later for nearest_task in nearest_topological_tasks.drain(..) { execution_graph.add_node(nearest_task); } @@ -97,101 +114,68 @@ impl TaskGraph { } } } - todo!() + + // Matching tasks have been added, now add topological dependencies + if include_topological_deps { + self.add_dependencies(&mut execution_graph, TaskDependencyType::is_topological); + } } TaskQueryKind::Resursive { task_names } => { - // Add all tasks matching the names + // Add all tasks matching the names across all packages for task_index in self.task_graph.node_indices() { let current_task_name = self.task_graph[task_index].task_id.task_name.as_str(); if task_names.contains(current_task_name) { execution_graph.add_node(task_index); } } - // Add topological edges - let mut topo_edges = Vec::<(TaskNodeIndex, TaskNodeIndex)>::with_capacity( - execution_graph.node_count(), - ); - for task_index in execution_graph.nodes() { - // for each added task - // Go through its dependencies - for edge_ref in self.task_graph.edges(task_index) { - let dep_index = edge_ref.target(); - if edge_ref.weight().is_topological() - && execution_graph.contains_node(dep_index) - { - // only add edge if it's topological and the dependency is also in the execution graph - topo_edges.push((task_index, dep_index)); - } - } - } - for (source, target) in topo_edges { - execution_graph.add_edge(source, target, ()); - } } } - Ok(execution_graph) - } -} - -/// Represents task query args of `vite run` -/// It will be converted to `TaskQuery`, but may be invalid, if so the error is returned early before loading the task graph. -#[derive(Debug, clap::Parser)] -pub struct CLITaskQuery { - /// Specifies one or multiple tasks to run, in form of `packageName#taskName` or `taskName`. - tasks: Vec, - - /// 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, -} - -#[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 }, -} + // Add dependencies as requested + // The order matters: add topological dependencies first, then explicit dependencies. + // We don't want to include topological dependencies of explicit dependencies even both types are requested. + if include_topologicial_deps { + self.add_dependencies(&mut execution_graph, TaskDependencyType::is_topological); + } + if query.include_explicit_deps { + self.add_dependencies(&mut execution_graph, TaskDependencyType::is_explicit); + } -impl CLITaskQuery { - /// Convert to `TaskQuery`, or return an error if invalid. - pub fn into_task_query(self, cwd: &Arc) -> Result { - let include_explicit_deps = !self.ignore_depends_on; + Ok(execution_graph) + } - let kind = if self.recursive { - if self.transitive { - return Err(CLITaskQueryError::RecursiveTransitiveConflict); - } - let task_names: HashSet = self - .tasks - .into_iter() - .map(|s| { - if let Some(package_name) = s.package_name { - return Err(CLITaskQueryError::PackageNameSpecifiedWithRecursive { - package_name, - task_name: s.task_name, - }); + /// Recursively add dependencies to the execution graph based on filtered edges in the task graph + fn add_dependencies( + &self, + execution_graph: &mut TaskExecutionGraph, + mut filter_edge: impl FnMut(TaskDependencyType) -> bool, + ) { + let mut current_starting_node_indices: HashSet = + execution_graph.nodes().collect(); + + // Continue until no new nodes are added + while !current_starting_node_indices.is_empty() { + // Record newly added nodes in this iteration as starting nodes for next iteration + let mut next_starting_node_indices = HashSet::::new(); + + for from_node in current_starting_node_indices { + // For each starting node, traverse its outgoing edges + for edge_ref in self.task_graph.edges(from_node) { + let to_node = edge_ref.target(); + let dependency_type = edge_ref.weight(); + if filter_edge(*dependency_type) { + let is_to_node_new = !execution_graph.contains_node(to_node); + // Add the dependency edge + execution_graph.add_edge(from_node, to_node, ()); + + // Add to_node for next iteration if it's newly added. + if is_to_node_new { + next_starting_node_indices.insert(to_node); + } } - Ok(s.task_name) - }) - .collect::>()?; - TaskQueryKind::Resursive { task_names } - } else { - TaskQueryKind::Normal { - task_specifiers: self.tasks.into_iter().collect(), - cwd: Arc::clone(cwd), - include_topological_deps: self.transitive, + } } - }; - Ok(TaskQuery { kind, include_explicit_deps }) + current_starting_node_indices = next_starting_node_indices; + } } } From a2e868313cbdbb7a77d69d15e0aaa09e1302833a Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 06:44:28 +0800 Subject: [PATCH 03/16] update --- crates/vite_task_graph/src/lib.rs | 16 ++-------------- crates/vite_task_graph/src/query/mod.rs | 10 +--------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 0a9218cf..9c78ba5a 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -17,14 +17,11 @@ use petgraph::{ visit::{Control, DfsEvent, depth_first_search}, }; use serde::Serialize; -use serde_json::value::Index; use specifier::TaskSpecifier; use vec1::smallvec_v1::SmallVec1; -use vite_path::{AbsolutePath, RelativePathBuf}; +use vite_path::AbsolutePath; use vite_str::Str; -use vite_workspace::{ - DependencyType, PackageInfo, PackageIx, PackageNodeIndex, WorkspaceRoot, package, -}; +use vite_workspace::{DependencyType, PackageInfo, PackageIx, PackageNodeIndex, WorkspaceRoot}; #[derive(Debug, Clone, Copy, Serialize)] enum TaskDependencyTypeInner { @@ -180,15 +177,6 @@ impl TaskGraph { let mut task_graph = DiGraph::::default(); let package_graph = vite_workspace::load_package_graph(&workspace_root)?; - // Index package indices by their absolute paths for quick lookup based on cwd - let package_indices_by_paths = package_graph - .node_indices() - .map(|package_index| { - let absolute_path: Arc = - Arc::clone(&package_graph[package_index].absolute_path); - (absolute_path, package_index) - }) - .collect::, PackageNodeIndex>>(); // 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/src/query/mod.rs b/crates/vite_task_graph/src/query/mod.rs index dfff38a1..a334eaa5 100644 --- a/crates/vite_task_graph/src/query/mod.rs +++ b/crates/vite_task_graph/src/query/mod.rs @@ -2,10 +2,7 @@ pub mod cli; use std::{collections::HashSet, sync::Arc}; -use petgraph::{ - prelude::DiGraphMap, - visit::{Control, DfsEvent, EdgeRef, depth_first_search}, -}; +use petgraph::{prelude::DiGraphMap, visit::EdgeRef}; use vite_path::AbsolutePath; use vite_str::Str; @@ -114,11 +111,6 @@ impl TaskGraph { } } } - - // Matching tasks have been added, now add topological dependencies - if include_topological_deps { - self.add_dependencies(&mut execution_graph, TaskDependencyType::is_topological); - } } TaskQueryKind::Resursive { task_names } => { // Add all tasks matching the names across all packages From 7f31cd987df74ffaee365c31bafdcd78bb6655b5 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 07:03:34 +0800 Subject: [PATCH 04/16] move snapshot method to test file --- crates/vite_task_graph/src/lib.rs | 76 +++----------------- crates/vite_task_graph/src/query/mod.rs | 5 +- crates/vite_task_graph/tests/snapshots.rs | 86 +++++++++++++++++++++-- 3 files changed, 93 insertions(+), 74 deletions(-) diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 9c78ba5a..8f2fca99 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -152,11 +152,11 @@ unsafe impl IndexType for TaskIx { pub type TaskNodeIndex = NodeIndex; pub type TaskEdgeIndex = EdgeIndex; -/// Full task graph of a workspace. +/// Full task graph of a workspace, with necessary HashMaps for quick task lookup /// /// It's immutable after created. The task nodes contain resolved task configurations and their dependencies. /// External factors (e.g. additional args from cli, current working directory, environmental variables) are not stored here. -pub struct TaskGraph { +pub struct IndexedTaskGraph { task_graph: DiGraph, /// Preserve the package graph for two purposes: @@ -168,7 +168,7 @@ pub struct TaskGraph { node_indices_by_task_id: HashMap, } -impl TaskGraph { +impl IndexedTaskGraph { /// Load the task graph from a discovered workspace using the provided config loader. pub async fn load( workspace_root: WorkspaceRoot<'_>, @@ -442,69 +442,15 @@ impl TaskGraph { Ok(*node_index) } - /// Create a stable json representation of the task graph for snapshot testing. - /// - /// All paths are relative to `base_dir`. - pub fn snapshot(&self, base_dir: &AbsolutePath) -> serde_json::Value { - use vite_path::RelativePathBuf; - - #[derive(serde::Serialize, PartialEq, PartialOrd, Eq, Ord)] - struct TaskIdSnapshot { - package_dir: RelativePathBuf, - task_name: Str, - } - impl TaskIdSnapshot { - fn from_task_id( - task_id: &TaskId, - package_graph: &DiGraph, - ) -> Self { - Self { - task_name: task_id.task_name.clone(), - package_dir: package_graph[task_id.package_index].path.clone(), - } - } - } - - #[derive(serde::Serialize)] - struct TaskNodeSnapshot { - id: TaskIdSnapshot, - command: Str, - cwd: RelativePathBuf, - depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)>, - } + pub fn task_graph(&self) -> &DiGraph { + &self.task_graph + } - let mut node_snapshots = - Vec::::with_capacity(self.task_graph.node_count()); - for a in self.task_graph.node_indices() { - let node = &self.task_graph[a]; - let mut depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)> = self - .task_graph - .edges_directed(a, petgraph::Direction::Outgoing) - .map(|edge| { - use petgraph::visit::EdgeRef as _; - let target_node = &self.task_graph[edge.target()]; - ( - TaskIdSnapshot::from_task_id( - &target_node.task_id, - self.indexed_package_graph.package_graph(), - ), - *edge.weight(), - ) - }) - .collect(); - depends_on.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - node_snapshots.push(TaskNodeSnapshot { - id: TaskIdSnapshot::from_task_id( - &node.task_id, - self.indexed_package_graph.package_graph(), - ), - command: node.resolved_config.command.clone(), - cwd: node.resolved_config.cwd.strip_prefix(base_dir).unwrap().unwrap(), - depends_on, - }); - } - node_snapshots.sort_unstable_by(|a, b| a.id.cmp(&b.id)); + pub fn get_package_name(&self, package_index: PackageNodeIndex) -> &str { + self.indexed_package_graph.package_graph()[package_index].package_json.name.as_str() + } - serde_json::to_value(&node_snapshots).unwrap() + pub fn get_package_path(&self, package_index: PackageNodeIndex) -> &Arc { + &self.indexed_package_graph.package_graph()[package_index].absolute_path } } diff --git a/crates/vite_task_graph/src/query/mod.rs b/crates/vite_task_graph/src/query/mod.rs index a334eaa5..876938ff 100644 --- a/crates/vite_task_graph/src/query/mod.rs +++ b/crates/vite_task_graph/src/query/mod.rs @@ -7,7 +7,8 @@ use vite_path::AbsolutePath; use vite_str::Str; use crate::{ - SpecifierLookupError, TaskDependencyType, TaskGraph, TaskNodeIndex, specifier::TaskSpecifier, + IndexedTaskGraph, SpecifierLookupError, TaskDependencyType, TaskNodeIndex, + specifier::TaskSpecifier, }; /// Different kinds of task queries. @@ -50,7 +51,7 @@ pub struct PackageUnknownError { cwd: Arc, } -impl TaskGraph { +impl IndexedTaskGraph { pub fn query_tasks( &self, query: TaskQuery, diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index 48e0c037..d8f17a23 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -2,10 +2,80 @@ use std::path::Path; use copy_dir::copy_dir; use tokio::runtime::Runtime; -use vite_path::AbsolutePath; -use vite_task_graph::loader::JsonUserConfigLoader; +use vite_path::{AbsolutePath, RelativePathBuf}; +use vite_str::Str; +use vite_task_graph::{IndexedTaskGraph, TaskDependencyType, TaskId, loader::JsonUserConfigLoader}; use vite_workspace::find_workspace_root; +/// 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, PartialEq, PartialOrd, Eq, Ord)] + struct TaskIdSnapshot { + package_dir: RelativePathBuf, + task_name: Str, + } + impl TaskIdSnapshot { + fn from_task_id( + task_id: &TaskId, + base_dir: &AbsolutePath, + indexed_task_graph: &IndexedTaskGraph, + ) -> Self { + 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(), + } + } + } + + #[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| { + use petgraph::visit::EdgeRef as _; + let target_node = &task_graph[edge.target()]; + ( + TaskIdSnapshot::from_task_id( + &target_node.task_id, + 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::from_task_id(&task_node.task_id, base_dir, indexed_task_graph), + command: task_node.resolved_config.command.clone(), + cwd: task_node.resolved_config.cwd.strip_prefix(base_dir).unwrap().unwrap(), + depends_on, + }); + } + node_snapshots.sort_unstable_by(|a, b| a.id.cmp(&b.id)); + + node_snapshots +} + 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(".") { @@ -25,11 +95,13 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { ); runtime.block_on(async { - let task_graph = - vite_task_graph::TaskGraph::load(workspace_root, JsonUserConfigLoader::default()) - .await - .expect(&format!("Failed to load task graph for case {case_name}")); - let task_graph_snapshot = task_graph.snapshot(&case_stage_path); + 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); }); } From b9661b9d140b46394972571a13b8ae96983cf421 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 07:35:26 +0800 Subject: [PATCH 05/16] init execution graph snapshot testing --- Cargo.lock | 1 + crates/vite_task_graph/Cargo.toml | 1 + .../cli-queries.toml | 4 + crates/vite_task_graph/tests/snapshots.rs | 138 +++++++++++++++--- 4 files changed, 120 insertions(+), 24 deletions(-) create mode 100644 crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml diff --git a/Cargo.lock b/Cargo.lock index 5df9b957..8ece5003 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3234,6 +3234,7 @@ dependencies = [ "tempfile", "thiserror 2.0.17", "tokio", + "toml", "vec1", "vite_path", "vite_str", diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml index 42e0ae0f..15ebfb2a 100644 --- a/crates/vite_task_graph/Cargo.toml +++ b/crates/vite_task_graph/Cargo.toml @@ -25,6 +25,7 @@ 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_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml new file mode 100644 index 00000000..3c27785a --- /dev/null +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml @@ -0,0 +1,4 @@ +[[query]] +name = "simple task by name" +cwd = "packages/a" +args = ["build"] diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index d8f17a23..31e98836 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -1,12 +1,40 @@ -use std::path::Path; +use core::panic; +use std::{path::Path, sync::Arc}; +use clap::Parser; use copy_dir::copy_dir; use tokio::runtime::Runtime; use vite_path::{AbsolutePath, RelativePathBuf}; use vite_str::Str; -use vite_task_graph::{IndexedTaskGraph, TaskDependencyType, TaskId, loader::JsonUserConfigLoader}; +use vite_task_graph::{ + IndexedTaskGraph, TaskDependencyType, TaskId, + loader::JsonUserConfigLoader, + query::{TaskExecutionGraph, 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 from_task_id( + task_id: &TaskId, + base_dir: &AbsolutePath, + indexed_task_graph: &IndexedTaskGraph, + ) -> Self { + 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`. @@ -14,28 +42,6 @@ fn snapshot_task_graph( indexed_task_graph: &IndexedTaskGraph, base_dir: &AbsolutePath, ) -> impl serde::Serialize { - #[derive(serde::Serialize, PartialEq, PartialOrd, Eq, Ord)] - struct TaskIdSnapshot { - package_dir: RelativePathBuf, - task_name: Str, - } - impl TaskIdSnapshot { - fn from_task_id( - task_id: &TaskId, - base_dir: &AbsolutePath, - indexed_task_graph: &IndexedTaskGraph, - ) -> Self { - 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(), - } - } - } - #[derive(serde::Serialize)] struct TaskNodeSnapshot { id: TaskIdSnapshot, @@ -76,6 +82,52 @@ fn snapshot_task_graph( 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, PartialOrd, Eq, Ord)] + struct EdgeSnapshot { + from: TaskIdSnapshot, + to: TaskIdSnapshot, + } + let task_graph = indexed_task_graph.task_graph(); + let mut edge_snapshots = Vec::::new(); + for (from_index, to_index, ()) in execution_graph.all_edges() { + edge_snapshots.push(EdgeSnapshot { + from: TaskIdSnapshot::from_task_id( + &task_graph[from_index].task_id, + base_dir, + indexed_task_graph, + ), + to: TaskIdSnapshot::from_task_id( + &task_graph[to_index].task_id, + base_dir, + indexed_task_graph, + ), + }); + } + edge_snapshots.sort_unstable(); + edge_snapshots +} + +#[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(".") { @@ -94,6 +146,13 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { 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, @@ -102,6 +161,37 @@ 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); + + for cli_query in cli_queries_file.queries { + 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 = cli_task_query.into_task_query(&cwd).expect(&format!( + "Invalid task query for query '{}' in case '{}'", + cli_query.name, case_name + )); + + let execution_graph = indexed_task_graph.query_tasks(task_query).expect(&format!( + "Failed to query tasks for query '{}' in case '{}'", + cli_query.name, case_name + )); + + let task_graph_snapshot = + snapshot_execution_graph(&execution_graph, &indexed_task_graph, &case_stage_path); + + insta::assert_json_snapshot!( + format!("execution graph - {}", cli_query.name), + task_graph_snapshot + ); + } + insta::assert_json_snapshot!("task graph", task_graph_snapshot); }); } From 89fa9653425cb2ec5e8787bfa8b64441e88ee0b2 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 07:48:07 +0800 Subject: [PATCH 06/16] update snapshot --- crates/vite_task_graph/src/lib.rs | 2 +- crates/vite_task_graph/tests/snapshots.rs | 63 ++++++++----------- ... name@transitive-dependency-workspace.snap | 14 +++++ 3 files changed, 42 insertions(+), 37 deletions(-) create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 8f2fca99..eb960bed 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -21,7 +21,7 @@ use specifier::TaskSpecifier; use vec1::smallvec_v1::SmallVec1; use vite_path::AbsolutePath; use vite_str::Str; -use vite_workspace::{DependencyType, PackageInfo, PackageIx, PackageNodeIndex, WorkspaceRoot}; +use vite_workspace::{PackageNodeIndex, WorkspaceRoot}; #[derive(Debug, Clone, Copy, Serialize)] enum TaskDependencyTypeInner { diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index 31e98836..c96f72a1 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -3,11 +3,12 @@ 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, TaskDependencyType, TaskId, + IndexedTaskGraph, TaskDependencyType, TaskNodeIndex, loader::JsonUserConfigLoader, query::{TaskExecutionGraph, cli::CLITaskQuery}, }; @@ -19,11 +20,12 @@ struct TaskIdSnapshot { task_name: Str, } impl TaskIdSnapshot { - fn from_task_id( - task_id: &TaskId, + 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 @@ -57,21 +59,12 @@ fn snapshot_task_graph( let mut depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)> = task_graph .edges_directed(task_index, petgraph::Direction::Outgoing) .map(|edge| { - use petgraph::visit::EdgeRef as _; - let target_node = &task_graph[edge.target()]; - ( - TaskIdSnapshot::from_task_id( - &target_node.task_id, - base_dir, - indexed_task_graph, - ), - *edge.weight(), - ) + (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::from_task_id(&task_node.task_id, base_dir, indexed_task_graph), + id: TaskIdSnapshot::new(task_index, base_dir, indexed_task_graph), command: task_node.resolved_config.command.clone(), cwd: task_node.resolved_config.cwd.strip_prefix(base_dir).unwrap().unwrap(), depends_on, @@ -90,29 +83,27 @@ fn snapshot_execution_graph( indexed_task_graph: &IndexedTaskGraph, base_dir: &AbsolutePath, ) -> impl serde::Serialize { - #[derive(serde::Serialize, PartialEq, PartialOrd, Eq, Ord)] - struct EdgeSnapshot { - from: TaskIdSnapshot, - to: TaskIdSnapshot, + #[derive(serde::Serialize, PartialEq)] + struct ExecutionNodeSnapshot { + task: TaskIdSnapshot, + deps: Vec, } - let task_graph = indexed_task_graph.task_graph(); - let mut edge_snapshots = Vec::::new(); - for (from_index, to_index, ()) in execution_graph.all_edges() { - edge_snapshots.push(EdgeSnapshot { - from: TaskIdSnapshot::from_task_id( - &task_graph[from_index].task_id, - base_dir, - indexed_task_graph, - ), - to: TaskIdSnapshot::from_task_id( - &task_graph[to_index].task_id, - base_dir, - indexed_task_graph, - ), + + 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, }); } - edge_snapshots.sort_unstable(); - edge_snapshots + execution_node_snapshots.sort_unstable_by(|a, b| a.task.cmp(&b.task)); + execution_node_snapshots } #[derive(serde::Deserialize)] @@ -183,12 +174,12 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { cli_query.name, case_name )); - let task_graph_snapshot = + let execution_graph_snapshot = snapshot_execution_graph(&execution_graph, &indexed_task_graph, &case_stage_path); insta::assert_json_snapshot!( format!("execution graph - {}", cli_query.name), - task_graph_snapshot + execution_graph_snapshot ); } diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap new file mode 100644 index 00000000..32511355 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap @@ -0,0 +1,14 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: execution_graph_snapshot +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +[ + { + "task": { + "package_dir": "packages/a", + "task_name": "build" + }, + "deps": [] + } +] From 82d632a7db15cbd029d6e29296d52d0e5d28a2b4 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 07:51:10 +0800 Subject: [PATCH 07/16] update snapshots --- .../cli-queries.toml | 5 +++++ .../packages/a/vite.config.json | 10 ++++++++++ ...pends on@transitive-dependency-workspace.snap | 14 ++++++++++++++ ... by name@transitive-dependency-workspace.snap | 12 ++++++++++++ ...sk graph@transitive-dependency-workspace.snap | 16 ++++++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/vite.config.json create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__execution graph - ignore depends on@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml index 3c27785a..9b0a5642 100644 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml @@ -2,3 +2,8 @@ name = "simple task by name" cwd = "packages/a" args = ["build"] + +[[query]] +name = "ignore depends on" +cwd = "packages/a" +args = ["--ignore-depends-on", "build"] diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/vite.config.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/vite.config.json new file mode 100644 index 00000000..7f96f0dd --- /dev/null +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/vite.config.json @@ -0,0 +1,10 @@ +{ + "tasks": { + "build": { + "dependsOn": ["test"] + }, + "test": { + "command": "echo test a" + } + } +} diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - ignore depends on@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - ignore depends on@transitive-dependency-workspace.snap new file mode 100644 index 00000000..32511355 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - ignore depends on@transitive-dependency-workspace.snap @@ -0,0 +1,14 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: execution_graph_snapshot +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +[ + { + "task": { + "package_dir": "packages/a", + "task_name": "build" + }, + "deps": [] + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap index 32511355..71d68865 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap @@ -9,6 +9,18 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac "package_dir": "packages/a", "task_name": "build" }, + "deps": [ + { + "package_dir": "packages/a", + "task_name": "test" + } + ] + }, + { + "task": { + "package_dir": "packages/a", + "task_name": "test" + }, "deps": [] } ] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap index 24cf5297..7b3888f1 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap @@ -12,6 +12,13 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac "command": "echo Building A", "cwd": "packages/a", "depends_on": [ + [ + { + "package_dir": "packages/a", + "task_name": "test" + }, + "Explicit" + ], [ { "package_dir": "packages/c", @@ -21,6 +28,15 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac ] ] }, + { + "id": { + "package_dir": "packages/a", + "task_name": "test" + }, + "command": "echo test a", + "cwd": "packages/a", + "depends_on": [] + }, { "id": { "package_dir": "packages/c", From 4dd2a6956db26e0bcac8116e42bf4c9e37650406 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 07:55:30 +0800 Subject: [PATCH 08/16] update snapshots --- .../cli-queries.toml | 15 +++++++++++ .../package.json | 7 ----- .../packages/a/src/.gitkeep | 0 ...ckage@transitive-dependency-workspace.snap | 26 +++++++++++++++++++ ...e cwd@transitive-dependency-workspace.snap | 26 +++++++++++++++++++ ...ckage@transitive-dependency-workspace.snap | 26 +++++++++++++++++++ 6 files changed, 93 insertions(+), 7 deletions(-) delete mode 100644 crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/package.json create mode 100644 crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/src/.gitkeep create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__execution graph - explicit package name under different package@transitive-dependency-workspace.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__execution graph - explicit package name under non-package cwd@transitive-dependency-workspace.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__execution graph - under subfolder of package@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml index 9b0a5642..95baf94d 100644 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml @@ -3,6 +3,21 @@ name = "simple task by name" cwd = "packages/a" args = ["build"] +[[query]] +name = "under subfolder of package" +cwd = "packages/a/src" +args = ["build"] + +[[query]] +name = "explicit package name under different package" +cwd = "packages/b" +args = ["@test/a#build"] + +[[query]] +name = "explicit package name under non-package cwd" +cwd = "" +args = ["@test/a#build"] + [[query]] name = "ignore depends on" cwd = "packages/a" diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/package.json deleted file mode 100644 index e976fa5d..00000000 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "transitive-dependency-workspace", - "version": "1.0.0", - "workspaces": [ - "packages/*" - ] -} diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/src/.gitkeep b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/src/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - explicit package name under different package@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - explicit package name under different package@transitive-dependency-workspace.snap new file mode 100644 index 00000000..71d68865 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - explicit package name under different package@transitive-dependency-workspace.snap @@ -0,0 +1,26 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: execution_graph_snapshot +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +[ + { + "task": { + "package_dir": "packages/a", + "task_name": "build" + }, + "deps": [ + { + "package_dir": "packages/a", + "task_name": "test" + } + ] + }, + { + "task": { + "package_dir": "packages/a", + "task_name": "test" + }, + "deps": [] + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - explicit package name under non-package cwd@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - explicit package name under non-package cwd@transitive-dependency-workspace.snap new file mode 100644 index 00000000..71d68865 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - explicit package name under non-package cwd@transitive-dependency-workspace.snap @@ -0,0 +1,26 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: execution_graph_snapshot +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +[ + { + "task": { + "package_dir": "packages/a", + "task_name": "build" + }, + "deps": [ + { + "package_dir": "packages/a", + "task_name": "test" + } + ] + }, + { + "task": { + "package_dir": "packages/a", + "task_name": "test" + }, + "deps": [] + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - under subfolder of package@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - under subfolder of package@transitive-dependency-workspace.snap new file mode 100644 index 00000000..71d68865 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - under subfolder of package@transitive-dependency-workspace.snap @@ -0,0 +1,26 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: execution_graph_snapshot +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +[ + { + "task": { + "package_dir": "packages/a", + "task_name": "build" + }, + "deps": [ + { + "package_dir": "packages/a", + "task_name": "test" + } + ] + }, + { + "task": { + "package_dir": "packages/a", + "task_name": "test" + }, + "deps": [] + } +] From c0daaec211752c22338721919be41918541b4c2d Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 08:04:35 +0800 Subject: [PATCH 09/16] update snapshot --- .../cli-queries.toml | 10 ++++ .../packages/a/package.json | 3 +- .../packages/{b => b1}/package.json | 2 +- .../packages/b2/package.json | 10 ++++ ...rsive@transitive-dependency-workspace.snap | 53 +++++++++++++++++++ ...itive@transitive-dependency-workspace.snap | 53 +++++++++++++++++++ ...graph@transitive-dependency-workspace.snap | 24 +++++++++ 7 files changed, 153 insertions(+), 2 deletions(-) rename crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/{b => b1}/package.json (78%) create mode 100644 crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b2/package.json create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__execution graph - recursive@transitive-dependency-workspace.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml index 95baf94d..23b7a841 100644 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml @@ -22,3 +22,13 @@ args = ["@test/a#build"] name = "ignore depends on" cwd = "packages/a" args = ["--ignore-depends-on", "build"] + +[[query]] +name = "transitive" +cwd = "packages/a" +args = ["--transitive", "build"] + +[[query]] +name = "recursive" +cwd = "" +args = ["--recursive", "build"] diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/package.json index 22dc7f47..5d4fadc3 100644 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/package.json +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/package.json @@ -5,6 +5,7 @@ "build": "echo Building A" }, "dependencies": { - "@test/b": "workspace:*" + "@test/b1": "workspace:*", + "@test/b2": "workspace:*" } } diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json similarity index 78% rename from crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b/package.json rename to crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json index c464a0f5..417800cd 100644 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b/package.json +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json @@ -1,5 +1,5 @@ { - "name": "@test/b", + "name": "@test/b1", "version": "1.0.0", "dependencies": { "@test/c": "workspace:*" diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b2/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b2/package.json new file mode 100644 index 00000000..88b142ad --- /dev/null +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b2/package.json @@ -0,0 +1,10 @@ +{ + "name": "@test/b2", + "version": "1.0.0", + "scripts": { + "build": "echo build b2" + }, + "dependencies": { + "@test/c": "workspace:*" + } +} diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - recursive@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - recursive@transitive-dependency-workspace.snap new file mode 100644 index 00000000..5036dfa5 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - recursive@transitive-dependency-workspace.snap @@ -0,0 +1,53 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: execution_graph_snapshot +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +[ + { + "task": { + "package_dir": "packages/a", + "task_name": "build" + }, + "deps": [ + { + "package_dir": "packages/a", + "task_name": "test" + }, + { + "package_dir": "packages/b2", + "task_name": "build" + }, + { + "package_dir": "packages/c", + "task_name": "build" + } + ] + }, + { + "task": { + "package_dir": "packages/a", + "task_name": "test" + }, + "deps": [] + }, + { + "task": { + "package_dir": "packages/b2", + "task_name": "build" + }, + "deps": [ + { + "package_dir": "packages/c", + "task_name": "build" + } + ] + }, + { + "task": { + "package_dir": "packages/c", + "task_name": "build" + }, + "deps": [] + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive@transitive-dependency-workspace.snap new file mode 100644 index 00000000..5036dfa5 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive@transitive-dependency-workspace.snap @@ -0,0 +1,53 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: execution_graph_snapshot +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +[ + { + "task": { + "package_dir": "packages/a", + "task_name": "build" + }, + "deps": [ + { + "package_dir": "packages/a", + "task_name": "test" + }, + { + "package_dir": "packages/b2", + "task_name": "build" + }, + { + "package_dir": "packages/c", + "task_name": "build" + } + ] + }, + { + "task": { + "package_dir": "packages/a", + "task_name": "test" + }, + "deps": [] + }, + { + "task": { + "package_dir": "packages/b2", + "task_name": "build" + }, + "deps": [ + { + "package_dir": "packages/c", + "task_name": "build" + } + ] + }, + { + "task": { + "package_dir": "packages/c", + "task_name": "build" + }, + "deps": [] + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap index 7b3888f1..79800985 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap @@ -19,6 +19,13 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac }, "Explicit" ], + [ + { + "package_dir": "packages/b2", + "task_name": "build" + }, + "Topological" + ], [ { "package_dir": "packages/c", @@ -37,6 +44,23 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac "cwd": "packages/a", "depends_on": [] }, + { + "id": { + "package_dir": "packages/b2", + "task_name": "build" + }, + "command": "echo build b2", + "cwd": "packages/b2", + "depends_on": [ + [ + { + "package_dir": "packages/c", + "task_name": "build" + }, + "Topological" + ] + ] + }, { "id": { "package_dir": "packages/c", From 19454bc1de813d6e099a68c2d69909333980cd62 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 08:06:38 +0800 Subject: [PATCH 10/16] update snapshots --- .../cli-queries.toml | 5 ++++ .../packages/b1/package.json | 3 +++ .../packages/c/package.json | 1 + ... task@transitive-dependency-workspace.snap | 26 +++++++++++++++++++ ...graph@transitive-dependency-workspace.snap | 26 +++++++++++++++++++ 5 files changed, 61 insertions(+) create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive in package without the task@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml index 23b7a841..93bb921a 100644 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml @@ -28,6 +28,11 @@ name = "transitive" cwd = "packages/a" args = ["--transitive", "build"] +[[query]] +name = "transitive in package without the task" +cwd = "packages/a" +args = ["--transitive", "lint"] + [[query]] name = "recursive" cwd = "" diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json index 417800cd..d034f217 100644 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b1/package.json @@ -1,6 +1,9 @@ { "name": "@test/b1", "version": "1.0.0", + "scripts": { + "lint": "echo lint b1" + }, "dependencies": { "@test/c": "workspace:*" } diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/c/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/c/package.json index 6e9b4cfd..55776d2b 100644 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/c/package.json +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/c/package.json @@ -2,6 +2,7 @@ "name": "@test/c", "version": "1.0.0", "scripts": { + "lint": "echo lint c", "build": "echo Building C" } } diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive in package without the task@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive in package without the task@transitive-dependency-workspace.snap new file mode 100644 index 00000000..e01dc8dd --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive in package without the task@transitive-dependency-workspace.snap @@ -0,0 +1,26 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: execution_graph_snapshot +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +[ + { + "task": { + "package_dir": "packages/b1", + "task_name": "lint" + }, + "deps": [ + { + "package_dir": "packages/c", + "task_name": "lint" + } + ] + }, + { + "task": { + "package_dir": "packages/c", + "task_name": "lint" + }, + "deps": [] + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap index 79800985..8248f70c 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap @@ -44,6 +44,23 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac "cwd": "packages/a", "depends_on": [] }, + { + "id": { + "package_dir": "packages/b1", + "task_name": "lint" + }, + "command": "echo lint b1", + "cwd": "packages/b1", + "depends_on": [ + [ + { + "package_dir": "packages/c", + "task_name": "lint" + }, + "Topological" + ] + ] + }, { "id": { "package_dir": "packages/b2", @@ -69,5 +86,14 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac "command": "echo Building C", "cwd": "packages/c", "depends_on": [] + }, + { + "id": { + "package_dir": "packages/c", + "task_name": "lint" + }, + "command": "echo lint c", + "cwd": "packages/c", + "depends_on": [] } ] From 3e4fa6f8722d6d800999a02d5ad1c0bd555ed2fd Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 08:21:13 +0800 Subject: [PATCH 11/16] snapshot errors --- crates/vite_task_graph/src/lib.rs | 4 +- crates/vite_task_graph/src/query/mod.rs | 2 +- crates/vite_task_graph/tests/snapshots.rs | 65 ++++++++++++++----- ...kage@transitive-dependency-workspace.snap} | 0 ... cwd@transitive-dependency-workspace.snap} | 0 ...s on@transitive-dependency-workspace.snap} | 0 ...sive@transitive-dependency-workspace.snap} | 0 ...name@transitive-dependency-workspace.snap} | 0 ...task@transitive-dependency-workspace.snap} | 0 ...tive@transitive-dependency-workspace.snap} | 0 ...kage@transitive-dependency-workspace.snap} | 0 11 files changed, 52 insertions(+), 19 deletions(-) rename crates/vite_task_graph/tests/snapshots/{snapshots__execution graph - explicit package name under different package@transitive-dependency-workspace.snap => snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap} (100%) rename crates/vite_task_graph/tests/snapshots/{snapshots__execution graph - explicit package name under non-package cwd@transitive-dependency-workspace.snap => snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap} (100%) rename crates/vite_task_graph/tests/snapshots/{snapshots__execution graph - ignore depends on@transitive-dependency-workspace.snap => snapshots__query - ignore depends on@transitive-dependency-workspace.snap} (100%) rename crates/vite_task_graph/tests/snapshots/{snapshots__execution graph - recursive@transitive-dependency-workspace.snap => snapshots__query - recursive@transitive-dependency-workspace.snap} (100%) rename crates/vite_task_graph/tests/snapshots/{snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap => snapshots__query - simple task by name@transitive-dependency-workspace.snap} (100%) rename crates/vite_task_graph/tests/snapshots/{snapshots__execution graph - transitive in package without the task@transitive-dependency-workspace.snap => snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap} (100%) rename crates/vite_task_graph/tests/snapshots/{snapshots__execution graph - transitive@transitive-dependency-workspace.snap => snapshots__query - transitive@transitive-dependency-workspace.snap} (100%) rename crates/vite_task_graph/tests/snapshots/{snapshots__execution graph - under subfolder of package@transitive-dependency-workspace.snap => snapshots__query - under subfolder of package@transitive-dependency-workspace.snap} (100%) diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index eb960bed..e4b29fbb 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)] -pub enum SpecifierLookupError { +pub enum SpecifierLookupError { #[error("Package '{package_name}' is ambiguous among multiple packages: {package_paths:?}")] AmbiguousPackageName { package_name: Str, package_paths: Box<[Arc]> }, @@ -129,7 +129,7 @@ pub enum SpecifierLookupError { #[error( "Nowhere to look for task '{task_name}' because the package is unknown: {unspecifier_package_error}" )] - PackageUnknown { unspecifier_package_error: UnknownPackageError, task_name: Str }, + PackageUnknown { unspecifier_package_error: PackageUnknownError, task_name: Str }, } /// newtype of `DefaultIx` for indices in task graphs diff --git a/crates/vite_task_graph/src/query/mod.rs b/crates/vite_task_graph/src/query/mod.rs index 876938ff..9b26f04c 100644 --- a/crates/vite_task_graph/src/query/mod.rs +++ b/crates/vite_task_graph/src/query/mod.rs @@ -48,7 +48,7 @@ pub type TaskExecutionGraph = DiGraphMap; #[derive(Debug, thiserror::Error)] #[error("The current working directory {cwd:?} is in not any package")] pub struct PackageUnknownError { - cwd: Arc, + pub cwd: Arc, } impl IndexedTaskGraph { diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index c96f72a1..9836a4e5 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -5,12 +5,12 @@ use clap::Parser; use copy_dir::copy_dir; use petgraph::visit::EdgeRef as _; use tokio::runtime::Runtime; -use vite_path::{AbsolutePath, RelativePathBuf}; +use vite_path::{AbsolutePath, RelativePathBuf, relative}; use vite_str::Str; use vite_task_graph::{ - IndexedTaskGraph, TaskDependencyType, TaskNodeIndex, + IndexedTaskGraph, SpecifierLookupError, TaskDependencyType, TaskNodeIndex, loader::JsonUserConfigLoader, - query::{TaskExecutionGraph, cli::CLITaskQuery}, + query::{PackageUnknownError, TaskExecutionGraph, cli::CLITaskQuery}, }; use vite_workspace::find_workspace_root; @@ -106,6 +106,33 @@ fn snapshot_execution_graph( execution_node_snapshots } +fn stablize_absolute_path(path: &mut Arc, base_dir: &AbsolutePath) { + let relative_path = path.strip_prefix(base_dir).unwrap().unwrap(); + let new_base_dir = + AbsolutePath::new(if cfg!(windows) { "C:\\workspace" } else { "/workspace" }).unwrap(); + *path = new_base_dir.join(relative_path).into(); +} + +fn stablize_specifier_lookup_error( + err: &mut SpecifierLookupError, + base_dir: &AbsolutePath, +) { + match err { + SpecifierLookupError::AmbiguousPackageName { package_paths, .. } => { + for path in package_paths.iter_mut() { + stablize_absolute_path(path, base_dir); + } + } + SpecifierLookupError::PackageNameNotFound { .. } => {} + SpecifierLookupError::TaskNameNotFound { package_index, .. } => { + *package_index = Default::default() + } + SpecifierLookupError::PackageUnknown { unspecifier_package_error, .. } => { + stablize_absolute_path(&mut unspecifier_package_error.cwd, base_dir); + } + } +} + #[derive(serde::Deserialize)] struct CLIQuery { pub name: Str, @@ -154,6 +181,8 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { let task_graph_snapshot = snapshot_task_graph(&indexed_task_graph, &case_stage_path); 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())), @@ -164,23 +193,27 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { )); let cwd: Arc = case_stage_path.join(&cli_query.cwd).into(); - let task_query = cli_task_query.into_task_query(&cwd).expect(&format!( - "Invalid task query for query '{}' in case '{}'", - cli_query.name, case_name - )); - - let execution_graph = indexed_task_graph.query_tasks(task_query).expect(&format!( - "Failed to query tasks for query '{}' in case '{}'", - cli_query.name, case_name - )); + 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) => { + stablize_specifier_lookup_error(&mut err, &case_stage_path); + insta::assert_snapshot!(snapshot_name, err); + continue; + } + }; let execution_graph_snapshot = snapshot_execution_graph(&execution_graph, &indexed_task_graph, &case_stage_path); - insta::assert_json_snapshot!( - format!("execution graph - {}", cli_query.name), - execution_graph_snapshot - ); + insta::assert_json_snapshot!(snapshot_name, execution_graph_snapshot); } insta::assert_json_snapshot!("task graph", task_graph_snapshot); diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - explicit package name under different package@transitive-dependency-workspace.snap b/crates/vite_task_graph/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__execution graph - explicit package name under different package@transitive-dependency-workspace.snap rename to crates/vite_task_graph/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - explicit package name under non-package cwd@transitive-dependency-workspace.snap b/crates/vite_task_graph/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__execution graph - explicit package name under non-package cwd@transitive-dependency-workspace.snap rename to crates/vite_task_graph/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__execution graph - ignore depends on@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__execution graph - ignore depends on@transitive-dependency-workspace.snap rename to crates/vite_task_graph/tests/snapshots/snapshots__query - ignore depends on@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - recursive@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__execution graph - recursive@transitive-dependency-workspace.snap rename to crates/vite_task_graph/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__execution graph - simple task by name@transitive-dependency-workspace.snap rename to crates/vite_task_graph/tests/snapshots/snapshots__query - simple task by name@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive in package without the task@transitive-dependency-workspace.snap b/crates/vite_task_graph/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__execution graph - transitive in package without the task@transitive-dependency-workspace.snap rename to crates/vite_task_graph/tests/snapshots/snapshots__query - transitive in package without the task@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__execution graph - transitive@transitive-dependency-workspace.snap rename to crates/vite_task_graph/tests/snapshots/snapshots__query - transitive@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__execution graph - under subfolder of package@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap similarity index 100% rename from crates/vite_task_graph/tests/snapshots/snapshots__execution graph - under subfolder of package@transitive-dependency-workspace.snap rename to crates/vite_task_graph/tests/snapshots/snapshots__query - under subfolder of package@transitive-dependency-workspace.snap From 2801ddc81898d323fd545ef2e8e71742aadce003 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 08:38:44 +0800 Subject: [PATCH 12/16] update tests --- .../transitive-dependency-workspace/cli-queries.toml | 5 +++++ .../packages/another-c/package.json | 8 ++++++++ ...ve and transitive@transitive-dependency-workspace.snap | 6 ++++++ 3 files changed, 19 insertions(+) create mode 100644 crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-c/package.json create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml index 93bb921a..4f22d55f 100644 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml @@ -37,3 +37,8 @@ args = ["--transitive", "lint"] name = "recursive" cwd = "" args = ["--recursive", "build"] + +[[query]] +name = "recursive and transitive" +cwd = "" +args = ["--recursive", "--transitive", "build"] diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-c/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-c/package.json new file mode 100644 index 00000000..55776d2b --- /dev/null +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-c/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/c", + "version": "1.0.0", + "scripts": { + "lint": "echo lint c", + "build": "echo Building C" + } +} diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap new file mode 100644 index 00000000..662f385a --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__query - recursive and transitive@transitive-dependency-workspace.snap @@ -0,0 +1,6 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: err +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +RecursiveTransitiveConflict From 477f6d7caad58203c74a5517a0dfd9d7c1c49140 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 09:00:22 +0800 Subject: [PATCH 13/16] resolve conflict --- crates/vite_task_graph/tests/snapshots.rs | 2 +- crates/vite_workspace/src/lib.rs | 25 +++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index 9836a4e5..a1c42aec 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -5,7 +5,7 @@ use clap::Parser; use copy_dir::copy_dir; use petgraph::visit::EdgeRef as _; use tokio::runtime::Runtime; -use vite_path::{AbsolutePath, RelativePathBuf, relative}; +use vite_path::{AbsolutePath, RelativePathBuf}; use vite_str::Str; use vite_task_graph::{ IndexedTaskGraph, SpecifierLookupError, TaskDependencyType, TaskNodeIndex, diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index d8dd9c75..ecccb377 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -2,11 +2,11 @@ mod error; pub mod package; mod package_manager; -use std::{collections::hash_map::Entry, fs, io}; +use std::{collections::hash_map::Entry, fs, io, sync::Arc}; use petgraph::graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use vec1::smallvec_v1::SmallVec1; use vite_glob::GlobPatternSet; use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf}; @@ -111,7 +111,12 @@ struct PackageGraphBuilder { } impl PackageGraphBuilder { - fn add_package(&mut self, package_path: RelativePathBuf, package_json: PackageJson) { + fn add_package( + &mut self, + package_path: RelativePathBuf, + absolute_path: Arc, + package_json: PackageJson, + ) { let deps = package_json.get_workspace_dependencies().collect::>(); let package_name = package_json.name.clone(); let id = self.graph.add_node(PackageInfo { @@ -208,7 +213,11 @@ pub fn load_package_graph( WorkspaceFile::NonWorkspacePackage(file) => { // For non-workspace packages, add the package.json to the graph as a root package let package_json: PackageJson = serde_json::from_reader(file)?; - graph_builder.add_package(RelativePathBuf::default(), package_json); + graph_builder.add_package( + RelativePathBuf::default(), + workspace_root.path.into(), + package_json, + ); return graph_builder.build(); } @@ -227,7 +236,7 @@ pub fn load_package_graph( }; has_root_package = has_root_package || package_path.as_str().is_empty(); - graph_builder.add_package(package_path, package_json); + graph_builder.add_package(package_path, absolute_path.into(), package_json); } // try add the root package anyway if the member globs do not include it. if !has_root_package { @@ -235,7 +244,11 @@ pub fn load_package_graph( match fs::read(&package_json_path) { Ok(package_json) => { let package_json: PackageJson = serde_json::from_slice(&package_json)?; - graph_builder.add_package(RelativePathBuf::default(), package_json); + graph_builder.add_package( + RelativePathBuf::default(), + workspace_root.path.into(), + package_json, + ); } Err(err) => { if err.kind() != io::ErrorKind::NotFound { From e6def8102ccb241c8885ea2ca7b66d76d9d222d6 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 09:09:13 +0800 Subject: [PATCH 14/16] update --- .../cli-queries.toml | 14 ++++++++++++-- .../packages/another-a/package.json | 7 +++++++ .../packages/another-c/package.json | 8 -------- crates/vite_task_graph/tests/snapshots.rs | 7 +++---- ...ask name@transitive-dependency-workspace.snap | 16 ++++++++++++++++ ... package@transitive-dependency-workspace.snap | 14 +------------- ...kage cwd@transitive-dependency-workspace.snap | 14 +------------- ...ecursive@transitive-dependency-workspace.snap | 7 +++++++ ...ent task@transitive-dependency-workspace.snap | 10 ++++++++++ ...sk graph@transitive-dependency-workspace.snap | 9 +++++++++ 10 files changed, 66 insertions(+), 40 deletions(-) create mode 100644 crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-a/package.json delete mode 100644 crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-c/package.json create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml index 4f22d55f..41491249 100644 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/cli-queries.toml @@ -10,12 +10,17 @@ args = ["build"] [[query]] name = "explicit package name under different package" -cwd = "packages/b" -args = ["@test/a#build"] +cwd = "packages/a" +args = ["@test/c#build"] [[query]] name = "explicit package name under non-package cwd" cwd = "" +args = ["@test/c#build"] + +[[query]] +name = "ambiguous task name" +cwd = "" args = ["@test/a#build"] [[query]] @@ -33,6 +38,11 @@ name = "transitive in package without the task" cwd = "packages/a" args = ["--transitive", "lint"] +[[query]] +name = "transitive non existent task" +cwd = "packages/a" +args = ["--transitive", "non-existent-task"] + [[query]] name = "recursive" cwd = "" diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-a/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-a/package.json new file mode 100644 index 00000000..5841d16a --- /dev/null +++ b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-a/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/a", + "version": "1.0.0", + "scripts": { + "build": "echo Building another A" + } +} diff --git a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-c/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-c/package.json deleted file mode 100644 index 55776d2b..00000000 --- a/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/another-c/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/c", - "version": "1.0.0", - "scripts": { - "lint": "echo lint c", - "build": "echo Building C" - } -} diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index a1c42aec..964f2719 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -178,7 +178,9 @@ 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); + 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); @@ -205,18 +207,15 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { Ok(ok) => ok, Err(mut err) => { stablize_specifier_lookup_error(&mut err, &case_stage_path); - insta::assert_snapshot!(snapshot_name, err); + 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); } - - insta::assert_json_snapshot!("task graph", task_graph_snapshot); }); } diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap new file mode 100644 index 00000000..b65fcafe --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap @@ -0,0 +1,16 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: err +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +AmbiguousPackageName { + package_name: "@test/a", + package_paths: [ + AbsolutePath( + "/workspace/packages/a", + ), + AbsolutePath( + "/workspace/packages/another-a", + ), + ], +} 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_graph/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap index 71d68865..f5e5cacc 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__query - explicit package name under different package@transitive-dependency-workspace.snap @@ -6,21 +6,9 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac [ { "task": { - "package_dir": "packages/a", + "package_dir": "packages/c", "task_name": "build" }, - "deps": [ - { - "package_dir": "packages/a", - "task_name": "test" - } - ] - }, - { - "task": { - "package_dir": "packages/a", - "task_name": "test" - }, "deps": [] } ] 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_graph/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap index 71d68865..f5e5cacc 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__query - explicit package name under non-package cwd@transitive-dependency-workspace.snap @@ -6,21 +6,9 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac [ { "task": { - "package_dir": "packages/a", + "package_dir": "packages/c", "task_name": "build" }, - "deps": [ - { - "package_dir": "packages/a", - "task_name": "test" - } - ] - }, - { - "task": { - "package_dir": "packages/a", - "task_name": "test" - }, "deps": [] } ] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap index 5036dfa5..b438fcb0 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__query - recursive@transitive-dependency-workspace.snap @@ -31,6 +31,13 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac }, "deps": [] }, + { + "task": { + "package_dir": "packages/another-a", + "task_name": "build" + }, + "deps": [] + }, { "task": { "package_dir": "packages/b2", diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap new file mode 100644 index 00000000..9e30bc13 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__query - transitive non existent task@transitive-dependency-workspace.snap @@ -0,0 +1,10 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: err +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +TaskNameNotFound { + package_name: "@test/a", + task_name: "non-existent-task", + package_index: NodeIndex(PackageIx(0)), +} diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap index 8248f70c..62952fda 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap @@ -44,6 +44,15 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac "cwd": "packages/a", "depends_on": [] }, + { + "id": { + "package_dir": "packages/another-a", + "task_name": "build" + }, + "command": "echo Building another A", + "cwd": "packages/another-a", + "depends_on": [] + }, { "id": { "package_dir": "packages/b1", From bc43724926bb51e19b1037e0f1ae756f8328f94e Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 09:14:56 +0800 Subject: [PATCH 15/16] fix typo --- crates/vite_task_graph/src/query/cli.rs | 2 +- crates/vite_task_graph/src/query/mod.rs | 8 ++++---- crates/vite_task_graph/tests/snapshots.rs | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/vite_task_graph/src/query/cli.rs b/crates/vite_task_graph/src/query/cli.rs index 6336fb9f..cb5e6e41 100644 --- a/crates/vite_task_graph/src/query/cli.rs +++ b/crates/vite_task_graph/src/query/cli.rs @@ -58,7 +58,7 @@ impl CLITaskQuery { Ok(s.task_name) }) .collect::>()?; - TaskQueryKind::Resursive { task_names } + TaskQueryKind::Recursive { task_names } } else { TaskQueryKind::Normal { task_specifiers: self.tasks.into_iter().collect(), diff --git a/crates/vite_task_graph/src/query/mod.rs b/crates/vite_task_graph/src/query/mod.rs index 9b26f04c..4d3e08c1 100644 --- a/crates/vite_task_graph/src/query/mod.rs +++ b/crates/vite_task_graph/src/query/mod.rs @@ -26,7 +26,7 @@ pub enum TaskQueryKind { /// A recursive task query specifying one or multiple task names. /// It will match all tasks with the given names across all packages with topological ordering. /// The whole workspace will be searched, so cwd is not relevant. - Resursive { task_names: HashSet }, + Recursive { task_names: HashSet }, } /// Represents a valid query for a task and its dependencies, usually issued from a CLI command `vite run ...`. @@ -60,7 +60,7 @@ impl IndexedTaskGraph { let include_topologicial_deps = match &query.kind { TaskQueryKind::Normal { include_topological_deps, .. } => *include_topological_deps, - TaskQueryKind::Resursive { .. } => true, // recursive means topological across all packages + TaskQueryKind::Recursive { .. } => true, // recursive means topological across all packages }; // Add starting tasks without dependencies @@ -101,7 +101,7 @@ impl IndexedTaskGraph { return Err(err); } // Add nearest tasks to execution graph - // Topoplogical dependencies of nearest tasks will be added later + // Topological dependencies of nearest tasks will be added later for nearest_task in nearest_topological_tasks.drain(..) { execution_graph.add_node(nearest_task); } @@ -113,7 +113,7 @@ impl IndexedTaskGraph { } } } - TaskQueryKind::Resursive { task_names } => { + TaskQueryKind::Recursive { task_names } => { // Add all tasks matching the names across all packages for task_index in self.task_graph.node_indices() { let current_task_name = self.task_graph[task_index].task_id.task_name.as_str(); diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index 964f2719..ff0cd3c2 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -106,21 +106,21 @@ fn snapshot_execution_graph( execution_node_snapshots } -fn stablize_absolute_path(path: &mut Arc, base_dir: &AbsolutePath) { +fn stabilize_absolute_path(path: &mut Arc, base_dir: &AbsolutePath) { let relative_path = path.strip_prefix(base_dir).unwrap().unwrap(); let new_base_dir = AbsolutePath::new(if cfg!(windows) { "C:\\workspace" } else { "/workspace" }).unwrap(); *path = new_base_dir.join(relative_path).into(); } -fn stablize_specifier_lookup_error( +fn stabilize_specifier_lookup_error( err: &mut SpecifierLookupError, base_dir: &AbsolutePath, ) { match err { SpecifierLookupError::AmbiguousPackageName { package_paths, .. } => { for path in package_paths.iter_mut() { - stablize_absolute_path(path, base_dir); + stabilize_absolute_path(path, base_dir); } } SpecifierLookupError::PackageNameNotFound { .. } => {} @@ -128,7 +128,7 @@ fn stablize_specifier_lookup_error( *package_index = Default::default() } SpecifierLookupError::PackageUnknown { unspecifier_package_error, .. } => { - stablize_absolute_path(&mut unspecifier_package_error.cwd, base_dir); + stabilize_absolute_path(&mut unspecifier_package_error.cwd, base_dir); } } } @@ -206,7 +206,7 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { let execution_graph = match indexed_task_graph.query_tasks(task_query) { Ok(ok) => ok, Err(mut err) => { - stablize_specifier_lookup_error(&mut err, &case_stage_path); + stabilize_specifier_lookup_error(&mut err, &case_stage_path); insta::assert_debug_snapshot!(snapshot_name, err); continue; } From a0b6de1e9ae2955b67eb8f71d7c73ad3f52b7e2e Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 7 Dec 2025 09:40:49 +0800 Subject: [PATCH 16/16] update path --- crates/vite_task_graph/tests/snapshots.rs | 6 ++++-- ...ambiguous task name@transitive-dependency-workspace.snap | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index ff0cd3c2..877de517 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -106,13 +106,15 @@ 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(); - let new_base_dir = - AbsolutePath::new(if cfg!(windows) { "C:\\workspace" } else { "/workspace" }).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, diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap index b65fcafe..3b2a19c2 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__query - ambiguous task name@transitive-dependency-workspace.snap @@ -7,10 +7,10 @@ AmbiguousPackageName { package_name: "@test/a", package_paths: [ AbsolutePath( - "/workspace/packages/a", + "//?/workspace/packages/a", ), AbsolutePath( - "/workspace/packages/another-a", + "//?/workspace/packages/another-a", ), ], }