dx_forge/version/
registry.rs

1//! Tool registry for managing installed DX tools
2
3use anyhow::{ Context, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7
8use super::types::{Version, VersionReq};
9
10/// Tool information for registry
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ToolInfo {
13    pub name: String,
14    pub version: Version,
15    pub installed_at: chrono::DateTime<chrono::Utc>,
16    pub source: ToolSource,
17    pub dependencies: HashMap<String, VersionReq>,
18}
19
20/// Source of a tool installation
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub enum ToolSource {
23    /// Local development
24    Local(PathBuf),
25    /// Published crate
26    Crate { version: String },
27    /// Git repository
28    Git { url: String, rev: String },
29    /// R2 storage
30    R2 { bucket: String, key: String },
31}
32
33/// DX Tool Version Registry
34///
35/// Manages installed tool versions, dependencies, and compatibility
36pub struct ToolRegistry {
37    registry_path: PathBuf,
38    tools: HashMap<String, ToolInfo>,
39}
40
41impl ToolRegistry {
42    /// Create or load a tool registry
43    pub fn new(forge_dir: &Path) -> Result<Self> {
44        let registry_path = forge_dir.join("tool_registry.json");
45
46        let tools = if registry_path.exists() {
47            let content = std::fs::read_to_string(&registry_path)
48                .context("Failed to read tool registry")?;
49            serde_json::from_str(&content).unwrap_or_default()
50        } else {
51            HashMap::new()
52        };
53
54        Ok(Self {
55            registry_path,
56            tools,
57        })
58    }
59
60    /// Register a new tool
61    pub fn register(
62        &mut self,
63        name: String,
64        version: Version,
65        source: ToolSource,
66        dependencies: HashMap<String, VersionReq>,
67    ) -> Result<()> {
68        let info = ToolInfo {
69            name: name.clone(),
70            version,
71            installed_at: chrono::Utc::now(),
72            source,
73            dependencies,
74        };
75
76        self.tools.insert(name, info);
77        self.save()?;
78
79        Ok(())
80    }
81
82    /// Get tool information
83    pub fn get(&self, name: &str) -> Option<&ToolInfo> {
84        self.tools.get(name)
85    }
86
87    /// Check if a tool is registered
88    pub fn is_registered(&self, name: &str) -> bool {
89        self.tools.contains_key(name)
90    }
91
92    /// Get tool version
93    pub fn version(&self, name: &str) -> Option<&Version> {
94        self.tools.get(name).map(|info| &info.version)
95    }
96
97    /// Check if all dependencies are satisfied
98    pub fn check_dependencies(&self, tool_name: &str) -> Result<Vec<String>> {
99        let mut missing = Vec::new();
100
101        if let Some(info) = self.tools.get(tool_name) {
102            for (dep_name, req) in &info.dependencies {
103                match self.tools.get(dep_name) {
104                    Some(dep_info) => {
105                        if !dep_info.version.satisfies(req) {
106                            missing.push(format!(
107                                "{} requires {} {}, but {} is installed",
108                                tool_name, dep_name, req, dep_info.version
109                            ));
110                        }
111                    }
112                    None => {
113                        missing.push(format!("{} requires {} {}", tool_name, dep_name, req));
114                    }
115                }
116            }
117        }
118
119        Ok(missing)
120    }
121
122    /// List all registered tools
123    pub fn list(&self) -> Vec<&ToolInfo> {
124        self.tools.values().collect()
125    }
126
127    /// Unregister a tool
128    pub fn unregister(&mut self, name: &str) -> Result<()> {
129        self.tools.remove(name);
130        self.save()?;
131        Ok(())
132    }
133
134    /// Check if an update is available
135    pub fn needs_update(&self, name: &str, latest: &Version) -> bool {
136        if let Some(info) = self.tools.get(name) {
137            &info.version < latest
138        } else {
139            false
140        }
141    }
142
143    /// Save registry to disk
144    fn save(&self) -> Result<()> {
145        let content = serde_json::to_string_pretty(&self.tools)?;
146        std::fs::write(&self.registry_path, content)?;
147        Ok(())
148    }
149}