use colored::Colorize;
use notify::RecursiveMode;
use notify_debouncer_mini::new_debouncer;
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use crate::error::{CliError, CliResult};
use crate::generators::{find_project_root, has_cargo_watch};
pub async fn run(port: u16, host: &str, cargo_args: &[String]) -> CliResult<()> {
let project_root = find_project_root()?;
println!();
println!(
"{}",
" ╔═══════════════════════════════════════════════════════════╗".bright_cyan()
);
println!(
"{}",
" ║ 🚀 Armature Development Server ║".bright_cyan()
);
println!(
"{}",
" ╚═══════════════════════════════════════════════════════════╝".bright_cyan()
);
println!();
if has_cargo_watch() {
println!(" {} Using cargo-watch for file watching", "→".green());
run_with_cargo_watch(port, host, cargo_args, &project_root).await
} else {
println!(" {} Using built-in file watcher", "→".yellow());
println!(
" {} Install cargo-watch for better performance: {}",
"💡".yellow(),
"cargo install cargo-watch".dimmed()
);
run_with_builtin_watcher(port, host, cargo_args, &project_root).await
}
}
async fn run_with_cargo_watch(
port: u16,
host: &str,
cargo_args: &[String],
project_root: &Path,
) -> CliResult<()> {
let mut args = vec![
"watch".to_string(),
"-x".to_string(),
"run".to_string(),
"-w".to_string(),
"src".to_string(),
"-c".to_string(), "-q".to_string(), ];
for arg in cargo_args {
args.push("--".to_string());
args.push(arg.clone());
}
println!(
" {} Starting server on http://{}:{}",
"→".green(),
host,
port
);
println!(" {} Watching for changes in src/", "→".green());
println!(" {} Press {} to stop", "→".dimmed(), "Ctrl+C".yellow());
println!();
let status = Command::new("cargo")
.args(&args)
.current_dir(project_root)
.env("PORT", port.to_string())
.env("HOST", host)
.status()?;
if !status.success() {
return Err(CliError::Command(
"cargo-watch exited with error".to_string(),
));
}
Ok(())
}
async fn run_with_builtin_watcher(
port: u16,
host: &str,
cargo_args: &[String],
project_root: &Path,
) -> CliResult<()> {
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
})
.map_err(|e| CliError::Command(format!("Failed to set Ctrl+C handler: {}", e)))?;
println!(
" {} Starting server on http://{}:{}",
"→".green(),
host,
port
);
println!(" {} Watching for changes in src/", "→".green());
println!(" {} Press {} to stop", "→".dimmed(), "Ctrl+C".yellow());
println!();
let mut child = start_server(project_root, port, host, cargo_args)?;
let (tx, rx) = std::sync::mpsc::channel();
let mut debouncer = new_debouncer(Duration::from_millis(500), tx)
.map_err(|e| CliError::Watch(e.to_string()))?;
debouncer
.watcher()
.watch(&project_root.join("src"), RecursiveMode::Recursive)?;
println!(" {} Waiting for changes...", "→".dimmed());
println!();
while running.load(Ordering::SeqCst) {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(Ok(events)) => {
let rust_changes: Vec<_> = events
.iter()
.filter(|e| e.path.extension().map(|ext| ext == "rs").unwrap_or(false))
.collect();
if !rust_changes.is_empty() {
println!();
println!(" {} File changed, rebuilding...", "↻".yellow().bold());
let _ = child.kill();
let _ = child.wait();
print!("\x1B[2J\x1B[1;1H");
child = start_server(project_root, port, host, cargo_args)?;
println!(" {} Waiting for changes...", "→".dimmed());
}
}
Ok(Err(e)) => {
eprintln!(" {} Watch error: {}", "⚠".yellow(), e);
}
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
match child.try_wait() {
Ok(Some(status)) => {
if !status.success() {
println!(
" {} Server crashed, waiting for changes to restart...",
"⚠".red()
);
}
}
Ok(None) => {
}
Err(e) => {
eprintln!(" {} Error checking server status: {}", "⚠".yellow(), e);
}
}
}
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
break;
}
}
}
println!();
println!(" {} Shutting down...", "→".yellow());
let _ = child.kill();
let _ = child.wait();
Ok(())
}
fn start_server(
project_root: &Path,
port: u16,
host: &str,
cargo_args: &[String],
) -> CliResult<Child> {
let mut args = vec!["run".to_string()];
args.extend(cargo_args.iter().cloned());
let child = Command::new("cargo")
.args(&args)
.current_dir(project_root)
.env("PORT", port.to_string())
.env("HOST", host)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
Ok(child)
}