litestar-vite 0.1.1__py3-none-any.whl → 0.15.0__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 (169) hide show
  1. litestar_vite/__init__.py +54 -4
  2. litestar_vite/__metadata__.py +12 -7
  3. litestar_vite/cli.py +1048 -10
  4. litestar_vite/codegen/__init__.py +48 -0
  5. litestar_vite/codegen/_export.py +229 -0
  6. litestar_vite/codegen/_inertia.py +619 -0
  7. litestar_vite/codegen/_openapi.py +280 -0
  8. litestar_vite/codegen/_routes.py +720 -0
  9. litestar_vite/codegen/_ts.py +235 -0
  10. litestar_vite/codegen/_utils.py +141 -0
  11. litestar_vite/commands.py +73 -0
  12. litestar_vite/config/__init__.py +997 -0
  13. litestar_vite/config/_constants.py +97 -0
  14. litestar_vite/config/_deploy.py +70 -0
  15. litestar_vite/config/_inertia.py +241 -0
  16. litestar_vite/config/_paths.py +63 -0
  17. litestar_vite/config/_runtime.py +235 -0
  18. litestar_vite/config/_spa.py +93 -0
  19. litestar_vite/config/_types.py +94 -0
  20. litestar_vite/deploy.py +366 -0
  21. litestar_vite/doctor.py +1181 -0
  22. litestar_vite/exceptions.py +78 -0
  23. litestar_vite/executor.py +360 -0
  24. litestar_vite/handler/__init__.py +9 -0
  25. litestar_vite/handler/_app.py +612 -0
  26. litestar_vite/handler/_routing.py +130 -0
  27. litestar_vite/html_transform.py +569 -0
  28. litestar_vite/inertia/__init__.py +77 -0
  29. litestar_vite/inertia/_utils.py +119 -0
  30. litestar_vite/inertia/exception_handler.py +178 -0
  31. litestar_vite/inertia/helpers.py +1571 -0
  32. litestar_vite/inertia/middleware.py +54 -0
  33. litestar_vite/inertia/plugin.py +199 -0
  34. litestar_vite/inertia/precognition.py +274 -0
  35. litestar_vite/inertia/request.py +334 -0
  36. litestar_vite/inertia/response.py +802 -0
  37. litestar_vite/inertia/types.py +335 -0
  38. litestar_vite/loader.py +464 -123
  39. litestar_vite/plugin/__init__.py +687 -0
  40. litestar_vite/plugin/_process.py +185 -0
  41. litestar_vite/plugin/_proxy.py +689 -0
  42. litestar_vite/plugin/_proxy_headers.py +244 -0
  43. litestar_vite/plugin/_static.py +37 -0
  44. litestar_vite/plugin/_utils.py +489 -0
  45. litestar_vite/py.typed +0 -0
  46. litestar_vite/scaffolding/__init__.py +20 -0
  47. litestar_vite/scaffolding/generator.py +270 -0
  48. litestar_vite/scaffolding/templates.py +437 -0
  49. litestar_vite/templates/__init__.py +0 -0
  50. litestar_vite/templates/addons/tailwindcss/tailwind.css.j2 +1 -0
  51. litestar_vite/templates/angular/index.html.j2 +12 -0
  52. litestar_vite/templates/angular/openapi-ts.config.ts.j2 +18 -0
  53. litestar_vite/templates/angular/package.json.j2 +36 -0
  54. litestar_vite/templates/angular/src/app/app.component.css.j2 +3 -0
  55. litestar_vite/templates/angular/src/app/app.component.html.j2 +1 -0
  56. litestar_vite/templates/angular/src/app/app.component.ts.j2 +9 -0
  57. litestar_vite/templates/angular/src/app/app.config.ts.j2 +5 -0
  58. litestar_vite/templates/angular/src/main.ts.j2 +9 -0
  59. litestar_vite/templates/angular/src/styles.css.j2 +9 -0
  60. litestar_vite/templates/angular/tsconfig.app.json.j2 +34 -0
  61. litestar_vite/templates/angular/tsconfig.json.j2 +20 -0
  62. litestar_vite/templates/angular/vite.config.ts.j2 +21 -0
  63. litestar_vite/templates/angular-cli/.postcssrc.json.j2 +5 -0
  64. litestar_vite/templates/angular-cli/angular.json.j2 +36 -0
  65. litestar_vite/templates/angular-cli/openapi-ts.config.ts.j2 +18 -0
  66. litestar_vite/templates/angular-cli/package.json.j2 +28 -0
  67. litestar_vite/templates/angular-cli/proxy.conf.json.j2 +18 -0
  68. litestar_vite/templates/angular-cli/src/app/app.component.css.j2 +3 -0
  69. litestar_vite/templates/angular-cli/src/app/app.component.html.j2 +1 -0
  70. litestar_vite/templates/angular-cli/src/app/app.component.ts.j2 +9 -0
  71. litestar_vite/templates/angular-cli/src/app/app.config.ts.j2 +5 -0
  72. litestar_vite/templates/angular-cli/src/index.html.j2 +13 -0
  73. litestar_vite/templates/angular-cli/src/main.ts.j2 +6 -0
  74. litestar_vite/templates/angular-cli/src/styles.css.j2 +10 -0
  75. litestar_vite/templates/angular-cli/tailwind.config.js.j2 +4 -0
  76. litestar_vite/templates/angular-cli/tsconfig.app.json.j2 +16 -0
  77. litestar_vite/templates/angular-cli/tsconfig.json.j2 +26 -0
  78. litestar_vite/templates/angular-cli/tsconfig.spec.json.j2 +9 -0
  79. litestar_vite/templates/astro/astro.config.mjs.j2 +28 -0
  80. litestar_vite/templates/astro/openapi-ts.config.ts.j2 +15 -0
  81. litestar_vite/templates/astro/src/layouts/Layout.astro.j2 +63 -0
  82. litestar_vite/templates/astro/src/pages/index.astro.j2 +36 -0
  83. litestar_vite/templates/astro/src/styles/global.css.j2 +1 -0
  84. litestar_vite/templates/base/.gitignore.j2 +42 -0
  85. litestar_vite/templates/base/openapi-ts.config.ts.j2 +15 -0
  86. litestar_vite/templates/base/package.json.j2 +39 -0
  87. litestar_vite/templates/base/resources/vite-env.d.ts.j2 +1 -0
  88. litestar_vite/templates/base/tsconfig.json.j2 +37 -0
  89. litestar_vite/templates/htmx/src/main.js.j2 +8 -0
  90. litestar_vite/templates/htmx/templates/base.html.j2.j2 +56 -0
  91. litestar_vite/templates/htmx/templates/index.html.j2.j2 +13 -0
  92. litestar_vite/templates/htmx/vite.config.ts.j2 +40 -0
  93. litestar_vite/templates/nuxt/app.vue.j2 +29 -0
  94. litestar_vite/templates/nuxt/composables/useApi.ts.j2 +33 -0
  95. litestar_vite/templates/nuxt/nuxt.config.ts.j2 +31 -0
  96. litestar_vite/templates/nuxt/openapi-ts.config.ts.j2 +15 -0
  97. litestar_vite/templates/nuxt/pages/index.vue.j2 +54 -0
  98. litestar_vite/templates/react/index.html.j2 +13 -0
  99. litestar_vite/templates/react/src/App.css.j2 +56 -0
  100. litestar_vite/templates/react/src/App.tsx.j2 +19 -0
  101. litestar_vite/templates/react/src/main.tsx.j2 +10 -0
  102. litestar_vite/templates/react/vite.config.ts.j2 +39 -0
  103. litestar_vite/templates/react-inertia/index.html.j2 +14 -0
  104. litestar_vite/templates/react-inertia/package.json.j2 +47 -0
  105. litestar_vite/templates/react-inertia/resources/App.css.j2 +68 -0
  106. litestar_vite/templates/react-inertia/resources/main.tsx.j2 +17 -0
  107. litestar_vite/templates/react-inertia/resources/pages/Home.tsx.j2 +18 -0
  108. litestar_vite/templates/react-inertia/resources/ssr.tsx.j2 +19 -0
  109. litestar_vite/templates/react-inertia/vite.config.ts.j2 +59 -0
  110. litestar_vite/templates/react-router/index.html.j2 +12 -0
  111. litestar_vite/templates/react-router/src/App.css.j2 +17 -0
  112. litestar_vite/templates/react-router/src/App.tsx.j2 +7 -0
  113. litestar_vite/templates/react-router/src/main.tsx.j2 +10 -0
  114. litestar_vite/templates/react-router/vite.config.ts.j2 +39 -0
  115. litestar_vite/templates/react-tanstack/index.html.j2 +12 -0
  116. litestar_vite/templates/react-tanstack/openapi-ts.config.ts.j2 +18 -0
  117. litestar_vite/templates/react-tanstack/src/App.css.j2 +17 -0
  118. litestar_vite/templates/react-tanstack/src/main.tsx.j2 +21 -0
  119. litestar_vite/templates/react-tanstack/src/routeTree.gen.ts.j2 +7 -0
  120. litestar_vite/templates/react-tanstack/src/routes/__root.tsx.j2 +9 -0
  121. litestar_vite/templates/react-tanstack/src/routes/books.tsx.j2 +9 -0
  122. litestar_vite/templates/react-tanstack/src/routes/index.tsx.j2 +9 -0
  123. litestar_vite/templates/react-tanstack/vite.config.ts.j2 +39 -0
  124. litestar_vite/templates/svelte/index.html.j2 +13 -0
  125. litestar_vite/templates/svelte/src/App.svelte.j2 +30 -0
  126. litestar_vite/templates/svelte/src/app.css.j2 +45 -0
  127. litestar_vite/templates/svelte/src/main.ts.j2 +8 -0
  128. litestar_vite/templates/svelte/src/vite-env.d.ts.j2 +2 -0
  129. litestar_vite/templates/svelte/svelte.config.js.j2 +5 -0
  130. litestar_vite/templates/svelte/vite.config.ts.j2 +39 -0
  131. litestar_vite/templates/svelte-inertia/index.html.j2 +14 -0
  132. litestar_vite/templates/svelte-inertia/resources/app.css.j2 +21 -0
  133. litestar_vite/templates/svelte-inertia/resources/main.ts.j2 +11 -0
  134. litestar_vite/templates/svelte-inertia/resources/pages/Home.svelte.j2 +43 -0
  135. litestar_vite/templates/svelte-inertia/resources/vite-env.d.ts.j2 +2 -0
  136. litestar_vite/templates/svelte-inertia/svelte.config.js.j2 +5 -0
  137. litestar_vite/templates/svelte-inertia/vite.config.ts.j2 +37 -0
  138. litestar_vite/templates/sveltekit/openapi-ts.config.ts.j2 +15 -0
  139. litestar_vite/templates/sveltekit/src/app.css.j2 +40 -0
  140. litestar_vite/templates/sveltekit/src/app.html.j2 +12 -0
  141. litestar_vite/templates/sveltekit/src/hooks.server.ts.j2 +55 -0
  142. litestar_vite/templates/sveltekit/src/routes/+layout.svelte.j2 +12 -0
  143. litestar_vite/templates/sveltekit/src/routes/+page.svelte.j2 +34 -0
  144. litestar_vite/templates/sveltekit/svelte.config.js.j2 +12 -0
  145. litestar_vite/templates/sveltekit/tsconfig.json.j2 +14 -0
  146. litestar_vite/templates/sveltekit/vite.config.ts.j2 +31 -0
  147. litestar_vite/templates/vue/env.d.ts.j2 +7 -0
  148. litestar_vite/templates/vue/index.html.j2 +13 -0
  149. litestar_vite/templates/vue/src/App.vue.j2 +28 -0
  150. litestar_vite/templates/vue/src/main.ts.j2 +5 -0
  151. litestar_vite/templates/vue/src/style.css.j2 +45 -0
  152. litestar_vite/templates/vue/vite.config.ts.j2 +39 -0
  153. litestar_vite/templates/vue-inertia/env.d.ts.j2 +7 -0
  154. litestar_vite/templates/vue-inertia/index.html.j2 +14 -0
  155. litestar_vite/templates/vue-inertia/package.json.j2 +50 -0
  156. litestar_vite/templates/vue-inertia/resources/main.ts.j2 +18 -0
  157. litestar_vite/templates/vue-inertia/resources/pages/Home.vue.j2 +22 -0
  158. litestar_vite/templates/vue-inertia/resources/ssr.ts.j2 +21 -0
  159. litestar_vite/templates/vue-inertia/resources/style.css.j2 +21 -0
  160. litestar_vite/templates/vue-inertia/vite.config.ts.j2 +59 -0
  161. litestar_vite-0.15.0.dist-info/METADATA +230 -0
  162. litestar_vite-0.15.0.dist-info/RECORD +164 -0
  163. {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0.dist-info}/WHEEL +1 -1
  164. litestar_vite/config.py +0 -100
  165. litestar_vite/plugin.py +0 -45
  166. litestar_vite/template_engine.py +0 -103
  167. litestar_vite-0.1.1.dist-info/METADATA +0 -68
  168. litestar_vite-0.1.1.dist-info/RECORD +0 -11
  169. {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,489 @@
1
+ """Utilities for logging, environment setup, and route detection."""
2
+
3
+ __all__ = (
4
+ "configure_proxy_logging",
5
+ "console",
6
+ "create_proxy_client",
7
+ "get_litestar_route_prefixes",
8
+ "infer_port_from_argv",
9
+ "is_litestar_route",
10
+ "is_non_serving_assets_cli",
11
+ "is_proxy_debug",
12
+ "log_fail",
13
+ "log_info",
14
+ "log_success",
15
+ "log_warn",
16
+ "normalize_prefix",
17
+ "pick_free_port",
18
+ "resolve_litestar_version",
19
+ "set_app_environment",
20
+ "set_environment",
21
+ "static_not_found_handler",
22
+ "vite_not_found_handler",
23
+ "write_runtime_config_file",
24
+ )
25
+
26
+ import importlib.metadata
27
+ import logging
28
+ import os
29
+ import sys
30
+ from pathlib import Path
31
+ from typing import TYPE_CHECKING, Any, Protocol, cast, overload
32
+
33
+ from rich.console import Console
34
+
35
+ from litestar_vite.codegen import write_if_changed as _write_if_changed
36
+ from litestar_vite.config import InertiaConfig, TypeGenConfig
37
+
38
+ if TYPE_CHECKING:
39
+ import httpx
40
+ from litestar import Litestar, Response
41
+ from litestar.connection import Request
42
+ from litestar.exceptions import NotFoundException
43
+
44
+ from litestar_vite.config import ViteConfig
45
+
46
+ _TICK = "[bold green]✓[/]"
47
+ _INFO = "[cyan]•[/]"
48
+ _WARN = "[yellow]![/]"
49
+ _FAIL = "[red]x[/]"
50
+
51
+ console = Console()
52
+
53
+
54
+ _vite_proxy_debug: bool | None = None
55
+
56
+
57
+ def is_proxy_debug() -> bool:
58
+ """Check if VITE_PROXY_DEBUG is enabled (cached).
59
+
60
+ Returns:
61
+ True if VITE_PROXY_DEBUG is set to a truthy value, else False.
62
+ """
63
+ global _vite_proxy_debug # noqa: PLW0603
64
+ if _vite_proxy_debug is None:
65
+ _vite_proxy_debug = os.environ.get("VITE_PROXY_DEBUG", "").lower() in {"1", "true", "yes"}
66
+ return _vite_proxy_debug
67
+
68
+
69
+ def configure_proxy_logging() -> None:
70
+ """Suppress verbose proxy-related logging unless debug is enabled.
71
+
72
+ Suppresses INFO-level logs from:
73
+ - httpx: logs every HTTP request
74
+ - websockets: logs connection events
75
+ - uvicorn.protocols.websockets: logs "connection open/closed"
76
+
77
+ Only show these logs when VITE_PROXY_DEBUG is enabled.
78
+ """
79
+
80
+ if not is_proxy_debug():
81
+ for logger_name in ("httpx", "websockets", "uvicorn.protocols.websockets"):
82
+ logging.getLogger(logger_name).setLevel(logging.WARNING)
83
+
84
+
85
+ configure_proxy_logging()
86
+
87
+
88
+ # Cache HTTP/2 availability check result
89
+ _h2_available: bool | None = None
90
+
91
+
92
+ def _check_h2_available() -> bool:
93
+ """Check if the h2 package is installed for HTTP/2 support.
94
+
95
+ Returns:
96
+ True if h2 is installed, False otherwise.
97
+ """
98
+ global _h2_available # noqa: PLW0603
99
+ if _h2_available is None:
100
+ try:
101
+ import h2 # noqa: F401 # pyright: ignore[reportMissingImports,reportUnusedImport]
102
+
103
+ _h2_available = True
104
+ except ImportError:
105
+ _h2_available = False
106
+ return _h2_available
107
+
108
+
109
+ def create_proxy_client(
110
+ http2: bool = True,
111
+ timeout: float = 30.0,
112
+ max_keepalive: int = 20,
113
+ max_connections: int = 40,
114
+ keepalive_expiry: float = 60.0,
115
+ ) -> "httpx.AsyncClient":
116
+ """Create an httpx.AsyncClient with connection pooling for proxy use.
117
+
118
+ This factory function creates a shared HTTP client with optimized settings
119
+ for proxying requests to Vite dev servers or SSR frameworks. The client
120
+ uses connection pooling for better performance.
121
+
122
+ Args:
123
+ http2: Enable HTTP/2 support (requires h2 package).
124
+ timeout: Request timeout in seconds.
125
+ max_keepalive: Maximum number of keep-alive connections per host.
126
+ max_connections: Maximum total concurrent connections.
127
+ keepalive_expiry: Idle timeout before closing keep-alive connections.
128
+
129
+ Returns:
130
+ A configured httpx.AsyncClient with connection pooling.
131
+ """
132
+ import httpx
133
+
134
+ http2_enabled = http2 and _check_h2_available()
135
+ limits = httpx.Limits(
136
+ max_keepalive_connections=max_keepalive, max_connections=max_connections, keepalive_expiry=keepalive_expiry
137
+ )
138
+ return httpx.AsyncClient(limits=limits, timeout=httpx.Timeout(timeout), http2=http2_enabled)
139
+
140
+
141
+ def infer_port_from_argv() -> str | None:
142
+ """Best-effort extraction of `--port/-p` from process argv.
143
+
144
+ Returns:
145
+ The port as a string if found, else None.
146
+ """
147
+
148
+ argv = sys.argv[1:]
149
+ for i, arg in enumerate(argv):
150
+ if arg in {"-p", "--port"} and i + 1 < len(argv) and argv[i + 1].isdigit():
151
+ return argv[i + 1]
152
+ if arg.startswith("--port="):
153
+ _, _, value = arg.partition("=")
154
+ if value.isdigit():
155
+ return value
156
+ return None
157
+
158
+
159
+ def is_non_serving_assets_cli() -> bool:
160
+ """Return True when running CLI assets commands that don't start a server.
161
+
162
+ This suppresses dev-proxy setup/logging for commands like `assets build`
163
+ where only a Vite build is performed and no proxy should be initialized.
164
+
165
+ Returns:
166
+ True when the current process is running a non-serving `litestar assets ...` command, otherwise False.
167
+ """
168
+
169
+ argv_str = " ".join(sys.argv)
170
+ non_serving_commands = (
171
+ " assets build",
172
+ " assets install",
173
+ " assets deploy",
174
+ " assets doctor",
175
+ " assets generate-types",
176
+ " assets export-routes",
177
+ " assets status",
178
+ " assets init",
179
+ )
180
+ return any(cmd in argv_str for cmd in non_serving_commands)
181
+
182
+
183
+ def log_success(message: str) -> None:
184
+ """Print a success message with consistent styling."""
185
+
186
+ console.print(f"{_TICK} {message}")
187
+
188
+
189
+ def log_info(message: str) -> None:
190
+ """Print an informational message with consistent styling."""
191
+
192
+ console.print(f"{_INFO} {message}")
193
+
194
+
195
+ def log_warn(message: str) -> None:
196
+ """Print a warning message with consistent styling."""
197
+
198
+ console.print(f"{_WARN} {message}")
199
+
200
+
201
+ def log_fail(message: str) -> None:
202
+ """Print an error message with consistent styling."""
203
+
204
+ console.print(f"{_FAIL} {message}")
205
+
206
+
207
+ @overload
208
+ def write_runtime_config_file(config: "ViteConfig", *, asset_url_override: str | None = None) -> str: ...
209
+
210
+
211
+ @overload
212
+ def write_runtime_config_file(
213
+ config: "ViteConfig", *, asset_url_override: str | None = None, return_status: bool
214
+ ) -> tuple[str, bool]: ...
215
+
216
+
217
+ def write_runtime_config_file(
218
+ config: "ViteConfig", *, asset_url_override: str | None = None, return_status: bool = False
219
+ ) -> str | tuple[str, bool]:
220
+ """Write a JSON handoff file for the Vite plugin and return its path.
221
+
222
+ The runtime config file is read by the JS plugin. We serialize with Litestar's JSON encoder for
223
+ consistency and format output deterministically for easier debugging.
224
+
225
+ Returns:
226
+ The path to the written config file.
227
+ """
228
+
229
+ root = config.root_dir or Path.cwd()
230
+ path = Path(root) / ".litestar.json"
231
+ types = config.types if isinstance(config.types, TypeGenConfig) else None
232
+ resource_dir = config.resource_dir
233
+ resource_dir_value = str(resource_dir)
234
+ bundle_dir_value = str(config.bundle_dir)
235
+ ssr_out_dir_value = str(config.ssr_output_dir) if config.ssr_output_dir else None
236
+
237
+ litestar_version = os.environ.get("LITESTAR_VERSION") or resolve_litestar_version()
238
+
239
+ deploy_asset_url = None
240
+ deploy = config.deploy_config
241
+ if deploy is not None and deploy.asset_url:
242
+ deploy_asset_url = deploy.asset_url
243
+
244
+ payload = {
245
+ "assetUrl": config.asset_url,
246
+ "deployAssetUrl": deploy_asset_url,
247
+ "bundleDir": bundle_dir_value,
248
+ "hotFile": config.hot_file,
249
+ "resourceDir": resource_dir_value,
250
+ "staticDir": str(config.static_dir),
251
+ "manifest": config.manifest_name,
252
+ "mode": config.mode,
253
+ "proxyMode": config.proxy_mode,
254
+ "port": config.port,
255
+ "host": config.host,
256
+ "ssrOutDir": ssr_out_dir_value,
257
+ "types": {
258
+ "enabled": True,
259
+ "output": str(types.output),
260
+ "openapiPath": str(types.openapi_path),
261
+ "routesPath": str(types.routes_path),
262
+ "pagePropsPath": str(types.page_props_path),
263
+ "generateZod": types.generate_zod,
264
+ "generateSdk": types.generate_sdk,
265
+ "generateRoutes": types.generate_routes,
266
+ "generatePageProps": types.generate_page_props,
267
+ "globalRoute": types.global_route,
268
+ }
269
+ if types
270
+ else None,
271
+ "logging": {
272
+ "level": config.logging_config.level,
273
+ "showPathsAbsolute": config.logging_config.show_paths_absolute,
274
+ "suppressNpmOutput": config.logging_config.suppress_npm_output,
275
+ "suppressViteBanner": config.logging_config.suppress_vite_banner,
276
+ "timestamps": config.logging_config.timestamps,
277
+ },
278
+ "spa": {"useScriptElement": config.inertia.use_script_element}
279
+ if isinstance(config.inertia, InertiaConfig)
280
+ else None,
281
+ "executor": config.runtime.executor,
282
+ "litestarVersion": litestar_version,
283
+ }
284
+
285
+ import msgspec
286
+ from litestar.serialization import encode_json
287
+
288
+ content = msgspec.json.format(encode_json(payload), indent=2)
289
+ changed = _write_if_changed(path, content)
290
+ if return_status:
291
+ return str(path), changed
292
+ return str(path)
293
+
294
+
295
+ def set_environment(config: "ViteConfig", asset_url_override: str | None = None) -> None:
296
+ """Configure environment variables for Vite integration.
297
+
298
+ Sets environment variables that can be used by both the Python backend
299
+ and the Vite frontend during development.
300
+
301
+ Args:
302
+ config: The Vite configuration.
303
+ asset_url_override: Optional asset URL to force (e.g., CDN base during build).
304
+ """
305
+ litestar_version = os.environ.get("LITESTAR_VERSION") or resolve_litestar_version()
306
+ asset_url = asset_url_override or config.asset_url
307
+ if asset_url:
308
+ os.environ.setdefault("ASSET_URL", asset_url)
309
+ if config.base_url:
310
+ os.environ.setdefault("VITE_BASE_URL", config.base_url)
311
+ os.environ.setdefault("VITE_ALLOW_REMOTE", str(True))
312
+
313
+ backend_host = os.environ.get("LITESTAR_HOST") or "127.0.0.1"
314
+ backend_port = os.environ.get("LITESTAR_PORT") or os.environ.get("PORT") or infer_port_from_argv() or "8000"
315
+ os.environ["LITESTAR_HOST"] = backend_host
316
+ os.environ["LITESTAR_PORT"] = str(backend_port)
317
+ os.environ.setdefault("APP_URL", f"http://{backend_host}:{backend_port}")
318
+
319
+ os.environ.setdefault("VITE_PROTOCOL", config.protocol)
320
+ if config.proxy_mode is not None:
321
+ os.environ.setdefault("VITE_PROXY_MODE", config.proxy_mode)
322
+
323
+ os.environ.setdefault("VITE_HOST", config.host)
324
+ os.environ.setdefault("VITE_PORT", str(config.port))
325
+ os.environ.setdefault("NUXT_HOST", config.host)
326
+ os.environ.setdefault("NUXT_PORT", str(config.port))
327
+ os.environ.setdefault("NITRO_HOST", config.host)
328
+ os.environ.setdefault("NITRO_PORT", str(config.port))
329
+ os.environ.setdefault("HOST", config.host)
330
+ os.environ.setdefault("PORT", str(config.port))
331
+
332
+ os.environ["LITESTAR_VERSION"] = litestar_version
333
+ os.environ.setdefault("LITESTAR_VITE_RUNTIME", config.runtime.executor or "node")
334
+ os.environ.setdefault("LITESTAR_VITE_INSTALL_CMD", " ".join(config.install_command))
335
+
336
+ if config.is_dev_mode:
337
+ os.environ.setdefault("VITE_DEV_MODE", str(config.is_dev_mode))
338
+
339
+ config_path = write_runtime_config_file(config, asset_url_override=asset_url_override)
340
+ os.environ["LITESTAR_VITE_CONFIG_PATH"] = config_path
341
+
342
+
343
+ def set_app_environment(app: "Litestar") -> None:
344
+ """Set environment variables derived from the Litestar app instance.
345
+
346
+ This is called after set_environment() once the app is available,
347
+ to export app-specific configuration like OpenAPI paths.
348
+
349
+ Args:
350
+ app: The Litestar application instance.
351
+ """
352
+ openapi_config = app.openapi_config
353
+ if openapi_config is not None and isinstance(openapi_config.path, str) and openapi_config.path:
354
+ os.environ.setdefault("LITESTAR_OPENAPI_PATH", openapi_config.path)
355
+
356
+
357
+ def resolve_litestar_version() -> str:
358
+ """Return the installed Litestar version string.
359
+
360
+ Returns:
361
+ The installed Litestar version, or "unknown" when unavailable.
362
+ """
363
+ try:
364
+ return importlib.metadata.version("litestar")
365
+ except importlib.metadata.PackageNotFoundError:
366
+ return "unknown"
367
+
368
+
369
+ def pick_free_port() -> int:
370
+ import socket
371
+
372
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
373
+ sock.bind(("127.0.0.1", 0))
374
+ return sock.getsockname()[1]
375
+
376
+
377
+ def normalize_prefix(prefix: str) -> str:
378
+ if not prefix.startswith("/"):
379
+ prefix = f"/{prefix}"
380
+ if not prefix.endswith("/"):
381
+ prefix = f"{prefix}/"
382
+ return prefix
383
+
384
+
385
+ class _RoutePrefixesState(Protocol):
386
+ litestar_vite_route_prefixes: tuple[str, ...]
387
+
388
+
389
+ def get_litestar_route_prefixes(app: "Litestar") -> tuple[str, ...]:
390
+ """Build a cached list of Litestar route prefixes for the given app.
391
+
392
+ This function collects all registered route paths from the Litestar application
393
+ and caches them for efficient lookup. The cache is stored in app.state to ensure
394
+ it's automatically cleaned up when the app is garbage collected.
395
+
396
+ Includes:
397
+ - All registered Litestar route paths
398
+ - OpenAPI schema path (customizable via openapi_config.path)
399
+ - Common API prefixes as fallback (/api, /schema, /docs)
400
+
401
+ Args:
402
+ app: The Litestar application instance.
403
+
404
+ Returns:
405
+ A tuple of route prefix strings (without trailing slashes).
406
+ """
407
+ state = cast("_RoutePrefixesState", app.state)
408
+ try:
409
+ return state.litestar_vite_route_prefixes
410
+ except AttributeError:
411
+ pass
412
+
413
+ prefixes: list[str] = []
414
+ for route in app.routes:
415
+ prefix = route.path.rstrip("/")
416
+ if prefix:
417
+ prefixes.append(prefix)
418
+
419
+ openapi_config = app.openapi_config
420
+ if openapi_config is not None:
421
+ schema_path = openapi_config.path
422
+ if schema_path:
423
+ prefixes.append(schema_path.rstrip("/"))
424
+
425
+ prefixes.extend(["/api", "/schema", "/docs"])
426
+
427
+ unique_prefixes = sorted(set(prefixes), key=len, reverse=True)
428
+ result = tuple(unique_prefixes)
429
+
430
+ state.litestar_vite_route_prefixes = result
431
+
432
+ if is_proxy_debug():
433
+ console.print(f"[dim][route-detection] Cached prefixes: {result}[/]")
434
+
435
+ return result
436
+
437
+
438
+ def is_litestar_route(path: str, app: "Litestar") -> bool:
439
+ """Check if a path matches a registered Litestar route.
440
+
441
+ This function determines if a request path should be handled by Litestar
442
+ rather than proxied to the Vite dev server or served as SPA content.
443
+
444
+ A path matches if it equals a registered prefix or starts with prefix + "/".
445
+
446
+ Args:
447
+ path: The request path to check (e.g., "/schema", "/api/users").
448
+ app: The Litestar application instance.
449
+
450
+ Returns:
451
+ True if the path matches a Litestar route, False otherwise.
452
+ """
453
+ excluded = get_litestar_route_prefixes(app)
454
+ return any(path == prefix or path.startswith(f"{prefix}/") for prefix in excluded)
455
+
456
+
457
+ def static_not_found_handler(
458
+ _request: "Request[Any, Any, Any]", _exc: "NotFoundException"
459
+ ) -> "Response[bytes]": # pragma: no cover - trivial
460
+ """Return an empty 404 response for static files routing misses.
461
+
462
+ Returns:
463
+ An empty 404 response.
464
+ """
465
+ from litestar import Response
466
+
467
+ return Response(status_code=404, content=b"")
468
+
469
+
470
+ def vite_not_found_handler(request: "Request[Any, Any, Any]", exc: "NotFoundException") -> "Response[Any]":
471
+ """Return a consistent 404 response for missing static assets / routes.
472
+
473
+ Inertia requests are delegated to the Inertia exception handler to support
474
+ redirect_404 configuration.
475
+
476
+ Args:
477
+ request: Incoming request.
478
+ exc: NotFound exception raised by routing.
479
+
480
+ Returns:
481
+ Response instance for the 404.
482
+ """
483
+ from litestar import Response
484
+
485
+ if request.headers.get("x-inertia", "").lower() == "true":
486
+ from litestar_vite.inertia.exception_handler import exception_to_http_response
487
+
488
+ return exception_to_http_response(request, exc)
489
+ return Response(status_code=404, content=b"")
litestar_vite/py.typed ADDED
File without changes
@@ -0,0 +1,20 @@
1
+ """Project scaffolding module for litestar-vite.
2
+
3
+ This module provides the `litestar assets init` command with framework-specific
4
+ template generation. It supports multiple frontend frameworks and build tools.
5
+
6
+ Supported frameworks:
7
+ - React (with TypeScript)
8
+ - Vue 3 (with TypeScript)
9
+ - Vue + Inertia.js
10
+ - Svelte 5 (with runes)
11
+ - SvelteKit
12
+ - Nuxt 3
13
+ - Astro
14
+ - HTMX + Alpine.js
15
+ """
16
+
17
+ from litestar_vite.scaffolding.generator import TemplateContext, generate_project
18
+ from litestar_vite.scaffolding.templates import FrameworkTemplate, get_available_templates
19
+
20
+ __all__ = ["FrameworkTemplate", "TemplateContext", "generate_project", "get_available_templates"]