litestar-vite 0.15.0__py3-none-any.whl → 0.15.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. litestar_vite/_codegen/__init__.py +26 -0
  2. litestar_vite/_codegen/inertia.py +407 -0
  3. litestar_vite/{codegen/_openapi.py → _codegen/openapi.py} +11 -58
  4. litestar_vite/{codegen/_routes.py → _codegen/routes.py} +43 -110
  5. litestar_vite/{codegen/_ts.py → _codegen/ts.py} +19 -19
  6. litestar_vite/_handler/__init__.py +8 -0
  7. litestar_vite/{handler/_app.py → _handler/app.py} +29 -117
  8. litestar_vite/cli.py +254 -155
  9. litestar_vite/codegen.py +39 -0
  10. litestar_vite/commands.py +6 -0
  11. litestar_vite/{config/__init__.py → config.py} +726 -99
  12. litestar_vite/deploy.py +3 -14
  13. litestar_vite/doctor.py +6 -8
  14. litestar_vite/executor.py +1 -45
  15. litestar_vite/handler.py +9 -0
  16. litestar_vite/html_transform.py +5 -148
  17. litestar_vite/inertia/__init__.py +0 -24
  18. litestar_vite/inertia/_utils.py +0 -5
  19. litestar_vite/inertia/exception_handler.py +16 -22
  20. litestar_vite/inertia/helpers.py +18 -546
  21. litestar_vite/inertia/plugin.py +11 -77
  22. litestar_vite/inertia/request.py +0 -48
  23. litestar_vite/inertia/response.py +17 -113
  24. litestar_vite/inertia/types.py +0 -19
  25. litestar_vite/loader.py +7 -7
  26. litestar_vite/plugin.py +2184 -0
  27. litestar_vite/templates/angular/package.json.j2 +1 -2
  28. litestar_vite/templates/angular-cli/package.json.j2 +1 -2
  29. litestar_vite/templates/base/package.json.j2 +1 -2
  30. litestar_vite/templates/react-inertia/package.json.j2 +1 -2
  31. litestar_vite/templates/vue-inertia/package.json.j2 +1 -2
  32. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/METADATA +5 -5
  33. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/RECORD +36 -49
  34. litestar_vite/codegen/__init__.py +0 -48
  35. litestar_vite/codegen/_export.py +0 -229
  36. litestar_vite/codegen/_inertia.py +0 -619
  37. litestar_vite/codegen/_utils.py +0 -141
  38. litestar_vite/config/_constants.py +0 -97
  39. litestar_vite/config/_deploy.py +0 -70
  40. litestar_vite/config/_inertia.py +0 -241
  41. litestar_vite/config/_paths.py +0 -63
  42. litestar_vite/config/_runtime.py +0 -235
  43. litestar_vite/config/_spa.py +0 -93
  44. litestar_vite/config/_types.py +0 -94
  45. litestar_vite/handler/__init__.py +0 -9
  46. litestar_vite/inertia/precognition.py +0 -274
  47. litestar_vite/plugin/__init__.py +0 -687
  48. litestar_vite/plugin/_process.py +0 -185
  49. litestar_vite/plugin/_proxy.py +0 -689
  50. litestar_vite/plugin/_proxy_headers.py +0 -244
  51. litestar_vite/plugin/_static.py +0 -37
  52. litestar_vite/plugin/_utils.py +0 -489
  53. /litestar_vite/{handler/_routing.py → _handler/routing.py} +0 -0
  54. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/WHEEL +0 -0
  55. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/licenses/LICENSE +0 -0
litestar_vite/cli.py CHANGED
@@ -1,4 +1,3 @@
1
- import contextlib
2
1
  import os
3
2
  import subprocess
4
3
  import sys
@@ -6,6 +5,7 @@ from pathlib import Path
6
5
  from textwrap import dedent
7
6
  from typing import TYPE_CHECKING, Any
8
7
 
8
+ import msgspec
9
9
  from click import Choice, Context, group, option
10
10
  from click import Path as ClickPath
