litestar-vite 0.1.1__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.
- litestar_vite/__init__.py +54 -4
- litestar_vite/__metadata__.py +12 -7
- litestar_vite/_codegen/__init__.py +26 -0
- litestar_vite/_codegen/inertia.py +407 -0
- litestar_vite/_codegen/openapi.py +233 -0
- litestar_vite/_codegen/routes.py +653 -0
- litestar_vite/_codegen/ts.py +235 -0
- litestar_vite/_handler/__init__.py +8 -0
- litestar_vite/_handler/app.py +524 -0
- litestar_vite/_handler/routing.py +130 -0
- litestar_vite/cli.py +1147 -10
- litestar_vite/codegen.py +39 -0
- litestar_vite/commands.py +79 -0
- litestar_vite/config.py +1594 -70
- litestar_vite/deploy.py +355 -0
- litestar_vite/doctor.py +1179 -0
- litestar_vite/exceptions.py +78 -0
- litestar_vite/executor.py +316 -0
- litestar_vite/handler.py +9 -0
- litestar_vite/html_transform.py +426 -0
- litestar_vite/inertia/__init__.py +53 -0
- litestar_vite/inertia/_utils.py +114 -0
- litestar_vite/inertia/exception_handler.py +172 -0
- litestar_vite/inertia/helpers.py +1043 -0
- litestar_vite/inertia/middleware.py +54 -0
- litestar_vite/inertia/plugin.py +133 -0
- litestar_vite/inertia/request.py +286 -0
- litestar_vite/inertia/response.py +706 -0
- litestar_vite/inertia/types.py +316 -0
- litestar_vite/loader.py +462 -121
- litestar_vite/plugin.py +2160 -21
- litestar_vite/py.typed +0 -0
- litestar_vite/scaffolding/__init__.py +20 -0
- litestar_vite/scaffolding/generator.py +270 -0
- litestar_vite/scaffolding/templates.py +437 -0
- litestar_vite/templates/__init__.py +0 -0
- litestar_vite/templates/addons/tailwindcss/tailwind.css.j2 +1 -0
- litestar_vite/templates/angular/index.html.j2 +12 -0
- litestar_vite/templates/angular/openapi-ts.config.ts.j2 +18 -0
- litestar_vite/templates/angular/package.json.j2 +35 -0
- litestar_vite/templates/angular/src/app/app.component.css.j2 +3 -0
- litestar_vite/templates/angular/src/app/app.component.html.j2 +1 -0
- litestar_vite/templates/angular/src/app/app.component.ts.j2 +9 -0
- litestar_vite/templates/angular/src/app/app.config.ts.j2 +5 -0
- litestar_vite/templates/angular/src/main.ts.j2 +9 -0
- litestar_vite/templates/angular/src/styles.css.j2 +9 -0
- litestar_vite/templates/angular/tsconfig.app.json.j2 +34 -0
- litestar_vite/templates/angular/tsconfig.json.j2 +20 -0
- litestar_vite/templates/angular/vite.config.ts.j2 +21 -0
- litestar_vite/templates/angular-cli/.postcssrc.json.j2 +5 -0
- litestar_vite/templates/angular-cli/angular.json.j2 +36 -0
- litestar_vite/templates/angular-cli/openapi-ts.config.ts.j2 +18 -0
- litestar_vite/templates/angular-cli/package.json.j2 +27 -0
- litestar_vite/templates/angular-cli/proxy.conf.json.j2 +18 -0
- litestar_vite/templates/angular-cli/src/app/app.component.css.j2 +3 -0
- litestar_vite/templates/angular-cli/src/app/app.component.html.j2 +1 -0
- litestar_vite/templates/angular-cli/src/app/app.component.ts.j2 +9 -0
- litestar_vite/templates/angular-cli/src/app/app.config.ts.j2 +5 -0
- litestar_vite/templates/angular-cli/src/index.html.j2 +13 -0
- litestar_vite/templates/angular-cli/src/main.ts.j2 +6 -0
- litestar_vite/templates/angular-cli/src/styles.css.j2 +10 -0
- litestar_vite/templates/angular-cli/tailwind.config.js.j2 +4 -0
- litestar_vite/templates/angular-cli/tsconfig.app.json.j2 +16 -0
- litestar_vite/templates/angular-cli/tsconfig.json.j2 +26 -0
- litestar_vite/templates/angular-cli/tsconfig.spec.json.j2 +9 -0
- litestar_vite/templates/astro/astro.config.mjs.j2 +28 -0
- litestar_vite/templates/astro/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/astro/src/layouts/Layout.astro.j2 +63 -0
- litestar_vite/templates/astro/src/pages/index.astro.j2 +36 -0
- litestar_vite/templates/astro/src/styles/global.css.j2 +1 -0
- litestar_vite/templates/base/.gitignore.j2 +42 -0
- litestar_vite/templates/base/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/base/package.json.j2 +38 -0
- litestar_vite/templates/base/resources/vite-env.d.ts.j2 +1 -0
- litestar_vite/templates/base/tsconfig.json.j2 +37 -0
- litestar_vite/templates/htmx/src/main.js.j2 +8 -0
- litestar_vite/templates/htmx/templates/base.html.j2.j2 +56 -0
- litestar_vite/templates/htmx/templates/index.html.j2.j2 +13 -0
- litestar_vite/templates/htmx/vite.config.ts.j2 +40 -0
- litestar_vite/templates/nuxt/app.vue.j2 +29 -0
- litestar_vite/templates/nuxt/composables/useApi.ts.j2 +33 -0
- litestar_vite/templates/nuxt/nuxt.config.ts.j2 +31 -0
- litestar_vite/templates/nuxt/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/nuxt/pages/index.vue.j2 +54 -0
- litestar_vite/templates/react/index.html.j2 +13 -0
- litestar_vite/templates/react/src/App.css.j2 +56 -0
- litestar_vite/templates/react/src/App.tsx.j2 +19 -0
- litestar_vite/templates/react/src/main.tsx.j2 +10 -0
- litestar_vite/templates/react/vite.config.ts.j2 +39 -0
- litestar_vite/templates/react-inertia/index.html.j2 +14 -0
- litestar_vite/templates/react-inertia/package.json.j2 +46 -0
- litestar_vite/templates/react-inertia/resources/App.css.j2 +68 -0
- litestar_vite/templates/react-inertia/resources/main.tsx.j2 +17 -0
- litestar_vite/templates/react-inertia/resources/pages/Home.tsx.j2 +18 -0
- litestar_vite/templates/react-inertia/resources/ssr.tsx.j2 +19 -0
- litestar_vite/templates/react-inertia/vite.config.ts.j2 +59 -0
- litestar_vite/templates/react-router/index.html.j2 +12 -0
- litestar_vite/templates/react-router/src/App.css.j2 +17 -0
- litestar_vite/templates/react-router/src/App.tsx.j2 +7 -0
- litestar_vite/templates/react-router/src/main.tsx.j2 +10 -0
- litestar_vite/templates/react-router/vite.config.ts.j2 +39 -0
- litestar_vite/templates/react-tanstack/index.html.j2 +12 -0
- litestar_vite/templates/react-tanstack/openapi-ts.config.ts.j2 +18 -0
- litestar_vite/templates/react-tanstack/src/App.css.j2 +17 -0
- litestar_vite/templates/react-tanstack/src/main.tsx.j2 +21 -0
- litestar_vite/templates/react-tanstack/src/routeTree.gen.ts.j2 +7 -0
- litestar_vite/templates/react-tanstack/src/routes/__root.tsx.j2 +9 -0
- litestar_vite/templates/react-tanstack/src/routes/books.tsx.j2 +9 -0
- litestar_vite/templates/react-tanstack/src/routes/index.tsx.j2 +9 -0
- litestar_vite/templates/react-tanstack/vite.config.ts.j2 +39 -0
- litestar_vite/templates/svelte/index.html.j2 +13 -0
- litestar_vite/templates/svelte/src/App.svelte.j2 +30 -0
- litestar_vite/templates/svelte/src/app.css.j2 +45 -0
- litestar_vite/templates/svelte/src/main.ts.j2 +8 -0
- litestar_vite/templates/svelte/src/vite-env.d.ts.j2 +2 -0
- litestar_vite/templates/svelte/svelte.config.js.j2 +5 -0
- litestar_vite/templates/svelte/vite.config.ts.j2 +39 -0
- litestar_vite/templates/svelte-inertia/index.html.j2 +14 -0
- litestar_vite/templates/svelte-inertia/resources/app.css.j2 +21 -0
- litestar_vite/templates/svelte-inertia/resources/main.ts.j2 +11 -0
- litestar_vite/templates/svelte-inertia/resources/pages/Home.svelte.j2 +43 -0
- litestar_vite/templates/svelte-inertia/resources/vite-env.d.ts.j2 +2 -0
- litestar_vite/templates/svelte-inertia/svelte.config.js.j2 +5 -0
- litestar_vite/templates/svelte-inertia/vite.config.ts.j2 +37 -0
- litestar_vite/templates/sveltekit/openapi-ts.config.ts.j2 +15 -0
- litestar_vite/templates/sveltekit/src/app.css.j2 +40 -0
- litestar_vite/templates/sveltekit/src/app.html.j2 +12 -0
- litestar_vite/templates/sveltekit/src/hooks.server.ts.j2 +55 -0
- litestar_vite/templates/sveltekit/src/routes/+layout.svelte.j2 +12 -0
- litestar_vite/templates/sveltekit/src/routes/+page.svelte.j2 +34 -0
- litestar_vite/templates/sveltekit/svelte.config.js.j2 +12 -0
- litestar_vite/templates/sveltekit/tsconfig.json.j2 +14 -0
- litestar_vite/templates/sveltekit/vite.config.ts.j2 +31 -0
- litestar_vite/templates/vue/env.d.ts.j2 +7 -0
- litestar_vite/templates/vue/index.html.j2 +13 -0
- litestar_vite/templates/vue/src/App.vue.j2 +28 -0
- litestar_vite/templates/vue/src/main.ts.j2 +5 -0
- litestar_vite/templates/vue/src/style.css.j2 +45 -0
- litestar_vite/templates/vue/vite.config.ts.j2 +39 -0
- litestar_vite/templates/vue-inertia/env.d.ts.j2 +7 -0
- litestar_vite/templates/vue-inertia/index.html.j2 +14 -0
- litestar_vite/templates/vue-inertia/package.json.j2 +49 -0
- litestar_vite/templates/vue-inertia/resources/main.ts.j2 +18 -0
- litestar_vite/templates/vue-inertia/resources/pages/Home.vue.j2 +22 -0
- litestar_vite/templates/vue-inertia/resources/ssr.ts.j2 +21 -0
- litestar_vite/templates/vue-inertia/resources/style.css.j2 +21 -0
- litestar_vite/templates/vue-inertia/vite.config.ts.j2 +59 -0
- litestar_vite-0.15.0rc2.dist-info/METADATA +230 -0
- litestar_vite-0.15.0rc2.dist-info/RECORD +151 -0
- {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0rc2.dist-info}/WHEEL +1 -1
- litestar_vite/template_engine.py +0 -103
- litestar_vite-0.1.1.dist-info/METADATA +0 -68
- litestar_vite-0.1.1.dist-info/RECORD +0 -11
- {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0rc2.dist-info}/licenses/LICENSE +0 -0
litestar_vite/config.py
CHANGED
|
@@ -1,100 +1,1624 @@
|
|
|
1
|
-
|
|
1
|
+
"""Litestar-Vite Configuration.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
from inspect import isclass
|
|
6
|
-
from typing import TYPE_CHECKING, Generic, TypeVar, cast
|
|
3
|
+
This module provides the configuration dataclasses for the Vite integration.
|
|
4
|
+
The configuration is split into logical groups:
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
- PathConfig: File system paths
|
|
7
|
+
- RuntimeConfig: Execution settings
|
|
8
|
+
- TypeGenConfig: Type generation settings
|
|
9
|
+
- ViteConfig: Root configuration combining all sub-configs
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Example usage::
|
|
12
12
|
|
|
13
|
+
# Minimal - SPA mode with defaults
|
|
14
|
+
VitePlugin(config=ViteConfig())
|
|
15
|
+
|
|
16
|
+
# Development mode
|
|
17
|
+
VitePlugin(config=ViteConfig(dev_mode=True))
|
|
18
|
+
|
|
19
|
+
# With type generation
|
|
20
|
+
VitePlugin(config=ViteConfig(dev_mode=True, types=True))
|
|
21
|
+
|
|
22
|
+
# Template mode for HTMX
|
|
23
|
+
VitePlugin(config=ViteConfig(mode="template", dev_mode=True))
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import os
|
|
28
|
+
from dataclasses import dataclass, field, replace
|
|
29
|
+
from importlib.util import find_spec
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import TYPE_CHECKING, Any, Literal, Protocol, cast, runtime_checkable
|
|
32
|
+
|
|
33
|
+
from litestar.exceptions import SerializationException
|
|
34
|
+
from litestar.serialization import decode_json
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger("litestar_vite")
|
|
13
37
|
|
|
14
38
|
if TYPE_CHECKING:
|
|
15
|
-
from collections.abc import
|
|
16
|
-
|
|
39
|
+
from collections.abc import Sequence
|
|
40
|
+
|
|
41
|
+
from litestar.types import Guard # pyright: ignore[reportUnknownVariableType]
|
|
42
|
+
|
|
43
|
+
from litestar_vite.executor import JSExecutor
|
|
44
|
+
|
|
45
|
+
__all__ = (
|
|
46
|
+
"FSSPEC_INSTALLED",
|
|
47
|
+
"JINJA_INSTALLED",
|
|
48
|
+
"DeployConfig",
|
|
49
|
+
"ExternalDevServer",
|
|
50
|
+
"InertiaConfig",
|
|
51
|
+
"InertiaSSRConfig",
|
|
52
|
+
"InertiaTypeGenConfig",
|
|
53
|
+
"LoggingConfig",
|
|
54
|
+
"PaginationContainer",
|
|
55
|
+
"PathConfig",
|
|
56
|
+
"RuntimeConfig",
|
|
57
|
+
"SPAConfig",
|
|
58
|
+
"TypeGenConfig",
|
|
59
|
+
"ViteConfig",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@runtime_checkable
|
|
64
|
+
class PaginationContainer(Protocol):
|
|
65
|
+
"""Protocol for pagination containers that can be unwrapped for Inertia scroll.
|
|
66
|
+
|
|
67
|
+
Any type that has `items` and pagination metadata can implement this protocol.
|
|
68
|
+
The response will extract items and calculate scroll_props automatically.
|
|
69
|
+
|
|
70
|
+
Built-in support:
|
|
71
|
+
- litestar.pagination.OffsetPagination
|
|
72
|
+
- litestar.pagination.ClassicPagination
|
|
73
|
+
- advanced_alchemy.service.OffsetPagination
|
|
74
|
+
|
|
75
|
+
Custom types can implement this protocol::
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class MyPagination:
|
|
79
|
+
items: list[T]
|
|
80
|
+
total: int
|
|
81
|
+
limit: int
|
|
82
|
+
offset: int
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
items: "Sequence[Any]"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
TRUE_VALUES = {"True", "true", "1", "yes", "Y", "T"}
|
|
89
|
+
JINJA_INSTALLED = bool(find_spec("jinja2"))
|
|
90
|
+
FSSPEC_INSTALLED = bool(find_spec("fsspec"))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _empty_dict_factory() -> dict[str, Any]:
|
|
94
|
+
"""Return an empty ``dict[str, Any]``.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
An empty dictionary.
|
|
98
|
+
"""
|
|
99
|
+
return {}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _empty_set_factory() -> set[str]:
|
|
103
|
+
"""Return an empty ``set[str]``.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
An empty set.
|
|
107
|
+
"""
|
|
108
|
+
return set()
|
|
17
109
|
|
|
18
|
-
from litestar.types import PathType
|
|
19
110
|
|
|
20
|
-
|
|
111
|
+
def _default_content_types() -> dict[str, str]:
|
|
112
|
+
"""Default content-type mappings keyed by file extension.
|
|
21
113
|
|
|
22
|
-
|
|
114
|
+
Returns:
|
|
115
|
+
Dictionary mapping file extensions to MIME types.
|
|
116
|
+
"""
|
|
117
|
+
return {
|
|
118
|
+
".js": "application/javascript",
|
|
119
|
+
".mjs": "application/javascript",
|
|
120
|
+
".cjs": "application/javascript",
|
|
121
|
+
".css": "text/css",
|
|
122
|
+
".html": "text/html",
|
|
123
|
+
".json": "application/json",
|
|
124
|
+
".svg": "image/svg+xml",
|
|
125
|
+
".png": "image/png",
|
|
126
|
+
".jpg": "image/jpeg",
|
|
127
|
+
".jpeg": "image/jpeg",
|
|
128
|
+
".webp": "image/webp",
|
|
129
|
+
".woff2": "font/woff2",
|
|
130
|
+
".woff": "font/woff",
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _default_storage_options() -> dict[str, Any]:
|
|
135
|
+
"""Return an empty storage options dictionary.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
An empty dictionary.
|
|
139
|
+
"""
|
|
140
|
+
return {}
|
|
23
141
|
|
|
24
142
|
|
|
25
143
|
@dataclass
|
|
26
|
-
class
|
|
27
|
-
"""
|
|
144
|
+
class DeployConfig:
|
|
145
|
+
"""CDN deployment configuration.
|
|
146
|
+
|
|
147
|
+
Attributes:
|
|
148
|
+
enabled: Enable deployment features.
|
|
149
|
+
storage_backend: fsspec URL for the target location (e.g., ``gcs://bucket/path``).
|
|
150
|
+
storage_options: Provider options forwarded to ``fsspec`` (credentials, region, etc.).
|
|
151
|
+
delete_orphaned: Remove remote files not present in the local bundle.
|
|
152
|
+
include_manifest: Upload ``manifest.json`` alongside assets.
|
|
153
|
+
content_types: Optional content-type overrides keyed by file extension.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
enabled: bool = False
|
|
157
|
+
storage_backend: "str | None" = field(default_factory=lambda: os.getenv("VITE_DEPLOY_STORAGE"))
|
|
158
|
+
storage_options: dict[str, Any] = field(default_factory=_default_storage_options)
|
|
159
|
+
delete_orphaned: bool = field(default_factory=lambda: os.getenv("VITE_DEPLOY_DELETE", "true") in TRUE_VALUES)
|
|
160
|
+
include_manifest: bool = True
|
|
161
|
+
content_types: dict[str, str] = field(default_factory=_default_content_types)
|
|
162
|
+
|
|
163
|
+
def __post_init__(self) -> None:
|
|
164
|
+
"""Apply environment fallbacks."""
|
|
165
|
+
if self.storage_backend is None:
|
|
166
|
+
self.storage_backend = os.getenv("VITE_DEPLOY_STORAGE")
|
|
167
|
+
|
|
168
|
+
def with_overrides(
|
|
169
|
+
self,
|
|
170
|
+
storage_backend: "str | None" = None,
|
|
171
|
+
storage_options: "dict[str, Any] | None" = None,
|
|
172
|
+
delete_orphaned: "bool | None" = None,
|
|
173
|
+
) -> "DeployConfig":
|
|
174
|
+
"""Return a copy with overrides applied.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
storage_backend: Override for the storage URL.
|
|
178
|
+
storage_options: Override for backend options.
|
|
179
|
+
delete_orphaned: Override deletion behaviour.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
DeployConfig copy with updated fields.
|
|
183
|
+
"""
|
|
184
|
+
return replace(
|
|
185
|
+
self,
|
|
186
|
+
storage_backend=storage_backend or self.storage_backend,
|
|
187
|
+
storage_options=storage_options or self.storage_options,
|
|
188
|
+
delete_orphaned=self.delete_orphaned if delete_orphaned is None else delete_orphaned,
|
|
189
|
+
)
|
|
190
|
+
|
|
28
191
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
192
|
+
@dataclass
|
|
193
|
+
class InertiaSSRConfig:
|
|
194
|
+
"""Server-side rendering settings for Inertia.js.
|
|
195
|
+
|
|
196
|
+
Inertia SSR runs a separate Node server that renders the initial HTML for an
|
|
197
|
+
Inertia page object. Litestar sends the page payload to the SSR server (by
|
|
198
|
+
default at ``http://127.0.0.1:13714/render``) and injects the returned head
|
|
199
|
+
tags and body markup into the HTML response.
|
|
200
|
+
|
|
201
|
+
Notes:
|
|
202
|
+
- This is *not* Litestar-Vite's ``mode="ssr"`` (Astro/Nuxt/SvelteKit proxy mode).
|
|
203
|
+
- When enabled, failures to contact the SSR server are treated as errors (no silent fallback).
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
enabled: bool = True
|
|
207
|
+
url: str = "http://127.0.0.1:13714/render"
|
|
208
|
+
timeout: float = 2.0
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass
|
|
212
|
+
class InertiaConfig:
|
|
213
|
+
"""Configuration for InertiaJS support.
|
|
214
|
+
|
|
215
|
+
This is the canonical configuration class for Inertia.js integration.
|
|
216
|
+
Presence of an InertiaConfig instance indicates Inertia is enabled.
|
|
217
|
+
|
|
218
|
+
Note:
|
|
219
|
+
SPA mode (HTML transformation vs Jinja2 templates) is controlled by
|
|
220
|
+
ViteConfig.mode='hybrid'. The app_selector for data-page injection
|
|
221
|
+
is configured via SPAConfig.app_selector.
|
|
222
|
+
|
|
223
|
+
Attributes:
|
|
224
|
+
root_template: Name of the root template to use.
|
|
225
|
+
component_opt_keys: Identifiers for getting inertia component from route opts.
|
|
226
|
+
redirect_unauthorized_to: Path for unauthorized request redirects.
|
|
227
|
+
redirect_404: Path for 404 request redirects.
|
|
228
|
+
extra_static_page_props: Static props added to every page response.
|
|
229
|
+
extra_session_page_props: Session keys to include in page props.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
root_template: str = "index.html"
|
|
233
|
+
"""Name of the root template to use.
|
|
234
|
+
|
|
235
|
+
This must be a path that is found by the Vite Plugin template config
|
|
236
|
+
"""
|
|
237
|
+
component_opt_keys: "tuple[str, ...]" = ("component", "page")
|
|
238
|
+
"""Identifiers to use on routes to get the inertia component to render.
|
|
239
|
+
|
|
240
|
+
The first key found in the route handler opts will be used. This allows
|
|
241
|
+
semantic flexibility - use "component" or "page" depending on preference.
|
|
242
|
+
|
|
243
|
+
Example:
|
|
244
|
+
# All equivalent:
|
|
245
|
+
@get("/", component="Home")
|
|
246
|
+
@get("/", page="Home")
|
|
247
|
+
|
|
248
|
+
# Custom keys:
|
|
249
|
+
InertiaConfig(component_opt_keys=("view", "component", "page"))
|
|
250
|
+
"""
|
|
251
|
+
redirect_unauthorized_to: "str | None" = None
|
|
252
|
+
"""Optionally supply a path where unauthorized requests should redirect."""
|
|
253
|
+
redirect_404: "str | None" = None
|
|
254
|
+
"""Optionally supply a path where 404 requests should redirect."""
|
|
255
|
+
extra_static_page_props: "dict[str, Any]" = field(default_factory=_empty_dict_factory)
|
|
256
|
+
"""A dictionary of values to automatically add in to page props on every response."""
|
|
257
|
+
extra_session_page_props: "set[str]" = field(default_factory=_empty_set_factory)
|
|
258
|
+
"""A set of session keys for which the value automatically be added (if it exists) to the response."""
|
|
259
|
+
encrypt_history: bool = False
|
|
260
|
+
"""Enable browser history encryption globally (v2 feature).
|
|
261
|
+
|
|
262
|
+
When True, all Inertia responses will include `encryptHistory: true`
|
|
263
|
+
in the page object. The Inertia client will encrypt history state
|
|
264
|
+
using browser's crypto API before pushing to history.
|
|
265
|
+
|
|
266
|
+
This prevents sensitive data from being visible in browser history
|
|
267
|
+
after a user logs out. Individual responses can override this setting.
|
|
268
|
+
|
|
269
|
+
Note: Encryption happens client-side; requires HTTPS in production.
|
|
270
|
+
See: https://inertiajs.com/history-encryption
|
|
271
|
+
"""
|
|
272
|
+
type_gen: "InertiaTypeGenConfig | None" = None
|
|
273
|
+
"""Type generation options for Inertia page props.
|
|
274
|
+
|
|
275
|
+
Controls default types in generated page-props.ts. Set to InertiaTypeGenConfig()
|
|
276
|
+
or leave as None for defaults. Use InertiaTypeGenConfig(include_default_auth=False)
|
|
277
|
+
to disable default User/AuthData interfaces for non-standard user models.
|
|
32
278
|
"""
|
|
33
279
|
|
|
34
|
-
|
|
35
|
-
"""
|
|
280
|
+
ssr: "InertiaSSRConfig | bool | None" = None
|
|
281
|
+
"""Enable server-side rendering (SSR) for Inertia responses.
|
|
36
282
|
|
|
37
|
-
|
|
283
|
+
When enabled, full-page HTML responses will be pre-rendered by a Node SSR server
|
|
284
|
+
and injected into the SPA HTML before returning to the client.
|
|
285
|
+
|
|
286
|
+
Supports:
|
|
287
|
+
- True: enable with defaults -> ``InertiaSSRConfig()``
|
|
288
|
+
- False/None: disabled -> ``None``
|
|
289
|
+
- InertiaSSRConfig: use as-is
|
|
38
290
|
"""
|
|
39
|
-
|
|
40
|
-
|
|
291
|
+
|
|
292
|
+
def __post_init__(self) -> None:
|
|
293
|
+
"""Normalize optional sub-configs."""
|
|
294
|
+
if self.ssr is True:
|
|
295
|
+
self.ssr = InertiaSSRConfig()
|
|
296
|
+
elif self.ssr is False:
|
|
297
|
+
self.ssr = None
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def ssr_config(self) -> "InertiaSSRConfig | None":
|
|
301
|
+
"""Return the SSR config when enabled, otherwise None.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
The resolved SSR config when enabled, otherwise None.
|
|
305
|
+
"""
|
|
306
|
+
if isinstance(self.ssr, InertiaSSRConfig) and self.ssr.enabled:
|
|
307
|
+
return self.ssr
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@dataclass
|
|
312
|
+
class InertiaTypeGenConfig:
|
|
313
|
+
"""Type generation options for Inertia page props.
|
|
314
|
+
|
|
315
|
+
Controls which default types are included in the generated page-props.ts file.
|
|
316
|
+
This follows Laravel Jetstream patterns - sensible defaults for common auth patterns.
|
|
317
|
+
|
|
318
|
+
Attributes:
|
|
319
|
+
include_default_auth: Include default User and AuthData interfaces.
|
|
320
|
+
Default User has: id, email, name. Users extend via module augmentation.
|
|
321
|
+
Set to False if your User model doesn't have these fields (uses uuid, username, etc.)
|
|
322
|
+
include_default_flash: Include default FlashMessages interface.
|
|
323
|
+
Uses { [category: string]: string[] } pattern for flash messages.
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
Standard auth (95% of users) - just extend defaults::
|
|
327
|
+
|
|
328
|
+
# Python: use defaults
|
|
329
|
+
ViteConfig(inertia=InertiaConfig())
|
|
330
|
+
|
|
331
|
+
# TypeScript: extend User interface
|
|
332
|
+
declare module 'litestar-vite-plugin/inertia' {
|
|
333
|
+
interface User {
|
|
334
|
+
avatarUrl?: string
|
|
335
|
+
roles: Role[]
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
Custom auth (5% of users) - define from scratch::
|
|
340
|
+
|
|
341
|
+
# Python: disable defaults
|
|
342
|
+
ViteConfig(inertia=InertiaConfig(
|
|
343
|
+
type_gen=InertiaTypeGenConfig(include_default_auth=False)
|
|
344
|
+
))
|
|
345
|
+
|
|
346
|
+
# TypeScript: define your custom User
|
|
347
|
+
declare module 'litestar-vite-plugin/inertia' {
|
|
348
|
+
interface User {
|
|
349
|
+
uuid: string // No id!
|
|
350
|
+
username: string // No email!
|
|
351
|
+
}
|
|
352
|
+
}
|
|
41
353
|
"""
|
|
354
|
+
|
|
355
|
+
include_default_auth: bool = True
|
|
356
|
+
"""Include default User and AuthData interfaces.
|
|
357
|
+
|
|
358
|
+
When True, generates:
|
|
359
|
+
- User: { id: string, email: string, name?: string | null }
|
|
360
|
+
- AuthData: { isAuthenticated: boolean, user?: User }
|
|
361
|
+
|
|
362
|
+
Users extend via TypeScript module augmentation.
|
|
363
|
+
Set to False if your User model has different required fields.
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
include_default_flash: bool = True
|
|
367
|
+
"""Include default FlashMessages interface.
|
|
368
|
+
|
|
369
|
+
When True, generates:
|
|
370
|
+
- FlashMessages: { [category: string]: string[] }
|
|
371
|
+
|
|
372
|
+
Standard flash message pattern used by most web frameworks.
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _resolve_proxy_mode() -> "Literal['vite', 'direct', 'proxy'] | None":
|
|
377
|
+
"""Resolve proxy_mode from environment variable.
|
|
378
|
+
|
|
379
|
+
Reads VITE_PROXY_MODE env var. Valid values:
|
|
380
|
+
- "vite" (default): Proxy to internal Vite server (allow list - assets only)
|
|
381
|
+
- "direct": Expose Vite port directly (no proxy)
|
|
382
|
+
- "proxy": Proxy everything except Litestar routes (deny list)
|
|
383
|
+
- "none": Disable proxy (for production)
|
|
384
|
+
|
|
385
|
+
Raises:
|
|
386
|
+
ValueError: If an invalid value is provided.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
The resolved proxy mode, or None if disabled.
|
|
390
|
+
"""
|
|
391
|
+
env_value = os.getenv("VITE_PROXY_MODE")
|
|
392
|
+
match env_value.strip().lower() if env_value is not None else None:
|
|
393
|
+
case None:
|
|
394
|
+
return "vite"
|
|
395
|
+
case "none":
|
|
396
|
+
return None
|
|
397
|
+
case "direct":
|
|
398
|
+
return "direct"
|
|
399
|
+
case "proxy":
|
|
400
|
+
return "proxy"
|
|
401
|
+
case "vite":
|
|
402
|
+
return "vite"
|
|
403
|
+
case _:
|
|
404
|
+
msg = f"Invalid VITE_PROXY_MODE: {env_value!r}. Expected one of: vite, direct, proxy, none"
|
|
405
|
+
raise ValueError(msg)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@dataclass
|
|
409
|
+
class PathConfig:
|
|
410
|
+
"""File system paths configuration.
|
|
411
|
+
|
|
412
|
+
Attributes:
|
|
413
|
+
root: The root directory of the project. Defaults to current working directory.
|
|
414
|
+
bundle_dir: Location of compiled assets and manifest.json.
|
|
415
|
+
resource_dir: TypeScript/JavaScript source directory (equivalent to ./src in Vue/React).
|
|
416
|
+
static_dir: Static public assets directory (served as-is by Vite).
|
|
417
|
+
manifest_name: Name of the Vite manifest file.
|
|
418
|
+
hot_file: Name of the hot file indicating dev server URL.
|
|
419
|
+
asset_url: Base URL for static asset references (prepended to Vite output).
|
|
420
|
+
ssr_output_dir: SSR output directory (optional).
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
root: "str | Path" = field(default_factory=Path.cwd)
|
|
424
|
+
bundle_dir: "str | Path" = field(default_factory=lambda: Path("public"))
|
|
425
|
+
resource_dir: "str | Path" = field(default_factory=lambda: Path("src"))
|
|
426
|
+
static_dir: "str | Path" = field(default_factory=lambda: Path("public"))
|
|
42
427
|
manifest_name: str = "manifest.json"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
428
|
+
hot_file: str = "hot"
|
|
429
|
+
asset_url: str = field(default_factory=lambda: os.getenv("ASSET_URL", "/static/"))
|
|
430
|
+
ssr_output_dir: "str | Path | None" = None
|
|
431
|
+
|
|
432
|
+
def __post_init__(self) -> None:
|
|
433
|
+
"""Normalize path types to Path objects.
|
|
434
|
+
|
|
435
|
+
This also adjusts defaults to prevent Vite's ``publicDir`` (input) from
|
|
436
|
+
colliding with ``outDir`` (output). ``bundle_dir`` is treated as the build
|
|
437
|
+
output directory. When ``static_dir`` equals ``bundle_dir``, Vite may warn
|
|
438
|
+
and effectively disable public asset copying, so ``static_dir`` defaults to
|
|
439
|
+
``<resource_dir>/public`` in that case.
|
|
440
|
+
"""
|
|
441
|
+
if isinstance(self.root, str):
|
|
442
|
+
object.__setattr__(self, "root", Path(self.root))
|
|
443
|
+
if isinstance(self.bundle_dir, str):
|
|
444
|
+
object.__setattr__(self, "bundle_dir", Path(self.bundle_dir))
|
|
445
|
+
if isinstance(self.resource_dir, str):
|
|
446
|
+
object.__setattr__(self, "resource_dir", Path(self.resource_dir))
|
|
447
|
+
if isinstance(self.static_dir, str):
|
|
448
|
+
object.__setattr__(self, "static_dir", Path(self.static_dir))
|
|
449
|
+
if isinstance(self.ssr_output_dir, str):
|
|
450
|
+
object.__setattr__(self, "ssr_output_dir", Path(self.ssr_output_dir))
|
|
451
|
+
|
|
452
|
+
if (
|
|
453
|
+
isinstance(self.bundle_dir, Path)
|
|
454
|
+
and isinstance(self.static_dir, Path)
|
|
455
|
+
and self.static_dir == self.bundle_dir
|
|
456
|
+
):
|
|
457
|
+
object.__setattr__(self, "static_dir", Path(self.resource_dir) / "public")
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
@dataclass
|
|
461
|
+
class ExternalDevServer:
|
|
462
|
+
"""Configuration for external (non-Vite) dev servers.
|
|
463
|
+
|
|
464
|
+
Use this when your frontend uses a framework with its own dev server
|
|
465
|
+
(Angular CLI, Next.js, Create React App, etc.) instead of Vite.
|
|
466
|
+
|
|
467
|
+
For SSR frameworks (Astro, Nuxt, SvelteKit) using Vite internally, leave
|
|
468
|
+
target as None - the proxy will read the dynamic port from the hotfile.
|
|
469
|
+
|
|
470
|
+
Attributes:
|
|
471
|
+
target: The URL of the external dev server (e.g., "http://localhost:4200").
|
|
472
|
+
If None, the proxy reads the target URL from the Vite hotfile.
|
|
473
|
+
command: Custom command to start the dev server (e.g., ["ng", "serve"]).
|
|
474
|
+
If None and start_dev_server=True, uses executor's default start command.
|
|
475
|
+
build_command: Custom command to build for production (e.g., ["ng", "build"]).
|
|
476
|
+
If None, uses executor's default build command (e.g., "npm run build").
|
|
477
|
+
http2: Enable HTTP/2 for proxy connections.
|
|
478
|
+
enabled: Whether the external proxy is enabled.
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
target: "str | None" = None
|
|
482
|
+
command: "list[str] | None" = None
|
|
483
|
+
build_command: "list[str] | None" = None
|
|
484
|
+
http2: bool = False
|
|
485
|
+
enabled: bool = True
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
@dataclass
|
|
489
|
+
class RuntimeConfig:
|
|
490
|
+
"""Runtime execution settings.
|
|
491
|
+
|
|
492
|
+
Attributes:
|
|
493
|
+
dev_mode: Enable development mode with HMR/watch.
|
|
494
|
+
proxy_mode: Proxy handling mode:
|
|
495
|
+
- "vite" (default): Proxy Vite assets only (allow list - SPA mode)
|
|
496
|
+
- "direct": Expose Vite port directly (no proxy)
|
|
497
|
+
- "proxy": Proxy everything except Litestar routes (deny list - SSR mode)
|
|
498
|
+
- None: No proxy (production mode)
|
|
499
|
+
external_dev_server: Configuration for external dev server (used with proxy_mode="proxy").
|
|
500
|
+
host: Vite dev server host.
|
|
501
|
+
port: Vite dev server port.
|
|
502
|
+
protocol: Protocol for dev server (http/https).
|
|
503
|
+
executor: JavaScript runtime executor (node, bun, deno).
|
|
504
|
+
run_command: Custom command to run Vite dev server (auto-detect if None).
|
|
505
|
+
build_command: Custom command to build with Vite (auto-detect if None).
|
|
506
|
+
build_watch_command: Custom command for watch mode build.
|
|
507
|
+
serve_command: Custom command to run production server (for SSR frameworks).
|
|
508
|
+
install_command: Custom command to install dependencies.
|
|
509
|
+
is_react: Enable React Fast Refresh support.
|
|
510
|
+
ssr_enabled: Enable Server-Side Rendering.
|
|
511
|
+
health_check: Enable health check for dev server startup.
|
|
512
|
+
detect_nodeenv: Detect and use nodeenv in virtualenv (opt-in).
|
|
513
|
+
set_environment: Set Vite environment variables from config.
|
|
514
|
+
set_static_folders: Automatically configure static file serving.
|
|
515
|
+
csp_nonce: Content Security Policy nonce for inline scripts.
|
|
516
|
+
spa_handler: Auto-register catch-all SPA route when mode="spa".
|
|
517
|
+
http2: Enable HTTP/2 for proxy HTTP requests (better multiplexing).
|
|
518
|
+
WebSocket traffic (HMR) uses a separate connection and is unaffected.
|
|
519
|
+
"""
|
|
520
|
+
|
|
521
|
+
dev_mode: bool = field(default_factory=lambda: os.getenv("VITE_DEV_MODE", "False") in TRUE_VALUES)
|
|
522
|
+
proxy_mode: "Literal['vite', 'direct', 'proxy'] | None" = field(default_factory=_resolve_proxy_mode)
|
|
523
|
+
external_dev_server: "ExternalDevServer | str | None" = None
|
|
524
|
+
host: str = field(default_factory=lambda: os.getenv("VITE_HOST", "127.0.0.1"))
|
|
525
|
+
port: int = field(default_factory=lambda: int(os.getenv("VITE_PORT", "5173")))
|
|
526
|
+
protocol: Literal["http", "https"] = "http"
|
|
527
|
+
executor: "Literal['node', 'bun', 'deno', 'yarn', 'pnpm'] | None" = None
|
|
528
|
+
run_command: "list[str] | None" = None
|
|
529
|
+
build_command: "list[str] | None" = None
|
|
530
|
+
build_watch_command: "list[str] | None" = None
|
|
531
|
+
serve_command: "list[str] | None" = None
|
|
532
|
+
install_command: "list[str] | None" = None
|
|
46
533
|
is_react: bool = False
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
534
|
+
ssr_enabled: bool = False
|
|
535
|
+
health_check: bool = field(default_factory=lambda: os.getenv("VITE_HEALTH_CHECK", "False") in TRUE_VALUES)
|
|
536
|
+
detect_nodeenv: bool = False
|
|
537
|
+
set_environment: bool = True
|
|
538
|
+
set_static_folders: bool = True
|
|
539
|
+
csp_nonce: "str | None" = None
|
|
540
|
+
spa_handler: bool = True
|
|
541
|
+
http2: bool = True
|
|
542
|
+
start_dev_server: bool = True
|
|
543
|
+
|
|
544
|
+
def __post_init__(self) -> None:
|
|
545
|
+
"""Normalize runtime settings and apply derived defaults."""
|
|
546
|
+
if isinstance(self.external_dev_server, str):
|
|
547
|
+
self.external_dev_server = ExternalDevServer(target=self.external_dev_server)
|
|
548
|
+
|
|
549
|
+
if self.external_dev_server is not None and self.proxy_mode in {None, "vite"}:
|
|
550
|
+
self.proxy_mode = "proxy"
|
|
551
|
+
|
|
552
|
+
if self.executor is None:
|
|
553
|
+
self.executor = "node"
|
|
554
|
+
|
|
555
|
+
executor_commands = {
|
|
556
|
+
"node": {
|
|
557
|
+
"run": ["npm", "run", "dev"],
|
|
558
|
+
"build": ["npm", "run", "build"],
|
|
559
|
+
"build_watch": ["npm", "run", "watch"],
|
|
560
|
+
"serve": ["npm", "run", "serve"],
|
|
561
|
+
"install": ["npm", "install"],
|
|
562
|
+
},
|
|
563
|
+
"bun": {
|
|
564
|
+
"run": ["bun", "run", "dev"],
|
|
565
|
+
"build": ["bun", "run", "build"],
|
|
566
|
+
"build_watch": ["bun", "run", "watch"],
|
|
567
|
+
"serve": ["bun", "run", "serve"],
|
|
568
|
+
"install": ["bun", "install"],
|
|
569
|
+
},
|
|
570
|
+
"deno": {
|
|
571
|
+
"run": ["deno", "task", "dev"],
|
|
572
|
+
"build": ["deno", "task", "build"],
|
|
573
|
+
"build_watch": ["deno", "task", "watch"],
|
|
574
|
+
"serve": ["deno", "task", "serve"],
|
|
575
|
+
"install": ["deno", "install"],
|
|
576
|
+
},
|
|
577
|
+
"yarn": {
|
|
578
|
+
"run": ["yarn", "dev"],
|
|
579
|
+
"build": ["yarn", "build"],
|
|
580
|
+
"build_watch": ["yarn", "watch"],
|
|
581
|
+
"serve": ["yarn", "serve"],
|
|
582
|
+
"install": ["yarn", "install"],
|
|
583
|
+
},
|
|
584
|
+
"pnpm": {
|
|
585
|
+
"run": ["pnpm", "dev"],
|
|
586
|
+
"build": ["pnpm", "build"],
|
|
587
|
+
"build_watch": ["pnpm", "watch"],
|
|
588
|
+
"serve": ["pnpm", "serve"],
|
|
589
|
+
"install": ["pnpm", "install"],
|
|
590
|
+
},
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if self.executor in executor_commands:
|
|
594
|
+
cmds = executor_commands[self.executor]
|
|
595
|
+
if self.run_command is None:
|
|
596
|
+
self.run_command = cmds["run"]
|
|
597
|
+
if self.build_command is None:
|
|
598
|
+
self.build_command = cmds["build"]
|
|
599
|
+
if self.build_watch_command is None:
|
|
600
|
+
self.build_watch_command = cmds["build_watch"]
|
|
601
|
+
if self.serve_command is None:
|
|
602
|
+
self.serve_command = cmds["serve"]
|
|
603
|
+
if self.install_command is None:
|
|
604
|
+
self.install_command = cmds["install"]
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
@dataclass
|
|
608
|
+
class TypeGenConfig:
|
|
609
|
+
"""Type generation settings.
|
|
610
|
+
|
|
611
|
+
Presence of this config enables type generation. Use ``types=None`` or
|
|
612
|
+
``types=False`` in ViteConfig to disable.
|
|
613
|
+
|
|
614
|
+
Attributes:
|
|
615
|
+
output: Output directory for generated types.
|
|
616
|
+
openapi_path: Path to export OpenAPI schema.
|
|
617
|
+
routes_path: Path to export routes metadata (JSON format).
|
|
618
|
+
routes_ts_path: Path to export typed routes TypeScript file.
|
|
619
|
+
generate_zod: Generate Zod schemas from OpenAPI.
|
|
620
|
+
generate_sdk: Generate SDK client from OpenAPI.
|
|
621
|
+
generate_routes: Generate typed routes.ts file (Ziggy-style).
|
|
622
|
+
generate_page_props: Generate Inertia page props TypeScript file.
|
|
623
|
+
Auto-enabled when both types and inertia are configured.
|
|
624
|
+
page_props_path: Path to export page props metadata (JSON format).
|
|
625
|
+
watch_patterns: File patterns to watch for type regeneration.
|
|
626
|
+
global_route: Register route() function globally on window object.
|
|
627
|
+
When True, adds ``window.route = route`` to generated routes.ts,
|
|
628
|
+
providing Laravel/Ziggy-style global access without imports.
|
|
629
|
+
fallback_type: Fallback value type for untyped containers in generated Inertia props.
|
|
630
|
+
Controls whether untyped dict/list become `unknown` (default) or `any`.
|
|
631
|
+
type_import_paths: Map schema/type names to TypeScript import paths for props types
|
|
632
|
+
that are not present in OpenAPI (e.g., internal/excluded schemas).
|
|
633
|
+
"""
|
|
634
|
+
|
|
635
|
+
output: Path = field(default_factory=lambda: Path("src/generated"))
|
|
636
|
+
openapi_path: "Path | None" = field(default=None)
|
|
637
|
+
routes_path: "Path | None" = field(default=None)
|
|
638
|
+
routes_ts_path: "Path | None" = field(default=None)
|
|
639
|
+
generate_zod: bool = False
|
|
640
|
+
generate_sdk: bool = True
|
|
641
|
+
generate_routes: bool = True
|
|
642
|
+
generate_page_props: bool = True
|
|
643
|
+
global_route: bool = False
|
|
644
|
+
"""Register route() function globally on window object.
|
|
645
|
+
|
|
646
|
+
When True, the generated routes.ts will include code that registers
|
|
647
|
+
the type-safe route() function on ``window.route``, similar to Laravel's
|
|
648
|
+
Ziggy library. This allows using route() without imports:
|
|
649
|
+
|
|
650
|
+
.. code-block:: typescript
|
|
651
|
+
|
|
652
|
+
// With global_route=True, no import needed:
|
|
653
|
+
window.route('user-profile', { userId: 123 })
|
|
654
|
+
|
|
655
|
+
// TypeScript users should add to global.d.ts:
|
|
656
|
+
// declare const route: typeof import('@/generated/routes').route
|
|
657
|
+
|
|
658
|
+
Default is False to encourage explicit imports for better tree-shaking.
|
|
659
|
+
"""
|
|
660
|
+
fallback_type: "Literal['unknown', 'any']" = "unknown"
|
|
661
|
+
type_import_paths: dict[str, str] = field(default_factory=lambda: cast("dict[str, str]", {}))
|
|
662
|
+
"""Map schema/type names to TypeScript import paths for Inertia props.
|
|
663
|
+
|
|
664
|
+
Use this for prop types that are not present in OpenAPI (e.g., internal schemas).
|
|
665
|
+
"""
|
|
666
|
+
page_props_path: "Path | None" = field(default=None)
|
|
667
|
+
"""Path to export page props metadata JSON.
|
|
668
|
+
|
|
669
|
+
The Vite plugin reads this file to generate page-props.ts.
|
|
670
|
+
Defaults to output / "inertia-pages.json".
|
|
671
|
+
"""
|
|
672
|
+
watch_patterns: list[str] = field(
|
|
673
|
+
default_factory=lambda: ["**/routes.py", "**/handlers.py", "**/controllers/**/*.py"]
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
def __post_init__(self) -> None:
|
|
677
|
+
"""Normalize path types and compute defaults based on output directory."""
|
|
678
|
+
if isinstance(self.output, str):
|
|
679
|
+
self.output = Path(self.output)
|
|
680
|
+
if self.openapi_path is None:
|
|
681
|
+
self.openapi_path = self.output / "openapi.json"
|
|
682
|
+
elif isinstance(self.openapi_path, str):
|
|
683
|
+
self.openapi_path = Path(self.openapi_path)
|
|
684
|
+
if self.routes_path is None:
|
|
685
|
+
self.routes_path = self.output / "routes.json"
|
|
686
|
+
elif isinstance(self.routes_path, str):
|
|
687
|
+
self.routes_path = Path(self.routes_path)
|
|
688
|
+
if self.routes_ts_path is None:
|
|
689
|
+
self.routes_ts_path = self.output / "routes.ts"
|
|
690
|
+
elif isinstance(self.routes_ts_path, str):
|
|
691
|
+
self.routes_ts_path = Path(self.routes_ts_path)
|
|
692
|
+
if self.page_props_path is None:
|
|
693
|
+
self.page_props_path = self.output / "inertia-pages.json"
|
|
694
|
+
elif isinstance(self.page_props_path, str):
|
|
695
|
+
self.page_props_path = Path(self.page_props_path)
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
@dataclass
|
|
699
|
+
class SPAConfig:
|
|
700
|
+
"""Configuration for SPA HTML transformations.
|
|
701
|
+
|
|
702
|
+
This configuration controls how the SPA HTML is transformed before serving,
|
|
703
|
+
including CSRF token injection and Inertia.js page data handling.
|
|
704
|
+
|
|
705
|
+
Note:
|
|
706
|
+
Route metadata is now generated as TypeScript (routes.ts) at build time
|
|
707
|
+
instead of runtime injection. Use TypeGenConfig.generate_routes to enable.
|
|
708
|
+
|
|
709
|
+
Attributes:
|
|
710
|
+
inject_csrf: Whether to inject CSRF token into HTML (as window.__LITESTAR_CSRF__).
|
|
711
|
+
csrf_var_name: Global variable name for CSRF token (e.g., window.__LITESTAR_CSRF__).
|
|
712
|
+
app_selector: CSS selector for the app root element (used for data attributes).
|
|
713
|
+
cache_transformed_html: Cache transformed HTML in production; disabled when inject_csrf=True because CSRF tokens are per-request.
|
|
714
|
+
"""
|
|
715
|
+
|
|
716
|
+
inject_csrf: bool = True
|
|
717
|
+
csrf_var_name: str = "__LITESTAR_CSRF__"
|
|
718
|
+
app_selector: str = "#app"
|
|
719
|
+
cache_transformed_html: bool = True
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def _get_default_log_level() -> "Literal['quiet', 'normal', 'verbose']":
|
|
723
|
+
"""Get default log level from environment variable.
|
|
724
|
+
|
|
725
|
+
Checks LITESTAR_VITE_LOG_LEVEL environment variable.
|
|
726
|
+
Falls back to "normal" if not set or invalid.
|
|
727
|
+
|
|
728
|
+
Returns:
|
|
729
|
+
The log level from environment or "normal" default.
|
|
730
|
+
"""
|
|
731
|
+
env_level = os.getenv("LITESTAR_VITE_LOG_LEVEL", "").lower()
|
|
732
|
+
match env_level:
|
|
733
|
+
case "quiet" | "normal" | "verbose":
|
|
734
|
+
return env_level
|
|
735
|
+
case _:
|
|
736
|
+
return "normal"
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def _to_root_path(root_dir: Path, path: Path) -> Path:
|
|
740
|
+
"""Resolve a path relative to the configured root directory.
|
|
741
|
+
|
|
742
|
+
Args:
|
|
743
|
+
root_dir: Application root directory.
|
|
744
|
+
path: Path to resolve.
|
|
745
|
+
|
|
746
|
+
Returns:
|
|
747
|
+
Absolute path rooted at ``root_dir`` when ``path`` is relative, otherwise ``path`` unchanged.
|
|
748
|
+
"""
|
|
749
|
+
return path if path.is_absolute() else (root_dir / path)
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
@dataclass
|
|
753
|
+
class LoggingConfig:
|
|
754
|
+
"""Logging configuration for console output.
|
|
755
|
+
|
|
756
|
+
Controls the verbosity and style of console output from both Python
|
|
757
|
+
and TypeScript (via .litestar.json bridge).
|
|
50
758
|
|
|
51
|
-
|
|
759
|
+
Attributes:
|
|
760
|
+
level: Logging verbosity level.
|
|
761
|
+
- "quiet": Minimal output (errors only)
|
|
762
|
+
- "normal": Standard operational messages (default)
|
|
763
|
+
- "verbose": Detailed debugging information
|
|
764
|
+
Can also be set via LITESTAR_VITE_LOG_LEVEL environment variable.
|
|
765
|
+
Precedence: explicit config > env var > default ("normal")
|
|
766
|
+
show_paths_absolute: Show absolute paths instead of relative paths.
|
|
767
|
+
Default False shows cleaner relative paths in output.
|
|
768
|
+
suppress_npm_output: Suppress npm/yarn/pnpm script echo lines.
|
|
769
|
+
When True, hides lines like "> dev" / "> vite" from output.
|
|
770
|
+
suppress_vite_banner: Suppress the Vite startup banner.
|
|
771
|
+
When True, only the LITESTAR banner is shown.
|
|
772
|
+
timestamps: Include timestamps in log messages.
|
|
773
|
+
|
|
774
|
+
Example:
|
|
775
|
+
Quiet mode for CI/CD::
|
|
776
|
+
|
|
777
|
+
ViteConfig(logging=LoggingConfig(level="quiet"))
|
|
778
|
+
|
|
779
|
+
Verbose debugging::
|
|
780
|
+
|
|
781
|
+
ViteConfig(logging=LoggingConfig(level="verbose", show_paths_absolute=True))
|
|
782
|
+
|
|
783
|
+
Environment variable::
|
|
784
|
+
|
|
785
|
+
export LITESTAR_VITE_LOG_LEVEL=quiet
|
|
52
786
|
"""
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
run_command: str = "npm run dev"
|
|
60
|
-
"""Default command to use for running Vite."""
|
|
61
|
-
build_command: str = "npm run build"
|
|
787
|
+
|
|
788
|
+
level: "Literal['quiet', 'normal', 'verbose']" = field(default_factory=_get_default_log_level)
|
|
789
|
+
show_paths_absolute: bool = False
|
|
790
|
+
suppress_npm_output: bool = False
|
|
791
|
+
suppress_vite_banner: bool = False
|
|
792
|
+
timestamps: bool = False
|
|
62
793
|
|
|
63
794
|
|
|
64
795
|
@dataclass
|
|
65
|
-
class
|
|
66
|
-
"""
|
|
796
|
+
class ViteConfig:
|
|
797
|
+
"""Root Vite configuration.
|
|
798
|
+
|
|
799
|
+
This is the main configuration class that combines all sub-configurations.
|
|
800
|
+
Supports shortcuts for common configurations:
|
|
801
|
+
|
|
802
|
+
- dev_mode: Shortcut for runtime.dev_mode
|
|
803
|
+
- types=True or TypeGenConfig(): Enable type generation (presence = enabled)
|
|
804
|
+
- inertia=True or InertiaConfig(): Enable Inertia.js (presence = enabled)
|
|
805
|
+
|
|
806
|
+
Mode auto-detection:
|
|
807
|
+
|
|
808
|
+
- If mode is not explicitly set:
|
|
809
|
+
|
|
810
|
+
- If Inertia is enabled and index.html exists -> Hybrid mode
|
|
811
|
+
- If Inertia is enabled without index.html -> Template mode
|
|
812
|
+
- Checks for index.html in common locations -> SPA mode
|
|
813
|
+
- Checks if Jinja2 template engine is configured -> Template mode
|
|
814
|
+
- Otherwise defaults to SPA mode
|
|
815
|
+
|
|
816
|
+
Dev-mode auto-enable:
|
|
817
|
+
|
|
818
|
+
- If mode="spa" and no built assets are found in bundle_dir, dev_mode is
|
|
819
|
+
enabled automatically (unless VITE_AUTO_DEV_MODE=False).
|
|
820
|
+
|
|
821
|
+
- Explicit mode parameter overrides auto-detection
|
|
822
|
+
|
|
823
|
+
Attributes:
|
|
824
|
+
mode: Serving mode - "spa", "template", "htmx", "hybrid", "ssr", "ssg", or "external".
|
|
825
|
+
Auto-detected if not set. Use "external" for non-Vite frameworks (Angular CLI, etc.)
|
|
826
|
+
that have their own build system - auto-serves bundle_dir in production.
|
|
827
|
+
paths: File system paths configuration.
|
|
828
|
+
runtime: Runtime execution settings.
|
|
829
|
+
types: Type generation settings (True/TypeGenConfig enables, False/None disables).
|
|
830
|
+
inertia: Inertia.js settings (True/InertiaConfig enables, False/None disables).
|
|
831
|
+
spa: SPA transformation settings (True enables with defaults, False disables).
|
|
832
|
+
logging: Logging configuration (True enables with defaults, None uses defaults).
|
|
833
|
+
dev_mode: Convenience shortcut for runtime.dev_mode.
|
|
834
|
+
base_url: Base URL for production assets (CDN support).
|
|
835
|
+
deploy: Deployment configuration for CDN publishing.
|
|
836
|
+
"""
|
|
837
|
+
|
|
838
|
+
mode: "Literal['spa', 'template', 'htmx', 'hybrid', 'inertia', 'ssr', 'ssg', 'external'] | None" = None
|
|
839
|
+
paths: PathConfig = field(default_factory=PathConfig)
|
|
840
|
+
runtime: RuntimeConfig = field(default_factory=RuntimeConfig)
|
|
841
|
+
types: "TypeGenConfig | bool | None" = None
|
|
842
|
+
inertia: "InertiaConfig | bool | None" = None
|
|
843
|
+
spa: "SPAConfig | bool | None" = None
|
|
844
|
+
logging: "LoggingConfig | bool | None" = None
|
|
845
|
+
dev_mode: bool = False
|
|
846
|
+
base_url: "str | None" = field(default_factory=lambda: os.getenv("VITE_BASE_URL"))
|
|
847
|
+
deploy: "DeployConfig | bool" = False
|
|
848
|
+
guards: "Sequence[Guard] | None" = None # pyright: ignore[reportUnknownVariableType]
|
|
849
|
+
"""Custom guards for the SPA catch-all route.
|
|
850
|
+
|
|
851
|
+
When set, these guards are applied to the SPA handler route that serves the
|
|
852
|
+
SPA index.html (mode="spa"/"ssr" with spa_handler=True).
|
|
853
|
+
"""
|
|
854
|
+
exclude_static_from_auth: bool = True
|
|
855
|
+
"""Exclude static file routes from authentication.
|
|
67
856
|
|
|
68
|
-
|
|
69
|
-
:
|
|
70
|
-
|
|
857
|
+
When True (default), static file routes are served with
|
|
858
|
+
opt={"exclude_from_auth": True}, which tells auth middleware to skip
|
|
859
|
+
authentication for asset requests. Set to False if you need to protect
|
|
860
|
+
static assets with authentication.
|
|
71
861
|
"""
|
|
862
|
+
spa_path: "str | None" = None
|
|
863
|
+
"""Path where the SPA handler serves index.html.
|
|
72
864
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
865
|
+
Controls where AppHandler registers its catch-all routes.
|
|
866
|
+
|
|
867
|
+
- Default: "/" (root)
|
|
868
|
+
- Non-root (e.g. "/web/"): optionally set `include_root_spa_paths=True` to
|
|
869
|
+
also serve at "/" and "/{path:path}".
|
|
870
|
+
"""
|
|
871
|
+
include_root_spa_paths: bool = False
|
|
872
|
+
"""Also register SPA routes at root when spa_path is non-root.
|
|
873
|
+
|
|
874
|
+
When True and spa_path is set to a non-root path (e.g., "/web/"),
|
|
875
|
+
the SPA handler will also serve at "/" and "/{path:path}" in addition
|
|
876
|
+
to the spa_path routes.
|
|
877
|
+
|
|
878
|
+
This is useful for Angular apps with --base-href /web/ that also
|
|
879
|
+
want to serve the SPA from the root path for convenience.
|
|
880
|
+
"""
|
|
881
|
+
|
|
882
|
+
_executor_instance: "JSExecutor | None" = field(default=None, repr=False)
|
|
883
|
+
_mode_auto_detected: bool = field(default=False, repr=False)
|
|
83
884
|
|
|
84
885
|
def __post_init__(self) -> None:
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
886
|
+
"""Normalize configurations and apply shortcuts."""
|
|
887
|
+
self._normalize_mode()
|
|
888
|
+
self._normalize_types()
|
|
889
|
+
self._normalize_inertia()
|
|
890
|
+
self._normalize_spa_flag()
|
|
891
|
+
self._normalize_logging()
|
|
892
|
+
self._apply_dev_mode_shortcut()
|
|
893
|
+
self._auto_detect_mode()
|
|
894
|
+
self._auto_configure_inertia()
|
|
895
|
+
self._auto_detect_react()
|
|
896
|
+
self._apply_ssr_mode_defaults()
|
|
897
|
+
self._normalize_deploy()
|
|
898
|
+
self._ensure_spa_default()
|
|
899
|
+
self._auto_enable_dev_mode()
|
|
900
|
+
self._warn_missing_assets()
|
|
901
|
+
|
|
902
|
+
def _auto_detect_react(self) -> None:
|
|
903
|
+
"""Enable React Fast Refresh automatically for React templates.
|
|
904
|
+
|
|
905
|
+
When serving HTML outside Vite's native index.html pipeline (template/hybrid modes),
|
|
906
|
+
@vitejs/plugin-react requires the React preamble to be injected into the HTML.
|
|
907
|
+
The asset loader handles this when `runtime.is_react` is enabled.
|
|
908
|
+
|
|
909
|
+
We auto-enable it when `@vitejs/plugin-react` is present in the project's package.json.
|
|
910
|
+
"""
|
|
911
|
+
if self.runtime.is_react:
|
|
912
|
+
return
|
|
913
|
+
|
|
914
|
+
package_json = self.root_dir / "package.json"
|
|
915
|
+
if not package_json.exists():
|
|
916
|
+
return
|
|
917
|
+
|
|
918
|
+
try:
|
|
919
|
+
payload = decode_json(package_json.read_text(encoding="utf-8"))
|
|
920
|
+
except (OSError, UnicodeDecodeError, SerializationException): # pragma: no cover - defensive
|
|
921
|
+
return
|
|
922
|
+
|
|
923
|
+
deps_any = payload.get("dependencies")
|
|
924
|
+
dev_deps_any = payload.get("devDependencies")
|
|
925
|
+
|
|
926
|
+
deps: dict[str, Any] = {}
|
|
927
|
+
dev_deps: dict[str, Any] = {}
|
|
928
|
+
|
|
929
|
+
if isinstance(deps_any, dict):
|
|
930
|
+
deps = cast("dict[str, Any]", deps_any)
|
|
931
|
+
if isinstance(dev_deps_any, dict):
|
|
932
|
+
dev_deps = cast("dict[str, Any]", dev_deps_any)
|
|
933
|
+
|
|
934
|
+
if "@vitejs/plugin-react" in deps or "@vitejs/plugin-react" in dev_deps:
|
|
935
|
+
self.runtime.is_react = True
|
|
936
|
+
|
|
937
|
+
def _normalize_mode(self) -> None:
|
|
938
|
+
"""Normalize mode aliases.
|
|
939
|
+
|
|
940
|
+
Aliases:
|
|
941
|
+
- 'ssg' → 'ssr': Static Site Generation uses the same proxy behavior as SSR.
|
|
942
|
+
Both use deny list proxy in dev mode (forward non-API routes to framework's
|
|
943
|
+
dev server). SSG pre-renders at build time, SSR renders per-request, but
|
|
944
|
+
their dev-time proxy behavior is identical.
|
|
945
|
+
|
|
946
|
+
- 'inertia' → 'hybrid': Inertia.js apps without Jinja templates use hybrid mode.
|
|
947
|
+
This is clearer terminology since "hybrid" refers to the SPA-with-server-routing
|
|
948
|
+
pattern that Inertia implements.
|
|
949
|
+
"""
|
|
950
|
+
if self.mode == "ssg":
|
|
951
|
+
self.mode = "ssr"
|
|
952
|
+
elif self.mode == "inertia":
|
|
953
|
+
self.mode = "hybrid"
|
|
954
|
+
|
|
955
|
+
def _normalize_types(self) -> None:
|
|
956
|
+
"""Normalize type generation configuration.
|
|
957
|
+
|
|
958
|
+
Supports:
|
|
959
|
+
- True: Enable with defaults -> TypeGenConfig()
|
|
960
|
+
- False/None: Disabled -> None
|
|
961
|
+
- TypeGenConfig: Use as-is (presence = enabled)
|
|
962
|
+
"""
|
|
963
|
+
if self.types is True:
|
|
964
|
+
self.types = TypeGenConfig()
|
|
965
|
+
elif self.types is False or self.types is None:
|
|
966
|
+
self.types = None
|
|
967
|
+
return
|
|
968
|
+
self._resolve_type_paths(self.types)
|
|
969
|
+
|
|
970
|
+
def _normalize_inertia(self) -> None:
|
|
971
|
+
"""Normalize inertia configuration.
|
|
972
|
+
|
|
973
|
+
Supports:
|
|
974
|
+
- True: Enable with defaults -> InertiaConfig()
|
|
975
|
+
- False/None: Disabled -> None
|
|
976
|
+
- InertiaConfig: Use as-is
|
|
977
|
+
"""
|
|
978
|
+
if self.inertia is True:
|
|
979
|
+
self.inertia = InertiaConfig()
|
|
980
|
+
elif self.inertia is False:
|
|
981
|
+
self.inertia = None
|
|
982
|
+
|
|
983
|
+
def _normalize_spa_flag(self) -> None:
|
|
984
|
+
if self.spa is True:
|
|
985
|
+
self.spa = SPAConfig()
|
|
986
|
+
|
|
987
|
+
def _normalize_logging(self) -> None:
|
|
988
|
+
"""Normalize logging configuration.
|
|
989
|
+
|
|
990
|
+
Supports:
|
|
991
|
+
- True: Enable with defaults -> LoggingConfig()
|
|
992
|
+
- False/None: Use defaults -> LoggingConfig()
|
|
993
|
+
- LoggingConfig: Use as-is
|
|
994
|
+
"""
|
|
995
|
+
if self.logging is True or self.logging is None or self.logging is False:
|
|
996
|
+
self.logging = LoggingConfig()
|
|
997
|
+
|
|
998
|
+
def _apply_dev_mode_shortcut(self) -> None:
|
|
999
|
+
if self.dev_mode:
|
|
1000
|
+
self.runtime.dev_mode = True
|
|
1001
|
+
|
|
1002
|
+
def _auto_detect_mode(self) -> None:
|
|
1003
|
+
if self.mode is None:
|
|
1004
|
+
self.mode = self._detect_mode()
|
|
1005
|
+
self._mode_auto_detected = True
|
|
1006
|
+
|
|
1007
|
+
def _auto_configure_inertia(self) -> None:
|
|
1008
|
+
"""Auto-configure settings when Inertia is enabled.
|
|
1009
|
+
|
|
1010
|
+
When Inertia is configured, automatically enable CSRF token injection
|
|
1011
|
+
in the SPA config, since Inertia forms need CSRF protection.
|
|
1012
|
+
"""
|
|
1013
|
+
if isinstance(self.inertia, InertiaConfig) and isinstance(self.spa, SPAConfig) and not self.spa.inject_csrf:
|
|
1014
|
+
self.spa = replace(self.spa, inject_csrf=True)
|
|
1015
|
+
|
|
1016
|
+
def _apply_ssr_mode_defaults(self) -> None:
|
|
1017
|
+
"""Apply intelligent defaults for mode='ssr'.
|
|
1018
|
+
|
|
1019
|
+
When mode='ssr' is set, automatically configure proxy_mode and spa_handler
|
|
1020
|
+
based on dev_mode and whether built assets exist:
|
|
1021
|
+
|
|
1022
|
+
- Dev mode: proxy_mode='proxy', spa_handler=False
|
|
1023
|
+
(Proxy all non-API routes to the SSR/SSG framework dev server)
|
|
1024
|
+
- Prod mode with built assets: proxy_mode=None, spa_handler=True
|
|
1025
|
+
(Serve static SSG output like Astro's dist/)
|
|
1026
|
+
- Prod mode without built assets: proxy_mode=None, spa_handler=False
|
|
1027
|
+
(True SSR - Node server handles HTML, Litestar only serves API)
|
|
1028
|
+
"""
|
|
1029
|
+
if self.mode != "ssr":
|
|
1030
|
+
return
|
|
1031
|
+
|
|
1032
|
+
if self.runtime.dev_mode:
|
|
1033
|
+
env_proxy = os.getenv("VITE_PROXY_MODE")
|
|
1034
|
+
if env_proxy is None:
|
|
1035
|
+
self.runtime.proxy_mode = "proxy"
|
|
1036
|
+
self.runtime.spa_handler = False
|
|
1037
|
+
else:
|
|
1038
|
+
self.runtime.proxy_mode = None
|
|
1039
|
+
if self.has_built_assets():
|
|
1040
|
+
self.runtime.spa_handler = True
|
|
1041
|
+
else:
|
|
1042
|
+
self.runtime.spa_handler = False
|
|
1043
|
+
|
|
1044
|
+
def _normalize_deploy(self) -> None:
|
|
1045
|
+
if self.deploy is True:
|
|
1046
|
+
self.deploy = DeployConfig(enabled=True)
|
|
1047
|
+
elif self.deploy is False:
|
|
1048
|
+
self.deploy = DeployConfig(enabled=False)
|
|
1049
|
+
|
|
1050
|
+
def _resolve_type_paths(self, types: TypeGenConfig) -> None:
|
|
1051
|
+
"""Resolve type generation paths relative to the configured root.
|
|
1052
|
+
|
|
1053
|
+
Args:
|
|
1054
|
+
types: Type generation configuration to mutate.
|
|
1055
|
+
|
|
1056
|
+
"""
|
|
1057
|
+
root_dir = self.root_dir
|
|
1058
|
+
|
|
1059
|
+
default_rel = Path("src/generated")
|
|
1060
|
+
default_openapi = default_rel / "openapi.json"
|
|
1061
|
+
default_routes = default_rel / "routes.json"
|
|
1062
|
+
default_page_props = default_rel / "inertia-pages.json"
|
|
1063
|
+
|
|
1064
|
+
if types.openapi_path == default_openapi and types.output != default_rel:
|
|
1065
|
+
types.openapi_path = types.output / "openapi.json"
|
|
1066
|
+
if types.routes_path == default_routes and types.output != default_rel:
|
|
1067
|
+
types.routes_path = types.output / "routes.json"
|
|
1068
|
+
if types.page_props_path == default_page_props and types.output != default_rel:
|
|
1069
|
+
types.page_props_path = types.output / "inertia-pages.json"
|
|
1070
|
+
|
|
1071
|
+
if types.routes_ts_path is None or (
|
|
1072
|
+
types.routes_ts_path == default_rel / "routes.ts" and types.output != default_rel
|
|
1073
|
+
):
|
|
1074
|
+
types.routes_ts_path = types.output / "routes.ts"
|
|
1075
|
+
|
|
1076
|
+
types.output = _to_root_path(root_dir, types.output)
|
|
1077
|
+
types.openapi_path = (
|
|
1078
|
+
_to_root_path(root_dir, types.openapi_path) if types.openapi_path else types.output / "openapi.json"
|
|
1079
|
+
)
|
|
1080
|
+
types.routes_path = (
|
|
1081
|
+
_to_root_path(root_dir, types.routes_path) if types.routes_path else types.output / "routes.json"
|
|
1082
|
+
)
|
|
1083
|
+
types.routes_ts_path = (
|
|
1084
|
+
_to_root_path(root_dir, types.routes_ts_path) if types.routes_ts_path else types.output / "routes.ts"
|
|
1085
|
+
)
|
|
1086
|
+
types.page_props_path = (
|
|
1087
|
+
_to_root_path(root_dir, types.page_props_path)
|
|
1088
|
+
if types.page_props_path
|
|
1089
|
+
else types.output / "inertia-pages.json"
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
def _ensure_spa_default(self) -> None:
|
|
1093
|
+
if self.mode in {"spa", "hybrid"} and self.spa is None:
|
|
1094
|
+
self.spa = SPAConfig()
|
|
1095
|
+
elif self.spa is None:
|
|
1096
|
+
self.spa = False
|
|
1097
|
+
|
|
1098
|
+
def _auto_enable_dev_mode(self) -> None:
|
|
1099
|
+
if not self._mode_auto_detected:
|
|
1100
|
+
return
|
|
1101
|
+
|
|
1102
|
+
auto_dev_mode = os.getenv("VITE_AUTO_DEV_MODE", "True") in TRUE_VALUES
|
|
1103
|
+
if (
|
|
1104
|
+
auto_dev_mode
|
|
1105
|
+
and not self.runtime.dev_mode
|
|
1106
|
+
and self.mode in {"spa", "hybrid"}
|
|
1107
|
+
and not self.has_built_assets()
|
|
1108
|
+
):
|
|
1109
|
+
self.runtime.dev_mode = True
|
|
1110
|
+
|
|
1111
|
+
def _warn_missing_assets(self) -> None:
|
|
1112
|
+
"""Warn if running in production mode without built assets."""
|
|
1113
|
+
import sys
|
|
1114
|
+
|
|
1115
|
+
if self.mode not in {"spa", "hybrid"}:
|
|
1116
|
+
return
|
|
1117
|
+
if self.runtime.dev_mode:
|
|
1118
|
+
return
|
|
1119
|
+
if self.has_built_assets():
|
|
1120
|
+
return
|
|
1121
|
+
|
|
1122
|
+
cli_commands_skip_warning = {
|
|
1123
|
+
"install",
|
|
1124
|
+
"build",
|
|
1125
|
+
"init",
|
|
1126
|
+
"serve",
|
|
1127
|
+
"deploy",
|
|
1128
|
+
"doctor",
|
|
1129
|
+
"generate-types",
|
|
1130
|
+
"export-routes",
|
|
1131
|
+
"status",
|
|
1132
|
+
}
|
|
1133
|
+
argv_str = " ".join(sys.argv)
|
|
1134
|
+
if any(f"assets {cmd}" in argv_str for cmd in cli_commands_skip_warning):
|
|
1135
|
+
return
|
|
1136
|
+
|
|
1137
|
+
if self.runtime.external_dev_server is not None:
|
|
1138
|
+
return
|
|
1139
|
+
|
|
1140
|
+
bundle_path = self._resolve_to_root(self.bundle_dir)
|
|
1141
|
+
manifest_path = bundle_path / ".vite" / self.manifest_name
|
|
1142
|
+
logger.warning(
|
|
1143
|
+
"Vite manifest not found at %s. "
|
|
1144
|
+
"Run 'litestar assets build' (or 'npm run build') to build assets, "
|
|
1145
|
+
"or set dev_mode=True for development. "
|
|
1146
|
+
"Assets will not load correctly without built files or a running Vite dev server.",
|
|
1147
|
+
manifest_path,
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
def _detect_mode(self) -> Literal["spa", "template", "htmx", "hybrid"]:
|
|
1151
|
+
"""Auto-detect the serving mode based on project structure.
|
|
1152
|
+
|
|
1153
|
+
Detection order:
|
|
1154
|
+
1. If Inertia is enabled:
|
|
1155
|
+
a. Default to hybrid mode for SPA-style Inertia applications
|
|
1156
|
+
b. Hybrid mode works with AppHandler + HTML transformation
|
|
1157
|
+
c. index.html is served by Vite dev server in dev mode or built assets in production
|
|
1158
|
+
Note: If using Jinja2 templates with Inertia, set mode="template" explicitly.
|
|
1159
|
+
2. Check for index.html in resource_dir, root_dir, or static_dir → SPA
|
|
1160
|
+
3. Check if Jinja2 is installed and likely to be used → Template
|
|
1161
|
+
4. Default to SPA
|
|
1162
|
+
|
|
1163
|
+
Returns:
|
|
1164
|
+
The detected mode.
|
|
1165
|
+
"""
|
|
1166
|
+
inertia_enabled = isinstance(self.inertia, InertiaConfig)
|
|
1167
|
+
|
|
1168
|
+
if inertia_enabled:
|
|
1169
|
+
return "hybrid"
|
|
1170
|
+
|
|
1171
|
+
if any(path.exists() for path in self.candidate_index_html_paths()):
|
|
1172
|
+
return "spa"
|
|
1173
|
+
|
|
1174
|
+
if JINJA_INSTALLED:
|
|
1175
|
+
return "template"
|
|
1176
|
+
|
|
1177
|
+
return "spa"
|
|
1178
|
+
|
|
1179
|
+
def validate_mode(self) -> None:
|
|
1180
|
+
"""Validate the mode configuration against the project structure.
|
|
1181
|
+
|
|
1182
|
+
Raises:
|
|
1183
|
+
ValueError: If the configuration is invalid for the selected mode.
|
|
1184
|
+
"""
|
|
1185
|
+
if self.mode == "spa":
|
|
1186
|
+
index_candidates = self.candidate_index_html_paths()
|
|
1187
|
+
if not self.runtime.dev_mode and not any(path.exists() for path in index_candidates):
|
|
1188
|
+
joined_paths = ", ".join(str(path) for path in index_candidates)
|
|
1189
|
+
msg = (
|
|
1190
|
+
"SPA mode requires index.html at one of: "
|
|
1191
|
+
f"{joined_paths}. "
|
|
1192
|
+
"Either create the file, run in dev mode, or switch to template mode."
|
|
1193
|
+
)
|
|
1194
|
+
raise ValueError(msg)
|
|
1195
|
+
|
|
1196
|
+
elif self.mode == "hybrid":
|
|
1197
|
+
index_candidates = self.candidate_index_html_paths()
|
|
1198
|
+
if not self.runtime.dev_mode and not any(path.exists() for path in index_candidates):
|
|
1199
|
+
joined_paths = ", ".join(str(path) for path in index_candidates)
|
|
1200
|
+
msg = (
|
|
1201
|
+
"Hybrid mode requires index.html at one of: "
|
|
1202
|
+
f"{joined_paths}. "
|
|
1203
|
+
"Either create the file or run in dev mode."
|
|
1204
|
+
)
|
|
1205
|
+
raise ValueError(msg)
|
|
1206
|
+
|
|
1207
|
+
elif self.mode in {"template", "htmx"}:
|
|
1208
|
+
if not JINJA_INSTALLED:
|
|
1209
|
+
msg = (
|
|
1210
|
+
f"{self.mode} mode requires Jinja2 to be installed. "
|
|
1211
|
+
"Install it with: pip install litestar-vite[jinja]"
|
|
1212
|
+
)
|
|
1213
|
+
raise ValueError(msg)
|
|
1214
|
+
|
|
1215
|
+
@property
|
|
1216
|
+
def executor(self) -> "JSExecutor":
|
|
1217
|
+
"""Get the JavaScript executor instance.
|
|
1218
|
+
|
|
1219
|
+
Returns:
|
|
1220
|
+
The configured JavaScript executor.
|
|
1221
|
+
"""
|
|
1222
|
+
if self._executor_instance is None:
|
|
1223
|
+
self._executor_instance = self._create_executor()
|
|
1224
|
+
return self._executor_instance
|
|
1225
|
+
|
|
1226
|
+
def reset_executor(self) -> None:
|
|
1227
|
+
"""Reset the cached executor instance.
|
|
1228
|
+
|
|
1229
|
+
Call this after modifying logging config to pick up new settings.
|
|
1230
|
+
"""
|
|
1231
|
+
self._executor_instance = None
|
|
1232
|
+
|
|
1233
|
+
def _create_executor(self) -> "JSExecutor":
|
|
1234
|
+
"""Create the appropriate executor based on runtime config.
|
|
1235
|
+
|
|
1236
|
+
Returns:
|
|
1237
|
+
An instance of the selected JSExecutor.
|
|
1238
|
+
"""
|
|
1239
|
+
from litestar_vite.executor import (
|
|
1240
|
+
BunExecutor,
|
|
1241
|
+
DenoExecutor,
|
|
1242
|
+
NodeenvExecutor,
|
|
1243
|
+
NodeExecutor,
|
|
1244
|
+
PnpmExecutor,
|
|
1245
|
+
YarnExecutor,
|
|
1246
|
+
)
|
|
1247
|
+
|
|
1248
|
+
executor_type = self.runtime.executor or "node"
|
|
1249
|
+
silent = self.logging_config.suppress_npm_output
|
|
1250
|
+
|
|
1251
|
+
if executor_type == "bun":
|
|
1252
|
+
return BunExecutor(silent=silent)
|
|
1253
|
+
if executor_type == "deno":
|
|
1254
|
+
return DenoExecutor(silent=silent)
|
|
1255
|
+
if executor_type == "yarn":
|
|
1256
|
+
return YarnExecutor(silent=silent)
|
|
1257
|
+
if executor_type == "pnpm":
|
|
1258
|
+
return PnpmExecutor(silent=silent)
|
|
1259
|
+
if self.runtime.detect_nodeenv:
|
|
1260
|
+
return NodeenvExecutor(self, silent=silent)
|
|
1261
|
+
return NodeExecutor(silent=silent)
|
|
1262
|
+
|
|
1263
|
+
@property
|
|
1264
|
+
def bundle_dir(self) -> Path:
|
|
1265
|
+
"""Get bundle directory path.
|
|
1266
|
+
|
|
1267
|
+
Returns:
|
|
1268
|
+
The configured bundle directory path.
|
|
1269
|
+
"""
|
|
1270
|
+
return self.paths.bundle_dir if isinstance(self.paths.bundle_dir, Path) else Path(self.paths.bundle_dir)
|
|
1271
|
+
|
|
1272
|
+
@property
|
|
1273
|
+
def resource_dir(self) -> Path:
|
|
1274
|
+
"""Get resource directory path.
|
|
1275
|
+
|
|
1276
|
+
Returns:
|
|
1277
|
+
The configured resource directory path.
|
|
1278
|
+
"""
|
|
1279
|
+
return self.paths.resource_dir if isinstance(self.paths.resource_dir, Path) else Path(self.paths.resource_dir)
|
|
1280
|
+
|
|
1281
|
+
@property
|
|
1282
|
+
def static_dir(self) -> Path:
|
|
1283
|
+
"""Get static directory path.
|
|
1284
|
+
|
|
1285
|
+
Returns:
|
|
1286
|
+
The configured static directory path.
|
|
1287
|
+
"""
|
|
1288
|
+
return self.paths.static_dir if isinstance(self.paths.static_dir, Path) else Path(self.paths.static_dir)
|
|
1289
|
+
|
|
1290
|
+
@property
|
|
1291
|
+
def root_dir(self) -> Path:
|
|
1292
|
+
"""Get root directory path.
|
|
1293
|
+
|
|
1294
|
+
Returns:
|
|
1295
|
+
The configured project root directory path.
|
|
1296
|
+
"""
|
|
1297
|
+
return self.paths.root if isinstance(self.paths.root, Path) else Path(self.paths.root)
|
|
1298
|
+
|
|
1299
|
+
@property
|
|
1300
|
+
def manifest_name(self) -> str:
|
|
1301
|
+
"""Get manifest file name.
|
|
1302
|
+
|
|
1303
|
+
Returns:
|
|
1304
|
+
The configured Vite manifest filename.
|
|
1305
|
+
"""
|
|
1306
|
+
return self.paths.manifest_name
|
|
1307
|
+
|
|
1308
|
+
@property
|
|
1309
|
+
def hot_file(self) -> str:
|
|
1310
|
+
"""Get hot file name.
|
|
1311
|
+
|
|
1312
|
+
Returns:
|
|
1313
|
+
The configured hotfile filename.
|
|
1314
|
+
"""
|
|
1315
|
+
return self.paths.hot_file
|
|
1316
|
+
|
|
1317
|
+
@property
|
|
1318
|
+
def asset_url(self) -> str:
|
|
1319
|
+
"""Get asset URL.
|
|
1320
|
+
|
|
1321
|
+
Returns:
|
|
1322
|
+
The configured asset URL prefix.
|
|
1323
|
+
"""
|
|
1324
|
+
return self.paths.asset_url
|
|
1325
|
+
|
|
1326
|
+
def _resolve_to_root(self, path: Path) -> Path:
|
|
1327
|
+
"""Resolve a path relative to the configured root directory.
|
|
1328
|
+
|
|
1329
|
+
Returns:
|
|
1330
|
+
The resolved absolute Path.
|
|
1331
|
+
"""
|
|
1332
|
+
|
|
1333
|
+
if path.is_absolute():
|
|
1334
|
+
return path
|
|
1335
|
+
return self.root_dir / path
|
|
1336
|
+
|
|
1337
|
+
def candidate_index_html_paths(self) -> list[Path]:
|
|
1338
|
+
"""Return possible index.html locations for SPA mode detection.
|
|
1339
|
+
|
|
1340
|
+
Order mirrors the JS plugin auto-detection:
|
|
1341
|
+
1. bundle_dir/index.html (for production static builds like Astro/Nuxt/SvelteKit)
|
|
1342
|
+
2. resource_dir/index.html
|
|
1343
|
+
3. root_dir/index.html
|
|
1344
|
+
4. static_dir/index.html
|
|
1345
|
+
|
|
1346
|
+
Returns:
|
|
1347
|
+
A de-duplicated list of candidate index.html paths, ordered by preference.
|
|
1348
|
+
"""
|
|
1349
|
+
|
|
1350
|
+
bundle_dir = self._resolve_to_root(self.bundle_dir)
|
|
1351
|
+
resource_dir = self._resolve_to_root(self.resource_dir)
|
|
1352
|
+
static_dir = self._resolve_to_root(self.static_dir)
|
|
1353
|
+
root_dir = self.root_dir
|
|
1354
|
+
|
|
1355
|
+
candidates = [
|
|
1356
|
+
bundle_dir / "index.html",
|
|
1357
|
+
resource_dir / "index.html",
|
|
1358
|
+
root_dir / "index.html",
|
|
1359
|
+
static_dir / "index.html",
|
|
1360
|
+
]
|
|
1361
|
+
|
|
1362
|
+
unique: list[Path] = []
|
|
1363
|
+
seen: set[Path] = set()
|
|
1364
|
+
for path in candidates:
|
|
1365
|
+
if path in seen:
|
|
1366
|
+
continue
|
|
1367
|
+
seen.add(path)
|
|
1368
|
+
unique.append(path)
|
|
1369
|
+
return unique
|
|
1370
|
+
|
|
1371
|
+
def has_built_assets(self) -> bool:
|
|
1372
|
+
"""Check if production assets exist in the bundle directory.
|
|
1373
|
+
|
|
1374
|
+
Returns:
|
|
1375
|
+
True if manifest.json or built index.html exists in bundle_dir.
|
|
1376
|
+
|
|
1377
|
+
Note:
|
|
1378
|
+
This method checks the bundle_dir (output directory) for built artifacts,
|
|
1379
|
+
NOT source directories. The presence of source index.html in resource_dir
|
|
1380
|
+
does not indicate built assets exist.
|
|
1381
|
+
"""
|
|
1382
|
+
bundle_path = self._resolve_to_root(self.bundle_dir)
|
|
1383
|
+
manifest_path = bundle_path / self.manifest_name
|
|
1384
|
+
index_path = bundle_path / "index.html"
|
|
1385
|
+
|
|
1386
|
+
return manifest_path.exists() or index_path.exists()
|
|
1387
|
+
|
|
1388
|
+
@property
|
|
1389
|
+
def host(self) -> str:
|
|
1390
|
+
"""Get dev server host.
|
|
1391
|
+
|
|
1392
|
+
Returns:
|
|
1393
|
+
The configured Vite dev server host.
|
|
1394
|
+
"""
|
|
1395
|
+
return self.runtime.host
|
|
1396
|
+
|
|
1397
|
+
@property
|
|
1398
|
+
def port(self) -> int:
|
|
1399
|
+
"""Get dev server port.
|
|
1400
|
+
|
|
1401
|
+
Returns:
|
|
1402
|
+
The configured Vite dev server port.
|
|
1403
|
+
"""
|
|
1404
|
+
return self.runtime.port
|
|
1405
|
+
|
|
1406
|
+
@property
|
|
1407
|
+
def protocol(self) -> str:
|
|
1408
|
+
"""Get dev server protocol.
|
|
1409
|
+
|
|
1410
|
+
Returns:
|
|
1411
|
+
The configured Vite dev server protocol.
|
|
1412
|
+
"""
|
|
1413
|
+
return self.runtime.protocol
|
|
1414
|
+
|
|
1415
|
+
@property
|
|
1416
|
+
def hot_reload(self) -> bool:
|
|
1417
|
+
"""Check if hot reload is enabled (derived from dev_mode and proxy_mode).
|
|
1418
|
+
|
|
1419
|
+
HMR requires dev_mode=True AND a Vite-based mode (vite, direct, or proxy/ssr).
|
|
1420
|
+
All modes support HMR since even SSR frameworks use Vite internally.
|
|
1421
|
+
|
|
1422
|
+
Returns:
|
|
1423
|
+
True if hot reload is enabled, otherwise False.
|
|
1424
|
+
"""
|
|
1425
|
+
return self.runtime.dev_mode and self.runtime.proxy_mode in {"vite", "direct", "proxy"}
|
|
1426
|
+
|
|
1427
|
+
@property
|
|
1428
|
+
def is_dev_mode(self) -> bool:
|
|
1429
|
+
"""Check if dev mode is enabled.
|
|
1430
|
+
|
|
1431
|
+
Returns:
|
|
1432
|
+
True if dev mode is enabled, otherwise False.
|
|
1433
|
+
"""
|
|
1434
|
+
return self.runtime.dev_mode
|
|
1435
|
+
|
|
1436
|
+
@property
|
|
1437
|
+
def is_react(self) -> bool:
|
|
1438
|
+
"""Check if React mode is enabled.
|
|
1439
|
+
|
|
1440
|
+
Returns:
|
|
1441
|
+
True if React mode is enabled, otherwise False.
|
|
1442
|
+
"""
|
|
1443
|
+
return self.runtime.is_react
|
|
1444
|
+
|
|
1445
|
+
@property
|
|
1446
|
+
def ssr_enabled(self) -> bool:
|
|
1447
|
+
"""Check if SSR is enabled.
|
|
1448
|
+
|
|
1449
|
+
Returns:
|
|
1450
|
+
True if SSR is enabled, otherwise False.
|
|
1451
|
+
"""
|
|
1452
|
+
return self.runtime.ssr_enabled
|
|
1453
|
+
|
|
1454
|
+
@property
|
|
1455
|
+
def run_command(self) -> list[str]:
|
|
1456
|
+
"""Get the run command.
|
|
1457
|
+
|
|
1458
|
+
Returns:
|
|
1459
|
+
The argv list used to start the Vite dev server.
|
|
1460
|
+
"""
|
|
1461
|
+
return self.runtime.run_command or ["npm", "run", "dev"]
|
|
1462
|
+
|
|
1463
|
+
@property
|
|
1464
|
+
def build_command(self) -> list[str]:
|
|
1465
|
+
"""Get the build command.
|
|
1466
|
+
|
|
1467
|
+
Returns:
|
|
1468
|
+
The argv list used to build production assets.
|
|
1469
|
+
"""
|
|
1470
|
+
return self.runtime.build_command or ["npm", "run", "build"]
|
|
1471
|
+
|
|
1472
|
+
@property
|
|
1473
|
+
def build_watch_command(self) -> list[str]:
|
|
1474
|
+
"""Get the watch command for building frontend in watch mode.
|
|
1475
|
+
|
|
1476
|
+
Used by `litestar assets serve` when hot_reload is disabled.
|
|
1477
|
+
|
|
1478
|
+
Returns:
|
|
1479
|
+
The command argv list used for watch builds.
|
|
1480
|
+
"""
|
|
1481
|
+
return self.runtime.build_watch_command or ["npm", "run", "build", "--", "--watch"]
|
|
1482
|
+
|
|
1483
|
+
@property
|
|
1484
|
+
def serve_command(self) -> "list[str] | None":
|
|
1485
|
+
"""Get the serve command for running production server.
|
|
1486
|
+
|
|
1487
|
+
Used by `litestar assets serve --production` for SSR frameworks.
|
|
1488
|
+
Returns None if not configured.
|
|
1489
|
+
|
|
1490
|
+
Returns:
|
|
1491
|
+
The command argv list used to serve production assets, or None if not configured.
|
|
1492
|
+
"""
|
|
1493
|
+
return self.runtime.serve_command
|
|
1494
|
+
|
|
1495
|
+
@property
|
|
1496
|
+
def install_command(self) -> list[str]:
|
|
1497
|
+
"""Get the install command.
|
|
1498
|
+
|
|
1499
|
+
Returns:
|
|
1500
|
+
The argv list used to install frontend dependencies.
|
|
1501
|
+
"""
|
|
1502
|
+
return self.runtime.install_command or ["npm", "install"]
|
|
1503
|
+
|
|
1504
|
+
@property
|
|
1505
|
+
def health_check(self) -> bool:
|
|
1506
|
+
"""Check if health check is enabled.
|
|
1507
|
+
|
|
1508
|
+
Returns:
|
|
1509
|
+
True if health checks are enabled, otherwise False.
|
|
1510
|
+
"""
|
|
1511
|
+
return self.runtime.health_check
|
|
1512
|
+
|
|
1513
|
+
@property
|
|
1514
|
+
def set_environment(self) -> bool:
|
|
1515
|
+
"""Check if environment should be set.
|
|
1516
|
+
|
|
1517
|
+
Returns:
|
|
1518
|
+
True if Vite environment variables should be set, otherwise False.
|
|
1519
|
+
"""
|
|
1520
|
+
return self.runtime.set_environment
|
|
1521
|
+
|
|
1522
|
+
@property
|
|
1523
|
+
def set_static_folders(self) -> bool:
|
|
1524
|
+
"""Check if static folders should be configured.
|
|
1525
|
+
|
|
1526
|
+
Returns:
|
|
1527
|
+
True if static folders should be configured, otherwise False.
|
|
1528
|
+
"""
|
|
1529
|
+
return self.runtime.set_static_folders
|
|
1530
|
+
|
|
1531
|
+
@property
|
|
1532
|
+
def detect_nodeenv(self) -> bool:
|
|
1533
|
+
"""Check if nodeenv detection is enabled.
|
|
1534
|
+
|
|
1535
|
+
Returns:
|
|
1536
|
+
True if nodeenv detection is enabled, otherwise False.
|
|
1537
|
+
"""
|
|
1538
|
+
return self.runtime.detect_nodeenv
|
|
1539
|
+
|
|
1540
|
+
@property
|
|
1541
|
+
def proxy_mode(self) -> "Literal['vite', 'direct', 'proxy'] | None":
|
|
1542
|
+
"""Get proxy mode.
|
|
1543
|
+
|
|
1544
|
+
Returns:
|
|
1545
|
+
The configured proxy mode, or None if proxying is disabled.
|
|
1546
|
+
"""
|
|
1547
|
+
return self.runtime.proxy_mode
|
|
1548
|
+
|
|
1549
|
+
@property
|
|
1550
|
+
def external_dev_server(self) -> "ExternalDevServer | None":
|
|
1551
|
+
"""Get external dev server config.
|
|
1552
|
+
|
|
1553
|
+
Returns:
|
|
1554
|
+
External dev server configuration, or None if not configured.
|
|
1555
|
+
"""
|
|
1556
|
+
if isinstance(self.runtime.external_dev_server, ExternalDevServer):
|
|
1557
|
+
return self.runtime.external_dev_server
|
|
1558
|
+
return None
|
|
1559
|
+
|
|
1560
|
+
@property
|
|
1561
|
+
def spa_handler(self) -> bool:
|
|
1562
|
+
"""Check if SPA handler auto-registration is enabled.
|
|
1563
|
+
|
|
1564
|
+
Returns:
|
|
1565
|
+
True if the SPA handler should be auto-registered, otherwise False.
|
|
1566
|
+
"""
|
|
1567
|
+
return self.runtime.spa_handler
|
|
1568
|
+
|
|
1569
|
+
@property
|
|
1570
|
+
def http2(self) -> bool:
|
|
1571
|
+
"""Check if HTTP/2 is enabled for proxy connections.
|
|
1572
|
+
|
|
1573
|
+
Returns:
|
|
1574
|
+
True if HTTP/2 is enabled for proxy connections, otherwise False.
|
|
1575
|
+
"""
|
|
1576
|
+
return self.runtime.http2
|
|
1577
|
+
|
|
1578
|
+
@property
|
|
1579
|
+
def ssr_output_dir(self) -> "Path | None":
|
|
1580
|
+
"""Get SSR output directory.
|
|
1581
|
+
|
|
1582
|
+
Returns:
|
|
1583
|
+
The configured SSR output directory, or None if not configured.
|
|
1584
|
+
"""
|
|
1585
|
+
if self.paths.ssr_output_dir is None:
|
|
1586
|
+
return None
|
|
1587
|
+
return (
|
|
1588
|
+
self.paths.ssr_output_dir
|
|
1589
|
+
if isinstance(self.paths.ssr_output_dir, Path)
|
|
1590
|
+
else Path(self.paths.ssr_output_dir)
|
|
1591
|
+
)
|
|
1592
|
+
|
|
1593
|
+
@property
|
|
1594
|
+
def spa_config(self) -> "SPAConfig | None":
|
|
1595
|
+
"""Get SPA configuration if enabled, or None if disabled.
|
|
1596
|
+
|
|
1597
|
+
Returns:
|
|
1598
|
+
SPAConfig instance if spa transformations are enabled, None otherwise.
|
|
1599
|
+
"""
|
|
1600
|
+
if isinstance(self.spa, SPAConfig):
|
|
1601
|
+
return self.spa
|
|
1602
|
+
return None
|
|
1603
|
+
|
|
1604
|
+
@property
|
|
1605
|
+
def deploy_config(self) -> "DeployConfig | None":
|
|
1606
|
+
"""Get deploy configuration if enabled.
|
|
1607
|
+
|
|
1608
|
+
Returns:
|
|
1609
|
+
DeployConfig instance when deployment is configured, None otherwise.
|
|
1610
|
+
"""
|
|
1611
|
+
if isinstance(self.deploy, DeployConfig) and self.deploy.enabled:
|
|
1612
|
+
return self.deploy
|
|
1613
|
+
return None
|
|
1614
|
+
|
|
1615
|
+
@property
|
|
1616
|
+
def logging_config(self) -> LoggingConfig:
|
|
1617
|
+
"""Get logging configuration.
|
|
1618
|
+
|
|
1619
|
+
Returns:
|
|
1620
|
+
LoggingConfig instance (always available after normalization).
|
|
1621
|
+
"""
|
|
1622
|
+
if isinstance(self.logging, LoggingConfig):
|
|
1623
|
+
return self.logging
|
|
1624
|
+
return LoggingConfig()
|