use anyhow::{ Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use super::types::{Version, VersionReq};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolInfo {
pub name: String,
pub version: Version,
pub installed_at: chrono::DateTime<chrono::Utc>,
pub source: ToolSource,
pub dependencies: HashMap<String, VersionReq>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ToolSource {
Local(PathBuf),
Crate { version: String },
Git { url: String, rev: String },
R2 { bucket: String, key: String },
}
pub struct ToolRegistry {
registry_path: PathBuf,
tools: HashMap<String, ToolInfo>,
}
impl ToolRegistry {
pub fn new(forge_dir: &Path) -> Result<Self> {
let registry_path = forge_dir.join("tool_registry.json");
let tools = if registry_path.exists() {
let content = std::fs::read_to_string(®istry_path)
.context("Failed to read tool registry")?;
serde_json::from_str(&content).unwrap_or_default()
} else {
HashMap::new()
};
Ok(Self {
registry_path,
tools,
})
}
pub fn register(
&mut self,
name: String,
version: Version,
source: ToolSource,
dependencies: HashMap<String, VersionReq>,
) -> Result<()> {
let info = ToolInfo {
name: name.clone(),
version,
installed_at: chrono::Utc::now(),
source,
dependencies,
};
self.tools.insert(name, info);
self.save()?;
Ok(())
}
pub fn get(&self, name: &str) -> Option<&ToolInfo> {
self.tools.get(name)
}
pub fn is_registered(&self, name: &str) -> bool {
self.tools.contains_key(name)
}
pub fn version(&self, name: &str) -> Option<&Version> {
self.tools.get(name).map(|info| &info.version)
}
pub fn check_dependencies(&self, tool_name: &str) -> Result<Vec<String>> {
let mut missing = Vec::new();
if let Some(info) = self.tools.get(tool_name) {
for (dep_name, req) in &info.dependencies {
match self.tools.get(dep_name) {
Some(dep_info) => {
if !dep_info.version.satisfies(req) {
missing.push(format!(
"{} requires {} {}, but {} is installed",
tool_name, dep_name, req, dep_info.version
));
}
}
None => {
missing.push(format!("{} requires {} {}", tool_name, dep_name, req));
}
}
}
}
Ok(missing)
}
pub fn list(&self) -> Vec<&ToolInfo> {
self.tools.values().collect()
}
pub fn unregister(&mut self, name: &str) -> Result<()> {
self.tools.remove(name);
self.save()?;
Ok(())
}
pub fn needs_update(&self, name: &str, latest: &Version) -> bool {
if let Some(info) = self.tools.get(name) {
&info.version < latest
} else {
false
}
}
fn save(&self) -> Result<()> {
let content = serde_json::to_string_pretty(&self.tools)?;
std::fs::write(&self.registry_path, content)?;
Ok(())
}
}