tauri_typegen/build/
mod.rs1pub mod dependency_resolver;
2pub mod output_manager;
3pub mod project_scanner;
4
5use crate::analysis::CommandAnalyzer;
6use crate::generators::generator::BindingsGenerator;
7use crate::interface::config::{ConfigError, GenerateConfig};
8use crate::interface::output::{Logger, ProgressReporter};
9use std::path::Path;
10
11pub use dependency_resolver::*;
12pub use output_manager::*;
13pub use project_scanner::*;
14
15pub struct BuildSystem {
17 logger: Logger,
18}
19
20impl BuildSystem {
21 pub fn new(verbose: bool, debug: bool) -> Self {
22 Self {
23 logger: Logger::new(verbose, debug),
24 }
25 }
26
27 pub fn generate_at_build_time() -> Result<(), Box<dyn std::error::Error>> {
29 let build_system = Self::new(false, false);
30 build_system.run_generation()
31 }
32
33 pub fn run_generation(&self) -> Result<(), Box<dyn std::error::Error>> {
35 let mut reporter = ProgressReporter::new(self.logger.clone(), 5);
36
37 reporter.start_step("Detecting Tauri project");
38 let project_scanner = ProjectScanner::new();
39 let project_info = match project_scanner.detect_project()? {
40 Some(info) => {
41 reporter.complete_step(Some(&format!(
42 "Found project at {}",
43 info.root_path.display()
44 )));
45 info
46 }
47 None => {
48 reporter.complete_step(Some("No Tauri project detected, skipping generation"));
49 return Ok(());
50 }
51 };
52
53 reporter.start_step("Loading configuration");
54 let config = self.load_configuration(&project_info)?;
55 reporter.complete_step(Some(&format!(
56 "Using {} validation with output to {}",
57 config.validation_library, config.output_path
58 )));
59
60 reporter.start_step("Setting up build dependencies");
61 self.setup_build_dependencies(&config)?;
62 reporter.complete_step(None);
63
64 reporter.start_step("Analyzing and generating bindings");
65 let generated_files = self.generate_bindings(&config)?;
66 reporter.complete_step(Some(&format!("Generated {} files", generated_files.len())));
67
68 reporter.start_step("Managing output");
69 let mut output_manager = OutputManager::new(&config.output_path);
70 output_manager.finalize_generation(&generated_files)?;
71 reporter.complete_step(None);
72
73 reporter.finish(&format!(
74 "Successfully generated TypeScript bindings for {} commands",
75 generated_files.len()
76 ));
77
78 Ok(())
79 }
80
81 fn load_configuration(
82 &self,
83 project_info: &ProjectInfo,
84 ) -> Result<GenerateConfig, ConfigError> {
85 if let Some(tauri_config_path) = &project_info.tauri_config_path {
87 if tauri_config_path.exists() {
88 match GenerateConfig::from_tauri_config(tauri_config_path) {
89 Ok(Some(config)) => {
90 self.logger
91 .debug("Loaded configuration from tauri.conf.json");
92 return Ok(config);
93 }
94 Ok(None) => {}
95 Err(e) => {
96 self.logger.warning(&format!(
97 "Failed to load config from tauri.conf.json: {}. Using defaults.",
98 e
99 ));
100 }
101 }
102 }
103 }
104
105 let standalone_config = project_info.root_path.join("typegen.json");
107 if standalone_config.exists() {
108 match GenerateConfig::from_file(&standalone_config) {
109 Ok(config) => {
110 self.logger.debug("Loaded configuration from typegen.json");
111 return Ok(config);
112 }
113 Err(e) => {
114 self.logger.warning(&format!(
115 "Failed to load config from typegen.json: {}. Using defaults.",
116 e
117 ));
118 }
119 }
120 }
121
122 self.logger.debug("Using default configuration");
124 Ok(GenerateConfig::default())
125 }
126
127 fn setup_build_dependencies(
128 &self,
129 config: &GenerateConfig,
130 ) -> Result<(), Box<dyn std::error::Error>> {
131 println!("cargo:rerun-if-changed={}", config.project_path);
133
134 if Path::new("tauri.conf.json").exists() {
136 println!("cargo:rerun-if-changed=tauri.conf.json");
137 }
138 if Path::new("typegen.json").exists() {
139 println!("cargo:rerun-if-changed=typegen.json");
140 }
141
142 if Path::new(&config.output_path).exists() {
144 println!("cargo:rerun-if-changed={}", config.output_path);
145 }
146
147 Ok(())
148 }
149
150 fn generate_bindings(
151 &self,
152 config: &GenerateConfig,
153 ) -> Result<Vec<String>, Box<dyn std::error::Error>> {
154 let mut analyzer = CommandAnalyzer::new();
155 let commands = analyzer.analyze_project(&config.project_path)?;
156
157 if commands.is_empty() {
158 self.logger
159 .info("No Tauri commands found. Skipping generation.");
160 return Ok(vec![]);
161 }
162
163 let validation = match config.validation_library.as_str() {
164 "zod" | "none" => Some(config.validation_library.clone()),
165 _ => return Err("Invalid validation library. Use 'zod' or 'none'".into()),
166 };
167
168 let mut generator = BindingsGenerator::new(validation);
169 let generated_files = generator.generate_models(
170 &commands,
171 analyzer.get_discovered_structs(),
172 &config.output_path,
173 &analyzer,
174 )?;
175
176 if config.should_visualize_deps() {
178 self.generate_dependency_visualization(&analyzer, &commands, &config.output_path)?;
179 }
180
181 Ok(generated_files)
182 }
183
184 fn generate_dependency_visualization(
185 &self,
186 analyzer: &CommandAnalyzer,
187 commands: &[crate::models::CommandInfo],
188 output_path: &str,
189 ) -> Result<(), Box<dyn std::error::Error>> {
190 use std::fs;
191
192 self.logger.debug("Generating dependency visualization");
193
194 let text_viz = analyzer.visualize_dependencies(commands);
195 let viz_file_path = Path::new(output_path).join("dependency-graph.txt");
196 fs::write(&viz_file_path, text_viz)?;
197
198 let dot_viz = analyzer.generate_dot_graph(commands);
199 let dot_file_path = Path::new(output_path).join("dependency-graph.dot");
200 fs::write(&dot_file_path, dot_viz)?;
201
202 self.logger.verbose(&format!(
203 "Generated dependency graphs: {} and {}",
204 viz_file_path.display(),
205 dot_file_path.display()
206 ));
207
208 Ok(())
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use tempfile::TempDir;
216
217 #[test]
218 fn test_build_system_creation() {
219 let build_system = BuildSystem::new(true, false);
220 assert!(build_system
221 .logger
222 .should_log(crate::interface::output::LogLevel::Verbose));
223 }
224
225 #[test]
226 fn test_load_default_configuration() {
227 let temp_dir = TempDir::new().unwrap();
228 let project_info = ProjectInfo {
229 root_path: temp_dir.path().to_path_buf(),
230 src_tauri_path: temp_dir.path().join("src-tauri"),
231 tauri_config_path: None,
232 };
233
234 let build_system = BuildSystem::new(false, false);
235 let config = build_system.load_configuration(&project_info).unwrap();
236
237 assert_eq!(config.validation_library, "none");
238 assert_eq!(config.project_path, "./src-tauri");
239 }
240}