rapid_cli/commands/
run.rs

1use super::RapidCommand;
2use crate::{
3	cli::{current_directory, Config},
4	rapid_config::config::{find_rapid_config, is_rapid, AppType, RapidConfig},
5	tui::logo,
6};
7use clap::{arg, value_parser, ArgAction, ArgMatches, Command};
8use spinach::Spinach;
9use std::{path::PathBuf, process::Command as StdCommand, str::FromStr};
10use log::info;
11pub struct Run {}
12
13impl RapidCommand for Run {
14	fn cmd() -> clap::Command {
15		Command::new("run")
16			.about("A command for running various rapid applications")
17			.arg(
18				arg!(
19					-server --server "Runs a rapid server application"
20				)
21				.required(false)
22				.action(ArgAction::SetTrue)
23				.value_parser(value_parser!(PathBuf)),
24			)
25			.arg(
26				arg!(
27					-app --app "Runs a rapid fullstack application"
28				)
29				.required(false)
30				.action(ArgAction::SetTrue)
31				.value_parser(value_parser!(PathBuf)),
32			)
33	}
34
35	fn execute(_: &Config, args: &ArgMatches) -> Result<(), crate::cli::CliError<'static>> {
36		// We need to register a handler here to quit the running process
37		ctrlc::set_handler(move || {
38			std::process::exit(0);
39		})
40		.expect("Error: Could not stop process");
41
42		println!("{}", logo());
43		parse_run_args(args).unwrap();
44		// The above code never errors out...
45		Ok(())
46	}
47}
48
49pub fn get_server_port(config: &RapidConfig, fallback_port: u16) -> u16 {
50	let app_type = &config.app_type;
51
52	match app_type.as_str() {
53		"server" => match &config.server {
54			Some(val) => match val.port {
55				Some(p) => p,
56				None => fallback_port,
57			},
58			_ => fallback_port,
59		},
60		"remix" => match &config.remix {
61			Some(val) => match val.server_port {
62				Some(s_port) => s_port,
63				None => fallback_port,
64			},
65			_ => fallback_port,
66		},
67		_ => fallback_port,
68	}
69}
70
71fn parse_run_args(args: &ArgMatches) -> Result<(), ()> {
72	// Grab the rapid config file early on because we will need it to for most of the below logic
73	let rapid_config = find_rapid_config();
74
75	let server_port = get_server_port(&rapid_config, 8080);
76
77	// We want to early exit before do anything at all if we are not inside of a rapid application
78	if !is_rapid() {
79		eprintln!("Could not find a valid config file in the current working directory. Please make sure you are in a official rapid project before running this command.");
80		// Exit 64 is a standard error code..
81		std::process::exit(64);
82	}
83	// As we add support for more apps this array can grow
84	const RUN_ARGS: [&str; 3] = ["server", "remix", "app"];
85
86	for arg in RUN_ARGS {
87		match args.get_one::<PathBuf>(arg) {
88			Some(val) => {
89				if val == &PathBuf::from("true") {
90					match arg {
91						"server" => {
92							handle_run_server(server_port, rapid_config.app_type);
93							return Ok(());
94						}
95						"remix" => {
96							handle_run_server(server_port, rapid_config.app_type);
97							return Ok(());
98						}
99						"app" => {
100							println!("Coming soon!");
101							return Ok(());
102						}
103						// Eventually, "rapid run" should default to running in application mode, not server mode
104						_ => {
105							println!("{} {}", "No application found for the type: ", arg);
106							return Ok(());
107						}
108					}
109				}
110			}
111			None => break,
112		}
113	}
114
115	// If no valid args were inputted then we want to fallback to the rapid config file
116	let application_type = AppType::from_str(&rapid_config.app_type).expect("Error: invalid rapid application type!");
117
118	match application_type {
119		AppType::Nextjs => {
120			handle_run_server(server_port, rapid_config.app_type);
121			return Ok(());
122		}
123		AppType::Server => {
124			handle_run_server(server_port, rapid_config.app_type);
125			return Ok(());
126		}
127		AppType::Remix => {
128			handle_run_server(server_port, rapid_config.app_type);
129			return Ok(());
130		}
131	}
132}
133
134fn handle_run_server(server_port: u16, app_type: String) {
135	// Before running this script we need to check if the user has cargo-watch and systemfd on their machine
136	// If they do, we want to continue -- however, if they dont, we need to trigger an isntall of the binaries
137	let install_list = StdCommand::new("sh")
138		.arg("-c")
139		.arg("cargo install --list")
140		.output()
141		.expect("Could not complete pre-run checks.");
142
143	// This is the hot reload command that powers how rapid is able to hot-reload its binary
144	// It uses a combination of cargo watch and systemfd to achieve this
145	// Checkout both crates here:
146	// - cargo-watch: https://crates.io/crates/cargo-watch
147	// - systemfd: https://crates.io/crates/systemfd
148	let mut hot_reload_command = format!(
149		"systemfd --no-pid --quiet -s http::{} -- cargo watch -x run -q --ignore 'bindings.ts'",
150		server_port
151	);
152
153	// If we have a app_type equal to remix we want to use a different watch command (this specific one will only reload the routes dir by default)
154	if app_type == String::from("remix") {
155		hot_reload_command = String::from("systemfd --no-pid --quiet -s http::8080 -- cargo watch -x run -w app/api -q --ignore 'bindings.ts'");
156	}
157
158	// Check if the user had cargo-watch and systemfd
159	if !std::str::from_utf8(&install_list.stdout).unwrap().contains("cargo-watch")
160		|| !std::str::from_utf8(&install_list.stdout).unwrap().contains("systemfd")
161	{
162		let s = Spinach::new("Installing build scripts...");
163		// To be safe, lets attempt install both cargo-watch and systemfd
164		StdCommand::new("sh")
165			.arg("-c")
166			.arg("cargo install cargo-watch")
167			.output()
168			.expect("Could not install rapid dev server binaries. Please try again.");
169
170		StdCommand::new("sh")
171			.arg("-c")
172			.arg("cargo install systemfd")
173			.output()
174			.expect("Could not install rapid dev server binaries. Please try again.");
175
176		s.succeed("Rapid build scripts installed!");
177	}
178
179	info!("building rapid application...");
180
181	// Trigger the shell command to actually run + watch the rapid server
182	StdCommand::new("sh")
183		.current_dir(current_directory())
184		.arg("-c")
185		.arg(hot_reload_command)
186		.spawn()
187		.unwrap()
188		.wait()
189		.expect("Error: Could not run development server. Please try again!");
190}