use std::env;
use std::path::PathBuf;
use std::process::Stdio;
use tokio::process::Command;
use crate::strategies::{XbpConfig, ServiceConfig, get_all_services, get_service_by_name};
use crate::commands::pm2::pm2_start;
use crate::logging::{log_info, log_success, log_debug};
pub async fn list_services(_debug: bool) -> Result<(), String> {
let config = load_xbp_config().await?;
let services = get_all_services(&config);
if services.is_empty() {
println!("No services configured.");
return Ok(());
}
println!("\nServices:");
println!("{:-<80}", "");
println!("{:<20} | {:<12} | {:<6} | {:<15} | {:<20}", "Name", "Target", "Port", "Branch", "URL");
println!("{:-<80}", "");
for service in &services {
let url = service.url.as_deref().unwrap_or("-");
println!(
"{:<20} | {:<12} | {:<6} | {:<15} | {:<20}",
service.name, service.target, service.port, service.branch, url
);
}
println!("{:-<80}", "");
println!("\nTotal: {} service(s)", services.len());
Ok(())
}
pub async fn get_service_config(name: &str) -> Result<ServiceConfig, String> {
let config = load_xbp_config().await?;
get_service_by_name(&config, name)
}
pub async fn run_service_command(
command: &str,
service_name: &str,
debug: bool,
) -> Result<(), String> {
let config = load_xbp_config().await?;
let service = get_service_by_name(&config, service_name)?;
let project_root = env::current_dir()
.map_err(|e| format!("Failed to get current directory: {}", e))?;
let working_dir = if service.force_run_from_root.unwrap_or(false) {
project_root.clone()
} else if let Some(root_dir) = &service.root_directory {
if root_dir.starts_with('/') {
PathBuf::from(root_dir)
} else {
project_root.clone().join(root_dir)
}
} else {
project_root.clone()
};
let _ = log_info(
"service",
&format!("Running '{}' for service '{}'", command, service_name),
Some(&format!("Working directory: {}", working_dir.display())),
)
.await;
let cmd_str = match command {
"pre" => service.commands.as_ref().and_then(|c| c.pre.as_ref()),
"install" => service.commands.as_ref().and_then(|c| c.install.as_ref()),
"build" => service.commands.as_ref().and_then(|c| c.build.as_ref()),
"start" => service.commands.as_ref().and_then(|c| c.start.as_ref()),
"dev" => service.commands.as_ref().and_then(|c| c.dev.as_ref()),
_ => return Err(format!("Unknown command: {}. Valid commands: pre, install, build, start, dev", command)),
};
let cmd_str = cmd_str.ok_or_else(|| format!("Command '{}' not configured for service '{}'", command, service_name))?;
if command == "build" && cmd_str.is_empty() {
let _ = log_info("service", "Build command is empty, skipping", None).await;
return Ok(());
}
if command == "start" && service.start_wrapper.as_deref() == Some("pm2") {
let log_dir = project_root.join(".xbp").join("logs").join(&service.name);
std::fs::create_dir_all(&log_dir)
.map_err(|e| format!("Failed to create log directory: {}", e))?;
let pm2_command = format!("{} --port {}", cmd_str, service.port);
let _ = log_info(
"service",
&format!("Starting service '{}' with PM2", service_name),
Some(&pm2_command),
)
.await;
pm2_start(&service.name, &pm2_command, Some(&log_dir), debug).await?;
let _ = log_success(
"service",
&format!("Service '{}' started successfully", service_name),
None,
)
.await;
return Ok(());
}
if command != "pre" {
if let Some(pre_cmd) = service.commands.as_ref().and_then(|c| c.pre.as_ref()) {
if !pre_cmd.is_empty() {
let _ = log_info("service", "Running pre-command", Some(pre_cmd)).await;
run_command_in_dir(&working_dir, pre_cmd, debug).await?;
}
}
}
run_command_in_dir(&working_dir, cmd_str, debug).await?;
let _ = log_success(
"service",
&format!("Command '{}' completed for service '{}'", command, service_name),
None,
)
.await;
Ok(())
}
async fn run_command_in_dir(dir: &PathBuf, command: &str, _debug: bool) -> Result<(), String> {
let _ = log_debug(
"service",
&format!("Executing: {} in {}", command, dir.display()),
None,
)
.await;
#[cfg(unix)]
let mut cmd = Command::new("sh");
#[cfg(unix)]
cmd.arg("-c");
#[cfg(windows)]
let mut cmd = Command::new("cmd");
#[cfg(windows)]
cmd.arg("/C");
cmd.arg(command);
cmd.current_dir(dir);
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
let status = cmd
.status()
.await
.map_err(|e| format!("Failed to execute command: {}", e))?;
if !status.success() {
return Err(format!(
"Command failed with exit code: {}",
status.code().unwrap_or(-1)
));
}
Ok(())
}
pub async fn load_xbp_config() -> Result<XbpConfig, String> {
let current_dir = env::current_dir()
.map_err(|e| format!("Failed to get current directory: {}", e))?;
let config_path = current_dir
.join(".xbp")
.join("xbp.json");
let config_path = if config_path.exists() {
config_path
} else {
let alt_path = current_dir.join("xbp.json");
if alt_path.exists() {
alt_path
} else {
return Err("No xbp.json found in current directory or .xbp/xbp.json".to_string());
}
};
let content = std::fs::read_to_string(&config_path)
.map_err(|e| format!("Failed to read config file: {}", e))?;
let config: XbpConfig = serde_json::from_str(&content)
.map_err(|e| format!("Failed to parse config file: {}", e))?;
Ok(config)
}
pub async fn is_xbp_project() -> bool {
let current_dir = match env::current_dir() {
Ok(dir) => dir,
Err(_) => return false,
};
current_dir.join(".xbp").join("xbp.json").exists()
|| current_dir.join("xbp.json").exists()
}
pub async fn show_service_help(service_name: &str) -> Result<(), String> {
let service = get_service_config(service_name).await?;
println!("\nService: {}", service.name);
println!("{:-<60}", "");
println!("Target: {}", service.target);
println!("Port: {}", service.port);
println!("Branch: {}", service.branch);
if let Some(url) = &service.url {
println!("URL: {}", url);
}
if let Some(root_dir) = &service.root_directory {
println!("Root Directory: {}", root_dir);
}
println!("Force Run From Root: {}", service.force_run_from_root.unwrap_or(false));
if let Some(commands) = &service.commands {
println!("\nAvailable Commands:");
println!("{:-<60}", "");
if commands.pre.is_some() {
println!(" xbp service pre {}", service_name);
}
if commands.install.is_some() {
println!(" xbp service install {}", service_name);
}
if commands.build.is_some() {
println!(" xbp service build {}", service_name);
}
if commands.start.is_some() {
println!(" xbp service start {}", service_name);
}
if commands.dev.is_some() {
println!(" xbp service dev {}", service_name);
}
}
println!("\nRedeploy:");
println!(" xbp redeploy {}", service_name);
Ok(())
}