use anyhow::{Result, Context};
use colored::Colorize;
use std::process::{Command, Stdio};
use std::collections::HashMap;
use tokio::process::Command as TokioCommand;
use crate::config::Config;
use crate::utils::{print_success, print_error, print_info};
pub async fn handle_dev(
services: Vec<String>,
all: bool,
config: &Config,
) -> Result<()> {
println!("{}", "Revoke Development Environment".bold());
println!("{}", "=".repeat(50));
check_docker().await?;
print_info("Starting infrastructure services...");
start_infrastructure().await?;
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
let services_to_start = if all {
discover_services()?
} else if services.is_empty() {
print_error("No services specified. Use --all or specify services with -s");
return Ok(());
} else {
services
};
if services_to_start.is_empty() {
print_info("No services found to start");
return Ok(());
}
println!("\n{}", "Starting services:".bold());
for service in &services_to_start {
println!(" - {}", service.cyan());
}
let mut handles = vec![];
for service in services_to_start {
let service_clone = service.clone();
let handle = tokio::spawn(async move {
start_service(&service_clone).await
});
handles.push((service, handle));
}
println!("\n{}", "Services are starting...".dimmed());
println!("{}", "Press Ctrl+C to stop all services".dimmed());
tokio::signal::ctrl_c().await?;
println!("\n{}", "Stopping services...".yellow());
for (service, handle) in handles {
handle.abort();
println!(" - Stopped {}", service.cyan());
}
print_success("Development environment stopped");
Ok(())
}
async fn check_docker() -> Result<()> {
let output = TokioCommand::new("docker")
.arg("version")
.output()
.await;
match output {
Ok(output) if output.status.success() => {
print_success("Docker is installed and running");
Ok(())
}
_ => {
print_error("Docker is not installed or not running");
Err(anyhow::anyhow!("Docker is required for development environment"))
}
}
}
async fn start_infrastructure() -> Result<()> {
if !std::path::Path::new("docker-compose.yml").exists() {
print_info("No docker-compose.yml found, creating default infrastructure...");
create_default_docker_compose().await?;
}
let output = TokioCommand::new("docker-compose")
.args(&["up", "-d", "consul"])
.output()
.await?;
if !output.status.success() {
print_error("Failed to start infrastructure");
return Err(anyhow::anyhow!("docker-compose failed"));
}
print_success("Infrastructure started successfully");
print_info("Waiting for Consul to be ready...");
for i in 0..30 {
let result = reqwest::get("http://localhost:8500/v1/status/leader").await;
if result.is_ok() {
print_success("Consul is ready");
break;
}
if i == 29 {
print_error("Consul failed to start");
return Err(anyhow::anyhow!("Consul not ready after 30 seconds"));
}
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
Ok(())
}
async fn create_default_docker_compose() -> Result<()> {
let content = r#"version: '3.8'
services:
consul:
image: consul:latest
ports:
- "8500:8500"
command: agent -dev -ui -client=0.0.0.0
environment:
- CONSUL_BIND_INTERFACE=eth0
# Add your services here
"#;
tokio::fs::write("docker-compose.yml", content).await?;
print_success("Created default docker-compose.yml");
Ok(())
}
fn discover_services() -> Result<Vec<String>> {
let mut services = Vec::new();
if let Ok(content) = std::fs::read_to_string("Cargo.toml") {
if content.contains("[workspace]") {
for line in content.lines() {
if line.trim().starts_with('"') && line.contains("service") {
if let Some(service) = line.split('"').nth(1) {
services.push(service.to_string());
}
}
}
} else {
services.push("app".to_string());
}
}
Ok(services)
}
async fn start_service(service: &str) -> Result<()> {
print_info(&format!("Starting {}...", service));
let service_path = if service == "app" {
".".to_string()
} else {
service.to_string()
};
let mut envs = HashMap::new();
envs.insert("CONSUL_ADDR", "http://localhost:8500");
envs.insert("RUST_LOG", "info");
let mut child = TokioCommand::new("cargo")
.current_dir(&service_path)
.args(&["run"])
.envs(&envs)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.context(format!("Failed to start {}", service))?;
print_success(&format!("{} started", service));
let status = child.wait().await?;
if !status.success() {
print_error(&format!("{} exited with error", service));
}
Ok(())
}