Skip to content

Commit 7eefd06

Browse files
authored
feat: add extra_commands to TypeGenConfig for frontend codegen CLIs (#231)
## Summary - Adds `extra_commands` field to `TypeGenConfig` so that `generate-types` and `build` can run additional code-generation CLIs (e.g., TanStack Router's `tsr generate`) after metadata export but before the typegen CLI - Commands are resolved through the project's JS executor (`node_modules/.bin` first, then npx/pnpm dlx/yarn dlx/bunx) so they work across all package managers - Failures warn and continue — independent generators like hey-api typegen still run even if an extra command fails - Updates scaffold `_print_recommended_config` to show `extra_commands` for react-tanstack projects - Adds `examples/react-tanstack/` with a full working example demonstrating the integration - Updates docs (`types.rst`), `llms.txt`, `llms-full.txt`, and integration tests ## Motivation `litestar assets generate-types` bypasses Vite entirely, so Vite plugin hooks (like TanStack Router's `TanStackRouterVite`) never fire. This means `routeTree.gen.ts` stays as a placeholder stub, causing `tsc --noEmit` / `make lint` to fail unless a full `vite build` has been run first.
1 parent 8444fd2 commit 7eefd06

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1034
-396
lines changed

.github/workflows/e2e-examples.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
example:
1717
# SPA examples
1818
- react
19+
- react-tanstack
1920
- vue
2021
- svelte
2122
- angular

docs/usage/types.rst

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ TypeGenConfig Reference
108108
* - ``type_import_paths``
109109
- ``{}``
110110
- Map schema/type names to TypeScript import paths for props types excluded from OpenAPI
111+
* - ``extra_commands``
112+
- ``[]``
113+
- Additional ``[binary, *args]`` commands to run during type generation (resolved through the project's JS executor)
111114

112115
Default Inertia shared-props types (``User``, ``AuthData``, ``FlashMessages``) are controlled by
113116
``InertiaTypeGenConfig`` under ``InertiaConfig.type_gen``.
@@ -147,7 +150,8 @@ This runs the full pipeline:
147150

148151
1. Exports OpenAPI schema to ``src/generated/openapi.json``
149152
2. Exports route metadata to ``src/generated/routes.json`` and ``routes.ts`` (if enabled)
150-
3. Runs ``litestar-vite-typegen`` (invokes ``@hey-api/openapi-ts`` and generates ``schemas.ts`` / page props types)
153+
3. Runs any ``extra_commands`` (e.g., ``tsr generate`` for TanStack Router)
154+
4. Runs ``litestar-vite-typegen`` (invokes ``@hey-api/openapi-ts`` and generates ``schemas.ts`` / page props types)
151155

152156
The command also writes/updates ``.litestar.json`` and reports whether it was
153157
updated or unchanged, matching the output style for other generated files.
@@ -387,6 +391,35 @@ This generates ``inertia-pages.json`` which the Vite plugin uses to create
387391

388392
See :doc:`/frameworks/inertia/type-generation` for Inertia-specific details.
389393

394+
Extra Commands
395+
--------------
396+
397+
Some frontend tools (e.g., TanStack Router, Generouted) use Vite plugins that
398+
generate TypeScript during ``vite build`` but also ship standalone CLIs.
399+
Because ``generate-types`` runs outside Vite, those plugin hooks never fire.
400+
401+
Use ``extra_commands`` to include them in the generation pipeline:
402+
403+
.. code-block:: python
404+
405+
VitePlugin(
406+
config=ViteConfig(
407+
types=TypeGenConfig(
408+
extra_commands=[["tsr", "generate"]],
409+
),
410+
)
411+
)
412+
413+
Each entry is ``[binary, *args]``. The binary is resolved through the
414+
project's JS executor — ``node_modules/.bin`` is checked first, then the
415+
configured package runner (npx, pnpm dlx, yarn dlx, etc.) is used as a
416+
fallback. This means the same config works regardless of which package
417+
manager the project uses.
418+
419+
Extra commands run after metadata export but before the typegen CLI, so their
420+
output (e.g., ``routeTree.gen.ts``) is available when ``tsc --noEmit`` runs
421+
during linting.
422+
390423
See Also
391424
--------
392425

examples/angular-cli/package.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,23 @@
1010
"generate-types": "openapi-ts --config openapi-ts.config.ts"
1111
},
1212
"dependencies": {
13-
"@angular/animations": "21.2.5",
14-
"@angular/common": "21.2.5",
15-
"@angular/compiler": "21.2.5",
16-
"@angular/core": "21.2.5",
17-
"@angular/forms": "21.2.5",
18-
"@angular/platform-browser": "21.2.5",
19-
"@angular/platform-browser-dynamic": "21.2.5",
20-
"@angular/router": "21.2.5",
13+
"@angular/animations": "21.2.6",
14+
"@angular/common": "21.2.6",
15+
"@angular/compiler": "21.2.6",
16+
"@angular/core": "21.2.6",
17+
"@angular/forms": "21.2.6",
18+
"@angular/platform-browser": "21.2.6",
19+
"@angular/platform-browser-dynamic": "21.2.6",
20+
"@angular/router": "21.2.6",
2121
"rxjs": "7.8.2"
2222
},
2323
"devDependencies": {
24-
"@angular/build": "21.2.3",
25-
"@angular/cli": "21.2.3",
26-
"@angular/compiler-cli": "21.2.5",
27-
"@hey-api/openapi-ts": "0.94.0",
24+
"@angular/build": "21.2.5",
25+
"@angular/cli": "21.2.5",
26+
"@angular/compiler-cli": "21.2.6",
27+
"@hey-api/openapi-ts": "0.94.5",
2828
"@tailwindcss/postcss": "4.2.2",
29-
"@types/node": "25.3.5",
29+
"@types/node": "25.5.0",
3030
"litestar-vite-plugin": "file:../..",
3131
"postcss": "8.5.8",
3232
"tailwindcss": "4.2.2",

examples/angular/package.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,26 @@
1010
"generate-types": "openapi-ts --config openapi-ts.config.ts"
1111
},
1212
"dependencies": {
13-
"@angular/animations": "21.2.5",
14-
"@angular/common": "21.2.5",
15-
"@angular/compiler": "21.2.5",
16-
"@angular/core": "21.2.5",
17-
"@angular/forms": "21.2.5",
18-
"@angular/platform-browser": "21.2.5",
19-
"@angular/router": "21.2.5",
13+
"@angular/animations": "21.2.6",
14+
"@angular/common": "21.2.6",
15+
"@angular/compiler": "21.2.6",
16+
"@angular/core": "21.2.6",
17+
"@angular/forms": "21.2.6",
18+
"@angular/platform-browser": "21.2.6",
19+
"@angular/router": "21.2.6",
2020
"rxjs": "7.8.2"
2121
},
2222
"devDependencies": {
23-
"@analogjs/vite-plugin-angular": "2.3.1",
24-
"@angular/build": "21.2.3",
25-
"@angular/compiler-cli": "21.2.5",
26-
"@angular/platform-browser-dynamic": "21.2.5",
27-
"@hey-api/openapi-ts": "0.94.0",
23+
"@analogjs/vite-plugin-angular": "2.4.0",
24+
"@angular/build": "21.2.5",
25+
"@angular/compiler-cli": "21.2.6",
26+
"@angular/platform-browser-dynamic": "21.2.6",
27+
"@hey-api/openapi-ts": "0.94.5",
2828
"@tailwindcss/vite": "4.2.2",
29-
"@types/node": "25.3.5",
29+
"@types/node": "25.5.0",
3030
"litestar-vite-plugin": "file:../..",
3131
"tailwindcss": "4.2.2",
3232
"typescript": "5.9.3",
33-
"vite": "8.0.1"
33+
"vite": "8.0.3"
3434
}
3535
}

