use super::RapidCommand;
use crate::{
tui::{logo, rapid_logo},
cli::{current_directory, Config},
constants::BOLT_EMOJI,
tui::{clean_console, indent},
};
use clap::{arg, value_parser, ArgAction, ArgMatches, Command};
use colorful::{Color, Colorful};
use include_dir::{include_dir, Dir};
use requestty::{prompt_one, prompt, Question, Answer};
use std::{
fs::remove_dir_all,
path::PathBuf,
process::{exit, Command as StdCommand},
thread, time,
};
use spinach::Spinach;
static PROJECT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates/server");
static REMIX_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates/remix");
static REMIX_WITHOUT_CLERK_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates/remix-without-clerk");
pub struct New {}
impl RapidCommand for New {
fn cmd() -> clap::Command {
Command::new("new")
.about("Creates a new Rapid project at the current working directory!")
.arg(
arg!(
-remix --remix "Scaffolds a Remix Rapid project!"
)
.required(false)
.action(ArgAction::SetTrue)
.value_parser(value_parser!(PathBuf)),
)
.arg(
arg!(
-server --server "Scaffolds a server-side only Rapid project!"
)
.required(false)
.action(ArgAction::SetTrue)
.value_parser(value_parser!(PathBuf)),
)
}
fn execute(_: &Config, args: &ArgMatches) -> Result<(), crate::cli::CliError<'static>> {
println!("{}", logo());
parse_new_args(args);
Ok(())
}
}
pub fn parse_new_args(args: &ArgMatches) {
const NEW_ARGS: [&str; 2] = ["remix", "server"];
let current_working_directory = current_directory();
let mut did_find_match = false;
for arg in NEW_ARGS {
match args.get_one::<PathBuf>(arg) {
Some(val) => {
if val == &PathBuf::from("true") {
match arg {
"remix" => {
init_remix_template(current_working_directory.clone());
did_find_match = true;
break;
}
"server" => {
init_server_template(current_working_directory.clone(), arg);
did_find_match = true;
break;
}
_ => {
init_remix_template(current_working_directory.clone());
did_find_match = true;
break;
}
}
}
}
None => {
init_remix_template(current_working_directory.clone());
did_find_match = true;
break;
}
}
}
if !did_find_match {
init_remix_template(current_working_directory);
}
}
pub fn init_remix_template(current_working_directory: PathBuf) {
let project_name = prompt_one(
Question::input("project_name")
.message("What will your project be called?")
.default("my-app")
.build(),
)
.expect("Error: Could not scaffold project. Please try again!");
let project_name = project_name.as_string().unwrap();
if !project_name.chars().all(|x| x.is_alphanumeric() || x == '-' || x == '_') {
println!("Aborting...your project name may only contain alphanumeric characters along with '-' and '_'...");
exit(64);
}
let path = current_working_directory.join(project_name);
if path.exists() {
let force = prompt_one(
Question::confirm("force_delete")
.message("Your specified directory is not empty and has files currently in it, do you want to overwrite?")
.default(false)
.build(),
)
.expect("Error: Could not scaffold project. Please try again!");
match !force.as_bool().unwrap() {
true => {
exit(64);
}
false => {
remove_dir_all(&path).expect("Error: Could not scaffold project. The specified directory must be empty. Please try again!");
}
}
}
let manager_choices = vec!["pnpm", "npm", "yarn"];
println!("{}", indent(1));
let package_manager = requestty::Question::select("packageManagerSelect")
.message("Which package manager would you like to use?:")
.choices(
manager_choices
)
.page_size(6)
.build();
let package_manager = prompt(vec![package_manager]).expect("Error: Could not scaffold project. Please try again!");
let package_manager = match package_manager.get("packageManagerSelect") {
Some(Answer::ListItem(choice)) => choice.text.clone(),
_ => {
println!("{}", "Aborting...an error occurred while trying to parse package manager selection. Please try again!".bold().color(Color::Red));
exit(64);
}
};
println!("{}", indent(1));
let _ = vec!["Clerk (authentication)"];
let tech_choices = requestty::Question::multi_select("What technologies would you like included?").choice_with_default("Clerk (authentication)", true);
let tech_choices = prompt_one(tech_choices).expect("Error: Could not scaffold project. Please try again!");
let tech_choices = match tech_choices {
Answer::ListItems(choices) => {
choices
},
_ => {
println!("{}", "Aborting...an error occurred while trying to parse technology choices. Please try again!".bold().color(Color::Red));
exit(64);
}
};
let should_include_clerk = tech_choices.iter().any(|x| x.text == "Clerk (authentication)");
println!("{}", indent(1));
let loading = Spinach::new(format!("{}", "Initializing a new Rapid Remix application..".color(Color::LightCyan)));
StdCommand::new("sh")
.current_dir(current_directory())
.arg("-c")
.arg(format!("mkdir {}", project_name))
.spawn()
.unwrap()
.wait()
.expect("Error: Could not scaffold project. Please try again!");
StdCommand::new("sh")
.current_dir(current_directory().join(project_name))
.arg("-c")
.arg(format!("git init --quiet"))
.spawn()
.unwrap()
.wait()
.expect("Error: Could not scaffold project. Please try again!");
if !should_include_clerk {
REMIX_WITHOUT_CLERK_DIR.extract(current_working_directory.join(project_name).clone()).unwrap();
} else {
REMIX_DIR.extract(current_working_directory.join(project_name).clone()).unwrap();
}
StdCommand::new("sh")
.current_dir(current_directory().join(project_name))
.arg("-c")
.arg(format!("mv Cargo__toml Cargo.toml"))
.spawn()
.unwrap()
.wait()
.expect("Error: Could not scaffold project. Please try again!");
let timeout = time::Duration::from_millis(1000);
thread::sleep(timeout);
loading.succeed("Initialized!");
let loading = Spinach::new(format!("{}", "Installing dependencies...".color(Color::LightCyan)));
StdCommand::new("sh")
.current_dir(current_directory().join(project_name))
.arg("-c")
.arg(format!("{} install > /dev/null 2>&1", package_manager))
.spawn()
.unwrap()
.wait()
.expect("Error: Could not install project dependencies!");
loading.succeed("Installed dependencies!");
clean_console();
println!(
"\n\n{} {} {} {}",
format!("{}", rapid_logo()).bold(),
"Success".bg_blue().color(Color::White).bold(),
BOLT_EMOJI,
"Welcome to your new Rapid application with Remix!"
);
println!(
"{} {} {} {} {}",
"\n\n🚀".bold(),
"Next Steps".bg_blue().color(Color::White).bold(),
BOLT_EMOJI,
format!("\n\ncd {}", project_name).bold(),
"\nrapid run".bold()
);
}
pub fn init_server_template(current_working_directory: PathBuf, _: &str) {
let project_name = prompt_one(
Question::input("project_name")
.message("What will your project be called?")
.default("my-app")
.build(),
)
.expect("Error: Could not scaffold project. Please try again!");
let project_name = project_name.as_string().unwrap();
if !project_name.chars().all(|x| x.is_alphanumeric() || x == '-' || x == '_') {
println!("Aborting...your project name may only contain alphanumeric characters along with '-' and '_'...");
exit(64);
}
let path = current_working_directory.join(project_name);
if path.exists() {
let force = prompt_one(
Question::confirm("force_delete")
.message("Your specified directory is not empty and has files currently in it, do you want to overwrite?")
.default(false)
.build(),
)
.expect("Error: Could not scaffold project. Please try again!");
match !force.as_bool().unwrap() {
true => {
exit(64);
}
false => {
remove_dir_all(&path).expect("Error: Could not scaffold project. The specified directory must be empty. Please try again!");
}
}
}
println!("{}", indent(1));
let loading = Spinach::new(format!("{}", "Initializing a new Rapid server application..".color(Color::LightCyan)));
StdCommand::new("sh")
.current_dir(current_directory())
.arg("-c")
.arg(format!("cargo new {} --quiet", project_name))
.spawn()
.unwrap()
.wait()
.expect("Error: Could not scaffold project. Please try again!");
StdCommand::new("sh")
.current_dir(current_directory().join(project_name))
.arg("-c")
.arg("cargo add rapid-web futures-util include_dir --quiet")
.spawn()
.unwrap()
.wait()
.expect("Error: Could not scaffold project. Please try again!");
remove_dir_all(current_working_directory.join(format!("{}/src", project_name))).unwrap();
PROJECT_DIR.extract(current_working_directory.join(project_name).clone()).unwrap();
let timeout = time::Duration::from_millis(675);
thread::sleep(timeout);
loading.stop();
clean_console();
println!(
"\n\n{} {} {} {}",
format!("{}", rapid_logo()).bold(),
"Success".bg_blue().color(Color::White).bold(),
BOLT_EMOJI,
"Welcome to your new rapid-web server application!"
);
println!(
"{} {} {} {} {}",
"\n\n🚀".bold(),
"Next Steps".bg_blue().color(Color::White).bold(),
BOLT_EMOJI,
format!("\n\ncd {}", project_name).bold(),
"\nrapid run".bold()
);
}