use std::collections::HashMap;
use std::process::Command;
use async_trait::async_trait;
use butterflow_models::Error;
use butterflow_models::Result;
use crate::Runner;
pub struct DirectRunner;
impl DirectRunner {
pub fn new() -> Self {
Self
}
}
impl Default for DirectRunner {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Runner for DirectRunner {
async fn run_command(&self, command: &str, env: &HashMap<String, String>) -> Result<String> {
if command.starts_with("#!/") {
let temp_dir = std::env::temp_dir();
let file_name = format!("butterflow-script-{}.sh", uuid::Uuid::new_v4());
let script_path = temp_dir.join(file_name);
std::fs::write(&script_path, command).map_err(|e| {
Error::Runtime(format!("Failed to write script to temporary file: {}", e))
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&script_path)
.map_err(|e| Error::Runtime(format!("Failed to get file permissions: {}", e)))?
.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&script_path, perms).map_err(|e| {
Error::Runtime(format!("Failed to set file permissions: {}", e))
})?;
}
let mut cmd = Command::new(&script_path);
for (key, value) in env {
cmd.env(key, value);
}
let output = cmd
.output()
.map_err(|e| Error::Runtime(format!("Failed to execute command: {}", e)))?;
std::fs::remove_file(&script_path).ok();
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(Error::Runtime(format!(
"Command failed with exit code {}: {}",
output.status.code().unwrap_or(-1),
stderr
)));
}
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
Ok(stdout)
} else {
let shell = if cfg!(target_os = "windows") {
"cmd"
} else {
"sh"
};
let shell_arg = if cfg!(target_os = "windows") {
"/C"
} else {
"-c"
};
let mut cmd = Command::new(shell);
cmd.arg(shell_arg).arg(command);
for (key, value) in env {
cmd.env(key, value);
}
let output = cmd
.output()
.map_err(|e| Error::Runtime(format!("Failed to execute command: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(Error::Runtime(format!(
"Command failed with exit code {}: {}",
output.status.code().unwrap_or(-1),
stderr
)));
}
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
Ok(stdout)
}
}
}