use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use cli::actions::ActionKind;
use cli::add_action::{
AddActionRequest, AddExecutionPlan, create_add_action_plan, create_add_execution_plan,
has_native_add_handler, unsupported_native_add_reason,
};
use cli::build_action::{BuildActionPlanRequest, create_build_action_plan};
use cli::build_executor::{
create_build_execution_plan, execute_build_plan, execute_build_watch_plan,
};
use cli::commands::{Input, InputValue, load_command_invocation};
use cli::configuration::{Configuration, load_configuration};
use cli::generate_action::{GenerateActionPlanOptions, build_generate_action_plan};
use cli::new_action::{DEFAULT_GIT_IGNORE, build_new_action_plan};
use cli::package_managers::{PackageManager, detect_package_manager};
use cli::runners::RunnerCommand;
use cli::start_action::{StartActionPlanRequest, create_start_action_plan};
use cli::start_executor::{create_start_execution_plan, execute_start_plan};
use schematics::factories::ApplicationOptions;
use schematics::generators::{generate_application, write_application};
use schematics::nest_add::{NestAddOptions, generate_nest_add};
use schematics::{GenerateOptions, GeneratedFile, generate, write_generated_files};
fn main() {
if let Err(error) = run() {
eprintln!("error: {error}");
std::process::exit(1);
}
}
fn run() -> Result<(), String> {
let args = env::args().skip(1).collect::<Vec<_>>();
if args.is_empty() || args.iter().any(|arg| arg == "--help" || arg == "-h") {
print_help();
return Ok(());
}
let invocation = load_command_invocation(args).map_err(|error| error.to_string())?;
let cwd = env::current_dir().map_err(|error| error.to_string())?;
let configuration = load_configuration(&cwd).unwrap_or_else(|_| Configuration::default());
match invocation.kind {
ActionKind::New => run_new(invocation.inputs, invocation.options, &cwd)?,
ActionKind::Generate => {
run_generate(invocation.inputs, invocation.options, &configuration, &cwd)?
}
ActionKind::Add => run_add(
invocation.inputs,
invocation.options,
invocation.extra_flags,
configuration,
&cwd,
)?,
ActionKind::Build => run_build(invocation.inputs, invocation.options, configuration, cwd)?,
ActionKind::Start => run_start(
invocation.inputs,
invocation.options,
invocation.extra_flags,
configuration,
cwd,
)?,
ActionKind::Info => run_info(&cwd),
}
Ok(())
}
fn run_new(inputs: Vec<Input>, options: Vec<Input>, cwd: &Path) -> Result<(), String> {
let plan = build_new_action_plan(&inputs, &options, cwd).map_err(|error| error.to_string())?;
let application_options = application_options(&inputs, &options)?;
let files = if plan.dry_run {
generate_application(application_options, cwd).map_err(|error| error.to_string())?
} else {
write_application(application_options, cwd).map_err(|error| error.to_string())?
};
if plan.dry_run {
println!("dry run: {} files would be created", files.len());
print_generated_files(&files, cwd);
return Ok(());
}
if plan.create_git_ignore {
fs::write(
cwd.join(&plan.project_directory).join(".gitignore"),
DEFAULT_GIT_IGNORE,
)
.map_err(|error| error.to_string())?;
}
println!("created {} files", files.len());
if let Some(command) = plan.initialize_git_command {
run_command(&command)?;
}
Ok(())
}
fn run_generate(
inputs: Vec<Input>,
options: Vec<Input>,
configuration: &Configuration,
cwd: &Path,
) -> Result<(), String> {
let dry_run = bool_input(&options, "dry-run");
let mut schematic_inputs = inputs;
schematic_inputs.extend(options);
let plan = build_generate_action_plan(
&schematic_inputs,
configuration,
GenerateActionPlanOptions::default(),
)
.map_err(|error| error.to_string())?;
let files =
generate(generate_options_from_plan(&plan)?, cwd).map_err(|error| error.to_string())?;
if dry_run {
println!("dry run: {} files would be created", files.len());
print_generated_files(&files, cwd);
} else {
write_generated_files(&files).map_err(|error| error.to_string())?;
println!("created {} files", files.len());
}
Ok(())
}
fn run_add(
inputs: Vec<Input>,
options: Vec<Input>,
extra_flags: Vec<String>,
configuration: Configuration,
cwd: &Path,
) -> Result<(), String> {
let library = string_input(&inputs, "library")
.ok_or_else(|| "add requires a library name".to_string())?;
let dry_run = bool_input(&options, "dry-run");
let add_extra_flags = extra_flags.clone();
let plan = create_add_action_plan(AddActionRequest {
library,
skip_install: bool_input(&options, "skip-install"),
dry_run,
project: string_input(&options, "project"),
source_root: string_input(&options, "sourceRoot"),
extra_flags,
package_manager: detect_package_manager(cwd).unwrap_or(PackageManager::Npm),
configuration,
});
if let Some(command) = &plan.install_command {
run_command(&command)?;
}
if dry_run {
if has_native_add_handler(&plan.collection_name) {
let files = generate_nest_add(
NestAddOptions {
collection: plan.collection_name.clone(),
source_root: plan.source_root.clone(),
extra_flags: add_extra_flags,
},
cwd,
)
.map_err(|error| error.to_string())?;
println!("dry run: {} files would be created", files.len());
print_generated_files(&files, cwd);
} else if let Some(reason) = unsupported_native_add_reason(&plan.collection_name) {
return Err(reason.to_string());
} else {
println!("{}", plan.schematic_command);
}
return Ok(());
}
match create_add_execution_plan(&plan, &add_extra_flags, cwd)? {
AddExecutionPlan::Native(native_plan) => {
let files = generate_nest_add(
NestAddOptions {
collection: native_plan.collection_name,
source_root: native_plan.source_root,
extra_flags: native_plan.extra_flags,
},
cwd,
)
.map_err(|error| error.to_string())?;
write_generated_files(&files).map_err(|error| error.to_string())?;
println!("created {} files", files.len());
Ok(())
}
AddExecutionPlan::Node(execution_plan) => run_command(&execution_plan.command),
}
}
fn run_build(
inputs: Vec<Input>,
options: Vec<Input>,
configuration: Configuration,
cwd: PathBuf,
) -> Result<(), String> {
let plan = create_build_action_plan(BuildActionPlanRequest {
cwd,
configuration,
command_inputs: inputs,
command_options: options,
ts_build_info_file: None,
})
.map_err(|error| error.to_string())?;
let execution_plan = create_build_execution_plan(&plan).map_err(|error| error.to_string())?;
for warning in &execution_plan.warnings {
eprintln!("warning: {warning}");
}
if execution_plan.watch.is_some() {
return execute_build_watch_plan(&execution_plan).map_err(|error| error.to_string());
}
execute_build_plan(&execution_plan).map_err(|error| error.to_string())
}
fn run_start(
inputs: Vec<Input>,
options: Vec<Input>,
extra_flags: Vec<String>,
configuration: Configuration,
cwd: PathBuf,
) -> Result<(), String> {
let plan = create_start_action_plan(StartActionPlanRequest {
cwd,
configuration,
command_inputs: inputs,
command_options: options,
extra_flags,
ts_build_info_file: None,
})
.map_err(|error| error.to_string())?;
let execution_plan = create_start_execution_plan(&plan).map_err(|error| error.to_string())?;
for warning in &execution_plan.warnings {
eprintln!("warning: {warning}");
}
execute_start_plan(&execution_plan).map_err(|error| error.to_string())
}
fn run_info(cwd: &Path) {
println!("cli {}", env!("CARGO_PKG_VERSION"));
println!(
"package manager {}",
detect_package_manager(cwd)
.unwrap_or(PackageManager::Npm)
.as_str()
);
}
fn print_help() {
println!("Nestrs CLI");
println!("Commands: new, generate, add, build, start, info");
}
fn run_command(command: &RunnerCommand) -> Result<(), String> {
command
.execute()
.map(|_| ())
.map_err(|error| error.to_string())
}
fn print_generated_files(files: &[GeneratedFile], root: &Path) {
for file in files {
let display_path = file.path.strip_prefix(root).unwrap_or(&file.path).display();
println!("create {display_path}");
}
}
fn application_options(inputs: &[Input], options: &[Input]) -> Result<ApplicationOptions, String> {
let name = string_input(inputs, "name").ok_or_else(|| "new requires a name".to_string())?;
Ok(ApplicationOptions {
name,
author: None,
description: None,
directory: string_input(options, "directory").map(PathBuf::from),
strict: bool_input_value(options, "strict"),
version: None,
language: Some("rs".to_string()),
package_manager: string_input(options, "packageManager"),
dependencies: None,
dev_dependencies: None,
spec: None,
spec_file_suffix: None,
})
}
fn generate_options_from_plan(
plan: &cli::generate_action::GenerateActionPlan,
) -> Result<GenerateOptions, String> {
let path = match schematic_option_string(&plan.schematic_options, "path") {
Some(path) => plan.source_root.join(path),
None => plan.source_root.clone(),
};
Ok(GenerateOptions {
schematic: plan.schematic.clone(),
name: schematic_option_string(&plan.schematic_options, "name")
.ok_or_else(|| "generate requires a name".to_string())?,
path: Some(path),
language: "rs".to_string(),
spec: plan.spec,
flat: plan.flat,
spec_file_suffix: plan.spec_file_suffix.clone(),
extra: Default::default(),
})
}
fn string_input(inputs: &[Input], name: &str) -> Option<String> {
inputs
.iter()
.find(|input| input.name == name)
.and_then(|input| match input.value.as_ref()? {
InputValue::String(value) => Some(value.clone()),
_ => None,
})
}
fn bool_input(inputs: &[Input], name: &str) -> bool {
bool_input_value(inputs, name).unwrap_or(false)
}
fn bool_input_value(inputs: &[Input], name: &str) -> Option<bool> {
inputs
.iter()
.find(|input| input.name == name)
.and_then(|input| match input.value.as_ref()? {
InputValue::Bool(value) => Some(*value),
_ => None,
})
}
fn schematic_option_string(
options: &[cli::schematics::SchematicOption],
name: &str,
) -> Option<String> {
options
.iter()
.find(|option| option.name == name)
.and_then(|option| match &option.value {
cli::schematics::SchematicOptionValue::String(value) => Some(value.clone()),
_ => None,
})
}