diff --git a/Cargo.lock b/Cargo.lock index 68b47f84f8566..c2104ab4754fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6471,6 +6471,16 @@ dependencies = [ name = "turborepo-fixed-map" version = "0.1.0" +[[package]] +name = "turborepo-frameworks" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "test-case", + "turborepo-repository", +] + [[package]] name = "turborepo-fs" version = "0.1.0" @@ -6611,6 +6621,7 @@ dependencies = [ "turborepo-errors", "turborepo-filewatch", "turborepo-fixed-map", + "turborepo-frameworks", "turborepo-fs", "turborepo-graph-utils", "turborepo-lockfiles", diff --git a/Cargo.toml b/Cargo.toml index 9d6e4cf9955c3..bc8a1b5be3fc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ turborepo-ci = { path = "crates/turborepo-ci" } turborepo-env = { path = "crates/turborepo-env" } turborepo-errors = { path = "crates/turborepo-errors" } turborepo-fixed-map = { path = "crates/turborepo-fixed-map" } +turborepo-frameworks = { path = "crates/turborepo-frameworks" } turborepo-fs = { path = "crates/turborepo-fs" } turborepo-lib = { path = "crates/turborepo-lib", default-features = false } turborepo-lockfiles = { path = "crates/turborepo-lockfiles" } diff --git a/crates/turborepo-frameworks/Cargo.toml b/crates/turborepo-frameworks/Cargo.toml new file mode 100644 index 0000000000000..d206a0273e852 --- /dev/null +++ b/crates/turborepo-frameworks/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "turborepo-frameworks" +version = "0.1.0" +edition = "2024" +license = "MIT" + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +turborepo-repository = { workspace = true } + +[dev-dependencies] +test-case = { workspace = true } + +[lints] +workspace = true diff --git a/crates/turborepo-lib/src/framework.rs b/crates/turborepo-frameworks/src/lib.rs similarity index 91% rename from crates/turborepo-lib/src/framework.rs rename to crates/turborepo-frameworks/src/lib.rs index 9b2f2da2cbe59..7296a46e00adf 100644 --- a/crates/turborepo-lib/src/framework.rs +++ b/crates/turborepo-frameworks/src/lib.rs @@ -34,14 +34,18 @@ struct EnvConditional { #[derive(Debug, PartialEq, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Framework { - slug: String, + slug: Slug, env_wildcards: Vec, env_conditionals: Option>, dependency_match: Matcher, } +#[derive(Debug, PartialEq, Clone, Deserialize)] +#[serde(transparent)] +pub struct Slug(String); + impl Framework { - pub fn slug(&self) -> String { + pub fn slug(&self) -> Slug { self.slug.clone() } @@ -99,6 +103,26 @@ impl Matcher { } } +impl Slug { + pub fn as_str(&self) -> &str { + &self.0 + } + + pub fn framework(&self) -> &Framework { + let frameworks = get_frameworks(); + frameworks + .iter() + .find(|framework| framework.slug.as_str() == self.as_str()) + .expect("slug is only constructed via deserialization") + } +} + +impl std::fmt::Display for Slug { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + pub fn infer_framework(workspace: &PackageInfo, is_monorepo: bool) -> Option<&Framework> { let frameworks = get_frameworks(); @@ -114,12 +138,12 @@ mod tests { use test_case::test_case; use turborepo_repository::{package_graph::PackageInfo, package_json::PackageJson}; - use crate::framework::{get_frameworks, infer_framework, Framework}; + use super::*; fn get_framework_by_slug(slug: &str) -> &Framework { get_frameworks() .iter() - .find(|framework| framework.slug == slug) + .find(|framework| framework.slug.as_str() == slug) .expect("framework not found") } @@ -316,8 +340,8 @@ mod tests { let mut framework = get_framework_by_slug("nextjs").clone(); if let Some(env_conditionals) = framework.env_conditionals.as_mut() { - env_conditionals.push(crate::framework::EnvConditional { - when: crate::framework::EnvConditionKey { + env_conditionals.push(EnvConditional { + when: EnvConditionKey { key: "ANOTHER_CONDITION".to_string(), value: Some("true".to_string()), }, @@ -344,4 +368,11 @@ mod tests { met" ); } + + #[test] + fn test_framework_slug_roundtrip() { + for framework in get_frameworks() { + assert_eq!(framework, framework.slug().framework()); + } + } } diff --git a/crates/turborepo-lib/Cargo.toml b/crates/turborepo-lib/Cargo.toml index fca5e0107daef..090b3303b46b7 100644 --- a/crates/turborepo-lib/Cargo.toml +++ b/crates/turborepo-lib/Cargo.toml @@ -133,6 +133,7 @@ turborepo-env = { workspace = true } turborepo-errors = { workspace = true } turborepo-filewatch = { path = "../turborepo-filewatch" } turborepo-fixed-map = { workspace = true } +turborepo-frameworks = { workspace = true } turborepo-fs = { path = "../turborepo-fs" } turborepo-graph-utils = { path = "../turborepo-graph-utils" } turborepo-lockfiles = { workspace = true } diff --git a/crates/turborepo-lib/src/lib.rs b/crates/turborepo-lib/src/lib.rs index 2f5f45de79d80..b9e4b46c20fc8 100644 --- a/crates/turborepo-lib/src/lib.rs +++ b/crates/turborepo-lib/src/lib.rs @@ -21,7 +21,6 @@ mod diagnostics; mod engine; mod boundaries; -mod framework; mod gitignore; mod hash; mod microfrontends; diff --git a/crates/turborepo-lib/src/run/summary/task_factory.rs b/crates/turborepo-lib/src/run/summary/task_factory.rs index 4f412fd09fe85..6efcc794045f5 100644 --- a/crates/turborepo-lib/src/run/summary/task_factory.rs +++ b/crates/turborepo-lib/src/run/summary/task_factory.rs @@ -125,7 +125,11 @@ impl<'a> TaskSummaryFactory<'a> { .expanded_outputs(task_id) .unwrap_or_default(); - let framework = self.hash_tracker.framework(task_id).unwrap_or_default(); + let framework = self + .hash_tracker + .framework(task_id) + .map(|framework| framework.to_string()) + .unwrap_or_default(); let hash = self .hash_tracker .hash(task_id) diff --git a/crates/turborepo-lib/src/task_hash.rs b/crates/turborepo-lib/src/task_hash.rs index 1d0d36ae28a7c..984ebafcb51da 100644 --- a/crates/turborepo-lib/src/task_hash.rs +++ b/crates/turborepo-lib/src/task_hash.rs @@ -10,6 +10,7 @@ use tracing::{debug, Span}; use turbopath::{AbsoluteSystemPath, AnchoredSystemPath, AnchoredSystemPathBuf}; use turborepo_cache::CacheHitMetadata; use turborepo_env::{BySource, DetailedMap, EnvironmentVariableMap}; +use turborepo_frameworks::{infer_framework, Slug as FrameworkSlug}; use turborepo_repository::package_graph::{PackageInfo, PackageName}; use turborepo_scm::SCM; use turborepo_telemetry::events::{ @@ -19,7 +20,6 @@ use turborepo_telemetry::events::{ use crate::{ cli::EnvMode, engine::TaskNode, - framework::infer_framework, hash::{FileHashes, LockFilePackages, TaskHashable, TurboHash}, opts::RunOpts, run::task_id::TaskId, @@ -228,7 +228,7 @@ pub struct TaskHashTrackerState { package_task_env_vars: HashMap, DetailedMap>, package_task_hashes: HashMap, String>, #[serde(skip)] - package_task_framework: HashMap, String>, + package_task_framework: HashMap, FrameworkSlug>, #[serde(skip)] package_task_outputs: HashMap, Vec>, #[serde(skip)] @@ -297,9 +297,9 @@ impl<'a> TaskHasher<'a> { framework.slug(), framework.env(self.env_at_execution_start) ); - telemetry.track_framework(framework.slug()); + telemetry.track_framework(framework.slug().to_string()); }); - let framework_slug = framework.map(|f| f.slug().to_string()); + let framework_slug = framework.map(|f| f.slug()); let env_vars = if let Some(framework) = framework { let mut computed_wildcards = framework.env(self.env_at_execution_start); @@ -570,7 +570,7 @@ impl TaskHashTracker { task_id: TaskId<'static>, env_vars: DetailedMap, hash: String, - framework_slug: Option, + framework_slug: Option, ) { let mut state = self.state.lock().expect("hash tracker mutex poisoned"); state @@ -589,7 +589,7 @@ impl TaskHashTracker { state.package_task_env_vars.get(task_id).cloned() } - pub fn framework(&self, task_id: &TaskId) -> Option { + pub fn framework(&self, task_id: &TaskId) -> Option { let state = self.state.lock().expect("hash tracker mutex poisoned"); state.package_task_framework.get(task_id).cloned() }