11
11
  from litestar.cli._utils import ( # pyright: ignore[reportPrivateImportUsage]
@@ -14,15 +14,25 @@ from litestar.cli._utils import ( # pyright: ignore[reportPrivateImportUsage]
14
14
  LitestarGroup,
15
15
  console,
16
16
  )
17
+ from litestar.exceptions import SerializationException
18
+ from litestar.serialization import decode_json, encode_json, get_serializer
17
19
  from rich.panel import Panel
18
20
  from rich.prompt import Confirm, Prompt
19
21
 
20
- from litestar_vite.codegen import encode_deterministic_json, generate_routes_json, generate_routes_ts, write_if_changed
21
- from litestar_vite.config import DeployConfig, ExternalDevServer, LoggingConfig, TypeGenConfig, ViteConfig
22
+ from litestar_vite.codegen import generate_inertia_pages_json, generate_routes_json, generate_routes_ts
23
+ from litestar_vite.config import (
24
+ DeployConfig,
25
+ ExternalDevServer,
26
+ InertiaConfig,
27
+ InertiaTypeGenConfig,
28
+ LoggingConfig,
29
+ TypeGenConfig,
30
+ ViteConfig,
31
+ )
22
32
  from litestar_vite.deploy import ViteDeployer, format_bytes
23
33
  from litestar_vite.doctor import ViteDoctor
24
34
  from litestar_vite.exceptions import ViteExecutionError
25
- from litestar_vite.plugin import VitePlugin, set_environment
35
+ from litestar_vite.plugin import VitePlugin, resolve_litestar_version, set_environment
26
36
  from litestar_vite.scaffolding import TemplateContext, generate_project, get_available_templates
27
37
  from litestar_vite.scaffolding.templates import FrameworkType, get_template
28
38
 
@@ -87,6 +97,19 @@ def _apply_cli_log_level(config: ViteConfig, *, verbose: bool = False, quiet: bo
87
97
  config.reset_executor()
88
98
 
89
99
 
100
+ def _relative_path(path: Path) -> str:
101
+ """Return path relative to CWD when possible.
102
+
103
+ Returns:
104
+ The relative path string when possible, otherwise the absolute path string.
105
+ """
106
+
107
+ try:
108
+ return str(path.relative_to(Path.cwd()))
109
+ except ValueError:
110
+ return str(path)
111
+
112
+
90
113
  def _print_recommended_config(template_name: str, resource_dir: str, bundle_dir: str) -> None:
91
114
  """Print recommended ViteConfig for the scaffolded template.
92
115
 
@@ -185,13 +208,9 @@ def _build_deploy_config(
185
208
  return deploy_config
186
209
 
187
210
 
188
- def _run_vite_build(
189
- config: ViteConfig, root_dir: Path, console: Any, no_build: bool, app: "Litestar | None" = None
190
- ) -> None:
211
+ def _run_vite_build(config: ViteConfig, root_dir: Path, console: Any, no_build: bool) -> None:
191
212
  """Run Vite build unless skipped.
192
213
 
193
- If app is provided, exports schema/routes before building.
194
-
195
214
  Raises:
196
215
  SystemExit: If the build fails.
197
216
  """
@@ -199,14 +218,10 @@ def _run_vite_build(
199
218
  console.print("[dim]Skipping Vite build (--no-build).[/]")
200
219
  return
201
220
 
202
- # Export schema/routes if app is provided
203
- if app is not None:
204
- _generate_schema_and_routes(app, config, console)
205
-
206
221
  console.rule("[yellow]Starting Vite build process[/]", align="left")
207
222
  if config.set_environment:
208
- set_environment(config=config, asset_url_override=config.asset_url)
209
- os.environ.setdefault("VITE_BASE_URL", config.base_url or "/")
223
+ set_environment(config=config, asset_url_override=config.base_url or config.asset_url)
224
+ os.environ["VITE_BASE_URL"] = config.base_url or config.asset_url or "/"
210
225
  try:
211
226
  config.executor.execute(config.build_command, cwd=root_dir)
212
227
  console.print("[bold green]✓ Build complete[/]")
@@ -218,36 +233,52 @@ def _run_vite_build(
218
233
  def _generate_schema_and_routes(app: "Litestar", config: ViteConfig, console: Any) -> None:
219
234
  """Export OpenAPI schema, routes, and Inertia page props prior to running a build.
220
235
 
221
- Uses the shared `export_integration_assets` function to guarantee byte-identical
222
- output between CLI and Plugin.
223
-
224
236
  Skips generation when type generation is disabled.
225
237
 
238
+ When Inertia page-props export is enabled, the generator may add additional component schemas
239
+ for routes that are excluded from OpenAPI. In that case, this function persists the augmented
240
+ OpenAPI document so the TypeScript generator can emit those types.
241
+
226
242
  Raises:
227
243
  LitestarCLIException: If export fails.
228
244
  """
229
- from litestar_vite.codegen import export_integration_assets
230
-
231
245
  types_config = config.types
232
246
  if not isinstance(types_config, TypeGenConfig):
233
247
  return
234
248
 
235
249
  console.print("[dim]Preparing OpenAPI schema and routes...[/]")
250
+ _export_openapi_schema(app, types_config)
251
+ _export_routes_metadata(app, types_config)
236
252
 
237
- try:
238
- result = export_integration_assets(app, config)
239
-
240
- # Report results with detailed status
241
- for file in result.exported_files:
242
- console.print(f"[green]✓ Exported {file}[/] [dim](updated)[/]")
243
- for file in result.unchanged_files:
244
- console.print(f"[dim]✓ {file} (unchanged)[/]")
245
-
246
- if not result.exported_files and not result.unchanged_files:
247
- console.print("[yellow]! No files exported (OpenAPI may not be available)[/]")
248
- except (OSError, TypeError, ValueError) as exc:
249
- msg = f"Failed to export type metadata: {exc}"
250
- raise LitestarCLIException(msg) from exc
253
+ if types_config.generate_routes:
254
+ console.print("[dim]3. Exporting typed routes...[/]")
255
+ try:
256
+ routes_ts_content = generate_routes_ts(app)
257
+ routes_ts_path = types_config.routes_ts_path or (types_config.output / "routes.ts")
258
+ routes_ts_path.parent.mkdir(parents=True, exist_ok=True)
259
+ routes_ts_path.write_text(routes_ts_content, encoding="utf-8")
260
+ console.print(f"[green]✓ Typed routes exported to {_relative_path(routes_ts_path)}[/]")
261
+ except OSError as exc: # pragma: no cover
262
+ msg = f"Failed to export typed routes: {exc}"
263
+ raise LitestarCLIException(msg) from exc
264
+
265
+ if isinstance(config.inertia, InertiaConfig) and types_config.generate_page_props and types_config.page_props_path:
266
+ openapi_schema: dict[str, Any] | None = None
267
+ try:
268
+ if types_config.openapi_path and types_config.openapi_path.exists():
269
+ openapi_schema = decode_json(types_config.openapi_path.read_bytes())
270
+ except (OSError, SerializationException):
271
+ pass
272
+
273
+ _export_inertia_pages_metadata(app, types_config, config.inertia, openapi_schema)
274
+ if openapi_schema is not None and types_config.openapi_path is not None:
275
+ try:
276
+ schema_content = msgspec.json.format(encode_json(openapi_schema), indent=2)
277
+ types_config.openapi_path.parent.mkdir(parents=True, exist_ok=True)
278
+ types_config.openapi_path.write_bytes(schema_content)
279
+ except OSError as exc: # pragma: no cover
280
+ msg = f"Failed to update OpenAPI schema: {exc}"
281
+ raise LitestarCLIException(msg) from exc
251
282
 
252
283
 
253
284
  @group(cls=LitestarGroup, name="assets")
@@ -585,46 +616,6 @@ def vite_install(app: "Litestar", verbose: "bool", quiet: "bool") -> None:
585
616
  console.print("[red]Executor not configured.[/]")
586
617
 
587
618
 
588
- @vite_group.command(name="update", help="Update frontend packages.")
589
- @option(
590
- "--latest", type=bool, help="Update to latest versions (ignoring semver constraints).", default=False, is_flag=True
591
- )
592
- @option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
593
- @option("--quiet", type=bool, help="Suppress non-essential output.", default=False, is_flag=True)
594
- def vite_update(app: "Litestar", latest: "bool", verbose: "bool", quiet: "bool") -> None:
595
- """Update frontend packages.
596
-
597
- By default, updates packages within their semver constraints defined in package.json.
598
- Use --latest to update to the newest versions available.
599
-
600
- Raises:
601
- SystemExit: If the update fails.
602
- """
603
- if verbose:
604
- app.debug = True
605
-
606
- plugin = app.plugins.get(VitePlugin)
607
-
608
- _apply_cli_log_level(plugin.config, verbose=verbose, quiet=quiet)
609
-
610
- if not quiet:
611
- if latest:
612
- console.rule("[yellow]Updating packages to latest versions[/]", align="left")
613
- else:
614
- console.rule("[yellow]Updating packages[/]", align="left")
615
-
616
- if plugin.config.executor:
617
- root_dir = Path(plugin.config.root_dir or Path.cwd())
618
- try:
619
- plugin.config.executor.update(root_dir, latest=latest)
620
- console.print("[bold green]✓ Packages updated[/]")
621
- except ViteExecutionError as e:
622
- console.print(f"[bold red]✗ Package update failed: {e!s}[/]")
623
- raise SystemExit(1) from None
624
- else:
625
- console.print("[red]Executor not configured.[/]")
626
-
627
-
628
619
  @vite_group.command(name="build", help="Building frontend assets with Vite.")
629
620
  @option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
630
621
  @option("--quiet", type=bool, help="Suppress non-essential output.", default=False, is_flag=True)
@@ -710,9 +701,8 @@ def vite_deploy(
710
701
 
711
702
  root_dir = Path(config.root_dir or Path.cwd())
712
703
  bundle_dir = config.bundle_dir
713
-
714
704
  try:
715
- _run_vite_build(config, root_dir, console, no_build, app=app)
705
+ _run_vite_build(config, root_dir, console, no_build)
716
706
  except SystemExit as exc:
717
707
  console.print(f"[red]{exc}[/]")
718
708
  sys.exit(1)
@@ -751,7 +741,7 @@ def vite_deploy(
751
741
 
752
742
  @vite_group.command(
753
743
  name="serve",
754
- help="Serve frontend assets. For meta-frameworks (mode='framework'; aliases: 'ssr'/'ssg'), runs production Node server. Otherwise runs Vite dev server.",
744
+ help="Serve frontend assets. For SSR frameworks (mode='ssr'), runs production Node server. Otherwise runs Vite dev server.",
755
745
  )
756
746
  @option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
757
747
  @option("--quiet", type=bool, help="Suppress non-essential output.", default=False, is_flag=True)
@@ -862,13 +852,12 @@ def export_routes(
862
852
 
863
853
  console.rule(f"[yellow]Exporting typed routes to {output}[/]", align="left")
864
854
 
865
- global_route = bool(isinstance(config.types, TypeGenConfig) and config.types.global_route)
866
- routes_ts_content = generate_routes_ts(app, only=only_list, exclude=exclude_list, global_route=global_route)
855
+ routes_ts_content = generate_routes_ts(app, only=only_list, exclude=exclude_list)
867
856
 
868
857
  try:
869
- changed = write_if_changed(output, routes_ts_content)
870
- status = "updated" if changed else "unchanged"
871
- console.print(f"[green]✓ Typed routes exported to {output}[/] [dim]({status})[/]")
858
+ output.parent.mkdir(parents=True, exist_ok=True)
859
+ output.write_text(routes_ts_content, encoding="utf-8")
860
+ console.print(f"[green]✓ Typed routes exported to {output}[/]")
872
861
  except OSError as e: # pragma: no cover
873
862
  msg = f"Failed to write routes to path {output}"
874
863
  raise LitestarCLIException(msg) from e
@@ -888,16 +877,125 @@ def export_routes(
888
877
  )
889
878
 
890
879
  try:
891
- content = encode_deterministic_json(routes_data)
892
- changed = write_if_changed(output, content)
893
- status = "updated" if changed else "unchanged"
894
- console.print(f"[green]✓ Routes exported to {output}[/] [dim]({status})[/]")
880
+ content = msgspec.json.format(encode_json(routes_data), indent=2)
881
+ output.parent.mkdir(parents=True, exist_ok=True)
882
+ output.write_bytes(content)
883
+ console.print(f"[green]✓ Routes exported to {output}[/]")
895
884
  console.print(f"[dim] {len(routes_data.get('routes', {}))} routes exported[/]")
896
885
  except OSError as e: # pragma: no cover
897
886
  msg = f"Failed to write routes to path {output}"
898
887
  raise LitestarCLIException(msg) from e
899
888
 
900
889
 
890
+ def _export_openapi_schema(app: "Litestar", types_config: Any) -> None:
891
+ """Export OpenAPI schema to file.
892
+
893
+ Args:
894
+ app: The Litestar application instance.
895
+ types_config: The TypeGenConfig instance.
896
+
897
+ Raises:
898
+ LitestarCLIException: If export fails.
899
+ """
900
+ console.print("[dim]1. Exporting OpenAPI schema...[/]")
901
+ try:
902
+ serializer = get_serializer(app.type_encoders)
903
+ schema_dict = app.openapi_schema.to_schema()
904
+ schema_content = msgspec.json.format(encode_json(schema_dict, serializer=serializer), indent=2)
905
+ types_config.openapi_path.parent.mkdir(parents=True, exist_ok=True)
906
+ types_config.openapi_path.write_bytes(schema_content)
907
+ console.print(f"[green]✓ Schema exported to {_relative_path(types_config.openapi_path)}[/]")
908
+ except OSError as e:
909
+ msg = f"Failed to export OpenAPI schema: {e}"
910
+ raise LitestarCLIException(msg) from e
911
+
912
+
913
+ def _export_routes_metadata(app: "Litestar", types_config: Any) -> None:
914
+ """Export routes metadata to file.
915
+
916
+ Args:
917
+ app: The Litestar application instance.
918
+ types_config: The TypeGenConfig instance.
919
+
920
+ Raises:
921
+ LitestarCLIException: If export fails.
922
+ """
923
+ console.print("[dim]2. Exporting route metadata...[/]")
924
+ try:
925
+ routes_data = generate_routes_json(app, include_components=True)
926
+ routes_data["litestar_version"] = resolve_litestar_version()
927
+ routes_content = msgspec.json.format(encode_json(routes_data), indent=2)
928
+ types_config.routes_path.parent.mkdir(parents=True, exist_ok=True)
929
+ types_config.routes_path.write_bytes(routes_content)
930
+ console.print(f"[green]✓ Routes exported to {_relative_path(types_config.routes_path)}[/]")
931
+ except OSError as e:
932
+ msg = f"Failed to export routes: {e}"
933
+ raise LitestarCLIException(msg) from e
934
+
935
+
936
+ def _export_routes_typescript(app: "Litestar", types_config: Any) -> None:
937
+ """Export typed routes TypeScript file (Ziggy-style).
938
+
939
+ Args:
940
+ app: The Litestar application instance.
941
+ types_config: The TypeGenConfig instance.
942
+
943
+ Raises:
944
+ LitestarCLIException: If export fails.
945
+ """
946
+ if not types_config.generate_routes:
947
+ return
948
+
949
+ console.print("[dim] Generating typed routes.ts...[/]")
950
+ try:
951
+ ts_content = generate_routes_ts(app)
952
+ types_config.routes_ts_path.parent.mkdir(parents=True, exist_ok=True)
953
+ types_config.routes_ts_path.write_text(ts_content)
954
+ console.print(f"[green]✓ Typed routes generated at {_relative_path(types_config.routes_ts_path)}[/]")
955
+ except OSError as e:
956
+ msg = f"Failed to generate typed routes: {e}"
957
+ raise LitestarCLIException(msg) from e
958
+
959
+
960
+ def _export_inertia_pages_metadata(
961
+ app: "Litestar", types_config: Any, inertia_config: Any, openapi_schema: "dict[str, Any] | None" = None
962
+ ) -> None:
963
+ """Export Inertia page props metadata to file.
964
+
965
+ Args:
966
+ app: The Litestar application instance.
967
+ types_config: The TypeGenConfig instance.
968
+ inertia_config: The InertiaConfig instance.
969
+ openapi_schema: Optional pre-loaded OpenAPI schema.
970
+
971
+ Raises:
972
+ LitestarCLIException: If export fails.
973
+ """
974
+ console.print("[dim]4. Exporting Inertia page props metadata...[/]")
975
+ inertia_type_gen = inertia_config.type_gen
976
+ if inertia_type_gen is None:
977
+ inertia_type_gen = InertiaTypeGenConfig()
978
+
979
+ try:
980
+ pages_data = generate_inertia_pages_json(
981
+ app,
982
+ openapi_schema=openapi_schema,
983
+ include_default_auth=inertia_type_gen.include_default_auth,
984
+ include_default_flash=inertia_type_gen.include_default_flash,
985
+ inertia_config=inertia_config,
986
+ types_config=types_config,
987
+ )
988
+ pages_content = msgspec.json.format(encode_json(pages_data), indent=2)
989
+ types_config.page_props_path.parent.mkdir(parents=True, exist_ok=True)
990
+ types_config.page_props_path.write_bytes(pages_content)
991
+ num_pages = len(pages_data.get("pages", {}))
992
+ console.print(f"[green]✓ Page props exported to {_relative_path(types_config.page_props_path)}[/]")
993
+ console.print(f"[dim] {num_pages} Inertia page(s) found[/]")
994
+ except OSError as e:
995
+ msg = f"Failed to export Inertia page props: {e}"
996
+ raise LitestarCLIException(msg) from e
997
+
998
+
901
999
  def _get_package_executor_cmd(executor: "str | None", package: str) -> "list[str]":
902
1000
  """Build package executor command list.
903
1001
 
@@ -924,43 +1022,67 @@ def _get_package_executor_cmd(executor: "str | None", package: str) -> "list[str
924
1022
  return ["npx", package]
925
1023
 
926
1024
 
927
- def _invoke_typegen_cli(config: Any, verbose: bool) -> None:
928
- """Invoke the unified TypeScript type generation CLI.
929
-
930
- This is the single entry point for TypeScript type generation, used by
931
- `litestar assets generate-types`. It calls `litestar-vite-typegen` which
932
- handles both @hey-api/openapi-ts and page-props.ts generation.
1025
+ def _run_openapi_ts(
1026
+ config: Any, verbose: bool, install_command: "list[str] | None" = None, executor: "str | None" = None
1027
+ ) -> None:
1028
+ """Run @hey-api/openapi-ts to generate TypeScript types.
933
1029
 
934
1030
  Args:
935
1031
  config: The ViteConfig instance (with .types resolved).
936
1032
  verbose: Whether to show verbose output.
937
-
938
- Raises:
939
- LitestarCLIException: If type generation fails.
1033
+ install_command: Command used to install JS dependencies.
1034
+ executor: The JS runtime executor (node, bun, deno, yarn, pnpm).
940
1035
  """
1036
+ types_config = config.types
941
1037
  root_dir = config.root_dir or Path.cwd()
942
- executor = config.runtime.executor
1038
+ resource_dir = Path(config.resource_dir)
1039
+ if not resource_dir.is_absolute():
1040
+ resource_dir = root_dir / resource_dir
943
1041
 
944
- # Build the command to run the unified TypeScript CLI
945
- pkg_cmd = _get_package_executor_cmd(executor, "litestar-vite-typegen")
946
- cmd = [*pkg_cmd]
1042
+ console.print("[dim]3. Running @hey-api/openapi-ts...[/]")
947
1043
 
948
- if verbose:
949
- cmd.append("--verbose")
1044
+ install_cmd = install_command or ["npm", "install"]
1045
+ pkg_cmd = _get_package_executor_cmd(executor, "@hey-api/openapi-ts")
950
1046
 
951
1047
  try:
952
- # Run the CLI, letting stdout/stderr pass through to the terminal
953
- result = subprocess.run(cmd, cwd=root_dir, check=False)
954
- if result.returncode != 0:
955
- msg = "TypeScript type generation failed"
956
- raise LitestarCLIException(msg)
1048
+ check_cmd = [*pkg_cmd, "--version"]
1049
+ subprocess.run(check_cmd, check=True, capture_output=True, cwd=root_dir)
1050
+
1051
+ candidate_configs = [
1052
+ resource_dir / "openapi-ts.config.ts",
1053
+ resource_dir / "hey-api.config.ts",
1054
+ root_dir / "openapi-ts.config.ts",
1055
+ root_dir / "hey-api.config.ts",
1056
+ ]
1057
+ config_path = next((p for p in candidate_configs if p.exists()), None)
1058
+
1059
+ if config_path is not None:
1060
+ openapi_cmd = [*pkg_cmd, "--file", str(config_path)]
1061
+ else:
1062
+ openapi_cmd = [*pkg_cmd, "-i", str(types_config.openapi_path), "-o", str(types_config.output)]
1063
+
1064
+ plugins: list[str] = ["@hey-api/typescript", "@hey-api/schemas"]
1065
+ if types_config.generate_sdk:
1066
+ plugins.extend(["@hey-api/sdk"])
1067
+ if types_config.generate_zod:
1068
+ plugins.append("zod")
1069
+
1070
+ if plugins:
1071
+ openapi_cmd.extend(["--plugins", *plugins])
1072
+
1073
+ subprocess.run(openapi_cmd, check=True, cwd=root_dir)
1074
+ console.print(f"[green]✓ Types generated in {types_config.output}[/]")
1075
+ except subprocess.CalledProcessError as e:
1076
+ console.print("[yellow]! @hey-api/openapi-ts failed - install it with:[/]")
1077
+ extra = ["@hey-api/openapi-ts"]
1078
+ if types_config.generate_zod:
1079
+ extra.append("zod")
1080
+ console.print(f"[dim] {' '.join([*install_cmd, '-D', *extra])}[/]")
1081
+ if verbose:
1082
+ console.print(f"[dim]Error: {e!s}[/]")
957
1083
  except FileNotFoundError:
958
1084
  runtime_name = executor or "Node.js"
959
- console.print(
960
- f"[yellow]! litestar-vite-typegen not found - ensure {runtime_name} and litestar-vite-plugin are installed[/]"
961
- )
962
- msg = f"Package executor not found - ensure {runtime_name} is installed"
963
- raise LitestarCLIException(msg) from None
1085
+ console.print(f"[yellow]! Package executor not found - ensure {runtime_name} is installed[/]")
964
1086
 
965
1087
 
966
1088
  @vite_group.command(name="generate-types", help="Generate TypeScript types from OpenAPI schema and routes.")
@@ -968,22 +1090,16 @@ def _invoke_typegen_cli(config: Any, verbose: bool) -> None:
968
1090
  def generate_types(app: "Litestar", verbose: "bool") -> None:
969
1091
  """Generate TypeScript types from OpenAPI schema and routes.
970
1092
 
971
- Uses the unified TypeScript CLI (litestar-vite-typegen) to ensure
972
- identical output between CLI, dev server, and build commands.
973
-
974
1093
  This command:
975
- 1. Exports OpenAPI schema, routes, and page props metadata
976
- 2. Invokes the unified TypeScript CLI which handles:
977
- - @hey-api/openapi-ts for API types
978
- - page-props.ts for Inertia page props (if enabled)
1094
+ 1. Exports the OpenAPI schema (uses litestar's built-in schema generation)
1095
+ 2. Exports route metadata
1096
+ 3. Runs @hey-api/openapi-ts to generate TypeScript types
1097
+ 4. If Inertia is enabled: exports page props metadata
979
1098
 
980
1099
  Args:
981
1100
  app: The Litestar application instance.
982
1101
  verbose: Whether to enable verbose output.
983
1102
  """
984
- from litestar_vite.codegen import export_integration_assets
985
- from litestar_vite.plugin._utils import write_runtime_config_file
986
-
987
1103
  if verbose:
988
1104
  app.debug = True
989
1105
 
@@ -997,35 +1113,20 @@ def generate_types(app: "Litestar", verbose: "bool") -> None:
997
1113
 
998
1114
  console.rule("[yellow]Generating TypeScript types[/]", align="left")
999
1115
 
1000
- config_path, config_changed = write_runtime_config_file(config, return_status=True)
1001
- config_display = Path(config_path)
1002
- with contextlib.suppress(ValueError):
1003
- config_display = config_display.relative_to(Path.cwd())
1004
- if config_changed:
1005
- console.print(f"[green] Exported {config_display}[/] [dim](updated)[/]")
1006
- else:
1007
- console.print(f"[dim]✓ {config_display} (unchanged)[/]")
1008
-
1009
- # Export all integration assets using the shared function
1010
- try:
1011
- result = export_integration_assets(app, config)
1012
-
1013
- # Report results with detailed status
1014
- for file in result.exported_files:
1015
- console.print(f"[green]✓ Exported {file}[/] [dim](updated)[/]")
1016
- for file in result.unchanged_files:
1017
- console.print(f"[dim]✓ {file} (unchanged)[/]")
1018
-
1019
- if not result.exported_files and not result.unchanged_files:
1020
- console.print("[yellow]! No files exported (OpenAPI may not be available)[/]")
1021
- return
1022
- except (OSError, TypeError, ValueError) as exc:
1023
- console.print(f"[red]✗ Failed to export type metadata: {exc}[/]")
1024
- return
1116
+ _export_openapi_schema(app, config.types)
1117
+ _export_routes_metadata(app, config.types)
1118
+ _export_routes_typescript(app, config.types)
1119
+ _run_openapi_ts(config, verbose, config.install_command, config.runtime.executor)
1120
+ if isinstance(config.inertia, InertiaConfig) and config.types.generate_page_props and config.types.page_props_path:
1121
+ openapi_schema: dict[str, Any] | None = None
1122
+ try:
1123
+ if config.types.openapi_path and config.types.openapi_path.exists():
1124
+ openapi_schema = decode_json(config.types.openapi_path.read_bytes())
1125
+ except (OSError, SerializationException):
1126
+ if verbose:
1127
+ console.print("[dim]! Could not load OpenAPI schema for type references[/]")
1025
1128
 
1026
- # Invoke the unified TypeScript type generation CLI
1027
- # This handles both @hey-api/openapi-ts and page-props.ts generation
1028
- _invoke_typegen_cli(config, verbose)
1129
+ _export_inertia_pages_metadata(app, config.types, config.inertia, openapi_schema)
1029
1130
 
1030
1131
 
1031
1132
  @vite_group.command(name="status", help="Check the status of the Vite integration.")
@@ -1042,13 +1143,11 @@ def vite_status(app: "Litestar") -> None:
1042
1143
  console.print(f"Assets URL: {config.asset_url}")
1043
1144
  console.print(f"Base URL: {config.base_url}")
1044
1145
 
1045
- manifest_candidates = config.candidate_manifest_paths()
1046
- found_manifest = next((path for path in manifest_candidates if path.exists()), None)
1047
- if found_manifest is not None:
1048
- console.print(f"[green]✓ Manifest found at {found_manifest}[/]")
1146
+ manifest_path = Path(f"{config.bundle_dir}/{config.manifest_name}")
1147
+ if manifest_path.exists():
1148
+ console.print(f"[green]✓ Manifest found at {manifest_path}[/]")
1049
1149
  else:
1050
- manifest_locations = " or ".join(str(path) for path in manifest_candidates)
1051
- console.print(f"[red]✗ Manifest not found at {manifest_locations}[/]")
1150
+ console.print(f"[red]✗ Manifest not found at {manifest_path}[/]")
1052
1151
 
1053
1152
  if config.dev_mode:
1054
1153
  url = f"{config.protocol}://{config.host}:{config.port}"
@@ -0,0 +1,39 @@
1
+ """Public code generation API.
2
+
3
+ The internal implementation lives in ``litestar_vite._codegen``.
4
+
5
+ This module provides a stable import surface for:
6
+
7
+ - Route metadata export (``routes.json`` + Ziggy-compatible TS)
8
+ - Inertia page props metadata export
9
+ """
10
+
11
+ from litestar_vite._codegen.inertia import ( # noqa: F401
12
+ InertiaPageMetadata,
13
+ _get_openapi_schema_ref, # pyright: ignore[reportPrivateUsage,reportUnusedImport]
14
+ _get_return_type_name, # pyright: ignore[reportPrivateUsage,reportUnusedImport]
15
+ extract_inertia_pages,
16
+ generate_inertia_pages_json,
17
+ )
18
+ from litestar_vite._codegen.routes import ( # noqa: F401
19
+ RouteMetadata,
20
+ _escape_ts_string, # pyright: ignore[reportPrivateUsage,reportUnusedImport]
21
+ _is_type_required, # pyright: ignore[reportPrivateUsage,reportUnusedImport]
22
+ _ts_type_for_param, # pyright: ignore[reportPrivateUsage,reportUnusedImport]
23
+ extract_route_metadata,
24
+ generate_routes_json,
25
+ generate_routes_ts,
26
+ )
27
+ from litestar_vite._codegen.ts import (
28
+ ts_type_from_openapi as _ts_type_from_openapi, # noqa: F401 # pyright: ignore[reportUnusedImport]
29
+ )
30
+
31
+ __all__ = (
32
+ "InertiaPageMetadata",
33
+ "RouteMetadata",
34
+ "extract_inertia_pages",
35
+ "extract_route_metadata",
36
+ "generate_inertia_pages_json",
37
+ "generate_routes_json",
38
+ "generate_routes_ts",
39
+ )
litestar_vite/commands.py CHANGED
@@ -12,8 +12,11 @@ from litestar_vite.exceptions import MissingDependencyError
12
12
  if TYPE_CHECKING:
13
13
  from pathlib import Path
14
14
 
15
+ from litestar import Litestar
16
+
15
17
 
16
18
  def init_vite(
19
+ app: "Litestar",
17
20
  root_path: "Path",
18
21
  resource_path: "Path",
19
22
  asset_url: "str",
@@ -21,12 +24,14 @@ def init_vite(
21
24
  bundle_path: "Path",
22
25
  enable_ssr: "bool",
23
26
  vite_port: int,
27
+ hot_file: "Path",
24
28
  litestar_port: int,
25
29
  framework: str = "react",
26
30
  ) -> None:
27
31
  """Initialize a new Vite project using the scaffolding system.
28
32
 
29
33
  Args:
34
+ app: The Litestar application instance.
30
35
  root_path: Root directory for the Vite project.
31
36
  resource_path: Directory containing source files.
32
37
  asset_url: Base URL for serving assets.
@@ -34,6 +39,7 @@ def init_vite(
34
39
  bundle_path: Output directory for built files.
35
40
  enable_ssr: Enable server-side rendering.
36
41
  vite_port: Port for Vite dev server.
42
+ hot_file: Path to hot reload manifest.
37
43
  litestar_port: Port for Litestar server.
38
44
  framework: Framework template to use (default: react).
39
45