1use crate::ui::UI;
4
5use vx_core::{FigmentConfigManager, PluginRegistry, Result, VxError};
6
7pub async fn handle(
8 registry: &PluginRegistry,
9 check: bool,
10 force: bool,
11 dry_run: bool,
12 verbose: bool,
13 no_parallel: bool,
14 no_auto_install: bool,
15) -> Result<()> {
16 let spinner = UI::new_spinner("Reading project configuration...");
17
18 let config_path = std::env::current_dir()
20 .map_err(|e| VxError::Other {
21 message: format!("Failed to get current directory: {}", e),
22 })?
23 .join(".vx.toml");
24
25 if !config_path.exists() {
26 spinner.finish_and_clear();
27 return handle_no_config().await;
28 }
29
30 let config_manager = FigmentConfigManager::new()?;
32 let config = config_manager.config();
33
34 spinner.finish_and_clear();
35
36 if config.tools.is_empty() {
37 UI::warn("No tools defined in .vx.toml");
38 return Ok(());
39 }
40
41 UI::header("Project Sync");
42 UI::info(&format!("Found {} tools to sync", config.tools.len()));
43
44 let mut sync_plan = Vec::new();
46 for (tool_name, tool_config) in &config.tools {
47 let version = tool_config.version.as_deref().unwrap_or("latest");
48 let status = check_tool_status(registry, tool_name, version).await?;
49 sync_plan.push(SyncItem {
50 name: tool_name.clone(),
51 version: version.to_string(),
52 status,
53 });
54 }
55
56 if check {
57 return display_check_results(&sync_plan);
58 }
59
60 if dry_run {
61 return display_dry_run(&sync_plan);
62 }
63
64 execute_sync(
66 registry,
67 &sync_plan,
68 force,
69 verbose,
70 !no_parallel,
71 !no_auto_install,
72 )
73 .await
74}
75
76async fn handle_no_config() -> Result<()> {
77 UI::warn("No .vx.toml configuration file found");
78 UI::info("Detecting project type...");
79
80 let current_dir = std::env::current_dir().map_err(|e| VxError::Other {
81 message: format!("Failed to get current directory: {}", e),
82 })?;
83
84 if current_dir.join("package.json").exists() {
86 suggest_node_config().await?;
87 } else if current_dir.join("pyproject.toml").exists()
88 || current_dir.join("requirements.txt").exists()
89 {
90 suggest_python_config().await?;
91 } else if current_dir.join("go.mod").exists() {
92 suggest_go_config().await?;
93 } else if current_dir.join("Cargo.toml").exists() {
94 suggest_rust_config().await?;
95 } else {
96 UI::info("No project type detected. Use 'vx init' to create a configuration file.");
97 }
98
99 Ok(())
100}
101
102async fn suggest_node_config() -> Result<()> {
103 UI::info("🔍 Detected Node.js project (package.json found)");
104 UI::info("💡 Suggested configuration:");
105 println!();
106 println!("[tools]");
107 println!("node = \"18.17.0\" # LTS version");
108 println!("npm = \"latest\"");
109 println!();
110 UI::info("Run 'vx init --template node' to create .vx.toml with these settings");
111 Ok(())
112}
113
114async fn suggest_python_config() -> Result<()> {
115 UI::info("🔍 Detected Python project");
116 UI::info("💡 Suggested configuration:");
117 println!();
118 println!("[tools]");
119 println!("python = \"3.11\"");
120 println!("uv = \"latest\"");
121 println!();
122 UI::info("Run 'vx init --template python' to create .vx.toml with these settings");
123 Ok(())
124}
125
126async fn suggest_go_config() -> Result<()> {
127 UI::info("🔍 Detected Go project (go.mod found)");
128 UI::info("💡 Suggested configuration:");
129 println!();
130 println!("[tools]");
131 println!("go = \"latest\"");
132 println!();
133 UI::info("Run 'vx init --template go' to create .vx.toml with these settings");
134 Ok(())
135}
136
137async fn suggest_rust_config() -> Result<()> {
138 UI::info("🔍 Detected Rust project (Cargo.toml found)");
139 UI::info("💡 Suggested configuration:");
140 println!();
141 println!("[tools]");
142 println!("cargo = \"latest\"");
143 println!();
144 UI::info("Run 'vx init --template rust' to create .vx.toml with these settings");
145 Ok(())
146}
147
148#[derive(Debug)]
149struct SyncItem {
150 name: String,
151 version: String,
152 status: ToolStatus,
153}
154
155#[derive(Debug)]
156#[allow(dead_code)]
157enum ToolStatus {
158 Installed(String), NotInstalled,
160 NotSupported,
161}
162
163async fn check_tool_status(
164 registry: &PluginRegistry,
165 tool_name: &str,
166 _version: &str,
167) -> Result<ToolStatus> {
168 if registry.supports_tool(tool_name) {
169 Ok(ToolStatus::NotInstalled)
172 } else {
173 Ok(ToolStatus::NotSupported)
174 }
175}
176
177fn display_check_results(sync_plan: &[SyncItem]) -> Result<()> {
178 UI::info("🔍 Checking project requirements...");
179 println!();
180
181 let mut installed_count = 0;
182 let mut missing_count = 0;
183 let mut unsupported_count = 0;
184
185 println!("Required tools:");
186 for item in sync_plan {
187 match &item.status {
188 ToolStatus::Installed(version) => {
189 println!(
190 " ✅ {}@{} (installed: {})",
191 item.name, item.version, version
192 );
193 installed_count += 1;
194 }
195 ToolStatus::NotInstalled => {
196 println!(" ❌ {}@{} (not installed)", item.name, item.version);
197 missing_count += 1;
198 }
199 ToolStatus::NotSupported => {
200 println!(" ⚠️ {}@{} (not supported)", item.name, item.version);
201 unsupported_count += 1;
202 }
203 }
204 }
205
206 println!();
207 println!("Summary:");
208 if installed_count > 0 {
209 println!(" - {} tools already installed", installed_count);
210 }
211 if missing_count > 0 {
212 println!(" - {} tools need installation", missing_count);
213 }
214 if unsupported_count > 0 {
215 println!(" - {} tools not supported", unsupported_count);
216 }
217
218 if missing_count > 0 {
219 println!();
220 UI::info("Run 'vx sync' to install missing tools.");
221 }
222
223 Ok(())
224}
225
226fn display_dry_run(sync_plan: &[SyncItem]) -> Result<()> {
227 UI::info("🔍 Sync plan preview:");
228 println!();
229
230 let mut will_install = Vec::new();
231 let mut will_skip = Vec::new();
232
233 for item in sync_plan {
234 match &item.status {
235 ToolStatus::Installed(_) => will_skip.push(item),
236 ToolStatus::NotInstalled => will_install.push(item),
237 ToolStatus::NotSupported => {
238 println!(" ⚠️ {}@{} (not supported)", item.name, item.version);
239 }
240 }
241 }
242
243 if !will_install.is_empty() {
244 println!("Will install:");
245 for item in &will_install {
246 println!(" 📦 {}@{}", item.name, item.version);
247 println!(
248 " - Install to: ~/.vx/tools/{}/{}/",
249 item.name, item.version
250 );
251 }
252 println!();
253 }
254
255 if !will_skip.is_empty() {
256 println!("Will skip:");
257 for item in &will_skip {
258 println!(" ⏭️ {}@{} (already installed)", item.name, item.version);
259 }
260 println!();
261 }
262
263 UI::info("Run 'vx sync' to execute this plan.");
264 Ok(())
265}
266
267async fn execute_sync(
268 _registry: &PluginRegistry,
269 sync_plan: &[SyncItem],
270 _force: bool,
271 verbose: bool,
272 _parallel: bool,
273 _auto_install: bool,
274) -> Result<()> {
275 UI::info("📦 Installing tools...");
276
277 let mut success_count = 0;
278 let mut error_count = 0;
279
280 for item in sync_plan {
281 match &item.status {
282 ToolStatus::Installed(_) if !_force => {
283 if verbose {
284 UI::info(&format!(
285 "⏭️ {}@{} (already installed)",
286 item.name, item.version
287 ));
288 }
289 }
290 ToolStatus::NotInstalled | ToolStatus::Installed(_) => {
291 if verbose {
292 UI::info(&format!("⬇️ Installing {}@{}...", item.name, item.version));
293 }
294
295 tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
298
299 UI::success(&format!(
300 "✅ {}@{} installed successfully",
301 item.name, item.version
302 ));
303 success_count += 1;
304 }
305 ToolStatus::NotSupported => {
306 UI::error(&format!(
307 "❌ {}@{} (not supported)",
308 item.name, item.version
309 ));
310 error_count += 1;
311 }
312 }
313 }
314
315 println!();
316 if error_count == 0 {
317 UI::success("🎉 Project sync completed! All tools are ready.");
318
319 if success_count > 0 {
320 println!();
321 println!("Next steps:");
322 for item in sync_plan {
323 if matches!(item.status, ToolStatus::NotInstalled) {
324 println!(" vx {} --version", item.name);
325 }
326 }
327 }
328 } else {
329 UI::error(&format!("❌ Sync completed with {} errors", error_count));
330 }
331
332 Ok(())
333}