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