Skip to main content

mai_cli/cli/
sync_cmd.rs

1use crate::core::config::{DependencySpec, LockFile, ProjectManifest};
2use crate::core::{Version, VersionReq};
3use crate::error::{Error, Result};
4use crate::storage::XdgPaths;
5use std::path::Path;
6
7#[derive(Debug, clap::Args)]
8pub struct SyncCommand;
9
10impl SyncCommand {
11    pub fn run(&self) -> Result<()> {
12        let mai_toml = Path::new("mai.toml");
13        if !mai_toml.exists() {
14            return Err(Error::project_error(
15                "mai.toml not found. Run 'mai init' first.",
16            ));
17        }
18
19        let manifest = ProjectManifest::load(mai_toml)?;
20        let paths = XdgPaths::new();
21        let mut lock_file = LockFile::new();
22
23        println!("Syncing project dependencies...");
24
25        let mut deps_by_tool: std::collections::HashMap<String, Vec<&DependencySpec>> =
26            std::collections::HashMap::new();
27        for dep in &manifest.dependencies {
28            let tool = dep.tool.clone().unwrap_or_else(|| "default".to_string());
29            deps_by_tool.entry(tool).or_default().push(dep);
30        }
31
32        for (tool, deps) in &deps_by_tool {
33            println!("\nTool: {}", tool);
34
35            for dep in deps {
36                print!("  Resolving {}@{}... ", dep.name, dep.version);
37
38                let version_req = VersionReq::parse(&dep.version).map_err(|e| {
39                    Error::version_error(format!("Invalid version for {}: {}", dep.name, e))
40                })?;
41
42                let pack_type_str = dep.pack_type.to_string();
43                let packs_dir = paths.data_dir().join("packs").join(&pack_type_str);
44
45                if !packs_dir.exists() {
46                    println!("✗ (packs directory not found)");
47                    continue;
48                }
49
50                let pack_dir = packs_dir.join(&dep.name);
51                if !pack_dir.exists() {
52                    println!("✗ (pack not found)");
53                    continue;
54                }
55
56                let mut available_versions = Vec::new();
57                if let Ok(entries) = std::fs::read_dir(&pack_dir) {
58                    for entry in entries.flatten() {
59                        if let Ok(name) = entry.file_name().into_string()
60                            && let Ok(v) = Version::parse(&name)
61                        {
62                            available_versions.push(v);
63                        }
64                    }
65                }
66
67                if available_versions.is_empty() {
68                    println!("✗ (no versions available)");
69                    continue;
70                }
71
72                let best_version =
73                    version_req
74                        .select_best(&available_versions)
75                        .ok_or_else(|| {
76                            Error::version_error(format!(
77                                "No matching version for {}@{}",
78                                dep.name, dep.version
79                            ))
80                        })?;
81
82                let version_path = pack_dir.join(best_version.to_string());
83                let content = std::fs::read(&version_path).unwrap_or_default();
84                let hash = crate::core::config::LockedDependency::compute_hash(&content);
85
86                let locked = crate::core::config::LockedDependency::new(
87                    &dep.name,
88                    dep.pack_type,
89                    best_version.clone(),
90                    hash,
91                )
92                .with_tool(tool);
93
94                lock_file.add_dependency(tool, locked);
95                println!("✓ {}", best_version);
96            }
97        }
98
99        lock_file.save(Path::new("mai.lock"))?;
100        println!(
101            "\n✓ Generated mai.lock with {} locked dependencies",
102            lock_file.packs.values().map(|v| v.len()).sum::<usize>()
103        );
104
105        Ok(())
106    }
107}