use std::path::Path;
use std::process::Command;
use crate::error::{Error, Result};
use crate::wrappers::{WrapperGenerator, WrapperSpec, ApplicationType};
pub struct CliToolGenerator;
impl WrapperGenerator for CliToolGenerator {
fn generate_wrapper(&self, spec: &WrapperSpec) -> Result<String> {
let interactive = match &spec.app_type {
ApplicationType::CliTool { interactive } => *interactive,
_ => return Err(Error::WrapperGeneration {
reason: "Application type must be CliTool".to_string(),
wrapper_type: Some("cli_tool".to_string()),
}),
};
self.generate_code(spec, interactive)
}
fn compile_wrapper(&self, code: &str, output_path: &Path) -> Result<()> {
let temp_dir = tempfile::tempdir()
.map_err(|e| Error::WrapperGeneration {
reason: format!("Failed to create temporary directory: {}", e),
wrapper_type: Some("cli_tool".to_string()),
})?;
let src_path = temp_dir.path().join("src/main.rs");
std::fs::create_dir_all(src_path.parent().unwrap())
.map_err(|e| Error::WrapperGeneration {
reason: format!("Failed to create source directory: {}", e),
wrapper_type: Some("cli_tool".to_string()),
})?;
std::fs::write(&src_path, code)
.map_err(|e| Error::WrapperGeneration {
reason: format!("Failed to write source code: {}", e),
wrapper_type: Some("cli_tool".to_string()),
})?;
let cargo_toml = r#"[package]
name = "cli-wrapper"
version = "0.1.0"
edition = "2021"
[dependencies]
wasm-sandbox = { path = "../.." }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = "0.3"
"#.to_string();
std::fs::write(temp_dir.path().join("Cargo.toml"), cargo_toml)
.map_err(|e| Error::WrapperGeneration {
reason: format!("Failed to write Cargo.toml: {}", e),
wrapper_type: Some("cli_tool".to_string()),
})?;
let status = Command::new("cargo")
.current_dir(temp_dir.path())
.args(["build", "--target", "wasm32-wasi", "--release"])
.status()
.map_err(|e| Error::WrapperGeneration {
reason: format!("Failed to run cargo build: {}", e),
wrapper_type: Some("cli_tool".to_string()),
})?;
if !status.success() {
return Err(Error::WrapperGeneration {
reason: "Cargo build failed".to_string(),
wrapper_type: Some("cli_tool".to_string()),
});
}
let wasm_path = temp_dir.path()
.join("target/wasm32-wasi/release/cli-wrapper.wasm");
std::fs::copy(wasm_path, output_path)
.map_err(|e| Error::WrapperGeneration {
reason: format!("Failed to copy output file: {}", e),
wrapper_type: Some("cli_tool".to_string()),
})?;
Ok(())
}
}
impl CliToolGenerator {
fn generate_code(&self, spec: &WrapperSpec, interactive: bool) -> Result<String> {
let app_path = spec.app_path.to_string_lossy();
let args = spec.arguments.join(", ");
let env_vars = spec.environment.iter()
.map(|(k, v)| format!("\"{}\" => \"{}\"", k, v))
.collect::<Vec<_>>()
.join(",\n ");
let mut code = format!(
r#"//! CLI tool wrapper for WASM sandbox
use std::{{
process::{{Command, Stdio}},
io::{{BufRead, BufReader, Write}},
sync::{{Arc, Mutex}},
collections::HashMap,
}};
use wasm_sandbox::{{
WasmSandbox, InstanceId, SandboxConfig, InstanceConfig,
security::{{ResourceLimits, Capabilities}},
communication::CommunicationChannel,
}};
use serde_json::Value;
use tracing::{{info, error, debug}};
/// Main function
fn main() -> Result<(), Box<dyn std::error::Error>> {{
// Initialize logging
tracing_subscriber::fmt::init();
// Create sandbox
let mut sandbox = WasmSandbox::new()?;
// Create command
let mut command = Command::new("{app_path}")
.args([{args}])
.env_clear() // Start with a clean environment
.envs(vec![
{env_vars}
])
"#,
app_path = app_path,
args = args,
env_vars = env_vars,
);
if interactive {
code.push_str(
r#" .stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
// Get stdio handles
let mut stdin = command.stdin.take().expect("Failed to get stdin");
let stdout = command.stdout.take().expect("Failed to get stdout");
let stderr = command.stderr.take().expect("Failed to get stderr");
// Create readers for stdout and stderr
let stdout_reader = BufReader::new(stdout);
let stderr_reader = BufReader::new(stderr);
// Create communication channels
let stdin_channel = Arc::new(Mutex::new(Vec::new()));
let stdout_channel = Arc::new(Mutex::new(Vec::new()));
let stderr_channel = Arc::new(Mutex::new(Vec::new()));
// Spawn stdout reader thread
let stdout_channel_clone = stdout_channel.clone();
std::thread::spawn(move || {
let mut lines = stdout_reader.lines();
while let Some(Ok(line)) = lines.next() {
println!("{}", line);
stdout_channel_clone.lock().unwrap().push(line);
}
});
// Spawn stderr reader thread
let stderr_channel_clone = stderr_channel.clone();
std::thread::spawn(move || {
let mut lines = stderr_reader.lines();
while let Some(Ok(line)) = lines.next() {
eprintln!("{}", line);
stderr_channel_clone.lock().unwrap().push(line);
}
});
// Main input loop
let mut buffer = String::new();
loop {
// Read input
std::io::stdin().read_line(&mut buffer)?;
// Check for exit command
if buffer.trim() == "exit" {
break;
}
// Send input to process
stdin.write_all(buffer.as_bytes())?;
stdin.flush()?;
// Store in channel
stdin_channel.lock().unwrap().push(buffer.clone());
buffer.clear();
}
// Wait for process to exit
let status = command.wait()?;
info!("Process exited with status: {}", status);
Ok(())
"#
);
} else {
code.push_str(
r#" .stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
// Get stdio handles
let stdout = command.stdout.take().expect("Failed to get stdout");
let stderr = command.stderr.take().expect("Failed to get stderr");
// Create readers
let stdout_reader = BufReader::new(stdout);
let stderr_reader = BufReader::new(stderr);
// Read all stdout
let stdout_lines: Vec<String> = stdout_reader.lines()
.filter_map(|line| line.ok())
.collect();
// Read all stderr
let stderr_lines: Vec<String> = stderr_reader.lines()
.filter_map(|line| line.ok())
.collect();
// Print output
for line in &stdout_lines {
println!("{}", line);
}
for line in &stderr_lines {
eprintln!("{}", line);
}
// Wait for process to exit
let status = command.wait()?;
info!("Process exited with status: {}", status);
Ok(())
"#
);
}
code.push_str("}\n");
Ok(code)
}
}