Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.1] - 2026-02-05

### Added
- **Smart Caching**: Skip regeneration when source files haven't changed
- Creates `.typecache` file in output directory with hashes of discovered commands, types, and configuration
- Compares hashes on subsequent runs to determine if regeneration is needed
- Significantly improves build times when nothing has changed

- **Force Regeneration Flag**: New option to bypass cache and force regeneration
- CLI: `--force` or `-f` flag (e.g., `cargo tauri-typegen generate --force`)
- Config: `"force": true` in `tauri.conf.json` under `plugins.typegen`
- CLI flag takes priority over config file setting

## [0.4.0] - 2025-12-26

### Added
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tauri-typegen"
version = "0.4.0"
version = "0.4.1"
authors = [ "Stefan Poindl" ]
description = "A rust crate that automatically generates TypeScript models and bindings from your Tauri commands"
edition = "2021"
Expand Down
71 changes: 65 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ A command-line tool that automatically generates TypeScript bindings from your T
- 🏷️ **Serde Support**: Respects `#[serde(rename)]` and `#[serde(rename_all)]` attributes
- 🎯 **Type Safety**: Keeps frontend and backend types in sync
- 🛠️ **Build Integration**: Works as standalone CLI or build dependency
- ⚡ **Smart Caching**: Only regenerates when source files change

## Table of Contents

Expand All @@ -29,6 +30,8 @@ A command-line tool that automatically generates TypeScript bindings from your T
- [TypeScript Compatibility](#typescript-compatibility)
- [API Reference](#api-reference)
- [Configuration](#configuration)
- [Caching](#caching)
- [Usage in CI](#usage-in-ci)
- [Examples](#examples)
- [Contributing](#contributing)

Expand Down Expand Up @@ -548,7 +551,8 @@ Options:
-v, --validation <LIBRARY> Validation library: zod or none [default: none]
--verbose Verbose output
--visualize-deps Generate dependency graph
-c, --config <FILE> Config file path
-c, --config <FILE> Config file path
-f, --force Force regeneration, ignoring cache
```

```bash
Expand Down Expand Up @@ -619,11 +623,12 @@ In `tauri.conf.json`:
```json
{
"plugins": {
"tauri-typegen": {
"project_path": ".",
"output_path": "../src/generated",
"validation_library": "zod",
"verbose": true
"typegen": {
"projectPath": ".",
"outputPath": "../src/generated",
"validationLibrary": "zod",
"verbose": true,
"force": false
}
}
}
Expand Down Expand Up @@ -692,6 +697,60 @@ export async function getFileInfo(): Promise<FileMetadata> {
}
```

## Caching

Tauri-typegen uses smart caching to skip regeneration when nothing has changed, improving build times.

### How It Works

A `.typecache` file is created in your output directory containing hashes of:
- All discovered Tauri commands
- All discovered structs and enums
- Configuration settings that affect output

On subsequent runs, these hashes are compared. If nothing changed, generation is skipped.

### Force Regeneration

To bypass the cache and force regeneration:

**CLI flag (highest priority):**
```bash
cargo tauri-typegen generate --force
# or
cargo tauri-typegen generate -f
```

**Config file (`tauri.conf.json`):**
```json
{
"plugins": {
"typegen": {
"force": true
}
}
}
```

**Programmatic:**
```rust
let mut config = GenerateConfig::default();
config.force = Some(true);
```

The CLI `--force` flag always overrides the config file value.

### Cache File Location

The cache file `.typecache` is stored in your output directory (e.g., `./src/generated/.typecache`). Add it to `.gitignore`:

```gitignore
# Tauri-typegen cache
.typecache
```

Or if your entire output directory is gitignored, the cache file is already excluded.

## Usage in CI

When running builds in CI/CD environments, you need to generate TypeScript bindings before the frontend build step.
Expand Down
48 changes: 46 additions & 2 deletions src/bin/cargo-tauri-typegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use clap::Parser;
use std::fs;
use std::path::PathBuf;
use tauri_typegen::analysis::CommandAnalyzer;
use tauri_typegen::build::GenerationCache;
use tauri_typegen::generators::create_generator;
use tauri_typegen::interface::{
print_dependency_visualization_info, print_usage_info, CargoCli, CargoSubcommands,
Expand Down Expand Up @@ -34,6 +35,7 @@ fn main() {
verbose,
visualize_deps,
config_file,
force,
} => {
if let Err(e) = run_generate(
project_path,
Expand All @@ -42,6 +44,7 @@ fn main() {
verbose,
visualize_deps,
config_file,
force,
) {
eprintln!("Error: {}", e);
std::process::exit(1);
Expand Down Expand Up @@ -81,6 +84,7 @@ fn run_generate(
verbose: bool,
visualize_deps: bool,
config_file: Option<PathBuf>,
force: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let logger = Logger::new(verbose, false);
let mut reporter = ProgressReporter::new(logger, 4);
Expand Down Expand Up @@ -144,6 +148,10 @@ fn run_generate(
if visualize_deps {
config.visualize_deps = Some(true);
}
// CLI --force flag overrides config
if force {
config.force = Some(true);
}

reporter.complete_step(Some(&format!(
"Using {} validation",
Expand Down Expand Up @@ -205,6 +213,35 @@ fn run_generate(
return Ok(());
}

// Check cache to see if regeneration is needed (unless force is set)
let discovered_structs = analyzer.get_discovered_structs();
let needs_regeneration = if config.should_force() {
if config.is_verbose() {
println!("🔄 Force flag set, regenerating bindings");
}
true
} else {
GenerationCache::needs_regeneration(
&config.output_path,
&commands,
discovered_structs,
&config,
)
.unwrap_or(true) // On error, assume regeneration is needed
};

if !needs_regeneration {
if config.is_verbose() {
println!("✨ Cache hit - no changes detected, skipping generation");
}
println!("✅ TypeScript bindings are up to date");
return Ok(());
}

if config.is_verbose() && !config.should_force() {
println!("🔄 Changes detected, regenerating bindings");
}

// Generate bindings
reporter.start_step("Generating TypeScript bindings");
let validation = match config.validation_library.as_str() {
Expand All @@ -215,7 +252,7 @@ fn run_generate(
let mut generator = create_generator(validation);
let generated_files = generator.generate_models(
&commands,
analyzer.get_discovered_structs(),
discovered_structs,
&config.output_path,
&analyzer,
&config,
Expand All @@ -235,6 +272,12 @@ fn run_generate(
print_dependency_visualization_info(&config.output_path);
}

// Save cache after successful generation
let cache = GenerationCache::new(&commands, discovered_structs, &config)?;
if let Err(e) = cache.save(&config.output_path) {
eprintln!("Warning: Failed to save generation cache: {}", e);
}

// Print summary
reporter.finish("Generation complete");
print_usage_info(&config.output_path, &generated_files, commands.len());
Expand Down Expand Up @@ -344,7 +387,8 @@ fn run_init(
Some(config.validation_library.clone()),
verbose,
visualize_deps,
None, // No config file since we just created one
None, // No config file since we just created one
false, // Respect cache behavior
)?;

logger.info("");
Expand Down
Loading