@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
4
4
5
5
use pathdiff:: diff_paths;
6
6
7
- use serde_json:: { json , Value } ;
7
+ use serde_json:: Value ;
8
8
9
9
use crate :: io:: {
10
10
get_internal_package_manifest_files, read_internal_package_manifests, read_lerna_manifest,
@@ -63,12 +63,19 @@ fn create_project_references(children: &mut Vec<String>) -> TypeScriptParentProj
63
63
}
64
64
}
65
65
66
+ fn vecs_match < T : PartialEq > ( a : & Vec < T > , b : & Vec < T > ) -> bool {
67
+ let matching = a. iter ( ) . zip ( b. iter ( ) ) . filter ( |& ( a, b) | a == b) . count ( ) ;
68
+ matching == a. len ( ) && matching == b. len ( )
69
+ }
70
+
66
71
// Create a tsconfig.json file in each parent directory to an internal package.
67
72
// This permits us to build the monorepo from the top down.
68
73
fn link_children_packages (
69
74
opts : & crate :: opts:: Link ,
70
75
internal_package_manifest_files : & Vec < PathBuf > ,
71
- ) -> Result < ( ) , Box < dyn Error > > {
76
+ ) -> Result < bool , Box < dyn Error > > {
77
+ let mut is_exit_success = true ;
78
+
72
79
internal_package_manifest_files
73
80
. iter ( )
74
81
. map ( |manifest_file| internal_package_relative_path ( & opts. root , manifest_file) )
@@ -77,14 +84,43 @@ fn link_children_packages(
77
84
// Create the data structure representing child TypeScript project references
78
85
. fold ( HashMap :: new ( ) , key_children_by_parent)
79
86
. iter_mut ( )
80
- . map ( |( directory, children) | {
81
- // TODO: implement --check
82
- write_project_references (
83
- opts. root . join ( directory) . join ( "tsconfig.json" ) ,
84
- & create_project_references ( children) ,
85
- )
87
+ . map ( |( directory, children) | -> Result < ( ) , Box < dyn Error > > {
88
+ let desired_project_references = create_project_references ( children) ;
89
+ let tsconfig_filename = opts. root . join ( directory) . join ( "tsconfig.json" ) ;
90
+ let current_project_references = read_tsconfig ( & tsconfig_filename)
91
+ . map ( |contents| {
92
+ contents
93
+ . get ( "references" )
94
+ . map ( |value| {
95
+ serde_json:: from_value :: < Vec < TypeScriptProjectReference > > ( value. clone ( ) )
96
+ . expect ( "Value starting as json should be serializable" )
97
+ } )
98
+ . unwrap_or_default ( )
99
+ } )
100
+ . unwrap_or_default ( ) ;
101
+ let needs_update = !vecs_match (
102
+ & current_project_references,
103
+ & desired_project_references. references ,
104
+ ) ;
105
+ if !needs_update {
106
+ return Ok ( ( ) ) ;
107
+ }
108
+ if opts. check_only {
109
+ is_exit_success = false ;
110
+ let serialized = serde_json:: to_string_pretty ( & desired_project_references) ?;
111
+ println ! (
112
+ "File has out-of-date project references: {:?}, expecting:" ,
113
+ tsconfig_filename
114
+ ) ;
115
+ println ! ( "{}" , serialized) ;
116
+ Ok ( ( ) )
117
+ } else {
118
+ write_project_references ( tsconfig_filename, & desired_project_references)
119
+ }
86
120
} )
87
- . collect :: < Result < ( ) , Box < dyn Error > > > ( )
121
+ . collect :: < Result < ( ) , Box < dyn Error > > > ( ) ?;
122
+
123
+ Ok ( is_exit_success)
88
124
}
89
125
90
126
fn tsconfig_filename < P : AsRef < Path > > ( manifest_file : P ) -> Result < PathBuf , Box < dyn Error > > {
@@ -119,7 +155,7 @@ fn key_internal_package_directory_by_package_name<P: AsRef<Path>>(
119
155
fn link_package_dependencies (
120
156
opts : & crate :: opts:: Link ,
121
157
internal_package_manifest_files : & Vec < PathBuf > ,
122
- ) -> Result < ( ) , Box < dyn Error > > {
158
+ ) -> Result < bool , Box < dyn Error > > {
123
159
let internal_manifests = read_internal_package_manifests ( internal_package_manifest_files) ?;
124
160
let package_directory_by_name =
125
161
key_internal_package_directory_by_package_name ( & opts. root , & internal_manifests) ;
@@ -134,9 +170,9 @@ fn link_package_dependencies(
134
170
. unwrap_or ( serde_json:: Map :: new ( ) )
135
171
} ;
136
172
137
- internal_package_manifest_files
173
+ let tsconfig_diffs = internal_package_manifest_files
138
174
. iter ( )
139
- . map ( |manifest_file| -> Result < ( ) , Box < dyn Error > > {
175
+ . map ( |manifest_file| -> Result < Option < ( PathBuf , serde_json :: Value ) > , Box < dyn Error > > {
140
176
let package_directory = manifest_file. parent ( ) . ok_or :: < Box < dyn Error > > (
141
177
String :: from ( "Unexpected internal package in monorepo root" ) . into ( ) ,
142
178
) ?;
@@ -148,7 +184,7 @@ fn link_package_dependencies(
148
184
String :: from ( "Failed to lookup package by manifest path" ) . into ( ) ,
149
185
) ?;
150
186
151
- let desired_project_references: serde_json :: Value = {
187
+ let desired_project_references: Vec < TypeScriptProjectReference > = {
152
188
let mut deps = get_dependency_group ( manifest, "dependencies" )
153
189
. iter ( )
154
190
. chain ( get_dependency_group ( manifest, "devDependencies" ) . iter ( ) )
@@ -162,35 +198,62 @@ fn link_package_dependencies(
162
198
. collect :: < Result < Vec < _ > , _ > > ( ) ?;
163
199
deps. sort_unstable ( ) ;
164
200
165
- let deps_to_write = serde_json :: Value :: Array ( deps
201
+ let deps_to_write = deps
166
202
. iter ( )
167
203
. map ( |dep| {
168
- json ! ( {
169
- " path" : dep. to_str( ) . expect( "Path not valid UTF-8 encoded" ) . to_string( )
170
- } )
204
+ TypeScriptProjectReference {
205
+ path : dep. to_str ( ) . expect ( "Path not valid UTF-8 encoded" ) . to_string ( )
206
+ }
171
207
} )
172
- . collect :: < Vec < _ > > ( ) ) ;
208
+ . collect :: < Vec < _ > > ( ) ;
173
209
174
210
deps_to_write
175
211
} ;
176
212
177
213
// Compare the current references against the desired references
178
- let needs_update = !desired_project_references. eq (
179
- tsconfig. get ( "references" ) . unwrap_or ( & serde_json:: Value :: Array ( vec ! [ ] ) )
214
+ let needs_update = !vecs_match (
215
+ & desired_project_references,
216
+ & tsconfig. get ( "references" )
217
+ . map ( |value| serde_json:: from_value :: < Vec < TypeScriptProjectReference > > ( value. clone ( ) ) . expect ( "Value starting as json should be serializable" ) )
218
+ . unwrap_or_default ( ) ,
180
219
) ;
181
220
if !needs_update {
182
- return Ok ( ( ) ) ;
221
+ return Ok ( None ) ;
183
222
}
184
223
185
- let new_tsconfig = tsconfig
224
+ // Update the current tsconfig with the desired references
225
+ tsconfig
186
226
. as_object_mut ( )
187
- . ok_or :: < Box < dyn Error > > ( String :: from ( "Expected tsconfig.json to contain an Object" ) . into ( ) ) ?;
188
- new_tsconfig . insert ( String :: from ( "references" ) , desired_project_references) ;
227
+ . ok_or :: < Box < dyn Error > > ( String :: from ( "Expected tsconfig.json to contain an Object" ) . into ( ) ) ?
228
+ . insert ( String :: from ( "references" ) , serde_json :: to_value ( desired_project_references) ? ) ;
189
229
190
- write_tsconfig ( tsconfig_file, & tsconfig)
230
+ Ok ( Some ( ( tsconfig_file, tsconfig) ) )
191
231
} )
192
232
. collect :: < Result < Vec < _ > , _ > > ( ) ?;
193
- Ok ( ( ) )
233
+
234
+ let mut is_exit_success = true ;
235
+
236
+ // take action on the computed diffs
237
+ tsconfig_diffs
238
+ . iter ( )
239
+ . filter_map ( |update| update. as_ref ( ) )
240
+ . map ( |( tsconfig_file, contents) | -> Result < ( ) , Box < dyn Error > > {
241
+ if opts. check_only {
242
+ is_exit_success = false ;
243
+ let serialized = serde_json:: to_string_pretty ( contents) ?;
244
+ println ! (
245
+ "File has out-of-date project references: {:?}, expecting:" ,
246
+ tsconfig_file
247
+ ) ;
248
+ println ! ( "{}" , serialized) ;
249
+ Ok ( ( ) )
250
+ } else {
251
+ write_tsconfig ( tsconfig_file, contents)
252
+ }
253
+ } )
254
+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
255
+
256
+ Ok ( is_exit_success)
194
257
}
195
258
196
259
pub fn link_typescript_project_references ( opts : crate :: opts:: Link ) -> Result < ( ) , Box < dyn Error > > {
@@ -199,12 +262,16 @@ pub fn link_typescript_project_references(opts: crate::opts::Link) -> Result<(),
199
262
get_internal_package_manifest_files ( & opts. root , & lerna_manifest, & opts. ignore )
200
263
. expect ( "Unable to enumerate internal package manifests" ) ;
201
264
202
- link_children_packages ( & opts, & internal_package_manifest_files)
265
+ let is_children_link_success = link_children_packages ( & opts, & internal_package_manifest_files)
203
266
. expect ( "Unable to link children packages" ) ;
204
- link_package_dependencies ( & opts, & internal_package_manifest_files)
205
- . expect ( "Unable to link internal package dependencies" ) ;
206
267
207
- // TODO: implement --check
268
+ let is_dependencies_link_success =
269
+ link_package_dependencies ( & opts, & internal_package_manifest_files)
270
+ . expect ( "Unable to link internal package dependencies" ) ;
271
+
272
+ if opts. check_only && !( is_children_link_success && is_dependencies_link_success) {
273
+ return Err ( "Found out-of-date project references" ) ?;
274
+ }
208
275
209
276
// STRETCH TODO: create `tsconfig.settings.json` files
210
277
0 commit comments