diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e936298..19d0ebe7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - **Added** First-party support for caching `vite build` with zero cache config, giving Vite projects correct cache hits out of the box ([vitejs/vite#22453](https://github.com/vitejs/vite/pull/22453)). +- **Added** Object-form `dependsOn` entries for direct workspace dependencies ([#479](https://github.com/voidzero-dev/vite-task/pull/479)). - **Added** [`@voidzero-dev/vite-task-client`](https://npmx.dev/package/@voidzero-dev/vite-task-client), allowing tools to report cache information to Vite Task at runtime so users do not need to configure it manually ([#441](https://github.com/voidzero-dev/vite-task/pull/441), [#454](https://github.com/voidzero-dev/vite-task/pull/454), [#449](https://github.com/voidzero-dev/vite-task/pull/449), [#450](https://github.com/voidzero-dev/vite-task/pull/450), [#458](https://github.com/voidzero-dev/vite-task/pull/458), [#431](https://github.com/voidzero-dev/vite-task/pull/431), [#459](https://github.com/voidzero-dev/vite-task/pull/459), [#472](https://github.com/voidzero-dev/vite-task/pull/472)). - **Changed** Cached tasks now restore automatically tracked output files by default; use `output: []` to disable restoration ([#460](https://github.com/voidzero-dev/vite-task/pull/460), [#461](https://github.com/voidzero-dev/vite-task/pull/461)). - **Changed** Environment values in task cache fingerprints are now stored only as SHA-256 digests, and env-related cache miss details report names without values ([#455](https://github.com/voidzero-dev/vite-task/pull/455)). diff --git a/Cargo.lock b/Cargo.lock index 1b5a3e483..f31c68a5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4356,6 +4356,7 @@ dependencies = [ "thiserror 2.0.18", "tracing", "ts-rs", + "vec1", "vite_path", "vite_str", "vite_workspace", diff --git a/crates/vite_task/docs/task-query.md b/crates/vite_task/docs/task-query.md index b2fcfe37c..3544d5be1 100644 --- a/crates/vite_task/docs/task-query.md +++ b/crates/vite_task/docs/task-query.md @@ -13,7 +13,7 @@ Both are built once and reused for every query, including nested `vp run` calls ### What goes into the task graph -The task graph contains a node for every task in every package, and edges only for explicit `dependsOn` declarations: +The task graph contains a node for every task in every package, and edges only for explicit `dependsOn` declarations. ```jsonc // packages/app/vite.config.* @@ -38,6 +38,33 @@ Task graph: Package dependency ordering (app depends on lib) is NOT stored as edges in the task graph. Why not is explained below. +Object-form `dependsOn` entries are also explicit task dependencies. At startup, +they are resolved against the declaring package's direct `package.json` +dependency fields and materialized as task graph edges: + +```jsonc +// packages/app/vite.config.* +{ + "tasks": { + "test": { + "command": "vitest run", + "dependsOn": [{ "task": "build", "from": ["dependencies", "devDependencies"] }], + }, + }, +} +``` + +If `app` directly depends on `ui` and `shared`, and both packages have `build`, +the task graph contains: + +``` +app#test ──dependsOn──> ui#build +app#test ──dependsOn──> shared#build +``` + +Dependency packages without the requested task are skipped. Recursive expansion +comes from dependency tasks declaring their own `dependsOn` entries. + ## What happens when you run a query Every `vp run` command goes through two stages: @@ -188,7 +215,7 @@ If you run `vp run --filter app build`, the package subgraph contains only `app` This is intentional — `dependsOn` is an explicit declaration that a task can't run without its dependency. Ignoring it would break the build. (Users can skip this with `--ignore-depends-on`.) -The expansion only follows explicit edges, not topological ones. Topological ordering comes from the package subgraph — it's already baked into the task execution graph by Stage 2. +The expansion follows explicit `dependsOn` edges, including edges materialized from object-form entries. It does not follow topological package edges. Topological ordering comes from the package subgraph — it's already baked into the task execution graph by Stage 2. ## Nested `vp run` diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml index 8cc7e55ee..5d18848ab 100644 --- a/crates/vite_task_graph/Cargo.toml +++ b/crates/vite_task_graph/Cargo.toml @@ -18,6 +18,7 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } +vec1 = { workspace = true, features = ["serde"] } vite_path = { workspace = true } vite_str = { workspace = true } vite_workspace = { workspace = true } diff --git a/crates/vite_task_graph/run-config.ts b/crates/vite_task_graph/run-config.ts index 280b9adc1..cd780fce5 100644 --- a/crates/vite_task_graph/run-config.ts +++ b/crates/vite_task_graph/run-config.ts @@ -8,6 +8,12 @@ auto: boolean, }; export type Command = string | Array; +export type DependencyType = "dependencies" | "devDependencies" | "peerDependencies"; + +export type DependsOnEntry = string | UserPackageDependency; + +export type DependsOnFrom = DependencyType | Array; + export type GlobWithBase = { /** * The glob pattern (positive or negative starting with `!`) @@ -30,9 +36,16 @@ command: Command, */ cwd?: string, /** - * Dependencies of this task. Use `package-name#task-name` to refer to tasks in other packages. + * Tasks that must run before this task. + * + * - A string runs one named task, such as `"build"` in the same package or + * `"package-name#build"` in another package. + * - An object runs a task in direct workspace dependency packages selected + * from package.json fields. For example, + * `{ "task": "build", "from": "dependencies" }` runs `build` in each + * direct workspace dependency that defines a `build` task. */ -dependsOn?: Array, } & ({ +dependsOn?: Array, } & ({ /** * Whether to cache the task */ @@ -95,6 +108,16 @@ scripts?: boolean, */ tasks?: boolean, }; +export type UserPackageDependency = { +/** + * Task name to run in dependency packages. + */ +task: string, +/** + * Package.json dependency field or fields to use when selecting direct dependency packages. + */ +from: DependsOnFrom, }; + export type RunConfig = { /** * Root-level cache configuration. diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs index a36ca1def..61358ec5b 100644 --- a/crates/vite_task_graph/src/config/mod.rs +++ b/crates/vite_task_graph/src/config/mod.rs @@ -7,8 +7,9 @@ use rustc_hash::FxHashSet; use serde::Serialize; pub use user::{ AutoTracking, Command, EnabledCacheConfig, GlobWithBase, InputBase, ResolvedGlobalCacheConfig, - UserCacheConfig, UserGlobalCacheConfig, UserInputEntry, UserInputsConfig, UserOutputEntry, - UserRunConfig, UserTaskConfig, UserTaskDefinition, + UserCacheConfig, UserDependencyType, UserDependsOnEntry, UserDependsOnFrom, + UserGlobalCacheConfig, UserInputEntry, UserInputsConfig, UserOutputEntry, + UserPackageDependency, UserRunConfig, UserTaskConfig, UserTaskDefinition, }; use vite_path::AbsolutePath; use vite_str::Str; diff --git a/crates/vite_task_graph/src/config/user.rs b/crates/vite_task_graph/src/config/user.rs index d7bb938c6..487ccec2a 100644 --- a/crates/vite_task_graph/src/config/user.rs +++ b/crates/vite_task_graph/src/config/user.rs @@ -7,6 +7,7 @@ use rustc_hash::FxHashMap; use serde::Deserialize; #[cfg(all(test, not(clippy)))] use ts_rs::TS; +use vec1::Vec1; use vite_path::RelativePathBuf; use vite_str::Str; @@ -65,6 +66,70 @@ pub enum UserInputEntry { /// Default (when field omitted): `[{auto: true}]` - infer from file accesses. pub type UserInputsConfig = Vec; +/// A supported package.json dependency field for package dependency selection. +#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy)] +// TS derive macro generates code using std types that clippy disallows; skip derive during linting +#[cfg_attr(all(test, not(clippy)), derive(TS), ts(rename = "DependencyType"))] +#[serde(rename_all = "camelCase")] +pub enum UserDependencyType { + /// Traverse dependencies declared in the package.json `dependencies` field. + Dependencies, + /// Traverse dependencies declared in the package.json `devDependencies` field. + DevDependencies, + /// Traverse dependencies declared in the package.json `peerDependencies` field. + PeerDependencies, +} + +/// The `from` selector for object-form `dependsOn` entries. +#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] +// TS derive macro generates code using std types that clippy disallows; skip derive during linting +#[cfg_attr(all(test, not(clippy)), derive(TS), ts(rename = "DependsOnFrom"))] +#[serde(untagged)] +pub enum UserDependsOnFrom { + /// Select one package.json dependency field. + Single(UserDependencyType), + /// Select the union of multiple package.json dependency fields. + Multiple( + #[cfg_attr(all(test, not(clippy)), ts(as = "Vec"))] + Vec1, + ), +} + +impl UserDependsOnFrom { + #[must_use] + pub fn as_slice(&self) -> &[UserDependencyType] { + match self { + Self::Single(dependency_type) => std::slice::from_ref(dependency_type), + Self::Multiple(dependency_types) => dependency_types, + } + } +} + +/// Object form for `dependsOn` entries that select direct workspace package dependencies. +#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] +// TS derive macro generates code using std types that clippy disallows; skip derive during linting +#[cfg_attr(all(test, not(clippy)), derive(TS))] +#[serde(deny_unknown_fields)] +pub struct UserPackageDependency { + /// Task name to run in dependency packages. + pub task: Str, + + /// Package.json dependency field or fields to use when selecting direct dependency packages. + pub from: UserDependsOnFrom, +} + +/// A single `dependsOn` entry. +#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] +// TS derive macro generates code using std types that clippy disallows; skip derive during linting +#[cfg_attr(all(test, not(clippy)), derive(TS), ts(rename = "DependsOnEntry"))] +#[serde(untagged)] +pub enum UserDependsOnEntry { + /// Same-package task or `package#task` specifier. + Task(Str), + /// Direct package dependency selection entry. + Package(UserPackageDependency), +} + /// A single output entry in the `output` array. /// /// Outputs can be: @@ -168,8 +233,15 @@ pub struct UserTaskOptions { #[serde(rename = "cwd")] pub cwd_relative_to_package: Option, - /// Dependencies of this task. Use `package-name#task-name` to refer to tasks in other packages. - pub depends_on: Option>, + /// Tasks that must run before this task. + /// + /// - A string runs one named task, such as `"build"` in the same package or + /// `"package-name#build"` in another package. + /// - An object runs a task in direct workspace dependency packages selected + /// from package.json fields. For example, + /// `{ "task": "build", "from": "dependencies" }` runs `build` in each + /// direct workspace dependency that defines a `build` task. + pub depends_on: Option>, /// Cache-related fields #[serde(flatten)] @@ -510,10 +582,73 @@ mod tests { ); let options = user_config.options; assert_eq!(options.cwd_relative_to_package.as_ref().unwrap().as_str(), "src"); - assert_eq!(options.depends_on.as_ref().unwrap().as_ref(), [Str::from("build")]); + assert_eq!( + options.depends_on.as_ref().unwrap().as_ref(), + [UserDependsOnEntry::Task(Str::from("build"))] + ); assert_eq!(options.cache_config, UserCacheConfig::Disabled { cache: MustBe!(false) }); } + #[test] + fn test_depends_on_package_dependency_single_from() { + let user_config_json = json!({ + "command": "echo test", + "dependsOn": [{ "task": "build", "from": "dependencies" }] + }); + let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!( + user_config.options.depends_on.as_ref().unwrap().as_ref(), + [UserDependsOnEntry::Package(UserPackageDependency { + task: "build".into(), + from: UserDependsOnFrom::Single(UserDependencyType::Dependencies), + })] + ); + } + + #[test] + fn test_depends_on_package_dependency_array_from() { + let user_config_json = json!({ + "command": "echo test", + "dependsOn": [{ + "task": "build", + "from": ["dependencies", "devDependencies", "peerDependencies"] + }] + }); + let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); + assert_eq!( + user_config.options.depends_on.as_ref().unwrap().as_ref(), + [UserDependsOnEntry::Package(UserPackageDependency { + task: "build".into(), + from: UserDependsOnFrom::Multiple( + Vec1::try_from_vec(vec![ + UserDependencyType::Dependencies, + UserDependencyType::DevDependencies, + UserDependencyType::PeerDependencies, + ]) + .unwrap() + ), + })] + ); + } + + #[test] + fn test_depends_on_package_dependency_empty_from_error() { + let user_config_json = json!({ + "command": "echo test", + "dependsOn": [{ "task": "build", "from": [] }] + }); + assert!(serde_json::from_value::(user_config_json).is_err()); + } + + #[test] + fn test_depends_on_package_dependency_unknown_from_error() { + let user_config_json = json!({ + "command": "echo test", + "dependsOn": [{ "task": "build", "from": "optionalDependencies" }] + }); + assert!(serde_json::from_value::(user_config_json).is_err()); + } + #[test] fn test_task_invalid_shorthand_error() { let user_config_json = json!({ diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 2985bd9cb..72dc950ac 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -10,21 +10,32 @@ use config::{ ResolvedGlobalCacheConfig, ResolvedTaskConfig, UserRunConfig, UserTaskConfig, UserTaskDefinition, }; -use petgraph::graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}; +use petgraph::{ + graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}, + visit::EdgeRef as _, +}; use rustc_hash::{FxBuildHasher, FxHashMap}; use serde::Serialize; pub use specifier::TaskSpecifier; use vite_path::AbsolutePath; use vite_str::Str; -use vite_workspace::{PackageNodeIndex, WorkspaceRoot, package_graph::IndexedPackageGraph}; +use vite_workspace::{ + DependencyType, PackageNodeIndex, WorkspaceRoot, package_graph::IndexedPackageGraph, +}; -use crate::{config::user::UserTaskOptions, display::TaskDisplay}; +use crate::{ + config::user::{ + UserDependencyType, UserDependsOnEntry, UserPackageDependency, UserTaskOptions, + }, + display::TaskDisplay, +}; /// The type of a task dependency edge in the task graph. /// -/// Currently only `Explicit` is produced (from `dependsOn` in the task config). -/// Topological ordering is handled at query time via the package subgraph rather -/// than by pre-computing edges in the task graph. +/// All edges are produced from `dependsOn` in task config, including string-form +/// task specifiers and object-form package dependency selections. Topological +/// ordering is handled at query time via the package subgraph rather than by +/// pre-computing package edges in the task graph. #[derive(Debug, Clone, Copy, Serialize)] pub struct TaskDependencyType; @@ -168,6 +179,37 @@ unsafe impl IndexType for TaskIx { pub type TaskNodeIndex = NodeIndex; pub type TaskEdgeIndex = EdgeIndex; +/// A `dependsOn` entry that selects direct package dependencies from a source task. +struct PackageDependencyEntry { + task_name: Str, + dependency_types: Box<[DependencyType]>, +} + +impl PackageDependencyEntry { + fn from_user_config(entry: &UserPackageDependency) -> Self { + Self { + task_name: entry.task.clone(), + dependency_types: entry + .from + .as_slice() + .iter() + .copied() + .map(DependencyType::from) + .collect(), + } + } +} + +impl From for DependencyType { + fn from(value: UserDependencyType) -> Self { + match value { + UserDependencyType::Dependencies => Self::Normal, + UserDependencyType::DevDependencies => Self::Dev, + UserDependencyType::PeerDependencies => Self::Peer, + } + } +} + /// Full task graph of a workspace, with necessary hash maps for quick task lookup /// /// It's immutable after created. The task nodes contain resolved task configurations and their dependencies. @@ -217,8 +259,9 @@ impl IndexedTaskGraph { let package_graph = vite_workspace::load_package_graph(workspace_root)?; - // Record dependency specifiers for each task node to add explicit dependencies later - let mut task_ids_with_dependency_specifiers: Vec<(TaskId, Option>)> = Vec::new(); + // Record dependency declarations for each task node to add explicit dependencies later. + let mut task_ids_with_depends_on_entries: Vec<(TaskId, Option>)> = + Vec::new(); // index tasks by ids let mut node_indices_by_task_id: FxHashMap = @@ -303,7 +346,7 @@ impl IndexedTaskGraph { UserTaskConfig { command, options: UserTaskOptions::default() } } }; - let dependency_specifiers = task_user_config.options.depends_on.clone(); + let depends_on_entries = task_user_config.options.depends_on.clone(); // Resolve the task configuration from the user config let resolved_config = ResolvedTaskConfig::resolve( @@ -331,7 +374,7 @@ impl IndexedTaskGraph { }; let node_index = task_graph.add_node(task_node); - task_ids_with_dependency_specifiers.push((task_id.clone(), dependency_specifiers)); + task_ids_with_depends_on_entries.push((task_id.clone(), depends_on_entries)); task_ids_by_node_index.insert(node_index, task_id.clone()); node_indices_by_task_id.insert(task_id, node_index); } @@ -376,21 +419,37 @@ impl IndexedTaskGraph { pre_post_scripts_enabled: root_pre_post_scripts_enabled.unwrap_or(true), }; - // Add explicit dependencies - for (from_task_id, dependency_specifiers) in task_ids_with_dependency_specifiers { + // Add explicit dependencies. String-form entries resolve to fixed task + // specifier edges. Object-form entries select direct package dependency + // tasks and materialize those selections as global task graph edges. + for (from_task_id, depends_on_entries) in task_ids_with_depends_on_entries { let from_node_index = me.node_indices_by_task_id[&from_task_id]; - for specifier in dependency_specifiers.iter().flat_map(|s| s.iter()).cloned() { - let to_node_index = me - .get_task_index_by_specifier::( - TaskSpecifier::parse_raw(&specifier), - || Ok(from_task_id.package_index), - ) - .map_err(|error| TaskGraphLoadError::DependencySpecifierLookupError { - error, - specifier, - task_display: me.display_task(from_node_index), - })?; - me.task_graph.update_edge(from_node_index, to_node_index, TaskDependencyType); + for entry in depends_on_entries.iter().flat_map(|entries| entries.iter()) { + match entry { + UserDependsOnEntry::Task(specifier) => { + let to_node_index = me + .get_task_index_by_specifier::( + TaskSpecifier::parse_raw(specifier), + || Ok(from_task_id.package_index), + ) + .map_err(|error| { + TaskGraphLoadError::DependencySpecifierLookupError { + error, + specifier: specifier.clone(), + task_display: me.display_task(from_node_index), + } + })?; + me.task_graph.update_edge( + from_node_index, + to_node_index, + TaskDependencyType, + ); + } + UserDependsOnEntry::Package(entry) => { + let entry = PackageDependencyEntry::from_user_config(entry); + me.add_package_dependency_edges(from_node_index, &from_task_id, &entry); + } + } } } @@ -451,6 +510,45 @@ impl IndexedTaskGraph { Ok(*node_index) } + /// Materialize one object-form `dependsOn` entry as ordinary task graph edges. + /// + /// Object entries such as `{ "task": "build", "from": "dependencies" }` + /// are anchored to the package that declares the source task. During graph + /// loading we resolve the selected direct package dependencies and add + /// `source_task -> dependency_package#task` edges, so later query planning + /// can treat them exactly like string-form `dependsOn` edges. + fn add_package_dependency_edges( + &mut self, + from_node_index: TaskNodeIndex, + from_task_id: &TaskId, + entry: &PackageDependencyEntry, + ) { + let package_graph = self.indexed_package_graph.package_graph(); + let dependency_tasks = package_graph + .edges(from_task_id.package_index) + // Only use package.json dependency fields requested by `from`. + .filter(|edge| entry.dependency_types.contains(edge.weight())) + // Missing tasks are intentionally ignored: selecting + // `{ task: "build", from: "dependencies" }` means "run build in + // direct dependency packages that define build", not "require every + // selected package to define build". + .filter_map(|edge| { + self.node_indices_by_task_id + .get(&TaskId { + package_index: edge.target(), + task_name: entry.task_name.clone(), + }) + .copied() + }) + .collect::>(); + + // Keep discovery separate from insertion so the package-graph walk and + // task-id lookups finish before we take a mutable borrow of task_graph. + for to_node_index in dependency_tasks { + self.task_graph.update_edge(from_node_index, to_node_index, TaskDependencyType); + } + } + #[must_use] pub const fn task_graph(&self) -> &TaskGraph { &self.task_graph diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/package.json new file mode 100644 index 000000000..73b7c3ebe --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/object-depends-on-global-graph", + "version": "1.0.0", + "workspaces": [ + "packages/*" + ] +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/package.json new file mode 100644 index 000000000..04137006d --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/package.json @@ -0,0 +1,14 @@ +{ + "name": "@test/app", + "version": "1.0.0", + "dependencies": { + "@test/prod-a": "workspace:*", + "@test/prod-missing-task": "workspace:*" + }, + "devDependencies": { + "@test/dev-a": "workspace:*" + }, + "peerDependencies": { + "@test/peer-a": "workspace:*" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/vite-task.json new file mode 100644 index 000000000..05008ba16 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/vite-task.json @@ -0,0 +1,20 @@ +{ + "tasks": { + "test_dependencies": { + "command": "vtt test dependencies", + "dependsOn": [{ "task": "build", "from": "dependencies" }] + }, + "test_dev": { + "command": "vtt test dev", + "dependsOn": [{ "task": "build", "from": "devDependencies" }] + }, + "test_union": { + "command": "vtt test union", + "dependsOn": [{ "task": "build", "from": ["dependencies", "devDependencies"] }] + }, + "test_recursive": { + "command": "vtt test recursive", + "dependsOn": [{ "task": "build_recursive", "from": "devDependencies" }] + } + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/package.json new file mode 100644 index 000000000..0d34156a7 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/package.json @@ -0,0 +1,10 @@ +{ + "name": "@test/dev-a", + "version": "1.0.0", + "scripts": { + "build": "vtt build dev-a" + }, + "devDependencies": { + "@test/shared": "workspace:*" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/vite-task.json new file mode 100644 index 000000000..2410dd913 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/vite-task.json @@ -0,0 +1,8 @@ +{ + "tasks": { + "build_recursive": { + "command": "vtt build recursive dev-a", + "dependsOn": [{ "task": "build_recursive", "from": "devDependencies" }] + } + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/peer-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/peer-a/package.json new file mode 100644 index 000000000..6cd065a8c --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/peer-a/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/peer-a", + "version": "1.0.0", + "scripts": { + "build": "vtt build peer-a" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-a/package.json new file mode 100644 index 000000000..40f1f23e8 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-a/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/prod-a", + "version": "1.0.0", + "scripts": { + "build": "vtt build prod-a" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-missing-task/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-missing-task/package.json new file mode 100644 index 000000000..9659e3961 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-missing-task/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/prod-missing-task", + "version": "1.0.0", + "scripts": { + "lint": "vtt lint prod-missing-task" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/shared/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/shared/package.json new file mode 100644 index 000000000..37434e0bc --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/shared/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/shared", + "version": "1.0.0", + "scripts": { + "build_recursive": "vtt build recursive shared" + } +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/pnpm-workspace.yaml new file mode 100644 index 000000000..18ec407ef --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots.toml new file mode 100644 index 000000000..9c6a0b6b1 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots.toml @@ -0,0 +1,11 @@ +# Object-form `dependsOn` selections are materialized as global task graph +# edges, so the edge structure for every `from` variant (dependencies, +# devDependencies, unions, recursive chains, and excluded peer/missing-task +# deps) is asserted by `snapshots/task_graph.md`. The only behavior the static +# graph cannot express is query-time `--ignore-depends-on` dropping those +# materialized edges, so that is the one plan case kept here. +[[plan]] +compact = true +name = "ignore_depends_on_omits_object_edges" +args = ["run", "--ignore-depends-on", "test_union"] +cwd = "packages/app" diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/query_ignore_depends_on_omits_object_edges.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/query_ignore_depends_on_omits_object_edges.jsonc new file mode 100644 index 000000000..001458484 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/query_ignore_depends_on_omits_object_edges.jsonc @@ -0,0 +1,4 @@ +// run --ignore-depends-on test_union +{ + "packages/app#test_union": [] +} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/task_graph.md new file mode 100644 index 000000000..a889d36c3 --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/task_graph.md @@ -0,0 +1,412 @@ +# task graph + +```mermaid +flowchart TD + task_0["/packages/app#test_dependencies"] + task_0 --> task_7 + task_1["/packages/app#test_dev"] + task_1 --> task_4 + task_2["/packages/app#test_recursive"] + task_2 --> task_5 + task_3["/packages/app#test_union"] + task_3 --> task_4 + task_3 --> task_7 + task_4["/packages/dev-a#build"] + task_5["/packages/dev-a#build_recursive"] + task_5 --> task_9 + task_6["/packages/peer-a#build"] + task_7["/packages/prod-a#build"] + task_8["/packages/prod-missing-task#lint"] + task_9["/packages/shared#build_recursive"] +``` + +## `/packages/app#test_dependencies` + +```json +{ + "task_display": { + "package_name": "@test/app", + "task_name": "test_dependencies", + "package_path": "/packages/app" + }, + "resolved_config": { + "commands": [ + "vtt test dependencies" + ], + "resolved_options": { + "cwd": "/packages/app", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "TaskConfig" +} +``` + +## `/packages/app#test_dev` + +```json +{ + "task_display": { + "package_name": "@test/app", + "task_name": "test_dev", + "package_path": "/packages/app" + }, + "resolved_config": { + "commands": [ + "vtt test dev" + ], + "resolved_options": { + "cwd": "/packages/app", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "TaskConfig" +} +``` + +## `/packages/app#test_recursive` + +```json +{ + "task_display": { + "package_name": "@test/app", + "task_name": "test_recursive", + "package_path": "/packages/app" + }, + "resolved_config": { + "commands": [ + "vtt test recursive" + ], + "resolved_options": { + "cwd": "/packages/app", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "TaskConfig" +} +``` + +## `/packages/app#test_union` + +```json +{ + "task_display": { + "package_name": "@test/app", + "task_name": "test_union", + "package_path": "/packages/app" + }, + "resolved_config": { + "commands": [ + "vtt test union" + ], + "resolved_options": { + "cwd": "/packages/app", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "TaskConfig" +} +``` + +## `/packages/dev-a#build` + +```json +{ + "task_display": { + "package_name": "@test/dev-a", + "task_name": "build", + "package_path": "/packages/dev-a" + }, + "resolved_config": { + "commands": [ + "vtt build dev-a" + ], + "resolved_options": { + "cwd": "/packages/dev-a", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "PackageJsonScript" +} +``` + +## `/packages/dev-a#build_recursive` + +```json +{ + "task_display": { + "package_name": "@test/dev-a", + "task_name": "build_recursive", + "package_path": "/packages/dev-a" + }, + "resolved_config": { + "commands": [ + "vtt build recursive dev-a" + ], + "resolved_options": { + "cwd": "/packages/dev-a", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "TaskConfig" +} +``` + +## `/packages/peer-a#build` + +```json +{ + "task_display": { + "package_name": "@test/peer-a", + "task_name": "build", + "package_path": "/packages/peer-a" + }, + "resolved_config": { + "commands": [ + "vtt build peer-a" + ], + "resolved_options": { + "cwd": "/packages/peer-a", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "PackageJsonScript" +} +``` + +## `/packages/prod-a#build` + +```json +{ + "task_display": { + "package_name": "@test/prod-a", + "task_name": "build", + "package_path": "/packages/prod-a" + }, + "resolved_config": { + "commands": [ + "vtt build prod-a" + ], + "resolved_options": { + "cwd": "/packages/prod-a", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "PackageJsonScript" +} +``` + +## `/packages/prod-missing-task#lint` + +```json +{ + "task_display": { + "package_name": "@test/prod-missing-task", + "task_name": "lint", + "package_path": "/packages/prod-missing-task" + }, + "resolved_config": { + "commands": [ + "vtt lint prod-missing-task" + ], + "resolved_options": { + "cwd": "/packages/prod-missing-task", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "PackageJsonScript" +} +``` + +## `/packages/shared#build_recursive` + +```json +{ + "task_display": { + "package_name": "@test/shared", + "task_name": "build_recursive", + "package_path": "/packages/shared" + }, + "resolved_config": { + "commands": [ + "vtt build recursive shared" + ], + "resolved_options": { + "cwd": "/packages/shared", + "cache_config": { + "env_config": { + "fingerprinted_envs": [], + "untracked_env": [ + "" + ] + }, + "input_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + }, + "output_config": { + "includes_auto": true, + "positive_globs": [], + "negative_globs": [] + } + } + } + }, + "source": "PackageJsonScript" +} +``` + diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/vite-task.json new file mode 100644 index 000000000..d548edfac --- /dev/null +++ b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/vite-task.json @@ -0,0 +1,3 @@ +{ + "cache": true +}