diff --git a/crates/turborepo-lib/src/query/external_package.rs b/crates/turborepo-lib/src/query/external_package.rs index f4cd438a45a86..05d7af00cc20a 100644 --- a/crates/turborepo-lib/src/query/external_package.rs +++ b/crates/turborepo-lib/src/query/external_package.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use async_graphql::Object; +use super::{package::Package, Array, Error}; use crate::run::Run; #[derive(Clone)] @@ -30,4 +31,20 @@ impl ExternalPackage { async fn name(&self) -> String { self.human_name().to_string() } + + async fn internal_dependants(&self) -> Result, Error> { + let Some(names) = self + .run + .pkg_dep_graph() + .internal_dependencies_for_external_dependency(&self.package) + else { + return Ok(Array::from(Vec::new())); + }; + let mut packages = names + .iter() + .map(|name| Package::new(self.run.clone(), name.as_package_name().clone())) + .collect::, Error>>()?; + packages.sort_by(|a, b| a.get_name().cmp(b.get_name())); + Ok(packages) + } } diff --git a/crates/turborepo-lib/src/query/mod.rs b/crates/turborepo-lib/src/query/mod.rs index 34bdf06103037..a45ed9efd3aa3 100644 --- a/crates/turborepo-lib/src/query/mod.rs +++ b/crates/turborepo-lib/src/query/mod.rs @@ -616,6 +616,18 @@ impl RepositoryQuery { Ok(packages) } + + async fn external_dependencies(&self) -> Result, Error> { + let mut packages = self + .run + .pkg_dep_graph() + .external_to_internal() + .keys() + .map(|pkg| ExternalPackage::new(self.run.clone(), pkg.clone())) + .collect::>(); + packages.sort_by_key(|pkg| pkg.human_name()); + Ok(packages) + } } pub async fn graphiql() -> impl IntoResponse { diff --git a/crates/turborepo-repository/src/package_graph/builder.rs b/crates/turborepo-repository/src/package_graph/builder.rs index 5be2c2945a8b9..d18b7c160bbc6 100644 --- a/crates/turborepo-repository/src/package_graph/builder.rs +++ b/crates/turborepo-repository/src/package_graph/builder.rs @@ -341,6 +341,7 @@ impl<'a, T: PackageDiscovery> BuildState<'a, ResolvedPackageManager, T> { lockfile, package_manager, repo_root: repo_root.to_owned(), + external_to_internal: std::sync::OnceLock::new(), }) } } @@ -539,6 +540,7 @@ impl BuildState<'_, ResolvedLockfile, T> { package_manager, lockfile, repo_root: repo_root.to_owned(), + external_to_internal: std::sync::OnceLock::new(), }) } } diff --git a/crates/turborepo-repository/src/package_graph/mod.rs b/crates/turborepo-repository/src/package_graph/mod.rs index 446332aef78dc..e49e603678ced 100644 --- a/crates/turborepo-repository/src/package_graph/mod.rs +++ b/crates/turborepo-repository/src/package_graph/mod.rs @@ -1,6 +1,7 @@ use std::{ collections::{BTreeMap, HashMap, HashSet}, fmt, + sync::OnceLock, }; use itertools::Itertools; @@ -34,6 +35,7 @@ pub struct PackageGraph { package_manager: PackageManager, lockfile: Option>, repo_root: AbsoluteSystemPathBuf, + external_to_internal: OnceLock>>, } /// The WorkspacePackage. @@ -555,6 +557,63 @@ impl PackageGraph { })) } + pub fn internal_dependencies_for_external_dependency( + &self, + external_package: &turborepo_lockfiles::Package, + ) -> Option<&HashSet> { + // In order to answer this once we have to calculate the info for every external + // package so we store the results + let map = self + .external_to_internal + .get_or_init(|| self.external_to_internal()); + map.get(external_package) + } + + // Gotta come up with a better name + pub fn external_to_internal( + &self, + ) -> HashMap> { + // TODO: provide size hint from Lockfile trait + let mut map: HashMap> = HashMap::new(); + // First find which packages directly depend on each external package + for (pkg, info) in self.packages.iter() { + for dep in info.transitive_dependencies.as_ref().into_iter().flatten() { + let rdeps = map.entry(dep.clone()).or_default(); + rdeps.insert(PackageNode::Workspace(pkg.clone())); + } + } + // Now trace through all ancestors of the direct dependants + let root_internal_dependencies = self + .root_internal_dependencies() + .into_iter() + .cloned() + .collect::>(); + let root_external_dependencies = + self.transitive_external_dependencies(Some(&PackageName::Root)); + for (external_pkg, rdeps) in map.iter_mut() { + // If one of the reverse dependencies of this external package is a root + // dependency, everything depends on this + if root_external_dependencies.contains(external_pkg) + || !root_internal_dependencies.is_disjoint(rdeps) + { + rdeps.extend(self.graph.node_weights().cloned()); + } else { + let transitive_rdeps = turborepo_graph_utils::transitive_closure( + &self.graph, + rdeps.iter().map(|node| { + *self + .node_lookup + .get(node) + .expect("all nodes should have an index") + }), + petgraph::Direction::Incoming, + ); + rdeps.extend(transitive_rdeps.into_iter().cloned()); + } + } + map + } + // Returns a map of package name and version for external dependencies #[allow(dead_code)] fn external_dependencies(