diff --git a/Cargo.lock b/Cargo.lock index b817469229c5d..eb3a70c080949 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6541,6 +6541,7 @@ dependencies = [ "biome_diagnostics", "biome_json_parser", "biome_json_syntax", + "insta", "pretty_assertions", "serde", "serde_json", diff --git a/crates/turborepo-lib/src/micro_frontends.rs b/crates/turborepo-lib/src/micro_frontends.rs index 8d5891e9725e6..16553d2cede6e 100644 --- a/crates/turborepo-lib/src/micro_frontends.rs +++ b/crates/turborepo-lib/src/micro_frontends.rs @@ -1,5 +1,6 @@ use std::collections::{HashMap, HashSet}; +use tracing::warn; use turbopath::AbsoluteSystemPath; use turborepo_micro_frontend::{Config as MFEConfig, Error, DEFAULT_MICRO_FRONTENDS_CONFIG}; use turborepo_repository::package_graph::PackageGraph; @@ -21,7 +22,15 @@ impl MicroFrontendsConfigs { let config_path = repo_root .resolve(package_info.package_path()) .join_component(DEFAULT_MICRO_FRONTENDS_CONFIG); - let Some(config) = MFEConfig::load(&config_path)? else { + let Some(config) = MFEConfig::load(&config_path).or_else(|err| { + if matches!(err, turborepo_micro_frontend::Error::UnsupportedVersion(_)) { + warn!("Ignoring {config_path}: {err}"); + Ok(None) + } else { + Err(err) + } + })? + else { continue; }; let tasks = config diff --git a/crates/turborepo-micro-frontend/Cargo.toml b/crates/turborepo-micro-frontend/Cargo.toml index 3c20340f1da70..d08ccd33d425f 100644 --- a/crates/turborepo-micro-frontend/Cargo.toml +++ b/crates/turborepo-micro-frontend/Cargo.toml @@ -17,6 +17,7 @@ turbopath = { workspace = true } turborepo-errors = { workspace = true } [dev-dependencies] +insta = { workspace = true } pretty_assertions = { workspace = true } [lints] diff --git a/crates/turborepo-micro-frontend/src/error.rs b/crates/turborepo-micro-frontend/src/error.rs index f13eba267362b..36354aeec2fb4 100644 --- a/crates/turborepo-micro-frontend/src/error.rs +++ b/crates/turborepo-micro-frontend/src/error.rs @@ -1,11 +1,18 @@ use turborepo_errors::ParseDiagnostic; +use crate::SUPPORTED_VERSIONS; + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Unable to read configuration file: {0}")] Io(#[from] std::io::Error), #[error("Unable to parse JSON: {0}")] JsonParse(String), + #[error( + "Unsupported micro-frontends configuration version: {0}. Supported versions: \ + {SUPPORTED_VERSIONS:?}" + )] + UnsupportedVersion(String), } impl Error { diff --git a/crates/turborepo-micro-frontend/src/lib.rs b/crates/turborepo-micro-frontend/src/lib.rs index e250d7779088a..4714e05922b6e 100644 --- a/crates/turborepo-micro-frontend/src/lib.rs +++ b/crates/turborepo-micro-frontend/src/lib.rs @@ -15,6 +15,7 @@ use turbopath::AbsoluteSystemPath; pub const DEFAULT_MICRO_FRONTENDS_CONFIG: &str = "micro-frontends.jsonc"; pub const MICRO_FRONTENDS_PACKAGES: &[&str] = [MICRO_FRONTENDS_PACKAGE_INTERNAL].as_slice(); pub const MICRO_FRONTENDS_PACKAGE_INTERNAL: &str = "@vercel/micro-frontends-internal"; +pub const SUPPORTED_VERSIONS: &[&str] = ["1"].as_slice(); /// The minimal amount of information Turborepo needs to correctly start a local /// proxy server for microfrontends @@ -31,11 +32,28 @@ impl Config { let Some(contents) = config_path.read_existing_to_string()? else { return Ok(None); }; - let config = Self::from_str(&contents, config_path.as_str()).map_err(Error::biome_error)?; + let config = Self::from_str(&contents, config_path.as_str())?; Ok(Some(config)) } - pub fn from_str(input: &str, source: &str) -> Result> { + pub fn from_str(input: &str, source: &str) -> Result { + #[derive(Deserializable, Default)] + struct VersionOnly { + version: String, + } + let (version_only, _errs) = biome_deserialize::json::deserialize_from_json_str( + input, + JsonParserOptions::default().with_allow_comments(), + source, + ) + .consume(); + // If parsing just the version fails, fallback to full schema to provide better + // error message + if let Some(VersionOnly { version }) = version_only { + if !SUPPORTED_VERSIONS.contains(&version.as_str()) { + return Err(Error::UnsupportedVersion(version)); + } + } let (config, errs) = biome_deserialize::json::deserialize_from_json_str( input, JsonParserOptions::default().with_allow_comments(), @@ -45,7 +63,7 @@ impl Config { if let Some(config) = config { Ok(config) } else { - Err(errs) + Err(Error::biome_error(errs)) } } } @@ -63,6 +81,8 @@ pub struct Development { #[cfg(test)] mod test { + use insta::assert_snapshot; + use super::*; #[test] @@ -71,4 +91,11 @@ mod test { let example_config = Config::from_str(input, "something.json"); assert!(example_config.is_ok()); } + + #[test] + fn test_unsupported_version() { + let input = r#"{"version": "yolo"}"#; + let err = Config::from_str(input, "something.json").unwrap_err(); + assert_snapshot!(err, @r###"Unsupported micro-frontends configuration version: yolo. Supported versions: ["1"]"###); + } }