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

fix: provide better error message for tsconfig parsing #198

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 2 additions & 27 deletions src/io.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,11 @@
use anyhow::Context;
use std::fs::File;
use std::io::{BufWriter, Read, Write};
use std::io::Read;
use std::path::Path;

use anyhow::Result;

use serde::{Deserialize, Serialize};

// REFACTOR: this belongs in a different file
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct TypescriptProjectReference {
pub path: String,
}

// REFACTOR: this belongs in a different file
#[derive(Serialize, PartialEq, Eq)]
pub struct TypescriptParentProjectReference {
pub files: Vec<String>,
pub references: Vec<TypescriptProjectReference>,
}

pub fn write_project_references<P: AsRef<Path>>(
path: P,
references: &TypescriptParentProjectReference,
) -> Result<()> {
let file = File::create(&path)?;
let mut writer = BufWriter::new(file);
serde_json::to_writer_pretty(&mut writer, references)?;
writer.write_all(b"\n")?;
writer.flush()?;
Ok(())
}
use serde::Deserialize;

pub(crate) fn read_json_from_file<T>(filename: &Path) -> Result<T>
where
Expand Down
56 changes: 20 additions & 36 deletions src/link.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use std::collections::HashMap;
use std::path::PathBuf;

use anyhow::{anyhow, bail, Result};
use anyhow::{bail, Result};

use pathdiff::diff_paths;

use crate::configuration_file::ConfigurationFile;
use crate::io::{
write_project_references, TypescriptParentProjectReference, TypescriptProjectReference,
};
use crate::monorepo_manifest::MonorepoManifest;
use crate::opts;
use crate::package_manifest::PackageManifest;
use crate::typescript_config::TypescriptConfig;
use crate::typescript_config::{
TypescriptConfig, TypescriptParentProjectReference, TypescriptProjectReference,
};

fn key_children_by_parent(
mut accumulator: HashMap<PathBuf, Vec<String>>,
Expand All @@ -35,17 +34,14 @@ fn key_children_by_parent(
accumulator
}

fn create_project_references(mut children: Vec<String>) -> TypescriptParentProjectReference {
fn create_project_references(mut children: Vec<String>) -> Vec<TypescriptProjectReference> {
// Sort the TypeScript project references for deterministic file contents.
// This minimizes diffs since the tsconfig.json files are stored in version control.
children.sort_unstable();
TypescriptParentProjectReference {
files: Vec::new(),
references: children
.into_iter()
.map(|path| TypescriptProjectReference { path })
.collect(),
}
children
.into_iter()
.map(|path| TypescriptProjectReference { path })
.collect()
}

fn vecs_match<T: PartialEq>(a: &[T], b: &[T]) -> bool {
Expand All @@ -54,7 +50,7 @@ fn vecs_match<T: PartialEq>(a: &[T], b: &[T]) -> bool {
}

// Create a tsconfig.json file in each parent directory to an internal package.
// This permits us to build the monorepo from the top down.
// This permits us to compile the monorepo from the top down.
fn link_children_packages(opts: &opts::Link, lerna_manifest: &MonorepoManifest) -> Result<bool> {
let mut is_exit_success = true;

Expand All @@ -66,19 +62,10 @@ fn link_children_packages(opts: &opts::Link, lerna_manifest: &MonorepoManifest)
.try_for_each(|(directory, children)| -> Result<()> {
let desired_project_references = create_project_references(children);
let tsconfig_filename = opts.root.join(&directory).join("tsconfig.json");
let tsconfig = TypescriptConfig::from_directory(&opts.root, &directory)?;
let current_project_references = tsconfig
.contents
.get("references")
.map(|value| {
serde_json::from_value::<Vec<TypescriptProjectReference>>(value.clone())
.expect("Value starting as JSON should be serializable as JSON")
})
.unwrap_or_default();
let needs_update = !vecs_match(
&current_project_references,
&desired_project_references.references,
);
let mut tsconfig =
TypescriptParentProjectReference::from_directory(&opts.root, &directory)?;
let current_project_references = tsconfig.contents.references;
let needs_update = !current_project_references.eq(&desired_project_references);
if !needs_update {
return Ok(());
}
Expand All @@ -92,7 +79,8 @@ fn link_children_packages(opts: &opts::Link, lerna_manifest: &MonorepoManifest)
println!("{}", serialized);
Ok(())
} else {
write_project_references(tsconfig_filename, &desired_project_references)
tsconfig.contents.references = desired_project_references;
tsconfig.write()
}
})?;

Expand Down Expand Up @@ -150,14 +138,10 @@ fn link_package_dependencies(opts: &opts::Link, lerna_manifest: &MonorepoManifes
}

// Update the current tsconfig with the desired references
tsconfig
.contents
.as_object_mut()
.ok_or_else(|| anyhow!("Expected tsconfig.json to contain an Object"))?
.insert(
String::from("references"),
serde_json::to_value(desired_project_references)?,
);
tsconfig.contents.insert(
String::from("references"),
serde_json::to_value(desired_project_references)?,
);

Ok(Some(tsconfig))
},
Expand Down
83 changes: 81 additions & 2 deletions src/typescript_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,94 @@ use std::fs::File;
use std::io::{BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};

use anyhow::Result;
use anyhow::{Context, Result};
use indoc::formatdoc;
use serde::{Deserialize, Serialize};

use crate::configuration_file::ConfigurationFile;
use crate::io::read_json_from_file;

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct TypescriptProjectReference {
pub path: String,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct TypescriptParentProjectReferenceFile {
/// This list is expected to be empty, but must be present to satisfy the
/// TypeScript compiler.
#[serde(default)]
pub files: Vec<String>,
#[serde(default)]
pub references: Vec<TypescriptProjectReference>,
}

pub struct TypescriptParentProjectReference {
monorepo_root: PathBuf,
directory: PathBuf,
pub contents: TypescriptParentProjectReferenceFile,
}

impl ConfigurationFile<TypescriptParentProjectReference> for TypescriptParentProjectReference {
const FILENAME: &'static str = "tsconfig.json";

fn from_directory(
monorepo_root: &Path,
directory: &Path,
) -> Result<TypescriptParentProjectReference> {
let filename = monorepo_root.join(directory).join(Self::FILENAME);
let manifest_contents: TypescriptParentProjectReferenceFile =
read_json_from_file(&filename).with_context(|| {
formatdoc!(
"
Unexpected contents in {:?}

I'm trying to parse the following property and value out
of this tsconfig.json file:

- references: {{ path: string }}[]

and the following value, if present:

- files: string[]
",
filename
)
})?;
Ok(TypescriptParentProjectReference {
monorepo_root: monorepo_root.to_owned(),
directory: directory.to_owned(),
contents: manifest_contents,
})
}

fn directory(&self) -> PathBuf {
self.directory.to_owned()
}

fn path(&self) -> PathBuf {
self.directory.join(Self::FILENAME)
}

fn write(&self) -> Result<()> {
let file = File::create(
self.monorepo_root
.join(&self.directory)
.join(Self::FILENAME),
)?;
let mut writer = BufWriter::new(file);
serde_json::to_writer_pretty(&mut writer, &self.contents)?;
writer.write_all(b"\n")?;
writer.flush()?;
Ok(())
}
}

pub struct TypescriptConfig {
// FIXME: how many times do we need to duplicate this value?
monorepo_root: PathBuf,
directory: PathBuf,
pub contents: serde_json::Value,
pub contents: serde_json::Map<String, serde_json::Value>,
}

impl ConfigurationFile<TypescriptConfig> for TypescriptConfig {
Expand Down