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