use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::collections::HashMap;
use colored::Colorize;
use extism::{Manifest, Plugin, Wasm, extism_fn};
use crate::error::{ForgeError, ForgeResult};
use crate::config::ForgeConfig;
pub struct PluginManager {
project_dir: PathBuf,
plugins: HashMap<String, Plugin>,
}
impl PluginManager {
pub fn new(config: &ForgeConfig, project_dir: &Path) -> ForgeResult<Self> {
let mut manager = Self {
project_dir: project_dir.to_path_buf(),
plugins: HashMap::new(),
};
if config.plugins.is_empty() {
return Ok(manager);
}
println!("{}", "🔌 Inicializando Subsistema de Plugins (WASM)...".cyan().bold());
for (name, source) in &config.plugins {
let wasm = if source.starts_with("http") {
Wasm::url(source)
} else {
let local_path = project_dir.join(source);
Wasm::file(local_path)
};
let path_str = manager.project_dir.to_string_lossy().to_string();
let manifest = Manifest::new([wasm])
.with_allowed_path(path_str, "/project");
let functions = [
extism::Function::from(forge_log_info),
];
match Plugin::new(&manifest, functions, true) {
Ok(plugin) => {
manager.plugins.insert(name.clone(), plugin);
println!(" {} Plugin '{}' cargado exitosamente.", "📦".green(), name);
}
Err(e) => {
eprintln!(" {} Fallo instanciando plugin '{}': {}", "⚠️ ".yellow(), name, e);
}
}
}
Ok(manager)
}
pub fn call_plugin<'a>(&mut self, plugin_name: &str, function: &str, input: impl extism::ToBytes<'a>) -> ForgeResult<Vec<u8>> {
let plugin = self.plugins.get_mut(plugin_name).ok_or_else(|| ForgeError::TaskFailed {
task_name: format!("Plugin '{}' no encontrado", plugin_name),
exit_code: 1,
})?;
let output = plugin.call::<_, Vec<u8>>(function, input).map_err(|e| ForgeError::TaskFailed {
task_name: format!("Error en WASM '{}::{}': {}", plugin_name, function, e),
exit_code: 1,
})?;
Ok(output)
}
pub fn has_plugin(&self, name: &str) -> bool {
self.plugins.contains_key(name)
}
}
extism_fn!(
forge_log_info(_plugin, msg: String) -> () {
println!(" {} {}", "🔌 Plugin:".cyan(), msg.dimmed());
}
);