diff --git a/crates/turborepo-lib/src/query/mod.rs b/crates/turborepo-lib/src/query/mod.rs index f6abe0aa7a6e13..f12a7da65d0a47 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 789f0c5135ebc1..e5092a87a5237b 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/turborepo-tests/integration/tests/command-query.t b/turborepo-tests/integration/tests/command-query.t index 0d924f0de5172f..25cd93f218b7da 100644 --- a/turborepo-tests/integration/tests/command-query.t +++ b/turborepo-tests/integration/tests/command-query.t @@ -55,6 +55,42 @@ 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": "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