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 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 let config = Config {
216 root_dir,
217 generation: crate::config::model::GenerationConfig {
218 enable_cache,
219 enable_backup,
220 conflict_strategy,
221 },
222 specs: vec![crate::config::model::SpecEntry {
223 name: spec_name.trim().to_string(),
224 path: spec_path_input.trim().to_string(),
225 schemas: crate::config::model::SchemasConfig {
226 output: spec_schemas_output.trim().to_string(),
227 naming: spec_naming,
228 },
229 apis: crate::config::model::ApisConfig {
230 output: spec_apis_output.trim().to_string(),
231 style: spec_api_style,
232 base_url: spec_base_url,
233 header_strategy: spec_header_strategy,
234 },
235 modules: crate::config::model::ModulesConfig {
236 ignore: vec![],
237 selected: vec![],
238 },
239 }],
240 ..Config::default()
241 };
242
243 validate_config(&config)?;
244 save_config(&config)?;
245 println!("{}", "✅ Created .vika.json".green());
246 }
247
248 let config = crate::config::loader::load_config()?;
250
251 let root_dir = PathBuf::from(&config.root_dir);
252 ensure_directory(&root_dir)?;
253
254 for spec in &config.specs {
256 let schemas_dir = PathBuf::from(&spec.schemas.output);
257 ensure_directory(&schemas_dir)?;
258
259 let apis_dir = PathBuf::from(&spec.apis.output);
260 ensure_directory(&apis_dir)?;
261
262 let http_client_path = apis_dir.join("http.ts");
264 if !http_client_path.exists() {
265 write_http_client_template(&http_client_path)?;
266 println!(
267 "{}",
268 format!(
269 "✅ Created {} for spec '{}'",
270 http_client_path.display(),
271 spec.name
272 )
273 .green()
274 );
275 } else {
276 println!(
277 "{}",
278 format!(
279 "⚠️ {} already exists for spec '{}'. Skipping.",
280 http_client_path.display(),
281 spec.name
282 )
283 .yellow()
284 );
285 }
286 }
287
288 println!();
289 println!("{}", "✨ Project initialized successfully!".bright_green());
290 println!();
291
292 if !config.specs.is_empty() {
294 println!("{}", "🚀 Starting code generation...".bright_cyan());
295 println!();
296
297 use crate::commands::generate;
299 if let Err(e) = generate::run(
300 None, false, None, false, config.generation.enable_cache, config.generation.enable_backup, config.generation.conflict_strategy == "force", )
308 .await
309 {
310 println!();
311 println!(
312 "{}",
313 "⚠️ Generation failed, but initialization completed.".yellow()
314 );
315 println!("{}", format!("Error: {}", e).red());
316 println!();
317 println!("You can run 'vika-cli generate' manually to retry.");
318 return Ok(()); }
320
321 println!();
322 println!(
323 "{}",
324 "✅ Initialization and generation completed!".bright_green()
325 );
326 println!();
327 println!("💡 To add more specs later, run: vika-cli add");
328 } else {
329 println!("Next steps:");
330 println!(" 1. Run: vika-cli generate");
331 println!(" 2. Select the modules you want to generate");
332 println!();
333 println!("💡 To add more specs later, run: vika-cli add");
334 }
335
336 Ok(())
337}