From 7cb26de80794462bc2c1296c4493c0e272dc282d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3man=20Joost?= Date: Fri, 1 Nov 2024 16:10:50 +1000 Subject: [PATCH] fix: honour handleTransparentWorkspaces setting in Yarn/Berry --- .../src/package_graph/builder.rs | 27 ++++++-- .../src/package_graph/dep_splitter.rs | 4 +- .../src/package_graph/mod.rs | 1 + .../src/package_graph/yarnrc.rs | 63 +++++++++++++++++++ 4 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 crates/turborepo-repository/src/package_graph/yarnrc.rs diff --git a/crates/turborepo-repository/src/package_graph/builder.rs b/crates/turborepo-repository/src/package_graph/builder.rs index 1075189b75171..9c25470fca39b 100644 --- a/crates/turborepo-repository/src/package_graph/builder.rs +++ b/crates/turborepo-repository/src/package_graph/builder.rs @@ -10,8 +10,8 @@ use turborepo_graph_utils as graph; use turborepo_lockfiles::Lockfile; use super::{ - dep_splitter::DependencySplitter, npmrc::NpmRc, PackageGraph, PackageInfo, PackageName, - PackageNode, + dep_splitter::DependencySplitter, npmrc::NpmRc, yarnrc::YarnRc, PackageGraph, PackageInfo, + PackageName, PackageNode, }; use crate::{ discovery::{ @@ -362,6 +362,17 @@ impl<'a, T: PackageDiscovery> BuildState<'a, ResolvedWorkspaces, T> { } _ => None, }; + let yarnrc_path = self.repo_root.join_component(".yarnrc.yml"); + let yarnrc = match package_manager { + PackageManager::Berry => { + // HOME? + match yarnrc_path.read_existing_to_string().ok().flatten() { + Some(contents) => YarnRc::from_reader(contents.as_bytes()).ok(), + None => None, + } + } + _ => None, + }; let split_deps = self .workspaces .iter() @@ -375,6 +386,7 @@ impl<'a, T: PackageDiscovery> BuildState<'a, ResolvedWorkspaces, T> { &self.workspaces, package_manager, npmrc.as_ref(), + yarnrc.as_ref(), entry.package_json.all_dependencies(), ), ) @@ -567,6 +579,7 @@ impl Dependencies { workspaces: &HashMap, package_manager: &PackageManager, npmrc: Option<&NpmRc>, + yarnrc: Option<&YarnRc>, dependencies: I, ) -> Self { let resolved_workspace_json_path = repo_root.resolve(workspace_json_path); @@ -575,8 +588,14 @@ impl Dependencies { .expect("package.json path should have parent"); let mut internal = HashSet::new(); let mut external = BTreeMap::new(); - let splitter = - DependencySplitter::new(repo_root, workspace_dir, workspaces, package_manager, npmrc); + let splitter = DependencySplitter::new( + repo_root, + workspace_dir, + workspaces, + package_manager, + npmrc, + yarnrc, + ); for (name, version) in dependencies.into_iter() { if let Some(workspace) = splitter.is_internal(name, version) { internal.insert(workspace); diff --git a/crates/turborepo-repository/src/package_graph/dep_splitter.rs b/crates/turborepo-repository/src/package_graph/dep_splitter.rs index 1a50cd6d5c35c..dc33a9482e4df 100644 --- a/crates/turborepo-repository/src/package_graph/dep_splitter.rs +++ b/crates/turborepo-repository/src/package_graph/dep_splitter.rs @@ -5,7 +5,7 @@ use turbopath::{ RelativeUnixPathBuf, }; -use super::{npmrc::NpmRc, PackageInfo, PackageName}; +use super::{npmrc::NpmRc, yarnrc::YarnRc, PackageInfo, PackageName}; use crate::package_manager::PackageManager; pub struct DependencySplitter<'a> { @@ -22,6 +22,7 @@ impl<'a> DependencySplitter<'a> { workspaces: &'a HashMap, package_manager: &PackageManager, npmrc: Option<&'a NpmRc>, + yarnrc: Option<&'a YarnRc>, ) -> Self { Self { repo_root, @@ -29,6 +30,7 @@ impl<'a> DependencySplitter<'a> { workspaces, link_workspace_packages: npmrc .and_then(|npmrc| npmrc.link_workspace_packages) + .or_else(|| yarnrc.map(|yarnrc| yarnrc.enableTransparentWorkspaces)) .unwrap_or(!matches!(package_manager, PackageManager::Pnpm9)), } } diff --git a/crates/turborepo-repository/src/package_graph/mod.rs b/crates/turborepo-repository/src/package_graph/mod.rs index 988c8850cad0f..2efe956db5712 100644 --- a/crates/turborepo-repository/src/package_graph/mod.rs +++ b/crates/turborepo-repository/src/package_graph/mod.rs @@ -20,6 +20,7 @@ use crate::{ pub mod builder; mod dep_splitter; mod npmrc; +mod yarnrc; pub use builder::{Error, PackageGraphBuilder}; diff --git a/crates/turborepo-repository/src/package_graph/yarnrc.rs b/crates/turborepo-repository/src/package_graph/yarnrc.rs new file mode 100644 index 0000000000000..61db22478f9fd --- /dev/null +++ b/crates/turborepo-repository/src/package_graph/yarnrc.rs @@ -0,0 +1,63 @@ +use std::io; + +use serde::Deserialize; +use serde_yaml; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("encountered error parsing yarnrc.yml: {0}")] + SerdeYaml(#[from] serde_yaml::Error), +} + +/// A yarnrc.yaml file representing settings affecting the package graph. +#[allow(non_snake_case)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] +pub struct YarnRc { + /// Used by Yarn(Berry) as `enableTransparentWorkspaces`. + /// When true, treats local workspaces that match a package name + /// and semver range as correct match resulting in turbo including + /// the package in the dependency graph + pub enableTransparentWorkspaces: bool, +} + +impl Default for YarnRc { + fn default() -> YarnRc { + YarnRc { + enableTransparentWorkspaces: true, + } + } +} + +impl YarnRc { + pub fn from_reader(mut reader: impl io::Read) -> Result { + let config: YarnRc = serde_yaml::from_reader(&mut reader)?; + Ok(config) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse_empty_yarnrc() { + let empty = YarnRc::from_reader(b"".as_slice()).unwrap(); + assert_eq!( + empty, + YarnRc { + enableTransparentWorkspaces: true + } + ); + } + + #[test] + fn test_parses_transparent_workspaces() { + let empty = YarnRc::from_reader(b"enableTransparentWorkspaces: false".as_slice()).unwrap(); + assert_eq!( + empty, + YarnRc { + enableTransparentWorkspaces: false + } + ); + } +}