@@ -63,6 +63,24 @@ fn find_inconsistencies(
6363 dep_map : & HashMap < String , DependencyVersion > ,
6464 turbo_config : Option < & RawTurboJson > ,
6565) -> HashMap < String , HashMap < String , Vec < String > > > {
66+ // Extract dependency configurations once to avoid borrowing issues
67+ let dep_configs: HashMap < String , ( Option < Vec < String > > , Option < Vec < String > > ) > =
68+ if let Some ( config) = turbo_config {
69+ if let Some ( deps) = & config. dependencies {
70+ deps. iter ( )
71+ . map ( |( name, config) | {
72+ let ignore = config. ignore . clone ( ) ;
73+ let pin_to_version = config. pin_to_version . clone ( ) ;
74+ ( name. clone ( ) , ( ignore, pin_to_version) )
75+ } )
76+ . collect ( )
77+ } else {
78+ HashMap :: new ( )
79+ }
80+ } else {
81+ HashMap :: new ( )
82+ } ;
83+
6684 let mut result: HashMap < String , HashMap < String , Vec < String > > > = HashMap :: new ( ) ;
6785
6886 // First, group all dependencies by their base name (without version suffix)
@@ -80,44 +98,102 @@ fn find_inconsistencies(
8098 . extend ( dep_info. locations . clone ( ) ) ;
8199 }
82100
101+ // Make a copy of the names to process to avoid borrowing issues
102+ let dep_names: Vec < String > = result. keys ( ) . cloned ( ) . collect ( ) ;
103+
83104 // Process dependencies according to rules
84- result. retain ( |name, versions| {
85- // Check if this dependency has a rule in the turbo.json config
86- if let Some ( config) = turbo_config. and_then ( |c| c. dependencies . as_ref ( ) ) {
87- if let Some ( dep_config) = config. get ( name) {
88- // Skip if no packages are specified in the rule
89- if dep_config. packages . is_empty ( ) {
90- return versions. len ( ) > 1 ; // Default behavior: only show
91- // multiple versions
92- }
105+ for name in dep_names {
106+ if let Some ( versions) = result. get_mut ( & name) {
107+ let mut should_keep = true ;
108+ let mut has_config = false ;
109+
110+ // Check if this dependency has a rule in the extracted configs
111+ if let Some ( ( ignore_opt, pin_opt) ) = dep_configs. get ( & name) {
112+ has_config = true ;
113+
114+ // First, check if there are "ignore" patterns
115+ if let Some ( ignore_patterns) = ignore_opt {
116+ // Create a new version map with ignored locations filtered out
117+ let mut filtered_versions: HashMap < String , Vec < String > > = HashMap :: new ( ) ;
118+ let mut all_ignored = true ;
119+
120+ // For each version, filter out the ignored locations
121+ for ( version, locations) in versions. iter ( ) {
122+ let non_ignored_locations: Vec < String > = locations
123+ . iter ( )
124+ . filter ( |location| {
125+ !matches_any_package_pattern ( location, ignore_patterns)
126+ } )
127+ . cloned ( )
128+ . collect ( ) ;
93129
94- // Check if the rule should be applied by matching package patterns
95- let has_matching_packages = versions . values ( ) . any ( |locations| {
96- locations
97- . iter ( )
98- . any ( |location| matches_any_package_pattern ( location , & dep_config . packages ) )
99- } ) ;
130+ // If we have any non-ignored locations, keep this version
131+ if !non_ignored_locations . is_empty ( ) {
132+ all_ignored = false ;
133+ filtered_versions . insert ( version . clone ( ) , non_ignored_locations ) ;
134+ }
135+ }
100136
101- if has_matching_packages {
102- // Rule applies to at least one package
103- if dep_config. ignore {
104- // If dependency is ignored, don't include in inconsistencies
105- return false ;
137+ // If all locations were ignored, mark for removal
138+ if all_ignored {
139+ should_keep = false ;
140+ } else {
141+ // Replace the original versions with filtered ones
142+ * versions = filtered_versions;
106143 }
144+ }
145+
146+ // Second, check for pinned versions
147+ if should_keep {
148+ if let Some ( pin_patterns) = pin_opt {
149+ // For each version and its locations, check if it matches any pin pattern
150+ let mut has_pins = false ;
151+ let mut has_violations = false ;
152+
153+ for ( version, locations) in versions. iter ( ) {
154+ // Collect all pin patterns that apply to any package in this version
155+ let matching_pin_locations: Vec < _ > = locations
156+ . iter ( )
157+ . filter ( |location| {
158+ matches_any_package_pattern ( location, pin_patterns)
159+ } )
160+ . collect ( ) ;
161+
162+ // If any locations match, check if versions all match what's expected
163+ if !matching_pin_locations. is_empty ( ) {
164+ has_pins = true ;
165+
166+ // For simplicity in this implementation, we'll assume all pinned
167+ // packages should use the same version (first pattern)
168+ // In a more complete implementation, we'd need to match each
169+ // location to its specific pin
170+ // pattern and version
171+ let expected_version = & pin_patterns[ 0 ] ;
172+ if version != expected_version {
173+ has_violations = true ;
174+ }
175+ }
176+ }
107177
108- if let Some ( pin_version) = & dep_config. pin_to_version {
109- // For pinned dependencies, check if ANY version doesn't match the pinned
110- // version If we find any non-matching version,
111- // report it as an inconsistency
112- return versions. keys ( ) . any ( |v| v != pin_version) ;
178+ // Update should_keep based on pin analysis
179+ if has_pins {
180+ should_keep = has_violations;
181+ }
113182 }
114183 }
115184 }
116- }
117185
118- // Default behavior: only show if there are multiple versions
119- versions. len ( ) > 1
120- } ) ;
186+ // If we don't have a specific config, use the default behavior
187+ if !has_config {
188+ should_keep = versions. len ( ) > 1 ;
189+ }
190+
191+ // If we shouldn't keep this dependency, remove it from the result
192+ if !should_keep {
193+ result. remove ( & name) ;
194+ }
195+ }
196+ }
121197
122198 result
123199}
@@ -134,21 +210,33 @@ fn matches_any_package_pattern(location: &str, patterns: &[String]) -> bool {
134210
135211 // Check if any pattern matches
136212 patterns. iter ( ) . any ( |pattern| {
137- // Special case: "**" or "*" means all packages
138- if pattern == "**" || pattern == "*" {
139- return true ;
140- }
141-
142- // Use glob pattern matching
143- if let Ok ( glob_pattern) = Pattern :: new ( pattern) {
144- glob_pattern. matches ( package_name)
213+ // Handle negation patterns
214+ if let Some ( negated_pattern) = pattern. strip_prefix ( '!' ) {
215+ // This is a negation pattern, so we should return false if it matches
216+ !matches_single_pattern ( package_name, negated_pattern)
145217 } else {
146- // If pattern is invalid, just do an exact match
147- pattern == package_name
218+ // Normal pattern
219+ matches_single_pattern ( package_name, pattern )
148220 }
149221 } )
150222}
151223
224+ // Helper to match a single pattern without negation handling
225+ fn matches_single_pattern ( package_name : & str , pattern : & str ) -> bool {
226+ // Special case: "**" or "*" means all packages
227+ if pattern == "**" || pattern == "*" {
228+ return true ;
229+ }
230+
231+ // Use glob pattern matching
232+ if let Ok ( glob_pattern) = Pattern :: new ( pattern) {
233+ glob_pattern. matches ( package_name)
234+ } else {
235+ // If pattern is invalid, just do an exact match
236+ pattern == package_name
237+ }
238+ }
239+
152240pub async fn run (
153241 base : CommandBase ,
154242 only : Option < cli:: DependencyFilter > ,
@@ -209,10 +297,11 @@ pub async fn run(
209297 if !dependencies. is_empty ( ) {
210298 // Silently collect pinned deps info without printing
211299 for ( dep_name, dep_config) in dependencies {
212- if !dep_config. packages . is_empty ( ) {
213- if let Some ( version) = & dep_config. pin_to_version {
214- // Store pinned deps for better error messages
215- pinned_deps. insert ( dep_name. clone ( ) , version. clone ( ) ) ;
300+ if let Some ( pin_patterns) = & dep_config. pin_to_version {
301+ if !pin_patterns. is_empty ( ) {
302+ // Store pinned deps for better error messages - use first version for
303+ // now
304+ pinned_deps. insert ( dep_name. clone ( ) , pin_patterns[ 0 ] . clone ( ) ) ;
216305 }
217306 }
218307 }
@@ -366,13 +455,17 @@ fn enforce_pinned_dependencies(
366455 color_config : turborepo_ui:: ColorConfig ,
367456) {
368457 for ( dep_name, config) in dependencies {
369- // Due to validation, we know only one of ignore or pin_to_version is set
370- if let Some ( pinned_version ) = & config. pin_to_version {
371- // Skip if no packages are specified
372- if config . packages . is_empty ( ) {
458+ // Only proceed if we have pin_to_version patterns
459+ if let Some ( pin_patterns ) = & config. pin_to_version {
460+ // Skip if no pin patterns are specified
461+ if pin_patterns . is_empty ( ) {
373462 continue ;
374463 }
375464
465+ // For simplicity, we'll use the first pin pattern's version
466+ // In a more sophisticated implementation, we'd need to handle multiple versions
467+ let pinned_version = & pin_patterns[ 0 ] ;
468+
376469 cprintln ! (
377470 color_config,
378471 BOLD ,
@@ -391,14 +484,21 @@ fn enforce_pinned_dependencies(
391484 let base_name = extract_base_name ( key) ;
392485
393486 if base_name == dep_name {
394- // Check if any location matches our package patterns
395- let matching_locations: Vec < String > = dep_info
487+ // Get locations that match our pin patterns
488+ let mut matching_locations: Vec < String > = dep_info
396489 . locations
397490 . iter ( )
398- . filter ( |location| matches_any_package_pattern ( location, & config . packages ) )
491+ . filter ( |location| matches_any_package_pattern ( location, pin_patterns ) )
399492 . cloned ( )
400493 . collect ( ) ;
401494
495+ // If we have ignore patterns, filter out any locations that match them
496+ if let Some ( ignore_patterns) = & config. ignore {
497+ matching_locations. retain ( |location| {
498+ !matches_any_package_pattern ( location, ignore_patterns)
499+ } ) ;
500+ }
501+
402502 if !matching_locations. is_empty ( ) {
403503 // Add to locations that need to be consolidated
404504 all_locations. extend ( matching_locations) ;
@@ -853,9 +953,8 @@ mod tests {
853953 dependencies. insert (
854954 "react" . to_string ( ) ,
855955 DependencyConfig {
856- packages : vec ! [ "*" . to_string( ) ] ,
857- ignore : false ,
858- pin_to_version : Some ( "18.0.0" . to_string ( ) ) ,
956+ ignore : None ,
957+ pin_to_version : Some ( vec ! [ "*" . to_string( ) , "18.0.0" . to_string( ) ] ) ,
859958 } ,
860959 ) ;
861960
@@ -1177,9 +1276,8 @@ mod tests {
11771276 dep_config_map. insert (
11781277 "react" . to_string ( ) ,
11791278 DependencyConfig {
1180- packages : vec ! [ "apps/*" . to_string( ) ] , // Only match packages in apps directory
1181- pin_to_version : Some ( "18.0.0" . to_string ( ) ) ,
1182- ignore : false ,
1279+ ignore : None ,
1280+ pin_to_version : Some ( vec ! [ "apps/*" . to_string( ) , "18.0.0" . to_string( ) ] ) ,
11831281 } ,
11841282 ) ;
11851283
0 commit comments