Skip to main content

aether_registry/
lib.rs

1//! Aether Package Registry
2//!
3//! Tracks installed node packages and resolves them for the manifest system.
4//! Packages are stored as JSON index files in `~/.aether/registry/`.
5
6use std::collections::HashMap;
7use std::path::PathBuf;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11#[derive(Debug, Error)]
12pub enum RegistryError {
13    #[error("Package not found: {0}")]
14    NotFound(String),
15    #[error("IO error: {0}")]
16    Io(#[from] std::io::Error),
17    #[error("JSON error: {0}")]
18    Json(#[from] serde_json::Error),
19}
20
21/// A package entry in the registry index.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct PackageEntry {
24    pub name: String,
25    pub version: String,
26    pub description: String,
27    pub author: String,
28    pub nodes: Vec<String>,
29    pub path: Option<PathBuf>,
30    pub checksum: Option<String>,
31}
32
33/// The local package registry.
34pub struct PackageRegistry {
35    pub root: PathBuf,
36    packages: HashMap<String, PackageEntry>,
37}
38
39impl PackageRegistry {
40    /// Open or create the registry at `~/.aether/registry/`.
41    pub fn open() -> Result<Self, RegistryError> {
42        let root = dirs_home().join(".aether").join("registry");
43        std::fs::create_dir_all(&root)?;
44        let mut reg = Self { root: root.clone(), packages: HashMap::new() };
45        reg.load_index()?;
46        Ok(reg)
47    }
48
49    /// Open a registry at a custom path (for testing).
50    pub fn open_at(root: PathBuf) -> Result<Self, RegistryError> {
51        std::fs::create_dir_all(&root)?;
52        let mut reg = Self { root, packages: HashMap::new() };
53        reg.load_index()?;
54        Ok(reg)
55    }
56
57    fn index_path(&self) -> PathBuf {
58        self.root.join("index.json")
59    }
60
61    fn load_index(&mut self) -> Result<(), RegistryError> {
62        let path = self.index_path();
63        if path.exists() {
64            let json = std::fs::read_to_string(&path)?;
65            let entries: Vec<PackageEntry> = serde_json::from_str(&json)?;
66            for e in entries {
67                self.packages.insert(e.name.clone(), e);
68            }
69        }
70        Ok(())
71    }
72
73    fn save_index(&self) -> Result<(), RegistryError> {
74        let entries: Vec<&PackageEntry> = self.packages.values().collect();
75        let json = serde_json::to_string_pretty(&entries)?;
76        std::fs::write(self.index_path(), json)?;
77        Ok(())
78    }
79
80    /// Install a package from a local path.
81    pub fn install_local(&mut self, entry: PackageEntry) -> Result<(), RegistryError> {
82        self.packages.insert(entry.name.clone(), entry);
83        self.save_index()
84    }
85
86    /// Remove a package.
87    pub fn remove(&mut self, name: &str) -> Result<(), RegistryError> {
88        if self.packages.remove(name).is_none() {
89            return Err(RegistryError::NotFound(name.to_string()));
90        }
91        self.save_index()
92    }
93
94    /// Look up a package by name.
95    pub fn get(&self, name: &str) -> Option<&PackageEntry> {
96        self.packages.get(name)
97    }
98
99    /// List all installed packages.
100    pub fn list(&self) -> Vec<&PackageEntry> {
101        let mut pkgs: Vec<_> = self.packages.values().collect();
102        pkgs.sort_by_key(|p| p.name.as_str());
103        pkgs
104    }
105
106    pub fn len(&self) -> usize { self.packages.len() }
107    pub fn is_empty(&self) -> bool { self.packages.is_empty() }
108}
109
110fn dirs_home() -> PathBuf {
111    std::env::var("HOME")
112        .or_else(|_| std::env::var("USERPROFILE"))
113        .map(PathBuf::from)
114        .unwrap_or_else(|_| PathBuf::from("."))
115}