diff --git a/crates/turborepo-lib/src/microfrontends.rs b/crates/turborepo-lib/src/microfrontends.rs index 77f0917fc1705..48a6f96120d5b 100644 --- a/crates/turborepo-lib/src/microfrontends.rs +++ b/crates/turborepo-lib/src/microfrontends.rs @@ -20,9 +20,22 @@ pub struct MicrofrontendsConfigs { } impl MicrofrontendsConfigs { - pub fn new( + /// Constructs a collection of configurations from disk + pub fn from_disk( repo_root: &AbsoluteSystemPath, package_graph: &PackageGraph, + ) -> Result, Error> { + Self::from_configs(package_graph.packages().map(|(name, info)| { + ( + name.as_str(), + MFEConfig::load_from_dir(&repo_root.resolve(info.package_path())), + ) + })) + } + + /// Constructs a collection of configurations from a list of configurations + pub fn from_configs<'a>( + configs: impl Iterator, Error>)>, ) -> Result, Error> { let PackageGraphResult { configs, @@ -30,12 +43,7 @@ impl MicrofrontendsConfigs { missing_default_apps, unsupported_version, mfe_package, - } = PackageGraphResult::new(package_graph.packages().map(|(name, info)| { - ( - name.as_str(), - MFEConfig::load_from_dir(&repo_root.resolve(info.package_path())), - ) - }))?; + } = PackageGraphResult::new(configs)?; for (package, err) in unsupported_version { warn!("Ignoring {package}: {err}"); diff --git a/crates/turborepo-lib/src/run/builder.rs b/crates/turborepo-lib/src/run/builder.rs index 0919834ea41a7..e621ec08e52e7 100644 --- a/crates/turborepo-lib/src/run/builder.rs +++ b/crates/turborepo-lib/src/run/builder.rs @@ -371,7 +371,8 @@ impl RunBuilder { repo_telemetry.track_package_manager(pkg_dep_graph.package_manager().name().to_string()); repo_telemetry.track_size(pkg_dep_graph.len()); run_telemetry.track_run_type(self.opts.run_opts.dry_run.is_some()); - let micro_frontend_configs = MicrofrontendsConfigs::new(&self.repo_root, &pkg_dep_graph)?; + let micro_frontend_configs = + MicrofrontendsConfigs::from_disk(&self.repo_root, &pkg_dep_graph)?; let scm = scm.await.expect("detecting scm panicked"); let async_cache = AsyncCache::new( @@ -398,10 +399,14 @@ impl RunBuilder { self.root_turbo_json_path.clone(), root_package_json.clone(), ) - } else if self.allow_no_turbo_json && !self.root_turbo_json_path.exists() { + } else if !self.root_turbo_json_path.exists() && + // Infer a turbo.json if allowing no turbo.json is explicitly allowed or if MFE configs are discovered + (self.allow_no_turbo_json || micro_frontend_configs.is_some()) + { TurboJsonLoader::workspace_no_turbo_json( self.repo_root.clone(), pkg_dep_graph.packages(), + micro_frontend_configs.clone(), ) } else if let Some(micro_frontends) = µ_frontend_configs { TurboJsonLoader::workspace_with_microfrontends( diff --git a/crates/turborepo-lib/src/turbo_json/loader.rs b/crates/turborepo-lib/src/turbo_json/loader.rs index caab0d837b939..b296c36bdacb0 100644 --- a/crates/turborepo-lib/src/turbo_json/loader.rs +++ b/crates/turborepo-lib/src/turbo_json/loader.rs @@ -40,6 +40,7 @@ enum Strategy { WorkspaceNoTurboJson { // Map of package names to their scripts packages: HashMap>, + microfrontends_configs: Option, }, TaskAccess { root_turbo_json: AbsoluteSystemPathBuf, @@ -89,12 +90,16 @@ impl TurboJsonLoader { pub fn workspace_no_turbo_json<'a>( repo_root: AbsoluteSystemPathBuf, packages: impl Iterator, + microfrontends_configs: Option, ) -> Self { let packages = workspace_package_scripts(packages); Self { repo_root, cache: HashMap::new(), - strategy: Strategy::WorkspaceNoTurboJson { packages }, + strategy: Strategy::WorkspaceNoTurboJson { + packages, + microfrontends_configs, + }, } } @@ -182,12 +187,20 @@ impl TurboJsonLoader { turbo_json } } - Strategy::WorkspaceNoTurboJson { packages } => { + Strategy::WorkspaceNoTurboJson { + packages, + microfrontends_configs, + } => { let script_names = packages.get(package).ok_or(Error::NoTurboJSON)?; if matches!(package, PackageName::Root) { root_turbo_json_from_scripts(script_names) } else { - workspace_turbo_json_from_scripts(script_names) + let turbo_json = workspace_turbo_json_from_scripts(script_names); + if let Some(mfe_configs) = microfrontends_configs { + mfe_configs.update_turbo_json(package, turbo_json) + } else { + turbo_json + } } } Strategy::TaskAccess { @@ -382,6 +395,7 @@ mod test { use anyhow::Result; use tempfile::tempdir; use test_case::test_case; + use turborepo_unescape::UnescapedString; use super::*; use crate::{task_graph::TaskDefinition, turbo_json::CONFIG_FILE}; @@ -680,7 +694,10 @@ mod test { let mut loader = TurboJsonLoader { repo_root: repo_root.to_owned(), cache: HashMap::new(), - strategy: Strategy::WorkspaceNoTurboJson { packages }, + strategy: Strategy::WorkspaceNoTurboJson { + packages, + microfrontends_configs: None, + }, }; { @@ -714,4 +731,92 @@ mod test { let goose_err = loader.load(&PackageName::from("goose")).unwrap_err(); assert!(matches!(goose_err, Error::NoTurboJSON)); } + + #[test] + fn test_no_turbo_json_with_mfe() { + let root_dir = tempdir().unwrap(); + let repo_root = AbsoluteSystemPath::from_std_path(root_dir.path()).unwrap(); + let packages = vec![ + (PackageName::Root, vec![]), + ( + PackageName::from("web"), + vec!["dev".to_owned(), "build".to_owned()], + ), + ( + PackageName::from("docs"), + vec!["dev".to_owned(), "build".to_owned()], + ), + ] + .into_iter() + .collect(); + + let microfrontends_configs = MicrofrontendsConfigs::from_configs( + vec![ + ( + "web", + turborepo_microfrontends::Config::from_str( + r#"{"version": "2", "applications": {"web": {}, "docs": {}}}"#, + "mfe.json", + ) + .map(Some), + ), + ( + "docs", + Err(turborepo_microfrontends::Error::ChildConfig { + reference: "web".into(), + }), + ), + ] + .into_iter(), + ) + .unwrap(); + + let mut loader = TurboJsonLoader { + repo_root: repo_root.to_owned(), + cache: HashMap::new(), + strategy: Strategy::WorkspaceNoTurboJson { + packages, + microfrontends_configs, + }, + }; + + { + let web_json = loader.load(&PackageName::from("web")).unwrap(); + for task_name in ["dev", "build", "proxy"] { + if let Some(def) = web_json.tasks.get(&TaskName::from(task_name)) { + assert_eq!( + def.cache.as_ref().map(|cache| *cache.as_inner()), + Some(false) + ); + // Make sure proxy is in there + if task_name == "dev" { + assert_eq!( + def.siblings.as_ref().unwrap().first().unwrap().as_inner(), + &UnescapedString::from("web#proxy") + ) + } + } else { + panic!("didn't find {task_name}"); + } + } + } + + { + let docs_json = loader.load(&PackageName::from("docs")).unwrap(); + for task_name in ["dev"] { + if let Some(def) = docs_json.tasks.get(&TaskName::from(task_name)) { + assert_eq!( + def.cache.as_ref().map(|cache| *cache.as_inner()), + Some(false) + ); + assert_eq!( + def.siblings.as_ref().unwrap().first().unwrap().as_inner(), + &UnescapedString::from("web#proxy") + ) + } else { + panic!("didn't find {task_name}"); + } + } + } + } }