examples/astro/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
"generate-types": "openapi-ts --config openapi-ts.config.ts"
1414
},
1515
"dependencies": {
16-
"astro": "5.18.0",
16+
"astro": "5.18.1",
1717
"zod": "4.3.6"
1818
},
1919
"devDependencies": {
20-
"@hey-api/openapi-ts": "0.94.0",
20+
"@hey-api/openapi-ts": "0.94.5",
2121
"@tailwindcss/vite": "4.2.2",
2222
"litestar-vite-plugin": "file:../..",
2323
"tailwindcss": "4.2.2",
2424
"typescript": "5.9.3",
25-
"vite": "8.0.1"
25+
"vite": "8.0.3"
2626
}
2727
}

examples/jinja-htmx/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@
1616
"@tailwindcss/vite": "4.2.2",
1717
"tailwindcss": "4.2.2",
1818
"litestar-vite-plugin": "file:../..",
19-
"vite": "8.0.1"
19+
"vite": "8.0.3"
2020
}
2121
}

examples/nuxt/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@
1313
"generate-types": "openapi-ts --config openapi-ts.config.ts"
1414
},
1515
"dependencies": {
16-
"nuxt": "4.3.1",
17-
"vue": "3.5.29",
16+
"nuxt": "4.4.2",
17+
"vue": "3.5.31",
1818
"zod": "4.3.6"
1919
},
2020
"devDependencies": {
21-
"@hey-api/openapi-ts": "0.94.0",
21+
"@hey-api/openapi-ts": "0.94.5",
2222
"@tailwindcss/vite": "4.2.2",
2323
"litestar-vite-plugin": "file:../..",
2424
"tailwindcss": "4.2.2",
2525
"typescript": "5.9.3",
26-
"vite": "8.0.1",
27-
"vue-tsc": "3.2.5"
26+
"vite": "8.0.3",
27+
"vue-tsc": "3.2.6"
2828
}
2929
}

