nestrs-cli-rs 0.1.0

Rust port of the Nest CLI for the nestrs organization.
Documentation
//! Rust-native compiler plugins are handled by Rust crates, so legacy transformer
//! plugin declarations are recorded as unresolved instead of being emulated through Node.

use std::path::PathBuf;

use serde_json::Value;

use crate::configuration::Plugin;

pub const PLUGIN_ENTRY_FILENAME: &str = "plugin";

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct NestCompilerPlugin {
    pub name: String,
    pub resolved_path: PathBuf,
    pub options: serde_json::Map<String, Value>,
    pub has_before_hook: bool,
    pub has_after_hook: bool,
    pub has_after_declarations_hook: bool,
    pub has_readonly_visitor: bool,
    pub path_to_source: Option<PathBuf>,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MultiNestCompilerPlugins {
    pub before_hooks: Vec<NestCompilerPlugin>,
    pub after_hooks: Vec<NestCompilerPlugin>,
    pub after_declarations_hooks: Vec<NestCompilerPlugin>,
    pub readonly_visitors: Vec<NestCompilerPlugin>,
    pub unresolved_plugins: Vec<String>,
}

impl MultiNestCompilerPlugins {
    pub fn is_any_plugin_registered(&self) -> bool {
        !self.before_hooks.is_empty()
            || !self.after_hooks.is_empty()
            || !self.after_declarations_hooks.is_empty()
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PluginsLoader {
    cwd: PathBuf,
    module_paths: Vec<PathBuf>,
}

impl Default for PluginsLoader {
    fn default() -> Self {
        Self::new(std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
    }
}

impl PluginsLoader {
    pub fn new(cwd: impl Into<PathBuf>) -> Self {
        let cwd = cwd.into();
        let module_paths = vec![cwd.join("node_modules")];
        Self { cwd, module_paths }
    }

    pub fn with_module_paths(cwd: impl Into<PathBuf>, module_paths: Vec<PathBuf>) -> Self {
        Self {
            cwd: cwd.into(),
            module_paths,
        }
    }

    pub fn load(
        &self,
        plugins: &[Plugin],
        path_to_source: Option<PathBuf>,
    ) -> Result<MultiNestCompilerPlugins, String> {
        let mut multi = MultiNestCompilerPlugins::default();
        for plugin in plugins {
            let (name, _) = plugin_entry_parts(plugin);
            multi.unresolved_plugins.push(name);
        }
        let _ = path_to_source;
        Ok(multi)
    }

    pub fn resolve_plugin_reference(&self, name: &str) -> Option<PathBuf> {
        self.node_module_paths()
            .into_iter()
            .flat_map(|module_path| {
                [
                    module_path.join(name).join(PLUGIN_ENTRY_FILENAME),
                    module_path
                        .join(name)
                        .join(format!("{PLUGIN_ENTRY_FILENAME}.js")),
                    module_path.join(name),
                    self.cwd.join(name),
                ]
            })
            .find(|candidate| candidate.exists())
    }

    pub fn node_module_paths(&self) -> Vec<PathBuf> {
        let mut paths = vec![self.cwd.join("node_modules")];
        paths.extend(self.module_paths.clone());
        paths.sort();
        paths.dedup();
        paths
    }
}

fn plugin_entry_parts(plugin: &Plugin) -> (String, serde_json::Map<String, Value>) {
    match plugin {
        Plugin::Name(name) => (name.clone(), serde_json::Map::new()),
        Plugin::Options(options) => {
            let mut merged_options = serde_json::Map::new();
            for option_group in &options.options {
                for (key, value) in option_group {
                    merged_options.insert(key.clone(), value.clone());
                }
            }
            (options.name.clone(), merged_options)
        }
    }
}