nestrs-cli-rs 0.1.0

Rust port of the Nest CLI for the nestrs organization.
Documentation
//! Upstream source: `../nest-cli/lib/compiler/swc/swc-compiler.ts`.

use std::fs;
use std::path::{Path, PathBuf};

use serde_json::Value;

use super::super::defaults::swc_defaults::{
    SourceCompilerOptions, SwcDefaults, swc_defaults_factory,
};
pub use super::super::{SwcCliOptions, SwcCompilerPlan};

pub type SwcCompilerExtras = SwcCompilerPlan;

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SwcCompiler;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SwcRunPlan {
    pub defaults: SwcDefaults,
    pub swcrc_path: PathBuf,
    pub swcrc: Option<Value>,
    pub cli_options: SwcCliOptions,
    pub watch_files: bool,
    pub type_check: bool,
}

impl SwcCompiler {
    pub fn plan_run(
        cwd: impl AsRef<Path>,
        ts_options: Option<&SourceCompilerOptions>,
        configuration: Option<&crate::configuration::Configuration>,
        swcrc_file_path: Option<&Path>,
        watch: bool,
        type_check: bool,
    ) -> Result<SwcRunPlan, String> {
        let defaults = swc_defaults_factory(ts_options, configuration);
        let swcrc_path = swcrc_file_path
            .map(Path::to_path_buf)
            .unwrap_or_else(|| PathBuf::from(".swcrc"));
        let swcrc = get_swcrc_file_content_if_exists(cwd.as_ref(), swcrc_file_path)?;
        let mut cli_options = defaults.cli_options.clone();
        cli_options.watch = watch;
        Ok(SwcRunPlan {
            defaults,
            swcrc_path,
            swcrc,
            cli_options,
            watch_files: watch,
            type_check,
        })
    }

    pub fn load_swc_cli_binary() -> Result<&'static str, String> {
        Err("Failed to load \"@swc/cli\" and/or \"@swc/core\" required packages. Please, make sure to install them as development dependencies.".to_string())
    }

    pub fn deep_merge(left: Value, right: Value) -> Value {
        match (left, right) {
            (Value::Object(mut left), Value::Object(right)) => {
                for (key, value) in right {
                    let merged = left
                        .remove(&key)
                        .map(|left_value| Self::deep_merge(left_value, value.clone()))
                        .unwrap_or(value);
                    left.insert(key, merged);
                }
                Value::Object(left)
            }
            (Value::Array(mut left), Value::Array(right)) => {
                for (index, value) in right.into_iter().enumerate() {
                    match left.get_mut(index) {
                        Some(left_value) => {
                            let merged = Self::deep_merge(left_value.clone(), value);
                            *left_value = merged;
                        }
                        None => left.push(value),
                    }
                }
                Value::Array(left)
            }
            (_, right) => right,
        }
    }
}

pub fn get_swcrc_file_content_if_exists(
    cwd: &Path,
    swcrc_file_path: Option<&Path>,
) -> Result<Option<Value>, String> {
    let explicit = swcrc_file_path.is_some();
    let path = cwd.join(swcrc_file_path.unwrap_or_else(|| Path::new(".swcrc")));
    match fs::read_to_string(&path) {
        Ok(content) => serde_json::from_str(&content)
            .map(Some)
            .map_err(|error| format!("Failed to load \"{}\". Please, check if the file exists and is valid JSON. {error}", path.display())),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound && !explicit => Ok(None),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Err(format!(
            "Failed to load \"{}\". Please, check if the file exists and is valid JSON.",
            path.display()
        )),
        Err(error) => Err(format!("Failed to load \"{}\": {error}", path.display())),
    }
}