atomcode_core/plugin/
loader.rs1use std::path::PathBuf;
5
6use super::manifest::{load_plugin_manifest, PluginManifest};
7use super::paths;
8use super::state::load_installed_plugins_file;
9
10#[derive(Debug, Clone)]
11pub struct InstalledPluginAssets {
12 pub plugin: String,
13 pub marketplace: String,
14 pub plugin_dir: PathBuf,
15 pub manifest: PluginManifest,
16}
17
18impl InstalledPluginAssets {
19 pub fn skills_dir(&self) -> PathBuf {
22 self.plugin_dir.join(self.manifest.skills_path())
23 }
24 pub fn skills_dirs(&self) -> Vec<PathBuf> {
32 self.manifest
33 .skills_paths()
34 .into_iter()
35 .map(|p| self.plugin_dir.join(p))
36 .collect()
37 }
38 pub fn commands_dir(&self) -> PathBuf {
39 self.plugin_dir.join(self.manifest.commands_path())
40 }
41 pub fn hooks_file(&self) -> PathBuf {
42 self.plugin_dir.join(self.manifest.hooks_path())
43 }
44}
45
46pub fn iter_installed_plugin_assets() -> Vec<InstalledPluginAssets> {
50 let Some(state_path) = paths::installed_plugins_file() else { return vec![]; };
51 let state = match load_installed_plugins_file(&state_path) {
52 Ok(s) => s,
53 Err(_) => return vec![],
54 };
55 let Some(plugins_root) = paths::plugins_root() else { return vec![]; };
56
57 state
58 .plugins
59 .into_values()
60 .filter_map(|e| {
61 let abs = plugins_root.join(&e.plugin_dir);
62 if !abs.exists() {
63 return None;
64 }
65 let manifest = load_plugin_manifest(&abs).unwrap_or_default();
66 Some(InstalledPluginAssets {
67 plugin: e.plugin,
68 marketplace: e.marketplace,
69 plugin_dir: abs,
70 manifest,
71 })
72 })
73 .collect()
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use crate::plugin::installer::install;
80 use crate::plugin::marketplace::add_marketplace;
81 use crate::plugin::test_support::isolated_home;
82 use std::path::PathBuf;
83 use std::process::Command;
84
85 fn make_repo(name: &str) -> PathBuf {
86 let work = tempfile::tempdir().unwrap().keep();
87 let repo = work.join(name);
88 std::fs::create_dir_all(&repo).unwrap();
89 Command::new("git").args(["init", "-q"]).current_dir(&repo).status().unwrap();
90 Command::new("git").args(["config", "user.email", "t@t"]).current_dir(&repo).status().unwrap();
91 Command::new("git").args(["config", "user.name", "t"]).current_dir(&repo).status().unwrap();
92 std::fs::create_dir_all(repo.join("skills/foo")).unwrap();
93 std::fs::write(
94 repo.join("skills/foo/SKILL.md"),
95 "---\nname: foo\ndescription: f\n---\nbody",
96 )
97 .unwrap();
98 Command::new("git").args(["add", "-A"]).current_dir(&repo).status().unwrap();
99 Command::new("git").args(["commit", "-q", "-m", "init"]).current_dir(&repo).status().unwrap();
100 repo
101 }
102
103 #[test]
104 #[serial_test::serial]
105 fn iter_yields_installed() {
106 let _home = isolated_home();
107 let repo = make_repo("p");
108 add_marketplace(&format!("file://{}", repo.display())).unwrap();
109 install("p", "p").unwrap();
110 let assets = iter_installed_plugin_assets();
111 assert_eq!(assets.len(), 1);
112 assert_eq!(assets[0].plugin, "p");
113 assert!(assets[0].skills_dir().exists());
114 }
115}