Skip to main content

microclaw_engine/internal/clawhub/
lockfile.rs

1use crate::internal::clawhub::types::LockFile;
2use microclaw_core::error::MicroClawError;
3use std::collections::HashMap;
4use std::path::Path;
5
6/// Read lockfile from disk
7pub fn read_lockfile(path: &Path) -> Result<LockFile, MicroClawError> {
8    if !path.exists() {
9        return Ok(LockFile {
10            version: 1,
11            skills: HashMap::new(),
12        });
13    }
14    let content = std::fs::read_to_string(path)
15        .map_err(|e| MicroClawError::Config(format!("Failed to read lockfile: {}", e)))?;
16    let lock: LockFile = serde_json::from_str(&content)
17        .map_err(|e| MicroClawError::Config(format!("Failed to parse lockfile: {}", e)))?;
18    Ok(lock)
19}
20
21/// Write lockfile to disk
22pub fn write_lockfile(path: &Path, lock: &LockFile) -> Result<(), MicroClawError> {
23    if let Some(parent) = path.parent() {
24        std::fs::create_dir_all(parent)
25            .map_err(|e| MicroClawError::Config(format!("Failed to create lockfile dir: {}", e)))?;
26    }
27    let content = serde_json::to_string_pretty(lock)
28        .map_err(|e| MicroClawError::Config(format!("Failed to serialize lockfile: {}", e)))?;
29    std::fs::write(path, content)
30        .map_err(|e| MicroClawError::Config(format!("Failed to write lockfile: {}", e)))?;
31    Ok(())
32}
33
34/// Check if a skill is managed by ClawHub (in lockfile)
35pub fn is_clawhub_managed(lock: &LockFile, slug: &str) -> bool {
36    lock.skills.contains_key(slug)
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use crate::internal::clawhub::types::LockEntry;
43
44    #[test]
45    fn test_lockfile_missing_returns_empty() {
46        let temp_path = std::env::temp_dir().join(format!("nonexistent_{}", uuid::Uuid::new_v4()));
47        let lock = read_lockfile(&temp_path).unwrap();
48        assert_eq!(lock.version, 1);
49        assert!(lock.skills.is_empty());
50    }
51
52    #[test]
53    fn test_lockfile_roundtrip() {
54        let temp_dir = std::env::temp_dir().join(format!("clawhub_test_{}", uuid::Uuid::new_v4()));
55        std::fs::create_dir_all(&temp_dir).unwrap();
56        let lock_path = temp_dir.join("clawhub.lock.json");
57
58        let mut lock = LockFile {
59            version: 1,
60            skills: HashMap::new(),
61        };
62        lock.skills.insert(
63            "test-skill".into(),
64            LockEntry {
65                slug: "test-skill".into(),
66                installed_version: "1.0.0".into(),
67                installed_at: "2026-02-18T00:00:00Z".into(),
68                content_hash: "sha256:abc".into(),
69                tree_hash: None,
70                local_path: "/tmp/test".into(),
71            },
72        );
73
74        write_lockfile(&lock_path, &lock).unwrap();
75        let read = read_lockfile(&lock_path).unwrap();
76        assert_eq!(
77            read.skills.get("test-skill").unwrap().installed_version,
78            "1.0.0"
79        );
80
81        std::fs::remove_dir_all(&temp_dir).ok();
82    }
83
84    #[test]
85    fn test_is_clawhub_managed() {
86        let mut lock = LockFile {
87            version: 1,
88            skills: HashMap::new(),
89        };
90        lock.skills.insert(
91            "my-skill".into(),
92            LockEntry {
93                slug: "my-skill".into(),
94                installed_version: "1.0.0".into(),
95                installed_at: "2026-02-18T00:00:00Z".into(),
96                content_hash: "sha256:abc".into(),
97                tree_hash: None,
98                local_path: "/tmp/test".into(),
99            },
100        );
101
102        assert!(is_clawhub_managed(&lock, "my-skill"));
103        assert!(!is_clawhub_managed(&lock, "other-skill"));
104    }
105}