examples/react-inertia-jinja/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"tailwindcss": "4.2.2",
2424
"litestar-vite-plugin": "file:../..",
2525
"typescript": "5.9.3",
26-
"vite": "8.0.1",
27-
"@hey-api/openapi-ts": "0.94.0"
26+
"vite": "8.0.3",
27+
"@hey-api/openapi-ts": "0.94.5"
2828
}
2929
}

examples/react-inertia/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"tailwindcss": "4.2.2",
2424
"litestar-vite-plugin": "file:../..",
2525
"typescript": "5.9.3",
26-
"vite": "8.0.1",
27-
"@hey-api/openapi-ts": "0.94.0"
26+
"vite": "8.0.3",
27+
"@hey-api/openapi-ts": "0.94.5"
2828
}
2929
}

examples/react-tanstack/app.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""SPA React + TanStack Router example - shared "Library" backend + React SPA frontend.
2+
3+
All examples in this repository expose the same backend:
4+
- `/api/summary` - overview + featured book
5+
- `/api/books` - list of books
6+
- `/api/books/{book_id}` - single book
7+
8+
This example uses TanStack Router for file-based routing. The TypeGenConfig
9+
includes extra_commands to run `tsr generate` so that `routeTree.gen.ts`
10+
is always up-to-date during `litestar assets generate-types` and `build`.
11+
"""
12+
13+
import os
14+
from pathlib import Path
15+
16+
from litestar import Controller, Litestar, get
17+
from litestar.exceptions import NotFoundException
18+
from msgspec import Struct
19+
20+
from litestar_vite import PathConfig, RuntimeConfig, TypeGenConfig, ViteConfig, VitePlugin
21+
22+
here = Path(__file__).parent
23+
DEV_MODE = os.getenv("VITE_DEV_MODE", "true").lower() in {"true", "1", "yes"}
24+
25+
26+
class Book(Struct):
27+
id: int
28+
title: str
29+
author: str
30+
year: int
31+
tags: list[str]
32+
33+
34+
class Summary(Struct):
35+
app: str
36+
headline: str
37+
total_books: int
38+
featured: Book
39+
40+
41+
BOOKS: list[Book] = [
42+
Book(id=1, title="Async Python", author="C. Developer", year=2024, tags=["python", "async"]),
43+
Book(id=2, title="Type-Safe Web", author="J. Dev", year=2025, tags=["typescript", "api"]),
44+
Book(id=3, title="Frontend Patterns", author="A. Designer", year=2023, tags=["frontend", "ux"]),
45+
]
46+
47+
48+
def _get_book(book_id: int) -> Book:
49+
for book in BOOKS:
50+
if book.id == book_id:
51+
return book
52+
raise NotFoundException(detail=f"Book {book_id} not found")
53+
54+
55+
def _get_summary() -> Summary:
56+
"""Build summary data.
57+
58+
Returns:
59+
The summary data.
60+
"""
61+
return Summary(
62+
app="litestar-vite library", headline="One backend, many frontends", total_books=len(BOOKS), featured=BOOKS[0]
63+
)
64+
65+
66+
class LibraryController(Controller):
67+
"""Library API controller."""
68+
69+
@get("/api/summary")
70+
async def summary(self) -> Summary:
71+
"""Overview endpoint used across all examples.
72+
73+
Returns:
74+
The result.
75+
"""
76+
return _get_summary()
77+
78+
@get("/api/books")
79+
async def books(self) -> list[Book]:
80+
"""Return all books.
81+
82+
Returns:
83+
The result.
84+
"""
85+
return BOOKS
86+
87+
@get("/api/books/{book_id:int}")
88+
async def book_detail(self, book_id: int) -> Book:
89+
"""Return a single book by id.
90+
91+
Returns:
92+
The result.
93+
"""
94+
return _get_book(book_id)
95+
96+
97+
# [docs-start:tanstack-vite-config]
98+
# TanStack Router uses a Vite plugin that generates routeTree.gen.ts.
99+
# extra_commands ensures `tsr generate` runs during `generate-types` and `build`,
100+
# so `tsc --noEmit` passes without needing a full Vite dev/build cycle first.
101+
vite = VitePlugin(
102+
config=ViteConfig(
103+
mode="spa",
104+
dev_mode=DEV_MODE,
105+
paths=PathConfig(root=here),
106+
types=TypeGenConfig(extra_commands=[["tsr", "generate"]]),
107+
runtime=RuntimeConfig(port=5005),
108+
)
109+
)
110+
111+
app = Litestar(route_handlers=[LibraryController], plugins=[vite], debug=True)
112+
# [docs-end:tanstack-vite-config]

0 commit comments

Comments
 (0)