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}