rapid_cli/commands/
init.rs

1use super::RapidCommand;
2use crate::{
3	cli::{current_directory, Config},
4	constants::BOLT_EMOJI,
5	tui::logo,
6};
7use clap::{arg, value_parser, ArgAction, ArgMatches, Command};
8use colorful::{Color, Colorful};
9use rust_embed::RustEmbed;
10use log::{error, info};
11use std::{
12	fs::{write, File},
13	path::PathBuf,
14};
15
16#[derive(RustEmbed)]
17#[folder = "templates/rapidUI/reactVite/"]
18struct Asset;
19
20#[derive(RustEmbed)]
21#[folder = "templates/rapidUI/remix/"]
22struct RemixAssets;
23
24#[derive(RustEmbed)]
25#[folder = "templates/rapidUI/nextjs/"]
26struct NextJsAssets;
27
28#[derive(RustEmbed)]
29#[folder = "templates/dockerfiles/"]
30struct Dockerfiles;
31
32pub struct Init {}
33
34impl RapidCommand for Init {
35	fn cmd() -> clap::Command {
36		Command::new("init")
37			.about("A command for initializing Rapid libraries in your projects!")
38			.subcommand(Command::new("fullstack").about("A command for initializing a fullstack rapid application in your react projects!"))
39			.subcommand(
40				Command::new("server")
41					.about("A command for initializing functionality inside of a rapid server")
42					.arg(
43						arg!(
44							-deploy --deploy "Initializes functionality inside of an existing rapid server application"
45						)
46						.required(false)
47						.action(ArgAction::SetTrue)
48						.value_parser(value_parser!(PathBuf)),
49					),
50			)
51			.subcommand(
52				Command::new("ui")
53					.about("A command for initializing Rapid-UI in your react projects!")
54					.arg(
55						arg!(
56							-vite --vite "Initializes rapid-ui in your React + Vitejs project!"
57						)
58						.required(false)
59						.action(ArgAction::SetTrue)
60						.value_parser(value_parser!(PathBuf)),
61					)
62					.arg(
63						arg!(
64							-remix --remix "Initializes rapid-ui in your Remix.run application!"
65						)
66						.required(false)
67						.action(ArgAction::SetTrue)
68						.value_parser(value_parser!(PathBuf)),
69					)
70					.arg(
71						arg!(
72							-nextjs --nextjs "Initializes rapid-ui in your Nextjs application!"
73						)
74						.required(false)
75						.action(ArgAction::SetTrue)
76						.value_parser(value_parser!(PathBuf)),
77					),
78			)
79	}
80
81	fn execute(_: &Config, args: &ArgMatches) -> Result<(), crate::cli::CliError<'static>> {
82		println!("{}", logo());
83		parse_init_args(args);
84		Ok(())
85	}
86}
87
88pub fn ui_subcommand_handler(init_args: [&str; 3], subcommand_args: &ArgMatches, current_working_directory: PathBuf) {
89	for ui_arg in init_args {
90		match subcommand_args.get_one::<PathBuf>(ui_arg) {
91			Some(val) => {
92				if val == &PathBuf::from("true") {
93					match ui_arg {
94						"vite" => {
95							init_vite_template(current_working_directory, ui_arg);
96							break;
97						}
98						"remix" => {
99							init_remix_template(current_working_directory, ui_arg);
100							break;
101						}
102						"nextjs" => {
103							init_nextjs_template(current_working_directory, ui_arg);
104							break;
105						}
106						_ => {
107							error!("{}", "no template found. Please try '--vite' or '--remix'".color(Color::Red));
108							break;
109						}
110					}
111				}
112			}
113			None => {
114				error!("{}", "no template found. Please try '--vite' or '--remix'".color(Color::Red));
115			}
116		}
117	}
118}
119
120pub fn server_subcommand_handler(init_args: [&str; 1], subcommand_args: &ArgMatches, current_working_directory: PathBuf) {
121	for server_arg in init_args {
122		match subcommand_args.get_one::<PathBuf>(server_arg) {
123			Some(val) => {
124				if val == &PathBuf::from("true") {
125					match server_arg {
126						"deploy" => {
127							init_deployments_dockerfile(current_working_directory);
128							return;
129						}
130						_ => {
131							error!("{}", "no command found. Please try '--deploy'".color(Color::Red));
132							return;
133						}
134					}
135				}
136			}
137			None => {
138				error!("{}", "no command found. Please try '--deploy'".color(Color::Red));
139			}
140		}
141	}
142
143	error!("{}", "no init commands found! Please try using '--deploy'".color(Color::Red));
144}
145
146fn parse_init_args(args: &ArgMatches) {
147	/// NOTE: We can add more args for templates here (ideally we add nextjs asap)
148	const UI_INIT_ARGS: [&str; 3] = ["vite", "remix", "nextjs"];
149	const SERVER_INIT_ARGS: [&str; 1] = ["deploy"];
150	const INIT_COMMANDS: [&str; 3] = ["ui", "fullstack", "server"];
151	// Get the current working directory of the user
152	let current_working_directory = current_directory();
153	for arg in INIT_COMMANDS {
154		match args.subcommand() {
155			Some((name, subcommand_args)) => {
156				match name {
157					"ui" => {
158						ui_subcommand_handler(UI_INIT_ARGS, subcommand_args, current_working_directory);
159						return;
160					}
161					"server" => {
162						server_subcommand_handler(SERVER_INIT_ARGS, subcommand_args, current_working_directory);
163						return;
164					}
165					"fullstack" => {
166						// TODO: this will be the command that runs for initializing a new rapid app inside of an existing nextjs or remix application (different from rapid new that scaffolds an entire fullstack app with rapid)
167						error!("coming soon...");
168						return;
169					}
170					_ => {
171						error!("no init scripts found for `{}`", arg);
172						break;
173					}
174				}
175			}
176			None => {} // Do nothing if we dont find anythign
177		}
178	}
179
180	error!("no init scripts found");
181}
182
183pub fn init_vite_template(current_working_directory: PathBuf, arg: &str) {
184	info!("Initializing rapid-ui with the template: {:?}...", arg);
185	let tailwind_config_contents = Asset::get("tailwind.config.js").unwrap();
186	let postcss_config_contents = Asset::get("postcss.config.js").unwrap();
187	let index_css_contents = Asset::get("index.css").unwrap();
188	// Make the two config files that we need
189	File::create(current_working_directory.join("tailwind.config.js")).expect("Failed to create a tailwind config file. Please try again!");
190	File::create(current_working_directory.join("postcss.config.js")).expect("Failed to create a postcss config file. Please try again!");
191	File::create(current_working_directory.join("src/index.css")).expect("Failed to create the css entrypoint file. Please try again!");
192	// Write the contents of the config files
193	write("tailwind.config.js", std::str::from_utf8(tailwind_config_contents.data.as_ref()).unwrap())
194		.expect("Could not write to tailwind config file!");
195	write("postcss.config.js", std::str::from_utf8(postcss_config_contents.data.as_ref()).unwrap()).expect("Could not write to postcss config file!");
196	write("src/index.css", std::str::from_utf8(index_css_contents.data.as_ref()).unwrap()).expect("Could not write to index.css file!");
197
198	info!(
199		"{} rapid-ui has been initialized in your Vite project!",
200		BOLT_EMOJI,
201	);
202}
203
204pub fn init_remix_template(current_working_directory: PathBuf, arg: &str) {
205	info!("initializing rapid-ui with the template {:?}...", arg);
206	let tailwind_config_contents = RemixAssets::get("tailwind.config.ts").unwrap();
207	let index_css_contents = RemixAssets::get("index.css").unwrap();
208	// Make the two config files that we need
209	File::create(current_working_directory.join("tailwind.config.ts")).expect("Failed to create a tailwind config file. Please try again!");
210	File::create(current_working_directory.join("app/index.css")).expect("Failed to create the css entrypoint file. Please try again!");
211	// Write the contents of the config files
212	write("tailwind.config.ts", std::str::from_utf8(tailwind_config_contents.data.as_ref()).unwrap())
213		.expect("Could not write to tailwind config file!");
214	write("app/index.css", std::str::from_utf8(index_css_contents.data.as_ref()).unwrap()).expect("Could not write to index.css file!");
215
216	info!(
217		"{} rapid-ui has been initialized in your Remix project!",
218		BOLT_EMOJI,
219	);
220}
221
222pub fn init_nextjs_template(current_working_directory: PathBuf, arg: &str) {
223	info!("initializing rapid-ui with the template {:?}...", arg);
224	let tailwind_config_contents = NextJsAssets::get("tailwind.config.ts").unwrap();
225	let postcss_config_contents = NextJsAssets::get("postcss.config.js").unwrap();
226
227	// Make the two config files that we need
228	File::create(current_working_directory.join("tailwind.config.ts")).expect("Failed to create a tailwind config file. Please try again!");
229	// Create our postcss file
230	write("postcss.config.js", std::str::from_utf8(postcss_config_contents.data.as_ref()).unwrap()).expect("Could not write to postcss config file!");
231	// Write the contents of the config files
232	write("tailwind.config.ts", std::str::from_utf8(tailwind_config_contents.data.as_ref()).unwrap())
233		.expect("Could not write to tailwind config file!");
234
235	info!(
236		"{} rapid-ui has been initialized in your NextJS project!",
237		BOLT_EMOJI,
238	);
239}
240
241pub fn init_deployments_dockerfile(current_working_directory: PathBuf) {
242	info!("Initializing rapid deployments...");
243	let dockerfile_conents = Dockerfiles::get("rapidServer.Dockerfile").unwrap();
244
245	// Create the Dockerfile
246	File::create(current_working_directory.join("rapid.Dockerfile"))
247		.expect("Failed to create the depoyment Dockerfile. Is there already a dockerfile created with the name 'rapid.Dockerfile'?");
248
249	// Write to the Dockerfuke
250	write("rapid.Dockerfile", std::str::from_utf8(dockerfile_conents.data.as_ref()).unwrap()).expect("Could not write to postcss config file!");
251
252	info!(
253		"{}\n{}{}\n{}{}",
254		BOLT_EMOJI,
255		"Build: ".bold(),
256		"docker build -t rapid-server -f ./rapid.Dockerfile .".color(Color::LightCyan),
257		"Run: ".bold(),
258		"docker run -p 8080:8080 rapid-server".color(Color::LightCyan),
259	);
260}