@@ -3,9 +3,7 @@ use std::collections::{HashMap, HashSet};
3
3
use itertools:: Itertools ;
4
4
use tracing:: warn;
5
5
use turbopath:: AbsoluteSystemPath ;
6
- use turborepo_micro_frontend:: {
7
- Config as MFEConfig , Error , DEFAULT_MICRO_FRONTENDS_CONFIG , MICRO_FRONTENDS_PACKAGES ,
8
- } ;
6
+ use turborepo_microfrontends:: { Config as MFEConfig , Error , MICROFRONTENDS_PACKAGES } ;
9
7
use turborepo_repository:: package_graph:: { PackageGraph , PackageName } ;
10
8
11
9
use crate :: {
@@ -15,58 +13,44 @@ use crate::{
15
13
} ;
16
14
17
15
#[ derive( Debug , Clone ) ]
18
- pub struct MicroFrontendsConfigs {
16
+ pub struct MicrofrontendsConfigs {
19
17
configs : HashMap < String , HashSet < TaskId < ' static > > > ,
18
+ config_filenames : HashMap < String , String > ,
20
19
mfe_package : Option < & ' static str > ,
21
20
}
22
21
23
- impl MicroFrontendsConfigs {
22
+ impl MicrofrontendsConfigs {
24
23
pub fn new (
25
24
repo_root : & AbsoluteSystemPath ,
26
25
package_graph : & PackageGraph ,
27
26
) -> Result < Option < Self > , Error > {
28
- let mut configs = HashMap :: new ( ) ;
29
- for ( package_name, package_info) in package_graph. packages ( ) {
30
- let config_path = repo_root
31
- . resolve ( package_info. package_path ( ) )
32
- . join_component ( DEFAULT_MICRO_FRONTENDS_CONFIG ) ;
33
- let Some ( config) = MFEConfig :: load ( & config_path) . or_else ( |err| {
34
- if matches ! ( err, turborepo_micro_frontend:: Error :: UnsupportedVersion ( _) ) {
35
- warn ! ( "Ignoring {config_path}: {err}" ) ;
36
- Ok ( None )
37
- } else {
38
- Err ( err)
39
- }
40
- } ) ?
41
- else {
42
- continue ;
43
- } ;
44
- let tasks = config
45
- . applications
46
- . iter ( )
47
- . map ( |( application, options) | {
48
- let dev_task = options. development . task . as_deref ( ) . unwrap_or ( "dev" ) ;
49
- TaskId :: new ( application, dev_task) . into_owned ( )
50
- } )
51
- . collect ( ) ;
52
- configs. insert ( package_name. to_string ( ) , tasks) ;
27
+ let PackageGraphResult {
28
+ configs,
29
+ config_filenames,
30
+ missing_default_apps,
31
+ unsupported_version,
32
+ mfe_package,
33
+ } = PackageGraphResult :: new ( package_graph. packages ( ) . map ( |( name, info) | {
34
+ (
35
+ name. as_str ( ) ,
36
+ MFEConfig :: load_from_dir ( & repo_root. resolve ( info. package_path ( ) ) ) ,
37
+ )
38
+ } ) ) ?;
39
+
40
+ for ( package, err) in unsupported_version {
41
+ warn ! ( "Ignoring {package}: {err}" ) ;
53
42
}
54
43
55
- let mfe_package = package_graph
56
- . packages ( )
57
- . map ( |( pkg, _) | pkg. as_str ( ) )
58
- . sorted ( )
59
- // We use `find_map` here instead of a simple `find` so we get the &'static str
60
- // instead of the &str tied to the lifetime of the package graph.
61
- . find_map ( |pkg| {
62
- MICRO_FRONTENDS_PACKAGES
63
- . iter ( )
64
- . find ( |static_pkg| pkg == * * static_pkg)
65
- } )
66
- . copied ( ) ;
44
+ if !missing_default_apps. is_empty ( ) {
45
+ warn ! (
46
+ "Missing default applications: {}" ,
47
+ missing_default_apps. join( ", " )
48
+ ) ;
49
+ }
67
50
68
51
Ok ( ( !configs. is_empty ( ) ) . then_some ( Self {
69
52
configs,
53
+ config_filenames,
70
54
mfe_package,
71
55
} ) )
72
56
}
@@ -89,6 +73,11 @@ impl MicroFrontendsConfigs {
89
73
. any ( |dev_tasks| dev_tasks. contains ( task_id) )
90
74
}
91
75
76
+ pub fn config_filename ( & self , package_name : & str ) -> Option < & str > {
77
+ let filename = self . config_filenames . get ( package_name) ?;
78
+ Some ( filename. as_str ( ) )
79
+ }
80
+
92
81
pub fn update_turbo_json (
93
82
& self ,
94
83
package_name : & PackageName ,
@@ -145,6 +134,74 @@ impl MicroFrontendsConfigs {
145
134
}
146
135
}
147
136
137
+ // Internal struct used to capture the results of checking the package graph
138
+ struct PackageGraphResult {
139
+ configs : HashMap < String , HashSet < TaskId < ' static > > > ,
140
+ config_filenames : HashMap < String , String > ,
141
+ missing_default_apps : Vec < String > ,
142
+ unsupported_version : Vec < ( String , String ) > ,
143
+ mfe_package : Option < & ' static str > ,
144
+ }
145
+
146
+ impl PackageGraphResult {
147
+ fn new < ' a > (
148
+ packages : impl Iterator < Item = ( & ' a str , Result < Option < MFEConfig > , Error > ) > ,
149
+ ) -> Result < Self , Error > {
150
+ let mut configs = HashMap :: new ( ) ;
151
+ let mut config_filenames = HashMap :: new ( ) ;
152
+ let mut referenced_default_apps = HashSet :: new ( ) ;
153
+ let mut unsupported_version = Vec :: new ( ) ;
154
+ let mut mfe_package = None ;
155
+ // We sort packages to ensure deterministic behavior
156
+ let sorted_packages = packages. sorted_by ( |( a, _) , ( b, _) | a. cmp ( b) ) ;
157
+ for ( package_name, config) in sorted_packages {
158
+ if let Some ( pkg) = MICROFRONTENDS_PACKAGES
159
+ . iter ( )
160
+ . find ( |static_pkg| package_name == * * static_pkg)
161
+ {
162
+ mfe_package = Some ( * pkg) ;
163
+ }
164
+
165
+ let Some ( config) = config. or_else ( |err| match err {
166
+ turborepo_microfrontends:: Error :: UnsupportedVersion ( _) => {
167
+ unsupported_version. push ( ( package_name. to_string ( ) , err. to_string ( ) ) ) ;
168
+ Ok ( None )
169
+ }
170
+ turborepo_microfrontends:: Error :: ChildConfig { reference } => {
171
+ referenced_default_apps. insert ( reference) ;
172
+ Ok ( None )
173
+ }
174
+ err => Err ( err) ,
175
+ } ) ?
176
+ else {
177
+ continue ;
178
+ } ;
179
+ let tasks = config
180
+ . development_tasks ( )
181
+ . map ( |( application, options) | {
182
+ let dev_task = options. unwrap_or ( "dev" ) ;
183
+ TaskId :: new ( application, dev_task) . into_owned ( )
184
+ } )
185
+ . collect ( ) ;
186
+ configs. insert ( package_name. to_string ( ) , tasks) ;
187
+ config_filenames. insert ( package_name. to_string ( ) , config. filename ( ) . to_owned ( ) ) ;
188
+ }
189
+ let default_apps_found = configs. keys ( ) . cloned ( ) . collect ( ) ;
190
+ let mut missing_default_apps = referenced_default_apps
191
+ . difference ( & default_apps_found)
192
+ . cloned ( )
193
+ . collect :: < Vec < _ > > ( ) ;
194
+ missing_default_apps. sort ( ) ;
195
+ Ok ( Self {
196
+ configs,
197
+ config_filenames,
198
+ missing_default_apps,
199
+ unsupported_version,
200
+ mfe_package,
201
+ } )
202
+ }
203
+ }
204
+
148
205
#[ derive( Debug , PartialEq , Eq ) ]
149
206
struct FindResult < ' a > {
150
207
dev : Option < TaskId < ' a > > ,
@@ -153,7 +210,11 @@ struct FindResult<'a> {
153
210
154
211
#[ cfg( test) ]
155
212
mod test {
213
+ use serde_json:: json;
156
214
use test_case:: test_case;
215
+ use turborepo_microfrontends:: {
216
+ MICROFRONTENDS_PACKAGE_EXTERNAL , MICROFRONTENDS_PACKAGE_INTERNAL ,
217
+ } ;
157
218
158
219
use super :: * ;
159
220
@@ -253,13 +314,112 @@ mod test {
253
314
"mfe-config-pkg" => [ "web#dev" , "docs#dev" ] ,
254
315
"mfe-web" => [ "mfe-web#dev" , "mfe-docs#serve" ]
255
316
) ;
256
- let mfe = MicroFrontendsConfigs {
317
+ let mfe = MicrofrontendsConfigs {
257
318
configs,
319
+ config_filenames : HashMap :: new ( ) ,
258
320
mfe_package : None ,
259
321
} ;
260
322
assert_eq ! (
261
323
mfe. package_turbo_json_update( & test. package_name( ) ) ,
262
324
test. expected( )
263
325
) ;
264
326
}
327
+
328
+ #[ test]
329
+ fn test_mfe_package_is_found ( ) {
330
+ let result = PackageGraphResult :: new (
331
+ vec ! [
332
+ // These should never be present in the same graph, but if for some reason they
333
+ // are, we defer to the external variant.
334
+ ( MICROFRONTENDS_PACKAGE_EXTERNAL , Ok ( None ) ) ,
335
+ ( MICROFRONTENDS_PACKAGE_INTERNAL , Ok ( None ) ) ,
336
+ ]
337
+ . into_iter ( ) ,
338
+ )
339
+ . unwrap ( ) ;
340
+ assert_eq ! ( result. mfe_package, Some ( MICROFRONTENDS_PACKAGE_EXTERNAL ) ) ;
341
+ }
342
+
343
+ #[ test]
344
+ fn test_no_mfe_package ( ) {
345
+ let result =
346
+ PackageGraphResult :: new ( vec ! [ ( "foo" , Ok ( None ) ) , ( "bar" , Ok ( None ) ) ] . into_iter ( ) )
347
+ . unwrap ( ) ;
348
+ assert_eq ! ( result. mfe_package, None ) ;
349
+ }
350
+
351
+ #[ test]
352
+ fn test_unsupported_versions_ignored ( ) {
353
+ let result = PackageGraphResult :: new (
354
+ vec ! [ ( "foo" , Err ( Error :: UnsupportedVersion ( "bad version" . into( ) ) ) ) ] . into_iter ( ) ,
355
+ )
356
+ . unwrap ( ) ;
357
+ assert_eq ! ( result. configs, HashMap :: new( ) ) ;
358
+ }
359
+
360
+ #[ test]
361
+ fn test_child_configs_with_missing_default ( ) {
362
+ let result = PackageGraphResult :: new (
363
+ vec ! [ (
364
+ "child" ,
365
+ Err ( Error :: ChildConfig {
366
+ reference: "main" . into( ) ,
367
+ } ) ,
368
+ ) ]
369
+ . into_iter ( ) ,
370
+ )
371
+ . unwrap ( ) ;
372
+ assert_eq ! ( result. configs, HashMap :: new( ) ) ;
373
+ assert_eq ! ( result. missing_default_apps, & [ "main" . to_string( ) ] ) ;
374
+ }
375
+
376
+ #[ test]
377
+ fn test_io_err_stops_traversal ( ) {
378
+ let result = PackageGraphResult :: new (
379
+ vec ! [
380
+ (
381
+ "a" ,
382
+ Err ( Error :: Io ( std:: io:: Error :: new(
383
+ std:: io:: ErrorKind :: Other ,
384
+ "something" ,
385
+ ) ) ) ,
386
+ ) ,
387
+ (
388
+ "b" ,
389
+ Err ( Error :: ChildConfig {
390
+ reference: "main" . into( ) ,
391
+ } ) ,
392
+ ) ,
393
+ ]
394
+ . into_iter ( ) ,
395
+ ) ;
396
+ assert ! ( result. is_err( ) ) ;
397
+ }
398
+
399
+ #[ test]
400
+ fn test_dev_task_collection ( ) {
401
+ let config = MFEConfig :: from_str (
402
+ & serde_json:: to_string_pretty ( & json ! ( {
403
+ "version" : "2" ,
404
+ "applications" : {
405
+ "web" : { } ,
406
+ "docs" : {
407
+ "development" : {
408
+ "task" : "serve"
409
+ }
410
+ }
411
+ }
412
+ } ) )
413
+ . unwrap ( ) ,
414
+ "something.txt" ,
415
+ )
416
+ . unwrap ( ) ;
417
+ let result = PackageGraphResult :: new ( vec ! [ ( "web" , Ok ( Some ( config) ) ) ] . into_iter ( ) ) . unwrap ( ) ;
418
+ assert_eq ! (
419
+ result. configs,
420
+ mfe_configs!(
421
+ "web" => [ "web#dev" , "docs#serve" ]
422
+ )
423
+ )
424
+ }
265
425
}
0 commit comments