use crate::actions::abstract_action::AbstractAction;
use crate::actions::{ActionInvocation, ActionKind, ActionSpec, action_spec};
use crate::commands::Input;
use crate::configuration::Configuration;
use crate::package_managers::{PackageManager, PackageManagerClient};
pub use crate::runners::schematic_runner::SCHEMATICS_CLI_RELATIVE_PATH;
pub use crate::runners::schematic_runner::find_closest_schematics_binary;
use crate::runners::{Runner, RunnerCommand, RunnerFactory};
use crate::schematics::{NESTJS_COLLECTION_NAME, SchematicOption};
use schematics::nest_add::{has_native_nest_add, unsupported_native_nest_add_reason};
use std::path::{Path, PathBuf};
pub const SCHEMATIC_NAME: &str = "nest-add";
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct AddAction;
impl AddAction {
pub const fn new() -> Self {
Self
}
pub fn spec(&self) -> &'static ActionSpec {
action_spec(ActionKind::Add).expect("add action spec")
}
pub fn handle_invocation(
&self,
inputs: Vec<Input>,
options: Vec<Input>,
extra_flags: Vec<String>,
) -> ActionInvocation {
<Self as AbstractAction>::handle(self, inputs, options, extra_flags)
}
pub fn create_plan(&self, request: AddActionRequest) -> AddActionPlan {
create_add_action_plan(request)
}
}
impl AbstractAction for AddAction {
fn kind(&self) -> ActionKind {
ActionKind::Add
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AddActionRequest {
pub library: String,
pub skip_install: bool,
pub dry_run: bool,
pub project: Option<String>,
pub source_root: Option<String>,
pub extra_flags: Vec<String>,
pub package_manager: PackageManager,
pub configuration: Configuration,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AddActionPlan {
pub library_name: String,
pub package_name: String,
pub collection_name: String,
pub tag_name: String,
pub source_root: String,
pub install_command: Option<RunnerCommand>,
pub schematic_command: String,
pub dry_run: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AddSchematicExecutionPlan {
pub schematics_binary: PathBuf,
pub command: RunnerCommand,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AddExecutionPlan {
Native(AddNativeExecutionPlan),
Node(AddSchematicExecutionPlan),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AddNativeExecutionPlan {
pub collection_name: String,
pub source_root: String,
pub extra_flags: Vec<String>,
pub dry_run: bool,
}
pub fn create_add_action_plan(request: AddActionRequest) -> AddActionPlan {
let package_name = get_package_name(&request.library);
let collection_name = get_collection_name(&request.library, &package_name);
let tag_name = get_tag_name(&request.library)
.filter(|tag| !tag.is_empty())
.unwrap_or_else(|| "latest".to_string());
let source_root = resolve_source_root(
request.source_root,
request.project.as_deref(),
&request.configuration,
);
let install_command = (!request.skip_install
&& !request.dry_run
&& !has_native_nest_add(&collection_name))
.then(|| {
PackageManagerClient::new(request.package_manager)
.add_production_command(&[collection_name.as_str()], &tag_name)
});
let mut options = vec![SchematicOption::new("sourceRoot", source_root.as_str())];
if request.dry_run {
options.push(SchematicOption::new("dry-run", true));
}
let mut schematic_command = format!(
"{collection_name}:{SCHEMATIC_NAME}{}",
options.iter().fold(String::new(), |mut output, option| {
output.push(' ');
output.push_str(&option.to_command_string());
output
})
);
if !request.extra_flags.is_empty() {
schematic_command.push(' ');
schematic_command.push_str(&request.extra_flags.join(" "));
}
AddActionPlan {
library_name: request.library,
package_name,
collection_name,
tag_name,
source_root,
install_command,
schematic_command,
dry_run: request.dry_run,
}
}
pub fn create_add_schematic_execution_plan(
plan: &AddActionPlan,
cwd: impl AsRef<Path>,
) -> Result<AddSchematicExecutionPlan, String> {
let cwd = cwd.as_ref();
let schematics_binary = find_closest_schematics_binary(cwd)?;
let command = RunnerFactory::create_schematic(&schematics_binary).describe(
&plan.schematic_command,
false,
Some(cwd.to_path_buf()),
);
Ok(AddSchematicExecutionPlan {
schematics_binary,
command,
})
}
pub fn create_add_execution_plan(
plan: &AddActionPlan,
extra_flags: &[String],
cwd: impl AsRef<Path>,
) -> Result<AddExecutionPlan, String> {
if has_native_nest_add(&plan.collection_name) {
return Ok(AddExecutionPlan::Native(AddNativeExecutionPlan {
collection_name: plan.collection_name.clone(),
source_root: plan.source_root.clone(),
extra_flags: extra_flags.to_vec(),
dry_run: plan.dry_run,
}));
}
if let Some(reason) = unsupported_native_nest_add_reason(&plan.collection_name) {
return Err(reason.to_string());
}
create_add_schematic_execution_plan(plan, cwd).map(AddExecutionPlan::Node)
}
pub fn has_native_add_handler(collection_name: &str) -> bool {
has_native_nest_add(collection_name)
}
pub fn unsupported_native_add_reason(collection_name: &str) -> Option<&'static str> {
unsupported_native_nest_add_reason(collection_name)
}
pub fn get_package_name(library: &str) -> String {
let end = package_end_index(library);
library[..end].to_string()
}
pub fn get_collection_name(library: &str, package_name: &str) -> String {
if let Some(tag_start) = package_tag_index(library) {
let tag_end = library[tag_start + 1..]
.find('/')
.map(|index| tag_start + 1 + index)
.unwrap_or(library.len());
format!("{}{}", &library[..tag_start], &library[tag_end..])
} else if let Some(suffix) = library.strip_prefix(package_name) {
format!("{package_name}{suffix}")
} else {
library.to_string()
}
}
pub fn get_tag_name(library: &str) -> Option<String> {
package_tag_index(library).and_then(|tag_start| {
let tag = library[tag_start + 1..]
.split('/')
.next()
.unwrap_or_default();
(!tag.is_empty()).then(|| tag.to_string())
})
}
fn package_end_index(library: &str) -> usize {
if library.starts_with('@') {
let slash = match library.find('/') {
Some(index) => index,
None => return library.len(),
};
let after_name = slash + 1;
let scoped_name_len = library[after_name..]
.find(['@', '/'])
.map(|index| after_name + index)
.unwrap_or(library.len());
scoped_name_len
} else {
library.find(['@', '/']).unwrap_or(library.len())
}
}
fn package_tag_index(library: &str) -> Option<usize> {
if library.starts_with('@') {
let slash = library.find('/')?;
library[slash + 1..]
.find('@')
.map(|index| slash + 1 + index)
} else {
library.find('@')
}
}
fn resolve_source_root(
source_root: Option<String>,
project: Option<&str>,
configuration: &Configuration,
) -> String {
if let Some(source_root) = source_root {
return source_root;
}
if let Some(project) = project {
if let Some(project_configuration) = configuration.projects.get(project) {
if let Some(source_root) = &project_configuration.source_root {
return source_root.clone();
}
}
}
configuration.source_root.clone()
}
pub fn default_collection_name() -> &'static str {
NESTJS_COLLECTION_NAME
}