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/cli.py
CHANGED
|
@@ -1,24 +1,1161 @@
|
|
|
1
|
-
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from textwrap import dedent
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
import msgspec
|
|
9
|
+
from click import Choice, Context, group, option
|
|
10
|
+
from click import Path as ClickPath
|
|
11
|
+
from litestar.cli._utils import ( # pyright: ignore[reportPrivateImportUsage]
|
|
12
|
+
LitestarCLIException,
|
|
13
|
+
LitestarEnv,
|
|
14
|
+
LitestarGroup,
|
|
15
|
+
console,
|
|
16
|
+
)
|
|
17
|
+
from litestar.exceptions import SerializationException
|
|
18
|
+
from litestar.serialization import decode_json, encode_json, get_serializer
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
from rich.prompt import Confirm, Prompt
|
|
4
21
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
22
|
+
from litestar_vite.codegen import generate_inertia_pages_json, generate_routes_json, generate_routes_ts
|
|
23
|
+
from litestar_vite.config import (
|
|
24
|
+
DeployConfig,
|
|
25
|
+
ExternalDevServer,
|
|
26
|
+
InertiaConfig,
|
|
27
|
+
InertiaTypeGenConfig,
|
|
28
|
+
LoggingConfig,
|
|
29
|
+
TypeGenConfig,
|
|
30
|
+
ViteConfig,
|
|
31
|
+
)
|
|
32
|
+
from litestar_vite.deploy import ViteDeployer, format_bytes
|
|
33
|
+
from litestar_vite.doctor import ViteDoctor
|
|
34
|
+
from litestar_vite.exceptions import ViteExecutionError
|
|
35
|
+
from litestar_vite.plugin import VitePlugin, resolve_litestar_version, set_environment
|
|
36
|
+
from litestar_vite.scaffolding import TemplateContext, generate_project, get_available_templates
|
|
37
|
+
from litestar_vite.scaffolding.templates import FrameworkType, get_template
|
|
7
38
|
|
|
8
39
|
if TYPE_CHECKING:
|
|
9
40
|
from litestar import Litestar
|
|
10
41
|
|
|
11
42
|
|
|
43
|
+
FRAMEWORK_CHOICES = [
|
|
44
|
+
"react",
|
|
45
|
+
"react-router",
|
|
46
|
+
"react-tanstack",
|
|
47
|
+
"react-inertia",
|
|
48
|
+
"vue",
|
|
49
|
+
"vue-inertia",
|
|
50
|
+
"svelte",
|
|
51
|
+
"svelte-inertia",
|
|
52
|
+
"sveltekit",
|
|
53
|
+
"nuxt",
|
|
54
|
+
"astro",
|
|
55
|
+
"htmx",
|
|
56
|
+
"angular",
|
|
57
|
+
"angular-cli",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _format_command(command: "list[str] | None") -> str:
|
|
62
|
+
"""Join a command list for display.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Space-joined command string or empty string.
|
|
66
|
+
"""
|
|
67
|
+
return " ".join(command or [])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _apply_cli_log_level(config: ViteConfig, *, verbose: bool = False, quiet: bool = False) -> None:
|
|
71
|
+
"""Apply CLI log level overrides to the config.
|
|
72
|
+
|
|
73
|
+
Precedence: --quiet > --verbose > config/env
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
config: The ViteConfig to modify.
|
|
77
|
+
verbose: If True, set log level to "verbose".
|
|
78
|
+
quiet: If True, set log level to "quiet" (takes precedence over verbose).
|
|
79
|
+
"""
|
|
80
|
+
if quiet:
|
|
81
|
+
config.logging = LoggingConfig(
|
|
82
|
+
level="quiet",
|
|
83
|
+
show_paths_absolute=config.logging_config.show_paths_absolute,
|
|
84
|
+
suppress_npm_output=config.logging_config.suppress_npm_output,
|
|
85
|
+
suppress_vite_banner=config.logging_config.suppress_vite_banner,
|
|
86
|
+
timestamps=config.logging_config.timestamps,
|
|
87
|
+
)
|
|
88
|
+
config.reset_executor()
|
|
89
|
+
elif verbose:
|
|
90
|
+
config.logging = LoggingConfig(
|
|
91
|
+
level="verbose",
|
|
92
|
+
show_paths_absolute=config.logging_config.show_paths_absolute,
|
|
93
|
+
suppress_npm_output=config.logging_config.suppress_npm_output,
|
|
94
|
+
suppress_vite_banner=config.logging_config.suppress_vite_banner,
|
|
95
|
+
timestamps=config.logging_config.timestamps,
|
|
96
|
+
)
|
|
97
|
+
config.reset_executor()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _relative_path(path: Path) -> str:
|
|
101
|
+
"""Return path relative to CWD when possible.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
The relative path string when possible, otherwise the absolute path string.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
return str(path.relative_to(Path.cwd()))
|
|
109
|
+
except ValueError:
|
|
110
|
+
return str(path)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _print_recommended_config(template_name: str, resource_dir: str, bundle_dir: str) -> None:
|
|
114
|
+
"""Print recommended ViteConfig for the scaffolded template.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
template_name: The name of the template that was scaffolded.
|
|
118
|
+
resource_dir: The resource directory used.
|
|
119
|
+
bundle_dir: The bundle directory used.
|
|
120
|
+
"""
|
|
121
|
+
spa_templates = {"react-router", "react-tanstack"}
|
|
122
|
+
mode = "spa" if template_name in spa_templates else "template"
|
|
123
|
+
|
|
124
|
+
config_snippet = dedent(
|
|
125
|
+
f"""\
|
|
126
|
+
from pathlib import Path
|
|
127
|
+
from litestar_vite import ViteConfig, PathConfig
|
|
128
|
+
|
|
129
|
+
vite_config = ViteConfig(
|
|
130
|
+
mode="{mode}",
|
|
131
|
+
dev_mode=True,
|
|
132
|
+
types=True,
|
|
133
|
+
paths=PathConfig(
|
|
134
|
+
root=Path(__file__).parent,
|
|
135
|
+
resource_dir="{resource_dir}",
|
|
136
|
+
bundle_dir="{bundle_dir}",
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
"""
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
console.print("\n[bold cyan]Recommended ViteConfig:[/]")
|
|
143
|
+
console.print(Panel(config_snippet, title="app.py", border_style="dim"))
|
|
144
|
+
console.print("[dim]Note: set dev_mode=False in production; set types=False to disable TypeScript generation.[/]")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _coerce_option_value(value: str) -> object:
|
|
148
|
+
"""Convert CLI key/value strings into basic Python types.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Converted value (bool, int, float, or original string).
|
|
152
|
+
"""
|
|
153
|
+
lowered = value.lower()
|
|
154
|
+
if lowered in {"true", "false"}:
|
|
155
|
+
return lowered == "true"
|
|
156
|
+
if value.isdigit():
|
|
157
|
+
return int(value)
|
|
158
|
+
try:
|
|
159
|
+
return float(value)
|
|
160
|
+
except ValueError:
|
|
161
|
+
return value
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _parse_storage_options(values: tuple[str, ...]) -> dict[str, object]:
|
|
165
|
+
"""Parse repeated --storage-option entries into a dictionary.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Dictionary of parsed storage options.
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
ValueError: If an option is not in key=value format.
|
|
172
|
+
"""
|
|
173
|
+
options: dict[str, object] = {}
|
|
174
|
+
for item in values:
|
|
175
|
+
if "=" not in item:
|
|
176
|
+
msg = f"Invalid storage option '{item}'. Expected key=value."
|
|
177
|
+
raise ValueError(msg)
|
|
178
|
+
key, raw = item.split("=", 1)
|
|
179
|
+
options[key] = _coerce_option_value(raw)
|
|
180
|
+
return options
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _build_deploy_config(
|
|
184
|
+
base_config: ViteConfig, storage: str | None, storage_options: dict[str, object], no_delete: bool
|
|
185
|
+
) -> DeployConfig:
|
|
186
|
+
"""Resolve deploy configuration from CLI overrides.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Resolved DeployConfig with CLI overrides applied.
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
SystemExit: If deployment is not configured or storage backend is missing.
|
|
193
|
+
"""
|
|
194
|
+
deploy_config = base_config.deploy_config
|
|
195
|
+
if deploy_config is None:
|
|
196
|
+
msg = "Deployment is not configured. Set ViteConfig.deploy to enable."
|
|
197
|
+
raise SystemExit(msg)
|
|
198
|
+
|
|
199
|
+
merged_options = {**deploy_config.storage_options, **storage_options}
|
|
200
|
+
deploy_config = deploy_config.with_overrides(
|
|
201
|
+
storage_backend=storage, storage_options=merged_options, delete_orphaned=False if no_delete else None
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if not deploy_config.storage_backend:
|
|
205
|
+
msg = "Storage backend is required (e.g., gcs://bucket/assets)."
|
|
206
|
+
raise SystemExit(msg)
|
|
207
|
+
|
|
208
|
+
return deploy_config
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _run_vite_build(config: ViteConfig, root_dir: Path, console: Any, no_build: bool) -> None:
|
|
212
|
+
"""Run Vite build unless skipped.
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
SystemExit: If the build fails.
|
|
216
|
+
"""
|
|
217
|
+
if no_build:
|
|
218
|
+
console.print("[dim]Skipping Vite build (--no-build).[/]")
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
console.rule("[yellow]Starting Vite build process[/]", align="left")
|
|
222
|
+
if config.set_environment:
|
|
223
|
+
set_environment(config=config, asset_url_override=config.base_url or config.asset_url)
|
|
224
|
+
os.environ["VITE_BASE_URL"] = config.base_url or config.asset_url or "/"
|
|
225
|
+
try:
|
|
226
|
+
config.executor.execute(config.build_command, cwd=root_dir)
|
|
227
|
+
console.print("[bold green]✓ Build complete[/]")
|
|
228
|
+
except ViteExecutionError as exc:
|
|
229
|
+
msg = f"Build failed: {exc!s}"
|
|
230
|
+
raise SystemExit(msg) from exc
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _generate_schema_and_routes(app: "Litestar", config: ViteConfig, console: Any) -> None:
|
|
234
|
+
"""Export OpenAPI schema, routes, and Inertia page props prior to running a build.
|
|
235
|
+
|
|
236
|
+
Skips generation when type generation is disabled.
|
|
237
|
+
|
|
238
|
+
When Inertia page-props export is enabled, the generator may add additional component schemas
|
|
239
|
+
for routes that are excluded from OpenAPI. In that case, this function persists the augmented
|
|
240
|
+
OpenAPI document so the TypeScript generator can emit those types.
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
LitestarCLIException: If export fails.
|
|
244
|
+
"""
|
|
245
|
+
types_config = config.types
|
|
246
|
+
if not isinstance(types_config, TypeGenConfig):
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
console.print("[dim]Preparing OpenAPI schema and routes...[/]")
|
|
250
|
+
_export_openapi_schema(app, types_config)
|
|
251
|
+
_export_routes_metadata(app, types_config)
|
|
252
|
+
|
|
253
|
+
if types_config.generate_routes:
|
|
254
|
+
console.print("[dim]3. Exporting typed routes...[/]")
|
|
255
|
+
try:
|
|
256
|
+
routes_ts_content = generate_routes_ts(app)
|
|
257
|
+
routes_ts_path = types_config.routes_ts_path or (types_config.output / "routes.ts")
|
|
258
|
+
routes_ts_path.parent.mkdir(parents=True, exist_ok=True)
|
|
259
|
+
routes_ts_path.write_text(routes_ts_content, encoding="utf-8")
|
|
260
|
+
console.print(f"[green]✓ Typed routes exported to {_relative_path(routes_ts_path)}[/]")
|
|
261
|
+
except OSError as exc: # pragma: no cover
|
|
262
|
+
msg = f"Failed to export typed routes: {exc}"
|
|
263
|
+
raise LitestarCLIException(msg) from exc
|
|
264
|
+
|
|
265
|
+
if isinstance(config.inertia, InertiaConfig) and types_config.generate_page_props and types_config.page_props_path:
|
|
266
|
+
openapi_schema: dict[str, Any] | None = None
|
|
267
|
+
try:
|
|
268
|
+
if types_config.openapi_path and types_config.openapi_path.exists():
|
|
269
|
+
openapi_schema = decode_json(types_config.openapi_path.read_bytes())
|
|
270
|
+
except (OSError, SerializationException):
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
_export_inertia_pages_metadata(app, types_config, config.inertia, openapi_schema)
|
|
274
|
+
if openapi_schema is not None and types_config.openapi_path is not None:
|
|
275
|
+
try:
|
|
276
|
+
schema_content = msgspec.json.format(encode_json(openapi_schema), indent=2)
|
|
277
|
+
types_config.openapi_path.parent.mkdir(parents=True, exist_ok=True)
|
|
278
|
+
types_config.openapi_path.write_bytes(schema_content)
|
|
279
|
+
except OSError as exc: # pragma: no cover
|
|
280
|
+
msg = f"Failed to update OpenAPI schema: {exc}"
|
|
281
|
+
raise LitestarCLIException(msg) from exc
|
|
282
|
+
|
|
283
|
+
|
|
12
284
|
@group(cls=LitestarGroup, name="assets")
|
|
13
285
|
def vite_group() -> None:
|
|
14
286
|
"""Manage Vite Tasks."""
|
|
15
287
|
|
|
16
288
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
289
|
+
def _select_framework_template(template: "str | None", no_prompt: bool) -> "tuple[str, Any]":
|
|
290
|
+
"""Select and validate the framework template.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
template: User-provided template name or None.
|
|
294
|
+
no_prompt: Whether to skip interactive prompts.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Tuple of (template_name, framework_template).
|
|
298
|
+
|
|
299
|
+
Notes:
|
|
300
|
+
When ``no_prompt=True`` and no template is provided, defaults to the ``react`` template.
|
|
301
|
+
"""
|
|
302
|
+
if template is None and not no_prompt:
|
|
303
|
+
available = get_available_templates()
|
|
304
|
+
console.print("\n[bold]Available framework templates:[/]")
|
|
305
|
+
for i, tmpl in enumerate(available, 1):
|
|
306
|
+
console.print(f" {i}. [cyan]{tmpl.type.value}[/] - {tmpl.description}")
|
|
307
|
+
|
|
308
|
+
template = Prompt.ask(
|
|
309
|
+
"\nSelect a framework template", choices=[t.type.value for t in available], default="react"
|
|
310
|
+
)
|
|
311
|
+
elif template is None:
|
|
312
|
+
template = "react"
|
|
313
|
+
|
|
314
|
+
framework = get_template(template)
|
|
315
|
+
if framework is None:
|
|
316
|
+
console.print(f"[red]Unknown template: {template}[/]")
|
|
317
|
+
sys.exit(1)
|
|
318
|
+
|
|
319
|
+
return template, framework
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _prompt_for_options(
|
|
323
|
+
framework: "Any",
|
|
324
|
+
enable_ssr: "bool | None",
|
|
325
|
+
tailwind: bool,
|
|
326
|
+
enable_types: bool,
|
|
327
|
+
generate_zod: bool,
|
|
328
|
+
generate_client: bool,
|
|
329
|
+
no_prompt: bool,
|
|
330
|
+
) -> "tuple[bool, bool, bool, bool, bool]":
|
|
331
|
+
"""Prompt user for optional features if not specified.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
framework: The framework template.
|
|
335
|
+
enable_ssr: SSR flag or None.
|
|
336
|
+
tailwind: TailwindCSS flag.
|
|
337
|
+
enable_types: Type generation flag.
|
|
338
|
+
generate_zod: Zod schema generation flag.
|
|
339
|
+
generate_client: API client generation flag.
|
|
340
|
+
no_prompt: Whether to skip prompts.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Tuple of (enable_ssr, tailwind, enable_types, generate_zod, generate_client).
|
|
344
|
+
"""
|
|
345
|
+
if enable_ssr is None:
|
|
346
|
+
enable_ssr = (
|
|
347
|
+
framework.has_ssr if no_prompt else Confirm.ask("Enable server-side rendering?", default=framework.has_ssr)
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
if not tailwind and not no_prompt:
|
|
351
|
+
tailwind = Confirm.ask("Add TailwindCSS?", default=False)
|
|
352
|
+
|
|
353
|
+
if not enable_types and not no_prompt:
|
|
354
|
+
enable_types = Confirm.ask("Enable TypeScript type generation?", default=True)
|
|
355
|
+
|
|
356
|
+
if enable_types:
|
|
357
|
+
if not generate_zod and not no_prompt:
|
|
358
|
+
generate_zod = Confirm.ask("Generate Zod schemas for validation?", default=False)
|
|
359
|
+
|
|
360
|
+
if not generate_client and not no_prompt:
|
|
361
|
+
generate_client = Confirm.ask("Generate API client?", default=True)
|
|
362
|
+
else:
|
|
363
|
+
generate_zod = False
|
|
364
|
+
generate_client = False
|
|
365
|
+
|
|
366
|
+
return enable_ssr or False, tailwind, enable_types, generate_zod, generate_client
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@vite_group.command(name="doctor", help="Diagnose and fix Vite configuration issues.")
|
|
370
|
+
@option("--check", is_flag=True, help="Exit with non-zero status if errors are found (for CI).")
|
|
371
|
+
@option("--fix", is_flag=True, help="Auto-fix detected issues (with confirmation).")
|
|
372
|
+
@option("--no-prompt", is_flag=True, help="Apply fixes without confirmation.")
|
|
373
|
+
@option("--verbose", is_flag=True, help="Enable verbose output.")
|
|
374
|
+
@option("--show-config", is_flag=True, help="Print .litestar.json and extracted litestar({ ... }) config block.")
|
|
375
|
+
@option("--runtime-checks", is_flag=True, help="Run runtime-state checks (Vite reachable / hotfile presence).")
|
|
376
|
+
def vite_doctor(
|
|
377
|
+
app: "Litestar", check: bool, fix: bool, no_prompt: bool, verbose: bool, show_config: bool, runtime_checks: bool
|
|
378
|
+
) -> None:
|
|
379
|
+
"""Diagnose and fix Vite configuration issues."""
|
|
380
|
+
if verbose:
|
|
381
|
+
app.debug = True
|
|
382
|
+
|
|
383
|
+
plugin = app.plugins.get(VitePlugin)
|
|
384
|
+
doctor = ViteDoctor(plugin.config, verbose=verbose)
|
|
385
|
+
|
|
386
|
+
success = doctor.run(fix=fix, no_prompt=no_prompt, show_config=show_config, runtime_checks=runtime_checks)
|
|
387
|
+
|
|
388
|
+
if check and not success:
|
|
389
|
+
sys.exit(1)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
@vite_group.command(name="init", help="Initialize vite for your project.")
|
|
393
|
+
@option(
|
|
394
|
+
"--template",
|
|
395
|
+
type=Choice(FRAMEWORK_CHOICES, case_sensitive=False),
|
|
396
|
+
help="Frontend framework template to use. Inertia variants available: react-inertia, vue-inertia, svelte-inertia.",
|
|
397
|
+
default=None,
|
|
398
|
+
required=False,
|
|
399
|
+
)
|
|
400
|
+
@option(
|
|
401
|
+
"--root-path",
|
|
402
|
+
type=ClickPath(dir_okay=True, file_okay=False, path_type=Path),
|
|
403
|
+
help="The path for the initial the Vite application. This is the equivalent to the top-level folder in a normal Vue or React application..",
|
|
404
|
+
default=None,
|
|
405
|
+
required=False,
|
|
406
|
+
)
|
|
407
|
+
@option(
|
|
408
|
+
"--frontend-dir",
|
|
409
|
+
type=str,
|
|
410
|
+
help="Optional subdirectory under root to place the generated frontend (e.g., 'web').",
|
|
411
|
+
default=".",
|
|
412
|
+
required=False,
|
|
413
|
+
)
|
|
414
|
+
@option(
|
|
415
|
+
"--bundle-path",
|
|
416
|
+
type=ClickPath(dir_okay=True, file_okay=False, path_type=Path),
|
|
417
|
+
help="The path for the built Vite assets. This is the where the output of `npm run build` will write files.",
|
|
418
|
+
default=None,
|
|
419
|
+
required=False,
|
|
420
|
+
)
|
|
421
|
+
@option(
|
|
422
|
+
"--resource-path",
|
|
423
|
+
type=ClickPath(dir_okay=True, file_okay=False, path_type=Path),
|
|
424
|
+
help="The path to your Javascript/Typescript source and associated assets. If this were a standalone Vue or React app, this would point to your `src/` folder.",
|
|
425
|
+
default=None,
|
|
426
|
+
required=False,
|
|
20
427
|
)
|
|
428
|
+
@option(
|
|
429
|
+
"--static-path",
|
|
430
|
+
type=ClickPath(dir_okay=True, file_okay=False, path_type=Path),
|
|
431
|
+
help="The optional path to your static (unprocessed) frontend assets. If this were a standalone Vite app, this would point to your `public/` folder.",
|
|
432
|
+
default=None,
|
|
433
|
+
required=False,
|
|
434
|
+
)
|
|
435
|
+
@option("--asset-url", type=str, help="Base url to serve assets from.", default=None, required=False)
|
|
436
|
+
@option("--vite-port", type=int, help="The port to run the vite server against.", default=None, required=False)
|
|
437
|
+
@option(
|
|
438
|
+
"--enable-ssr",
|
|
439
|
+
"enable_ssr",
|
|
440
|
+
flag_value=True,
|
|
441
|
+
default=None,
|
|
442
|
+
required=False,
|
|
443
|
+
show_default=False,
|
|
444
|
+
help="Enable SSR support.",
|
|
445
|
+
)
|
|
446
|
+
@option(
|
|
447
|
+
"--disable-ssr",
|
|
448
|
+
"enable_ssr",
|
|
449
|
+
flag_value=False,
|
|
450
|
+
default=None,
|
|
451
|
+
required=False,
|
|
452
|
+
show_default=False,
|
|
453
|
+
help="Disable SSR support.",
|
|
454
|
+
)
|
|
455
|
+
@option(
|
|
456
|
+
"--tailwind", type=bool, help="Add TailwindCSS to the project.", required=False, show_default=False, is_flag=True
|
|
457
|
+
)
|
|
458
|
+
@option(
|
|
459
|
+
"--enable-types",
|
|
460
|
+
type=bool,
|
|
461
|
+
help="Enable TypeScript type generation from routes.",
|
|
462
|
+
required=False,
|
|
463
|
+
show_default=False,
|
|
464
|
+
is_flag=True,
|
|
465
|
+
)
|
|
466
|
+
@option(
|
|
467
|
+
"--generate-zod",
|
|
468
|
+
type=bool,
|
|
469
|
+
help="Generate Zod schemas for runtime validation.",
|
|
470
|
+
required=False,
|
|
471
|
+
show_default=False,
|
|
472
|
+
is_flag=True,
|
|
473
|
+
)
|
|
474
|
+
@option(
|
|
475
|
+
"--generate-client",
|
|
476
|
+
type=bool,
|
|
477
|
+
help="Generate API client from OpenAPI schema.",
|
|
478
|
+
required=False,
|
|
479
|
+
show_default=False,
|
|
480
|
+
is_flag=True,
|
|
481
|
+
)
|
|
482
|
+
@option("--overwrite", type=bool, help="Overwrite any files in place.", default=False, is_flag=True)
|
|
21
483
|
@option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
|
|
22
|
-
|
|
23
|
-
""
|
|
24
|
-
|
|
484
|
+
@option(
|
|
485
|
+
"--no-prompt",
|
|
486
|
+
help="Do not prompt for confirmation and use all defaults for initializing the project.",
|
|
487
|
+
type=bool,
|
|
488
|
+
default=False,
|
|
489
|
+
required=False,
|
|
490
|
+
show_default=True,
|
|
491
|
+
is_flag=True,
|
|
492
|
+
)
|
|
493
|
+
@option(
|
|
494
|
+
"--no-install",
|
|
495
|
+
help="Do not execute the installation commands after generating templates.",
|
|
496
|
+
type=bool,
|
|
497
|
+
default=False,
|
|
498
|
+
required=False,
|
|
499
|
+
show_default=True,
|
|
500
|
+
is_flag=True,
|
|
501
|
+
)
|
|
502
|
+
def vite_init(
|
|
503
|
+
ctx: "Context",
|
|
504
|
+
template: "str | None",
|
|
505
|
+
vite_port: "int | None",
|
|
506
|
+
enable_ssr: "bool | None",
|
|
507
|
+
asset_url: "str | None",
|
|
508
|
+
root_path: "Path | None",
|
|
509
|
+
frontend_dir: str,
|
|
510
|
+
bundle_path: "Path | None",
|
|
511
|
+
resource_path: "Path | None",
|
|
512
|
+
static_path: "Path | None",
|
|
513
|
+
tailwind: "bool",
|
|
514
|
+
enable_types: "bool",
|
|
515
|
+
generate_zod: "bool",
|
|
516
|
+
generate_client: "bool",
|
|
517
|
+
overwrite: "bool",
|
|
518
|
+
verbose: "bool",
|
|
519
|
+
no_prompt: "bool",
|
|
520
|
+
no_install: "bool",
|
|
521
|
+
) -> None:
|
|
522
|
+
"""Initialize a new Vite project with framework templates."""
|
|
523
|
+
if callable(ctx.obj):
|
|
524
|
+
ctx.obj = ctx.obj()
|
|
525
|
+
elif verbose:
|
|
526
|
+
ctx.obj.app.debug = True
|
|
527
|
+
env: LitestarEnv = ctx.obj
|
|
528
|
+
plugin = env.app.plugins.get(VitePlugin)
|
|
529
|
+
config = plugin._config # pyright: ignore[reportPrivateUsage]
|
|
530
|
+
|
|
531
|
+
console.rule("[yellow]Initializing Vite[/]", align="left")
|
|
532
|
+
|
|
533
|
+
root_path = Path(root_path or config.root_dir or Path.cwd())
|
|
534
|
+
frontend_dir = frontend_dir or "."
|
|
535
|
+
asset_url = asset_url or config.asset_url
|
|
536
|
+
vite_port = vite_port or config.port
|
|
537
|
+
litestar_port = env.port or 8000
|
|
538
|
+
template, framework = _select_framework_template(template, no_prompt)
|
|
539
|
+
console.print(f"\n[green]Using {framework.name} template[/]")
|
|
540
|
+
resource_path_str = str(resource_path or framework.resource_dir or config.resource_dir)
|
|
541
|
+
bundle_path_str = str(bundle_path or config.bundle_dir)
|
|
542
|
+
static_path_str = str(static_path or config.static_dir)
|
|
543
|
+
|
|
544
|
+
if (
|
|
545
|
+
any((root_path / p).exists() for p in [resource_path_str, bundle_path_str, static_path_str])
|
|
546
|
+
and not any([overwrite, no_prompt])
|
|
547
|
+
and not Confirm.ask("Files were found in the paths specified. Are you sure you wish to overwrite the contents?")
|
|
548
|
+
):
|
|
549
|
+
console.print("Skipping Vite initialization")
|
|
550
|
+
sys.exit(2)
|
|
551
|
+
|
|
552
|
+
enable_ssr, tailwind, enable_types, generate_zod, generate_client = _prompt_for_options(
|
|
553
|
+
framework, enable_ssr, tailwind, enable_types, generate_zod, generate_client, no_prompt
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
project_name = root_path.name or "my-project"
|
|
557
|
+
is_inertia = framework.type in {
|
|
558
|
+
FrameworkType.REACT_INERTIA,
|
|
559
|
+
FrameworkType.VUE_INERTIA,
|
|
560
|
+
FrameworkType.SVELTE_INERTIA,
|
|
561
|
+
}
|
|
562
|
+
context = TemplateContext(
|
|
563
|
+
project_name=project_name,
|
|
564
|
+
framework=framework,
|
|
565
|
+
use_typescript=framework.uses_typescript,
|
|
566
|
+
use_tailwind=tailwind,
|
|
567
|
+
vite_port=vite_port,
|
|
568
|
+
litestar_port=litestar_port,
|
|
569
|
+
asset_url=asset_url,
|
|
570
|
+
resource_dir=resource_path_str,
|
|
571
|
+
bundle_dir=bundle_path_str,
|
|
572
|
+
static_dir=static_path_str,
|
|
573
|
+
base_dir=frontend_dir,
|
|
574
|
+
enable_ssr=enable_ssr,
|
|
575
|
+
enable_inertia=is_inertia,
|
|
576
|
+
enable_types=enable_types,
|
|
577
|
+
generate_zod=generate_zod,
|
|
578
|
+
generate_client=generate_client,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
console.print("\n[yellow]Generating project files...[/]")
|
|
582
|
+
generated = generate_project(root_path, context, overwrite=overwrite)
|
|
583
|
+
console.print(f"\n[green]Generated {len(generated)} files[/]")
|
|
584
|
+
|
|
585
|
+
if not no_install:
|
|
586
|
+
console.rule("[yellow]Starting package installation process[/]", align="left")
|
|
587
|
+
config.executor.install(root_path)
|
|
588
|
+
|
|
589
|
+
console.print("\n[bold green]Vite initialization complete![/]")
|
|
590
|
+
|
|
591
|
+
_print_recommended_config(template, context.resource_dir, context.bundle_dir)
|
|
592
|
+
|
|
593
|
+
next_steps_cmd = _format_command(config.run_command)
|
|
594
|
+
console.print(f"\n[dim]Next steps:\n cd {root_path}\n {next_steps_cmd}[/]")
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
@vite_group.command(name="install", help="Install frontend packages.")
|
|
598
|
+
@option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
|
|
599
|
+
@option("--quiet", type=bool, help="Suppress non-essential output.", default=False, is_flag=True)
|
|
600
|
+
def vite_install(app: "Litestar", verbose: "bool", quiet: "bool") -> None:
|
|
601
|
+
"""Install frontend packages."""
|
|
602
|
+
if verbose:
|
|
603
|
+
app.debug = True
|
|
604
|
+
|
|
605
|
+
plugin = app.plugins.get(VitePlugin)
|
|
606
|
+
|
|
607
|
+
_apply_cli_log_level(plugin.config, verbose=verbose, quiet=quiet)
|
|
608
|
+
|
|
609
|
+
if not quiet:
|
|
610
|
+
console.rule("[yellow]Starting package installation process[/]", align="left")
|
|
611
|
+
|
|
612
|
+
if plugin.config.executor:
|
|
613
|
+
root_dir = Path(plugin.config.root_dir or Path.cwd())
|
|
614
|
+
plugin.config.executor.install(root_dir)
|
|
615
|
+
else:
|
|
616
|
+
console.print("[red]Executor not configured.[/]")
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
@vite_group.command(name="build", help="Building frontend assets with Vite.")
|
|
620
|
+
@option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
|
|
621
|
+
@option("--quiet", type=bool, help="Suppress non-essential output.", default=False, is_flag=True)
|
|
622
|
+
def vite_build(app: "Litestar", verbose: "bool", quiet: "bool") -> None:
|
|
623
|
+
"""Run vite build.
|
|
624
|
+
|
|
625
|
+
Raises:
|
|
626
|
+
SystemExit: If the build fails.
|
|
627
|
+
"""
|
|
628
|
+
if verbose:
|
|
629
|
+
app.debug = True
|
|
630
|
+
|
|
631
|
+
plugin = app.plugins.get(VitePlugin)
|
|
632
|
+
|
|
633
|
+
_apply_cli_log_level(plugin.config, verbose=verbose, quiet=quiet)
|
|
634
|
+
|
|
635
|
+
if not quiet:
|
|
636
|
+
console.rule("[yellow]Starting Vite build process[/]", align="left")
|
|
637
|
+
_generate_schema_and_routes(app, plugin.config, console)
|
|
638
|
+
if plugin.config.set_environment:
|
|
639
|
+
set_environment(config=plugin.config)
|
|
640
|
+
|
|
641
|
+
executor = plugin.config.executor
|
|
642
|
+
try:
|
|
643
|
+
root_dir = plugin.config.root_dir or Path.cwd()
|
|
644
|
+
if not (Path(root_dir) / "node_modules").exists():
|
|
645
|
+
console.print("[dim]Installing frontend dependencies (node_modules missing)...[/]")
|
|
646
|
+
executor.install(Path(root_dir))
|
|
647
|
+
ext = plugin.config.runtime.external_dev_server
|
|
648
|
+
if isinstance(ext, ExternalDevServer) and ext.enabled:
|
|
649
|
+
build_cmd = ext.build_command or executor.build_command
|
|
650
|
+
console.print(f"[dim]Running external build: {' '.join(build_cmd)}[/]")
|
|
651
|
+
executor.execute(build_cmd, cwd=root_dir)
|
|
652
|
+
else:
|
|
653
|
+
executor.execute(plugin.config.build_command, cwd=root_dir)
|
|
654
|
+
console.print("[bold green]✓ Assets built[/]")
|
|
655
|
+
except ViteExecutionError as e:
|
|
656
|
+
console.print(f"[bold red]x Asset build failed: {e!s}[/]")
|
|
657
|
+
raise SystemExit(1) from None
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
@vite_group.command(name="deploy", help="Build and deploy Vite assets to remote storage via fsspec.")
|
|
661
|
+
@option("--storage", type=str, help="Override storage backend URL (e.g., gcs://bucket/assets).")
|
|
662
|
+
@option(
|
|
663
|
+
"--storage-option",
|
|
664
|
+
type=str,
|
|
665
|
+
multiple=True,
|
|
666
|
+
help="Storage option key=value forwarded to fsspec (repeat for multiple).",
|
|
667
|
+
)
|
|
668
|
+
@option("--no-build", is_flag=True, help="Deploy existing build without running Vite build.")
|
|
669
|
+
@option("--dry-run", is_flag=True, help="Preview upload/delete plan without making changes.")
|
|
670
|
+
@option("--no-delete", is_flag=True, help="Do not delete orphaned remote files.")
|
|
671
|
+
@option(
|
|
672
|
+
"--verbose",
|
|
673
|
+
is_flag=True,
|
|
674
|
+
help="Enable verbose output. Install providers separately: gcsfs for gcs://, s3fs for s3://, adlfs for abfs://.",
|
|
675
|
+
)
|
|
676
|
+
def vite_deploy(
|
|
677
|
+
app: "Litestar",
|
|
678
|
+
storage: "str | None",
|
|
679
|
+
storage_option: "tuple[str, ...]",
|
|
680
|
+
no_build: bool,
|
|
681
|
+
dry_run: bool,
|
|
682
|
+
no_delete: bool,
|
|
683
|
+
verbose: bool,
|
|
684
|
+
) -> None:
|
|
685
|
+
"""Build and deploy assets to CDN-backed storage."""
|
|
686
|
+
if verbose:
|
|
687
|
+
app.debug = True
|
|
688
|
+
|
|
689
|
+
plugin = app.plugins.get(VitePlugin)
|
|
690
|
+
config = plugin.config
|
|
691
|
+
|
|
692
|
+
try:
|
|
693
|
+
storage_options = _parse_storage_options(storage_option)
|
|
694
|
+
deploy_config = _build_deploy_config(config, storage, storage_options, no_delete)
|
|
695
|
+
except ValueError as exc: # pragma: no cover - CLI validation path
|
|
696
|
+
console.print(f"[red]{exc}[/]")
|
|
697
|
+
sys.exit(1)
|
|
698
|
+
except SystemExit as exc:
|
|
699
|
+
console.print(f"[red]{exc}[/]")
|
|
700
|
+
sys.exit(1)
|
|
701
|
+
|
|
702
|
+
root_dir = Path(config.root_dir or Path.cwd())
|
|
703
|
+
bundle_dir = config.bundle_dir
|
|
704
|
+
try:
|
|
705
|
+
_run_vite_build(config, root_dir, console, no_build)
|
|
706
|
+
except SystemExit as exc:
|
|
707
|
+
console.print(f"[red]{exc}[/]")
|
|
708
|
+
sys.exit(1)
|
|
709
|
+
|
|
710
|
+
console.rule("[yellow]Deploying assets[/]", align="left")
|
|
711
|
+
console.print(f"Storage: {deploy_config.storage_backend}")
|
|
712
|
+
console.print(f"Delete orphaned: {deploy_config.delete_orphaned}")
|
|
713
|
+
if dry_run:
|
|
714
|
+
console.print("[dim]Dry-run enabled. No changes will be made.[/]")
|
|
715
|
+
|
|
716
|
+
try:
|
|
717
|
+
deployer = ViteDeployer(bundle_dir=bundle_dir, manifest_name=config.manifest_name, deploy_config=deploy_config)
|
|
718
|
+
except ImportError as exc: # pragma: no cover - backend import errors
|
|
719
|
+
console.print(f"[red]Missing backend dependency: {exc}[/]")
|
|
720
|
+
console.print("Install provider package, e.g., `pip install gcsfs` for gcs:// URLs.")
|
|
721
|
+
sys.exit(1)
|
|
722
|
+
except ValueError as exc:
|
|
723
|
+
console.print(f"[red]{exc}[/]")
|
|
724
|
+
sys.exit(1)
|
|
725
|
+
|
|
726
|
+
def _on_progress(action: str, path: str) -> None:
|
|
727
|
+
symbol = "+" if action == "upload" else "-"
|
|
728
|
+
console.print(f" {symbol} {path}")
|
|
729
|
+
|
|
730
|
+
result = deployer.sync(dry_run=dry_run, on_progress=_on_progress)
|
|
731
|
+
|
|
732
|
+
console.rule("[yellow]Deploy summary[/]", align="left")
|
|
733
|
+
console.print(f"Uploaded: {len(result.uploaded)} files ({format_bytes(result.uploaded_bytes)})")
|
|
734
|
+
console.print(f"Deleted: {len(result.deleted)} files ({format_bytes(result.deleted_bytes)})")
|
|
735
|
+
console.print(f"Remote: {deployer.remote_path}")
|
|
736
|
+
if result.dry_run:
|
|
737
|
+
console.print("[dim]No changes applied (dry-run).[/]")
|
|
738
|
+
else:
|
|
739
|
+
console.print("[bold green]✓ Deploy complete[/]")
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
@vite_group.command(
|
|
743
|
+
name="serve",
|
|
744
|
+
help="Serve frontend assets. For SSR frameworks (mode='ssr'), runs production Node server. Otherwise runs Vite dev server.",
|
|
745
|
+
)
|
|
746
|
+
@option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
|
|
747
|
+
@option("--quiet", type=bool, help="Suppress non-essential output.", default=False, is_flag=True)
|
|
748
|
+
@option("--production", type=bool, help="Force production mode (run serve_command).", default=False, is_flag=True) # pyright: ignore
|
|
749
|
+
def vite_serve(app: "Litestar", verbose: "bool", quiet: "bool", production: "bool") -> None:
|
|
750
|
+
"""Run frontend server.
|
|
751
|
+
|
|
752
|
+
In dev mode (default): Runs the dev server (npm run dev) for all frameworks.
|
|
753
|
+
In production mode (--production or dev_mode=False): Runs the production
|
|
754
|
+
server (npm run serve) for SSR frameworks.
|
|
755
|
+
|
|
756
|
+
Use --production to force running the production server (serve_command).
|
|
757
|
+
"""
|
|
758
|
+
if verbose:
|
|
759
|
+
app.debug = True
|
|
760
|
+
|
|
761
|
+
plugin = app.plugins.get(VitePlugin)
|
|
762
|
+
|
|
763
|
+
_apply_cli_log_level(plugin.config, verbose=verbose, quiet=quiet)
|
|
764
|
+
if plugin.config.set_environment:
|
|
765
|
+
set_environment(config=plugin.config)
|
|
766
|
+
|
|
767
|
+
use_production_server = production or not plugin.config.dev_mode
|
|
768
|
+
|
|
769
|
+
if use_production_server:
|
|
770
|
+
console.rule("[yellow]Starting production server[/]", align="left")
|
|
771
|
+
command_to_run = plugin.config.serve_command
|
|
772
|
+
if command_to_run is None:
|
|
773
|
+
console.print("[red]serve_command not configured. Add 'serve' script to package.json.[/]")
|
|
774
|
+
return
|
|
775
|
+
elif plugin.config.hot_reload:
|
|
776
|
+
console.rule("[yellow]Starting Vite process with HMR Enabled[/]", align="left")
|
|
777
|
+
command_to_run = plugin.config.run_command
|
|
778
|
+
else:
|
|
779
|
+
console.rule("[yellow]Starting Vite watch and build process[/]", align="left")
|
|
780
|
+
command_to_run = plugin.config.build_watch_command
|
|
781
|
+
|
|
782
|
+
if plugin.config.executor:
|
|
783
|
+
try:
|
|
784
|
+
root_dir = plugin.config.root_dir or Path.cwd()
|
|
785
|
+
plugin.config.executor.execute(command_to_run, cwd=root_dir)
|
|
786
|
+
console.print("[yellow]Server process stopped.[/]")
|
|
787
|
+
except ViteExecutionError as e:
|
|
788
|
+
console.print(f"[bold red]Server process failed: {e!s}[/]")
|
|
789
|
+
else:
|
|
790
|
+
console.print("[red]Executor not configured.[/]")
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
@vite_group.command(name="export-routes", help="Export route metadata for type-safe routing.")
|
|
794
|
+
@option(
|
|
795
|
+
"--output",
|
|
796
|
+
help="Output file path",
|
|
797
|
+
type=ClickPath(dir_okay=False, path_type=Path),
|
|
798
|
+
default=None,
|
|
799
|
+
show_default=False,
|
|
800
|
+
)
|
|
801
|
+
@option("--only", help="Only include routes matching these patterns (comma-separated)", type=str, default=None)
|
|
802
|
+
@option("--except", "exclude", help="Exclude routes matching these patterns (comma-separated)", type=str, default=None)
|
|
803
|
+
@option("--include-components", help="Include Inertia component names", type=bool, default=True, is_flag=True)
|
|
804
|
+
@option(
|
|
805
|
+
"--typescript",
|
|
806
|
+
"--ts",
|
|
807
|
+
"typescript",
|
|
808
|
+
help="Generate typed routes.ts file (Ziggy-style) instead of JSON",
|
|
809
|
+
type=bool,
|
|
810
|
+
default=False,
|
|
811
|
+
is_flag=True,
|
|
812
|
+
)
|
|
813
|
+
@option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
|
|
814
|
+
def export_routes(
|
|
815
|
+
app: "Litestar",
|
|
816
|
+
output: "Path | None",
|
|
817
|
+
only: "str | None",
|
|
818
|
+
exclude: "str | None",
|
|
819
|
+
include_components: "bool",
|
|
820
|
+
typescript: "bool",
|
|
821
|
+
verbose: "bool",
|
|
822
|
+
) -> None:
|
|
823
|
+
"""Export route metadata for type-safe routing.
|
|
824
|
+
|
|
825
|
+
Args:
|
|
826
|
+
app: The Litestar application instance.
|
|
827
|
+
output: The path to the output file. Uses TypeGenConfig if not provided.
|
|
828
|
+
only: Comma-separated list of route patterns to include.
|
|
829
|
+
exclude: Comma-separated list of route patterns to exclude.
|
|
830
|
+
include_components: Include Inertia component names in output.
|
|
831
|
+
typescript: Generate typed routes.ts file instead of JSON.
|
|
832
|
+
verbose: Whether to enable verbose output.
|
|
833
|
+
|
|
834
|
+
Raises:
|
|
835
|
+
LitestarCLIException: If the output file cannot be written.
|
|
836
|
+
"""
|
|
837
|
+
if verbose:
|
|
838
|
+
app.debug = True
|
|
839
|
+
|
|
840
|
+
plugin = app.plugins.get(VitePlugin)
|
|
841
|
+
config = plugin.config
|
|
842
|
+
|
|
843
|
+
only_list = [p.strip() for p in only.split(",")] if only else None
|
|
844
|
+
exclude_list = [p.strip() for p in exclude.split(",")] if exclude else None
|
|
845
|
+
|
|
846
|
+
if typescript:
|
|
847
|
+
if output is None:
|
|
848
|
+
if isinstance(config.types, TypeGenConfig) and config.types.routes_ts_path:
|
|
849
|
+
output = config.types.routes_ts_path
|
|
850
|
+
else:
|
|
851
|
+
output = Path("routes.ts")
|
|
852
|
+
|
|
853
|
+
console.rule(f"[yellow]Exporting typed routes to {output}[/]", align="left")
|
|
854
|
+
|
|
855
|
+
routes_ts_content = generate_routes_ts(app, only=only_list, exclude=exclude_list)
|
|
856
|
+
|
|
857
|
+
try:
|
|
858
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
859
|
+
output.write_text(routes_ts_content, encoding="utf-8")
|
|
860
|
+
console.print(f"[green]✓ Typed routes exported to {output}[/]")
|
|
861
|
+
except OSError as e: # pragma: no cover
|
|
862
|
+
msg = f"Failed to write routes to path {output}"
|
|
863
|
+
raise LitestarCLIException(msg) from e
|
|
864
|
+
else:
|
|
865
|
+
if output is None:
|
|
866
|
+
if isinstance(config.types, TypeGenConfig) and config.types.routes_path is not None:
|
|
867
|
+
output = config.types.routes_path
|
|
868
|
+
elif isinstance(config.types, TypeGenConfig):
|
|
869
|
+
output = config.types.output / "routes.json"
|
|
870
|
+
else:
|
|
871
|
+
output = Path("routes.json")
|
|
872
|
+
|
|
873
|
+
console.rule(f"[yellow]Exporting routes to {output}[/]", align="left")
|
|
874
|
+
|
|
875
|
+
routes_data = generate_routes_json(
|
|
876
|
+
app, only=only_list, exclude=exclude_list, include_components=include_components
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
try:
|
|
880
|
+
content = msgspec.json.format(encode_json(routes_data), indent=2)
|
|
881
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
882
|
+
output.write_bytes(content)
|
|
883
|
+
console.print(f"[green]✓ Routes exported to {output}[/]")
|
|
884
|
+
console.print(f"[dim] {len(routes_data.get('routes', {}))} routes exported[/]")
|
|
885
|
+
except OSError as e: # pragma: no cover
|
|
886
|
+
msg = f"Failed to write routes to path {output}"
|
|
887
|
+
raise LitestarCLIException(msg) from e
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def _export_openapi_schema(app: "Litestar", types_config: Any) -> None:
|
|
891
|
+
"""Export OpenAPI schema to file.
|
|
892
|
+
|
|
893
|
+
Args:
|
|
894
|
+
app: The Litestar application instance.
|
|
895
|
+
types_config: The TypeGenConfig instance.
|
|
896
|
+
|
|
897
|
+
Raises:
|
|
898
|
+
LitestarCLIException: If export fails.
|
|
899
|
+
"""
|
|
900
|
+
console.print("[dim]1. Exporting OpenAPI schema...[/]")
|
|
901
|
+
try:
|
|
902
|
+
serializer = get_serializer(app.type_encoders)
|
|
903
|
+
schema_dict = app.openapi_schema.to_schema()
|
|
904
|
+
schema_content = msgspec.json.format(encode_json(schema_dict, serializer=serializer), indent=2)
|
|
905
|
+
types_config.openapi_path.parent.mkdir(parents=True, exist_ok=True)
|
|
906
|
+
types_config.openapi_path.write_bytes(schema_content)
|
|
907
|
+
console.print(f"[green]✓ Schema exported to {_relative_path(types_config.openapi_path)}[/]")
|
|
908
|
+
except OSError as e:
|
|
909
|
+
msg = f"Failed to export OpenAPI schema: {e}"
|
|
910
|
+
raise LitestarCLIException(msg) from e
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
def _export_routes_metadata(app: "Litestar", types_config: Any) -> None:
|
|
914
|
+
"""Export routes metadata to file.
|
|
915
|
+
|
|
916
|
+
Args:
|
|
917
|
+
app: The Litestar application instance.
|
|
918
|
+
types_config: The TypeGenConfig instance.
|
|
919
|
+
|
|
920
|
+
Raises:
|
|
921
|
+
LitestarCLIException: If export fails.
|
|
922
|
+
"""
|
|
923
|
+
console.print("[dim]2. Exporting route metadata...[/]")
|
|
924
|
+
try:
|
|
925
|
+
routes_data = generate_routes_json(app, include_components=True)
|
|
926
|
+
routes_data["litestar_version"] = resolve_litestar_version()
|
|
927
|
+
routes_content = msgspec.json.format(encode_json(routes_data), indent=2)
|
|
928
|
+
types_config.routes_path.parent.mkdir(parents=True, exist_ok=True)
|
|
929
|
+
types_config.routes_path.write_bytes(routes_content)
|
|
930
|
+
console.print(f"[green]✓ Routes exported to {_relative_path(types_config.routes_path)}[/]")
|
|
931
|
+
except OSError as e:
|
|
932
|
+
msg = f"Failed to export routes: {e}"
|
|
933
|
+
raise LitestarCLIException(msg) from e
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
def _export_routes_typescript(app: "Litestar", types_config: Any) -> None:
|
|
937
|
+
"""Export typed routes TypeScript file (Ziggy-style).
|
|
938
|
+
|
|
939
|
+
Args:
|
|
940
|
+
app: The Litestar application instance.
|
|
941
|
+
types_config: The TypeGenConfig instance.
|
|
942
|
+
|
|
943
|
+
Raises:
|
|
944
|
+
LitestarCLIException: If export fails.
|
|
945
|
+
"""
|
|
946
|
+
if not types_config.generate_routes:
|
|
947
|
+
return
|
|
948
|
+
|
|
949
|
+
console.print("[dim] Generating typed routes.ts...[/]")
|
|
950
|
+
try:
|
|
951
|
+
ts_content = generate_routes_ts(app)
|
|
952
|
+
types_config.routes_ts_path.parent.mkdir(parents=True, exist_ok=True)
|
|
953
|
+
types_config.routes_ts_path.write_text(ts_content)
|
|
954
|
+
console.print(f"[green]✓ Typed routes generated at {_relative_path(types_config.routes_ts_path)}[/]")
|
|
955
|
+
except OSError as e:
|
|
956
|
+
msg = f"Failed to generate typed routes: {e}"
|
|
957
|
+
raise LitestarCLIException(msg) from e
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
def _export_inertia_pages_metadata(
|
|
961
|
+
app: "Litestar", types_config: Any, inertia_config: Any, openapi_schema: "dict[str, Any] | None" = None
|
|
962
|
+
) -> None:
|
|
963
|
+
"""Export Inertia page props metadata to file.
|
|
964
|
+
|
|
965
|
+
Args:
|
|
966
|
+
app: The Litestar application instance.
|
|
967
|
+
types_config: The TypeGenConfig instance.
|
|
968
|
+
inertia_config: The InertiaConfig instance.
|
|
969
|
+
openapi_schema: Optional pre-loaded OpenAPI schema.
|
|
970
|
+
|
|
971
|
+
Raises:
|
|
972
|
+
LitestarCLIException: If export fails.
|
|
973
|
+
"""
|
|
974
|
+
console.print("[dim]4. Exporting Inertia page props metadata...[/]")
|
|
975
|
+
inertia_type_gen = inertia_config.type_gen
|
|
976
|
+
if inertia_type_gen is None:
|
|
977
|
+
inertia_type_gen = InertiaTypeGenConfig()
|
|
978
|
+
|
|
979
|
+
try:
|
|
980
|
+
pages_data = generate_inertia_pages_json(
|
|
981
|
+
app,
|
|
982
|
+
openapi_schema=openapi_schema,
|
|
983
|
+
include_default_auth=inertia_type_gen.include_default_auth,
|
|
984
|
+
include_default_flash=inertia_type_gen.include_default_flash,
|
|
985
|
+
inertia_config=inertia_config,
|
|
986
|
+
types_config=types_config,
|
|
987
|
+
)
|
|
988
|
+
pages_content = msgspec.json.format(encode_json(pages_data), indent=2)
|
|
989
|
+
types_config.page_props_path.parent.mkdir(parents=True, exist_ok=True)
|
|
990
|
+
types_config.page_props_path.write_bytes(pages_content)
|
|
991
|
+
num_pages = len(pages_data.get("pages", {}))
|
|
992
|
+
console.print(f"[green]✓ Page props exported to {_relative_path(types_config.page_props_path)}[/]")
|
|
993
|
+
console.print(f"[dim] {num_pages} Inertia page(s) found[/]")
|
|
994
|
+
except OSError as e:
|
|
995
|
+
msg = f"Failed to export Inertia page props: {e}"
|
|
996
|
+
raise LitestarCLIException(msg) from e
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
def _get_package_executor_cmd(executor: "str | None", package: str) -> "list[str]":
|
|
1000
|
+
"""Build package executor command list.
|
|
1001
|
+
|
|
1002
|
+
Maps executor to its "npx equivalent" and returns a command list
|
|
1003
|
+
suitable for subprocess.run.
|
|
1004
|
+
|
|
1005
|
+
Args:
|
|
1006
|
+
executor: The JS runtime executor (node, bun, deno, yarn, pnpm).
|
|
1007
|
+
package: The package to run.
|
|
1008
|
+
|
|
1009
|
+
Returns:
|
|
1010
|
+
Command list for subprocess.run.
|
|
1011
|
+
"""
|
|
1012
|
+
match executor:
|
|
1013
|
+
case "bun":
|
|
1014
|
+
return ["bunx", package]
|
|
1015
|
+
case "deno":
|
|
1016
|
+
return ["deno", "run", "-A", f"npm:{package}"]
|
|
1017
|
+
case "yarn":
|
|
1018
|
+
return ["yarn", "dlx", package]
|
|
1019
|
+
case "pnpm":
|
|
1020
|
+
return ["pnpm", "dlx", package]
|
|
1021
|
+
case _:
|
|
1022
|
+
return ["npx", package]
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
def _run_openapi_ts(
|
|
1026
|
+
config: Any, verbose: bool, install_command: "list[str] | None" = None, executor: "str | None" = None
|
|
1027
|
+
) -> None:
|
|
1028
|
+
"""Run @hey-api/openapi-ts to generate TypeScript types.
|
|
1029
|
+
|
|
1030
|
+
Args:
|
|
1031
|
+
config: The ViteConfig instance (with .types resolved).
|
|
1032
|
+
verbose: Whether to show verbose output.
|
|
1033
|
+
install_command: Command used to install JS dependencies.
|
|
1034
|
+
executor: The JS runtime executor (node, bun, deno, yarn, pnpm).
|
|
1035
|
+
"""
|
|
1036
|
+
types_config = config.types
|
|
1037
|
+
root_dir = config.root_dir or Path.cwd()
|
|
1038
|
+
resource_dir = Path(config.resource_dir)
|
|
1039
|
+
if not resource_dir.is_absolute():
|
|
1040
|
+
resource_dir = root_dir / resource_dir
|
|
1041
|
+
|
|
1042
|
+
console.print("[dim]3. Running @hey-api/openapi-ts...[/]")
|
|
1043
|
+
|
|
1044
|
+
install_cmd = install_command or ["npm", "install"]
|
|
1045
|
+
pkg_cmd = _get_package_executor_cmd(executor, "@hey-api/openapi-ts")
|
|
1046
|
+
|
|
1047
|
+
try:
|
|
1048
|
+
check_cmd = [*pkg_cmd, "--version"]
|
|
1049
|
+
subprocess.run(check_cmd, check=True, capture_output=True, cwd=root_dir)
|
|
1050
|
+
|
|
1051
|
+
candidate_configs = [
|
|
1052
|
+
resource_dir / "openapi-ts.config.ts",
|
|
1053
|
+
resource_dir / "hey-api.config.ts",
|
|
1054
|
+
root_dir / "openapi-ts.config.ts",
|
|
1055
|
+
root_dir / "hey-api.config.ts",
|
|
1056
|
+
]
|
|
1057
|
+
config_path = next((p for p in candidate_configs if p.exists()), None)
|
|
1058
|
+
|
|
1059
|
+
if config_path is not None:
|
|
1060
|
+
openapi_cmd = [*pkg_cmd, "--file", str(config_path)]
|
|
1061
|
+
else:
|
|
1062
|
+
openapi_cmd = [*pkg_cmd, "-i", str(types_config.openapi_path), "-o", str(types_config.output)]
|
|
1063
|
+
|
|
1064
|
+
plugins: list[str] = ["@hey-api/typescript", "@hey-api/schemas"]
|
|
1065
|
+
if types_config.generate_sdk:
|
|
1066
|
+
plugins.extend(["@hey-api/sdk"])
|
|
1067
|
+
if types_config.generate_zod:
|
|
1068
|
+
plugins.append("zod")
|
|
1069
|
+
|
|
1070
|
+
if plugins:
|
|
1071
|
+
openapi_cmd.extend(["--plugins", *plugins])
|
|
1072
|
+
|
|
1073
|
+
subprocess.run(openapi_cmd, check=True, cwd=root_dir)
|
|
1074
|
+
console.print(f"[green]✓ Types generated in {types_config.output}[/]")
|
|
1075
|
+
except subprocess.CalledProcessError as e:
|
|
1076
|
+
console.print("[yellow]! @hey-api/openapi-ts failed - install it with:[/]")
|
|
1077
|
+
extra = ["@hey-api/openapi-ts"]
|
|
1078
|
+
if types_config.generate_zod:
|
|
1079
|
+
extra.append("zod")
|
|
1080
|
+
console.print(f"[dim] {' '.join([*install_cmd, '-D', *extra])}[/]")
|
|
1081
|
+
if verbose:
|
|
1082
|
+
console.print(f"[dim]Error: {e!s}[/]")
|
|
1083
|
+
except FileNotFoundError:
|
|
1084
|
+
runtime_name = executor or "Node.js"
|
|
1085
|
+
console.print(f"[yellow]! Package executor not found - ensure {runtime_name} is installed[/]")
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
@vite_group.command(name="generate-types", help="Generate TypeScript types from OpenAPI schema and routes.")
|
|
1089
|
+
@option("--verbose", type=bool, help="Enable verbose output.", default=False, is_flag=True)
|
|
1090
|
+
def generate_types(app: "Litestar", verbose: "bool") -> None:
|
|
1091
|
+
"""Generate TypeScript types from OpenAPI schema and routes.
|
|
1092
|
+
|
|
1093
|
+
This command:
|
|
1094
|
+
1. Exports the OpenAPI schema (uses litestar's built-in schema generation)
|
|
1095
|
+
2. Exports route metadata
|
|
1096
|
+
3. Runs @hey-api/openapi-ts to generate TypeScript types
|
|
1097
|
+
4. If Inertia is enabled: exports page props metadata
|
|
1098
|
+
|
|
1099
|
+
Args:
|
|
1100
|
+
app: The Litestar application instance.
|
|
1101
|
+
verbose: Whether to enable verbose output.
|
|
1102
|
+
"""
|
|
1103
|
+
if verbose:
|
|
1104
|
+
app.debug = True
|
|
1105
|
+
|
|
1106
|
+
plugin = app.plugins.get(VitePlugin)
|
|
1107
|
+
config = plugin.config
|
|
1108
|
+
|
|
1109
|
+
if not isinstance(config.types, TypeGenConfig):
|
|
1110
|
+
console.print("[yellow]Type generation is not enabled in ViteConfig[/]")
|
|
1111
|
+
console.print("[dim]Set types=True or types=TypeGenConfig() in ViteConfig[/]")
|
|
1112
|
+
return
|
|
1113
|
+
|
|
1114
|
+
console.rule("[yellow]Generating TypeScript types[/]", align="left")
|
|
1115
|
+
|
|
1116
|
+
_export_openapi_schema(app, config.types)
|
|
1117
|
+
_export_routes_metadata(app, config.types)
|
|
1118
|
+
_export_routes_typescript(app, config.types)
|
|
1119
|
+
_run_openapi_ts(config, verbose, config.install_command, config.runtime.executor)
|
|
1120
|
+
if isinstance(config.inertia, InertiaConfig) and config.types.generate_page_props and config.types.page_props_path:
|
|
1121
|
+
openapi_schema: dict[str, Any] | None = None
|
|
1122
|
+
try:
|
|
1123
|
+
if config.types.openapi_path and config.types.openapi_path.exists():
|
|
1124
|
+
openapi_schema = decode_json(config.types.openapi_path.read_bytes())
|
|
1125
|
+
except (OSError, SerializationException):
|
|
1126
|
+
if verbose:
|
|
1127
|
+
console.print("[dim]! Could not load OpenAPI schema for type references[/]")
|
|
1128
|
+
|
|
1129
|
+
_export_inertia_pages_metadata(app, config.types, config.inertia, openapi_schema)
|
|
1130
|
+
|
|
1131
|
+
|
|
1132
|
+
@vite_group.command(name="status", help="Check the status of the Vite integration.")
|
|
1133
|
+
def vite_status(app: "Litestar") -> None:
|
|
1134
|
+
"""Check the status of the Vite integration."""
|
|
1135
|
+
import httpx
|
|
1136
|
+
|
|
1137
|
+
plugin = app.plugins.get(VitePlugin)
|
|
1138
|
+
config = plugin.config
|
|
1139
|
+
|
|
1140
|
+
console.rule("[yellow]Vite Integration Status[/]", align="left")
|
|
1141
|
+
console.print(f"Dev Mode: {config.dev_mode}")
|
|
1142
|
+
console.print(f"Hot Reload: {config.hot_reload}")
|
|
1143
|
+
console.print(f"Assets URL: {config.asset_url}")
|
|
1144
|
+
console.print(f"Base URL: {config.base_url}")
|
|
1145
|
+
|
|
1146
|
+
manifest_path = Path(f"{config.bundle_dir}/{config.manifest_name}")
|
|
1147
|
+
if manifest_path.exists():
|
|
1148
|
+
console.print(f"[green]✓ Manifest found at {manifest_path}[/]")
|
|
1149
|
+
else:
|
|
1150
|
+
console.print(f"[red]✗ Manifest not found at {manifest_path}[/]")
|
|
1151
|
+
|
|
1152
|
+
if config.dev_mode:
|
|
1153
|
+
url = f"{config.protocol}://{config.host}:{config.port}"
|
|
1154
|
+
try:
|
|
1155
|
+
response = httpx.get(url, timeout=0.5)
|
|
1156
|
+
if response.status_code == 200:
|
|
1157
|
+
console.print(f"[green]✓ Vite server running at {url}[/]")
|
|
1158
|
+
else:
|
|
1159
|
+
console.print(f"[yellow]! Vite server reachable at {url} but returned {response.status_code}[/]")
|
|
1160
|
+
except httpx.HTTPError as e:
|
|
1161
|
+
console.print(f"[red]✗ Vite server not reachable at {url}: {e!s}[/]")
|