use tokio::process::Command;
use std::path::PathBuf;
use std::fs;
pub async fn pm2_list(debug: bool) -> Result<(), String> {
let mut cmd = Command::new("pm2");
cmd.arg("list");
let output = cmd.output().await.map_err(|e| format!("failed to run pm2 list: {}", e))?;
if debug {
println!(
"[DEBUG] pm2 list status={:?} stdout='{}' stderr='{}'",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
if !output.status.success() {
return Err(String::from_utf8_lossy(&output.stderr).to_string());
}
print!("{}", String::from_utf8_lossy(&output.stdout));
Ok(())
}
pub async fn pm2_logs(project: Option<String>, debug: bool) -> Result<(), String> {
let mut cmd = Command::new("pm2");
cmd.arg("logs");
if let Some(name) = project {
cmd.arg(name);
}
let mut child = cmd
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.spawn()
.map_err(|e| format!("failed to spawn pm2 logs: {}", e))?;
if debug { println!("[DEBUG] spawned pm2 logs"); }
let status = child.wait().await.map_err(|e| format!("pm2 logs wait failed: {}", e))?;
if !status.success() { return Err(format!("pm2 logs exited with status: {}", status)); }
Ok(())
}
pub async fn pm2_stop(name: &str, debug: bool) -> Result<(), String> {
let mut cmd = Command::new("pm2");
cmd.arg("stop").arg(name);
let output = cmd.output().await.map_err(|e| format!("failed to run pm2 stop: {}", e))?;
if debug {
println!(
"[DEBUG] pm2 stop {} status={:?} stdout='{}' stderr='{}'",
name,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
Ok(())
}
pub async fn pm2_delete(name: &str, debug: bool) -> Result<(), String> {
let mut cmd = Command::new("pm2");
cmd.arg("delete").arg(name);
let output = cmd.output().await.map_err(|e| format!("failed to run pm2 delete: {}", e))?;
if debug {
println!(
"[DEBUG] pm2 delete {} status={:?} stdout='{}' stderr='{}'",
name,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
Ok(())
}
pub async fn pm2_start(
name: &str,
command: &str,
log_dir: Option<&PathBuf>,
debug: bool,
) -> Result<(), String> {
let mut cmd = Command::new("pm2");
cmd.arg("start");
if let Some(log_path) = log_dir {
let stdout_log = log_path.join(format!("{}-stdout.log", name));
let stderr_log = log_path.join(format!("{}-stderr.log", name));
if let Some(parent) = log_path.parent() {
fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create log directory: {}", e))?;
}
cmd.arg("--name").arg(name);
cmd.arg("--log").arg(stdout_log.to_string_lossy().as_ref());
cmd.arg("--error").arg(stderr_log.to_string_lossy().as_ref());
cmd.arg("--").arg(command);
} else {
cmd.arg("--name").arg(name);
cmd.arg("--").arg(command);
}
let output = cmd.output().await.map_err(|e| format!("failed to run pm2 start: {}", e))?;
if debug {
println!(
"[DEBUG] pm2 start {} status={:?} stdout='{}' stderr='{}'",
name,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
if !output.status.success() {
return Err(format!(
"pm2 start failed: {}",
String::from_utf8_lossy(&output.stderr)
));
}
Ok(())
}
pub async fn pm2_cleanup(debug: bool) -> Result<(), String> {
let mut cmd = Command::new("pm2");
cmd.arg("list").arg("--no-color");
let output = cmd.output().await.map_err(|e| format!("failed to run pm2 list: {}", e))?;
if !output.status.success() {
return Err(format!("Failed to list PM2 processes: {}", String::from_utf8_lossy(&output.stderr)));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut processes_to_delete = Vec::new();
for line in stdout.lines() {
if line.contains("stopped") || line.contains("errored") {
let parts: Vec<&str> = line.split('│').collect();
if parts.len() >= 3 {
let name = parts[2].trim();
if !name.is_empty() && name != "name" {
processes_to_delete.push(name.to_string());
}
}
}
}
for process_name in processes_to_delete {
if debug {
println!("[DEBUG] Deleting stopped/errored process: {}", process_name);
}
pm2_delete(&process_name, debug).await?;
}
let mut save_cmd = Command::new("pm2");
save_cmd.arg("save");
let save_output = save_cmd.output().await.map_err(|e| format!("failed to run pm2 save: {}", e))?;
if !save_output.status.success() {
return Err(format!("Failed to save PM2 process list: {}", String::from_utf8_lossy(&save_output.stderr)));
}
Ok(())
}
pub async fn pm2_save(debug: bool) -> Result<(), String> {
let mut cmd = Command::new("pm2");
cmd.arg("save");
let output = cmd.output().await.map_err(|e| format!("failed to run pm2 save: {}", e))?;
if debug {
println!(
"[DEBUG] pm2 save status={:?} stdout='{}' stderr='{}'",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
if !output.status.success() {
return Err(format!("Failed to save PM2 process list: {}", String::from_utf8_lossy(&output.stderr)));
}
Ok(())
}