From d55c48fe65254202239dad766b3842725a5d1dac Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 20 Nov 2025 21:33:03 +0800 Subject: [PATCH 01/31] feat: new rust API and binary --- Cargo.lock | 53 +++++++++++++++ Cargo.toml | 2 + crates/vite_task/Cargo.toml | 1 + crates/vite_task/src/cli.rs | 16 +++++ crates/vite_task/src/lib.rs | 8 +++ crates/vite_task/src/session.rs | 43 +++++++++++++ crates/vite_task/src/ui.rs | 2 + crates/vite_task_bin/Cargo.toml | 23 +++++++ crates/vite_task_bin/src/main.rs | 71 +++++++++++++++++++++ crates/vite_task_bin/tests/snap_tests.rs | 6 ++ crates/vite_task_bin/tests/vite.config.json | 0 11 files changed, 225 insertions(+) create mode 100644 crates/vite_task/src/cli.rs create mode 100644 crates/vite_task/src/session.rs create mode 100644 crates/vite_task_bin/Cargo.toml create mode 100644 crates/vite_task_bin/src/main.rs create mode 100644 crates/vite_task_bin/tests/snap_tests.rs create mode 100644 crates/vite_task_bin/tests/vite.config.json diff --git a/Cargo.lock b/Cargo.lock index 3513f60c..7468f495 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -411,6 +411,46 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.5.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" +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" @@ -3049,6 +3089,7 @@ dependencies = [ "bincode", "brush-parser", "bstr", + "clap", "compact_str 0.9.0", "dashmap", "diff-struct", @@ -3079,6 +3120,18 @@ dependencies = [ "wax", ] +[[package]] +name = "vite_task_bin" +version = "0.0.0" +dependencies = [ + "anyhow", + "clap", + "serde_json", + "tokio", + "vite_str", + "vite_task", +] + [[package]] name = "vite_tui" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 4f4e55c5..2560a4cc 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.52" color-eyre = "0.6.5" compact_str = "0.9.0" const_format = "0.2.34" @@ -116,6 +117,7 @@ uuid = "1.18.1" vite_glob = { path = "crates/vite_glob" } vite_path = { path = "crates/vite_path" } vite_str = { path = "crates/vite_str" } +vite_task = { path = "crates/vite_task" } vite_workspace = { path = "crates/vite_workspace" } wax = "0.6.0" which = "8.0.0" diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml index 06d4b0a9..47ffd05c 100644 --- a/crates/vite_task/Cargo.toml +++ b/crates/vite_task/Cargo.toml @@ -16,6 +16,7 @@ anyhow = { workspace = true } bincode = { workspace = true, features = ["derive"] } brush-parser = { workspace = true } bstr = { workspace = true } +clap = { workspace = true, features = ["derive"] } compact_str = { workspace = true, features = ["serde"] } dashmap = { workspace = true } diff-struct = { workspace = true } diff --git a/crates/vite_task/src/cli.rs b/crates/vite_task/src/cli.rs new file mode 100644 index 00000000..34e30568 --- /dev/null +++ b/crates/vite_task/src/cli.rs @@ -0,0 +1,16 @@ +use clap::{Parser, Subcommand}; +use vite_str::Str; + +#[derive(Debug, Parser)] +pub enum CLIArgs { + #[clap(flatten)] + SubCommands(SubCommands), + + Run { + #[clap(short, long)] + recursive: bool, + task_name: Str, + #[clap(last = true)] + extra_args: Vec, + }, +} diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 4bc75d0c..7ae68261 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -1,4 +1,5 @@ mod cache; +pub mod cli; mod cmd; mod collections; mod config; @@ -8,6 +9,7 @@ mod fingerprint; mod fs; mod maybe_str; mod schedule; +pub mod session; mod types; mod ui; @@ -21,3 +23,9 @@ pub use error::Error; pub use execute::{CURRENT_EXECUTION_ID, EXECUTION_SUMMARY_DIR}; pub use schedule::{ExecutionPlan, ExecutionStatus, ExecutionSummary}; pub use types::ResolveCommandResult; + +pub enum CLIArgs { + CustomArgs(Vec), +} + +pub fn cli_main(args: CLIArgs) {} diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs new file mode 100644 index 00000000..7008113c --- /dev/null +++ b/crates/vite_task/src/session.rs @@ -0,0 +1,43 @@ +use std::path::Path; + +use serde::{Deserialize, Serialize}; +use vite_str::Str; + +use crate::cli::CLIArgs; + +// Represents the real subprocess to be spawned for a custom subcommand (vite ...) +pub struct SubcommandProcess { + pub program: Str, + pub args: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct ViteUserConfig {} + +pub trait SessionHandler: Send + Sync { + /// What to spawn for `vite ` + fn process_for_subcommand( + &mut self, + subcommand: &Subcommand, + ) -> anyhow::Result; + + fn resolve_config(&mut self, package_dir: &Path) -> anyhow::Result; +} + +pub struct Session { + handler: Box>, +} + +pub enum SessionRunArgs { + CustomSubCommand { subcommand_name: Str, extra_args: Vec }, +} + +impl Session { + pub async fn init( + handler: Box>, + ) -> Result { + Ok(Self { handler }) + } + + pub async fn run(&mut self, args: CLIArgs) {} +} diff --git a/crates/vite_task/src/ui.rs b/crates/vite_task/src/ui.rs index 5d6351cd..632dc143 100644 --- a/crates/vite_task/src/ui.rs +++ b/crates/vite_task/src/ui.rs @@ -11,6 +11,8 @@ use crate::{ schedule::{CacheStatus, ExecutionFailure, ExecutionSummary, PreExecutionStatus}, }; +pub trait TaskReporter {} + /// Wrap of `OwoColorize` that ignores style if `NO_COLOR` is set. trait ColorizeExt { fn style(&self, style: Style) -> Styled<&Self>; diff --git a/crates/vite_task_bin/Cargo.toml b/crates/vite_task_bin/Cargo.toml new file mode 100644 index 00000000..124919e9 --- /dev/null +++ b/crates/vite_task_bin/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "vite_task_bin" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +publish = false +rust-version.workspace = true + +[[bin]] +name = "vite" +path = "src/main.rs" + +[dependencies] +anyhow = { workspace = true } +clap = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tokio = { workspace = true, features = ["full"] } +vite_str = { workspace = true } +vite_task = { workspace = true } + +[lints] +workspace = true diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs new file mode 100644 index 00000000..3a019712 --- /dev/null +++ b/crates/vite_task_bin/src/main.rs @@ -0,0 +1,71 @@ +use clap::Parser; +use vite_str::Str; +use vite_task::{ + cli::CLIArgs as ViteTaskCLIArgs, + session::{Session, SessionHandler, SubcommandProcess}, +}; + +#[derive(Parser, Debug, PartialEq, Eq)] +#[clap(disable_help_flag = true)] +enum ViteTaskSubcommands { + /// linter + Lint { + #[clap(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, +} + +#[test] +fn test_subcommand() { + let a = ViteTaskSubcommands::try_parse_from(["vite", "lint", "hello"]); + assert_eq!(a.unwrap(), ViteTaskSubcommands::Lint { args: vec![Str::from("hello")] }); + + let b = ViteTaskSubcommands::try_parse_from(["vite", "lint", "--help"]); + assert_eq!(b.unwrap(), ViteTaskSubcommands::Lint { args: vec![Str::from("--help")] }); +} + +#[derive(Parser, Debug)] +enum ViteArgs { + Dev, + #[clap(flatten)] + ViteTaskCLIArgs(ViteTaskCLIArgs), +} + +struct ViteTaskHandler; +impl SessionHandler for ViteTaskHandler { + fn process_for_subcommand( + &mut self, + subcommand: &ViteTaskSubcommands, + ) -> anyhow::Result { + match subcommand { + ViteTaskSubcommands::Lint { args } => { + Ok(SubcommandProcess { program: Str::from("oxlint"), args: args.clone() }) + } + } + } + + fn resolve_config( + &mut self, + package_dir: &std::path::Path, + ) -> anyhow::Result { + struct ViteConfig { + task: vite_task::session::ViteUserConfig, + } + todo!() + } +} + +#[tokio::main] +async fn main() { + let args = ViteArgs::parse(); + + let mut session = Session::init(Box::new(ViteTaskHandler)).await.unwrap(); + match dbg!(args) { + ViteArgs::Dev => { + println!("vite dev mode"); + } + ViteArgs::ViteTaskCLIArgs(vite_task_args) => { + session.run(vite_task_args).await; + } + } +} diff --git a/crates/vite_task_bin/tests/snap_tests.rs b/crates/vite_task_bin/tests/snap_tests.rs new file mode 100644 index 00000000..5072d78b --- /dev/null +++ b/crates/vite_task_bin/tests/snap_tests.rs @@ -0,0 +1,6 @@ +use std::env::vars_os; + +#[test] +fn hello() { + dbg!(env!("CARGO_BIN_EXE_vite")); +} diff --git a/crates/vite_task_bin/tests/vite.config.json b/crates/vite_task_bin/tests/vite.config.json new file mode 100644 index 00000000..e69de29b From 8f733a73d7dcc8eaba7b1d4a5cf4b4554d92389b Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 21 Nov 2025 16:36:17 +0800 Subject: [PATCH 02/31] async trait --- Cargo.lock | 12 ++++++++++++ Cargo.toml | 1 + crates/vite_task/Cargo.toml | 1 + crates/vite_task/src/session.rs | 3 ++- crates/vite_task_bin/src/main.rs | 4 +++- 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7468f495..9b7d913e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,6 +153,17 @@ dependencies = [ "tree-sitter-facade-sg", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -3086,6 +3097,7 @@ name = "vite_task" version = "0.0.0" dependencies = [ "anyhow", + "async-trait", "bincode", "brush-parser", "bstr", diff --git a/Cargo.toml b/Cargo.toml index 2560a4cc..54eac046 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ anyhow = "1.0.98" assert2 = "0.3.16" assertables = "9.8.1" ast-grep-core = "0.32.2" +async-trait = "0.1.89" base64 = "0.22.1" bincode = "2.0.1" bindgen = "0.72.1" diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml index 47ffd05c..c9494d38 100644 --- a/crates/vite_task/Cargo.toml +++ b/crates/vite_task/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } bincode = { workspace = true, features = ["derive"] } brush-parser = { workspace = true } bstr = { workspace = true } diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs index 7008113c..51a77559 100644 --- a/crates/vite_task/src/session.rs +++ b/crates/vite_task/src/session.rs @@ -14,6 +14,7 @@ pub struct SubcommandProcess { #[derive(Serialize, Deserialize)] pub struct ViteUserConfig {} +#[async_trait::async_trait] pub trait SessionHandler: Send + Sync { /// What to spawn for `vite ` fn process_for_subcommand( @@ -21,7 +22,7 @@ pub trait SessionHandler: Send + Sync { subcommand: &Subcommand, ) -> anyhow::Result; - fn resolve_config(&mut self, package_dir: &Path) -> anyhow::Result; + async fn resolve_config(&mut self, package_dir: &Path) -> anyhow::Result; } pub struct Session { diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs index 3a019712..5f915de7 100644 --- a/crates/vite_task_bin/src/main.rs +++ b/crates/vite_task_bin/src/main.rs @@ -32,6 +32,8 @@ enum ViteArgs { } struct ViteTaskHandler; + +#[async_trait::async_trait] impl SessionHandler for ViteTaskHandler { fn process_for_subcommand( &mut self, @@ -44,7 +46,7 @@ impl SessionHandler for ViteTaskHandler { } } - fn resolve_config( + async fn resolve_config( &mut self, package_dir: &std::path::Path, ) -> anyhow::Result { From 0682eb05992f855a1c063ad6aa50b8ee30200087 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 23 Nov 2025 17:29:20 +0800 Subject: [PATCH 03/31] wip --- Cargo.lock | 2 ++ crates/vite_task/src/cli.rs | 4 +-- crates/vite_task/src/lib.rs | 1 + crates/vite_task/src/reporter.rs | 6 ++++ crates/vite_task/src/session.rs | 16 +++++----- crates/vite_task_bin/Cargo.toml | 2 ++ crates/vite_task_bin/src/main.rs | 52 +++++++++++++++++++------------- 7 files changed, 52 insertions(+), 31 deletions(-) create mode 100644 crates/vite_task/src/reporter.rs diff --git a/Cargo.lock b/Cargo.lock index 9b7d913e..a115cc23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3137,7 +3137,9 @@ name = "vite_task_bin" version = "0.0.0" dependencies = [ "anyhow", + "async-trait", "clap", + "serde", "serde_json", "tokio", "vite_str", diff --git a/crates/vite_task/src/cli.rs b/crates/vite_task/src/cli.rs index 34e30568..11f109d1 100644 --- a/crates/vite_task/src/cli.rs +++ b/crates/vite_task/src/cli.rs @@ -2,9 +2,9 @@ use clap::{Parser, Subcommand}; use vite_str::Str; #[derive(Debug, Parser)] -pub enum CLIArgs { +pub enum CLIArgs { #[clap(flatten)] - SubCommands(SubCommands), + SubCommands(CustomSubCommand), Run { #[clap(short, long)] diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 7ae68261..289223f0 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -8,6 +8,7 @@ mod execute; mod fingerprint; mod fs; mod maybe_str; +mod reporter; mod schedule; pub mod session; mod types; diff --git a/crates/vite_task/src/reporter.rs b/crates/vite_task/src/reporter.rs new file mode 100644 index 00000000..5640b4d9 --- /dev/null +++ b/crates/vite_task/src/reporter.rs @@ -0,0 +1,6 @@ +/// Describes how to report events during a Vite Task session. +/// It's an abstraction over different kinds of ui (stream, terminial ui, web, etc). +pub trait Reporter { + /// Report the execution plan that is about to be executed. + fn report_execution_plan(self, tree: &str); +} diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs index 51a77559..47c00f40 100644 --- a/crates/vite_task/src/session.rs +++ b/crates/vite_task/src/session.rs @@ -15,30 +15,30 @@ pub struct SubcommandProcess { pub struct ViteUserConfig {} #[async_trait::async_trait] -pub trait SessionHandler: Send + Sync { +pub trait SessionHandler: Send + Sync { /// What to spawn for `vite ` - fn process_for_subcommand( + async fn process_for_subcommand( &mut self, - subcommand: &Subcommand, + subcommand: CustomSubcommand, ) -> anyhow::Result; async fn resolve_config(&mut self, package_dir: &Path) -> anyhow::Result; } -pub struct Session { - handler: Box>, +pub struct Session { + handler: Box>, } pub enum SessionRunArgs { CustomSubCommand { subcommand_name: Str, extra_args: Vec }, } -impl Session { +impl Session { pub async fn init( - handler: Box>, + handler: Box>, ) -> Result { Ok(Self { handler }) } - pub async fn run(&mut self, args: CLIArgs) {} + pub async fn run(&mut self, args: CLIArgs) {} } diff --git a/crates/vite_task_bin/Cargo.toml b/crates/vite_task_bin/Cargo.toml index 124919e9..37a45363 100644 --- a/crates/vite_task_bin/Cargo.toml +++ b/crates/vite_task_bin/Cargo.toml @@ -13,7 +13,9 @@ path = "src/main.rs" [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } clap = { workspace = true, features = ["derive"] } +serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } vite_str = { workspace = true } diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs index 5f915de7..fb85d4b7 100644 --- a/crates/vite_task_bin/src/main.rs +++ b/crates/vite_task_bin/src/main.rs @@ -7,53 +7,66 @@ use vite_task::{ #[derive(Parser, Debug, PartialEq, Eq)] #[clap(disable_help_flag = true)] -enum ViteTaskSubcommands { - /// linter +enum ViteTaskCustomSubcommand { + /// oxlint Lint { #[clap(trailing_var_arg = true, allow_hyphen_values = true)] args: Vec, }, + /// vitest + Test { + #[clap(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + /// oxfmt + Fmt { + #[clap(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, } #[test] fn test_subcommand() { - let a = ViteTaskSubcommands::try_parse_from(["vite", "lint", "hello"]); - assert_eq!(a.unwrap(), ViteTaskSubcommands::Lint { args: vec![Str::from("hello")] }); + let a = ViteTaskCustomSubcommand::try_parse_from(["vite", "lint", "hello"]); + assert_eq!(a.unwrap(), ViteTaskCustomSubcommand::Lint { args: vec![Str::from("hello")] }); - let b = ViteTaskSubcommands::try_parse_from(["vite", "lint", "--help"]); - assert_eq!(b.unwrap(), ViteTaskSubcommands::Lint { args: vec![Str::from("--help")] }); + let b = ViteTaskCustomSubcommand::try_parse_from(["vite", "lint", "--help"]); + assert_eq!(b.unwrap(), ViteTaskCustomSubcommand::Lint { args: vec![Str::from("--help")] }); } #[derive(Parser, Debug)] enum ViteArgs { - Dev, #[clap(flatten)] - ViteTaskCLIArgs(ViteTaskCLIArgs), + ViteTaskCLIArgs(ViteTaskCLIArgs), } struct ViteTaskHandler; #[async_trait::async_trait] -impl SessionHandler for ViteTaskHandler { - fn process_for_subcommand( +impl SessionHandler for ViteTaskHandler { + async fn process_for_subcommand( &mut self, - subcommand: &ViteTaskSubcommands, + subcommand: ViteTaskCustomSubcommand, ) -> anyhow::Result { - match subcommand { - ViteTaskSubcommands::Lint { args } => { - Ok(SubcommandProcess { program: Str::from("oxlint"), args: args.clone() }) - } - } + let (program, args) = match subcommand { + ViteTaskCustomSubcommand::Lint { args } => ("oxlint", args), + ViteTaskCustomSubcommand::Test { args } => ("vitest", args), + ViteTaskCustomSubcommand::Fmt { args } => ("oxfmt", args), + }; + Ok(SubcommandProcess { program: program.into(), args }) } async fn resolve_config( &mut self, package_dir: &std::path::Path, ) -> anyhow::Result { + #[derive(serde::Deserialize)] struct ViteConfig { task: vite_task::session::ViteUserConfig, } - todo!() + let config_file = tokio::fs::read(package_dir.join("vite.config.json")).await?; + let vite_config: ViteConfig = serde_json::from_slice(&config_file)?; + Ok(vite_config.task) } } @@ -62,10 +75,7 @@ async fn main() { let args = ViteArgs::parse(); let mut session = Session::init(Box::new(ViteTaskHandler)).await.unwrap(); - match dbg!(args) { - ViteArgs::Dev => { - println!("vite dev mode"); - } + match args { ViteArgs::ViteTaskCLIArgs(vite_task_args) => { session.run(vite_task_args).await; } From 1f63250a283c543d145f651327ecb4ae6b7e8bc1 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 23 Nov 2025 22:13:51 +0800 Subject: [PATCH 04/31] update --- crates/vite_task/src/reporter.rs | 2 +- crates/vite_task/src/session.rs | 17 ++++++++++++----- crates/vite_task_bin/src/main.rs | 13 +++++++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/crates/vite_task/src/reporter.rs b/crates/vite_task/src/reporter.rs index 5640b4d9..42252975 100644 --- a/crates/vite_task/src/reporter.rs +++ b/crates/vite_task/src/reporter.rs @@ -2,5 +2,5 @@ /// It's an abstraction over different kinds of ui (stream, terminial ui, web, etc). pub trait Reporter { /// Report the execution plan that is about to be executed. - fn report_execution_plan(self, tree: &str); + fn report_execution_plan(self: Box, tree: &str); } diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs index 47c00f40..c8ce90c9 100644 --- a/crates/vite_task/src/session.rs +++ b/crates/vite_task/src/session.rs @@ -1,9 +1,9 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use vite_str::Str; -use crate::cli::CLIArgs; +use crate::{cli::CLIArgs, reporter::Reporter}; // Represents the real subprocess to be spawned for a custom subcommand (vite ...) pub struct SubcommandProcess { @@ -29,8 +29,9 @@ pub struct Session { handler: Box>, } -pub enum SessionRunArgs { - CustomSubCommand { subcommand_name: Str, extra_args: Vec }, +pub struct SessionStartParams { + pub cwd: PathBuf, + pub args: CLIArgs, } impl Session { @@ -40,5 +41,11 @@ impl Session { Ok(Self { handler }) } - pub async fn run(&mut self, args: CLIArgs) {} + pub async fn start( + &mut self, + params: SessionStartParams, + reporter: Box, + ) { + reporter.report_execution_plan("tree"); + } } diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs index fb85d4b7..92b53e8e 100644 --- a/crates/vite_task_bin/src/main.rs +++ b/crates/vite_task_bin/src/main.rs @@ -2,7 +2,7 @@ use clap::Parser; use vite_str::Str; use vite_task::{ cli::CLIArgs as ViteTaskCLIArgs, - session::{Session, SessionHandler, SubcommandProcess}, + session::{Session, SessionHandler, SessionStartParams, SubcommandProcess}, }; #[derive(Parser, Debug, PartialEq, Eq)] @@ -71,13 +71,18 @@ impl SessionHandler for ViteTaskHandler { } #[tokio::main] -async fn main() { +async fn main() -> anyhow::Result<()> { let args = ViteArgs::parse(); - let mut session = Session::init(Box::new(ViteTaskHandler)).await.unwrap(); + let mut session = Session::init(Box::new(ViteTaskHandler)).await?; match args { ViteArgs::ViteTaskCLIArgs(vite_task_args) => { - session.run(vite_task_args).await; + session + .start( + SessionStartParams { cwd: std::env::current_dir()?, args: vite_task_args }, + todo!(), + ) + .await; } } } From 00c3b9d2f67ff0404de17c9fd191d766af799fef Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 30 Nov 2025 12:56:49 +0800 Subject: [PATCH 05/31] update --- crates/vite_task/src/lib.rs | 1 + crates/vite_task/src/workspace.rs | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 crates/vite_task/src/workspace.rs diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 289223f0..bf061752 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -13,6 +13,7 @@ mod schedule; pub mod session; mod types; mod ui; +mod workspace; #[cfg(test)] mod test_utils; diff --git a/crates/vite_task/src/workspace.rs b/crates/vite_task/src/workspace.rs new file mode 100644 index 00000000..b333e905 --- /dev/null +++ b/crates/vite_task/src/workspace.rs @@ -0,0 +1,6 @@ +use vite_path::AbsolutePathBuf; + +/// Lazy-loading the package graph and task graph in the workspace. +pub struct Workspace { + root_path: AbsolutePathBuf, +} From d2df392fe2f675d3a2e831b81c1cccc14017f86e Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 30 Nov 2025 22:08:30 +0800 Subject: [PATCH 06/31] wip --- Cargo.lock | 1 + crates/vite_task/src/lib.rs | 2 +- .../src/{reporter.rs => reporter/mod.rs} | 2 + crates/vite_task/src/reporter/stream.rs | 6 ++ crates/vite_task/src/session.rs | 30 ++++++-- crates/vite_task/src/workspace.rs | 77 ++++++++++++++++++- crates/vite_task_bin/Cargo.toml | 1 + crates/vite_task_bin/src/main.rs | 18 +++-- 8 files changed, 121 insertions(+), 16 deletions(-) rename crates/vite_task/src/{reporter.rs => reporter/mod.rs} (94%) create mode 100644 crates/vite_task/src/reporter/stream.rs diff --git a/Cargo.lock b/Cargo.lock index a115cc23..dd1760bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3142,6 +3142,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "vite_path", "vite_str", "vite_task", ] diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index bf061752..c5794cb7 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -8,7 +8,7 @@ mod execute; mod fingerprint; mod fs; mod maybe_str; -mod reporter; +pub mod reporter; mod schedule; pub mod session; mod types; diff --git a/crates/vite_task/src/reporter.rs b/crates/vite_task/src/reporter/mod.rs similarity index 94% rename from crates/vite_task/src/reporter.rs rename to crates/vite_task/src/reporter/mod.rs index 42252975..2b583b52 100644 --- a/crates/vite_task/src/reporter.rs +++ b/crates/vite_task/src/reporter/mod.rs @@ -1,3 +1,5 @@ +pub mod stream; + /// Describes how to report events during a Vite Task session. /// It's an abstraction over different kinds of ui (stream, terminial ui, web, etc). pub trait Reporter { diff --git a/crates/vite_task/src/reporter/stream.rs b/crates/vite_task/src/reporter/stream.rs new file mode 100644 index 00000000..e2ebf5d5 --- /dev/null +++ b/crates/vite_task/src/reporter/stream.rs @@ -0,0 +1,6 @@ +#[derive(Default)] +pub struct StreamReporter(()); + +impl crate::reporter::Reporter for StreamReporter { + fn report_execution_plan(self: Box, tree: &str) {} +} diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs index c8ce90c9..d5c04091 100644 --- a/crates/vite_task/src/session.rs +++ b/crates/vite_task/src/session.rs @@ -1,9 +1,17 @@ -use std::path::{Path, PathBuf}; +use std::{ + any, + collections::HashMap, + ffi::OsString, + path::{Path, PathBuf}, + sync::{Arc, LazyLock}, +}; +use petgraph::prelude::StableDiGraph; use serde::{Deserialize, Serialize}; +use vite_path::AbsolutePath; use vite_str::Str; -use crate::{cli::CLIArgs, reporter::Reporter}; +use crate::{ResolvedTask, Workspace, cli::CLIArgs, reporter::Reporter}; // Represents the real subprocess to be spawned for a custom subcommand (vite ...) pub struct SubcommandProcess { @@ -25,17 +33,26 @@ pub trait SessionHandler: Send + Sync { async fn resolve_config(&mut self, package_dir: &Path) -> anyhow::Result; } +type Lazy = LazyLock T + Send + Sync>>; + pub struct Session { handler: Box>, + + /// Lazily discovered workspace + lazy_workspace: Arc>>>, + lazy_task_graph: Lazy>, } -pub struct SessionStartParams { - pub cwd: PathBuf, +/// Parameters of a CLI invocation of Vite Task, including current working directory, CLI args, and envs. +pub struct CLIParams { + pub cwd: Arc, pub args: CLIArgs, + pub envs: HashMap, } impl Session { pub async fn init( + cwd: &Arc, handler: Box>, ) -> Result { Ok(Self { handler }) @@ -43,9 +60,10 @@ impl Session { pub async fn start( &mut self, - params: SessionStartParams, + params: CLIParams, reporter: Box, - ) { + ) -> anyhow::Result<()> { reporter.report_execution_plan("tree"); + Ok(()) } } diff --git a/crates/vite_task/src/workspace.rs b/crates/vite_task/src/workspace.rs index b333e905..551e83e3 100644 --- a/crates/vite_task/src/workspace.rs +++ b/crates/vite_task/src/workspace.rs @@ -1,6 +1,77 @@ -use vite_path::AbsolutePathBuf; +use std::sync::{Arc, LazyLock, OnceLock}; -/// Lazy-loading the package graph and task graph in the workspace. +use vite_path::AbsolutePath; +use vite_workspace::{WorkspaceFile, find_workspace_root, get_package_graph}; + +/// Type alias for a LazyLock that uses a Boxed FnOnce to initialize the value. +type LazyLockWithBoxFn = LazyLock T>>; + +/// Represents a lazily loaded workspace. +/// No IO is performed in initialization. +/// The workspace discovery and task graph are lazily loaded when the respective methods are called. pub struct Workspace { - root_path: AbsolutePathBuf, + discovered: LazyLockWithBoxFn>>, +} + +struct DiscoveredWorkspace { + root_path: Arc, + task_graph: LazyLockWithBoxFn>>, +} + +impl DiscoveredWorkspace { + fn discover(cwd: &AbsolutePath) -> Result { + let workspace_root = find_workspace_root(cwd)?; + let root_path = workspace_root.path.to_absolute_path_buf().into(); + Ok(DiscoveredWorkspace { + root_path: Arc::clone(&root_path), + task_graph: LazyLock::new(Box::new(move || { + Ok(LoadedTaskGraph::load(&root_path, workspace_root.workspace_file)?) + })), + }) + } +} + +struct LoadedTaskGraph {} +impl LoadedTaskGraph { + fn load( + workspace_root: &AbsolutePath, + workspace_file: WorkspaceFile, + ) -> Result { + Ok(LoadedTaskGraph {}) + } +} + +impl Workspace { + fn new(cwd: Arc) -> Self { + Self { + discovered: LazyLock::new(Box::new(move || { + DiscoveredWorkspace::discover(&cwd).map_err(Arc::new) + })), + } + } + + fn discover_once(&self) -> anyhow::Result<&DiscoveredWorkspace> { + let discovered = + self.discovered.as_ref().map_err(|err| anyhow::Error::from(Arc::clone(&err))); + Ok(discovered?) + } + + /// Get the root path of the workspace. + /// This will trigger workspace discovery if not already done. + pub fn get_root(&self) -> anyhow::Result<&Arc> { + Ok(&self.discover_once()?.root_path) + } + + fn load_task_graph_once(&self) -> anyhow::Result<&LoadedTaskGraph> { + let discovered = self.discover_once()?; + let task_graph = + discovered.task_graph.as_ref().map_err(|err| anyhow::Error::from(Arc::clone(&err))); + Ok(task_graph?) + } + + pub fn get_task_graph(&self) -> anyhow::Result<&()> { + let discovered_workspace = self.discover_once()?; + let package_graph = get_package_graph(&discovered_workspace.root_path)?; + todo!() + } } diff --git a/crates/vite_task_bin/Cargo.toml b/crates/vite_task_bin/Cargo.toml index 37a45363..4d376313 100644 --- a/crates/vite_task_bin/Cargo.toml +++ b/crates/vite_task_bin/Cargo.toml @@ -18,6 +18,7 @@ clap = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } +vite_path = { workspace = true } vite_str = { workspace = true } vite_task = { workspace = true } diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs index 92b53e8e..9dc216dd 100644 --- a/crates/vite_task_bin/src/main.rs +++ b/crates/vite_task_bin/src/main.rs @@ -1,8 +1,12 @@ +use std::{path::Path, sync::Arc}; + use clap::Parser; +use vite_path::{AbsolutePath, current_dir}; use vite_str::Str; use vite_task::{ cli::CLIArgs as ViteTaskCLIArgs, - session::{Session, SessionHandler, SessionStartParams, SubcommandProcess}, + reporter::stream::StreamReporter, + session::{CLIParams, Session, SessionHandler, SubcommandProcess}, }; #[derive(Parser, Debug, PartialEq, Eq)] @@ -72,17 +76,19 @@ impl SessionHandler for ViteTaskHandler { #[tokio::main] async fn main() -> anyhow::Result<()> { + let cwd = Arc::::from(current_dir()?); let args = ViteArgs::parse(); - let mut session = Session::init(Box::new(ViteTaskHandler)).await?; + let mut session = Session::init(&cwd, Box::new(ViteTaskHandler)).await?; match args { ViteArgs::ViteTaskCLIArgs(vite_task_args) => { session .start( - SessionStartParams { cwd: std::env::current_dir()?, args: vite_task_args }, - todo!(), + CLIParams { cwd, args: vite_task_args, envs: std::env::vars_os().collect() }, + Box::new(StreamReporter::default()), ) - .await; + .await?; } - } + }; + Ok(()) } From 9607335832a0144bdc4ceba970bb63ea26e812a4 Mon Sep 17 00:00:00 2001 From: branchseer Date: Sun, 30 Nov 2025 22:11:39 +0800 Subject: [PATCH 07/31] rename TaskCache to CommandCache --- crates/vite_task/src/cache.rs | 6 +++--- crates/vite_task/src/config/workspace.rs | 10 +++++----- crates/vite_task/src/lib.rs | 2 +- crates/vite_task/src/schedule.rs | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/vite_task/src/cache.rs b/crates/vite_task/src/cache.rs index 43fe7842..75d51cc1 100644 --- a/crates/vite_task/src/cache.rs +++ b/crates/vite_task/src/cache.rs @@ -43,7 +43,7 @@ impl CommandCacheValue { } #[derive(Debug)] -pub struct TaskCache { +pub struct CommandCache { conn: Mutex, pub(crate) path: AbsolutePathBuf, } @@ -85,7 +85,7 @@ impl Display for FingerprintMismatch { } } -impl TaskCache { +impl CommandCache { pub fn load_from_path(cache_path: AbsolutePathBuf) -> Result { let path: &AbsolutePath = cache_path.as_ref(); tracing::info!("Creating task cache directory at {:?}", path); @@ -185,7 +185,7 @@ impl TaskCache { } // basic database operations -impl TaskCache { +impl CommandCache { async fn get_key_by_value>( &self, table: &str, diff --git a/crates/vite_task/src/config/workspace.rs b/crates/vite_task/src/config/workspace.rs index 3f22c3e2..ca207d00 100644 --- a/crates/vite_task/src/config/workspace.rs +++ b/crates/vite_task/src/config/workspace.rs @@ -18,7 +18,7 @@ use super::{ }; use crate::{ Error, - cache::TaskCache, + cache::CommandCache, cmd::try_parse_as_and_list, collections::{HashMap, HashSet}, config::{DisplayOptions, TaskGroupId, name::TaskName}, @@ -34,7 +34,7 @@ pub struct Workspace { /// None indicates that it cannot find the package root from the current directory.. /// This allows distinguishing between workspace-level tasks and package-level tasks. pub(crate) current_package_path: Option, - pub(crate) task_cache: TaskCache, + pub(crate) task_cache: CommandCache, pub(crate) fs: CachedFileSystem, pub(crate) package_graph: Graph, #[expect(unused)] @@ -99,7 +99,7 @@ impl Workspace { tracing::info!("Creating task cache directory at {}", cache_dir.display()); std::fs::create_dir_all(cache_dir)?; } - let task_cache = TaskCache::load_from_path(cache_path)?; + let task_cache = CommandCache::load_from_path(cache_path)?; let package_json_path = workspace_root.join("package.json"); let package_json = if package_json_path.as_path().exists() { @@ -154,7 +154,7 @@ impl Workspace { tracing::info!("Creating task cache directory at {}", cache_dir.display()); std::fs::create_dir_all(cache_dir)?; } - let task_cache = TaskCache::load_from_path(cache_path)?; + let task_cache = CommandCache::load_from_path(cache_path)?; // Build the complete task graph let mut task_graph_builder = TaskGraphBuilder::default(); @@ -199,7 +199,7 @@ impl Workspace { }) } - pub const fn cache(&self) -> &TaskCache { + pub const fn cache(&self) -> &CommandCache { &self.task_cache } diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index c5794cb7..702d0122 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -19,7 +19,7 @@ mod workspace; mod test_utils; // Public exports for vite-plus-cli to use -pub use cache::TaskCache; +pub use cache::CommandCache; pub use config::{ResolvedTask, Workspace}; pub use error::Error; pub use execute::{CURRENT_EXECUTION_ID, EXECUTION_SUMMARY_DIR}; diff --git a/crates/vite_task/src/schedule.rs b/crates/vite_task/src/schedule.rs index f7040b2f..c29457d6 100644 --- a/crates/vite_task/src/schedule.rs +++ b/crates/vite_task/src/schedule.rs @@ -10,7 +10,7 @@ use vite_path::AbsolutePath; use crate::{ Error, - cache::{CacheMiss, CommandCacheValue, TaskCache}, + cache::{CacheMiss, CommandCache, CommandCacheValue}, config::{DisplayOptions, ResolvedTask, Workspace}, execute::{OutputKind, execute_task}, fs::FileSystem, @@ -183,7 +183,7 @@ impl ExecutionPlan { async fn get_cached_or_execute<'a>( execution_id: &'a str, task: ResolvedTask, - cache: &'a TaskCache, + cache: &'a CommandCache, fs: &'a impl FileSystem, base_dir: &'a AbsolutePath, ) -> Result<(CacheStatus, BoxFuture<'a, Result>), Error> { From 55316db0433ff72c9d37202ea35c61461de13ccf Mon Sep 17 00:00:00 2001 From: branchseer Date: Tue, 2 Dec 2025 16:09:50 +0800 Subject: [PATCH 08/31] update --- crates/vite_task/src/config/task_graph.rs | 1 + crates/vite_task/src/session.rs | 21 ++++++++++++--------- crates/vite_task_bin/src/main.rs | 8 ++++++-- 3 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 crates/vite_task/src/config/task_graph.rs diff --git a/crates/vite_task/src/config/task_graph.rs b/crates/vite_task/src/config/task_graph.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/vite_task/src/config/task_graph.rs @@ -0,0 +1 @@ + diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs index d5c04091..da6606a1 100644 --- a/crates/vite_task/src/session.rs +++ b/crates/vite_task/src/session.rs @@ -33,37 +33,40 @@ pub trait SessionHandler: Send + Sync { async fn resolve_config(&mut self, package_dir: &Path) -> anyhow::Result; } -type Lazy = LazyLock T + Send + Sync>>; - pub struct Session { handler: Box>, - - /// Lazily discovered workspace - lazy_workspace: Arc>>>, - lazy_task_graph: Lazy>, + workspace: Workspace, } /// Parameters of a CLI invocation of Vite Task, including current working directory, CLI args, and envs. +/// +/// This may come from a real CLI command, or be parsed from a task script. pub struct CLIParams { pub cwd: Arc, pub args: CLIArgs, - pub envs: HashMap, + pub envs: Arc>, } impl Session { - pub async fn init( + pub async fn new( cwd: &Arc, handler: Box>, ) -> Result { - Ok(Self { handler }) + Ok(Self { handler, workspace: Workspace::load(cwd.to_absolute_path_buf(), true)? }) } + fn plan(&self, params: CLIParams) {} + pub async fn start( &mut self, params: CLIParams, reporter: Box, ) -> anyhow::Result<()> { + let plan = self.plan(params); reporter.report_execution_plan("tree"); Ok(()) } } + +/// +struct ExecutionPlan {} diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs index 9dc216dd..ea5b48ac 100644 --- a/crates/vite_task_bin/src/main.rs +++ b/crates/vite_task_bin/src/main.rs @@ -79,12 +79,16 @@ async fn main() -> anyhow::Result<()> { let cwd = Arc::::from(current_dir()?); let args = ViteArgs::parse(); - let mut session = Session::init(&cwd, Box::new(ViteTaskHandler)).await?; + let mut session = Session::new(&cwd, Box::new(ViteTaskHandler)).await?; match args { ViteArgs::ViteTaskCLIArgs(vite_task_args) => { session .start( - CLIParams { cwd, args: vite_task_args, envs: std::env::vars_os().collect() }, + CLIParams { + cwd, + args: vite_task_args, + envs: Arc::new(std::env::vars_os().collect()), + }, Box::new(StreamReporter::default()), ) .await?; From 96602a1d334f0ede47e26cf001d8f7cd155d7d56 Mon Sep 17 00:00:00 2001 From: branchseer Date: Wed, 3 Dec 2025 15:08:46 +0800 Subject: [PATCH 09/31] add TaskDependencyType to task graph --- crates/vite_task/src/config/mod.rs | 2 +- .../src/config/task_graph_builder.rs | 55 +++++++++++++------ crates/vite_task/src/config/workspace.rs | 27 ++++----- crates/vite_task/src/session.rs | 11 +--- crates/vite_task_bin/src/main.rs | 2 +- crates/vite_task_bin/tests/snap_tests.rs | 2 +- 6 files changed, 57 insertions(+), 42 deletions(-) diff --git a/crates/vite_task/src/config/mod.rs b/crates/vite_task/src/config/mod.rs index 009b5fbd..e63d06b1 100644 --- a/crates/vite_task/src/config/mod.rs +++ b/crates/vite_task/src/config/mod.rs @@ -1,6 +1,6 @@ mod name; mod task_command; -mod task_graph_builder; +pub mod task_graph_builder; mod workspace; use std::{ diff --git a/crates/vite_task/src/config/task_graph_builder.rs b/crates/vite_task/src/config/task_graph_builder.rs index 31736a5b..d1eb862b 100644 --- a/crates/vite_task/src/config/task_graph_builder.rs +++ b/crates/vite_task/src/config/task_graph_builder.rs @@ -10,6 +10,14 @@ use crate::{ collections::{HashMap, HashSet}, }; +#[derive(Debug, Clone, Copy)] +pub enum TaskDependencyType { + /// The dependency is explicit defined by user in `dependsOn`. + Explicit, + /// The dependency is added due to topological ordering based on package dependencies. + Topological, +} + /// Uniquely identifies a task group, which is a script in `package.json`, or an entry in `vite-task.json`. /// /// A task group can be parsed into one task or multiple tasks split by `&&` @@ -41,47 +49,60 @@ pub struct TaskId { pub subcommand_index: Option, } +#[derive(Debug, Clone)] +pub struct TaskGraphNode { + task: ResolvedTask, + dependeny_types_by_task_id: HashMap, +} + #[derive(Default, Debug, Clone)] pub struct TaskGraphBuilder { - pub(crate) resolved_tasks_and_dep_ids_by_id: HashMap)>, + pub(crate) task_nodes_by_id: HashMap, } impl TaskGraphBuilder { - pub(crate) fn add_task_with_deps( + pub(crate) fn add_task_with_explicit_deps( &mut self, task: ResolvedTask, dep_ids: HashSet, ) -> Result<(), Error> { - if let Some((old_task, _)) = - self.resolved_tasks_and_dep_ids_by_id.insert(task.id(), (task, dep_ids)) - { - return Err(Error::DuplicatedTask(old_task.display_name())); + let task_node = TaskGraphNode { + task, + dependeny_types_by_task_id: dep_ids + .into_iter() + .map(|dep_id| (dep_id, TaskDependencyType::Explicit)) + .collect(), + }; + if let Some(old_task_node) = self.task_nodes_by_id.insert(task_node.task.id(), task_node) { + return Err(Error::DuplicatedTask(old_task_node.task.display_name())); } Ok(()) } /// Build the complete task graph including all tasks and their dependencies - pub(crate) fn build_complete_graph(self) -> Result, Error> { - let mut task_graph = StableDiGraph::::new(); + pub(crate) fn build_complete_graph( + self, + ) -> Result, Error> { + let mut task_graph = StableDiGraph::::new(); let mut node_indices_by_task_ids = HashMap::::new(); // Add all tasks to the graph - for (task_id, (resolved_task, _)) in &self.resolved_tasks_and_dep_ids_by_id { - let node_index = task_graph.add_node(resolved_task.clone()); + for (task_id, task_node) in &self.task_nodes_by_id { + let node_index = task_graph.add_node(task_node.task.clone()); // TODO(perf): remove clone here node_indices_by_task_ids.insert(task_id.clone(), node_index); } // Add edges from explicit dependencies - for (task_id, (_, deps)) in &self.resolved_tasks_and_dep_ids_by_id { - let current_task_index = node_indices_by_task_ids[task_id]; - for dep in deps { - let Some(&dep_index) = node_indices_by_task_ids.get(dep) else { + for (task_id, task_node) in self.task_nodes_by_id { + let current_task_index = node_indices_by_task_ids[&task_id]; + for (dep_id, dep_type) in task_node.dependeny_types_by_task_id { + let Some(&dep_index) = node_indices_by_task_ids.get(&dep_id) else { return Err(Error::TaskDependencyNotFound { - name: dep.task_group_id.task_group_name.clone(), - package_path: dep.task_group_id.config_path.clone(), + name: dep_id.task_group_id.task_group_name.clone(), + package_path: dep_id.task_group_id.config_path.clone(), }); }; - task_graph.add_edge(current_task_index, dep_index, ()); + task_graph.add_edge(current_task_index, dep_index, dep_type); } } diff --git a/crates/vite_task/src/config/workspace.rs b/crates/vite_task/src/config/workspace.rs index ca207d00..1130400d 100644 --- a/crates/vite_task/src/config/workspace.rs +++ b/crates/vite_task/src/config/workspace.rs @@ -21,7 +21,7 @@ use crate::{ cache::CommandCache, cmd::try_parse_as_and_list, collections::{HashMap, HashSet}, - config::{DisplayOptions, TaskGroupId, name::TaskName}, + config::{DisplayOptions, TaskDependencyType, TaskGroupId, name::TaskName}, fs::CachedFileSystem, }; @@ -39,7 +39,7 @@ pub struct Workspace { pub(crate) package_graph: Graph, #[expect(unused)] pub(crate) package_json: PackageJson, - pub(crate) task_graph: StableDiGraph, + pub(crate) task_graph: StableDiGraph, } impl Workspace { @@ -391,7 +391,7 @@ impl Workspace { // The consistency of node indexes between the full graph and the subgraph will make it easier to render the subgraph in UI. let filtered_graph = self.task_graph.filter_map( |node_index, _| filtered_tasks_by_node_index.remove(&node_index), - |_, ()| Some(()), // All edges between filtered tasks are preserved. + |_, dep_type| Some(()), // All edges between filtered tasks are preserved. ); Ok(filtered_graph) } @@ -478,7 +478,7 @@ impl Workspace { }) .collect::, Error>>()?; - task_graph_builder.add_task_with_deps(resolved_task, deps)?; + task_graph_builder.add_task_with_explicit_deps(resolved_task, deps)?; } } @@ -504,7 +504,7 @@ impl Workspace { } else { HashSet::default() }; - task_graph_builder.add_task_with_deps(resolved_task, deps)?; + task_graph_builder.add_task_with_explicit_deps(resolved_task, deps)?; } } else { let resolved_task = Self::resolve_task( @@ -514,7 +514,8 @@ impl Workspace { None, base_dir, )?; - task_graph_builder.add_task_with_deps(resolved_task, HashSet::default())?; + task_graph_builder + .add_task_with_explicit_deps(resolved_task, HashSet::default())?; } } } @@ -536,7 +537,7 @@ impl Workspace { HashMap::default(); // Iterate through all tasks in the graph builder to collect them - for task_id in task_graph_builder.resolved_tasks_and_dep_ids_by_id.keys() { + for task_id in task_graph_builder.task_nodes_by_id.keys() { // Extract package name and task name from the task_id // Determine the order/index for subtasks @@ -589,12 +590,12 @@ impl Workspace { } // Update the task graph builder with additional dependencies - if !additional_deps.is_empty() - && let Some((_task, deps)) = - task_graph_builder.resolved_tasks_and_dep_ids_by_id.get_mut(first_task) - { - deps.extend(additional_deps); - } + // if !additional_deps.is_empty() + // && let Some(task_node) = + // task_graph_builder.task_nodes_by_id.get_mut(first_task) + // { + // deps.extend(additional_deps); + // } } } } diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs index da6606a1..3bf152cf 100644 --- a/crates/vite_task/src/session.rs +++ b/crates/vite_task/src/session.rs @@ -1,17 +1,10 @@ -use std::{ - any, - collections::HashMap, - ffi::OsString, - path::{Path, PathBuf}, - sync::{Arc, LazyLock}, -}; +use std::{collections::HashMap, ffi::OsString, path::Path, sync::Arc}; -use petgraph::prelude::StableDiGraph; use serde::{Deserialize, Serialize}; use vite_path::AbsolutePath; use vite_str::Str; -use crate::{ResolvedTask, Workspace, cli::CLIArgs, reporter::Reporter}; +use crate::{Workspace, cli::CLIArgs, reporter::Reporter}; // Represents the real subprocess to be spawned for a custom subcommand (vite ...) pub struct SubcommandProcess { diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs index ea5b48ac..3bb1091f 100644 --- a/crates/vite_task_bin/src/main.rs +++ b/crates/vite_task_bin/src/main.rs @@ -1,4 +1,4 @@ -use std::{path::Path, sync::Arc}; +use std::sync::Arc; use clap::Parser; use vite_path::{AbsolutePath, current_dir}; diff --git a/crates/vite_task_bin/tests/snap_tests.rs b/crates/vite_task_bin/tests/snap_tests.rs index 5072d78b..f26d0c1c 100644 --- a/crates/vite_task_bin/tests/snap_tests.rs +++ b/crates/vite_task_bin/tests/snap_tests.rs @@ -1,4 +1,4 @@ -use std::env::vars_os; +use std::env::{var_os, vars_os}; #[test] fn hello() { From 44b90a1f31a3a0a4d030b4bef329129835cca23d Mon Sep 17 00:00:00 2001 From: branchseer Date: Wed, 3 Dec 2025 23:02:55 +0800 Subject: [PATCH 10/31] init vite_task_graph --- Cargo.lock | 34 +++++++++++++++++ crates/vite_task_graph/Cargo.toml | 18 +++++++++ crates/vite_task_graph/README.md | 3 ++ crates/vite_task_graph/src/config.rs | 55 ++++++++++++++++++++++++++++ crates/vite_task_graph/src/lib.rs | 37 +++++++++++++++++++ 5 files changed, 147 insertions(+) create mode 100644 crates/vite_task_graph/Cargo.toml create mode 100644 crates/vite_task_graph/README.md create mode 100644 crates/vite_task_graph/src/config.rs create mode 100644 crates/vite_task_graph/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index dd1760bc..c627a660 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,6 +1611,28 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "monostate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4cc965c89dd0615a9e822ff8002f7633d2466143d51bd58693e4b2c75aabad" +dependencies = [ + "monostate-impl", + "serde", + "serde_core", +] + +[[package]] +name = "monostate-impl" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f5b99488110875b5904839d396c2cdfaf241ff6622638acb879cc7effad5de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "nix" version = "0.23.2" @@ -3147,6 +3169,18 @@ dependencies = [ "vite_task", ] +[[package]] +name = "vite_task_graph" +version = "0.1.0" +dependencies = [ + "monostate", + "petgraph", + "serde", + "vite_path", + "vite_str", + "vite_workspace", +] + [[package]] name = "vite_tui" version = "0.0.0" diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml new file mode 100644 index 00000000..0eac8537 --- /dev/null +++ b/crates/vite_task_graph/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "vite_task_graph" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true + +[dependencies] +monostate = "1.0.2" +petgraph = { workspace = true } +serde = { workspace = true, features = ["derive"] } +vite_path = { workspace = true } +vite_str = { workspace = true } +vite_workspace = { workspace = true } + +[lints] +workspace = true diff --git a/crates/vite_task_graph/README.md b/crates/vite_task_graph/README.md new file mode 100644 index 00000000..afd40419 --- /dev/null +++ b/crates/vite_task_graph/README.md @@ -0,0 +1,3 @@ +# vite_task_graph + +Crate for building task graphs based on package graphs and task configurations. diff --git a/crates/vite_task_graph/src/config.rs b/crates/vite_task_graph/src/config.rs new file mode 100644 index 00000000..0bfefb69 --- /dev/null +++ b/crates/vite_task_graph/src/config.rs @@ -0,0 +1,55 @@ +use std::collections::{HashMap, HashSet}; + +use monostate::MustBe; +use serde::Deserialize; +use vite_path::RelativePathBuf; +use vite_str::Str; + +/// Cache-related fields of a task defined by user in `vite.config.*` +#[derive(Debug, Deserialize)] +#[serde(untagged, rename_all = "camelCase")] +pub enum UserCacheConfig { + /// Cache is enabled + Enabled { + /// The `cache` field must be true or omitted + #[serde(default)] + cache: MustBe!(true), + + // Fields only relevant when cache is enabled + /// Environment variable names to be fingerprinted and passed to the task. + envs: HashSet, + + /// Environment variable names to be passed to the task without fingerprinting. + pass_through_envs: HashSet, + }, + /// Cache is disabled + Disabled { + /// The `cache` field must be false + cache: MustBe!(false), + }, +} + +/// Task configuration defined by user in `vite.config.*` +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserTaskConfig { + /// If None, the script from `package.json` with the same name will be used + command: Option>, + + /// The working directory for the task, relative to the package root (not workspace root). + #[serde(default)] // default to empty if omitted + cwd: RelativePathBuf, + + /// Explicit dependencies of this task. + #[serde(default)] // default to empty if omitted + depends_on: HashSet, + + /// Cache-related fields + #[serde(flatten)] + cache_config: UserCacheConfig, +} + +/// User configuration file structure for `vite.config.*` +pub struct UserConfigFile { + tasks: HashMap, +} diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs new file mode 100644 index 00000000..9131badb --- /dev/null +++ b/crates/vite_task_graph/src/lib.rs @@ -0,0 +1,37 @@ +mod config; + +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +use petgraph::prelude::StableDiGraph; +use vite_path::RelativePath; +use vite_str::Str; +use vite_workspace::WorkspaceRoot; + +/// The type of a desk dependency, explaining why it's introduced. +#[derive(Debug, Clone, Copy)] +pub enum TaskDependencyType { + /// The dependency is explicit defined by user in `dependsOn`. + /// If a dependency is both explicit and topological, Explicit takes precedence. + Explicit, + /// The dependency is added due to topological ordering based on package dependencies. + Topological, +} + +/// Full task graph of a workspace. +/// +/// 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 { + // graph: StableDiGraph, +} + +pub struct TaskNode {} + +impl TaskGraph { + pub fn load(workspace_root: WorkspaceRoot<'_>, load_user_config: ()) -> Self { + todo!() + } +} From 6c6ceaf81a8f4d34ab098b97951825330bcd46a2 Mon Sep 17 00:00:00 2001 From: branchseer Date: Wed, 3 Dec 2025 23:13:50 +0800 Subject: [PATCH 11/31] add tests --- Cargo.lock | 1 + crates/vite_task_graph/Cargo.toml | 3 ++ crates/vite_task_graph/src/config.rs | 70 ++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c627a660..1aa94fc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3176,6 +3176,7 @@ dependencies = [ "monostate", "petgraph", "serde", + "serde_json", "vite_path", "vite_str", "vite_workspace", diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml index 0eac8537..b4c673fe 100644 --- a/crates/vite_task_graph/Cargo.toml +++ b/crates/vite_task_graph/Cargo.toml @@ -14,5 +14,8 @@ vite_path = { workspace = true } vite_str = { workspace = true } vite_workspace = { workspace = true } +[dev-dependencies] +serde_json = { workspace = true } + [lints] workspace = true diff --git a/crates/vite_task_graph/src/config.rs b/crates/vite_task_graph/src/config.rs index 0bfefb69..8f1e324d 100644 --- a/crates/vite_task_graph/src/config.rs +++ b/crates/vite_task_graph/src/config.rs @@ -6,8 +6,8 @@ use vite_path::RelativePathBuf; use vite_str::Str; /// Cache-related fields of a task defined by user in `vite.config.*` -#[derive(Debug, Deserialize)] -#[serde(untagged, rename_all = "camelCase")] +#[derive(Debug, Deserialize, PartialEq, Eq)] +#[serde(untagged, deny_unknown_fields, rename_all = "camelCase")] pub enum UserCacheConfig { /// Cache is enabled Enabled { @@ -17,9 +17,11 @@ pub enum UserCacheConfig { // Fields only relevant when cache is enabled /// Environment variable names to be fingerprinted and passed to the task. + #[serde(default)] // default to empty if omitted envs: HashSet, /// Environment variable names to be passed to the task without fingerprinting. + #[serde(default)] // default to empty if omitted pass_through_envs: HashSet, }, /// Cache is disabled @@ -30,8 +32,8 @@ pub enum UserCacheConfig { } /// Task configuration defined by user in `vite.config.*` -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct UserTaskConfig { /// If None, the script from `package.json` with the same name will be used command: Option>, @@ -53,3 +55,63 @@ pub struct UserTaskConfig { pub struct UserConfigFile { tasks: HashMap, } + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + #[test] + fn test_defaults() { + let user_config_json = json!({}); + let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!( + user_config, + UserTaskConfig { + command: None, + cwd: "".try_into().unwrap(), + depends_on: HashSet::new(), + cache_config: UserCacheConfig::Enabled { + cache: MustBe!(true), + envs: HashSet::new(), + pass_through_envs: HashSet::new(), + }, + } + ); + } + + #[test] + fn test_cache_disabled() { + let user_config_json = json!({ + "cache": false + }); + let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!(user_config.cache_config, UserCacheConfig::Disabled { cache: MustBe!(false) }); + } + + #[test] + fn test_cache_explictly_enabled() { + let user_config_json = json!({ + "cache": true, + "envs": ["NODE_ENV"], + }); + assert_eq!( + serde_json::from_value::(user_config_json).unwrap(), + UserCacheConfig::Enabled { + cache: MustBe!(true), + envs: ["NODE_ENV".into()].into_iter().collect(), + pass_through_envs: HashSet::new(), + }, + ); + } + + #[test] + fn test_cache_disabled_but_with_fields() { + let user_config_json = json!({ + "cache": false, + "envs": ["NODE_ENV"], + }); + assert!(serde_json::from_value::(user_config_json).is_err()); + } +} From 0a4071d53b32f4120c92141f6bbce097dd8b5300 Mon Sep 17 00:00:00 2001 From: branchseer Date: Wed, 3 Dec 2025 23:17:07 +0800 Subject: [PATCH 12/31] update tests --- crates/vite_task_graph/src/config.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/vite_task_graph/src/config.rs b/crates/vite_task_graph/src/config.rs index 8f1e324d..75fc0b59 100644 --- a/crates/vite_task_graph/src/config.rs +++ b/crates/vite_task_graph/src/config.rs @@ -33,7 +33,7 @@ pub enum UserCacheConfig { /// Task configuration defined by user in `vite.config.*` #[derive(Debug, Deserialize, PartialEq, Eq)] -#[serde(deny_unknown_fields, rename_all = "camelCase")] +#[serde(rename_all = "camelCase")] pub struct UserTaskConfig { /// If None, the script from `package.json` with the same name will be used command: Option>, @@ -114,4 +114,12 @@ mod tests { }); assert!(serde_json::from_value::(user_config_json).is_err()); } + + #[test] + fn test_deny_unknown_field() { + let user_config_json = json!({ + "foo": 42, + }); + assert!(serde_json::from_value::(user_config_json).is_err()); + } } From 9747cda626df6e298cef8f9e4c356bf7bbdc5b07 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 4 Dec 2025 23:45:13 +0800 Subject: [PATCH 13/31] update --- Cargo.lock | 10 +- Cargo.toml | 2 +- crates/vite_path/src/absolute.rs | 7 ++ crates/vite_task_graph/Cargo.toml | 8 +- crates/vite_task_graph/src/builder.rs | 95 +++++++++++++++++++ crates/vite_task_graph/src/config/command.rs | 20 ++++ crates/vite_task_graph/src/config/mod.rs | 78 +++++++++++++++ .../src/{config.rs => config/user.rs} | 40 +++++--- crates/vite_task_graph/src/lib.rs | 66 +++++++++++-- crates/vite_task_graph/src/loader.rs | 31 ++++++ 10 files changed, 330 insertions(+), 27 deletions(-) create mode 100644 crates/vite_task_graph/src/builder.rs create mode 100644 crates/vite_task_graph/src/config/command.rs create mode 100644 crates/vite_task_graph/src/config/mod.rs rename crates/vite_task_graph/src/{config.rs => config/user.rs} (77%) create mode 100644 crates/vite_task_graph/src/loader.rs diff --git a/Cargo.lock b/Cargo.lock index 1aa94fc5..14096410 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1078,7 +1078,7 @@ dependencies = [ "fspy_detours_sys", "fspy_shared", "ntapi", - "smallvec 2.0.0-alpha.11", + "smallvec 2.0.0-alpha.12", "tempfile", "widestring", "winapi", @@ -2549,9 +2549,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smallvec" -version = "2.0.0-alpha.11" +version = "2.0.0-alpha.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b96efa4bd6bdd2ff0c6615cc36fc4970cbae63cfd46ddff5cee35a1b4df570" +checksum = "ef784004ca8777809dcdad6ac37629f0a97caee4c685fcea805278d81dd8b857" [[package]] name = "socket2" @@ -3173,10 +3173,14 @@ dependencies = [ name = "vite_task_graph" version = "0.1.0" dependencies = [ + "anyhow", "monostate", "petgraph", "serde", "serde_json", + "smallvec 2.0.0-alpha.12", + "thiserror 2.0.17", + "tokio", "vite_path", "vite_str", "vite_workspace", diff --git a/Cargo.toml b/Cargo.toml index 54eac046..493e15cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ serde_yml = "0.0.12" sha2 = "0.10.9" shared_memory = "0.12.4" shell-escape = "0.1.5" -smallvec = { version = "2.0.0-alpha.11", features = ["std"] } +smallvec = { version = "2.0.0-alpha.12", features = ["std"] } stackalloc = "1.2.1" supports-color = "3.0.1" syscalls = { version = "0.6.18", default-features = false } diff --git a/crates/vite_path/src/absolute.rs b/crates/vite_path/src/absolute.rs index 77610108..203adae2 100644 --- a/crates/vite_path/src/absolute.rs +++ b/crates/vite_path/src/absolute.rs @@ -1,6 +1,7 @@ use std::{ ffi::OsStr, fmt::Display, + hash::Hash, ops::Deref, path::{Path, PathBuf}, sync::Arc, @@ -31,6 +32,12 @@ impl PartialEq for &AbsolutePath { } } +impl Hash for AbsolutePath { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + 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_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml index b4c673fe..90a0eca0 100644 --- a/crates/vite_task_graph/Cargo.toml +++ b/crates/vite_task_graph/Cargo.toml @@ -7,15 +7,17 @@ license.workspace = true rust-version.workspace = true [dependencies] +anyhow = { workspace = true } monostate = "1.0.2" petgraph = { workspace = true } serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +smallvec = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["fs"] } vite_path = { workspace = true } vite_str = { workspace = true } vite_workspace = { workspace = true } -[dev-dependencies] -serde_json = { workspace = true } - [lints] workspace = true diff --git a/crates/vite_task_graph/src/builder.rs b/crates/vite_task_graph/src/builder.rs new file mode 100644 index 00000000..de67e1ab --- /dev/null +++ b/crates/vite_task_graph/src/builder.rs @@ -0,0 +1,95 @@ +use std::{ + collections::{HashMap, hash_map::Entry}, + sync::Arc, +}; + +use petgraph::{ + graph::DiGraph, + stable_graph::{NodeIndex, StableDiGraph}, +}; +use smallvec::SmallVec; +use vite_path::AbsolutePath; +use vite_str::Str; + +use crate::{ResolvedUserTaskConfig, TaskDependencyType, TaskId, TaskNode}; + +#[derive(Debug)] +struct TaskConfigWithDependencies { + task_config: ResolvedUserTaskConfig, + dependency_specifiers: Arc<[Str]>, +} + +#[derive(Default, Debug)] +pub struct TaskGraphBuilder { + /// Grouping task configs and dependency specifiers by their TaskId + resolved_config_by_task_id: HashMap, + + /// Grouping package dirs by their package names. + /// Due to rare but possible name conflicts in monorepos, we use `SmallVec` to store multiple dirs for same name. + package_dirs_by_name: HashMap, 1>>, +} + +pub struct TaskDependencyNotFound {} + +/// The built and indexed task graph. +pub struct IndexedTaskGraph { + pub task_graph: StableDiGraph, + /// Grouping package dirs by their package names. + /// Due to rare but possible name conflicts in monorepos, we use `SmallVec` to store multiple dirs for same name. + pub package_dirs_by_name: HashMap, 1>>, +} + +impl TaskGraphBuilder { + /// Add a task to the builder. + /// + /// # Panics + /// Panics if a task node with the same `TaskId` was already added in the builder. + pub fn add_task(&mut self, task_node: TaskNode, dependency_specifiers: &Arc<[Str]>) { + match self.resolved_config_by_task_id.entry(task_node.task_id) { + Entry::Vacant(vacant) => { + vacant.insert(TaskConfigWithDependencies { + task_config: task_node.resolved_config, + dependency_specifiers: Arc::clone(&dependency_specifiers), + }); + } + Entry::Occupied(occupied) => { + panic!("Task with id {:?} was already added: {:?}", occupied.key(), occupied.get(),); + } + } + self.package_dirs_by_name + .entry(task_node.package_name.clone()) + .or_default() + .push(Arc::clone(&task_node.package_dir)); + } + + /// Build the complete task graph with tasks connected to their explict dependencies, and return it along with package_dirs_by_name. + pub(crate) fn build( + self, + ) -> Result, TaskDependencyNotFound> { + let mut task_graph = DiGraph::::new(); + + let mut node_indices_by_task_ids = HashMap::::new(); + + // Add all tasks to the graph + for (task_id, task_node) in self.resolved_config_by_task_id { + let node_index = task_graph.add_node(task_node.task.clone()); + node_indices_by_task_ids.insert(task_id.clone(), node_index); + } + + // Add edges from explicit dependencies + for (task_id, task_node) in self.resolved_config_by_task_id { + let current_task_index = node_indices_by_task_ids[&task_id]; + for (dep_id, dep_type) in task_node.dependeny_types_by_task_id { + let Some(&dep_index) = node_indices_by_task_ids.get(&dep_id) else { + return Err(Error::TaskDependencyNotFound { + name: dep_id.task_group_id.task_group_name.clone(), + package_path: dep_id.task_group_id.config_path.clone(), + }); + }; + task_graph.add_edge(current_task_index, dep_index, dep_type); + } + } + + Ok(task_graph) + } +} diff --git a/crates/vite_task_graph/src/config/command.rs b/crates/vite_task_graph/src/config/command.rs new file mode 100644 index 00000000..dfc7a444 --- /dev/null +++ b/crates/vite_task_graph/src/config/command.rs @@ -0,0 +1,20 @@ +use std::collections::{BTreeMap, HashMap}; + +use vite_str::Str; + +/// The command to run for a task +#[derive(Debug, PartialEq, Eq)] +pub enum TaskCommand { + /// The command is unparsed shell script because of unsupported shell syntaxes + ShellScript(Str), + /// The command is parsed into program and args + Parsed(TaskParsedCommand), +} + +/// A parsed command: "FOO=BAR program arg1 arg2" +#[derive(Debug, PartialEq, Eq)] +pub struct TaskParsedCommand { + pub envs: HashMap, + pub program: Str, + pub args: Box<[Str]>, +} diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs new file mode 100644 index 00000000..8c460ae8 --- /dev/null +++ b/crates/vite_task_graph/src/config/mod.rs @@ -0,0 +1,78 @@ +mod command; +mod user; + +use std::collections::HashSet; + +pub use command::TaskCommand; +use monostate::MustBe; +pub use user::{UserCacheConfig, UserConfigFile, UserTaskConfig}; +use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf}; +use vite_str::Str; + +/// Task configuration resolved from `package.json` scripts and/or `vite.config.ts` tasks, +/// without considering external factors like additional args from cli or environment variables. +/// +/// It should resolve as much as possible to the final form to save duplicated work when it's further resolved into a spawnable command later. +/// but must be independent of external factors. +/// +/// For example, `cwd` is resolved to absolute ones (no external factor can change it), +/// but `command` is not parsed into program and args yet because environment variables in it may need to be expanded. +/// +/// `depends_on` is not included here because it's represented in the task graph. +#[derive(Debug)] +pub struct ResolvedUserTaskConfig { + /// The command to run for this task + pub command: Str, + + /// The working directory for the task + pub cwd: AbsolutePathBuf, + + /// Cache-related config. None means caching is disabled. + pub cache_config: Option, +} + +#[derive(Debug)] +pub struct CacheConfig { + /// environment variable names to be fingerprinted and passed to the task, with defaults populated + pub envs: HashSet, + /// environment variable names to be passed to the task without fingerprinting, with defaults populated + pub pass_through_envs: HashSet, +} + +#[derive(Debug, thiserror::Error)] +pub enum ResolveTaskError { + /// Both package.json script and vite.config.* task define commands for the task + #[error("Both package.json script and vite.config.* task define commands for the task")] + CommandConflict, + + /// Neither package.json script nor vite.config.* task define a command for the task + #[error("Neither package.json script nor vite.config.* task define a command for the task")] + NoCommand, +} + +impl ResolvedUserTaskConfig { + /// Resolves from user config, package dir, and package.json script (if any). + pub fn resolve( + user_config: UserTaskConfig, + package_dir: &AbsolutePath, + package_json_script: Option<&str>, + ) -> Result { + let command = match (&user_config.command, package_json_script) { + (Some(_), Some(_)) => return Err(ResolveTaskError::CommandConflict), + (None, None) => return Err(ResolveTaskError::NoCommand), + (Some(cmd), None) => cmd.as_ref(), + (None, Some(script)) => script, + }; + let cwd = package_dir.join(user_config.cwd_relative_to_package); + let cache_config = match user_config.cache_config { + UserCacheConfig::Disabled { cache: MustBe!(false) } => None, + UserCacheConfig::Enabled { cache: MustBe!(true), envs, pass_through_envs } => { + Some(CacheConfig { + envs: envs.into_iter().collect(), + pass_through_envs: pass_through_envs.into_iter().collect(), + }) + } + }; + Ok(Self { command: command.into(), cwd, cache_config }) + } +} diff --git a/crates/vite_task_graph/src/config.rs b/crates/vite_task_graph/src/config/user.rs similarity index 77% rename from crates/vite_task_graph/src/config.rs rename to crates/vite_task_graph/src/config/user.rs index 75fc0b59..415551e9 100644 --- a/crates/vite_task_graph/src/config.rs +++ b/crates/vite_task_graph/src/config/user.rs @@ -1,4 +1,9 @@ -use std::collections::{HashMap, HashSet}; +//! Configuration structures for user-defined tasks in `vite.config.*` + +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use monostate::MustBe; use serde::Deserialize; @@ -18,11 +23,11 @@ pub enum UserCacheConfig { // Fields only relevant when cache is enabled /// Environment variable names to be fingerprinted and passed to the task. #[serde(default)] // default to empty if omitted - envs: HashSet, + envs: Box<[Str]>, /// Environment variable names to be passed to the task without fingerprinting. #[serde(default)] // default to empty if omitted - pass_through_envs: HashSet, + pass_through_envs: Box<[Str]>, }, /// Cache is disabled Disabled { @@ -36,22 +41,24 @@ pub enum UserCacheConfig { #[serde(rename_all = "camelCase")] pub struct UserTaskConfig { /// If None, the script from `package.json` with the same name will be used - command: Option>, + pub command: Option>, /// The working directory for the task, relative to the package root (not workspace root). #[serde(default)] // default to empty if omitted - cwd: RelativePathBuf, + #[serde(rename = "cwd")] + pub cwd_relative_to_package: RelativePathBuf, /// Explicit dependencies of this task. #[serde(default)] // default to empty if omitted - depends_on: HashSet, + pub depends_on: Arc<[Str]>, /// Cache-related fields #[serde(flatten)] - cache_config: UserCacheConfig, + pub cache_config: UserCacheConfig, } /// User configuration file structure for `vite.config.*` +#[derive(Debug, Deserialize)] pub struct UserConfigFile { tasks: HashMap, } @@ -70,17 +77,26 @@ mod tests { user_config, UserTaskConfig { command: None, - cwd: "".try_into().unwrap(), - depends_on: HashSet::new(), + cwd_relative_to_package: "".try_into().unwrap(), + depends_on: Default::default(), cache_config: UserCacheConfig::Enabled { cache: MustBe!(true), - envs: HashSet::new(), - pass_through_envs: HashSet::new(), + envs: Default::default(), + pass_through_envs: Default::default(), }, } ); } + #[test] + fn test_cwd_rename() { + let user_config_json = json!({ + "cwd": "src" + }); + let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!(user_config.cwd_relative_to_package.as_str(), "src"); + } + #[test] fn test_cache_disabled() { let user_config_json = json!({ @@ -101,7 +117,7 @@ mod tests { UserCacheConfig::Enabled { cache: MustBe!(true), envs: ["NODE_ENV".into()].into_iter().collect(), - pass_through_envs: HashSet::new(), + pass_through_envs: Default::default(), }, ); } diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 9131badb..45d3b19b 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -1,20 +1,23 @@ -mod config; +mod builder; +pub mod config; +pub mod loader; use std::{ collections::{HashMap, HashSet}, sync::Arc, }; -use petgraph::prelude::StableDiGraph; -use vite_path::RelativePath; +use config::{ResolvedUserTaskConfig, UserConfigFile}; +use petgraph::graph::DiGraph; +use vite_path::{AbsolutePath, RelativePath}; use vite_str::Str; use vite_workspace::WorkspaceRoot; /// The type of a desk dependency, explaining why it's introduced. #[derive(Debug, Clone, Copy)] pub enum TaskDependencyType { - /// The dependency is explicit defined by user in `dependsOn`. - /// If a dependency is both explicit and topological, Explicit takes precedence. + /// The dependency is explicitly declared by user in `dependsOn`. + /// If a dependency is both explicit and topological, `TaskDependencyType::Explicit` takes precedenc Explicit, /// The dependency is added due to topological ordering based on package dependencies. Topological, @@ -25,13 +28,60 @@ pub enum TaskDependencyType { /// 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 { - // graph: StableDiGraph, + graph: DiGraph, } -pub struct TaskNode {} +/// Uniquely identifies a task, by its name and the path where it's defined. +/// +/// For user defined tasks, the path is where the package dir. +/// We don't use package names because multiple packages can have the same name in a monorepo. +/// +/// For synthesized tasks, the path is the cwd where the command is run. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct TaskId { + /// For user defined tasks, this is the name of the script or the entry in `vite-task.json`. + /// + /// For synthesized tasks, this is the program. + pub task_name: Str, + + /// For user defined tasks, this is the path where the task is defined. + /// + /// For synthesized tasks, there's no config file. This value will be the cwd, + /// so that same synthesized command running under different folders will be treated as different tasks, + /// + /// Note that this is not always the cwd where the command is run, which is stored in `ResolvedUserTaskConfig`. + pub task_path: Arc, +} + +/// A node in the task graph, representing a task with its resolved configuration. +#[derive(Debug)] +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 Some(""). + /// - For synthesized tasks, this will be None, so that they won't be matched by any task specifiers. + pub package_name: Option, + + /// 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. + /// + /// However, it does not contain external factors like additional args from cli and env vars. + pub resolved_config: ResolvedUserTaskConfig, +} impl TaskGraph { - pub fn load(workspace_root: WorkspaceRoot<'_>, load_user_config: ()) -> Self { + /// Load the task graph from a discovered workspace using the provided config loader. + pub fn load( + workspace_root: WorkspaceRoot<'_>, + config_loader: impl loader::UserConfigLoader, + ) -> Self { + let package_graph = vite_workspace::get_package_graph(&workspace_root.path); todo!() } } diff --git a/crates/vite_task_graph/src/loader.rs b/crates/vite_task_graph/src/loader.rs new file mode 100644 index 00000000..9aa15f26 --- /dev/null +++ b/crates/vite_task_graph/src/loader.rs @@ -0,0 +1,31 @@ +use vite_path::AbsolutePath; + +use crate::config::UserConfigFile; + +/// Loader trait for loading user configuration files (vite.config.*). +pub trait UserConfigLoader { + fn load_user_config_file( + &self, + package_path: &AbsolutePath, + ) -> impl std::future::Future> + Send; +} + +/// A `UserConfigLoader` implementation that only loads `vite.config.json`. +/// +/// This is mainly for examples and testing as it does not require Node.js environment. +#[derive(Default, Debug)] +pub struct JsonUserConfigLoader(()); + +impl UserConfigLoader for JsonUserConfigLoader { + fn load_user_config_file( + &self, + package_path: &AbsolutePath, + ) -> impl std::future::Future> + Send { + async move { + let config_path = package_path.join("vite.config.json"); + let config_content = tokio::fs::read_to_string(&config_path).await?; + let user_config: UserConfigFile = serde_json::from_str(&config_content)?; + Ok(user_config) + } + } +} From df6886fabd2bcf135e61bff9e6cd8ed9654171f3 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 4 Dec 2025 23:45:56 +0800 Subject: [PATCH 14/31] rename get_package_graph to discover_package_graph --- crates/vite_task/src/config/workspace.rs | 2 +- crates/vite_task/src/workspace.rs | 4 ++-- crates/vite_task_graph/src/lib.rs | 2 +- crates/vite_workspace/src/lib.rs | 30 ++++++++++++------------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/vite_task/src/config/workspace.rs b/crates/vite_task/src/config/workspace.rs index 1130400d..9af9bda3 100644 --- a/crates/vite_task/src/config/workspace.rs +++ b/crates/vite_task/src/config/workspace.rs @@ -131,7 +131,7 @@ impl Workspace { let (workspace_root, cwd, current_package_path) = Self::determine_current_package_path(&cwd)?; - let package_graph = vite_workspace::get_package_graph(workspace_root)?; + let package_graph = vite_workspace::discover_package_graph(workspace_root)?; // Load vite-task.json files for all packages let packages_with_task_jsons = Self::load_vite_task_jsons(&package_graph, workspace_root)?; diff --git a/crates/vite_task/src/workspace.rs b/crates/vite_task/src/workspace.rs index 551e83e3..be5706c1 100644 --- a/crates/vite_task/src/workspace.rs +++ b/crates/vite_task/src/workspace.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, LazyLock, OnceLock}; use vite_path::AbsolutePath; -use vite_workspace::{WorkspaceFile, find_workspace_root, get_package_graph}; +use vite_workspace::{WorkspaceFile, discover_package_graph, find_workspace_root}; /// Type alias for a LazyLock that uses a Boxed FnOnce to initialize the value. type LazyLockWithBoxFn = LazyLock T>>; @@ -71,7 +71,7 @@ impl Workspace { pub fn get_task_graph(&self) -> anyhow::Result<&()> { let discovered_workspace = self.discover_once()?; - let package_graph = get_package_graph(&discovered_workspace.root_path)?; + let package_graph = discover_package_graph(&discovered_workspace.root_path)?; todo!() } } diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 45d3b19b..b3444c50 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -81,7 +81,7 @@ impl TaskGraph { workspace_root: WorkspaceRoot<'_>, config_loader: impl loader::UserConfigLoader, ) -> Self { - let package_graph = vite_workspace::get_package_graph(&workspace_root.path); + let package_graph = vite_workspace::discover_package_graph(&workspace_root.path); todo!() } } diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index 51d1cb6e..f3af155e 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -160,7 +160,7 @@ impl PackageGraphBuilder { } } -pub fn get_package_graph( +pub fn discover_package_graph( cwd: impl AsRef, ) -> Result, Error> { let mut graph_builder = PackageGraphBuilder::default(); @@ -242,7 +242,7 @@ mod tests { }); fs::write(temp_dir_path.join("package.json"), package_json.to_string()).unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should have exactly 1 node (the single package) assert_eq!(graph.node_count(), 1); @@ -292,7 +292,7 @@ mod tests { }); fs::write(temp_dir_path.join("packages/pkg-b/package.json"), pkg_b.to_string()).unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should have 3 nodes: root + pkg-a + pkg-b assert_eq!(graph.node_count(), 3); @@ -341,7 +341,7 @@ mod tests { fs::write(temp_dir_path.join("packages/excluded-test/package.json"), excluded.to_string()) .unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should have the included package let mut found_included = false; @@ -396,7 +396,7 @@ mod tests { fs::write(temp_dir_path.join("packages/excluded/a/package.json"), excluded.to_string()) .unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should have the included package let mut found_included = false; @@ -459,7 +459,7 @@ mod tests { }); fs::write(temp_dir_path.join("packages/pkg-c/package.json"), pkg_c.to_string()).unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should have correct edge types let mut found_normal_dep = false; @@ -510,7 +510,7 @@ mod tests { fs::write(temp_dir_path.join("packages/pkg-2/package.json"), pkg_2.to_string()).unwrap(); // Should return an error for duplicate package names - let result = get_package_graph(temp_dir_path); + let result = discover_package_graph(temp_dir_path); assert!(result.is_err()); if let Err(Error::DuplicatedPackageName { name, .. }) = result { @@ -554,7 +554,7 @@ mod tests { }); fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a.to_string()).unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should have 2 nodes but no edges (nameless package can't be referenced) assert_eq!(graph.node_count(), 2); @@ -593,7 +593,7 @@ mod tests { }); fs::write(temp_dir_path.join("packages/pkg-b/package.json"), pkg_b.to_string()).unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should correctly parse workspace protocol with version let mut found_edge = false; @@ -641,7 +641,7 @@ mod tests { }); fs::write(temp_dir_path.join("packages/pkg-b/package.json"), pkg_b.to_string()).unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should have 2 nodes and 2 edges (circular) assert_eq!(graph.node_count(), 2); @@ -689,7 +689,7 @@ mod tests { }); fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a.to_string()).unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should have both root and pkg-a (root added automatically) assert_eq!(graph.node_count(), 2); @@ -757,7 +757,7 @@ mod tests { }); fs::write(temp_dir_path.join("apps/web/package.json"), web_app.to_string()).unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should have 4 nodes: root + shared + ui + web-app assert_eq!(graph.node_count(), 4); @@ -851,7 +851,7 @@ mod tests { }); fs::write(temp_dir_path.join("packages/cli/package.json"), cli_pkg.to_string()).unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Should have 4 nodes: root + core + utils + cli-tool assert_eq!(graph.node_count(), 4); @@ -941,7 +941,7 @@ mod tests { fs::write(temp_dir_path.join("packages/old.backup/package.json"), backup_pkg.to_string()) .unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Check which packages were included let mut packages_found = HashSet::::new(); @@ -993,7 +993,7 @@ mod tests { }); fs::write(temp_dir_path.join("services/api/package.json"), api_pkg.to_string()).unwrap(); - let graph = get_package_graph(temp_dir_path).unwrap(); + let graph = discover_package_graph(temp_dir_path).unwrap(); // Verify packages assert_eq!(graph.node_count(), 3); // root + database + api From f9dcbfe096ba84774a7c0886670414f711760842 Mon Sep 17 00:00:00 2001 From: branchseer Date: Thu, 4 Dec 2025 23:48:11 +0800 Subject: [PATCH 15/31] add load_package_graph --- crates/vite_workspace/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index f3af155e..1c8f949a 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -160,11 +160,19 @@ impl PackageGraphBuilder { } } +/// Discover the workspace from cwd and load the package graph. pub fn discover_package_graph( cwd: impl AsRef, ) -> Result, Error> { - let mut graph_builder = PackageGraphBuilder::default(); let workspace_root = find_workspace_root(cwd.as_ref())?; + discover_package_graph(&workspace_root.path) +} + +/// Load the package graph from a discovered workspace. +pub fn load_package_graph( + workspace_root: &WorkspaceRoot<'_>, +) -> Result, Error> { + let mut graph_builder = PackageGraphBuilder::default(); let workspaces = match &workspace_root.workspace_file { WorkspaceFile::PnpmWorkspaceYaml(file) => { let workspace: PnpmWorkspace = serde_yml::from_reader(file)?; From 24b2e41e3f828e96633305001dbdf45137905a8b Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 08:40:14 +0800 Subject: [PATCH 16/31] update --- crates/vite_task_graph/src/builder.rs | 51 ++++++++++---------- crates/vite_task_graph/src/lib.rs | 29 +++++++---- crates/vite_workspace/src/package_manager.rs | 3 ++ 3 files changed, 48 insertions(+), 35 deletions(-) diff --git a/crates/vite_task_graph/src/builder.rs b/crates/vite_task_graph/src/builder.rs index de67e1ab..99d2efa2 100644 --- a/crates/vite_task_graph/src/builder.rs +++ b/crates/vite_task_graph/src/builder.rs @@ -56,40 +56,41 @@ impl TaskGraphBuilder { panic!("Task with id {:?} was already added: {:?}", occupied.key(), occupied.get(),); } } - self.package_dirs_by_name - .entry(task_node.package_name.clone()) - .or_default() - .push(Arc::clone(&task_node.package_dir)); + // self.package_dirs_by_name + // .entry(task_node.package_name.clone()) + // .or_default() + // .push(Arc::clone(&task_node.package_dir)); } /// Build the complete task graph with tasks connected to their explict dependencies, and return it along with package_dirs_by_name. pub(crate) fn build( self, ) -> Result, TaskDependencyNotFound> { - let mut task_graph = DiGraph::::new(); + todo!() + // let mut task_graph = DiGraph::::new(); - let mut node_indices_by_task_ids = HashMap::::new(); + // let mut node_indices_by_task_ids = HashMap::::new(); - // Add all tasks to the graph - for (task_id, task_node) in self.resolved_config_by_task_id { - let node_index = task_graph.add_node(task_node.task.clone()); - node_indices_by_task_ids.insert(task_id.clone(), node_index); - } + // // Add all tasks to the graph + // for (task_id, task_node) in self.resolved_config_by_task_id { + // let node_index = task_graph.add_node(task_node.task.clone()); + // node_indices_by_task_ids.insert(task_id.clone(), node_index); + // } - // Add edges from explicit dependencies - for (task_id, task_node) in self.resolved_config_by_task_id { - let current_task_index = node_indices_by_task_ids[&task_id]; - for (dep_id, dep_type) in task_node.dependeny_types_by_task_id { - let Some(&dep_index) = node_indices_by_task_ids.get(&dep_id) else { - return Err(Error::TaskDependencyNotFound { - name: dep_id.task_group_id.task_group_name.clone(), - package_path: dep_id.task_group_id.config_path.clone(), - }); - }; - task_graph.add_edge(current_task_index, dep_index, dep_type); - } - } + // // Add edges from explicit dependencies + // for (task_id, task_node) in self.resolved_config_by_task_id { + // let current_task_index = node_indices_by_task_ids[&task_id]; + // for (dep_id, dep_type) in task_node.dependeny_types_by_task_id { + // let Some(&dep_index) = node_indices_by_task_ids.get(&dep_id) else { + // return Err(Error::TaskDependencyNotFound { + // name: dep_id.task_group_id.task_group_name.clone(), + // package_path: dep_id.task_group_id.config_path.clone(), + // }); + // }; + // task_graph.add_edge(current_task_index, dep_index, dep_type); + // } + // } - Ok(task_graph) + // Ok(task_graph) } } diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index b3444c50..354e48e4 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -23,14 +23,6 @@ pub enum TaskDependencyType { Topological, } -/// Full task graph of a workspace. -/// -/// 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 { - graph: DiGraph, -} - /// Uniquely identifies a task, by its name and the path where it's defined. /// /// For user defined tasks, the path is where the package dir. @@ -75,13 +67,30 @@ pub struct TaskNode { pub resolved_config: ResolvedUserTaskConfig, } +#[derive(Debug, thiserror::Error)] +pub enum TaskGraphLoadError { + #[error("Failed to load package graph: {0}")] + PackageGraphLoadError(#[from] vite_workspace::Error), + // ConfigLoadError(loader::ConfigLoadError), +} + +/// Full task graph of a workspace. +/// +/// 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 { + graph: DiGraph, +} + impl TaskGraph { /// Load the task graph from a discovered workspace using the provided config loader. pub fn load( workspace_root: WorkspaceRoot<'_>, config_loader: impl loader::UserConfigLoader, - ) -> Self { - let package_graph = vite_workspace::discover_package_graph(&workspace_root.path); + ) -> Result { + let package_graph = vite_workspace::load_package_graph(&workspace_root)?; + + for package in package_graph.node_weights() {} todo!() } } diff --git a/crates/vite_workspace/src/package_manager.rs b/crates/vite_workspace/src/package_manager.rs index 2be76d1f..aba5dbb9 100644 --- a/crates/vite_workspace/src/package_manager.rs +++ b/crates/vite_workspace/src/package_manager.rs @@ -61,8 +61,11 @@ pub enum WorkspaceFile { /// If the workspace file is not found, but a package is found, `workspace_file` will be `NonWorkspacePackage` with the `package.json` File. #[derive(Debug)] pub struct WorkspaceRoot<'a> { + /// The absolute path of the workspace root directory. pub path: &'a AbsolutePath, + /// The cwd that the workspace was found from, relative to the workspace root. pub cwd: RelativePathBuf, + /// The workspace file. pub workspace_file: WorkspaceFile, } From 7502c2e5d8dff6017bb8db6d3e880758c269d6bc Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 09:18:38 +0800 Subject: [PATCH 17/31] fix recursive call --- crates/vite_workspace/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index 1c8f949a..89a895c7 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -165,7 +165,7 @@ pub fn discover_package_graph( cwd: impl AsRef, ) -> Result, Error> { let workspace_root = find_workspace_root(cwd.as_ref())?; - discover_package_graph(&workspace_root.path) + load_package_graph(&workspace_root) } /// Load the package graph from a discovered workspace. From 895a26b97c6ca372c3a4e866a6ce41ce146fe975 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 11:59:09 +0800 Subject: [PATCH 18/31] add snap test --- Cargo.lock | 74 ++++- Cargo.toml | 3 + crates/vite_path/src/absolute.rs | 2 +- crates/vite_task_graph/Cargo.toml | 8 +- crates/vite_task_graph/src/builder.rs | 96 ------ crates/vite_task_graph/src/config/mod.rs | 14 +- crates/vite_task_graph/src/config/user.rs | 18 +- crates/vite_task_graph/src/lib.rs | 296 ++++++++++++++++-- crates/vite_task_graph/src/loader.rs | 8 +- .../fixtures/cache-sharing/package.json | 0 .../fixtures/cache-sharing/pnpm-lock.yaml | 0 .../cache-sharing/pnpm-workspace.yaml | 0 .../comprehensive-task-graph/package.json | 0 .../packages/api/package.json | 0 .../packages/app/package.json | 0 .../packages/config/package.json | 0 .../packages/pkg#special/package.json | 0 .../packages/shared/package.json | 0 .../packages/tools/package.json | 0 .../packages/ui/package.json | 0 .../pnpm-workspace.yaml | 0 .../fixtures/conflict-test/package.json | 0 .../packages/scope-a-b/package.json | 0 .../packages/scope-a/package.json | 0 .../packages/test-package/package.json | 0 .../packages/test-package/vite-task.json | 0 .../conflict-test/pnpm-workspace.yaml | 0 .../fixtures/empty-package-test/package.json | 0 .../packages/another-empty/package.json | 0 .../packages/another-empty/vite-task.json | 0 .../packages/empty-name/package.json | 0 .../packages/empty-name/vite-task.json | 0 .../packages/normal-package/package.json | 0 .../packages/normal-package/vite-task.json | 0 .../empty-package-test/pnpm-workspace.yaml | 0 .../explicit-deps-workspace/package.json | 0 .../packages/app/package.json | 0 .../packages/app/vite-task.json | 0 .../packages/core/package.json | 0 .../packages/core/vite-task.json | 0 .../packages/utils/package.json | 0 .../packages/utils/vite-task.json | 0 .../pnpm-workspace.yaml | 0 .../fingerprint-ignore-test/README.md | 0 .../fingerprint-ignore-test/package.json | 0 .../fingerprint-ignore-test/vite-task.json | 0 .../apps/web/package.json | 0 .../package.json | 0 .../packages/app/package.json | 0 .../packages/core/package.json | 0 .../packages/utils/package.json | 0 .../pnpm-workspace.yaml | 0 .../package.json | 0 .../packages/a/package.json | 0 .../packages/b/package.json | 0 .../packages/c/package.json | 0 .../pnpm-workspace.yaml | 0 crates/vite_task_graph/tests/snapshots.rs | 50 +++ .../snapshots__task graph@cache-sharing.snap | 34 ++ ...__task graph@comprehensive-task-graph.snap | 214 +++++++++++++ .../snapshots__task graph@conflict-test.snap | 25 ++ ...pshots__task graph@empty-package-test.snap | 6 + ...s__task graph@explicit-deps-workspace.snap | 79 +++++ ...s__task graph@fingerprint-ignore-test.snap | 6 + ...graph@recursive-topological-workspace.snap | 79 +++++ ...graph@transitive-dependency-workspace.snap | 25 ++ 66 files changed, 910 insertions(+), 127 deletions(-) delete mode 100644 crates/vite_task_graph/src/builder.rs rename crates/{vite_task => vite_task_graph/tests}/fixtures/cache-sharing/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/cache-sharing/pnpm-lock.yaml (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/cache-sharing/pnpm-workspace.yaml (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/comprehensive-task-graph/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/comprehensive-task-graph/packages/api/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/comprehensive-task-graph/packages/app/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/comprehensive-task-graph/packages/config/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/comprehensive-task-graph/packages/pkg#special/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/comprehensive-task-graph/packages/shared/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/comprehensive-task-graph/packages/tools/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/comprehensive-task-graph/packages/ui/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/comprehensive-task-graph/pnpm-workspace.yaml (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/conflict-test/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/conflict-test/packages/scope-a-b/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/conflict-test/packages/scope-a/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/conflict-test/packages/test-package/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/conflict-test/packages/test-package/vite-task.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/conflict-test/pnpm-workspace.yaml (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/empty-package-test/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/empty-package-test/packages/another-empty/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/empty-package-test/packages/another-empty/vite-task.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/empty-package-test/packages/empty-name/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/empty-package-test/packages/empty-name/vite-task.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/empty-package-test/packages/normal-package/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/empty-package-test/packages/normal-package/vite-task.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/empty-package-test/pnpm-workspace.yaml (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/explicit-deps-workspace/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/explicit-deps-workspace/packages/app/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/explicit-deps-workspace/packages/app/vite-task.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/explicit-deps-workspace/packages/core/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/explicit-deps-workspace/packages/core/vite-task.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/explicit-deps-workspace/packages/utils/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/explicit-deps-workspace/packages/utils/vite-task.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/explicit-deps-workspace/pnpm-workspace.yaml (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/fingerprint-ignore-test/README.md (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/fingerprint-ignore-test/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/fingerprint-ignore-test/vite-task.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/recursive-topological-workspace/apps/web/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/recursive-topological-workspace/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/recursive-topological-workspace/packages/app/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/recursive-topological-workspace/packages/core/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/recursive-topological-workspace/packages/utils/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/recursive-topological-workspace/pnpm-workspace.yaml (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/transitive-dependency-workspace/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/transitive-dependency-workspace/packages/a/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/transitive-dependency-workspace/packages/b/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/transitive-dependency-workspace/packages/c/package.json (100%) rename crates/{vite_task => vite_task_graph/tests}/fixtures/transitive-dependency-workspace/pnpm-workspace.yaml (100%) create mode 100644 crates/vite_task_graph/tests/snapshots.rs create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap diff --git a/Cargo.lock b/Cargo.lock index 14096410..2ef86b7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -524,6 +524,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "const_format" version = "0.2.34" @@ -560,6 +572,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "copy_dir" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" +dependencies = [ + "walkdir", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -870,6 +891,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55dd888a213fc57e957abf2aa305ee3e8a28dbe05687a251f33b637cd46b0070" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "env_filter" version = "0.1.3" @@ -1290,6 +1317,19 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1362,6 +1402,20 @@ version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +[[package]] +name = "insta" +version = "1.44.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698" +dependencies = [ + "console", + "globset", + "once_cell", + "serde", + "similar", + "walkdir", +] + [[package]] name = "instability" version = "0.3.9" @@ -2529,6 +2583,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "siphasher" version = "1.0.1" @@ -3061,6 +3121,15 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vec1" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab68b56840f69efb0fefbe3ab6661499217ffdc58e2eef7c3f6f69835386322" +dependencies = [ + "smallvec 1.15.1", +] + [[package]] name = "version_check" version = "0.9.5" @@ -3174,13 +3243,16 @@ name = "vite_task_graph" version = "0.1.0" dependencies = [ "anyhow", + "copy_dir", + "insta", "monostate", "petgraph", "serde", "serde_json", - "smallvec 2.0.0-alpha.12", + "tempfile", "thiserror 2.0.17", "tokio", + "vec1", "vite_path", "vite_str", "vite_workspace", diff --git a/Cargo.toml b/Cargo.toml index 493e15cb..9e4d4195 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ color-eyre = "0.6.5" compact_str = "0.9.0" const_format = "0.2.34" constcat = "0.6.1" +copy_dir = "0.1.3" crossterm = { version = "0.29.0", features = ["event-stream"] } csv-async = { version = "1.3.1", features = ["tokio"] } ctor = "0.6" @@ -72,6 +73,7 @@ fspy_test_utils = { path = "crates/fspy_test_utils" } futures = "0.3.31" futures-core = "0.3.31" futures-util = "0.3.31" +insta = "1.44.3" itertools = "0.14.0" libc = "0.2.172" memmap2 = "0.9.7" @@ -115,6 +117,7 @@ tree-sitter-bash = "0.23.1" tui-term = "0.2.0" twox-hash = "2.1.1" uuid = "1.18.1" +vec1 = "1.12.1" vite_glob = { path = "crates/vite_glob" } vite_path = { path = "crates/vite_path" } vite_str = { path = "crates/vite_str" } diff --git a/crates/vite_path/src/absolute.rs b/crates/vite_path/src/absolute.rs index 203adae2..fc85af8e 100644 --- a/crates/vite_path/src/absolute.rs +++ b/crates/vite_path/src/absolute.rs @@ -12,7 +12,7 @@ use ref_cast::{RefCastCustom, ref_cast_custom}; use crate::relative::{FromPathError, InvalidPathDataError, RelativePathBuf}; /// A path that is guaranteed to be absolute -#[derive(RefCastCustom, Debug, PartialEq, Eq)] +#[derive(RefCastCustom, Debug, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct AbsolutePath(Path); impl AsRef for AbsolutePath { diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml index 90a0eca0..03cee953 100644 --- a/crates/vite_task_graph/Cargo.toml +++ b/crates/vite_task_graph/Cargo.toml @@ -12,12 +12,18 @@ monostate = "1.0.2" petgraph = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -smallvec = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["fs"] } +vec1 = { workspace = true, features = ["smallvec-v1"] } vite_path = { workspace = true } vite_str = { workspace = true } vite_workspace = { workspace = true } +[dev-dependencies] +copy_dir = { workspace = true } +insta = { workspace = true, features = ["glob", "json"] } +tempfile = { workspace = true } +tokio = { workspace = true, features = ["fs", "rt-multi-thread"] } + [lints] workspace = true diff --git a/crates/vite_task_graph/src/builder.rs b/crates/vite_task_graph/src/builder.rs deleted file mode 100644 index 99d2efa2..00000000 --- a/crates/vite_task_graph/src/builder.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::{ - collections::{HashMap, hash_map::Entry}, - sync::Arc, -}; - -use petgraph::{ - graph::DiGraph, - stable_graph::{NodeIndex, StableDiGraph}, -}; -use smallvec::SmallVec; -use vite_path::AbsolutePath; -use vite_str::Str; - -use crate::{ResolvedUserTaskConfig, TaskDependencyType, TaskId, TaskNode}; - -#[derive(Debug)] -struct TaskConfigWithDependencies { - task_config: ResolvedUserTaskConfig, - dependency_specifiers: Arc<[Str]>, -} - -#[derive(Default, Debug)] -pub struct TaskGraphBuilder { - /// Grouping task configs and dependency specifiers by their TaskId - resolved_config_by_task_id: HashMap, - - /// Grouping package dirs by their package names. - /// Due to rare but possible name conflicts in monorepos, we use `SmallVec` to store multiple dirs for same name. - package_dirs_by_name: HashMap, 1>>, -} - -pub struct TaskDependencyNotFound {} - -/// The built and indexed task graph. -pub struct IndexedTaskGraph { - pub task_graph: StableDiGraph, - /// Grouping package dirs by their package names. - /// Due to rare but possible name conflicts in monorepos, we use `SmallVec` to store multiple dirs for same name. - pub package_dirs_by_name: HashMap, 1>>, -} - -impl TaskGraphBuilder { - /// Add a task to the builder. - /// - /// # Panics - /// Panics if a task node with the same `TaskId` was already added in the builder. - pub fn add_task(&mut self, task_node: TaskNode, dependency_specifiers: &Arc<[Str]>) { - match self.resolved_config_by_task_id.entry(task_node.task_id) { - Entry::Vacant(vacant) => { - vacant.insert(TaskConfigWithDependencies { - task_config: task_node.resolved_config, - dependency_specifiers: Arc::clone(&dependency_specifiers), - }); - } - Entry::Occupied(occupied) => { - panic!("Task with id {:?} was already added: {:?}", occupied.key(), occupied.get(),); - } - } - // self.package_dirs_by_name - // .entry(task_node.package_name.clone()) - // .or_default() - // .push(Arc::clone(&task_node.package_dir)); - } - - /// Build the complete task graph with tasks connected to their explict dependencies, and return it along with package_dirs_by_name. - pub(crate) fn build( - self, - ) -> Result, TaskDependencyNotFound> { - todo!() - // let mut task_graph = DiGraph::::new(); - - // let mut node_indices_by_task_ids = HashMap::::new(); - - // // Add all tasks to the graph - // for (task_id, task_node) in self.resolved_config_by_task_id { - // let node_index = task_graph.add_node(task_node.task.clone()); - // node_indices_by_task_ids.insert(task_id.clone(), node_index); - // } - - // // Add edges from explicit dependencies - // for (task_id, task_node) in self.resolved_config_by_task_id { - // let current_task_index = node_indices_by_task_ids[&task_id]; - // for (dep_id, dep_type) in task_node.dependeny_types_by_task_id { - // let Some(&dep_index) = node_indices_by_task_ids.get(&dep_id) else { - // return Err(Error::TaskDependencyNotFound { - // name: dep_id.task_group_id.task_group_name.clone(), - // package_path: dep_id.task_group_id.config_path.clone(), - // }); - // }; - // task_graph.add_edge(current_task_index, dep_index, dep_type); - // } - // } - - // Ok(task_graph) - } -} diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index 8c460ae8..f05d2417 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -1,7 +1,7 @@ mod command; mod user; -use std::collections::HashSet; +use std::{collections::HashSet, sync::Arc}; pub use command::TaskCommand; use monostate::MustBe; @@ -51,6 +51,18 @@ pub enum ResolveTaskError { } impl ResolvedUserTaskConfig { + pub fn resolve_package_json_script( + package_dir: &AbsolutePath, + package_json_script: &str, + ) -> Self { + Self::resolve( + UserTaskConfig::package_json_script_default(), + package_dir, + Some(package_json_script), + ) + .expect("Command conflict/missing for package.json script should never happen") + } + /// Resolves from user config, package dir, and package.json script (if any). pub fn resolve( user_config: UserTaskConfig, diff --git a/crates/vite_task_graph/src/config/user.rs b/crates/vite_task_graph/src/config/user.rs index 415551e9..bd3c7418 100644 --- a/crates/vite_task_graph/src/config/user.rs +++ b/crates/vite_task_graph/src/config/user.rs @@ -57,10 +57,26 @@ pub struct UserTaskConfig { pub cache_config: UserCacheConfig, } +impl UserTaskConfig { + /// The default user task config for package.json scripts. + pub fn package_json_script_default() -> Self { + Self { + command: None, + cwd_relative_to_package: RelativePathBuf::default(), + depends_on: Arc::new([]), + cache_config: UserCacheConfig::Enabled { + cache: MustBe!(true), + envs: Box::new([]), + pass_through_envs: Box::new([]), + }, + } + } +} + /// User configuration file structure for `vite.config.*` #[derive(Debug, Deserialize)] pub struct UserConfigFile { - tasks: HashMap, + pub tasks: HashMap, } #[cfg(test)] diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 354e48e4..632f52c9 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -1,20 +1,21 @@ -mod builder; pub mod config; pub mod loader; use std::{ - collections::{HashMap, HashSet}, + collections::{HashMap, hash_map::Entry}, sync::Arc, }; use config::{ResolvedUserTaskConfig, UserConfigFile}; -use petgraph::graph::DiGraph; -use vite_path::{AbsolutePath, RelativePath}; +use petgraph::graph::{DiGraph, NodeIndex}; +use serde::Serialize; +use vec1::smallvec_v1::SmallVec1; +use vite_path::AbsolutePath; use vite_str::Str; use vite_workspace::WorkspaceRoot; /// The type of a desk dependency, explaining why it's introduced. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize)] pub enum TaskDependencyType { /// The dependency is explicitly declared by user in `dependsOn`. /// If a dependency is both explicit and topological, `TaskDependencyType::Explicit` takes precedenc @@ -25,24 +26,20 @@ pub enum TaskDependencyType { /// Uniquely identifies a task, by its name and the path where it's defined. /// -/// For user defined tasks, the path is where the package dir. -/// We don't use package names because multiple packages can have the same name in a monorepo. -/// -/// For synthesized tasks, the path is the cwd where the command is run. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +/// We use package_dir instead of package_name because multiple packages can have the same name in a monorepo. +#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)] pub struct TaskId { + /// This is the path of the package where the task is defined. + /// + /// Note that this is not always the cwd where the command is run, which is stored in `ResolvedUserTaskConfig`. + /// + /// `package_dir` is declared from `task_name` to make the `PartialOrd` implmentation group tasks in same packages together. + pub package_dir: Arc, + /// For user defined tasks, this is the name of the script or the entry in `vite-task.json`. /// /// For synthesized tasks, this is the program. pub task_name: Str, - - /// For user defined tasks, this is the path where the task is defined. - /// - /// For synthesized tasks, there's no config file. This value will be the cwd, - /// so that same synthesized command running under different folders will be treated as different tasks, - /// - /// Note that this is not always the cwd where the command is run, which is stored in `ResolvedUserTaskConfig`. - pub task_path: Arc, } /// A node in the task graph, representing a task with its resolved configuration. @@ -54,9 +51,8 @@ pub struct TaskNode { /// 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 Some(""). - /// - For synthesized tasks, this will be None, so that they won't be matched by any task specifiers. - pub package_name: Option, + /// 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. /// @@ -71,7 +67,43 @@ pub struct TaskNode { pub enum TaskGraphLoadError { #[error("Failed to load package graph: {0}")] PackageGraphLoadError(#[from] vite_workspace::Error), - // ConfigLoadError(loader::ConfigLoadError), + + #[error("Failed to load task config file for package at {package_path:?}: {error}")] + ConfigLoadError { + #[source] + error: anyhow::Error, + package_path: Arc, + }, + + #[error("Failed to resolve task config for task {0} at {1:?}: {2}", task_id.task_name, task_id.package_dir, error)] + ResolveConfigError { + #[source] + error: crate::config::ResolveTaskError, + task_id: TaskId, + }, + + #[error("Failed to lookup dependency '{specifier}' of task {0} at {1:?}: {error}", origin_task_id.task_name, origin_task_id.task_name)] + DependencySpecifierLookupError { + #[source] + error: SpecifierLookupError, + specifier: Str, + // Where the dependency specifier is defined + origin_task_id: TaskId, + }, +} + +#[derive(Debug, thiserror::Error)] +pub enum SpecifierLookupError { + #[error( + "Package name '{package_name}' is ambiguous among multiple packages: {package_paths:?}" + )] + AmbiguousPackageName { package_name: Str, package_paths: Box<[Arc]> }, + + #[error("Package name '{package_name}' not found")] + PackageNameNotFound { package_name: Str }, + + #[error("Task name '{0}' not found in package {1:?}", task_id.task_name, task_id.package_dir)] + TaskNameNotFound { task_id: TaskId }, } /// Full task graph of a workspace. @@ -80,17 +112,231 @@ pub enum TaskGraphLoadError { /// External factors (e.g. additional args from cli, current working directory, environmental variables) are not stored here. pub struct TaskGraph { graph: DiGraph, + + /// Grouping package dirs by their package names. + /// Due to rare but possible name conflicts in monorepos, we use `SmallVec1` to store multiple dirs for same name. + package_dirs_by_name: HashMap; 1]>>, + + /// task indices by task id for quick lookup + node_indices_by_task_id: HashMap, } impl TaskGraph { /// Load the task graph from a discovered workspace using the provided config loader. - pub fn load( + pub async fn load( workspace_root: WorkspaceRoot<'_>, config_loader: impl loader::UserConfigLoader, ) -> Result { + let mut task_graph = DiGraph::::new(); + let package_graph = vite_workspace::load_package_graph(&workspace_root)?; - for package in package_graph.node_weights() {} - todo!() + let mut dependency_specifiers_with_node_indices: Vec<(Arc<[Str]>, NodeIndex)> = Vec::new(); + + // Load task nodes into `task_graph` + for package in package_graph.node_weights() { + let package_dir: Arc = workspace_root.path.join(&package.path).into(); + + // Collect package.json scripts into a mutable map for draining lookup. + let mut package_json_scripts: HashMap<&str, &str> = package + .package_json + .scripts + .iter() + .map(|(name, value)| (name.as_str(), value.as_str())) + .collect(); + + // Load vite.config.* for the package + let user_config: UserConfigFile = + config_loader.load_user_config_file(&package_dir).await.map_err(|error| { + TaskGraphLoadError::ConfigLoadError { error, package_path: package_dir.clone() } + })?; + + for (task_name, task_user_config) in user_config.tasks { + // For each task defined in vite.config.*, look up the corresponding package.json script (if any) + let package_json_script = package_json_scripts.remove(task_name.as_str()); + + let task_id = + TaskId { task_name: task_name.clone(), package_dir: Arc::clone(&package_dir) }; + + let dependency_specifiers = Arc::clone(&task_user_config.depends_on); + + // Resolve the task configuration combining vite.config.* and package.json script + let resolved_config = ResolvedUserTaskConfig::resolve( + task_user_config, + &package_dir, + package_json_script, + ) + .map_err(|err| TaskGraphLoadError::ResolveConfigError { + error: err, + task_id: task_id.clone(), + })?; + + let task_node = TaskNode { + task_id, + package_name: package.package_json.name.clone(), + resolved_config, + }; + + let node_index = task_graph.add_node(task_node); + dependency_specifiers_with_node_indices.push((dependency_specifiers, node_index)); + } + + // For remaining package.json scripts not defined in vite.config.*, create tasks with default config + for (script_name, package_json_script) in package_json_scripts.drain() { + let task_id = TaskId { + task_name: Str::from(script_name), + package_dir: Arc::clone(&package_dir), + }; + let resolved_config = ResolvedUserTaskConfig::resolve_package_json_script( + &package_dir, + package_json_script, + ); + task_graph.add_node(TaskNode { + task_id, + package_name: package.package_json.name.clone(), + resolved_config, + }); + } + } + + // index tasks by ids + let mut node_indices_by_task_id: HashMap = + HashMap::with_capacity(task_graph.node_count()); + for node_index in task_graph.node_indices() { + let task_node = &task_graph[node_index]; + + let existing_entry = + node_indices_by_task_id.insert(task_node.task_id.clone(), node_index); + if existing_entry.is_some() { + // This should never happen as we enforce unique task ids when adding nodes. + panic!("Duplicate task id found: {:?}", task_node.task_id); + } + } + + // Grouping package dirs by their package names. + let mut package_dirs_by_name: HashMap; 1]>> = + HashMap::new(); + for package in package_graph.node_weights() { + let package_dir: Arc = workspace_root.path.join(&package.path).into(); + match package_dirs_by_name.entry(package.package_json.name.clone()) { + Entry::Vacant(vacant) => { + vacant.insert(SmallVec1::new(package_dir)); + } + Entry::Occupied(occupied) => { + occupied.into_mut().push(package_dir); + } + } + } + + // Construct `Self` with task_graph with all task nodes ready and indexed, but no edges. + let mut me = Self { graph: task_graph, node_indices_by_task_id, package_dirs_by_name }; + + // Add explict dependencies + for (dependency_specifiers, from_node_index) in dependency_specifiers_with_node_indices { + let from_task_id = me.graph[from_node_index].task_id.clone(); + + for specifier in dependency_specifiers.iter().cloned() { + let to_node_index = me + .get_task_index_by_specifier(&specifier, &from_task_id.package_dir) + .map_err(|error| TaskGraphLoadError::DependencySpecifierLookupError { + error, + specifier, + origin_task_id: from_task_id.clone(), + })?; + me.graph.update_edge(from_node_index, to_node_index, TaskDependencyType::Explicit); + } + } + + // TODO: Add topological dependencies based on package dependencies + + Ok(me) + } + + /// Lookup the node index of a task by its 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( + &self, + specifier: &str, + package_origin: &Arc, + ) -> Result { + let (package_dir, task_name): (Arc, Str) = + if let Some((package_name, task_name)) = specifier.rsplit_once('#') { + // Lookup package path by the package name from '#' + let Some(package_paths) = self.package_dirs_by_name.get(package_name) else { + return Err(SpecifierLookupError::PackageNameNotFound { + package_name: package_name.into(), + }); + }; + if package_paths.len() > 1 { + return Err(SpecifierLookupError::AmbiguousPackageName { + package_name: package_name.into(), + package_paths: package_paths.iter().cloned().collect(), + }); + }; + (Arc::clone(package_paths.first()), task_name.into()) + } else { + // No '#', so the specifier only contains task name, look up in the origin path package + (Arc::clone(&package_origin), specifier.into()) + }; + let task_id = TaskId { task_name, package_dir }; + let Some(node_index) = self.node_indices_by_task_id.get(&task_id) else { + return Err(SpecifierLookupError::TaskNameNotFound { task_id }); + }; + 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 std::collections::BTreeMap; + + 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, base_dir: &AbsolutePath) -> Self { + Self { + task_name: task_id.task_name.clone(), + package_dir: task_id.package_dir.strip_prefix(base_dir).unwrap().unwrap(), + } + } + } + + #[derive(serde::Serialize)] + struct TaskNodeSnapshot { + id: TaskIdSnapshot, + command: Str, + cwd: RelativePathBuf, + depends_on: BTreeMap, + } + + let mut node_snapshots = Vec::::with_capacity(self.graph.node_count()); + for a in self.graph.node_indices() { + let node = &self.graph[a]; + let depends_on = self + .graph + .edges_directed(a, petgraph::Direction::Outgoing) + .map(|edge| { + use petgraph::visit::EdgeRef as _; + let target_node = &self.graph[edge.target()]; + (TaskIdSnapshot::from_task_id(&target_node.task_id, base_dir), *edge.weight()) + }) + .collect(); + node_snapshots.push(TaskNodeSnapshot { + id: TaskIdSnapshot::from_task_id(&node.task_id, base_dir), + 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)); + + serde_json::to_value(&node_snapshots).unwrap() } } diff --git a/crates/vite_task_graph/src/loader.rs b/crates/vite_task_graph/src/loader.rs index 9aa15f26..7a3c932b 100644 --- a/crates/vite_task_graph/src/loader.rs +++ b/crates/vite_task_graph/src/loader.rs @@ -23,7 +23,13 @@ impl UserConfigLoader for JsonUserConfigLoader { ) -> impl std::future::Future> + Send { async move { let config_path = package_path.join("vite.config.json"); - let config_content = tokio::fs::read_to_string(&config_path).await?; + let config_content = match tokio::fs::read_to_string(&config_path).await { + Ok(content) => content, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + return Ok(UserConfigFile { tasks: Default::default() }); + } + Err(err) => return Err(err.into()), + }; let user_config: UserConfigFile = serde_json::from_str(&config_content)?; Ok(user_config) } diff --git a/crates/vite_task/fixtures/cache-sharing/package.json b/crates/vite_task_graph/tests/fixtures/cache-sharing/package.json similarity index 100% rename from crates/vite_task/fixtures/cache-sharing/package.json rename to crates/vite_task_graph/tests/fixtures/cache-sharing/package.json diff --git a/crates/vite_task/fixtures/cache-sharing/pnpm-lock.yaml b/crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-lock.yaml similarity index 100% rename from crates/vite_task/fixtures/cache-sharing/pnpm-lock.yaml rename to crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-lock.yaml diff --git a/crates/vite_task/fixtures/cache-sharing/pnpm-workspace.yaml b/crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task/fixtures/cache-sharing/pnpm-workspace.yaml rename to crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-workspace.yaml diff --git a/crates/vite_task/fixtures/comprehensive-task-graph/package.json b/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/package.json similarity index 100% rename from crates/vite_task/fixtures/comprehensive-task-graph/package.json rename to crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/package.json diff --git a/crates/vite_task/fixtures/comprehensive-task-graph/packages/api/package.json b/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/api/package.json similarity index 100% rename from crates/vite_task/fixtures/comprehensive-task-graph/packages/api/package.json rename to crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/api/package.json diff --git a/crates/vite_task/fixtures/comprehensive-task-graph/packages/app/package.json b/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/app/package.json similarity index 100% rename from crates/vite_task/fixtures/comprehensive-task-graph/packages/app/package.json rename to crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/app/package.json diff --git a/crates/vite_task/fixtures/comprehensive-task-graph/packages/config/package.json b/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/config/package.json similarity index 100% rename from crates/vite_task/fixtures/comprehensive-task-graph/packages/config/package.json rename to crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/config/package.json diff --git a/crates/vite_task/fixtures/comprehensive-task-graph/packages/pkg#special/package.json b/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/pkg#special/package.json similarity index 100% rename from crates/vite_task/fixtures/comprehensive-task-graph/packages/pkg#special/package.json rename to crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/pkg#special/package.json diff --git a/crates/vite_task/fixtures/comprehensive-task-graph/packages/shared/package.json b/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/shared/package.json similarity index 100% rename from crates/vite_task/fixtures/comprehensive-task-graph/packages/shared/package.json rename to crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/shared/package.json diff --git a/crates/vite_task/fixtures/comprehensive-task-graph/packages/tools/package.json b/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/tools/package.json similarity index 100% rename from crates/vite_task/fixtures/comprehensive-task-graph/packages/tools/package.json rename to crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/tools/package.json diff --git a/crates/vite_task/fixtures/comprehensive-task-graph/packages/ui/package.json b/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/ui/package.json similarity index 100% rename from crates/vite_task/fixtures/comprehensive-task-graph/packages/ui/package.json rename to crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/packages/ui/package.json diff --git a/crates/vite_task/fixtures/comprehensive-task-graph/pnpm-workspace.yaml b/crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task/fixtures/comprehensive-task-graph/pnpm-workspace.yaml rename to crates/vite_task_graph/tests/fixtures/comprehensive-task-graph/pnpm-workspace.yaml diff --git a/crates/vite_task/fixtures/conflict-test/package.json b/crates/vite_task_graph/tests/fixtures/conflict-test/package.json similarity index 100% rename from crates/vite_task/fixtures/conflict-test/package.json rename to crates/vite_task_graph/tests/fixtures/conflict-test/package.json diff --git a/crates/vite_task/fixtures/conflict-test/packages/scope-a-b/package.json b/crates/vite_task_graph/tests/fixtures/conflict-test/packages/scope-a-b/package.json similarity index 100% rename from crates/vite_task/fixtures/conflict-test/packages/scope-a-b/package.json rename to crates/vite_task_graph/tests/fixtures/conflict-test/packages/scope-a-b/package.json diff --git a/crates/vite_task/fixtures/conflict-test/packages/scope-a/package.json b/crates/vite_task_graph/tests/fixtures/conflict-test/packages/scope-a/package.json similarity index 100% rename from crates/vite_task/fixtures/conflict-test/packages/scope-a/package.json rename to crates/vite_task_graph/tests/fixtures/conflict-test/packages/scope-a/package.json diff --git a/crates/vite_task/fixtures/conflict-test/packages/test-package/package.json b/crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/package.json similarity index 100% rename from crates/vite_task/fixtures/conflict-test/packages/test-package/package.json rename to crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/package.json diff --git a/crates/vite_task/fixtures/conflict-test/packages/test-package/vite-task.json b/crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite-task.json similarity index 100% rename from crates/vite_task/fixtures/conflict-test/packages/test-package/vite-task.json rename to crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite-task.json diff --git a/crates/vite_task/fixtures/conflict-test/pnpm-workspace.yaml b/crates/vite_task_graph/tests/fixtures/conflict-test/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task/fixtures/conflict-test/pnpm-workspace.yaml rename to crates/vite_task_graph/tests/fixtures/conflict-test/pnpm-workspace.yaml diff --git a/crates/vite_task/fixtures/empty-package-test/package.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/package.json similarity index 100% rename from crates/vite_task/fixtures/empty-package-test/package.json rename to crates/vite_task_graph/tests/fixtures/empty-package-test/package.json diff --git a/crates/vite_task/fixtures/empty-package-test/packages/another-empty/package.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/package.json similarity index 100% rename from crates/vite_task/fixtures/empty-package-test/packages/another-empty/package.json rename to crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/package.json diff --git a/crates/vite_task/fixtures/empty-package-test/packages/another-empty/vite-task.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite-task.json similarity index 100% rename from crates/vite_task/fixtures/empty-package-test/packages/another-empty/vite-task.json rename to crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite-task.json diff --git a/crates/vite_task/fixtures/empty-package-test/packages/empty-name/package.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/package.json similarity index 100% rename from crates/vite_task/fixtures/empty-package-test/packages/empty-name/package.json rename to crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/package.json diff --git a/crates/vite_task/fixtures/empty-package-test/packages/empty-name/vite-task.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite-task.json similarity index 100% rename from crates/vite_task/fixtures/empty-package-test/packages/empty-name/vite-task.json rename to crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite-task.json diff --git a/crates/vite_task/fixtures/empty-package-test/packages/normal-package/package.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/package.json similarity index 100% rename from crates/vite_task/fixtures/empty-package-test/packages/normal-package/package.json rename to crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/package.json diff --git a/crates/vite_task/fixtures/empty-package-test/packages/normal-package/vite-task.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite-task.json similarity index 100% rename from crates/vite_task/fixtures/empty-package-test/packages/normal-package/vite-task.json rename to crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite-task.json diff --git a/crates/vite_task/fixtures/empty-package-test/pnpm-workspace.yaml b/crates/vite_task_graph/tests/fixtures/empty-package-test/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task/fixtures/empty-package-test/pnpm-workspace.yaml rename to crates/vite_task_graph/tests/fixtures/empty-package-test/pnpm-workspace.yaml diff --git a/crates/vite_task/fixtures/explicit-deps-workspace/package.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/package.json similarity index 100% rename from crates/vite_task/fixtures/explicit-deps-workspace/package.json rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/package.json diff --git a/crates/vite_task/fixtures/explicit-deps-workspace/packages/app/package.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/package.json similarity index 100% rename from crates/vite_task/fixtures/explicit-deps-workspace/packages/app/package.json rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/package.json diff --git a/crates/vite_task/fixtures/explicit-deps-workspace/packages/app/vite-task.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite-task.json similarity index 100% rename from crates/vite_task/fixtures/explicit-deps-workspace/packages/app/vite-task.json rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite-task.json diff --git a/crates/vite_task/fixtures/explicit-deps-workspace/packages/core/package.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/package.json similarity index 100% rename from crates/vite_task/fixtures/explicit-deps-workspace/packages/core/package.json rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/package.json diff --git a/crates/vite_task/fixtures/explicit-deps-workspace/packages/core/vite-task.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite-task.json similarity index 100% rename from crates/vite_task/fixtures/explicit-deps-workspace/packages/core/vite-task.json rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite-task.json diff --git a/crates/vite_task/fixtures/explicit-deps-workspace/packages/utils/package.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/package.json similarity index 100% rename from crates/vite_task/fixtures/explicit-deps-workspace/packages/utils/package.json rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/package.json diff --git a/crates/vite_task/fixtures/explicit-deps-workspace/packages/utils/vite-task.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite-task.json similarity index 100% rename from crates/vite_task/fixtures/explicit-deps-workspace/packages/utils/vite-task.json rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite-task.json diff --git a/crates/vite_task/fixtures/explicit-deps-workspace/pnpm-workspace.yaml b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task/fixtures/explicit-deps-workspace/pnpm-workspace.yaml rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/pnpm-workspace.yaml diff --git a/crates/vite_task/fixtures/fingerprint-ignore-test/README.md b/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/README.md similarity index 100% rename from crates/vite_task/fixtures/fingerprint-ignore-test/README.md rename to crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/README.md diff --git a/crates/vite_task/fixtures/fingerprint-ignore-test/package.json b/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/package.json similarity index 100% rename from crates/vite_task/fixtures/fingerprint-ignore-test/package.json rename to crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/package.json diff --git a/crates/vite_task/fixtures/fingerprint-ignore-test/vite-task.json b/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite-task.json similarity index 100% rename from crates/vite_task/fixtures/fingerprint-ignore-test/vite-task.json rename to crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite-task.json diff --git a/crates/vite_task/fixtures/recursive-topological-workspace/apps/web/package.json b/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/apps/web/package.json similarity index 100% rename from crates/vite_task/fixtures/recursive-topological-workspace/apps/web/package.json rename to crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/apps/web/package.json diff --git a/crates/vite_task/fixtures/recursive-topological-workspace/package.json b/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/package.json similarity index 100% rename from crates/vite_task/fixtures/recursive-topological-workspace/package.json rename to crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/package.json diff --git a/crates/vite_task/fixtures/recursive-topological-workspace/packages/app/package.json b/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/app/package.json similarity index 100% rename from crates/vite_task/fixtures/recursive-topological-workspace/packages/app/package.json rename to crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/app/package.json diff --git a/crates/vite_task/fixtures/recursive-topological-workspace/packages/core/package.json b/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/core/package.json similarity index 100% rename from crates/vite_task/fixtures/recursive-topological-workspace/packages/core/package.json rename to crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/core/package.json diff --git a/crates/vite_task/fixtures/recursive-topological-workspace/packages/utils/package.json b/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/utils/package.json similarity index 100% rename from crates/vite_task/fixtures/recursive-topological-workspace/packages/utils/package.json rename to crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/packages/utils/package.json diff --git a/crates/vite_task/fixtures/recursive-topological-workspace/pnpm-workspace.yaml b/crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task/fixtures/recursive-topological-workspace/pnpm-workspace.yaml rename to crates/vite_task_graph/tests/fixtures/recursive-topological-workspace/pnpm-workspace.yaml diff --git a/crates/vite_task/fixtures/transitive-dependency-workspace/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/package.json similarity index 100% rename from crates/vite_task/fixtures/transitive-dependency-workspace/package.json rename to crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/package.json diff --git a/crates/vite_task/fixtures/transitive-dependency-workspace/packages/a/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/package.json similarity index 100% rename from crates/vite_task/fixtures/transitive-dependency-workspace/packages/a/package.json rename to crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/a/package.json diff --git a/crates/vite_task/fixtures/transitive-dependency-workspace/packages/b/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b/package.json similarity index 100% rename from crates/vite_task/fixtures/transitive-dependency-workspace/packages/b/package.json rename to crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/b/package.json diff --git a/crates/vite_task/fixtures/transitive-dependency-workspace/packages/c/package.json b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/c/package.json similarity index 100% rename from crates/vite_task/fixtures/transitive-dependency-workspace/packages/c/package.json rename to crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/packages/c/package.json diff --git a/crates/vite_task/fixtures/transitive-dependency-workspace/pnpm-workspace.yaml b/crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/pnpm-workspace.yaml similarity index 100% rename from crates/vite_task/fixtures/transitive-dependency-workspace/pnpm-workspace.yaml rename to crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace/pnpm-workspace.yaml diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs new file mode 100644 index 00000000..5f31b000 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -0,0 +1,50 @@ +use std::{ + env::{current_dir, var_os}, + path::{Path, PathBuf}, +}; + +use copy_dir::copy_dir; +use tokio::runtime::Runtime; +use vite_path::AbsolutePath; +use vite_task_graph::loader::JsonUserConfigLoader; +use vite_workspace::{discover_package_graph, find_workspace_root}; + +fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { + let case_name = case_path.file_name().unwrap().to_str().unwrap(); + if case_name.starts_with(".") { + return; // skip hidden files like .DS_Store + } + + // Copy the case directory to a temporary directory to avoid discovering workspace outside of the test case. + let case_stage_path = tmpdir.join(case_name); + copy_dir(case_path, &case_stage_path).unwrap(); + + let workspace_root = find_workspace_root(&case_stage_path).unwrap(); + + assert_eq!( + &case_stage_path, workspace_root.path, + "folder '{}' should be a workspace root", + case_name + ); + + 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_snaphost = task_graph.snapshot(&case_stage_path); + insta::assert_json_snapshot!("task graph", task_graph_snaphost); + }); +} + +#[test] +fn test_snapshots() { + let tokio_runtime = Runtime::new().unwrap(); + let tmp_dir = tempfile::tempdir().unwrap(); + let tmp_dir_path = AbsolutePath::new(tmp_dir.path()).unwrap(); + insta::glob!(current_dir().unwrap(), "tests/fixtures/*", |case_path| run_case( + &tokio_runtime, + tmp_dir_path, + case_path + )); +} diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap new file mode 100644 index 00000000..6d744fc9 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap @@ -0,0 +1,34 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: task_graph_snaphost +input_file: crates/vite_task_graph/tests/fixtures/cache-sharing +--- +[ + { + "id": { + "package_dir": "", + "task_name": "a" + }, + "command": "echo a", + "cwd": "", + "depends_on": {} + }, + { + "id": { + "package_dir": "", + "task_name": "b" + }, + "command": "echo a && echo b", + "cwd": "", + "depends_on": {} + }, + { + "id": { + "package_dir": "", + "task_name": "c" + }, + "command": "echo a && echo b && echo c", + "cwd": "", + "depends_on": {} + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap new file mode 100644 index 00000000..65d72c8c --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap @@ -0,0 +1,214 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: task_graph_snaphost +input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph +--- +[ + { + "id": { + "package_dir": "packages/api", + "task_name": "build" + }, + "command": "echo Generate schemas && echo Compile TypeScript && echo Bundle API && echo Copy assets", + "cwd": "packages/api", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/api", + "task_name": "dev" + }, + "command": "echo Watch mode && echo Start dev server", + "cwd": "packages/api", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/api", + "task_name": "start" + }, + "command": "echo Starting API server", + "cwd": "packages/api", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/api", + "task_name": "test" + }, + "command": "echo Testing API", + "cwd": "packages/api", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/app", + "task_name": "build" + }, + "command": "echo Clean dist && echo Build client && echo Build server && echo Generate manifest && echo Optimize assets", + "cwd": "packages/app", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/app", + "task_name": "deploy" + }, + "command": "echo Validate && echo Upload && echo Verify", + "cwd": "packages/app", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/app", + "task_name": "dev" + }, + "command": "echo Running dev server", + "cwd": "packages/app", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/app", + "task_name": "preview" + }, + "command": "echo Preview build", + "cwd": "packages/app", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/app", + "task_name": "test" + }, + "command": "echo Unit tests && echo Integration tests", + "cwd": "packages/app", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/config", + "task_name": "build" + }, + "command": "echo Building config", + "cwd": "packages/config", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/config", + "task_name": "validate" + }, + "command": "echo Validating config", + "cwd": "packages/config", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/pkg#special", + "task_name": "build" + }, + "command": "echo Building package with hash", + "cwd": "packages/pkg#special", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/pkg#special", + "task_name": "test" + }, + "command": "echo Testing package with hash", + "cwd": "packages/pkg#special", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/shared", + "task_name": "build" + }, + "command": "echo Cleaning && echo Compiling shared && echo Generating types", + "cwd": "packages/shared", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/shared", + "task_name": "lint" + }, + "command": "echo Linting shared", + "cwd": "packages/shared", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/shared", + "task_name": "test" + }, + "command": "echo Setting up test env && echo Running tests && echo Cleanup", + "cwd": "packages/shared", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/shared", + "task_name": "typecheck" + }, + "command": "echo Type checking shared", + "cwd": "packages/shared", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/tools", + "task_name": "generate" + }, + "command": "echo Generating tools", + "cwd": "packages/tools", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/tools", + "task_name": "validate" + }, + "command": "echo Validating", + "cwd": "packages/tools", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/ui", + "task_name": "build" + }, + "command": "echo Compile styles && echo Build components && echo Generate types", + "cwd": "packages/ui", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/ui", + "task_name": "lint" + }, + "command": "echo Linting UI", + "cwd": "packages/ui", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/ui", + "task_name": "storybook" + }, + "command": "echo Running storybook", + "cwd": "packages/ui", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/ui", + "task_name": "test" + }, + "command": "echo Testing UI", + "cwd": "packages/ui", + "depends_on": {} + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap new file mode 100644 index 00000000..422ca1fa --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap @@ -0,0 +1,25 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: task_graph_snaphost +input_file: crates/vite_task_graph/tests/fixtures/conflict-test +--- +[ + { + "id": { + "package_dir": "packages/scope-a", + "task_name": "b#c" + }, + "command": "echo Task b#c in scope-a", + "cwd": "packages/scope-a", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/scope-a-b", + "task_name": "c" + }, + "command": "echo Task c in scope-a#b", + "cwd": "packages/scope-a-b", + "depends_on": {} + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap new file mode 100644 index 00000000..544d8eea --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap @@ -0,0 +1,6 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: task_graph_snaphost +input_file: crates/vite_task_graph/tests/fixtures/empty-package-test +--- +[] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap new file mode 100644 index 00000000..1adad17f --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap @@ -0,0 +1,79 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: task_graph_snaphost +input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace +--- +[ + { + "id": { + "package_dir": "packages/app", + "task_name": "build" + }, + "command": "echo 'Building @test/app'", + "cwd": "packages/app", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/app", + "task_name": "start" + }, + "command": "echo 'Starting @test/app'", + "cwd": "packages/app", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/app", + "task_name": "test" + }, + "command": "echo 'Testing @test/app'", + "cwd": "packages/app", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/core", + "task_name": "build" + }, + "command": "echo 'Building @test/core'", + "cwd": "packages/core", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/core", + "task_name": "clean" + }, + "command": "echo 'Cleaning @test/core'", + "cwd": "packages/core", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/core", + "task_name": "test" + }, + "command": "echo 'Testing @test/core'", + "cwd": "packages/core", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/utils", + "task_name": "build" + }, + "command": "echo 'Building @test/utils'", + "cwd": "packages/utils", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/utils", + "task_name": "test" + }, + "command": "echo 'Testing @test/utils'", + "cwd": "packages/utils", + "depends_on": {} + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap new file mode 100644 index 00000000..b8ce387e --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap @@ -0,0 +1,6 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: task_graph_snaphost +input_file: crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test +--- +[] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap new file mode 100644 index 00000000..22a52101 --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap @@ -0,0 +1,79 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: task_graph_snaphost +input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspace +--- +[ + { + "id": { + "package_dir": "apps/web", + "task_name": "build" + }, + "command": "echo 'Building @test/web'", + "cwd": "apps/web", + "depends_on": {} + }, + { + "id": { + "package_dir": "apps/web", + "task_name": "dev" + }, + "command": "echo 'Running @test/web in dev mode'", + "cwd": "apps/web", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/app", + "task_name": "build" + }, + "command": "echo 'Building @test/app'", + "cwd": "packages/app", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/app", + "task_name": "test" + }, + "command": "echo 'Testing @test/app'", + "cwd": "packages/app", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/core", + "task_name": "build" + }, + "command": "echo 'Building @test/core'", + "cwd": "packages/core", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/core", + "task_name": "test" + }, + "command": "echo 'Testing @test/core'", + "cwd": "packages/core", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/utils", + "task_name": "build" + }, + "command": "echo 'Preparing @test/utils' && echo 'Building @test/utils' && echo 'Done @test/utils'", + "cwd": "packages/utils", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/utils", + "task_name": "test" + }, + "command": "echo 'Testing @test/utils'", + "cwd": "packages/utils", + "depends_on": {} + } +] 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 new file mode 100644 index 00000000..58db507f --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap @@ -0,0 +1,25 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: task_graph_snaphost +input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace +--- +[ + { + "id": { + "package_dir": "packages/a", + "task_name": "build" + }, + "command": "echo Building A", + "cwd": "packages/a", + "depends_on": {} + }, + { + "id": { + "package_dir": "packages/c", + "task_name": "build" + }, + "command": "echo Building C", + "cwd": "packages/c", + "depends_on": {} + } +] From 49bf5e6c50fd0adca0aed928ecd64947963ca92a Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:02:33 +0800 Subject: [PATCH 19/31] update --- crates/vite_task_graph/tests/snapshots.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index 5f31b000..e4ec324c 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -42,9 +42,5 @@ fn test_snapshots() { let tokio_runtime = Runtime::new().unwrap(); let tmp_dir = tempfile::tempdir().unwrap(); let tmp_dir_path = AbsolutePath::new(tmp_dir.path()).unwrap(); - insta::glob!(current_dir().unwrap(), "tests/fixtures/*", |case_path| run_case( - &tokio_runtime, - tmp_dir_path, - case_path - )); + insta::glob!("fixtures/*", |case_path| run_case(&tokio_runtime, tmp_dir_path, case_path)); } From 15d2ec5d977d819386a52e180769d505107727f6 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:02:45 +0800 Subject: [PATCH 20/31] rename vite-task.json to vite.config.json --- .../packages/test-package/{vite-task.json => vite.config.json} | 0 .../packages/another-empty/{vite-task.json => vite.config.json} | 0 .../packages/empty-name/{vite-task.json => vite.config.json} | 0 .../packages/normal-package/{vite-task.json => vite.config.json} | 0 .../packages/app/{vite-task.json => vite.config.json} | 0 .../packages/core/{vite-task.json => vite.config.json} | 0 .../packages/utils/{vite-task.json => vite.config.json} | 0 .../fingerprint-ignore-test/{vite-task.json => vite.config.json} | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/{vite-task.json => vite.config.json} (100%) rename crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/{vite-task.json => vite.config.json} (100%) rename crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/{vite-task.json => vite.config.json} (100%) rename crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/{vite-task.json => vite.config.json} (100%) rename crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/{vite-task.json => vite.config.json} (100%) rename crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/{vite-task.json => vite.config.json} (100%) rename crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/{vite-task.json => vite.config.json} (100%) rename crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/{vite-task.json => vite.config.json} (100%) diff --git a/crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite-task.json b/crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite-task.json rename to crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite-task.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite-task.json rename to crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite-task.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite-task.json rename to crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite-task.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite-task.json rename to crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite-task.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite-task.json rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite-task.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite-task.json rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite-task.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite-task.json rename to crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json diff --git a/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite-task.json b/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json similarity index 100% rename from crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite-task.json rename to crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json From c3c5d6c8618f705db77c9cf2b9986731cd2c3600 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:03:52 +0800 Subject: [PATCH 21/31] cacheable -> cache --- .../docs/rfc-cache-fingerprint-ignore-patterns.md | 4 ++-- crates/vite_task/docs/task-cache.md | 10 +++++----- crates/vite_task/docs/vite-run.md | 4 ++-- .../packages/test-package/vite.config.json | 2 +- .../packages/another-empty/vite.config.json | 8 ++++---- .../packages/empty-name/vite.config.json | 6 +++--- .../packages/normal-package/vite.config.json | 4 ++-- .../packages/app/vite.config.json | 2 +- .../packages/core/vite.config.json | 2 +- .../packages/utils/vite.config.json | 2 +- .../fixtures/fingerprint-ignore-test/vite.config.json | 2 +- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/crates/vite_task/docs/rfc-cache-fingerprint-ignore-patterns.md b/crates/vite_task/docs/rfc-cache-fingerprint-ignore-patterns.md index dc7b5434..2cbd04e0 100644 --- a/crates/vite_task/docs/rfc-cache-fingerprint-ignore-patterns.md +++ b/crates/vite_task/docs/rfc-cache-fingerprint-ignore-patterns.md @@ -31,7 +31,7 @@ Extend `TaskConfig` in `vite-task.json` to support a new optional field `fingerp "tasks": { "my-task": { "command": "echo bar", - "cacheable": true, + "cache": true, "fingerprintIgnores": [ "node_modules/**/*", "!node_modules/**/*/package.json" @@ -386,7 +386,7 @@ Example: "tasks": { "install": { "command": "pnpm install", - "cacheable": true, + "cache": true, "fingerprintIgnores": [ "node_modules/**/*", "!node_modules/**/*/package.json" diff --git a/crates/vite_task/docs/task-cache.md b/crates/vite_task/docs/task-cache.md index d21b7e42..cb91260a 100644 --- a/crates/vite_task/docs/task-cache.md +++ b/crates/vite_task/docs/task-cache.md @@ -513,16 +513,16 @@ Tasks can be marked as cacheable in `vite-task.json`: "tasks": { "build": { "command": "tsc && rollup -c", - "cacheable": true, + "cache": true, "dependsOn": ["^build"] }, "deploy": { "command": "deploy-script.sh", - "cacheable": false // Never cache deployment tasks + "cache": false // Never cache deployment tasks }, "test": { "command": "jest", - "cacheable": true + "cache": true } } } @@ -754,11 +754,11 @@ Benefit: Each `&&` separated command is cached independently. If only terser con "tasks": { "deploy": { "command": "deploy-to-production.sh", - "cacheable": false // Always run fresh + "cache": false // Always run fresh }, "notify": { "command": "slack-webhook.sh", - "cacheable": false // Side effect: sends notification + "cache": false // Side effect: sends notification } } } diff --git a/crates/vite_task/docs/vite-run.md b/crates/vite_task/docs/vite-run.md index 1da30f19..f2326860 100644 --- a/crates/vite_task/docs/vite-run.md +++ b/crates/vite_task/docs/vite-run.md @@ -125,12 +125,12 @@ Tasks can declare explicit dependencies in `vite-task.json` files using the `dep "tasks": { "lint": { "command": "eslint src", - "cacheable": true, + "cache": true, "dependsOn": ["build", "core#build"] }, "deploy": { "command": "deploy-script --prod", - "cacheable": false, + "cache": false, "dependsOn": ["test", "build", "utils#lint"] } } diff --git a/crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite.config.json b/crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite.config.json index 3cbe3a64..aca6743c 100644 --- a/crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite.config.json +++ b/crates/vite_task_graph/tests/fixtures/conflict-test/packages/test-package/vite.config.json @@ -2,7 +2,7 @@ "tasks": { "test": { "command": "echo Testing", - "cacheable": true, + "cache": true, "dependsOn": ["@test/scope-a#b#c"] } } diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json index dd531e4c..b69972a3 100644 --- a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json +++ b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/another-empty/vite.config.json @@ -2,20 +2,20 @@ "tasks": { "build": { "command": "echo 'Building another-empty package'", - "cacheable": true, + "cache": true, "dependsOn": ["lint", "normal-package#test"] }, "test": { "command": "echo 'Testing another-empty package'", - "cacheable": true + "cache": true }, "lint": { "command": "echo 'Linting another-empty package'", - "cacheable": true + "cache": true }, "deploy": { "command": "echo 'Deploying another-empty package'", - "cacheable": false, + "cache": false, "dependsOn": ["build", "test"] } } diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json index dc8c0a45..8ddea45b 100644 --- a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json +++ b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/empty-name/vite.config.json @@ -2,16 +2,16 @@ "tasks": { "build": { "command": "echo 'Building empty-name package'", - "cacheable": true, + "cache": true, "dependsOn": ["test"] }, "test": { "command": "echo 'Testing empty-name package'", - "cacheable": true + "cache": true }, "lint": { "command": "echo 'Linting empty-name package'", - "cacheable": true + "cache": true } } } diff --git a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json index 8554ee5a..26981cf1 100644 --- a/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json +++ b/crates/vite_task_graph/tests/fixtures/empty-package-test/packages/normal-package/vite.config.json @@ -2,11 +2,11 @@ "tasks": { "build": { "command": "echo 'Building normal-package'", - "cacheable": true + "cache": true }, "test": { "command": "echo 'Testing normal-package'", - "cacheable": true + "cache": true } } } diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json index 451e2836..5f218989 100644 --- a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json +++ b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/app/vite.config.json @@ -2,7 +2,7 @@ "tasks": { "deploy": { "command": "deploy-script --prod", - "cacheable": false, + "cache": false, "dependsOn": ["@test/app#build", "@test/app#test", "@test/utils#lint"] } } diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json index 3961abe0..1e27bd96 100644 --- a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json +++ b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/core/vite.config.json @@ -2,7 +2,7 @@ "tasks": { "lint": { "command": "eslint src", - "cacheable": true, + "cache": true, "dependsOn": ["@test/core#clean"] } } diff --git a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json index ce6c551e..f974f9b0 100644 --- a/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json +++ b/crates/vite_task_graph/tests/fixtures/explicit-deps-workspace/packages/utils/vite.config.json @@ -2,7 +2,7 @@ "tasks": { "lint": { "command": "eslint src", - "cacheable": true, + "cache": true, "dependsOn": ["@test/core#build", "@test/utils#build"] } } diff --git a/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json b/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json index 17892d36..c3dc8116 100644 --- a/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json +++ b/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json @@ -2,7 +2,7 @@ "tasks": { "create-files": { "command": "mkdir -p node_modules/pkg-a && echo '{\"name\":\"pkg-a\"}' > node_modules/pkg-a/package.json && echo 'content' > node_modules/pkg-a/index.js && mkdir -p dist && echo 'output' > dist/bundle.js", - "cacheable": true, + "cache": true, "fingerprintIgnores": [ "node_modules/**/*", "!node_modules/**/package.json", From d5591b25c1cb1a38e380bc799836968bc422caa9 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:06:34 +0800 Subject: [PATCH 22/31] update snapshots --- crates/vite_task_graph/src/lib.rs | 5 +- .../fingerprint-ignore-test/vite.config.json | 7 +- .../snapshots__task graph@cache-sharing.snap | 6 +- ...__task graph@comprehensive-task-graph.snap | 46 +++---- .../snapshots__task graph@conflict-test.snap | 21 ++- ...pshots__task graph@empty-package-test.snap | 122 +++++++++++++++++- ...s__task graph@explicit-deps-workspace.snap | 88 +++++++++++-- ...s__task graph@fingerprint-ignore-test.snap | 12 +- ...graph@recursive-topological-workspace.snap | 16 +-- ...graph@transitive-dependency-workspace.snap | 4 +- 10 files changed, 271 insertions(+), 56 deletions(-) diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 632f52c9..f22a55fa 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -313,13 +313,13 @@ impl TaskGraph { id: TaskIdSnapshot, command: Str, cwd: RelativePathBuf, - depends_on: BTreeMap, + depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)>, } let mut node_snapshots = Vec::::with_capacity(self.graph.node_count()); for a in self.graph.node_indices() { let node = &self.graph[a]; - let depends_on = self + let mut depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)> = self .graph .edges_directed(a, petgraph::Direction::Outgoing) .map(|edge| { @@ -328,6 +328,7 @@ impl TaskGraph { (TaskIdSnapshot::from_task_id(&target_node.task_id, base_dir), *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, base_dir), command: node.resolved_config.command.clone(), diff --git a/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json b/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json index c3dc8116..effd7a42 100644 --- a/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json +++ b/crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test/vite.config.json @@ -2,12 +2,7 @@ "tasks": { "create-files": { "command": "mkdir -p node_modules/pkg-a && echo '{\"name\":\"pkg-a\"}' > node_modules/pkg-a/package.json && echo 'content' > node_modules/pkg-a/index.js && mkdir -p dist && echo 'output' > dist/bundle.js", - "cache": true, - "fingerprintIgnores": [ - "node_modules/**/*", - "!node_modules/**/package.json", - "dist/**/*" - ] + "cache": true } } } diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap index 6d744fc9..17925eac 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap @@ -11,7 +11,7 @@ input_file: crates/vite_task_graph/tests/fixtures/cache-sharing }, "command": "echo a", "cwd": "", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -20,7 +20,7 @@ input_file: crates/vite_task_graph/tests/fixtures/cache-sharing }, "command": "echo a && echo b", "cwd": "", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -29,6 +29,6 @@ input_file: crates/vite_task_graph/tests/fixtures/cache-sharing }, "command": "echo a && echo b && echo c", "cwd": "", - "depends_on": {} + "depends_on": [] } ] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap index 65d72c8c..fc12dd46 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap @@ -11,7 +11,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Generate schemas && echo Compile TypeScript && echo Bundle API && echo Copy assets", "cwd": "packages/api", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -20,7 +20,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Watch mode && echo Start dev server", "cwd": "packages/api", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -29,7 +29,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Starting API server", "cwd": "packages/api", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -38,7 +38,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Testing API", "cwd": "packages/api", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -47,7 +47,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Clean dist && echo Build client && echo Build server && echo Generate manifest && echo Optimize assets", "cwd": "packages/app", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -56,7 +56,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Validate && echo Upload && echo Verify", "cwd": "packages/app", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -65,7 +65,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Running dev server", "cwd": "packages/app", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -74,7 +74,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Preview build", "cwd": "packages/app", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -83,7 +83,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Unit tests && echo Integration tests", "cwd": "packages/app", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -92,7 +92,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Building config", "cwd": "packages/config", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -101,7 +101,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Validating config", "cwd": "packages/config", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -110,7 +110,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Building package with hash", "cwd": "packages/pkg#special", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -119,7 +119,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Testing package with hash", "cwd": "packages/pkg#special", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -128,7 +128,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Cleaning && echo Compiling shared && echo Generating types", "cwd": "packages/shared", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -137,7 +137,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Linting shared", "cwd": "packages/shared", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -146,7 +146,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Setting up test env && echo Running tests && echo Cleanup", "cwd": "packages/shared", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -155,7 +155,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Type checking shared", "cwd": "packages/shared", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -164,7 +164,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Generating tools", "cwd": "packages/tools", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -173,7 +173,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Validating", "cwd": "packages/tools", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -182,7 +182,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Compile styles && echo Build components && echo Generate types", "cwd": "packages/ui", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -191,7 +191,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Linting UI", "cwd": "packages/ui", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -200,7 +200,7 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Running storybook", "cwd": "packages/ui", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -209,6 +209,6 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Testing UI", "cwd": "packages/ui", - "depends_on": {} + "depends_on": [] } ] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap index 422ca1fa..b6416acb 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap @@ -11,7 +11,7 @@ input_file: crates/vite_task_graph/tests/fixtures/conflict-test }, "command": "echo Task b#c in scope-a", "cwd": "packages/scope-a", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -20,6 +20,23 @@ input_file: crates/vite_task_graph/tests/fixtures/conflict-test }, "command": "echo Task c in scope-a#b", "cwd": "packages/scope-a-b", - "depends_on": {} + "depends_on": [] + }, + { + "id": { + "package_dir": "packages/test-package", + "task_name": "test" + }, + "command": "echo Testing", + "cwd": "packages/test-package", + "depends_on": [ + [ + { + "package_dir": "packages/scope-a-b", + "task_name": "c" + }, + "Explicit" + ] + ] } ] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap index 544d8eea..03d18c3a 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap @@ -3,4 +3,124 @@ source: crates/vite_task_graph/tests/snapshots.rs expression: task_graph_snaphost input_file: crates/vite_task_graph/tests/fixtures/empty-package-test --- -[] +[ + { + "id": { + "package_dir": "packages/another-empty", + "task_name": "build" + }, + "command": "echo 'Building another-empty package'", + "cwd": "packages/another-empty", + "depends_on": [ + [ + { + "package_dir": "packages/another-empty", + "task_name": "lint" + }, + "Explicit" + ], + [ + { + "package_dir": "packages/normal-package", + "task_name": "test" + }, + "Explicit" + ] + ] + }, + { + "id": { + "package_dir": "packages/another-empty", + "task_name": "deploy" + }, + "command": "echo 'Deploying another-empty package'", + "cwd": "packages/another-empty", + "depends_on": [ + [ + { + "package_dir": "packages/another-empty", + "task_name": "build" + }, + "Explicit" + ], + [ + { + "package_dir": "packages/another-empty", + "task_name": "test" + }, + "Explicit" + ] + ] + }, + { + "id": { + "package_dir": "packages/another-empty", + "task_name": "lint" + }, + "command": "echo 'Linting another-empty package'", + "cwd": "packages/another-empty", + "depends_on": [] + }, + { + "id": { + "package_dir": "packages/another-empty", + "task_name": "test" + }, + "command": "echo 'Testing another-empty package'", + "cwd": "packages/another-empty", + "depends_on": [] + }, + { + "id": { + "package_dir": "packages/empty-name", + "task_name": "build" + }, + "command": "echo 'Building empty-name package'", + "cwd": "packages/empty-name", + "depends_on": [ + [ + { + "package_dir": "packages/empty-name", + "task_name": "test" + }, + "Explicit" + ] + ] + }, + { + "id": { + "package_dir": "packages/empty-name", + "task_name": "lint" + }, + "command": "echo 'Linting empty-name package'", + "cwd": "packages/empty-name", + "depends_on": [] + }, + { + "id": { + "package_dir": "packages/empty-name", + "task_name": "test" + }, + "command": "echo 'Testing empty-name package'", + "cwd": "packages/empty-name", + "depends_on": [] + }, + { + "id": { + "package_dir": "packages/normal-package", + "task_name": "build" + }, + "command": "echo 'Building normal-package'", + "cwd": "packages/normal-package", + "depends_on": [] + }, + { + "id": { + "package_dir": "packages/normal-package", + "task_name": "test" + }, + "command": "echo 'Testing normal-package'", + "cwd": "packages/normal-package", + "depends_on": [] + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap index 1adad17f..1bf74ae0 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap @@ -11,7 +11,38 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Building @test/app'", "cwd": "packages/app", - "depends_on": {} + "depends_on": [] + }, + { + "id": { + "package_dir": "packages/app", + "task_name": "deploy" + }, + "command": "deploy-script --prod", + "cwd": "packages/app", + "depends_on": [ + [ + { + "package_dir": "packages/app", + "task_name": "build" + }, + "Explicit" + ], + [ + { + "package_dir": "packages/app", + "task_name": "test" + }, + "Explicit" + ], + [ + { + "package_dir": "packages/utils", + "task_name": "lint" + }, + "Explicit" + ] + ] }, { "id": { @@ -20,7 +51,7 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Starting @test/app'", "cwd": "packages/app", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -29,7 +60,7 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Testing @test/app'", "cwd": "packages/app", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -38,7 +69,7 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Building @test/core'", "cwd": "packages/core", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -47,7 +78,24 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Cleaning @test/core'", "cwd": "packages/core", - "depends_on": {} + "depends_on": [] + }, + { + "id": { + "package_dir": "packages/core", + "task_name": "lint" + }, + "command": "eslint src", + "cwd": "packages/core", + "depends_on": [ + [ + { + "package_dir": "packages/core", + "task_name": "clean" + }, + "Explicit" + ] + ] }, { "id": { @@ -56,7 +104,7 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Testing @test/core'", "cwd": "packages/core", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -65,7 +113,31 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Building @test/utils'", "cwd": "packages/utils", - "depends_on": {} + "depends_on": [] + }, + { + "id": { + "package_dir": "packages/utils", + "task_name": "lint" + }, + "command": "eslint src", + "cwd": "packages/utils", + "depends_on": [ + [ + { + "package_dir": "packages/core", + "task_name": "build" + }, + "Explicit" + ], + [ + { + "package_dir": "packages/utils", + "task_name": "build" + }, + "Explicit" + ] + ] }, { "id": { @@ -74,6 +146,6 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Testing @test/utils'", "cwd": "packages/utils", - "depends_on": {} + "depends_on": [] } ] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap index b8ce387e..d8563f0f 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap @@ -3,4 +3,14 @@ source: crates/vite_task_graph/tests/snapshots.rs expression: task_graph_snaphost input_file: crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test --- -[] +[ + { + "id": { + "package_dir": "", + "task_name": "create-files" + }, + "command": "mkdir -p node_modules/pkg-a && echo '{\"name\":\"pkg-a\"}' > node_modules/pkg-a/package.json && echo 'content' > node_modules/pkg-a/index.js && mkdir -p dist && echo 'output' > dist/bundle.js", + "cwd": "", + "depends_on": [] + } +] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap index 22a52101..ccff5e4f 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap @@ -11,7 +11,7 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Building @test/web'", "cwd": "apps/web", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -20,7 +20,7 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Running @test/web in dev mode'", "cwd": "apps/web", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -29,7 +29,7 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Building @test/app'", "cwd": "packages/app", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -38,7 +38,7 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Testing @test/app'", "cwd": "packages/app", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -47,7 +47,7 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Building @test/core'", "cwd": "packages/core", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -56,7 +56,7 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Testing @test/core'", "cwd": "packages/core", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -65,7 +65,7 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Preparing @test/utils' && echo 'Building @test/utils' && echo 'Done @test/utils'", "cwd": "packages/utils", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -74,6 +74,6 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Testing @test/utils'", "cwd": "packages/utils", - "depends_on": {} + "depends_on": [] } ] 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 58db507f..f303b3d4 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 @@ -11,7 +11,7 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac }, "command": "echo Building A", "cwd": "packages/a", - "depends_on": {} + "depends_on": [] }, { "id": { @@ -20,6 +20,6 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac }, "command": "echo Building C", "cwd": "packages/c", - "depends_on": {} + "depends_on": [] } ] From e2bb9d139695d17e1d4c705d8ac5aecd778ce3d8 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:08:41 +0800 Subject: [PATCH 23/31] remove unused files --- Cargo.lock | 15 ---- crates/vite_task/src/lib.rs | 2 - crates/vite_task/src/ui.rs | 2 - crates/vite_task/src/workspace.rs | 77 ---------------- crates/vite_task_bin/Cargo.toml | 26 ------ crates/vite_task_bin/src/main.rs | 98 --------------------- crates/vite_task_bin/tests/snap_tests.rs | 6 -- crates/vite_task_bin/tests/vite.config.json | 0 8 files changed, 226 deletions(-) delete mode 100644 crates/vite_task/src/workspace.rs delete mode 100644 crates/vite_task_bin/Cargo.toml delete mode 100644 crates/vite_task_bin/src/main.rs delete mode 100644 crates/vite_task_bin/tests/snap_tests.rs delete mode 100644 crates/vite_task_bin/tests/vite.config.json diff --git a/Cargo.lock b/Cargo.lock index 2ef86b7f..eed84cfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3223,21 +3223,6 @@ dependencies = [ "wax", ] -[[package]] -name = "vite_task_bin" -version = "0.0.0" -dependencies = [ - "anyhow", - "async-trait", - "clap", - "serde", - "serde_json", - "tokio", - "vite_path", - "vite_str", - "vite_task", -] - [[package]] name = "vite_task_graph" version = "0.1.0" diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 702d0122..1396ba36 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -10,10 +10,8 @@ mod fs; mod maybe_str; pub mod reporter; mod schedule; -pub mod session; mod types; mod ui; -mod workspace; #[cfg(test)] mod test_utils; diff --git a/crates/vite_task/src/ui.rs b/crates/vite_task/src/ui.rs index 632dc143..5d6351cd 100644 --- a/crates/vite_task/src/ui.rs +++ b/crates/vite_task/src/ui.rs @@ -11,8 +11,6 @@ use crate::{ schedule::{CacheStatus, ExecutionFailure, ExecutionSummary, PreExecutionStatus}, }; -pub trait TaskReporter {} - /// Wrap of `OwoColorize` that ignores style if `NO_COLOR` is set. trait ColorizeExt { fn style(&self, style: Style) -> Styled<&Self>; diff --git a/crates/vite_task/src/workspace.rs b/crates/vite_task/src/workspace.rs deleted file mode 100644 index be5706c1..00000000 --- a/crates/vite_task/src/workspace.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::sync::{Arc, LazyLock, OnceLock}; - -use vite_path::AbsolutePath; -use vite_workspace::{WorkspaceFile, discover_package_graph, find_workspace_root}; - -/// Type alias for a LazyLock that uses a Boxed FnOnce to initialize the value. -type LazyLockWithBoxFn = LazyLock T>>; - -/// Represents a lazily loaded workspace. -/// No IO is performed in initialization. -/// The workspace discovery and task graph are lazily loaded when the respective methods are called. -pub struct Workspace { - discovered: LazyLockWithBoxFn>>, -} - -struct DiscoveredWorkspace { - root_path: Arc, - task_graph: LazyLockWithBoxFn>>, -} - -impl DiscoveredWorkspace { - fn discover(cwd: &AbsolutePath) -> Result { - let workspace_root = find_workspace_root(cwd)?; - let root_path = workspace_root.path.to_absolute_path_buf().into(); - Ok(DiscoveredWorkspace { - root_path: Arc::clone(&root_path), - task_graph: LazyLock::new(Box::new(move || { - Ok(LoadedTaskGraph::load(&root_path, workspace_root.workspace_file)?) - })), - }) - } -} - -struct LoadedTaskGraph {} -impl LoadedTaskGraph { - fn load( - workspace_root: &AbsolutePath, - workspace_file: WorkspaceFile, - ) -> Result { - Ok(LoadedTaskGraph {}) - } -} - -impl Workspace { - fn new(cwd: Arc) -> Self { - Self { - discovered: LazyLock::new(Box::new(move || { - DiscoveredWorkspace::discover(&cwd).map_err(Arc::new) - })), - } - } - - fn discover_once(&self) -> anyhow::Result<&DiscoveredWorkspace> { - let discovered = - self.discovered.as_ref().map_err(|err| anyhow::Error::from(Arc::clone(&err))); - Ok(discovered?) - } - - /// Get the root path of the workspace. - /// This will trigger workspace discovery if not already done. - pub fn get_root(&self) -> anyhow::Result<&Arc> { - Ok(&self.discover_once()?.root_path) - } - - fn load_task_graph_once(&self) -> anyhow::Result<&LoadedTaskGraph> { - let discovered = self.discover_once()?; - let task_graph = - discovered.task_graph.as_ref().map_err(|err| anyhow::Error::from(Arc::clone(&err))); - Ok(task_graph?) - } - - pub fn get_task_graph(&self) -> anyhow::Result<&()> { - let discovered_workspace = self.discover_once()?; - let package_graph = discover_package_graph(&discovered_workspace.root_path)?; - todo!() - } -} diff --git a/crates/vite_task_bin/Cargo.toml b/crates/vite_task_bin/Cargo.toml deleted file mode 100644 index 4d376313..00000000 --- a/crates/vite_task_bin/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "vite_task_bin" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[[bin]] -name = "vite" -path = "src/main.rs" - -[dependencies] -anyhow = { workspace = true } -async-trait = { workspace = true } -clap = { workspace = true, features = ["derive"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -tokio = { workspace = true, features = ["full"] } -vite_path = { workspace = true } -vite_str = { workspace = true } -vite_task = { workspace = true } - -[lints] -workspace = true diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs deleted file mode 100644 index 3bb1091f..00000000 --- a/crates/vite_task_bin/src/main.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use vite_path::{AbsolutePath, current_dir}; -use vite_str::Str; -use vite_task::{ - cli::CLIArgs as ViteTaskCLIArgs, - reporter::stream::StreamReporter, - session::{CLIParams, Session, SessionHandler, SubcommandProcess}, -}; - -#[derive(Parser, Debug, PartialEq, Eq)] -#[clap(disable_help_flag = true)] -enum ViteTaskCustomSubcommand { - /// oxlint - Lint { - #[clap(trailing_var_arg = true, allow_hyphen_values = true)] - args: Vec, - }, - /// vitest - Test { - #[clap(trailing_var_arg = true, allow_hyphen_values = true)] - args: Vec, - }, - /// oxfmt - Fmt { - #[clap(trailing_var_arg = true, allow_hyphen_values = true)] - args: Vec, - }, -} - -#[test] -fn test_subcommand() { - let a = ViteTaskCustomSubcommand::try_parse_from(["vite", "lint", "hello"]); - assert_eq!(a.unwrap(), ViteTaskCustomSubcommand::Lint { args: vec![Str::from("hello")] }); - - let b = ViteTaskCustomSubcommand::try_parse_from(["vite", "lint", "--help"]); - assert_eq!(b.unwrap(), ViteTaskCustomSubcommand::Lint { args: vec![Str::from("--help")] }); -} - -#[derive(Parser, Debug)] -enum ViteArgs { - #[clap(flatten)] - ViteTaskCLIArgs(ViteTaskCLIArgs), -} - -struct ViteTaskHandler; - -#[async_trait::async_trait] -impl SessionHandler for ViteTaskHandler { - async fn process_for_subcommand( - &mut self, - subcommand: ViteTaskCustomSubcommand, - ) -> anyhow::Result { - let (program, args) = match subcommand { - ViteTaskCustomSubcommand::Lint { args } => ("oxlint", args), - ViteTaskCustomSubcommand::Test { args } => ("vitest", args), - ViteTaskCustomSubcommand::Fmt { args } => ("oxfmt", args), - }; - Ok(SubcommandProcess { program: program.into(), args }) - } - - async fn resolve_config( - &mut self, - package_dir: &std::path::Path, - ) -> anyhow::Result { - #[derive(serde::Deserialize)] - struct ViteConfig { - task: vite_task::session::ViteUserConfig, - } - let config_file = tokio::fs::read(package_dir.join("vite.config.json")).await?; - let vite_config: ViteConfig = serde_json::from_slice(&config_file)?; - Ok(vite_config.task) - } -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let cwd = Arc::::from(current_dir()?); - let args = ViteArgs::parse(); - - let mut session = Session::new(&cwd, Box::new(ViteTaskHandler)).await?; - match args { - ViteArgs::ViteTaskCLIArgs(vite_task_args) => { - session - .start( - CLIParams { - cwd, - args: vite_task_args, - envs: Arc::new(std::env::vars_os().collect()), - }, - Box::new(StreamReporter::default()), - ) - .await?; - } - }; - Ok(()) -} diff --git a/crates/vite_task_bin/tests/snap_tests.rs b/crates/vite_task_bin/tests/snap_tests.rs deleted file mode 100644 index f26d0c1c..00000000 --- a/crates/vite_task_bin/tests/snap_tests.rs +++ /dev/null @@ -1,6 +0,0 @@ -use std::env::{var_os, vars_os}; - -#[test] -fn hello() { - dbg!(env!("CARGO_BIN_EXE_vite")); -} diff --git a/crates/vite_task_bin/tests/vite.config.json b/crates/vite_task_bin/tests/vite.config.json deleted file mode 100644 index e69de29b..00000000 From f4f7c506d5723aeec9572e37cbe8991d4a207ac2 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:10:11 +0800 Subject: [PATCH 24/31] update --- .../src/config/task_graph_builder.rs | 1 + crates/vite_task_graph/src/config/command.rs | 20 ------------------- crates/vite_task_graph/src/config/mod.rs | 6 ++---- crates/vite_task_graph/src/config/user.rs | 5 +---- crates/vite_task_graph/src/lib.rs | 2 -- crates/vite_task_graph/tests/snapshots.rs | 7 ++----- 6 files changed, 6 insertions(+), 35 deletions(-) delete mode 100644 crates/vite_task_graph/src/config/command.rs diff --git a/crates/vite_task/src/config/task_graph_builder.rs b/crates/vite_task/src/config/task_graph_builder.rs index d1eb862b..0ce0bee2 100644 --- a/crates/vite_task/src/config/task_graph_builder.rs +++ b/crates/vite_task/src/config/task_graph_builder.rs @@ -15,6 +15,7 @@ pub enum TaskDependencyType { /// The dependency is explicit defined by user in `dependsOn`. Explicit, /// The dependency is added due to topological ordering based on package dependencies. + #[expect(unused)] Topological, } diff --git a/crates/vite_task_graph/src/config/command.rs b/crates/vite_task_graph/src/config/command.rs deleted file mode 100644 index dfc7a444..00000000 --- a/crates/vite_task_graph/src/config/command.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use vite_str::Str; - -/// The command to run for a task -#[derive(Debug, PartialEq, Eq)] -pub enum TaskCommand { - /// The command is unparsed shell script because of unsupported shell syntaxes - ShellScript(Str), - /// The command is parsed into program and args - Parsed(TaskParsedCommand), -} - -/// A parsed command: "FOO=BAR program arg1 arg2" -#[derive(Debug, PartialEq, Eq)] -pub struct TaskParsedCommand { - pub envs: HashMap, - pub program: Str, - pub args: Box<[Str]>, -} diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index f05d2417..a4f09439 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -1,12 +1,10 @@ -mod command; mod user; -use std::{collections::HashSet, sync::Arc}; +use std::collections::HashSet; -pub use command::TaskCommand; use monostate::MustBe; pub use user::{UserCacheConfig, UserConfigFile, UserTaskConfig}; -use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf}; +use vite_path::{AbsolutePath, AbsolutePathBuf}; use vite_str::Str; /// Task configuration resolved from `package.json` scripts and/or `vite.config.ts` tasks, diff --git a/crates/vite_task_graph/src/config/user.rs b/crates/vite_task_graph/src/config/user.rs index bd3c7418..1baff7ce 100644 --- a/crates/vite_task_graph/src/config/user.rs +++ b/crates/vite_task_graph/src/config/user.rs @@ -1,9 +1,6 @@ //! Configuration structures for user-defined tasks in `vite.config.*` -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use monostate::MustBe; use serde::Deserialize; diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index f22a55fa..b237e163 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -290,8 +290,6 @@ impl TaskGraph { /// /// All paths are relative to `base_dir`. pub fn snapshot(&self, base_dir: &AbsolutePath) -> serde_json::Value { - use std::collections::BTreeMap; - use vite_path::RelativePathBuf; #[derive(serde::Serialize, PartialEq, PartialOrd, Eq, Ord)] diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index e4ec324c..9ded16f2 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -1,13 +1,10 @@ -use std::{ - env::{current_dir, var_os}, - path::{Path, PathBuf}, -}; +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_workspace::{discover_package_graph, find_workspace_root}; +use vite_workspace::find_workspace_root; fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { let case_name = case_path.file_name().unwrap().to_str().unwrap(); From 56896f6a7f147d7573bd9b7d195190f486b991bc Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:14:33 +0800 Subject: [PATCH 25/31] revert unfinished changes to vite_task --- crates/vite_task/Cargo.toml | 2 - .../rfc-cache-fingerprint-ignore-patterns.md | 4 +- crates/vite_task/docs/task-cache.md | 10 ++-- crates/vite_task/docs/vite-run.md | 4 +- crates/vite_task/src/cache.rs | 6 +- crates/vite_task/src/config/mod.rs | 2 +- .../src/config/task_graph_builder.rs | 56 ++++++------------- crates/vite_task/src/config/workspace.rs | 39 +++++++------ crates/vite_task/src/lib.rs | 10 +--- crates/vite_task/src/schedule.rs | 4 +- 10 files changed, 52 insertions(+), 85 deletions(-) diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml index c9494d38..06d4b0a9 100644 --- a/crates/vite_task/Cargo.toml +++ b/crates/vite_task/Cargo.toml @@ -13,11 +13,9 @@ workspace = true [dependencies] anyhow = { workspace = true } -async-trait = { workspace = true } bincode = { workspace = true, features = ["derive"] } brush-parser = { workspace = true } bstr = { workspace = true } -clap = { workspace = true, features = ["derive"] } compact_str = { workspace = true, features = ["serde"] } dashmap = { workspace = true } diff-struct = { workspace = true } diff --git a/crates/vite_task/docs/rfc-cache-fingerprint-ignore-patterns.md b/crates/vite_task/docs/rfc-cache-fingerprint-ignore-patterns.md index 2cbd04e0..dc7b5434 100644 --- a/crates/vite_task/docs/rfc-cache-fingerprint-ignore-patterns.md +++ b/crates/vite_task/docs/rfc-cache-fingerprint-ignore-patterns.md @@ -31,7 +31,7 @@ Extend `TaskConfig` in `vite-task.json` to support a new optional field `fingerp "tasks": { "my-task": { "command": "echo bar", - "cache": true, + "cacheable": true, "fingerprintIgnores": [ "node_modules/**/*", "!node_modules/**/*/package.json" @@ -386,7 +386,7 @@ Example: "tasks": { "install": { "command": "pnpm install", - "cache": true, + "cacheable": true, "fingerprintIgnores": [ "node_modules/**/*", "!node_modules/**/*/package.json" diff --git a/crates/vite_task/docs/task-cache.md b/crates/vite_task/docs/task-cache.md index cb91260a..d21b7e42 100644 --- a/crates/vite_task/docs/task-cache.md +++ b/crates/vite_task/docs/task-cache.md @@ -513,16 +513,16 @@ Tasks can be marked as cacheable in `vite-task.json`: "tasks": { "build": { "command": "tsc && rollup -c", - "cache": true, + "cacheable": true, "dependsOn": ["^build"] }, "deploy": { "command": "deploy-script.sh", - "cache": false // Never cache deployment tasks + "cacheable": false // Never cache deployment tasks }, "test": { "command": "jest", - "cache": true + "cacheable": true } } } @@ -754,11 +754,11 @@ Benefit: Each `&&` separated command is cached independently. If only terser con "tasks": { "deploy": { "command": "deploy-to-production.sh", - "cache": false // Always run fresh + "cacheable": false // Always run fresh }, "notify": { "command": "slack-webhook.sh", - "cache": false // Side effect: sends notification + "cacheable": false // Side effect: sends notification } } } diff --git a/crates/vite_task/docs/vite-run.md b/crates/vite_task/docs/vite-run.md index f2326860..1da30f19 100644 --- a/crates/vite_task/docs/vite-run.md +++ b/crates/vite_task/docs/vite-run.md @@ -125,12 +125,12 @@ Tasks can declare explicit dependencies in `vite-task.json` files using the `dep "tasks": { "lint": { "command": "eslint src", - "cache": true, + "cacheable": true, "dependsOn": ["build", "core#build"] }, "deploy": { "command": "deploy-script --prod", - "cache": false, + "cacheable": false, "dependsOn": ["test", "build", "utils#lint"] } } diff --git a/crates/vite_task/src/cache.rs b/crates/vite_task/src/cache.rs index 75d51cc1..43fe7842 100644 --- a/crates/vite_task/src/cache.rs +++ b/crates/vite_task/src/cache.rs @@ -43,7 +43,7 @@ impl CommandCacheValue { } #[derive(Debug)] -pub struct CommandCache { +pub struct TaskCache { conn: Mutex, pub(crate) path: AbsolutePathBuf, } @@ -85,7 +85,7 @@ impl Display for FingerprintMismatch { } } -impl CommandCache { +impl TaskCache { pub fn load_from_path(cache_path: AbsolutePathBuf) -> Result { let path: &AbsolutePath = cache_path.as_ref(); tracing::info!("Creating task cache directory at {:?}", path); @@ -185,7 +185,7 @@ impl CommandCache { } // basic database operations -impl CommandCache { +impl TaskCache { async fn get_key_by_value>( &self, table: &str, diff --git a/crates/vite_task/src/config/mod.rs b/crates/vite_task/src/config/mod.rs index e63d06b1..009b5fbd 100644 --- a/crates/vite_task/src/config/mod.rs +++ b/crates/vite_task/src/config/mod.rs @@ -1,6 +1,6 @@ mod name; mod task_command; -pub mod task_graph_builder; +mod task_graph_builder; mod workspace; use std::{ diff --git a/crates/vite_task/src/config/task_graph_builder.rs b/crates/vite_task/src/config/task_graph_builder.rs index 0ce0bee2..31736a5b 100644 --- a/crates/vite_task/src/config/task_graph_builder.rs +++ b/crates/vite_task/src/config/task_graph_builder.rs @@ -10,15 +10,6 @@ use crate::{ collections::{HashMap, HashSet}, }; -#[derive(Debug, Clone, Copy)] -pub enum TaskDependencyType { - /// The dependency is explicit defined by user in `dependsOn`. - Explicit, - /// The dependency is added due to topological ordering based on package dependencies. - #[expect(unused)] - Topological, -} - /// Uniquely identifies a task group, which is a script in `package.json`, or an entry in `vite-task.json`. /// /// A task group can be parsed into one task or multiple tasks split by `&&` @@ -50,60 +41,47 @@ pub struct TaskId { pub subcommand_index: Option, } -#[derive(Debug, Clone)] -pub struct TaskGraphNode { - task: ResolvedTask, - dependeny_types_by_task_id: HashMap, -} - #[derive(Default, Debug, Clone)] pub struct TaskGraphBuilder { - pub(crate) task_nodes_by_id: HashMap, + pub(crate) resolved_tasks_and_dep_ids_by_id: HashMap)>, } impl TaskGraphBuilder { - pub(crate) fn add_task_with_explicit_deps( + pub(crate) fn add_task_with_deps( &mut self, task: ResolvedTask, dep_ids: HashSet, ) -> Result<(), Error> { - let task_node = TaskGraphNode { - task, - dependeny_types_by_task_id: dep_ids - .into_iter() - .map(|dep_id| (dep_id, TaskDependencyType::Explicit)) - .collect(), - }; - if let Some(old_task_node) = self.task_nodes_by_id.insert(task_node.task.id(), task_node) { - return Err(Error::DuplicatedTask(old_task_node.task.display_name())); + if let Some((old_task, _)) = + self.resolved_tasks_and_dep_ids_by_id.insert(task.id(), (task, dep_ids)) + { + return Err(Error::DuplicatedTask(old_task.display_name())); } Ok(()) } /// Build the complete task graph including all tasks and their dependencies - pub(crate) fn build_complete_graph( - self, - ) -> Result, Error> { - let mut task_graph = StableDiGraph::::new(); + pub(crate) fn build_complete_graph(self) -> Result, Error> { + let mut task_graph = StableDiGraph::::new(); let mut node_indices_by_task_ids = HashMap::::new(); // Add all tasks to the graph - for (task_id, task_node) in &self.task_nodes_by_id { - let node_index = task_graph.add_node(task_node.task.clone()); // TODO(perf): remove clone here + for (task_id, (resolved_task, _)) in &self.resolved_tasks_and_dep_ids_by_id { + let node_index = task_graph.add_node(resolved_task.clone()); node_indices_by_task_ids.insert(task_id.clone(), node_index); } // Add edges from explicit dependencies - for (task_id, task_node) in self.task_nodes_by_id { - let current_task_index = node_indices_by_task_ids[&task_id]; - for (dep_id, dep_type) in task_node.dependeny_types_by_task_id { - let Some(&dep_index) = node_indices_by_task_ids.get(&dep_id) else { + for (task_id, (_, deps)) in &self.resolved_tasks_and_dep_ids_by_id { + let current_task_index = node_indices_by_task_ids[task_id]; + for dep in deps { + let Some(&dep_index) = node_indices_by_task_ids.get(dep) else { return Err(Error::TaskDependencyNotFound { - name: dep_id.task_group_id.task_group_name.clone(), - package_path: dep_id.task_group_id.config_path.clone(), + name: dep.task_group_id.task_group_name.clone(), + package_path: dep.task_group_id.config_path.clone(), }); }; - task_graph.add_edge(current_task_index, dep_index, dep_type); + task_graph.add_edge(current_task_index, dep_index, ()); } } diff --git a/crates/vite_task/src/config/workspace.rs b/crates/vite_task/src/config/workspace.rs index 9af9bda3..3f22c3e2 100644 --- a/crates/vite_task/src/config/workspace.rs +++ b/crates/vite_task/src/config/workspace.rs @@ -18,10 +18,10 @@ use super::{ }; use crate::{ Error, - cache::CommandCache, + cache::TaskCache, cmd::try_parse_as_and_list, collections::{HashMap, HashSet}, - config::{DisplayOptions, TaskDependencyType, TaskGroupId, name::TaskName}, + config::{DisplayOptions, TaskGroupId, name::TaskName}, fs::CachedFileSystem, }; @@ -34,12 +34,12 @@ pub struct Workspace { /// None indicates that it cannot find the package root from the current directory.. /// This allows distinguishing between workspace-level tasks and package-level tasks. pub(crate) current_package_path: Option, - pub(crate) task_cache: CommandCache, + pub(crate) task_cache: TaskCache, pub(crate) fs: CachedFileSystem, pub(crate) package_graph: Graph, #[expect(unused)] pub(crate) package_json: PackageJson, - pub(crate) task_graph: StableDiGraph, + pub(crate) task_graph: StableDiGraph, } impl Workspace { @@ -99,7 +99,7 @@ impl Workspace { tracing::info!("Creating task cache directory at {}", cache_dir.display()); std::fs::create_dir_all(cache_dir)?; } - let task_cache = CommandCache::load_from_path(cache_path)?; + let task_cache = TaskCache::load_from_path(cache_path)?; let package_json_path = workspace_root.join("package.json"); let package_json = if package_json_path.as_path().exists() { @@ -131,7 +131,7 @@ impl Workspace { let (workspace_root, cwd, current_package_path) = Self::determine_current_package_path(&cwd)?; - let package_graph = vite_workspace::discover_package_graph(workspace_root)?; + let package_graph = vite_workspace::get_package_graph(workspace_root)?; // Load vite-task.json files for all packages let packages_with_task_jsons = Self::load_vite_task_jsons(&package_graph, workspace_root)?; @@ -154,7 +154,7 @@ impl Workspace { tracing::info!("Creating task cache directory at {}", cache_dir.display()); std::fs::create_dir_all(cache_dir)?; } - let task_cache = CommandCache::load_from_path(cache_path)?; + let task_cache = TaskCache::load_from_path(cache_path)?; // Build the complete task graph let mut task_graph_builder = TaskGraphBuilder::default(); @@ -199,7 +199,7 @@ impl Workspace { }) } - pub const fn cache(&self) -> &CommandCache { + pub const fn cache(&self) -> &TaskCache { &self.task_cache } @@ -391,7 +391,7 @@ impl Workspace { // The consistency of node indexes between the full graph and the subgraph will make it easier to render the subgraph in UI. let filtered_graph = self.task_graph.filter_map( |node_index, _| filtered_tasks_by_node_index.remove(&node_index), - |_, dep_type| Some(()), // All edges between filtered tasks are preserved. + |_, ()| Some(()), // All edges between filtered tasks are preserved. ); Ok(filtered_graph) } @@ -478,7 +478,7 @@ impl Workspace { }) .collect::, Error>>()?; - task_graph_builder.add_task_with_explicit_deps(resolved_task, deps)?; + task_graph_builder.add_task_with_deps(resolved_task, deps)?; } } @@ -504,7 +504,7 @@ impl Workspace { } else { HashSet::default() }; - task_graph_builder.add_task_with_explicit_deps(resolved_task, deps)?; + task_graph_builder.add_task_with_deps(resolved_task, deps)?; } } else { let resolved_task = Self::resolve_task( @@ -514,8 +514,7 @@ impl Workspace { None, base_dir, )?; - task_graph_builder - .add_task_with_explicit_deps(resolved_task, HashSet::default())?; + task_graph_builder.add_task_with_deps(resolved_task, HashSet::default())?; } } } @@ -537,7 +536,7 @@ impl Workspace { HashMap::default(); // Iterate through all tasks in the graph builder to collect them - for task_id in task_graph_builder.task_nodes_by_id.keys() { + for task_id in task_graph_builder.resolved_tasks_and_dep_ids_by_id.keys() { // Extract package name and task name from the task_id // Determine the order/index for subtasks @@ -590,12 +589,12 @@ impl Workspace { } // Update the task graph builder with additional dependencies - // if !additional_deps.is_empty() - // && let Some(task_node) = - // task_graph_builder.task_nodes_by_id.get_mut(first_task) - // { - // deps.extend(additional_deps); - // } + if !additional_deps.is_empty() + && let Some((_task, deps)) = + task_graph_builder.resolved_tasks_and_dep_ids_by_id.get_mut(first_task) + { + deps.extend(additional_deps); + } } } } diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 1396ba36..4bc75d0c 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -1,5 +1,4 @@ mod cache; -pub mod cli; mod cmd; mod collections; mod config; @@ -8,7 +7,6 @@ mod execute; mod fingerprint; mod fs; mod maybe_str; -pub mod reporter; mod schedule; mod types; mod ui; @@ -17,15 +15,9 @@ mod ui; mod test_utils; // Public exports for vite-plus-cli to use -pub use cache::CommandCache; +pub use cache::TaskCache; pub use config::{ResolvedTask, Workspace}; pub use error::Error; pub use execute::{CURRENT_EXECUTION_ID, EXECUTION_SUMMARY_DIR}; pub use schedule::{ExecutionPlan, ExecutionStatus, ExecutionSummary}; pub use types::ResolveCommandResult; - -pub enum CLIArgs { - CustomArgs(Vec), -} - -pub fn cli_main(args: CLIArgs) {} diff --git a/crates/vite_task/src/schedule.rs b/crates/vite_task/src/schedule.rs index c29457d6..f7040b2f 100644 --- a/crates/vite_task/src/schedule.rs +++ b/crates/vite_task/src/schedule.rs @@ -10,7 +10,7 @@ use vite_path::AbsolutePath; use crate::{ Error, - cache::{CacheMiss, CommandCache, CommandCacheValue}, + cache::{CacheMiss, CommandCacheValue, TaskCache}, config::{DisplayOptions, ResolvedTask, Workspace}, execute::{OutputKind, execute_task}, fs::FileSystem, @@ -183,7 +183,7 @@ impl ExecutionPlan { async fn get_cached_or_execute<'a>( execution_id: &'a str, task: ResolvedTask, - cache: &'a CommandCache, + cache: &'a TaskCache, fs: &'a impl FileSystem, base_dir: &'a AbsolutePath, ) -> Result<(CacheStatus, BoxFuture<'a, Result>), Error> { From 1c3b24eaa253839358e16390c97be8cb73cf83ca Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:14:53 +0800 Subject: [PATCH 26/31] update lock file --- Cargo.lock | 53 ----------------------------------------------------- 1 file changed, 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eed84cfb..f1fc6520 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,17 +153,6 @@ dependencies = [ "tree-sitter-facade-sg", ] -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -422,46 +411,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "clap" -version = "4.5.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" -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" @@ -3188,11 +3137,9 @@ name = "vite_task" version = "0.0.0" dependencies = [ "anyhow", - "async-trait", "bincode", "brush-parser", "bstr", - "clap", "compact_str 0.9.0", "dashmap", "diff-struct", From 68b1341c3308d63ce7acabb0ee262f3cbc4c66bd Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:15:48 +0800 Subject: [PATCH 27/31] get_package_graph -> discover_package_graph --- crates/vite_task/src/config/workspace.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vite_task/src/config/workspace.rs b/crates/vite_task/src/config/workspace.rs index 3f22c3e2..70c6713f 100644 --- a/crates/vite_task/src/config/workspace.rs +++ b/crates/vite_task/src/config/workspace.rs @@ -131,7 +131,7 @@ impl Workspace { let (workspace_root, cwd, current_package_path) = Self::determine_current_package_path(&cwd)?; - let package_graph = vite_workspace::get_package_graph(workspace_root)?; + let package_graph = vite_workspace::discover_package_graph(workspace_root)?; // Load vite-task.json files for all packages let packages_with_task_jsons = Self::load_vite_task_jsons(&package_graph, workspace_root)?; From 979cedbeab9e21db2d79f69ca1382725708bfd65 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:17:52 +0800 Subject: [PATCH 28/31] remove tests based on fixtures from vite_task --- crates/vite_task/src/config/mod.rs | 1100 ---------------------------- crates/vite_task/src/lib.rs | 3 - crates/vite_task/src/schedule.rs | 43 -- crates/vite_task/src/test_utils.rs | 28 - 4 files changed, 1174 deletions(-) delete mode 100644 crates/vite_task/src/test_utils.rs diff --git a/crates/vite_task/src/config/mod.rs b/crates/vite_task/src/config/mod.rs index 009b5fbd..e7b16edf 100644 --- a/crates/vite_task/src/config/mod.rs +++ b/crates/vite_task/src/config/mod.rs @@ -282,1103 +282,3 @@ pub struct CommandFingerprint { /// Changes to this config invalidate the cache to ensure correct fingerprint tracking. pub fingerprint_ignores: Option>, } - -#[cfg(test)] -mod tests { - use petgraph::stable_graph::StableDiGraph; - - use super::*; - use crate::{ - Error, - test_utils::{get_fixture_path, with_unique_cache_path}, - }; - - #[test] - fn test_recursive_topological_build() { - with_unique_cache_path("recursive_topological_build", |cache_path| { - let fixture_path = get_fixture_path("fixtures/recursive-topological-workspace"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test recursive topological build - let task_graph = workspace - .build_task_subgraph(&["build".into()], Arc::default(), true) - .expect("Failed to resolve tasks"); - - // Verify that all build tasks are included - let task_names: Vec<_> = - task_graph.node_weights().map(super::ResolvedTask::display_name).collect(); - - assert!(task_names.contains(&"@test/core#build".into())); - assert!(task_names.contains(&"@test/utils#build".into())); - assert!(task_names.contains(&"@test/app#build".into())); - assert!(task_names.contains(&"@test/web#build".into())); - - // Verify dependencies exist in the correct direction - let has_edge = |from: &str, to: &str| -> bool { - task_graph.edge_indices().any(|edge_idx| { - let (source, target) = task_graph.edge_endpoints(edge_idx).unwrap(); - task_graph[source].display_name() == from - && task_graph[target].display_name() == to - }) - }; - - // With topological mode, edges go from dependencies to dependents - assert!( - has_edge("@test/utils#build(subcommand 0)", "@test/core#build"), - "Core should have edge to Utils (Utils depends on Core)" - ); - assert!( - has_edge("@test/app#build", "@test/utils#build"), - "Utils should have edge to App (App depends on Utils)" - ); - assert!( - has_edge("@test/web#build", "@test/app#build"), - "App should have edge to Web (Web depends on App)" - ); - assert!( - has_edge("@test/web#build", "@test/core#build"), - "Core should have edge to Web (Web depends on Core)" - ); - - // TODO: fix indirect dependencies - // assert!( - // !has_edge("@test/web#build", "@test/utils#build"), - // "Web should have edge to utils (It should be indirect via App)" - // ); - }); - } - - #[test] - fn test_topological_run_false_no_implicit_deps() { - with_unique_cache_path("topological_run_false", |cache_path| { - let fixture_path = get_fixture_path("fixtures/recursive-topological-workspace"); - - // Load with topological_run = false - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), false) - .expect("Failed to load workspace"); - - let task_graph = workspace - .build_task_subgraph(&["@test/web#build".into()], Arc::default(), false) - .expect("Failed to resolve tasks"); - - let has_edge = |from: &str, to: &str| -> bool { - task_graph.edge_indices().any(|edge_idx| { - let (source, target) = task_graph.edge_endpoints(edge_idx).unwrap(); - task_graph[source].display_name() == from - && task_graph[target].display_name() == to - }) - }; - - // When topological_run is false, @test/web#build should NOT depend on @test/core#build - // even though @test/web depends on @test/core as a package dependency - assert!( - !has_edge("@test/core#build", "@test/web#build"), - "With topological_run=false, Core#build should NOT have edge to Web#build" - ); - }); - } - - #[test] - fn test_explicit_deps_with_topological_false() { - with_unique_cache_path("explicit_deps_topological_false", |cache_path| { - let fixture_path = get_fixture_path("fixtures/explicit-deps-workspace"); - - // Load with topological_run = false - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), false) - .expect("Failed to load workspace"); - - // Test @test/utils#lint which has explicit dependencies - let task_graph = workspace - .build_task_subgraph(&["@test/utils#lint".into()], Arc::default(), false) - .expect("Failed to resolve tasks"); - - let has_edge = |from: &str, to: &str| -> bool { - task_graph.edge_indices().any(|edge_idx| { - let (source, target) = task_graph.edge_endpoints(edge_idx).unwrap(); - task_graph[source].display_name() == from - && task_graph[target].display_name() == to - }) - }; - - // Verify explicit dependencies are honored - assert!( - has_edge("@test/utils#lint", "@test/core#build"), - "Explicit dependency from utils#lint to core#build should exist" - ); - assert!( - has_edge("@test/utils#lint", "@test/utils#build"), - "Explicit dependency from utils#build to utils#lint should exist" - ); - - // Verify NO implicit dependencies from package dependencies - // Even though @test/utils depends on @test/core, utils#build should NOT depend on core#build - assert!( - !has_edge("@test/core#build", "@test/utils#build"), - "With topological_run=false, no implicit dependency should exist" - ); - }); - } - - #[test] - fn test_explicit_deps_with_topological_true() { - with_unique_cache_path("explicit_deps_topological_true", |cache_path| { - let fixture_path = get_fixture_path("fixtures/explicit-deps-workspace"); - - // Load with topological_run = true - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test @test/utils#lint which has explicit dependencies - let task_graph = workspace - .build_task_subgraph(&["@test/utils#lint".into()], Arc::default(), false) - .expect("Failed to resolve tasks"); - - let has_edge = |from: &str, to: &str| -> bool { - task_graph.edge_indices().any(|edge_idx| { - let (source, target) = task_graph.edge_endpoints(edge_idx).unwrap(); - task_graph[source].display_name() == from - && task_graph[target].display_name() == to - }) - }; - - // Verify explicit dependencies are still honored - assert!( - has_edge("@test/utils#lint", "@test/core#build"), - "Explicit dependency from core#build to utils#lint should exist" - ); - assert!( - has_edge("@test/utils#lint", "@test/utils#build"), - "Explicit dependency from utils#build to utils#lint should exist" - ); - - // Verify implicit dependencies ARE added - assert!( - has_edge("@test/utils#build", "@test/core#build"), - "With topological_run=true, implicit dependency should exist" - ); - }); - } - - #[test] - fn test_recursive_with_topological_false() { - with_unique_cache_path("recursive_topological_false", |cache_path| { - let fixture_path = get_fixture_path("fixtures/recursive-topological-workspace"); - - // Load with topological_run = false - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), false) - .expect("Failed to load workspace"); - - // Test recursive build with topological_run=false - let task_graph = workspace - .build_task_subgraph(&["build".into()], Arc::default(), true) - .expect("Failed to resolve tasks"); - - // Verify that all build tasks are included (recursive flag works) - let task_names: Vec<_> = - task_graph.node_weights().map(super::ResolvedTask::display_name).collect(); - - assert!(task_names.contains(&"@test/core#build".into())); - assert!(task_names.contains(&"@test/utils#build".into())); - assert!(task_names.contains(&"@test/app#build".into())); - assert!(task_names.contains(&"@test/web#build".into())); - - // But verify NO implicit dependencies exist - let has_edge = |from: &str, to: &str| -> bool { - task_graph.edge_indices().any(|edge_idx| { - let (source, target) = task_graph.edge_endpoints(edge_idx).unwrap(); - task_graph[source].display_name() == from - && task_graph[target].display_name() == to - }) - }; - - // With topological_run=false, these implicit dependencies should NOT exist - assert!( - !has_edge("@test/core#build", "@test/utils#build"), - "No implicit edge from core to utils" - ); - assert!( - !has_edge("@test/utils#build", "@test/app#build"), - "No implicit edge from utils to app" - ); - assert!( - !has_edge("@test/app#build", "@test/web#build"), - "No implicit edge from app to web" - ); - }); - } - - #[test] - fn test_topological_true_vs_false_comparison() { - let fixture_path = get_fixture_path("fixtures/recursive-topological-workspace"); - - // Use separate cache paths to avoid database locking - with_unique_cache_path("topological_comparison_true", |cache_path_true| { - // Load with topological_run = true - let workspace_true = - Workspace::load_with_cache_path(fixture_path.clone(), Some(cache_path_true), true) - .expect("Failed to load workspace with topological=true"); - - let graph_true = workspace_true - .build_task_subgraph(&["@test/app#build".into()], Arc::default(), false) - .expect("Failed to resolve tasks"); - - with_unique_cache_path("topological_comparison_false", |cache_path_false| { - // Load with topological_run = false - let workspace_false = - Workspace::load_with_cache_path(fixture_path, Some(cache_path_false), false) - .expect("Failed to load workspace with topological=false"); - - let graph_false = workspace_false - .build_task_subgraph(&["@test/app#build".into()], Arc::default(), false) - .expect("Failed to resolve tasks"); - - // Count edges in each graph - let edge_count_true = graph_true.edge_count(); - let edge_count_false = graph_false.edge_count(); - - // With topological=true, there should be more edges due to implicit dependencies - assert!( - edge_count_true > edge_count_false, - "Graph with topological=true ({edge_count_true}) should have more edges than topological=false ({edge_count_false})" - ); - - // Verify specific edge differences - let has_edge = - |graph: &StableDiGraph, from: &str, to: &str| -> bool { - graph.edge_indices().any(|edge_idx| { - let (source, target) = graph.edge_endpoints(edge_idx).unwrap(); - graph[source].display_name() == from - && graph[target].display_name() == to - }) - }; - - // This edge should exist with topological=true but not with topological=false - assert!( - has_edge(&graph_true, "@test/app#build", "@test/utils#build"), - "Implicit edge should exist with topological=true" - ); - assert!( - !has_edge(&graph_false, "@test/app#build", "@test/utils#build"), - "Implicit edge should NOT exist with topological=false" - ); - }); - }); - } - - #[test] - fn test_recursive_without_topological() { - with_unique_cache_path("recursive_without_topological", |cache_path| { - let fixture_path = get_fixture_path("fixtures/recursive-topological-workspace"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test recursive build without topological flag - // Note: Even without topological flag, cross-package dependencies are now always included - let task_graph = workspace - .build_task_subgraph(&["build".into()], Arc::default(), true) - .expect("Failed to resolve tasks"); - - // Verify that all build tasks are included - let task_names: Vec<_> = - task_graph.node_weights().map(super::ResolvedTask::display_name).collect(); - - assert!(task_names.contains(&"@test/core#build".into())); - assert!(task_names.contains(&"@test/utils#build".into())); - assert!(task_names.contains(&"@test/app#build".into())); - assert!(task_names.contains(&"@test/web#build".into())); - - // Cross-package dependencies should exist even without topological flag - let has_edge = |from: &str, to: &str| -> bool { - task_graph.edge_indices().any(|edge_idx| { - let (source, target) = task_graph.edge_endpoints(edge_idx).unwrap(); - task_graph[source].display_name() == from - && task_graph[target].display_name() == to - }) - }; - - // Verify some cross-package dependencies exist - assert!( - has_edge("@test/utils#build(subcommand 0)", "@test/core#build"), - "utils should have edge to core" - ); - }); - } - - #[test] - fn test_recursive_run_with_scope_error() { - with_unique_cache_path("recursive_run_with_scope_error", |cache_path| { - let fixture_path = get_fixture_path("fixtures/recursive-topological-workspace"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test that specifying a scoped task with recursive flag returns an error - let result = - workspace.build_task_subgraph(&["@test/core#build".into()], Arc::default(), true); - - assert!(result.is_err()); - match result { - Err(Error::RecursiveRunWithScope(task)) => { - assert_eq!(task, "@test/core#build"); - } - _ => panic!("Expected RecursiveRunWithScope error"), - } - }); - } - - #[test] - fn test_non_recursive_single_package() { - with_unique_cache_path("non_recursive_single_package", |cache_path| { - let fixture_path = get_fixture_path("fixtures/recursive-topological-workspace"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test non-recursive build of a single package - let task_graph = workspace - .build_task_subgraph(&["@test/utils#build".into()], Arc::default(), false) - .expect("Failed to resolve tasks"); - - // @test/utils has compound commands (3 subtasks) plus dependencies on @test/core#build - let all_tasks: Vec<_> = - task_graph.node_weights().map(super::ResolvedTask::display_name).collect(); - - // Should include utils subtasks - assert!(all_tasks.contains(&"@test/utils#build(subcommand 0)".into())); - assert!(all_tasks.contains(&"@test/utils#build(subcommand 1)".into())); - assert!(all_tasks.contains(&"@test/utils#build".into())); - - // Should also include dependency on core - assert!(all_tasks.contains(&"@test/core#build".into())); - }); - } - - #[test] - fn test_recursive_topological_with_compound_commands() { - with_unique_cache_path("recursive_topological_with_compound_commands", |cache_path| { - let fixture_path = get_fixture_path("fixtures/recursive-topological-workspace"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test recursive topological build with compound commands - let task_graph = workspace - .build_task_subgraph(&["build".into()], Arc::default(), true) - .expect("Failed to resolve tasks"); - - // Check all tasks including subcommands - let all_tasks: Vec<_> = - task_graph.node_weights().map(super::ResolvedTask::display_name).collect(); - - // Utils should have 3 subtasks (indices 0, 1, and None) - assert!(all_tasks.contains(&"@test/utils#build(subcommand 0)".into())); - assert!(all_tasks.contains(&"@test/utils#build(subcommand 1)".into())); - assert!(all_tasks.contains(&"@test/utils#build".into())); - - // Verify dependencies - let has_edge = |from_name: &str, to_name: &str| -> bool { - task_graph.edge_indices().any(|edge_idx| { - let (source, target) = task_graph.edge_endpoints(edge_idx).unwrap(); - task_graph[source].display_name() == from_name - && task_graph[target].display_name() == to_name - }) - }; - - // Within-package dependencies for @test/utils compound command - assert!( - has_edge("@test/utils#build(subcommand 1)", "@test/utils#build(subcommand 0)"), - "Second subtask should have edge to first" - ); - assert!( - has_edge("@test/utils#build", "@test/utils#build(subcommand 1)"), - "Last subtask should have edge to second" - ); - - // Cross-package dependencies - // Core's LAST subtask should have edge to utils' FIRST subtask - assert!( - has_edge("@test/utils#build(subcommand 0)", "@test/core#build"), - "Utils' first subtask should have edge to core's last subtask" - ); - - // Utils' LAST subtask should have edge to app - assert!( - has_edge("@test/app#build", "@test/utils#build"), - "app should have edge to Utils' last subtask" - ); - }); - } - - #[test] - fn test_transitive_dependency_resolution() { - with_unique_cache_path("transitive_dependency_resolution", |cache_path| { - let fixture_path = get_fixture_path("fixtures/transitive-dependency-workspace"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test recursive topological build with transitive dependencies - let task_graph = workspace - .build_task_subgraph(&["build".into()], Arc::default(), true) - .expect("Failed to resolve tasks"); - - // Verify that all build tasks are included - let task_names: Vec<_> = - task_graph.node_weights().map(super::ResolvedTask::display_name).collect(); - - assert!( - task_names.contains(&"@test/a#build".into()), - "Package A build task should be included" - ); - assert!( - task_names.contains(&"@test/c#build".into()), - "Package C build task should be included" - ); - assert_eq!(task_names.len(), 2, "Only A and C should have build tasks"); - - // Verify dependencies exist in the correct direction - let has_edge = |from: &str, to: &str| -> bool { - task_graph.edge_indices().any(|edge_idx| { - let (source, target) = task_graph.edge_endpoints(edge_idx).unwrap(); - task_graph[source].display_name() == from - && task_graph[target].display_name() == to - }) - }; - - // With transitive dependency resolution, A should have edge to C (A depends on C transitively) - assert!( - has_edge("@test/a#build", "@test/c#build"), - "A should have edge to C (A depends on C transitively through B)" - ); - }); - } - - #[test] - fn test_comprehensive_task_graph() { - with_unique_cache_path("comprehensive_task_graph", |cache_path| { - let fixture_path = get_fixture_path("fixtures/comprehensive-task-graph"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test build task graph - let build_graph = workspace - .build_task_subgraph(&["build".into()], Arc::default(), true) - .expect("Failed to resolve build tasks"); - - let build_tasks: Vec<_> = - build_graph.node_weights().map(super::ResolvedTask::display_name).collect(); - - // Verify all packages with build scripts are included - assert!(build_tasks.contains(&"@test/shared#build".into())); - assert!(build_tasks.contains(&"@test/ui#build".into())); - assert!(build_tasks.contains(&"@test/api#build".into())); - assert!(build_tasks.contains(&"@test/app#build".into())); - assert!(build_tasks.contains(&"@test/config#build".into())); - - // Tools doesn't have a build script - assert!(!build_tasks.iter().any(|task| task.starts_with("@test/tools#"))); - - let has_edge = - |graph: &StableDiGraph, from: &str, to: &str| -> bool { - graph.edge_indices().any(|edge_idx| { - let (source, target) = graph.edge_endpoints(edge_idx).unwrap(); - graph[source].display_name() == from && graph[target].display_name() == to - }) - }; - - // Verify dependency edges for build tasks (between last subtasks) - assert!(has_edge(&build_graph, "@test/ui#build(subcommand 0)", "@test/shared#build")); - assert!(has_edge(&build_graph, "@test/api#build(subcommand 0)", "@test/shared#build")); - assert!(has_edge(&build_graph, "@test/api#build(subcommand 0)", "@test/config#build")); - assert!(has_edge(&build_graph, "@test/app#build(subcommand 0)", "@test/ui#build")); - assert!(has_edge(&build_graph, "@test/app#build(subcommand 0)", "@test/api#build")); - assert!(has_edge(&build_graph, "@test/app#build(subcommand 0)", "@test/shared#build")); - - // Test that UI has compound commands (3 subtasks) - let ui_tasks: Vec<_> = build_graph - .node_weights() - .filter(|task| task.display_name().starts_with("@test/ui#build")) - .map(|task| task.name.subcommand_index) - .collect(); - assert_eq!(ui_tasks.len(), 3); - assert!(ui_tasks.contains(&Some(0))); - assert!(ui_tasks.contains(&Some(1))); - assert!(ui_tasks.contains(&None)); - - // Verify UI compound task internal dependencies - assert!(has_edge( - &build_graph, - "@test/ui#build(subcommand 1)", - "@test/ui#build(subcommand 0)", - )); - assert!(has_edge(&build_graph, "@test/ui#build", "@test/ui#build(subcommand 1)")); - - // Test that shared has compound commands (3 subtasks for build) - let shared_build_tasks: Vec<_> = build_graph - .node_weights() - .filter(|task| task.display_name().starts_with("@test/shared#build")) - .collect(); - assert_eq!(shared_build_tasks.len(), 3); - - // Test that API has compound commands (4 subtasks for build) - let api_build_tasks: Vec<_> = build_graph - .node_weights() - .filter(|task| task.display_name().starts_with("@test/api#build")) - .collect(); - assert_eq!(api_build_tasks.len(), 4); - - // Test that app has compound commands (5 subtasks for build) - let app_build_tasks: Vec<_> = build_graph - .node_weights() - .filter(|task| task.display_name().starts_with("@test/app#build")) - .collect(); - assert_eq!(app_build_tasks.len(), 5); - - // Verify cross-package dependencies connect to first subtask - assert!(has_edge(&build_graph, "@test/api#build(subcommand 0)", "@test/shared#build")); - assert!(has_edge(&build_graph, "@test/api#build(subcommand 0)", "@test/config#build")); - assert!(has_edge(&build_graph, "@test/app#build(subcommand 0)", "@test/api#build")); - - // Test test task graph - let test_graph = workspace - .build_task_subgraph(&["test".into()], Arc::default(), true) - .expect("Failed to resolve test tasks"); - - let test_tasks: Vec<_> = - test_graph.node_weights().map(super::ResolvedTask::display_name).collect(); - - assert!(test_tasks.contains(&"@test/shared#test".into())); - assert!(test_tasks.contains(&"@test/ui#test".into())); - assert!(test_tasks.contains(&"@test/api#test".into())); - assert!(test_tasks.contains(&"@test/app#test".into())); - - // Config and tools don't have test scripts - assert!(!test_tasks.iter().any(|task| task == "@test/config#test")); - assert!(!test_tasks.iter().any(|task| task == "@test/tools#test")); - - // Verify shared#test has compound commands (3 subtasks) - let shared_test_tasks: Vec<_> = test_graph - .node_weights() - .filter(|task| task.display_name().starts_with("@test/shared#test")) - .collect(); - assert_eq!(shared_test_tasks.len(), 3); - - // Test specific package task - let api_build_graph = workspace - .build_task_subgraph(&["@test/api#build".into()], Arc::default(), false) - .expect("Failed to resolve api build task"); - - let api_deps: Vec<_> = - api_build_graph.node_weights().map(super::ResolvedTask::display_name).collect(); - - // Should include api and its dependencies - assert!(api_deps.contains(&"@test/api#build".into())); - assert!(api_deps.contains(&"@test/shared#build".into())); - assert!(api_deps.contains(&"@test/config#build".into())); - // Should not include app or ui - assert!(!api_deps.contains(&"@test/app#build".into())); - assert!(!api_deps.contains(&"@test/ui#build".into())); - }); - } - - #[test] - fn test_scripts_with_hash_in_names() { - with_unique_cache_path("scripts_with_hash_in_names", |cache_path| { - let fixture_path = get_fixture_path("fixtures/comprehensive-task-graph"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test that we can't use recursive with task names containing # (would be interpreted as scope) - let result = - workspace.build_task_subgraph(&["test#integration".into()], Arc::default(), true); - assert!(result.is_err(), "Recursive run with # in task name should fail"); - }); - } - - #[test] - fn test_task_graph_visualization() { - with_unique_cache_path("task_graph_visualization", |cache_path| { - let fixture_path = get_fixture_path("fixtures/comprehensive-task-graph"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test app build task graph - this should show the full dependency tree - let app_build_graph = workspace - .build_task_subgraph(&["@test/app#build".into()], Arc::default(), false) - .expect("Failed to resolve app build task"); - - // Expected task graph structure: - // - // @test/config#build ─────────────────┐ - // ▼ - // @test/shared#build[0] ──► [1] ──► [None] ──┐ - // │ │ - // ▼ ▼ - // @test/ui#build[0] ──► [1] ──► [None] ──► @test/app#build[0] ──► [1] ──► [2] ──► [3] ──► [None] - // ▲ - // @test/api#build[0] ──► [1] ──► [2] ──► [None] ──┘ - // ▲ - // └─────────────────────────────────────┘ - - let has_full_edge = - |graph: &StableDiGraph, from_name: &str, to_name: &str| -> bool { - graph.edge_indices().any(|edge_idx| { - let (source, target) = graph.edge_endpoints(edge_idx).unwrap(); - graph[source].display_name() == from_name - && graph[target].display_name() == to_name - }) - }; - - // Verify all tasks are present - let all_tasks: Vec<_> = - app_build_graph.node_weights().map(super::ResolvedTask::display_name).collect(); - - // App should have 5 subtasks (indices: 0, 1, 2, 3, None) - assert_eq!( - all_tasks.iter().filter(|name| name.starts_with("@test/app#build")).count(), - 5 - ); - // API should have 4 subtasks (indices: 0, 1, 2, None) - assert_eq!( - all_tasks.iter().filter(|name| name.starts_with("@test/api#build")).count(), - 4 - ); - // Shared should have 3 subtasks (indices: 0, 1, None) - assert_eq!( - all_tasks.iter().filter(|name| name.starts_with("@test/shared#build")).count(), - 3 - ); - // UI should have 3 subtasks (indices: 0, 1, None) - assert_eq!( - all_tasks.iter().filter(|name| name.starts_with("@test/ui#build")).count(), - 3 - ); - // Config should have 1 task (no &&) - assert_eq!( - all_tasks.iter().filter(|name| name.starts_with("@test/config#build")).count(), - 1 - ); - - // Verify internal task dependencies (within compound commands) - // App internal deps (5 commands => indices 0, 1, 2, 3, None) - assert!(has_full_edge( - &app_build_graph, - "@test/app#build(subcommand 1)", - "@test/app#build(subcommand 0)", - )); - assert!(has_full_edge( - &app_build_graph, - "@test/app#build(subcommand 2)", - "@test/app#build(subcommand 1)", - )); - assert!(has_full_edge( - &app_build_graph, - "@test/app#build(subcommand 3)", - "@test/app#build(subcommand 2)", - )); - assert!(has_full_edge( - &app_build_graph, - "@test/app#build", - "@test/app#build(subcommand 3)", - )); - - // API internal deps (4 commands => indices 0, 1, 2, None) - assert!(has_full_edge( - &app_build_graph, - "@test/api#build(subcommand 1)", - "@test/api#build(subcommand 0)", - )); - assert!(has_full_edge( - &app_build_graph, - "@test/api#build(subcommand 2)", - "@test/api#build(subcommand 1)", - )); - assert!(has_full_edge( - &app_build_graph, - "@test/api#build", - "@test/api#build(subcommand 2)", - )); - - // Verify cross-package dependencies - // Dependencies TO app#build[0] (first subtask) - assert!(has_full_edge( - &app_build_graph, - "@test/app#build(subcommand 0)", - "@test/ui#build", - )); - assert!(has_full_edge( - &app_build_graph, - "@test/app#build(subcommand 0)", - "@test/api#build", - )); - assert!(has_full_edge( - &app_build_graph, - "@test/app#build(subcommand 0)", - "@test/shared#build", - )); - - // Dependencies TO api#build[0] - assert!(has_full_edge( - &app_build_graph, - "@test/api#build(subcommand 0)", - "@test/shared#build", - )); - assert!(has_full_edge( - &app_build_graph, - "@test/api#build(subcommand 0)", - "@test/config#build", - )); - - // Dependencies TO ui#build[0] - assert!(has_full_edge( - &app_build_graph, - "@test/ui#build(subcommand 0)", - "@test/shared#build", - )); - }); - } - - #[test] - fn test_cache_sharing_between_subtasks() { - with_unique_cache_path("cache_sharing_between_subtasks", |cache_path| { - let fixtures_dir = get_fixture_path("fixtures/cache-sharing"); - - let workspace = Workspace::load_with_cache_path( - fixtures_dir, - Some(cache_path), - false, // topological_run - ) - .unwrap(); - - let tasks = vec![ - "@test/cache-sharing#a".into(), - "@test/cache-sharing#b".into(), - "@test/cache-sharing#c".into(), - ]; - let task_graph = workspace.build_task_subgraph(&tasks, Arc::default(), false).unwrap(); - - // Get all tasks from the graph - let tasks: Vec<_> = task_graph - .node_weights() - .map(|task| (task.display_name(), task.name.subcommand_index)) - .collect(); - - // Task 'a' should have only one task (no &&) - assert_eq!( - tasks.iter().filter(|(name, _)| *name == "@test/cache-sharing#a").count(), - 1 - ); - - // Task 'b' should have 2 subtasks: 'echo a' (index 0) and main (None). - let b_tasks: Vec<_> = tasks - .iter() - .filter(|(name, _)| name.starts_with("@test/cache-sharing#b")) - .collect(); - assert_eq!(b_tasks.len(), 2, "Expected 2 subtasks for task 'b', got {}", b_tasks.len()); - - // Task 'c' should have 3 subtasks: 'echo a' (index 0), 'echo b' (index 1), and main (None) - assert_eq!( - tasks.iter().filter(|(name, _)| name.starts_with("@test/cache-sharing#c")).count(), - 3 - ); - - // Now verify that the cache keys are the same for "echo a" commands - // The first subtask of 'b' (echo a) should have the same cache key as task 'a' (echo a) - let task_a = task_graph - .node_weights() - .find(|t| { - t.display_name() == "@test/cache-sharing#a" && t.name.subcommand_index.is_none() - }) - .unwrap(); - - let task_b_subtask_0 = task_graph - .node_weights() - .find(|t| t.display_name() == "@test/cache-sharing#b(subcommand 0)") - .unwrap(); - - let task_c_subtask_0 = task_graph - .node_weights() - .find(|t| t.display_name() == "@test/cache-sharing#c(subcommand 0)") - .unwrap(); - - // All three should have command "echo a" - let task_a_command = &task_a.resolved_command.fingerprint.command; - let task_b_command = &task_b_subtask_0.resolved_command.fingerprint.command; - let task_c_command = &task_c_subtask_0.resolved_command.fingerprint.command; - - assert_eq!( - task_a_command.to_string(), - "echo a", - "Task 'a' should have command 'echo a'" - ); - assert_eq!( - task_b_command.to_string(), - "echo a", - "First subtask of 'b' should have command 'echo a'" - ); - assert_eq!( - task_c_command.to_string(), - "echo a", - "First subtask of 'c' should have command 'echo a'" - ); - - // The cache keys should be the same (same package, same command fingerprint, same args) - assert_eq!( - task_a.resolved_command.fingerprint, task_b_subtask_0.resolved_command.fingerprint, - "Task 'a' and first subtask of 'b' should have identical fingerprints for cache sharing" - ); - assert_eq!( - task_a.resolved_command.fingerprint, task_c_subtask_0.resolved_command.fingerprint, - "Task 'a' and first subtask of 'c' should have identical fingerprints for cache sharing" - ); - }); - } - - #[test] - fn test_empty_package_name_handling() { - with_unique_cache_path("empty_package_name", |cache_path| { - // Create a separate fixture directory for testing empty package names - // to avoid conflicts with the comprehensive-task-graph test - let fixture_path = get_fixture_path("fixtures/empty-package-test"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace with empty package name"); - - // Test that empty-name package is loaded correctly - let empty_name_package = - workspace.package_graph.node_weights().find(|p| p.package_json.name.is_empty()); - assert!(empty_name_package.is_some(), "Should find package with empty name"); - - // Test resolving build task recursively - should find both packages - let build_tasks = workspace - .build_task_subgraph(&["build".into()], Arc::default(), true) - .expect("Failed to resolve build tasks recursively"); - - let task_names: Vec<_> = - build_tasks.node_weights().map(super::ResolvedTask::display_name).collect(); - - assert!( - task_names.contains(&"build".into()), - "Should find empty-name package build task, found: {task_names:?}" - ); - assert!( - task_names.contains(&"normal-package#build".into()), - "Should find normal-package build task" - ); - - // Test that empty-name package internal dependencies work - let empty_build = workspace - .build_task_subgraph(&["#build".into()], Arc::default(), false) - .expect("Failed to resolve empty-name build"); - - let empty_build_tasks: Vec<_> = - empty_build.node_weights().map(super::ResolvedTask::display_name).collect(); - - assert!(empty_build_tasks.contains(&"build".into()), "Should have build task"); - assert!( - empty_build_tasks.contains(&"test".into()), - "Should have test task as dependency" - ); - - // Verify internal dependencies work correctly - let has_edge = - |graph: &StableDiGraph, from: &str, to: &str| -> bool { - graph.edge_indices().any(|edge_idx| { - let (source, target) = graph.edge_endpoints(edge_idx).unwrap(); - let source_task = &graph[source]; - let target_task = &graph[target]; - source_task.display_name() == from && target_task.display_name() == to - }) - }; - - assert!( - has_edge(&empty_build, "build", "test"), - "Empty-name build should depend on empty-name test (internal dependency)" - ); - }); - } - - #[test] - fn test_multiple_nameless_packages() { - with_unique_cache_path("multiple_nameless_packages", |cache_path| { - let fixture_path = get_fixture_path("fixtures/empty-package-test"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace with multiple nameless packages"); - - // Verify both nameless packages are loaded - let nameless_packages: Vec<_> = workspace - .package_graph - .node_weights() - .filter(|p| p.package_json.name.is_empty()) - .collect(); - - assert_eq!(nameless_packages.len(), 2, "Should find exactly 2 nameless packages"); - - // Test recursive build includes both nameless packages - let build_tasks = workspace - .build_task_subgraph(&["build".into()], Arc::default(), true) - .expect("Failed to resolve build tasks recursively"); - - let task_names: Vec<_> = - build_tasks.node_weights().map(super::ResolvedTask::display_name).collect(); - - // Count build tasks from nameless packages (they appear as just "build") - let nameless_build_count = task_names.iter().filter(|name| *name == "build").count(); - - assert_eq!( - nameless_build_count, 2, - "Should find 2 'build' tasks from nameless packages, found tasks: {task_names:?}" - ); - - // Verify normal package build is also included - assert!( - task_names.contains(&"normal-package#build".into()), - "Should also include normal-package#build" - ); - - // Test that nameless packages can have different internal dependencies - // The second nameless package has more complex dependencies - let deploy_tasks = workspace - .build_task_subgraph(&["deploy".into()], Arc::default(), true) - .expect("Failed to resolve deploy tasks"); - - let deploy_task_names: Vec<_> = - deploy_tasks.node_weights().map(super::ResolvedTask::display_name).collect(); - - // Check that deploy task and its dependencies are resolved - assert!( - deploy_task_names.contains(&"deploy".into()), - "Should find deploy task from second nameless package" - ); - assert!( - deploy_task_names.contains(&"lint".into()), - "Should include lint as dependency of build in second nameless package" - ); - assert!( - deploy_task_names.contains(&"normal-package#test".into()), - "Should include normal-package#test as dependency" - ); - - // Verify that dependencies between nameless packages don't interfere - let test_tasks = workspace - .build_task_subgraph(&["test".into()], Arc::default(), true) - .expect("Failed to resolve test tasks"); - - let test_task_names: Vec<_> = - test_tasks.node_weights().map(super::ResolvedTask::display_name).collect(); - - // Should have test tasks from both nameless packages and normal-package - let nameless_test_count = test_task_names.iter().filter(|name| *name == "test").count(); - - assert_eq!(nameless_test_count, 2, "Should find 2 'test' tasks from nameless packages"); - - // Test topological ordering with nameless packages - // The second nameless package depends on normal-package - // With topological ordering, build tasks should respect package dependencies - let build_graph = workspace - .build_task_subgraph(&["build".into()], Arc::default(), true) - .expect("Failed to resolve build with topological"); - - // Helper to check edges - let has_edge = |graph: &StableDiGraph, - from_pattern: &str, - to_pattern: &str| - -> bool { - graph.edge_indices().any(|edge_idx| { - let (source, target) = graph.edge_endpoints(edge_idx).unwrap(); - let source_name = graph[source].display_name(); - let target_name = graph[target].display_name(); - - // For nameless packages, we need to check the package path - // Since both show as "build", we need another way to distinguish them - let source_matches = source_name == from_pattern; - let target_matches = target_name == to_pattern; - - source_matches && target_matches - }) - }; - - // The second nameless package depends on normal-package - // So with topological ordering, normal-package#build should run before the second nameless build - assert!( - has_edge(&build_graph, "build", "normal-package#build") - && has_edge(&build_graph, "build", "normal-package#test"), - "Should have dependency from normal-package to second nameless package due to topological ordering" - ); - }); - } - - #[test] - fn test_task_without_sharp_in_explicit_mode() { - with_unique_cache_path("task_without_sharp_explicit", |cache_path| { - let fixture_path = get_fixture_path("fixtures/comprehensive-task-graph"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), false) - .expect("Failed to load workspace"); - - // When in explicit mode (non-recursive), tasks without '#' should resolve to current package - // This test simulates being in a package directory - - // First, test that the original scoped task works - let api_build_scoped = workspace - .build_task_subgraph(&["@test/api#build".into()], Arc::default(), false) - .expect("Failed to resolve @test/api#build"); - - // Find the number of tasks for API build - let api_build_task_count = api_build_scoped.node_count(); - assert!(api_build_task_count > 0, "Should find API build task"); - - // Test that we can resolve task with '#' in package - let app_test_scoped = workspace - .build_task_subgraph(&["@test/app#test".into()], Arc::default(), false) - .expect("Failed to resolve @test/app#test"); - - // Should include dependencies - assert!(app_test_scoped.node_count() > 0, "Should find app test task"); - - // Verify task names in graph - let mut found_app_test = false; - for task in app_test_scoped.node_weights() { - if task.display_name() == "@test/app#test" { - found_app_test = true; - break; - } - } - assert!(found_app_test, "Should find @test/app#test task in graph"); - }); - } - - #[test] - fn test_dependency_resolution_with_ambiguous_names() { - with_unique_cache_path("dependency_ambiguous_names", |cache_path| { - let fixture_path = get_fixture_path("fixtures/conflict-test"); - - // This should fail with a TaskNameConflict error because the dependency - // "@test/scope-a#b#c" is ambiguous - it could mean: - // - Package "@test/scope-a" with task "b#c", or - // - Package "@test/scope-a#b" with task "c" - // And both packages exist in the fixture - let result = Workspace::load_with_cache_path(fixture_path, Some(cache_path), false); - - // The workspace loading should fail due to the conflict - assert!(result.is_err(), "Should fail to load workspace with conflicting task names"); - - if let Err(e) = result { - // Verify it's the expected error type - match e { - Error::AmbiguousTaskRequest { .. } => { - // This is the expected error - } - _ => panic!("Expected TaskNameConflict error, but got: {e:?}"), - } - } - }); - } -} diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs index 4bc75d0c..5b1b7058 100644 --- a/crates/vite_task/src/lib.rs +++ b/crates/vite_task/src/lib.rs @@ -11,9 +11,6 @@ mod schedule; mod types; mod ui; -#[cfg(test)] -mod test_utils; - // Public exports for vite-plus-cli to use pub use cache::TaskCache; pub use config::{ResolvedTask, Workspace}; diff --git a/crates/vite_task/src/schedule.rs b/crates/vite_task/src/schedule.rs index f7040b2f..61cc6b98 100644 --- a/crates/vite_task/src/schedule.rs +++ b/crates/vite_task/src/schedule.rs @@ -247,46 +247,3 @@ async fn get_cached_or_execute<'a>( ), }) } - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - Workspace, - test_utils::{get_fixture_path, with_unique_cache_path}, - }; - - #[track_caller] - fn assert_order(plan: &ExecutionPlan, before: &str, after: &str) { - let before_index = plan.steps.iter().position(|t| t.display_name() == before); - let after_index = plan.steps.iter().position(|t| t.display_name() == after); - assert!(before_index.is_some(), "Task {before} not found in plan"); - assert!(after_index.is_some(), "Task {after} not found in plan"); - assert!(before_index < after_index, "Task {before} should be before {after}"); - } - - #[test] - fn test_execution_non_parallel() { - with_unique_cache_path("comprehensive_task_graph", |cache_path| { - let fixture_path = get_fixture_path("fixtures/comprehensive-task-graph"); - - let workspace = Workspace::load_with_cache_path(fixture_path, Some(cache_path), true) - .expect("Failed to load workspace"); - - // Test build task graph - let build_graph = workspace - .build_task_subgraph(&["build".into()], Arc::default(), true) - .expect("Failed to resolve build tasks"); - - let plan = - ExecutionPlan::plan(build_graph, false).expect("Circular dependency detected"); - - assert_order(&plan, "@test/shared#build", "@test/ui#build(subcommand 0)"); - assert_order(&plan, "@test/shared#build", "@test/api#build(subcommand 0)"); - assert_order(&plan, "@test/config#build", "@test/api#build(subcommand 0)"); - assert_order(&plan, "@test/ui#build", "@test/app#build(subcommand 0)"); - assert_order(&plan, "@test/api#build", "@test/app#build(subcommand 0)"); - assert_order(&plan, "@test/shared#build", "@test/app#build(subcommand 0)"); - }); - } -} diff --git a/crates/vite_task/src/test_utils.rs b/crates/vite_task/src/test_utils.rs deleted file mode 100644 index a30f561a..00000000 --- a/crates/vite_task/src/test_utils.rs +++ /dev/null @@ -1,28 +0,0 @@ -use vite_path::{AbsolutePath, AbsolutePathBuf, current_dir}; -use vite_str::format; - -pub fn with_unique_cache_path(test_name: &str, f: F) -> R -where - F: FnOnce(AbsolutePathBuf) -> R, -{ - let temp_dir = tempfile::tempdir().expect("Failed to create temp directory"); - let cache_path = - AbsolutePath::new(temp_dir.path()).unwrap().join(format!("vite-test-{}", test_name)); - - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(cache_path))); - - // The temp directory and all its contents will be automatically cleaned up - // when temp_dir goes out of scope - - match result { - Ok(r) => r, - Err(panic) => std::panic::resume_unwind(panic), - } -} - -pub fn get_fixture_path(rel_path: &str) -> AbsolutePathBuf { - // The current dir is the manifest dir of the crate being tested. - // We don't use `env!("CARGO_MANIFEST_DIR")` because we want the test binary to be relocatable, - // so it can be cross-compiled and then run in a different os. - current_dir().unwrap().join(rel_path) -} From b7ee7ae141137d20a38d738a5f7f28dbdf2e88d1 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:19:46 +0800 Subject: [PATCH 29/31] fix typo --- crates/vite_task_graph/src/config/user.rs | 2 +- crates/vite_task_graph/src/lib.rs | 4 ++-- .../tests/fixtures/cache-sharing/pnpm-lock.yaml | 1 - crates/vite_task_graph/tests/snapshots.rs | 4 ++-- .../tests/snapshots/snapshots__task graph@cache-sharing.snap | 2 +- .../snapshots__task graph@comprehensive-task-graph.snap | 2 +- .../tests/snapshots/snapshots__task graph@conflict-test.snap | 2 +- .../snapshots/snapshots__task graph@empty-package-test.snap | 2 +- .../snapshots__task graph@explicit-deps-workspace.snap | 2 +- .../snapshots__task graph@fingerprint-ignore-test.snap | 2 +- ...snapshots__task graph@recursive-topological-workspace.snap | 2 +- ...snapshots__task graph@transitive-dependency-workspace.snap | 2 +- 12 files changed, 13 insertions(+), 14 deletions(-) diff --git a/crates/vite_task_graph/src/config/user.rs b/crates/vite_task_graph/src/config/user.rs index 1baff7ce..e9f42dab 100644 --- a/crates/vite_task_graph/src/config/user.rs +++ b/crates/vite_task_graph/src/config/user.rs @@ -120,7 +120,7 @@ mod tests { } #[test] - fn test_cache_explictly_enabled() { + fn test_cache_explicitly_enabled() { let user_config_json = json!({ "cache": true, "envs": ["NODE_ENV"], diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index b237e163..f990bd79 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -33,7 +33,7 @@ pub struct TaskId { /// /// Note that this is not always the cwd where the command is run, which is stored in `ResolvedUserTaskConfig`. /// - /// `package_dir` is declared from `task_name` to make the `PartialOrd` implmentation group tasks in same packages together. + /// `package_dir` is declared from `task_name` to make the `PartialOrd` implementation group tasks in same packages together. pub package_dir: Arc, /// For user defined tasks, this is the name of the script or the entry in `vite-task.json`. @@ -231,7 +231,7 @@ impl TaskGraph { // Construct `Self` with task_graph with all task nodes ready and indexed, but no edges. let mut me = Self { graph: task_graph, node_indices_by_task_id, package_dirs_by_name }; - // Add explict dependencies + // Add explicit dependencies for (dependency_specifiers, from_node_index) in dependency_specifiers_with_node_indices { let from_task_id = me.graph[from_node_index].task_id.clone(); diff --git a/crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-lock.yaml b/crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-lock.yaml index 9b60ae17..b6688e7c 100644 --- a/crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-lock.yaml +++ b/crates/vite_task_graph/tests/fixtures/cache-sharing/pnpm-lock.yaml @@ -5,5 +5,4 @@ settings: excludeLinksFromLockfile: false importers: - .: {} diff --git a/crates/vite_task_graph/tests/snapshots.rs b/crates/vite_task_graph/tests/snapshots.rs index 9ded16f2..48e0c037 100644 --- a/crates/vite_task_graph/tests/snapshots.rs +++ b/crates/vite_task_graph/tests/snapshots.rs @@ -29,8 +29,8 @@ fn run_case(runtime: &Runtime, tmpdir: &AbsolutePath, case_path: &Path) { vite_task_graph::TaskGraph::load(workspace_root, JsonUserConfigLoader::default()) .await .expect(&format!("Failed to load task graph for case {case_name}")); - let task_graph_snaphost = task_graph.snapshot(&case_stage_path); - insta::assert_json_snapshot!("task graph", task_graph_snaphost); + let task_graph_snapshot = task_graph.snapshot(&case_stage_path); + insta::assert_json_snapshot!("task graph", task_graph_snapshot); }); } diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap index 17925eac..684053b5 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@cache-sharing.snap @@ -1,6 +1,6 @@ --- source: crates/vite_task_graph/tests/snapshots.rs -expression: task_graph_snaphost +expression: task_graph_snapshot input_file: crates/vite_task_graph/tests/fixtures/cache-sharing --- [ diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap index fc12dd46..81726275 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap @@ -1,6 +1,6 @@ --- source: crates/vite_task_graph/tests/snapshots.rs -expression: task_graph_snaphost +expression: task_graph_snapshot input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph --- [ diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap index b6416acb..ad54a4dd 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@conflict-test.snap @@ -1,6 +1,6 @@ --- source: crates/vite_task_graph/tests/snapshots.rs -expression: task_graph_snaphost +expression: task_graph_snapshot input_file: crates/vite_task_graph/tests/fixtures/conflict-test --- [ diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap index 03d18c3a..1e072f5b 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap @@ -1,6 +1,6 @@ --- source: crates/vite_task_graph/tests/snapshots.rs -expression: task_graph_snaphost +expression: task_graph_snapshot input_file: crates/vite_task_graph/tests/fixtures/empty-package-test --- [ diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap index 1bf74ae0..12da1ba8 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap @@ -1,6 +1,6 @@ --- source: crates/vite_task_graph/tests/snapshots.rs -expression: task_graph_snaphost +expression: task_graph_snapshot input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace --- [ diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap index d8563f0f..6e6714cd 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@fingerprint-ignore-test.snap @@ -1,6 +1,6 @@ --- source: crates/vite_task_graph/tests/snapshots.rs -expression: task_graph_snaphost +expression: task_graph_snapshot input_file: crates/vite_task_graph/tests/fixtures/fingerprint-ignore-test --- [ diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap index ccff5e4f..c10c59b8 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap @@ -1,6 +1,6 @@ --- source: crates/vite_task_graph/tests/snapshots.rs -expression: task_graph_snaphost +expression: task_graph_snapshot input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspace --- [ 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 f303b3d4..623fd5de 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 @@ -1,6 +1,6 @@ --- source: crates/vite_task_graph/tests/snapshots.rs -expression: task_graph_snaphost +expression: task_graph_snapshot input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspace --- [ From 352b7246a4583dc310f97937d3766679fa5398a1 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:25:26 +0800 Subject: [PATCH 30/31] remove unnecessary files --- crates/vite_task/src/cli.rs | 16 ------ crates/vite_task/src/config/task_graph.rs | 1 - crates/vite_task/src/reporter/mod.rs | 8 --- crates/vite_task/src/reporter/stream.rs | 6 --- crates/vite_task/src/session.rs | 65 ----------------------- 5 files changed, 96 deletions(-) delete mode 100644 crates/vite_task/src/cli.rs delete mode 100644 crates/vite_task/src/config/task_graph.rs delete mode 100644 crates/vite_task/src/reporter/mod.rs delete mode 100644 crates/vite_task/src/reporter/stream.rs delete mode 100644 crates/vite_task/src/session.rs diff --git a/crates/vite_task/src/cli.rs b/crates/vite_task/src/cli.rs deleted file mode 100644 index 11f109d1..00000000 --- a/crates/vite_task/src/cli.rs +++ /dev/null @@ -1,16 +0,0 @@ -use clap::{Parser, Subcommand}; -use vite_str::Str; - -#[derive(Debug, Parser)] -pub enum CLIArgs { - #[clap(flatten)] - SubCommands(CustomSubCommand), - - Run { - #[clap(short, long)] - recursive: bool, - task_name: Str, - #[clap(last = true)] - extra_args: Vec, - }, -} diff --git a/crates/vite_task/src/config/task_graph.rs b/crates/vite_task/src/config/task_graph.rs deleted file mode 100644 index 8b137891..00000000 --- a/crates/vite_task/src/config/task_graph.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/vite_task/src/reporter/mod.rs b/crates/vite_task/src/reporter/mod.rs deleted file mode 100644 index 2b583b52..00000000 --- a/crates/vite_task/src/reporter/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod stream; - -/// Describes how to report events during a Vite Task session. -/// It's an abstraction over different kinds of ui (stream, terminial ui, web, etc). -pub trait Reporter { - /// Report the execution plan that is about to be executed. - fn report_execution_plan(self: Box, tree: &str); -} diff --git a/crates/vite_task/src/reporter/stream.rs b/crates/vite_task/src/reporter/stream.rs deleted file mode 100644 index e2ebf5d5..00000000 --- a/crates/vite_task/src/reporter/stream.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[derive(Default)] -pub struct StreamReporter(()); - -impl crate::reporter::Reporter for StreamReporter { - fn report_execution_plan(self: Box, tree: &str) {} -} diff --git a/crates/vite_task/src/session.rs b/crates/vite_task/src/session.rs deleted file mode 100644 index 3bf152cf..00000000 --- a/crates/vite_task/src/session.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::{collections::HashMap, ffi::OsString, path::Path, sync::Arc}; - -use serde::{Deserialize, Serialize}; -use vite_path::AbsolutePath; -use vite_str::Str; - -use crate::{Workspace, cli::CLIArgs, reporter::Reporter}; - -// Represents the real subprocess to be spawned for a custom subcommand (vite ...) -pub struct SubcommandProcess { - pub program: Str, - pub args: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct ViteUserConfig {} - -#[async_trait::async_trait] -pub trait SessionHandler: Send + Sync { - /// What to spawn for `vite ` - async fn process_for_subcommand( - &mut self, - subcommand: CustomSubcommand, - ) -> anyhow::Result; - - async fn resolve_config(&mut self, package_dir: &Path) -> anyhow::Result; -} - -pub struct Session { - handler: Box>, - workspace: Workspace, -} - -/// Parameters of a CLI invocation of Vite Task, including current working directory, CLI args, and envs. -/// -/// This may come from a real CLI command, or be parsed from a task script. -pub struct CLIParams { - pub cwd: Arc, - pub args: CLIArgs, - pub envs: Arc>, -} - -impl Session { - pub async fn new( - cwd: &Arc, - handler: Box>, - ) -> Result { - Ok(Self { handler, workspace: Workspace::load(cwd.to_absolute_path_buf(), true)? }) - } - - fn plan(&self, params: CLIParams) {} - - pub async fn start( - &mut self, - params: CLIParams, - reporter: Box, - ) -> anyhow::Result<()> { - let plan = self.plan(params); - reporter.report_execution_plan("tree"); - Ok(()) - } -} - -/// -struct ExecutionPlan {} From 73e98756221ff8c144bc2133f0dfa05e17108429 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 12:26:28 +0800 Subject: [PATCH 31/31] cargo shear fix --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9e4d4195..cc1061af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ anyhow = "1.0.98" assert2 = "0.3.16" assertables = "9.8.1" ast-grep-core = "0.32.2" -async-trait = "0.1.89" base64 = "0.22.1" bincode = "2.0.1" bindgen = "0.72.1" @@ -47,7 +46,6 @@ 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.52" color-eyre = "0.6.5" compact_str = "0.9.0" const_format = "0.2.34" @@ -121,7 +119,6 @@ vec1 = "1.12.1" vite_glob = { path = "crates/vite_glob" } vite_path = { path = "crates/vite_path" } vite_str = { path = "crates/vite_str" } -vite_task = { path = "crates/vite_task" } vite_workspace = { path = "crates/vite_workspace" } wax = "0.6.0" which = "8.0.0"