diff --git a/crates/turborepo-lib/src/boundaries/config.rs b/crates/turborepo-lib/src/boundaries/config.rs index 6bf20f0438d04..454258dfd2827 100644 --- a/crates/turborepo-lib/src/boundaries/config.rs +++ b/crates/turborepo-lib/src/boundaries/config.rs @@ -6,9 +6,13 @@ use struct_iterable::Iterable; use turborepo_errors::Spanned; #[derive(Serialize, Default, Debug, Clone, Iterable, Deserializable, PartialEq)] -pub struct RootBoundariesConfig { +pub struct BoundariesConfig { + #[serde(skip_serializing_if = "Option::is_none")] pub tags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub implicit_dependencies: Option>>>, } + pub type RulesMap = HashMap>; #[derive(Serialize, Default, Debug, Clone, Iterable, Deserializable, PartialEq)] diff --git a/crates/turborepo-lib/src/boundaries/imports.rs b/crates/turborepo-lib/src/boundaries/imports.rs index 82117659bb105..d1d629158f1e2 100644 --- a/crates/turborepo-lib/src/boundaries/imports.rs +++ b/crates/turborepo-lib/src/boundaries/imports.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, sync::Arc, }; @@ -10,8 +10,9 @@ use oxc_resolver::{ResolveError, Resolver, TsConfig}; use swc_common::{comments::SingleThreadedComments, SourceFile, Span}; use turbo_trace::ImportType; use turbopath::{AbsoluteSystemPath, AnchoredSystemPathBuf, PathRelation, RelativeUnixPath}; +use turborepo_errors::Spanned; use turborepo_repository::{ - package_graph::{PackageInfo, PackageName, PackageNode}, + package_graph::{PackageName, PackageNode}, package_json::PackageJson, }; @@ -20,6 +21,64 @@ use crate::{ run::Run, }; +/// All the places a dependency can be declared +#[derive(Clone, Copy)] +pub struct DependencyLocations<'a> { + pub(crate) internal_dependencies: &'a HashSet<&'a PackageNode>, + pub(crate) package_json: &'a PackageJson, + pub(crate) unresolved_external_dependencies: Option<&'a BTreeMap>, + pub(crate) implicit_dependencies: &'a HashMap>, + pub(crate) global_implicit_dependencies: &'a HashMap>, +} + +impl<'a> DependencyLocations<'a> { + /// Go through all the possible places a package could be declared to see if + /// it's a valid import. We don't use `oxc_resolver` because there are some + /// cases where you can resolve a package that isn't declared properly. + fn is_dependency(&self, package_name: &PackageNode) -> bool { + self.internal_dependencies.contains(package_name) + || self + .unresolved_external_dependencies + .is_some_and(|external_dependencies| { + external_dependencies.contains_key(package_name.as_package_name().as_str()) + }) + || self + .package_json + .dependencies + .as_ref() + .is_some_and(|dependencies| { + dependencies.contains_key(package_name.as_package_name().as_str()) + }) + || self + .package_json + .dev_dependencies + .as_ref() + .is_some_and(|dev_dependencies| { + dev_dependencies.contains_key(package_name.as_package_name().as_str()) + }) + || self + .package_json + .peer_dependencies + .as_ref() + .is_some_and(|peer_dependencies| { + peer_dependencies.contains_key(package_name.as_package_name().as_str()) + }) + || self + .package_json + .optional_dependencies + .as_ref() + .is_some_and(|optional_dependencies| { + optional_dependencies.contains_key(package_name.as_package_name().as_str()) + }) + || self + .implicit_dependencies + .contains_key(package_name.as_package_name().as_str()) + || self + .global_implicit_dependencies + .contains_key(package_name.as_package_name().as_str()) + } +} + impl Run { /// Checks if the given import can be resolved as a tsconfig path alias, /// e.g. `@/types/foo` -> `./src/foo`, and if so, checks the resolved paths. @@ -79,9 +138,7 @@ impl Run { span: &Span, file_path: &AbsoluteSystemPath, file_content: &str, - package_info: &PackageInfo, - internal_dependencies: &HashSet<&PackageNode>, - unresolved_external_dependencies: Option<&BTreeMap>, + dependency_locations: DependencyLocations<'_>, resolver: &Resolver, ) -> Result<(), Error> { // If the import is prefixed with `@boundaries-ignore`, we ignore it, but print @@ -154,9 +211,7 @@ impl Run { span, file_path, file_content, - &package_info.package_json, - internal_dependencies, - unresolved_external_dependencies, + dependency_locations, resolver, ) } else { @@ -207,45 +262,6 @@ impl Run { } } - /// Go through all the possible places a package could be declared to see if - /// it's a valid import. We don't use `oxc_resolver` because there are some - /// cases where you can resolve a package that isn't declared properly. - fn is_dependency( - internal_dependencies: &HashSet<&PackageNode>, - package_json: &PackageJson, - unresolved_external_dependencies: Option<&BTreeMap>, - package_name: &PackageNode, - ) -> bool { - internal_dependencies.contains(&package_name) - || unresolved_external_dependencies.is_some_and(|external_dependencies| { - external_dependencies.contains_key(package_name.as_package_name().as_str()) - }) - || package_json - .dependencies - .as_ref() - .is_some_and(|dependencies| { - dependencies.contains_key(package_name.as_package_name().as_str()) - }) - || package_json - .dev_dependencies - .as_ref() - .is_some_and(|dev_dependencies| { - dev_dependencies.contains_key(package_name.as_package_name().as_str()) - }) - || package_json - .peer_dependencies - .as_ref() - .is_some_and(|peer_dependencies| { - peer_dependencies.contains_key(package_name.as_package_name().as_str()) - }) - || package_json - .optional_dependencies - .as_ref() - .is_some_and(|optional_dependencies| { - optional_dependencies.contains_key(package_name.as_package_name().as_str()) - }) - } - fn get_package_name(import: &str) -> String { if import.starts_with("@") { import.split('/').take(2).join("/") @@ -266,9 +282,7 @@ impl Run { span: SourceSpan, file_path: &AbsoluteSystemPath, file_content: &str, - package_json: &PackageJson, - internal_dependencies: &HashSet<&PackageNode>, - unresolved_external_dependencies: Option<&BTreeMap>, + dependency_locations: DependencyLocations<'_>, resolver: &Resolver, ) -> Option { let package_name = Self::get_package_name(import); @@ -282,12 +296,7 @@ impl Run { } let package_name = PackageNode::Workspace(PackageName::Other(package_name)); let folder = file_path.parent().expect("file_path should have a parent"); - let is_valid_dependency = Self::is_dependency( - internal_dependencies, - package_json, - unresolved_external_dependencies, - &package_name, - ); + let is_valid_dependency = dependency_locations.is_dependency(&package_name); if !is_valid_dependency && !matches!( @@ -300,12 +309,7 @@ impl Run { "@types/{}", package_name.as_package_name().as_str() ))); - let is_types_dependency = Self::is_dependency( - internal_dependencies, - package_json, - unresolved_external_dependencies, - &types_package_name, - ); + let is_types_dependency = dependency_locations.is_dependency(&types_package_name); if is_types_dependency { return match import_type { diff --git a/crates/turborepo-lib/src/boundaries/mod.rs b/crates/turborepo-lib/src/boundaries/mod.rs index f8845cef055fa..e07810aa449a1 100644 --- a/crates/turborepo-lib/src/boundaries/mod.rs +++ b/crates/turborepo-lib/src/boundaries/mod.rs @@ -8,7 +8,7 @@ use std::{ sync::{Arc, LazyLock, Mutex}, }; -pub use config::{Permissions, RootBoundariesConfig, Rule}; +pub use config::{BoundariesConfig, Permissions, Rule}; use git2::Repository; use globwalk::Settings; use miette::{Diagnostic, NamedSource, Report, SourceSpan}; @@ -31,8 +31,9 @@ use turborepo_repository::package_graph::{PackageInfo, PackageName, PackageNode} use turborepo_ui::{color, ColorConfig, BOLD_GREEN, BOLD_RED}; use crate::{ - boundaries::{tags::ProcessedRulesMap, tsconfig::TsConfigLoader}, + boundaries::{imports::DependencyLocations, tags::ProcessedRulesMap, tsconfig::TsConfigLoader}, run::Run, + turbo_json::TurboJson, }; #[derive(Clone, Debug, Error, Diagnostic)] @@ -211,12 +212,11 @@ impl BoundariesResult { impl Run { pub async fn check_boundaries(&self) -> Result { - let package_tags = self.get_package_tags(); let rules_map = self.get_processed_rules_map(); let packages: Vec<_> = self.pkg_dep_graph().packages().collect(); let repo = Repository::discover(self.repo_root()).ok().map(Mutex::new); let mut result = BoundariesResult::default(); - + let global_implicit_dependencies = self.get_implicit_dependencies(&PackageName::Root); for (package_name, package_info) in packages { if !self.filtered_pkgs().contains(package_name) || matches!(package_name, PackageName::Root) @@ -228,8 +228,8 @@ impl Run { &repo, package_name, package_info, - &package_tags, &rules_map, + &global_implicit_dependencies, &mut result, ) .await?; @@ -251,6 +251,19 @@ impl Run { None } + pub fn get_implicit_dependencies(&self, pkg: &PackageName) -> HashMap> { + self.turbo_json_loader() + .load(pkg) + .ok() + .and_then(|turbo_json| turbo_json.boundaries.as_ref()) + .and_then(|boundaries| boundaries.implicit_dependencies.as_ref()) + .into_iter() + .flatten() + .flatten() + .map(|dep| dep.clone().split()) + .collect::>() + } + /// Either returns a list of errors and number of files checked or a single, /// fatal error async fn check_package( @@ -258,19 +271,29 @@ impl Run { repo: &Option>, package_name: &PackageName, package_info: &PackageInfo, - all_package_tags: &HashMap>>>, tag_rules: &Option, + global_implicit_dependencies: &HashMap>, result: &mut BoundariesResult, ) -> Result<(), Error> { - self.check_package_files(repo, package_name, package_info, result) - .await?; - - if let Some(current_package_tags) = all_package_tags.get(package_name) { + let implicit_dependencies = self.get_implicit_dependencies(package_name); + self.check_package_files( + repo, + package_name, + package_info, + implicit_dependencies, + global_implicit_dependencies, + result, + ) + .await?; + + if let Ok(TurboJson { + tags: Some(tags), .. + }) = self.turbo_json_loader().load(package_name) + { if let Some(tag_rules) = tag_rules { result.diagnostics.extend(self.check_package_tags( PackageNode::Workspace(package_name.clone()), - current_package_tags, - all_package_tags, + tags, tag_rules, )?); } else { @@ -297,6 +320,8 @@ impl Run { repo: &Option>, package_name: &PackageName, package_info: &PackageInfo, + implicit_dependencies: HashMap>, + global_implicit_dependencies: &HashMap>, result: &mut BoundariesResult, ) -> Result<(), Error> { let package_root = self.repo_root().resolve(package_info.package_path()); @@ -393,6 +418,14 @@ impl Run { // Visit the AST and find imports let mut finder = ImportFinder::default(); module.visit_with(&mut finder); + let dependency_locations = DependencyLocations { + internal_dependencies: &internal_dependencies, + package_json: &package_info.package_json, + implicit_dependencies: &implicit_dependencies, + global_implicit_dependencies, + unresolved_external_dependencies, + }; + for (import, span, import_type) in finder.imports() { self.check_import( &comments, @@ -406,9 +439,7 @@ impl Run { span, file_path, &file_content, - package_info, - &internal_dependencies, - unresolved_external_dependencies, + dependency_locations, &resolver, )?; } diff --git a/crates/turborepo-lib/src/boundaries/tags.rs b/crates/turborepo-lib/src/boundaries/tags.rs index 775b2c07b4002..b86a58aea9a4f 100644 --- a/crates/turborepo-lib/src/boundaries/tags.rs +++ b/crates/turborepo-lib/src/boundaries/tags.rs @@ -49,17 +49,25 @@ impl From for ProcessedPermissions { } impl Run { + pub fn load_package_tags(&self, pkg: &PackageName) -> Option<&Spanned>>> { + self.turbo_json_loader() + .load(pkg) + .ok() + .and_then(|turbo_json| turbo_json.tags.as_ref()) + } + pub(crate) fn get_package_tags(&self) -> HashMap>>> { let mut package_tags = HashMap::new(); - let turbo_json_loader = self.turbo_json_loader(); for (package, _) in self.pkg_dep_graph().packages() { if let Ok(TurboJson { tags: Some(tags), boundaries, .. - }) = turbo_json_loader.load(package) + }) = self.turbo_json_loader().load(package) { - if boundaries.is_some() && !matches!(package, PackageName::Root) { + if boundaries.as_ref().is_some_and(|b| b.tags.is_some()) + && !matches!(package, PackageName::Root) + { warn!( "Boundaries rules can only be defined in the root turbo.json. Any rules \ defined in a package's turbo.json will be ignored." @@ -163,7 +171,6 @@ impl Run { &self, pkg: PackageNode, current_package_tags: &Spanned>>, - all_package_tags: &HashMap>>>, tags_rules: &ProcessedRulesMap, ) -> Result, Error> { let mut diagnostics = Vec::new(); @@ -174,7 +181,9 @@ impl Run { if matches!(dependency, PackageNode::Root) { continue; } - let dependency_tags = all_package_tags.get(dependency.as_package_name()); + + let dependency_tags = self.load_package_tags(dependency.as_package_name()); + diagnostics.extend(self.validate_relation( pkg.as_package_name(), dependency.as_package_name(), @@ -190,7 +199,7 @@ impl Run { if matches!(dependent, PackageNode::Root) { continue; } - let dependent_tags = all_package_tags.get(dependent.as_package_name()); + let dependent_tags = self.load_package_tags(dependent.as_package_name()); diagnostics.extend(self.validate_relation( pkg.as_package_name(), dependent.as_package_name(), diff --git a/crates/turborepo-lib/src/turbo_json/mod.rs b/crates/turborepo-lib/src/turbo_json/mod.rs index c4ecd9368a3a4..a7e72f7065623 100644 --- a/crates/turborepo-lib/src/turbo_json/mod.rs +++ b/crates/turborepo-lib/src/turbo_json/mod.rs @@ -31,7 +31,7 @@ pub mod parser; pub use loader::TurboJsonLoader; -use crate::{boundaries::RootBoundariesConfig, config::UnnecessaryPackageTaskSyntaxError}; +use crate::{boundaries::BoundariesConfig, config::UnnecessaryPackageTaskSyntaxError}; #[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone, Deserializable)] #[serde(rename_all = "camelCase")] @@ -54,7 +54,7 @@ pub struct TurboJson { text: Option>, path: Option>, pub(crate) tags: Option>>>, - pub(crate) boundaries: Option>, + pub(crate) boundaries: Option>, pub(crate) extends: Spanned>, pub(crate) global_deps: Vec, pub(crate) global_env: Vec, @@ -153,7 +153,7 @@ pub struct RawTurboJson { pub tags: Option>>>, #[serde(skip_serializing_if = "Option::is_none")] - pub boundaries: Option>, + pub boundaries: Option>, #[deserializable(rename = "//")] #[serde(skip)] @@ -777,7 +777,7 @@ mod tests { use super::{RawTurboJson, SpacesJson, Spanned, TurboJson, UIMode}; use crate::{ - boundaries::RootBoundariesConfig, + boundaries::BoundariesConfig, cli::OutputLogsMode, run::task_id::TaskName, task_graph::{TaskDefinition, TaskOutputs}, @@ -816,15 +816,35 @@ mod tests { }"#, "tags and dependents" )] + #[test_case( + r#"{ + "implicitDependencies": ["my-package"], + }"#, + "implicit dependencies" + )] + #[test_case( + r#"{ + "implicitDependencies": ["my-package"], + "tags": { + "my-tag": { + "dependents": { + "allow": ["my-package"], + "deny": ["my-other-package"] + } + } + }, + }"#, + "implicit dependencies and tags" + )] fn test_deserialize_boundaries(json: &str, name: &str) { let deserialized_result = deserialize_from_json_str( json, JsonParserOptions::default().with_allow_comments(), "turbo.json", ); - let raw_task_definition: RootBoundariesConfig = + let raw_boundaries_config: BoundariesConfig = deserialized_result.into_deserialized().unwrap(); - insta::assert_json_snapshot!(name.replace(' ', "_"), raw_task_definition); + insta::assert_json_snapshot!(name.replace(' ', "_"), raw_boundaries_config); } #[test_case( diff --git a/crates/turborepo-lib/src/turbo_json/parser.rs b/crates/turborepo-lib/src/turbo_json/parser.rs index 57c2e787a86c3..4f4a95cc843d9 100644 --- a/crates/turborepo-lib/src/turbo_json/parser.rs +++ b/crates/turborepo-lib/src/turbo_json/parser.rs @@ -16,7 +16,7 @@ use turborepo_errors::{ParseDiagnostic, WithMetadata}; use turborepo_unescape::UnescapedString; use crate::{ - boundaries::{Permissions, RootBoundariesConfig, Rule}, + boundaries::{BoundariesConfig, Permissions, Rule}, run::task_id::TaskName, turbo_json::{Pipeline, RawTaskDefinition, RawTurboJson, Spanned}, }; @@ -155,7 +155,7 @@ impl WithMetadata for Pipeline { } } -impl WithMetadata for RootBoundariesConfig { +impl WithMetadata for BoundariesConfig { fn add_text(&mut self, text: Arc) { self.tags.add_text(text.clone()); if let Some(tags) = &mut self.tags { @@ -164,6 +164,12 @@ impl WithMetadata for RootBoundariesConfig { rule.value.add_text(text.clone()); } } + self.implicit_dependencies.add_text(text.clone()); + if let Some(implicit_dependencies) = &mut self.implicit_dependencies { + for dep in implicit_dependencies.as_inner_mut() { + dep.add_text(text.clone()); + } + } } fn add_path(&mut self, path: Arc) { @@ -174,6 +180,12 @@ impl WithMetadata for RootBoundariesConfig { rule.value.add_path(path.clone()); } } + self.implicit_dependencies.add_path(path.clone()); + if let Some(implicit_dependencies) = &mut self.implicit_dependencies { + for dep in implicit_dependencies.as_inner_mut() { + dep.add_path(path.clone()); + } + } } } diff --git a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__empty_boundaries.snap b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__empty_boundaries.snap index e4b2050155490..ebe28bcad8bf0 100644 --- a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__empty_boundaries.snap +++ b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__empty_boundaries.snap @@ -1,7 +1,5 @@ --- source: crates/turborepo-lib/src/turbo_json/mod.rs -expression: raw_task_definition +expression: raw_boundaries_config --- -{ - "tags": null -} +{} diff --git a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__empty_tags.snap b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__empty_tags.snap index a496ebdb0a319..5d0f7d879976f 100644 --- a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__empty_tags.snap +++ b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__empty_tags.snap @@ -1,6 +1,6 @@ --- source: crates/turborepo-lib/src/turbo_json/mod.rs -expression: raw_task_definition +expression: raw_boundaries_config --- { "tags": {} diff --git a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__implicit_dependencies.snap b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__implicit_dependencies.snap new file mode 100644 index 0000000000000..6dc4941c4b142 --- /dev/null +++ b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__implicit_dependencies.snap @@ -0,0 +1,9 @@ +--- +source: crates/turborepo-lib/src/turbo_json/mod.rs +expression: raw_boundaries_config +--- +{ + "implicit_dependencies": [ + "my-package" + ] +} diff --git a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__implicit_dependencies_and_tags.snap b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__implicit_dependencies_and_tags.snap new file mode 100644 index 0000000000000..bc368549fb0b3 --- /dev/null +++ b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__implicit_dependencies_and_tags.snap @@ -0,0 +1,22 @@ +--- +source: crates/turborepo-lib/src/turbo_json/mod.rs +expression: raw_boundaries_config +--- +{ + "tags": { + "my-tag": { + "dependencies": null, + "dependents": { + "allow": [ + "my-package" + ], + "deny": [ + "my-other-package" + ] + } + } + }, + "implicit_dependencies": [ + "my-package" + ] +} diff --git a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies.snap b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies.snap index 671335b532098..c40788bc0790d 100644 --- a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies.snap +++ b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies.snap @@ -1,6 +1,6 @@ --- source: crates/turborepo-lib/src/turbo_json/mod.rs -expression: raw_task_definition +expression: raw_boundaries_config --- { "tags": { diff --git a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies_2.snap b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies_2.snap index 28c4ef872b5cc..65403aefa91ec 100644 --- a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies_2.snap +++ b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependencies_2.snap @@ -1,6 +1,6 @@ --- source: crates/turborepo-lib/src/turbo_json/mod.rs -expression: raw_task_definition +expression: raw_boundaries_config --- { "tags": { diff --git a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependents.snap b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependents.snap index 4c0600b860a5c..83ae0c0da9076 100644 --- a/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependents.snap +++ b/crates/turborepo-lib/src/turbo_json/snapshots/turborepo_lib__turbo_json__tests__tags_and_dependents.snap @@ -1,6 +1,6 @@ --- source: crates/turborepo-lib/src/turbo_json/mod.rs -expression: raw_task_definition +expression: raw_boundaries_config --- { "tags": { diff --git a/turborepo-tests/integration/fixtures/boundaries/packages/module-package/my-module.mjs b/turborepo-tests/integration/fixtures/boundaries/packages/module-package/my-module.mjs index 5300c6eb7abe2..a0458a377bfa1 100644 --- a/turborepo-tests/integration/fixtures/boundaries/packages/module-package/my-module.mjs +++ b/turborepo-tests/integration/fixtures/boundaries/packages/module-package/my-module.mjs @@ -1 +1,3 @@ +import { anotherImplicitDependency } from "another-implicit-dependency"; + export const walkThePlank = "walk the plank matey"; diff --git a/turborepo-tests/integration/fixtures/boundaries/packages/utils/index.jsx b/turborepo-tests/integration/fixtures/boundaries/packages/utils/index.jsx index feaab5c7d7bef..3b8cf7ac5d23e 100644 --- a/turborepo-tests/integration/fixtures/boundaries/packages/utils/index.jsx +++ b/turborepo-tests/integration/fixtures/boundaries/packages/utils/index.jsx @@ -1 +1,3 @@ +import { ui } from "ui"; + export const ship = "Queen Anne"; diff --git a/turborepo-tests/integration/fixtures/boundaries/packages/utils/turbo.json b/turborepo-tests/integration/fixtures/boundaries/packages/utils/turbo.json new file mode 100644 index 0000000000000..ba830b2ba63bb --- /dev/null +++ b/turborepo-tests/integration/fixtures/boundaries/packages/utils/turbo.json @@ -0,0 +1,7 @@ +{ + "boundaries": { + "implicitDependencies": [ + "ui" + ] + } +} \ No newline at end of file diff --git a/turborepo-tests/integration/fixtures/boundaries/turbo.json b/turborepo-tests/integration/fixtures/boundaries/turbo.json index a5d3e78857a45..908fafaf954ff 100644 --- a/turborepo-tests/integration/fixtures/boundaries/turbo.json +++ b/turborepo-tests/integration/fixtures/boundaries/turbo.json @@ -11,6 +11,9 @@ ] } } - } + }, + "implicitDependencies": [ + "another-implicit-dependency" + ] } }