From 490e499ba4b8c11e4e55889f8999f1b267814ae3 Mon Sep 17 00:00:00 2001 From: Nicholas Yang Date: Thu, 26 Sep 2024 14:08:05 -0400 Subject: [PATCH] feat(query): add filter by having task (#9188) ### Description Adds a `has` filter that lets you check if a package has a task. ### Testing Instructions Added tests to `command-query.t` --- crates/turborepo-lib/src/query/mod.rs | 15 +++++++ crates/turborepo-lib/src/query/package.rs | 10 ++++- crates/turborepo-lib/src/query/task.rs | 1 + .../packages/another/package.json | 4 +- .../integration/tests/command-ls.t | 3 +- .../integration/tests/command-query.t | 39 +++++++++++++++++++ .../integration/tests/edit-turbo-json/task.t | 10 ++--- turborepo-tests/integration/tests/no-args.t | 4 ++ 8 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 crates/turborepo-lib/src/query/task.rs diff --git a/crates/turborepo-lib/src/query/mod.rs b/crates/turborepo-lib/src/query/mod.rs index f6abe0aa7a6e1..f12a7da65d0a4 100644 --- a/crates/turborepo-lib/src/query/mod.rs +++ b/crates/turborepo-lib/src/query/mod.rs @@ -67,6 +67,7 @@ impl FromIterator for Array { #[derive(Enum, Copy, Clone, Eq, PartialEq, Debug)] enum PackageFields { Name, + TaskName, DirectDependencyCount, DirectDependentCount, IndirectDependentCount, @@ -96,6 +97,7 @@ struct PackagePredicate { greater_than: Option, less_than: Option, not: Option>, + has: Option, } impl PackagePredicate { @@ -226,6 +228,14 @@ impl PackagePredicate { } } + fn check_has(pkg: &Package, field: &PackageFields, value: &Any) -> bool { + match (field, &value.0) { + (PackageFields::Name, Value::String(name)) => pkg.name.as_ref() == name, + (PackageFields::TaskName, Value::String(name)) => pkg.task_names().contains(name), + _ => false, + } + } + fn check(&self, pkg: &Package) -> bool { let and = self .and @@ -254,6 +264,10 @@ impl PackagePredicate { .as_ref() .map(|pair| Self::check_greater_than(pkg, &pair.field, &pair.value)); let not = self.not.as_ref().map(|predicate| !predicate.check(pkg)); + let has = self + .has + .as_ref() + .map(|pair| Self::check_has(pkg, &pair.field, &pair.value)); and.into_iter() .chain(or) @@ -262,6 +276,7 @@ impl PackagePredicate { .chain(greater_than) .chain(less_than) .chain(not) + .chain(has) .all(|p| p) } } diff --git a/crates/turborepo-lib/src/query/package.rs b/crates/turborepo-lib/src/query/package.rs index 789f0c5135ebc..e5092a87a5237 100644 --- a/crates/turborepo-lib/src/query/package.rs +++ b/crates/turborepo-lib/src/query/package.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; use async_graphql::Object; use itertools::Itertools; @@ -15,6 +15,14 @@ pub struct Package { } impl Package { + pub fn task_names(&self) -> HashSet { + self.run + .pkg_dep_graph() + .package_json(&self.name) + .map(|json| json.scripts.keys().cloned().collect()) + .unwrap_or_default() + } + pub fn direct_dependents_count(&self) -> usize { self.run .pkg_dep_graph() diff --git a/crates/turborepo-lib/src/query/task.rs b/crates/turborepo-lib/src/query/task.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/crates/turborepo-lib/src/query/task.rs @@ -0,0 +1 @@ + diff --git a/turborepo-tests/integration/fixtures/basic_monorepo/packages/another/package.json b/turborepo-tests/integration/fixtures/basic_monorepo/packages/another/package.json index e9e34ea52c154..0094525bb63b2 100644 --- a/turborepo-tests/integration/fixtures/basic_monorepo/packages/another/package.json +++ b/turborepo-tests/integration/fixtures/basic_monorepo/packages/another/package.json @@ -1,4 +1,6 @@ { "name": "another", - "scripts": {} + "scripts": { + "dev": "echo building" + } } diff --git a/turborepo-tests/integration/tests/command-ls.t b/turborepo-tests/integration/tests/command-ls.t index ebf8c9f3042f8..6108a209ff74b 100644 --- a/turborepo-tests/integration/tests/command-ls.t +++ b/turborepo-tests/integration/tests/command-ls.t @@ -47,7 +47,8 @@ Run info on package `another` WARNING ls command is experimental and may change in the future another depends on: - tasks: + tasks: + dev: echo building Run info on package `my-app` diff --git a/turborepo-tests/integration/tests/command-query.t b/turborepo-tests/integration/tests/command-query.t index 0d924f0de5172..7d9b2f8e1654a 100644 --- a/turborepo-tests/integration/tests/command-query.t +++ b/turborepo-tests/integration/tests/command-query.t @@ -55,6 +55,45 @@ Query packages that have at least one dependent package } } +Query packages that have a task named `build` + $ ${TURBO} query "query { packages(filter: { has: { field: TASK_NAME, value: \"build\" } }) { items { name } } }" | jq + WARNING query command is experimental and may change in the future + { + "data": { + "packages": { + "items": [ + { + "name": "my-app" + }, + { + "name": "util" + } + ] + } + } + } + +Query packages that have a task named `build` or `dev` + $ ${TURBO} query "query { packages(filter: { or: [{ has: { field: TASK_NAME, value: \"build\" } }, { has: { field: TASK_NAME, value: \"dev\" } }] }) { items { name } } }" | jq + WARNING query command is experimental and may change in the future + { + "data": { + "packages": { + "items": [ + { + "name": "another" + }, + { + "name": "my-app" + }, + { + "name": "util" + } + ] + } + } + } + Get dependents of `util` $ ${TURBO} query "query { packages(filter: { equal: { field: NAME, value: \"util\" } }) { items { directDependents { items { name } } } } }" | jq WARNING query command is experimental and may change in the future diff --git a/turborepo-tests/integration/tests/edit-turbo-json/task.t b/turborepo-tests/integration/tests/edit-turbo-json/task.t index d72b6cdf7287b..6e419cd808746 100644 --- a/turborepo-tests/integration/tests/edit-turbo-json/task.t +++ b/turborepo-tests/integration/tests/edit-turbo-json/task.t @@ -6,7 +6,7 @@ Baseline task hashes $ ${TURBO} build --dry=json | jq -r '.tasks | sort_by(.taskId)[] | {taskId, hash}' { "taskId": "another#build", - "hash": "3639431fdcdf9f9e" + "hash": "e9a99dd97d223d88" } { "taskId": "my-app#build", @@ -22,7 +22,7 @@ Change only my-app#build $ ${TURBO} build --dry=json | jq -r '.tasks | sort_by(.taskId)[] | {taskId, hash}' { "taskId": "another#build", - "hash": "3639431fdcdf9f9e" + "hash": "e9a99dd97d223d88" } { "taskId": "my-app#build", @@ -38,7 +38,7 @@ Change my-app#build dependsOn $ ${TURBO} build --dry=json | jq -r '.tasks | sort_by(.taskId)[] | {taskId, hash}' { "taskId": "another#build", - "hash": "3639431fdcdf9f9e" + "hash": "e9a99dd97d223d88" } { "taskId": "my-app#build", @@ -54,7 +54,7 @@ Non-materially modifying the dep graph does nothing. $ ${TURBO} build --dry=json | jq -r '.tasks | sort_by(.taskId)[] | {taskId, hash}' { "taskId": "another#build", - "hash": "3639431fdcdf9f9e" + "hash": "e9a99dd97d223d88" } { "taskId": "my-app#build", @@ -71,7 +71,7 @@ Change util#build impacts itself and my-app $ ${TURBO} build --dry=json | jq -r '.tasks | sort_by(.taskId)[] | {taskId, hash}' { "taskId": "another#build", - "hash": "3639431fdcdf9f9e" + "hash": "e9a99dd97d223d88" } { "taskId": "my-app#build", diff --git a/turborepo-tests/integration/tests/no-args.t b/turborepo-tests/integration/tests/no-args.t index b76576b838c1d..a508040296d85 100644 --- a/turborepo-tests/integration/tests/no-args.t +++ b/turborepo-tests/integration/tests/no-args.t @@ -126,6 +126,8 @@ Run without any tasks, get a list of potential tasks to run my-app, util maybefails my-app, util + dev + another [1] Run again with a filter and get only the packages that match @@ -176,6 +178,8 @@ Initialize a new monorepo cross-workspace cross-workspace-underlying-task blank-pkg + dev + another missing-workspace-config-task missing-workspace-config missing-workspace-config-task-with-deps