vika_cli/commands/
init.rs

1use crate::config::loader::save_config;
2use crate::config::model::Config;
3use crate::config::validator::validate_config;
4use crate::error::{GenerationError, Result};
5use crate::generator::writer::{ensure_directory, write_http_client_template};
6use colored::*;
7use dialoguer::{Confirm, Input, Select};
8use std::path::PathBuf;
9
10pub fn run() -> Result<()> {
11    println!("{}", "Initializing vika-cli project...".bright_cyan());
12    println!();
13
14    // Check if config already exists
15    let config_path = PathBuf::from(".vika.json");
16    if config_path.exists() {
17        println!(
18            "{}",
19            "⚠️  .vika.json already exists. Skipping config creation.".yellow()
20        );
21    } else {
22        println!("{}", "Let's configure your vika-cli preferences:".bright_cyan());
23        println!();
24
25        // Paths configuration
26        println!("{}", "📁 Paths Configuration".bright_yellow());
27        println!();
28
29        let root_dir: String = Input::new()
30            .with_prompt("Root directory for generated code")
31            .default("src".to_string())
32            .interact_text()
33            .map_err(|e| GenerationError::InvalidOperation {
34                message: format!("Failed to get user input: {}", e),
35            })?;
36
37        println!();
38
39        let schemas_output: String = Input::new()
40            .with_prompt("Schemas output directory")
41            .default("src/schemas".to_string())
42            .interact_text()
43            .map_err(|e| GenerationError::InvalidOperation {
44                message: format!("Failed to get user input: {}", e),
45            })?;
46
47        println!();
48
49        let apis_output: String = Input::new()
50            .with_prompt("APIs output directory")
51            .default("src/apis".to_string())
52            .interact_text()
53            .map_err(|e| GenerationError::InvalidOperation {
54                message: format!("Failed to get user input: {}", e),
55            })?;
56
57        println!();
58
59        // Schema naming convention
60        let naming_options = vec!["PascalCase", "camelCase", "snake_case", "kebab-case"];
61        let naming_index = Select::new()
62            .with_prompt("Schema naming convention")
63            .items(&[
64                "PascalCase - ProductDto, UserProfile (recommended)",
65                "camelCase - productDto, userProfile",
66                "snake_case - product_dto, user_profile",
67                "kebab-case - product-dto, user-profile",
68            ])
69            .default(0)
70            .interact()
71            .map_err(|e| GenerationError::InvalidOperation {
72                message: format!("Failed to get user selection: {}", e),
73            })?;
74
75        let naming = naming_options[naming_index].to_string();
76
77        println!();
78
79        // API configuration
80        println!("{}", "🔌 API Configuration".bright_yellow());
81        println!();
82
83        let api_style_options = vec!["fetch"];
84        let api_style_index = Select::new()
85            .with_prompt("API client style")
86            .items(&["fetch - Native Fetch API (recommended)"])
87            .default(0)
88            .interact()
89            .map_err(|e| GenerationError::InvalidOperation {
90                message: format!("Failed to get user selection: {}", e),
91            })?;
92
93        let api_style = api_style_options[api_style_index].to_string();
94
95        println!();
96
97        let base_url_input: String = Input::new()
98            .with_prompt("API base URL (optional, press Enter to skip)")
99            .allow_empty(true)
100            .interact_text()
101            .map_err(|e| GenerationError::InvalidOperation {
102                message: format!("Failed to get user input: {}", e),
103            })?;
104
105        let base_url = if base_url_input.trim().is_empty() {
106            None
107        } else {
108            Some(base_url_input.trim().to_string())
109        };
110
111        println!();
112
113        let header_strategy_options = vec!["consumerInjected", "bearerToken", "fixed"];
114        let header_strategy_index = Select::new()
115            .with_prompt("Header strategy for API requests")
116            .items(&[
117                "consumerInjected - Headers provided by consumer (recommended)",
118                "bearerToken - Automatic Bearer token injection",
119                "fixed - Fixed headers from config",
120            ])
121            .default(0)
122            .interact()
123            .map_err(|e| GenerationError::InvalidOperation {
124                message: format!("Failed to get user selection: {}", e),
125            })?;
126
127        let header_strategy = header_strategy_options[header_strategy_index].to_string();
128
129        println!();
130
131        // Generation preferences
132        println!("{}", "⚙️  Generation Preferences".bright_yellow());
133        println!();
134
135        let enable_cache = Confirm::new()
136            .with_prompt("Enable caching for faster regeneration?")
137            .default(true)
138            .interact()
139            .map_err(|e| GenerationError::InvalidOperation {
140                message: format!("Failed to get user input: {}", e),
141            })?;
142
143        println!();
144
145        let enable_backup = Confirm::new()
146            .with_prompt("Enable automatic backups before overwriting files?")
147            .default(false)
148            .interact()
149            .map_err(|e| GenerationError::InvalidOperation {
150                message: format!("Failed to get user input: {}", e),
151            })?;
152
153        println!();
154
155        let conflict_strategy_options = vec!["ask", "force", "skip"];
156        let conflict_strategy_index = Select::new()
157            .with_prompt("What should happen when a file was modified by you?")
158            .items(&[
159                "ask - Prompt before overwriting (recommended)",
160                "force - Always overwrite without asking",
161                "skip - Skip modified files",
162            ])
163            .default(0)
164            .interact()
165            .map_err(|e| GenerationError::InvalidOperation {
166                message: format!("Failed to get user selection: {}", e),
167            })?;
168
169        let conflict_strategy = conflict_strategy_options[conflict_strategy_index].to_string();
170
171        println!();
172
173        // Create config with user preferences
174        let mut config = Config::default();
175        config.root_dir = root_dir;
176        config.schemas.output = schemas_output;
177        config.schemas.naming = naming;
178        config.apis.output = apis_output;
179        config.apis.style = api_style;
180        config.apis.base_url = base_url;
181        config.apis.header_strategy = header_strategy;
182        config.generation.enable_cache = enable_cache;
183        config.generation.enable_backup = enable_backup;
184        config.generation.conflict_strategy = conflict_strategy;
185
186        validate_config(&config)?;
187        save_config(&config)?;
188        println!("{}", "✅ Created .vika.json".green());
189    }
190
191    // Create directory structure
192    let config = crate::config::loader::load_config()?;
193
194    let root_dir = PathBuf::from(&config.root_dir);
195    ensure_directory(&root_dir)?;
196
197    let schemas_dir = PathBuf::from(&config.schemas.output);
198    ensure_directory(&schemas_dir)?;
199
200    let apis_dir = PathBuf::from(&config.apis.output);
201    ensure_directory(&apis_dir)?;
202
203    // Write http client template
204    let http_client_path = apis_dir.join("http.ts");
205    if !http_client_path.exists() {
206        write_http_client_template(&http_client_path)?;
207        println!(
208            "{}",
209            format!("✅ Created {}", http_client_path.display()).green()
210        );
211    } else {
212        println!(
213            "{}",
214            format!(
215                "⚠️  {} already exists. Skipping.",
216                http_client_path.display()
217            )
218            .yellow()
219        );
220    }
221
222    println!();
223    println!("{}", "✨ Project initialized successfully!".bright_green());
224    println!();
225    println!("Next steps:");
226    println!("  1. Run: vika-cli generate --spec <path-or-url-to-swagger>");
227    println!("  2. Select the modules you want to generate");
228
229    Ok(())
230}