Skip to content
This repository was archived by the owner on Sep 17, 2023. It is now read-only.

Commit 2d6faa4

Browse files
committed
feat: only write tsconfig.json files when content changes
1 parent 72e3349 commit 2d6faa4

File tree

2 files changed

+99
-32
lines changed

2 files changed

+99
-32
lines changed

src/io.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ pub struct PackageManifest {
2525
pub extra_fields: Value,
2626
}
2727

28-
#[derive(Serialize)]
28+
#[derive(Serialize, Deserialize, PartialEq)]
2929
pub struct TypeScriptProjectReference {
3030
pub path: String,
3131
}
3232

33-
#[derive(Serialize)]
33+
#[derive(Serialize, PartialEq)]
3434
pub struct TypeScriptParentProjectReferences {
3535
pub files: Vec<String>,
3636
pub references: Vec<TypeScriptProjectReference>,

src/link.rs

Lines changed: 97 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
44

55
use pathdiff::diff_paths;
66

7-
use serde_json::{json, Value};
7+
use serde_json::Value;
88

99
use crate::io::{
1010
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
6363
}
6464
}
6565

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+
6671
// Create a tsconfig.json file in each parent directory to an internal package.
6772
// This permits us to build the monorepo from the top down.
6873
fn link_children_packages(
6974
opts: &crate::opts::Link,
7075
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+
7279
internal_package_manifest_files
7380
.iter()
7481
.map(|manifest_file| internal_package_relative_path(&opts.root, manifest_file))
@@ -77,14 +84,43 @@ fn link_children_packages(
7784
// Create the data structure representing child TypeScript project references
7885
.fold(HashMap::new(), key_children_by_parent)
7986
.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+
}
86120
})
87-
.collect::<Result<(), Box<dyn Error>>>()
121+
.collect::<Result<(), Box<dyn Error>>>()?;
122+
123+
Ok(is_exit_success)
88124
}
89125

90126
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>>(
119155
fn link_package_dependencies(
120156
opts: &crate::opts::Link,
121157
internal_package_manifest_files: &Vec<PathBuf>,
122-
) -> Result<(), Box<dyn Error>> {
158+
) -> Result<bool, Box<dyn Error>> {
123159
let internal_manifests = read_internal_package_manifests(internal_package_manifest_files)?;
124160
let package_directory_by_name =
125161
key_internal_package_directory_by_package_name(&opts.root, &internal_manifests);
@@ -134,9 +170,9 @@ fn link_package_dependencies(
134170
.unwrap_or(serde_json::Map::new())
135171
};
136172

137-
internal_package_manifest_files
173+
let tsconfig_diffs = internal_package_manifest_files
138174
.iter()
139-
.map(|manifest_file| -> Result<(), Box<dyn Error>> {
175+
.map(|manifest_file| -> Result<Option<(PathBuf, serde_json::Value)>, Box<dyn Error>> {
140176
let package_directory = manifest_file.parent().ok_or::<Box<dyn Error>>(
141177
String::from("Unexpected internal package in monorepo root").into(),
142178
)?;
@@ -148,7 +184,7 @@ fn link_package_dependencies(
148184
String::from("Failed to lookup package by manifest path").into(),
149185
)?;
150186

151-
let desired_project_references: serde_json::Value = {
187+
let desired_project_references: Vec<TypeScriptProjectReference> = {
152188
let mut deps = get_dependency_group(manifest, "dependencies")
153189
.iter()
154190
.chain(get_dependency_group(manifest, "devDependencies").iter())
@@ -162,35 +198,62 @@ fn link_package_dependencies(
162198
.collect::<Result<Vec<_>, _>>()?;
163199
deps.sort_unstable();
164200

165-
let deps_to_write = serde_json::Value::Array(deps
201+
let deps_to_write = deps
166202
.iter()
167203
.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+
}
171207
})
172-
.collect::<Vec<_>>());
208+
.collect::<Vec<_>>();
173209

174210
deps_to_write
175211
};
176212

177213
// 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(),
180219
);
181220
if !needs_update {
182-
return Ok(());
221+
return Ok(None);
183222
}
184223

185-
let new_tsconfig = tsconfig
224+
// Update the current tsconfig with the desired references
225+
tsconfig
186226
.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)?);
189229

190-
write_tsconfig(tsconfig_file, &tsconfig)
230+
Ok(Some((tsconfig_file, tsconfig)))
191231
})
192232
.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)
194257
}
195258

196259
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<(),
199262
get_internal_package_manifest_files(&opts.root, &lerna_manifest, &opts.ignore)
200263
.expect("Unable to enumerate internal package manifests");
201264

202-
link_children_packages(&opts, &internal_package_manifest_files)
265+
let is_children_link_success = link_children_packages(&opts, &internal_package_manifest_files)
203266
.expect("Unable to link children packages");
204-
link_package_dependencies(&opts, &internal_package_manifest_files)
205-
.expect("Unable to link internal package dependencies");
206267

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+
}
208275

209276
// STRETCH TODO: create `tsconfig.settings.json` files
210277

0 commit comments

Comments
 (0)