Imported from thwbh/tauri-typegen (
AGENTS.md). Install upstream withnpx skills add thwbh/tauri-typegen. Copyright stays with the author.
tauri-typegen
tauri-typegen generates TypeScript (and optionally Zod) bindings from a Tauri
backend's Rust code. Tauri ships no built-in way to keep frontend types in sync
with #[tauri::command] functions and their models; this crate fills that gap
by parsing the Rust source with syn and rendering typed output with Tera
templates.
It ships as both a library (tauri-typegen) and a cargo subcommand binary
(cargo-tauri-typegen). Consumers use it as a build dependency
(BuildSystem::generate_at_build_time() in build.rs) or as a CLI
(cargo tauri-typegen generate).
- Edition: 2021 · MSRV: 1.77.2 · CI toolchain: 1.93.0
- Validation modes:
none(vanilla TS types) andzod(runtime schemas + hooks). There are no other modes — don't introduce one without instruction.
Build, test, and verify
Run these from the repo root. They mirror CI (.github/workflows/ci.yml); all
three must pass before a change is complete:
cargo test --all-features
cargo fmt -- --check # formatting is enforced; run `cargo fmt` to fix
cargo clippy --all-targets --all-features -- -D warnings # warnings fail CI
To run a focused test: cargo test <name> (e.g. cargo test command_parser,
cargo test --test integration_e2e <name>).
Architecture (the generation pipeline)
The flow is analysis → models → generation, single-pass over a cached AST.
- Analysis (
src/analysis/) — parses Rust files into the data model.ast_cache.rsparses and caches every.rsfile once (single-pass goal).command_parser.rs,struct_parser.rs,event_parser.rs,channel_parser.rs,serde_parser.rs,validator_parser.rsextract their respective constructs.type_resolver.rs+dependency_graph.rsperform lazy, on-demand type discovery.mod.rs(CommandAnalyzer) orchestrates the pass.
- Models (
src/models.rs) — the shared data model:CommandInfo,ParameterInfo,StructInfo,FieldInfo,EnumVariantInfo,TypeStructure, etc. Analysis fills these; generation reads them. - Generation (
src/generators/) — turns models into output files.base/holds shared machinery:template_context.rs(the*Contextstructs handed to templates),type_visitor.rs(theTypeVisitortrait), andtemplates.rs.ts/andzod/each provide a generator, aTypeVisitorimpl, and atemplates/directory of.terafiles. Zod addsschema_builder.rsandfilters.rs.mod.rsbuilds the contexts (create_command_contexts,create_event_contexts, …) consumed by every template.
- Build/IO (
src/build/) — project scanning, caching (.typecache), and writing output. Interface (src/interface/) — CLI, config parsing, logging.BuildSystemandGenerateConfigare re-exported fromlib.rs.
Conventions you must follow
- Match surrounding code. This is idiomatic Rust formatted with
rustfmt; keep clippy clean (CI denies warnings). - Deduplication happens at the generation layer, not analysis. Commands and
events are deduped by name (first-occurrence-wins) in
src/generators/mod.rs(create_command_contexts/create_event_contexts). The analyzer stays a faithful record of the source — do not dedupe there. This is how cross-platform#[cfg(...)]-gated duplicate commands collapse to one declaration. - Parse serde/attribute metadata with
attr.parse_nested_meta(...), the AST-based approach (seeserde_parser.rs), not string matching. - Serde naming precedence: field/parameter-level
#[serde(rename = "...")]overrides container-level#[serde(rename_all = "...")]. Command-levelrename_allaffects parameters/channels only — never the command (invoke) name or the generated function name. - Enums: all-unit-variant enums become string-literal unions /
z.enum; enums with data become discriminated unions /z.discriminatedUnion. Seeis_simple_enum()/is_complex_enum()onStructInfo. - Output formatting lives in templates. Change
.terafiles undersrc/generators/{ts,zod}/templates/for output shape; keep the two backends consistent. Bothcommands.tsfunctions and thetypes.tsParams interfaces derive from the same deduped contexts — don't reintroduce a second source.
Adding or changing features
- New tests are required. Per repo policy: changes are validated against
existing tests; new features are validated against new tests.
- Unit tests:
#[cfg(test)]modules colocated in the relevantsrc/**file. - End-to-end tests:
tests/integration_e2e.rs, using theTestProject/TestGeneratorhelpers intests/common/mod.rsand fixtures intests/fixtures/. Prefer an e2e test when behavior spans analysis + generation (e.g. the cfg-dedup regression test). - Backward-compatibility tests:
tests/regression/.
- Unit tests:
- Touching the data model (
models.rs) ripples into both generators and their templates — update all of analysis, bothTypeVisitorimpls, and the affected.terafiles together. - Always run the full verify trio (test + fmt + clippy) before declaring done.
Versioning and changelog
The project follows Semantic Versioning and keeps a
Keep a Changelog-style CHANGELOG.md. Git tags
are bare version numbers, no v prefix (e.g. 0.5.1).
The version source of truth is crates.io, not Cargo.toml. The
version in Cargo.toml is intentionally stale and is never bumped for a
release — do not "update the version" there as part of a change. The release
workflow (.github/workflows/cd.yml) reads the latest published version from
crates.io, computes the next one, and overwrites Cargo.toml only on the CI
runner (the change is not committed back).
Releases are manual via the CD workflow's workflow_dispatch trigger
with a major / minor / patch choice. The workflow then: fetches the
current crates.io version → bumps it → extracts release notes from
CHANGELOG.md for the new version → cargo set-version → build + test →
cargo publish → gh release create (tag + GitHub release).
What this means for you as an agent:
- When a change is user-facing, add or update a CHANGELOG entry for the
upcoming version. CD fails if no
## [<new_version>] - <date>section exists for the version being released, so the entry must already be in place (committed via PR) before a release is run. - Use the existing section structure:
## [X.Y.Z] - YYYY-MM-DDwith### Added/### Changed/### Fixedsubsections, newest at the top. Match the entry style — a bold lead-in followed by sub-bullets. - Pick the next version per SemVer relative to the latest crates.io release
(which is what CD will bump from), not the number in
Cargo.toml. - Do not create git tags, run
cargo publish, or bumpCargo.tomlyourself — releasing is the CD workflow's job.
Roadmap (maintainer notes — not active work unless asked)
Future plans
- When using zod, schema validation should match the parameters in the
validatormacro. Example:#[validate(range(min = 1, max = 10, message = "Must be between 1 and 10"))]→z.coerce.number().min(1).max(10), carrying over the message per https://zod.dev/api. (Groundwork:validator_parser.rs.)
Nice to have — Macro-Based Command Discovery
Concept: extract commands from tauri::generate_handler! invocations
instead of parsing all files for #[tauri::command].
Benefits: only analyzes registered commands; better performance; no false positives from unused commands; single source of truth.
Approaches: (1) parse main.rs/lib.rs for
tauri::generate_handler![...]; (2) hook the build/macro-expansion process;
(3) custom proc-macro wrapper around generate_handler!; (4) hybrid — macro
parsing first, fall back to file parsing, cross-reference to validate.
Challenges: multiple/conditional registration points (plugins), complex proc-macro analysis, compilation-process integration, dynamic registration.
Resources:
- https://docs.rs/tauri/latest/tauri/macro.generate_handler.html
- https://docs.rs/tauri-macros/latest/tauri_macros/
- https://v2.tauri.app/develop/calling-rust/
Priority: Low — current file-parsing approach works well for most cases.