tauri-typegen 0.5.0

A rust crate that automatically generates TypeScript models and bindings from your Tauri commands
Documentation
pub mod cli;
pub mod config;
pub mod output;

use crate::analysis::CommandAnalyzer;
use crate::generators::create_generator;

pub use cli::*;
pub use config::*;
pub use output::*;

/// Generate TypeScript bindings from a Tauri project.
///
/// This is the main entry point for programmatic generation of TypeScript bindings.
/// It analyzes Rust source code in the specified project, discovers Tauri commands,
/// and generates TypeScript interfaces and command functions.
///
/// # Arguments
///
/// * `config` - Configuration specifying project paths, output location, and validation options
///
/// # Returns
///
/// Returns a list of generated file paths on success.
///
/// # Errors
///
/// Returns an error if:
/// - Configuration is invalid
/// - Project directory cannot be read
/// - Rust code has syntax errors
/// - Output directory cannot be written to
///
/// # Example
///
/// ```rust,no_run
/// use tauri_typegen::{GenerateConfig, generate_from_config};
///
/// let config = GenerateConfig {
///     project_path: "./src-tauri".to_string(),
///     output_path: "./src/generated".to_string(),
///     validation_library: "zod".to_string(),
///     verbose: Some(true),
///     ..Default::default()
/// };
///
/// let files = generate_from_config(&config)?;
/// println!("Generated {} files", files.len());
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn generate_from_config(
    config: &config::GenerateConfig,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let logger = output::Logger::new(config.is_verbose(), false);

    if config.is_verbose() {
        logger.info(&format!(
            "🔍 Analyzing Tauri commands in: {}",
            config.project_path
        ));
    }

    // Validate configuration
    config
        .validate()
        .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;

    // Analyze commands with struct discovery
    let mut analyzer = CommandAnalyzer::new();

    // Apply custom type mappings from configuration
    if let Some(ref mappings) = config.type_mappings {
        analyzer.add_type_mappings(mappings);
        if config.is_verbose() {
            logger.info(&format!(
                "📝 Applied {} custom type mappings",
                mappings.len()
            ));
            for (rust_type, ts_type) in mappings {
                logger.verbose(&format!("  {} → {}", rust_type, ts_type));
            }
        }
    }

    let commands = analyzer.analyze_project(&config.project_path)?;

    if config.is_verbose() {
        logger.info(&format!("📋 Found {} Tauri commands:", commands.len()));
        for cmd in &commands {
            logger.verbose(&format!("  - {} ({})", cmd.name, cmd.file_path));
        }

        let discovered_structs = analyzer.get_discovered_structs();
        logger.info(&format!(
            "đŸ—ī¸  Found {} struct definitions:",
            discovered_structs.len()
        ));
        for (name, struct_info) in discovered_structs {
            let struct_type = if struct_info.is_enum {
                "enum"
            } else {
                "struct"
            };
            logger.verbose(&format!(
                "  - {} ({}) with {} fields",
                name,
                struct_type,
                struct_info.fields.len()
            ));
            for field in &struct_info.fields {
                let visibility = if field.is_public { "pub" } else { "private" };
                let optional = if field.is_optional { "?" } else { "" };
                logger.verbose(&format!(
                    "    â€ĸ {}{}: {} ({})",
                    field.name, optional, field.rust_type, visibility
                ));
            }
        }

        if discovered_structs.is_empty() {
            logger.info("  â„šī¸  No custom struct definitions found in the project");
        }
    }

    if commands.is_empty() {
        if config.is_verbose() {
            logger.warning("âš ī¸  No Tauri commands found. Make sure your project contains functions with #[tauri::command] attributes.");
        }
        return Ok(vec![]);
    }

    // Validate validation library
    let validation = match config.validation_library.as_str() {
        "zod" | "none" => Some(config.validation_library.clone()),
        _ => {
            return Err("Invalid validation library. Use 'zod' or 'none'".into());
        }
    };

    if config.is_verbose() {
        logger.info(&format!(
            "🚀 Generating TypeScript models with {} validation...",
            validation.as_ref().unwrap()
        ));
    }

    // Generate TypeScript models with discovered structs
    let mut generator = create_generator(validation);
    let generated_files = generator.generate_models(
        &commands,
        analyzer.get_discovered_structs(),
        &config.output_path,
        &analyzer,
        config,
    )?;

    if config.is_verbose() {
        logger.info(&format!(
            "✅ Successfully generated {} files for {} commands:",
            generated_files.len(),
            commands.len()
        ));
        for file in &generated_files {
            logger.verbose(&format!("  📄 {}/{}", config.output_path, file));
        }
    }

    Ok(generated_files)
}