vx_cli/commands/
init.rs

1// Init command implementation
2
3use crate::ui::UI;
4use std::collections::HashMap;
5use std::fs;
6use std::io::{self, Write};
7
8use vx_core::{Result, VxError};
9
10pub async fn handle(
11    interactive: bool,
12    template: Option<String>,
13    tools: Option<String>,
14    force: bool,
15    dry_run: bool,
16    list_templates: bool,
17) -> Result<()> {
18    if list_templates {
19        return list_available_templates();
20    }
21
22    let config_path = std::env::current_dir()
23        .map_err(|e| VxError::Other {
24            message: format!("Failed to get current directory: {}", e),
25        })?
26        .join(".vx.toml");
27
28    // Check if config already exists
29    if config_path.exists() && !force {
30        UI::warn("Configuration file .vx.toml already exists");
31        UI::info("Use --force to overwrite or edit the existing file");
32        return Ok(());
33    }
34
35    let config_content = if interactive {
36        generate_interactive_config().await?
37    } else if let Some(template_name) = template {
38        generate_template_config(&template_name)?
39    } else if let Some(tools_str) = tools {
40        generate_tools_config(&tools_str)?
41    } else {
42        generate_auto_detected_config().await?
43    };
44
45    if dry_run {
46        UI::info("Preview of .vx.toml configuration:");
47        println!();
48        println!("{}", config_content);
49        return Ok(());
50    }
51
52    // Write configuration file
53    fs::write(&config_path, config_content).map_err(|e| VxError::Other {
54        message: format!("Failed to write .vx.toml: {}", e),
55    })?;
56
57    UI::success("✅ Created .vx.toml configuration file");
58
59    // Show next steps
60    println!();
61    println!("Next steps:");
62    println!("  1. Review the configuration: cat .vx.toml");
63    println!("  2. Install tools: vx sync");
64    println!("  3. Start using tools: vx <tool> --version");
65    println!();
66    println!("Optional:");
67    println!("  - Add to version control: git add .vx.toml");
68    println!("  - Customize configuration: vx config edit --local");
69
70    Ok(())
71}
72
73fn list_available_templates() -> Result<()> {
74    UI::info("Available templates:");
75    println!();
76    println!("  node        - Node.js project with npm");
77    println!("  python      - Python project with uv");
78    println!("  rust        - Rust project with cargo");
79    println!("  go          - Go project");
80    println!("  fullstack   - Full-stack project (Node.js + Python)");
81    println!("  minimal     - Minimal configuration");
82    println!();
83    println!("Usage: vx init --template <template>");
84    Ok(())
85}
86
87async fn generate_interactive_config() -> Result<String> {
88    UI::header("🚀 VX Project Initialization");
89
90    // Get project name
91    print!("Project name (optional): ");
92    io::stdout().flush().unwrap();
93    let mut project_name = String::new();
94    io::stdin().read_line(&mut project_name).unwrap();
95    let project_name = project_name.trim();
96
97    // Get description
98    print!("Description (optional): ");
99    io::stdout().flush().unwrap();
100    let mut description = String::new();
101    io::stdin().read_line(&mut description).unwrap();
102    let description = description.trim();
103
104    // Select tools
105    println!();
106    println!("Select tools to include:");
107    let available_tools = vec![
108        ("node", "18.17.0", "Node.js JavaScript runtime"),
109        ("npm", "latest", "Node.js package manager"),
110        ("python", "3.11", "Python interpreter"),
111        ("uv", "latest", "Fast Python package manager"),
112        ("go", "latest", "Go programming language"),
113        ("cargo", "latest", "Rust package manager"),
114    ];
115
116    let mut selected_tools = HashMap::new();
117    for (tool, default_version, desc) in &available_tools {
118        print!("Include {} ({})? (y/N): ", tool, desc);
119        io::stdout().flush().unwrap();
120        let mut input = String::new();
121        io::stdin().read_line(&mut input).unwrap();
122        if input.trim().to_lowercase().starts_with('y') {
123            selected_tools.insert(tool.to_string(), default_version.to_string());
124        }
125    }
126
127    if selected_tools.is_empty() {
128        selected_tools.insert("node".to_string(), "18.17.0".to_string());
129        UI::info("No tools selected, adding Node.js as default");
130    }
131
132    generate_config_content(project_name, description, &selected_tools, true)
133}
134
135fn generate_template_config(template_name: &str) -> Result<String> {
136    let tools = match template_name {
137        "node" => {
138            let mut tools = HashMap::new();
139            tools.insert("node".to_string(), "18.17.0".to_string());
140            tools.insert("npm".to_string(), "latest".to_string());
141            tools
142        }
143        "python" => {
144            let mut tools = HashMap::new();
145            tools.insert("python".to_string(), "3.11".to_string());
146            tools.insert("uv".to_string(), "latest".to_string());
147            tools
148        }
149        "rust" => {
150            let mut tools = HashMap::new();
151            tools.insert("cargo".to_string(), "latest".to_string());
152            tools
153        }
154        "go" => {
155            let mut tools = HashMap::new();
156            tools.insert("go".to_string(), "latest".to_string());
157            tools
158        }
159        "fullstack" => {
160            let mut tools = HashMap::new();
161            tools.insert("node".to_string(), "18.17.0".to_string());
162            tools.insert("python".to_string(), "3.11".to_string());
163            tools.insert("uv".to_string(), "latest".to_string());
164            tools
165        }
166        "minimal" => HashMap::new(),
167        _ => {
168            return Err(VxError::Other {
169                message: format!(
170                    "Unknown template: {}. Use --list-templates to see available templates.",
171                    template_name
172                ),
173            });
174        }
175    };
176
177    generate_config_content("", "", &tools, false)
178}
179
180fn generate_tools_config(tools_str: &str) -> Result<String> {
181    let mut tools = HashMap::new();
182
183    for tool_spec in tools_str.split(',') {
184        let tool_spec = tool_spec.trim();
185        if tool_spec.contains('@') {
186            let parts: Vec<&str> = tool_spec.split('@').collect();
187            if parts.len() == 2 {
188                tools.insert(parts[0].to_string(), parts[1].to_string());
189            }
190        } else {
191            tools.insert(tool_spec.to_string(), "latest".to_string());
192        }
193    }
194
195    generate_config_content("", "", &tools, false)
196}
197
198async fn generate_auto_detected_config() -> Result<String> {
199    let current_dir = std::env::current_dir().map_err(|e| VxError::Other {
200        message: format!("Failed to get current directory: {}", e),
201    })?;
202
203    let mut tools = HashMap::new();
204    let mut detected_types = Vec::new();
205
206    // Check for Node.js project
207    if current_dir.join("package.json").exists() {
208        tools.insert("node".to_string(), "18.17.0".to_string());
209        tools.insert("npm".to_string(), "latest".to_string());
210        detected_types.push("Node.js");
211        UI::info("🔍 Detected Node.js project (package.json found)");
212    }
213
214    // Check for Python project
215    if current_dir.join("pyproject.toml").exists() || current_dir.join("requirements.txt").exists()
216    {
217        tools.insert("python".to_string(), "3.11".to_string());
218        tools.insert("uv".to_string(), "latest".to_string());
219        detected_types.push("Python");
220        UI::info("🔍 Detected Python project");
221    }
222
223    // Check for Go project
224    if current_dir.join("go.mod").exists() {
225        tools.insert("go".to_string(), "latest".to_string());
226        detected_types.push("Go");
227        UI::info("🔍 Detected Go project (go.mod found)");
228    }
229
230    // Check for Rust project
231    if current_dir.join("Cargo.toml").exists() {
232        tools.insert("cargo".to_string(), "latest".to_string());
233        detected_types.push("Rust");
234        UI::info("🔍 Detected Rust project (Cargo.toml found)");
235    }
236
237    if tools.is_empty() {
238        UI::info("No project type detected, creating minimal configuration");
239        tools.insert("node".to_string(), "18.17.0".to_string());
240    } else if detected_types.len() > 1 {
241        UI::info(&format!(
242            "🔍 Detected mixed project ({})",
243            detected_types.join(" + ")
244        ));
245    }
246
247    generate_config_content("", "", &tools, false)
248}
249
250fn generate_config_content(
251    project_name: &str,
252    description: &str,
253    tools: &HashMap<String, String>,
254    include_extras: bool,
255) -> Result<String> {
256    let mut content = String::new();
257
258    // Header comment
259    content.push_str("# VX Project Configuration\n");
260    content.push_str("# This file defines the tools and versions required for this project.\n");
261    content.push_str("# Run 'vx sync' to install all required tools.\n");
262
263    if !project_name.is_empty() {
264        content.push_str(&format!("# Project: {}\n", project_name));
265    }
266    if !description.is_empty() {
267        content.push_str(&format!("# Description: {}\n", description));
268    }
269
270    content.push('\n');
271
272    // Tools section
273    content.push_str("[tools]\n");
274    if tools.is_empty() {
275        content.push_str("# Add your tools here, for example:\n");
276        content.push_str("# node = \"18.17.0\"\n");
277        content.push_str("# python = \"3.11\"\n");
278        content.push_str("# uv = \"latest\"\n");
279    } else {
280        for (tool, version) in tools {
281            content.push_str(&format!("{} = \"{}\"\n", tool, version));
282        }
283    }
284
285    content.push('\n');
286
287    // Settings section
288    content.push_str("[settings]\n");
289    content.push_str("auto_install = true\n");
290    content.push_str("cache_duration = \"7d\"\n");
291
292    if include_extras {
293        content.push_str("parallel_install = true\n");
294        content.push('\n');
295
296        // Scripts section
297        content.push_str("[scripts]\n");
298        content.push_str("# Add custom scripts here\n");
299        content.push_str("# dev = \"vx node server.js\"\n");
300        content.push_str("# test = \"vx uv run pytest\"\n");
301        content.push('\n');
302
303        // Environment section
304        content.push_str("[env]\n");
305        content.push_str("# Add environment variables here\n");
306        content.push_str("# NODE_ENV = \"development\"\n");
307        content.push_str("# DEBUG = \"true\"\n");
308    }
309
310    Ok(content)
311}