devalang_wasm/engine/plugin/
loader.rs

1#[cfg(feature = "cli")]
2use std::path::PathBuf;
3
4#[derive(Debug, Clone)]
5pub struct PluginInfo {
6    pub author: String,
7    pub name: String,
8    pub version: Option<String>,
9    pub description: Option<String>,
10    pub exports: Vec<PluginExport>,
11}
12
13#[derive(Debug, Clone)]
14pub struct PluginExport {
15    pub name: String,
16    pub kind: String,
17}
18
19#[cfg(feature = "cli")]
20pub fn load_plugin(author: &str, name: &str) -> Result<(PluginInfo, Vec<u8>), String> {
21    use serde::Deserialize;
22
23    #[derive(Debug, Deserialize)]
24    struct LocalPluginToml {
25        plugin: LocalPluginInfo,
26        #[serde(rename = "exports", default)]
27        exports: Vec<LocalExportEntry>,
28    }
29
30    #[derive(Debug, Deserialize)]
31    struct LocalPluginInfo {
32        name: String,
33        #[serde(default)]
34        version: Option<String>,
35        #[serde(default)]
36        description: Option<String>,
37        #[serde(default)]
38        publisher: Option<String>,
39    }
40
41    #[derive(Debug, Deserialize)]
42    struct LocalExportEntry {
43        name: String,
44        kind: String,
45    }
46
47    // Find .deva directory from current dir or parents
48    let deva_dir = find_deva_dir()?;
49
50    // Try new layout: .deva/plugins/<publisher>/<name>/
51    let plugin_dir = deva_dir.join("plugins").join(author).join(name);
52    let toml_path = plugin_dir.join("plugin.toml");
53    let wasm_path = plugin_dir.join(format!("{}.wasm", name));
54
55    if !toml_path.exists() {
56        return Err(format!("❌ Plugin file not found: {}", toml_path.display()));
57    }
58
59    if !wasm_path.exists() {
60        return Err(format!("❌ Plugin wasm not found: {}", wasm_path.display()));
61    }
62
63    // Load toml
64    let toml_content = std::fs::read_to_string(&toml_path)
65        .map_err(|e| format!("Failed to read '{}': {}", toml_path.display(), e))?;
66    let plugin_toml: LocalPluginToml = toml::from_str(&toml_content)
67        .map_err(|e| format!("Failed to parse '{}': {}", toml_path.display(), e))?;
68
69    // Load wasm bytes
70    let wasm_bytes = std::fs::read(&wasm_path)
71        .map_err(|e| format!("Failed to read '{}': {}", wasm_path.display(), e))?;
72
73    // Convert to PluginInfo
74    let info = PluginInfo {
75        author: plugin_toml
76            .plugin
77            .publisher
78            .unwrap_or_else(|| author.to_string()),
79        name: plugin_toml.plugin.name.clone(),
80        version: plugin_toml.plugin.version.clone(),
81        description: plugin_toml.plugin.description.clone(),
82        exports: plugin_toml
83            .exports
84            .iter()
85            .map(|e| PluginExport {
86                name: e.name.clone(),
87                kind: e.kind.clone(),
88            })
89            .collect(),
90    };
91
92    Ok((info, wasm_bytes))
93}
94
95#[cfg(feature = "cli")]
96fn find_deva_dir() -> Result<PathBuf, String> {
97    use std::env;
98
99    let current_dir =
100        env::current_dir().map_err(|e| format!("Failed to get current directory: {}", e))?;
101
102    // Start from current dir and walk up to find .deva
103    let mut dir = current_dir.as_path();
104    loop {
105        let deva_path = dir.join(".deva");
106        if deva_path.exists() && deva_path.is_dir() {
107            return Ok(deva_path);
108        }
109
110        // Move to parent directory
111        match dir.parent() {
112            Some(parent) => dir = parent,
113            None => {
114                return Err(
115                    "Could not find .deva directory in current directory or parents".to_string(),
116                );
117            }
118        }
119    }
120}