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_runtime_client};
6use colored::*;
7use dialoguer::{Confirm, Input, Select};
8use std::path::PathBuf;
9
10pub async fn run() -> Result<()> {
11 println!("{}", "Initializing vika-cli project...".bright_cyan());
12 println!();
13
14 let config_path = PathBuf::from(".vika.json");
16 if config_path.exists() {
17 return Err(GenerationError::InvalidOperation {
18 message: ".vika.json already exists. Use 'vika-cli generate' to generate code or 'vika-cli update' to update existing code.".to_string(),
19 }.into());
20 } else {
21 println!(
22 "{}",
23 "Let's configure your vika-cli preferences:".bright_cyan()
24 );
25 println!();
26
27 println!("{}", "⚙️ Global Configuration".bright_yellow());
29 println!();
30
31 let root_dir: String = Input::new()
32 .with_prompt("Root directory for generated code")
33 .default("src".to_string())
34 .interact_text()
35 .map_err(|e| GenerationError::InvalidOperation {
36 message: format!("Failed to get user input: {}", e),
37 })?;
38
39 println!();
40
41 println!("{}", "⚙️ Generation Preferences".bright_yellow());
43 println!();
44
45 let enable_cache = Confirm::new()
46 .with_prompt("Enable caching for faster regeneration?")
47 .default(true)
48 .interact()
49 .map_err(|e| GenerationError::InvalidOperation {
50 message: format!("Failed to get user input: {}", e),
51 })?;
52
53 println!();
54
55 let enable_backup = Confirm::new()
56 .with_prompt("Enable automatic backups before overwriting files?")
57 .default(false)
58 .interact()
59 .map_err(|e| GenerationError::InvalidOperation {
60 message: format!("Failed to get user input: {}", e),
61 })?;
62
63 println!();
64
65 let conflict_strategy_options = ["ask", "force", "skip"];
66 let conflict_strategy_index = Select::new()
67 .with_prompt("What should happen when a file was modified by you?")
68 .items(&[
69 "ask - Prompt before overwriting (recommended)",
70 "force - Always overwrite without asking",
71 "skip - Skip modified files",
72 ])
73 .default(0)
74 .interact()
75 .map_err(|e| GenerationError::InvalidOperation {
76 message: format!("Failed to get user selection: {}", e),
77 })?;
78
79 let conflict_strategy = conflict_strategy_options[conflict_strategy_index].to_string();
80
81 println!();
82
83 println!("{}", "📋 Spec Configuration".bright_yellow());
85 println!();
86
87 println!("{}", "Enter your OpenAPI specification:".bright_cyan());
89 println!();
90
91 let spec_name: String = Input::new()
92 .with_prompt("Spec name (kebab-case, e.g., 'ecommerce', 'auth-api')")
93 .interact_text()
94 .map_err(|e| GenerationError::InvalidOperation {
95 message: format!("Failed to get user input: {}", e),
96 })?;
97
98 let spec_path_input: String = Input::new()
99 .with_prompt(format!("Path or URL for '{}'", spec_name.trim()))
100 .interact_text()
101 .map_err(|e| GenerationError::InvalidOperation {
102 message: format!("Failed to get user input: {}", e),
103 })?;
104
105 println!();
106 println!(
107 "{}",
108 format!("📋 Configuration for spec '{}'", spec_name.trim()).bright_cyan()
109 );
110 println!();
111
112 println!("{}", "📁 Schemas Configuration".bright_yellow());
114 println!();
115
116 let spec_schemas_output: String = Input::new()
117 .with_prompt(format!(
118 "Schemas output directory for '{}'",
119 spec_name.trim()
120 ))
121 .default(format!("src/schemas/{}", spec_name.trim()))
122 .interact_text()
123 .map_err(|e| GenerationError::InvalidOperation {
124 message: format!("Failed to get user input: {}", e),
125 })?;
126
127 println!();
128
129 let spec_naming_options = ["PascalCase", "camelCase", "snake_case", "kebab-case"];
130 let spec_naming_index = Select::new()
131 .with_prompt(format!(
132 "Schema naming convention for '{}'",
133 spec_name.trim()
134 ))
135 .items(&[
136 "PascalCase - ProductDto, UserProfile (recommended)",
137 "camelCase - productDto, userProfile",
138 "snake_case - product_dto, user_profile",
139 "kebab-case - product-dto, user-profile",
140 ])
141 .default(0)
142 .interact()
143 .map_err(|e| GenerationError::InvalidOperation {
144 message: format!("Failed to get user selection: {}", e),
145 })?;
146 let spec_naming = spec_naming_options[spec_naming_index].to_string();
147
148 println!();
149
150 println!("{}", "🔌 API Configuration".bright_yellow());
152 println!();
153
154 let spec_apis_output: String = Input::new()
155 .with_prompt(format!("APIs output directory for '{}'", spec_name.trim()))
156 .default(format!("src/apis/{}", spec_name.trim()))
157 .interact_text()
158 .map_err(|e| GenerationError::InvalidOperation {
159 message: format!("Failed to get user input: {}", e),
160 })?;
161
162 println!();
163
164 let spec_api_style_options = ["fetch"];
165 let spec_api_style_index = Select::new()
166 .with_prompt(format!("API client style for '{}'", spec_name.trim()))
167 .items(&["fetch - Native Fetch API (recommended)"])
168 .default(0)
169 .interact()
170 .map_err(|e| GenerationError::InvalidOperation {
171 message: format!("Failed to get user selection: {}", e),
172 })?;
173 let spec_api_style = spec_api_style_options[spec_api_style_index].to_string();
174
175 println!();
176
177 let spec_base_url_input: String = Input::new()
178 .with_prompt(format!(
179 "API base URL for '{}' (optional, press Enter to skip)",
180 spec_name.trim()
181 ))
182 .allow_empty(true)
183 .interact_text()
184 .map_err(|e| GenerationError::InvalidOperation {
185 message: format!("Failed to get user input: {}", e),
186 })?;
187
188 let spec_base_url = if spec_base_url_input.trim().is_empty() {
189 None
190 } else {
191 Some(spec_base_url_input.trim().to_string())
192 };
193
194 println!();
195
196 let spec_header_strategy_options = ["consumerInjected", "bearerToken", "fixed"];
197 let spec_header_strategy_index = Select::new()
198 .with_prompt(format!("Header strategy for '{}'", spec_name.trim()))
199 .items(&[
200 "consumerInjected - Headers provided by consumer (recommended)",
201 "bearerToken - Automatic Bearer token injection",
202 "fixed - Fixed headers from config",
203 ])
204 .default(0)
205 .interact()
206 .map_err(|e| GenerationError::InvalidOperation {
207 message: format!("Failed to get user selection: {}", e),
208 })?;
209 let spec_header_strategy =
210 spec_header_strategy_options[spec_header_strategy_index].to_string();
211
212 println!();
213
214 println!("{}", "📎 Hooks Configuration".bright_yellow());
216 println!();
217
218 let enable_hooks = Confirm::new()
219 .with_prompt("Do you want to generate hooks (React Query or SWR)?")
220 .default(false)
221 .interact()
222 .map_err(|e| GenerationError::InvalidOperation {
223 message: format!("Failed to get user input: {}", e),
224 })?;
225
226 let hooks_config = if enable_hooks {
227 let hook_library_options = ["react-query", "swr"];
228 let hook_library_index = Select::new()
229 .with_prompt("Which hook library do you want to use?")
230 .items(&hook_library_options)
231 .default(0)
232 .interact()
233 .map_err(|e| GenerationError::InvalidOperation {
234 message: format!("Failed to get user selection: {}", e),
235 })?;
236 let hook_library = hook_library_options[hook_library_index].to_string();
237
238 let hooks_output: String = Input::new()
239 .with_prompt("Hooks output directory")
240 .default("src/hooks".to_string())
241 .interact_text()
242 .map_err(|e| GenerationError::InvalidOperation {
243 message: format!("Failed to get user input: {}", e),
244 })?;
245
246 let query_keys_output: String = Input::new()
247 .with_prompt("Query keys output directory")
248 .default("src/query-keys".to_string())
249 .interact_text()
250 .map_err(|e| GenerationError::InvalidOperation {
251 message: format!("Failed to get user input: {}", e),
252 })?;
253
254 Some(crate::config::model::HooksConfig {
255 output: hooks_output.trim().to_string(),
256 query_keys_output: query_keys_output.trim().to_string(),
257 library: Some(hook_library),
258 })
259 } else {
260 None
261 };
262
263 println!();
264
265 let config = Config {
267 root_dir,
268 generation: crate::config::model::GenerationConfig {
269 enable_cache,
270 enable_backup,
271 conflict_strategy,
272 },
273 specs: vec![crate::config::model::SpecEntry {
274 name: spec_name.trim().to_string(),
275 path: spec_path_input.trim().to_string(),
276 schemas: crate::config::model::SchemasConfig {
277 output: spec_schemas_output.trim().to_string(),
278 naming: spec_naming,
279 },
280 apis: crate::config::model::ApisConfig {
281 output: spec_apis_output.trim().to_string(),
282 style: spec_api_style,
283 base_url: spec_base_url,
284 header_strategy: spec_header_strategy,
285 timeout: None,
286 retries: None,
287 retry_delay: None,
288 headers: None,
289 },
290 hooks: hooks_config,
291 modules: crate::config::model::ModulesConfig {
292 ignore: vec![],
293 selected: vec![],
294 },
295 }],
296 ..Config::default()
297 };
298
299 validate_config(&config)?;
300 save_config(&config)?;
301 println!("{}", "✅ Created .vika.json".green());
302 }
303
304 let config = crate::config::loader::load_config()?;
306
307 let root_dir = PathBuf::from(&config.root_dir);
308 ensure_directory(&root_dir)?;
309
310 for spec in &config.specs {
312 let schemas_dir = PathBuf::from(&spec.schemas.output);
313 ensure_directory(&schemas_dir)?;
314
315 let apis_dir = PathBuf::from(&spec.apis.output);
316 ensure_directory(&apis_dir)?;
317
318 let root_dir_path = PathBuf::from(&config.root_dir);
320 ensure_directory(&root_dir_path)?;
321 let runtime_dir = root_dir_path.join("runtime");
322 if !runtime_dir.exists() {
323 write_runtime_client(&root_dir_path, None, Some(&spec.apis))?;
324 println!(
325 "{}",
326 format!("✅ Created runtime client for spec '{}'", spec.name).green()
327 );
328 } else {
329 println!(
330 "{}",
331 format!(
332 "⚠️ Runtime client already exists for spec '{}'. Skipping.",
333 spec.name
334 )
335 .yellow()
336 );
337 }
338 }
339
340 println!();
341 println!("{}", "✨ Project initialized successfully!".bright_green());
342 println!();
343
344 if !config.specs.is_empty() {
346 println!("{}", "🚀 Starting code generation...".bright_cyan());
347 println!();
348
349 use crate::commands::generate;
351 if let Err(e) = generate::run(
352 None, false, None, false, config.generation.enable_cache, config.generation.enable_backup, config.generation.conflict_strategy == "force", false, false, )
362 .await
363 {
364 println!();
365 println!(
366 "{}",
367 "⚠️ Generation failed, but initialization completed.".yellow()
368 );
369 println!("{}", format!("Error: {}", e).red());
370 println!();
371 println!("You can run 'vika-cli generate' manually to retry.");
372 return Ok(()); }
374
375 println!();
376 println!(
377 "{}",
378 "✅ Initialization and generation completed!".bright_green()
379 );
380 println!();
381 println!("💡 To add more specs later, run: vika-cli add");
382 } else {
383 println!("Next steps:");
384 println!(" 1. Run: vika-cli generate");
385 println!(" 2. Select the modules you want to generate");
386 println!();
387 println!("💡 To add more specs later, run: vika-cli add");
388 }
389
390 Ok(())
391}