use anyhow::{Context, Result};
use console::{style, Emoji};
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
pub fn run(port: u16, watch: bool) -> Result<()> {
println!();
println!("{} {}", Emoji("⚡", ""), style("Starting Kegani dev server").bold());
println!("{}", style("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━").dim());
println!(" {} {}", style("Port:").dim(), style(port).cyan());
println!(" {} {}", style("Hot reload:").dim(), style(if watch { "enabled" } else { "disabled" }).cyan());
println!("{}", style("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━").dim());
if watch {
run_with_watch()
} else {
run_once()
}
}
fn run_once() -> Result<()> {
let status = Command::new("cargo")
.args(["run"])
.current_dir(".")
.env("RUST_LOG", "info")
.status()
.context("Failed to run cargo run")?;
if !status.success() {
anyhow::bail!("Server exited with error code: {:?}", status.code());
}
Ok(())
}
fn run_with_watch() -> Result<()> {
let project_dir = std::env::current_dir().context("Failed to get current directory")?;
println!();
println!("{} {} watching for .rs file changes...", style("👀").cyan(), style("Hot reload").dim());
println!();
let should_restart = Arc::new(AtomicBool::new(true));
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = RecommendedWatcher::new(
move |res: Result<notify::Event, notify::Error>| {
if let Ok(event) = res {
let _ = tx.send(event);
}
},
Config::default().with_poll_interval(Duration::from_secs(1)),
)
.context("Failed to create file watcher")?;
for dir in &["src", "internal", "manifest"] {
let path = project_dir.join(dir);
if path.is_dir() {
let _ = watcher.watch(&path, RecursiveMode::Recursive);
}
}
let flag = should_restart.clone();
std::thread::spawn(move || {
for event in rx {
match event.kind {
notify::EventKind::Modify(_) | notify::EventKind::Create(_) => {
let changed: Vec<_> = event
.paths
.iter()
.filter(|p| {
p.extension()
.map(|e| e == "rs" || e == "yaml" || e == "toml")
.unwrap_or(false)
})
.collect();
if !changed.is_empty() {
println!();
println!("{} {} changed", style("📝").yellow(), style("File changed:").yellow());
for p in &changed {
println!(" - {}", p.file_name().unwrap().to_string_lossy());
}
flag.store(true, Ordering::Relaxed);
}
}
_ => {}
}
}
});
loop {
while !should_restart.load(Ordering::Acquire) {
std::thread::sleep(Duration::from_millis(500));
}
should_restart.store(false, Ordering::Relaxed);
print!("{} {}", Emoji("⚡", ""), style("Building...").cyan());
let status = Command::new("cargo")
.args(["run"])
.current_dir(&project_dir)
.env("RUST_LOG", "info")
.status();
match status {
Ok(exit) if exit.success() => {
println!();
println!("{} {}", Emoji("✅", ""), style("Server stopped. Waiting for changes...").dim());
}
Ok(exit) => {
println!();
println!(
"{} {} exit code {:?}. Waiting for changes...",
style("⚠").yellow(),
style("Compile error").red(),
exit.code()
);
}
Err(e) => {
println!();
println!("{} {} {}. Waiting for changes...", style("❌").red(), style("Error").red(), e);
}
}
}
}