vika_cli/commands/
generate.rs

1use crate::config::loader::{load_config, save_config};
2use crate::config::validator::validate_config;
3use crate::error::Result;
4use crate::progress::ProgressReporter;
5use colored::*;
6use std::path::PathBuf;
7use tabled::Tabled;
8
9#[allow(clippy::too_many_arguments)]
10pub async fn run(
11    _spec: Option<String>,
12    all_specs: bool,
13    spec_name: Option<String>,
14    verbose: bool,
15    cache: bool,
16    backup: bool,
17    force: bool,
18    react_query: bool,
19    swr: bool,
20) -> Result<()> {
21    // Validate hook flags - only one can be set
22    let hook_flags_count = [react_query, swr].iter().filter(|&&f| f).count();
23    if hook_flags_count > 1 {
24        return Err(crate::error::GenerationError::InvalidHookFlags.into());
25    }
26
27    let mut progress = ProgressReporter::new(verbose);
28
29    progress.success("Starting code generation...");
30    println!();
31
32    // Load config
33    progress.start_spinner("Loading configuration...");
34    let mut config = load_config()?;
35    validate_config(&config)?;
36    progress.finish_spinner("Configuration loaded");
37
38    use crate::specs::manager::resolve_spec_selection;
39    use crate::specs::runner::{run_all_specs, run_single_spec, GenerateOptions};
40
41    // Resolve which specs to generate
42    let specs_to_generate = resolve_spec_selection(&config, spec_name.clone(), all_specs)?;
43
44    // Ensure runtime client exists at root_dir (shared across all specs)
45    use crate::generator::writer::{ensure_directory, write_runtime_client};
46    let root_dir_path = PathBuf::from(&config.root_dir);
47    ensure_directory(&root_dir_path)?;
48    let runtime_dir = root_dir_path.join("runtime");
49    if !runtime_dir.exists() {
50        // Use first spec's apis config for runtime client configuration (or default)
51        let apis_config = config.specs.first().map(|s| &s.apis);
52        write_runtime_client(&root_dir_path, None, apis_config)?;
53        if verbose {
54            progress.success("Created runtime client files");
55        }
56    }
57
58    // Determine hook type: CLI flags take precedence, then check config
59    let hook_type = if react_query {
60        Some(crate::specs::runner::HookType::ReactQuery)
61    } else if swr {
62        Some(crate::specs::runner::HookType::Swr)
63    } else {
64        // Check if any spec has hooks.library configured
65        // If multiple specs have different libraries, we'll use the first one
66        // (This could be enhanced to support per-spec hook types)
67        config.specs.iter().find_map(|spec| {
68            spec.hooks
69                .as_ref()
70                .and_then(|h| h.library.as_ref())
71                .and_then(|lib| match lib.as_str() {
72                    "react-query" => Some(crate::specs::runner::HookType::ReactQuery),
73                    "swr" => Some(crate::specs::runner::HookType::Swr),
74                    _ => None,
75                })
76        })
77    };
78
79    let options = GenerateOptions {
80        use_cache: if cache {
81            true
82        } else {
83            config.generation.enable_cache
84        },
85        use_backup: if backup {
86            true
87        } else {
88            config.generation.enable_backup
89        },
90        use_force: if force {
91            true
92        } else {
93            config.generation.conflict_strategy == "force"
94        },
95        verbose,
96        hook_type,
97    };
98
99    if specs_to_generate.len() > 1 {
100        // Generate all selected specs
101        progress.success("Starting multi-spec generation...");
102        println!();
103
104        let stats = run_all_specs(&specs_to_generate, &config, &options).await?;
105
106        // Update config with selected modules for each spec
107        for stat in &stats {
108            if let Some(spec_entry) = config.specs.iter_mut().find(|s| s.name == stat.spec_name) {
109                spec_entry.modules.selected = stat.modules.clone();
110            }
111        }
112        save_config(&config)?;
113
114        println!();
115        progress.success(&format!(
116            "Successfully generated code for {} spec(s)!",
117            stats.len()
118        ));
119        println!();
120
121        // Display summary
122        use tabled::{Table, Tabled};
123        #[derive(Tabled)]
124        struct SpecSummary {
125            #[tabled(rename = "Spec")]
126            spec: String,
127            #[tabled(rename = "Modules")]
128            modules: usize,
129            #[tabled(rename = "Files")]
130            files: usize,
131        }
132
133        let table_data: Vec<SpecSummary> = stats
134            .iter()
135            .map(|s| SpecSummary {
136                spec: s.spec_name.clone(),
137                modules: s.modules_generated,
138                files: s.files_generated,
139            })
140            .collect();
141
142        let table = Table::new(table_data);
143        println!("{}", "Generation summary:".bright_cyan());
144        println!("{}", table);
145        println!();
146    } else {
147        // Generate single spec
148        let spec_entry = &specs_to_generate[0];
149        let stats = run_single_spec(spec_entry, &config, &options).await?;
150
151        // Update config with selected modules
152        if let Some(spec_entry) = config.specs.iter_mut().find(|s| s.name == stats.spec_name) {
153            spec_entry.modules.selected = stats.modules.clone();
154        }
155        save_config(&config)?;
156
157        println!();
158        progress.success(&format!(
159            "Successfully generated {} files for spec '{}'!",
160            stats.files_generated, stats.spec_name
161        ));
162        println!();
163
164        // Use spec-specific configs for output paths
165        let schemas_config = &spec_entry.schemas;
166        let apis_config = &spec_entry.apis;
167
168        println!("{}", "Generated files:".bright_cyan());
169        println!("  📁 Schemas: {}", schemas_config.output);
170        println!("  📁 APIs: {}", apis_config.output);
171        println!();
172    }
173
174    Ok(())
175}