rusty_stack/bootstrap/
launcher.rs1use crate::installers::common::utils::{print_error, print_step, print_success};
11use std::path::PathBuf;
12use std::process::Command;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub enum BuildMode {
21 Debug,
22 #[default]
23 Release,
24}
25
26impl std::fmt::Display for BuildMode {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 BuildMode::Debug => write!(f, "debug"),
30 BuildMode::Release => write!(f, "release"),
31 }
32 }
33}
34
35#[derive(Debug, Clone)]
37pub struct LaunchResult {
38 pub success: bool,
40 pub binary_path: Option<String>,
42 pub errors: Vec<String>,
44}
45
46impl LaunchResult {
47 pub fn success(binary_path: String) -> Self {
49 Self {
50 success: true,
51 binary_path: Some(binary_path),
52 errors: Vec::new(),
53 }
54 }
55
56 pub fn failure(error: String) -> Self {
58 Self {
59 success: false,
60 binary_path: None,
61 errors: vec![error],
62 }
63 }
64}
65
66pub fn launch_tui(build_mode: BuildMode, extra_args: &[String]) -> LaunchResult {
81 let project_root = detect_project_root();
83 let rusty_stack_dir = project_root.join("rusty-stack");
84
85 if !rusty_stack_dir.exists() {
86 let msg = format!(
87 "Rust project directory not found: {}",
88 rusty_stack_dir.display()
89 );
90 print_error(&msg);
91 return LaunchResult::failure(msg);
92 }
93
94 print_step(&format!("Build mode: {build_mode}"));
95
96 let (exec_path, build_args) = match build_mode {
98 BuildMode::Debug => ("target/debug/rusty-stack", vec!["build"]),
99 BuildMode::Release => ("target/release/rusty-stack", vec!["build", "--release"]),
100 };
101
102 print_step(&format!("Building with cargo {}...", build_mode));
103
104 let output = Command::new("cargo")
105 .args(&build_args)
106 .current_dir(&rusty_stack_dir)
107 .stdout(std::process::Stdio::inherit())
108 .stderr(std::process::Stdio::inherit())
109 .output();
110
111 match output {
112 Ok(output) if output.status.success() => {
113 let binary_path = rusty_stack_dir.join(exec_path);
114 if !binary_path.exists() {
115 let msg = format!("Binary not found after build: {}", binary_path.display());
116 print_error(&msg);
117 return LaunchResult::failure(msg);
118 }
119 print_success(&format!("Built successfully: {}", binary_path.display()));
120 print_step(&format!("Launching {}", exec_path));
121
122 let _ = extra_args; LaunchResult::success(binary_path.to_string_lossy().to_string())
127 }
128 Ok(output) => {
129 let stderr = String::from_utf8_lossy(&output.stderr);
130 let msg = format!("Build failed: {stderr}");
131 print_error(&msg);
132 LaunchResult::failure(msg)
133 }
134 Err(e) => {
135 let msg = format!("Failed to run cargo: {e}");
136 print_error(&msg);
137 LaunchResult::failure(msg)
138 }
139 }
140}
141
142fn detect_project_root() -> PathBuf {
153 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
154
155 if cwd.join("rusty-stack").exists() {
157 return cwd;
158 }
159
160 if let Some(parent) = cwd.parent() {
162 if parent.join("rusty-stack").exists() {
163 return parent.to_path_buf();
164 }
165
166 if let Some(grandparent) = parent.parent() {
168 if grandparent.join("rusty-stack").exists() {
169 return grandparent.to_path_buf();
170 }
171 }
172 }
173
174 cwd
176}
177
178#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_build_mode_default_is_release() {
188 assert_eq!(BuildMode::default(), BuildMode::Release);
189 }
190
191 #[test]
192 fn test_build_mode_display() {
193 assert_eq!(format!("{}", BuildMode::Debug), "debug");
194 assert_eq!(format!("{}", BuildMode::Release), "release");
195 }
196
197 #[test]
198 fn test_launch_result_success() {
199 let result = LaunchResult::success("/path/to/rusty-stack".to_string());
200 assert!(result.success);
201 assert_eq!(result.binary_path, Some("/path/to/rusty-stack".to_string()));
202 assert!(result.errors.is_empty());
203 }
204
205 #[test]
206 fn test_launch_result_failure() {
207 let result = LaunchResult::failure("Build error".to_string());
208 assert!(!result.success);
209 assert!(result.binary_path.is_none());
210 assert_eq!(result.errors.len(), 1);
211 }
212
213 #[test]
214 fn test_detect_project_root_no_panic() {
215 let root = detect_project_root();
217 assert!(!root.as_os_str().is_empty());
219 }
220
221 #[test]
222 fn test_launch_tui_returns_result() {
223 let result = launch_tui(BuildMode::Release, &[]);
226 let _ = result.success;
228 }
229}