use colored::Colorize;
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;
use crate::error::{CliError, Result};
pub async fn start() -> Result<()> {
println!("\n{}\n", "Starting local development environment...".green().bold());
if !std::path::Path::new("lmrc.toml").exists() {
return Err(CliError::Config(
"lmrc.toml not found. Run this command from project root.".to_string(),
));
}
if !std::path::Path::new("docker/docker-compose.yml").exists() {
println!("{}", "⚠ docker-compose.yml not found. Skipping infrastructure services.".yellow());
} else {
println!("{}", " Starting infrastructure services...".cyan());
start_docker_compose()?;
println!("\n{}", " Waiting for services to be ready...".cyan());
wait_for_services()?;
}
println!("\n{}", " Building workspace...".cyan());
build_workspace()?;
print_success_message();
Ok(())
}
pub async fn stop() -> Result<()> {
println!("\n{}\n", "Stopping local development environment...".yellow().bold());
if std::path::Path::new("docker/docker-compose.yml").exists() {
println!("{}", " Stopping infrastructure services...".cyan());
stop_docker_compose()?;
println!("{}", "✓ Services stopped".green());
}
Ok(())
}
pub async fn status() -> Result<()> {
println!("\n{}\n", "Development Environment Status".cyan().bold());
if std::path::Path::new("docker/docker-compose.yml").exists() {
println!("{}", "Infrastructure Services:".cyan());
show_docker_status()?;
}
Ok(())
}
pub async fn logs(service: Option<String>) -> Result<()> {
if !std::path::Path::new("docker/docker-compose.yml").exists() {
return Err(CliError::Config("docker-compose.yml not found".to_string()));
}
let mut cmd = Command::new("docker");
cmd.current_dir("docker").arg("compose").arg("logs");
if let Some(svc) = service {
cmd.arg(svc);
} else {
cmd.arg("--tail=50");
}
cmd.arg("-f");
cmd.status()?;
Ok(())
}
fn start_docker_compose() -> Result<()> {
let status = Command::new("docker")
.current_dir("docker")
.args(&["compose", "up", "-d"])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()?;
if !status.success() {
return Err(CliError::Command("Failed to start docker-compose".to_string()));
}
Ok(())
}
fn stop_docker_compose() -> Result<()> {
let status = Command::new("docker")
.current_dir("docker")
.args(&["compose", "down"])
.stdout(Stdio::null())
.status()?;
if !status.success() {
return Err(CliError::Command("Failed to stop docker-compose".to_string()));
}
Ok(())
}
fn show_docker_status() -> Result<()> {
Command::new("docker")
.current_dir("docker")
.args(&["compose", "ps"])
.status()?;
Ok(())
}
fn wait_for_services() -> Result<()> {
let services = vec![
("PostgreSQL", "postgres", 5432, "pg_isready"),
("RabbitMQ", "rabbitmq", 5672, "amqp"),
("Redis", "redis", 6379, "redis-cli"),
("Vault", "vault", 8200, "http"),
];
for (name, container, port, check_type) in services {
if is_service_in_compose(container)? {
print!(" {} {}...", "Checking".cyan(), name);
std::io::Write::flush(&mut std::io::stdout()).unwrap();
if wait_for_service(port, check_type, 30) {
println!(" {} (localhost:{})", "Ready".green(), port);
} else {
println!(" {} (timeout)", "Failed".yellow());
}
}
}
Ok(())
}
fn is_service_in_compose(service_name: &str) -> Result<bool> {
let output = Command::new("docker")
.current_dir("docker")
.args(&["compose", "config", "--services"])
.output()?;
let services = String::from_utf8_lossy(&output.stdout);
Ok(services.lines().any(|s| s.contains(service_name)))
}
fn wait_for_service(port: u16, check_type: &str, timeout_secs: u64) -> bool {
let start = std::time::Instant::now();
while start.elapsed().as_secs() < timeout_secs {
match check_type {
"pg_isready" => {
if check_postgres(port) {
return true;
}
}
"redis-cli" => {
if check_redis(port) {
return true;
}
}
"http" => {
if check_http(port) {
return true;
}
}
"amqp" => {
if check_port(port) {
return true;
}
}
_ => {
if check_port(port) {
return true;
}
}
}
thread::sleep(Duration::from_millis(500));
}
false
}
fn check_postgres(port: u16) -> bool {
Command::new("pg_isready")
.args(&["-h", "localhost", "-p", &port.to_string()])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn check_redis(port: u16) -> bool {
Command::new("redis-cli")
.args(&["-p", &port.to_string(), "ping"])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn check_http(port: u16) -> bool {
use std::net::TcpStream;
TcpStream::connect(format!("localhost:{}", port)).is_ok()
}
fn check_port(port: u16) -> bool {
use std::net::TcpStream;
TcpStream::connect(format!("localhost:{}", port)).is_ok()
}
fn build_workspace() -> Result<()> {
let status = Command::new("cargo")
.arg("build")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()?;
if !status.success() {
return Err(CliError::Command("Failed to build workspace".to_string()));
}
println!("{}", " ✓ Workspace built successfully".green());
Ok(())
}
fn print_success_message() {
println!("\n{}\n", "✓ Development environment ready!".green().bold());
println!("{}", "Services:".cyan());
if std::path::Path::new("docker/docker-compose.yml").exists() {
if let Ok(output) = Command::new("docker")
.current_dir("docker")
.args(&["compose", "ps", "--format", "json"])
.output()
{
if output.status.success() {
println!(" PostgreSQL: localhost:5432");
println!(" RabbitMQ AMQP: localhost:5672");
println!(" RabbitMQ UI: http://localhost:15672 (guest/guest)");
println!(" Redis: localhost:6379");
println!(" Vault: http://localhost:8200 (token: root)");
}
}
}
println!("\n{}", "Commands:".cyan());
println!(" Run application: cargo run -p <app-name>");
println!(" Run tests: cargo test");
println!(" View logs: lmrc dev logs");
println!(" Stop services: lmrc dev stop");
println!();
}