dx_forge/version/
registry.rs1use anyhow::{ Context, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7
8use super::types::{Version, VersionReq};
9
10#[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#[derive(Debug, Clone, Serialize, Deserialize)]
22pub enum ToolSource {
23 Local(PathBuf),
25 Crate { version: String },
27 Git { url: String, rev: String },
29 R2 { bucket: String, key: String },
31}
32
33pub struct ToolRegistry {
37 registry_path: PathBuf,
38 tools: HashMap<String, ToolInfo>,
39}
40
41impl ToolRegistry {
42 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(®istry_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 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 pub fn get(&self, name: &str) -> Option<&ToolInfo> {
84 self.tools.get(name)
85 }
86
87 pub fn is_registered(&self, name: &str) -> bool {
89 self.tools.contains_key(name)
90 }
91
92 pub fn version(&self, name: &str) -> Option<&Version> {
94 self.tools.get(name).map(|info| &info.version)
95 }
96
97 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 pub fn list(&self) -> Vec<&ToolInfo> {
124 self.tools.values().collect()
125 }
126
127 pub fn unregister(&mut self, name: &str) -> Result<()> {
129 self.tools.remove(name);
130 self.save()?;
131 Ok(())
132 }
133
134 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 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}