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