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.
Files changed (154) hide show
  1. litestar_vite/__init__.py +54 -4
  2. litestar_vite/__metadata__.py +12 -7
  3. litestar_vite/_codegen/__init__.py +26 -0
  4. litestar_vite/_codegen/inertia.py +407 -0
  5. litestar_vite/_codegen/openapi.py +233 -0
  6. litestar_vite/_codegen/routes.py +653 -0
  7. litestar_vite/_codegen/ts.py +235 -0
  8. litestar_vite/_handler/__init__.py +8 -0
  9. litestar_vite/_handler/app.py +524 -0
  10. litestar_vite/_handler/routing.py +130 -0
  11. litestar_vite/cli.py +1147 -10
  12. litestar_vite/codegen.py +39 -0
  13. litestar_vite/commands.py +79 -0
  14. litestar_vite/config.py +1594 -70
  15. litestar_vite/deploy.py +355 -0
  16. litestar_vite/doctor.py +1179 -0
  17. litestar_vite/exceptions.py +78 -0
  18. litestar_vite/executor.py +316 -0
  19. litestar_vite/handler.py +9 -0
  20. litestar_vite/html_transform.py +426 -0
  21. litestar_vite/inertia/__init__.py +53 -0
  22. litestar_vite/inertia/_utils.py +114 -0
  23. litestar_vite/inertia/exception_handler.py +172 -0
  24. litestar_vite/inertia/helpers.py +1043 -0
  25. litestar_vite/inertia/middleware.py +54 -0
  26. litestar_vite/inertia/plugin.py +133 -0
  27. litestar_vite/inertia/request.py +286 -0
  28. litestar_vite/inertia/response.py +706 -0
  29. litestar_vite/inertia/types.py +316 -0
  30. litestar_vite/loader.py +462 -121
  31. litestar_vite/plugin.py +2160 -21
  32. litestar_vite/py.typed +0 -0
  33. litestar_vite/scaffolding/__init__.py +20 -0
  34. litestar_vite/scaffolding/generator.py +270 -0
  35. litestar_vite/scaffolding/templates.py +437 -0
  36. litestar_vite/templates/__init__.py +0 -0
  37. litestar_vite/templates/addons/tailwindcss/tailwind.css.j2 +1 -0
  38. litestar_vite/templates/angular/index.html.j2 +12 -0
  39. litestar_vite/templates/angular/openapi-ts.config.ts.j2 +18 -0
  40. litestar_vite/templates/angular/package.json.j2 +35 -0
  41. litestar_vite/templates/angular/src/app/app.component.css.j2 +3 -0
  42. litestar_vite/templates/angular/src/app/app.component.html.j2 +1 -0
  43. litestar_vite/templates/angular/src/app/app.component.ts.j2 +9 -0
  44. litestar_vite/templates/angular/src/app/app.config.ts.j2 +5 -0
  45. litestar_vite/templates/angular/src/main.ts.j2 +9 -0
  46. litestar_vite/templates/angular/src/styles.css.j2 +9 -0
  47. litestar_vite/templates/angular/tsconfig.app.json.j2 +34 -0
  48. litestar_vite/templates/angular/tsconfig.json.j2 +20 -0
  49. litestar_vite/templates/angular/vite.config.ts.j2 +21 -0
  50. litestar_vite/templates/angular-cli/.postcssrc.json.j2 +5 -0
  51. litestar_vite/templates/angular-cli/angular.json.j2 +36 -0
  52. litestar_vite/templates/angular-cli/openapi-ts.config.ts.j2 +18 -0
  53. litestar_vite/templates/angular-cli/package.json.j2 +27 -0
  54. litestar_vite/templates/angular-cli/proxy.conf.json.j2 +18 -0
  55. litestar_vite/templates/angular-cli/src/app/app.component.css.j2 +3 -0
  56. litestar_vite/templates/angular-cli/src/app/app.component.html.j2 +1 -0
  57. litestar_vite/templates/angular-cli/src/app/app.component.ts.j2 +9 -0
  58. litestar_vite/templates/angular-cli/src/app/app.config.ts.j2 +5 -0
  59. litestar_vite/templates/angular-cli/src/index.html.j2 +13 -0
  60. litestar_vite/templates/angular-cli/src/main.ts.j2 +6 -0
  61. litestar_vite/templates/angular-cli/src/styles.css.j2 +10 -0
  62. litestar_vite/templates/angular-cli/tailwind.config.js.j2 +4 -0
  63. litestar_vite/templates/angular-cli/tsconfig.app.json.j2 +16 -0
  64. litestar_vite/templates/angular-cli/tsconfig.json.j2 +26 -0
  65. litestar_vite/templates/angular-cli/tsconfig.spec.json.j2 +9 -0
  66. litestar_vite/templates/astro/astro.config.mjs.j2 +28 -0
  67. litestar_vite/templates/astro/openapi-ts.config.ts.j2 +15 -0
  68. litestar_vite/templates/astro/src/layouts/Layout.astro.j2 +63 -0
  69. litestar_vite/templates/astro/src/pages/index.astro.j2 +36 -0
  70. litestar_vite/templates/astro/src/styles/global.css.j2 +1 -0
  71. litestar_vite/templates/base/.gitignore.j2 +42 -0
  72. litestar_vite/templates/base/openapi-ts.config.ts.j2 +15 -0
  73. litestar_vite/templates/base/package.json.j2 +38 -0
  74. litestar_vite/templates/base/resources/vite-env.d.ts.j2 +1 -0
  75. litestar_vite/templates/base/tsconfig.json.j2 +37 -0
  76. litestar_vite/templates/htmx/src/main.js.j2 +8 -0
  77. litestar_vite/templates/htmx/templates/base.html.j2.j2 +56 -0
  78. litestar_vite/templates/htmx/templates/index.html.j2.j2 +13 -0
  79. litestar_vite/templates/htmx/vite.config.ts.j2 +40 -0
  80. litestar_vite/templates/nuxt/app.vue.j2 +29 -0
  81. litestar_vite/templates/nuxt/composables/useApi.ts.j2 +33 -0
  82. litestar_vite/templates/nuxt/nuxt.config.ts.j2 +31 -0
  83. litestar_vite/templates/nuxt/openapi-ts.config.ts.j2 +15 -0
  84. litestar_vite/templates/nuxt/pages/index.vue.j2 +54 -0
  85. litestar_vite/templates/react/index.html.j2 +13 -0
  86. litestar_vite/templates/react/src/App.css.j2 +56 -0
  87. litestar_vite/templates/react/src/App.tsx.j2 +19 -0
  88. litestar_vite/templates/react/src/main.tsx.j2 +10 -0
  89. litestar_vite/templates/react/vite.config.ts.j2 +39 -0
  90. litestar_vite/templates/react-inertia/index.html.j2 +14 -0
  91. litestar_vite/templates/react-inertia/package.json.j2 +46 -0
  92. litestar_vite/templates/react-inertia/resources/App.css.j2 +68 -0
  93. litestar_vite/templates/react-inertia/resources/main.tsx.j2 +17 -0
  94. litestar_vite/templates/react-inertia/resources/pages/Home.tsx.j2 +18 -0
  95. litestar_vite/templates/react-inertia/resources/ssr.tsx.j2 +19 -0
  96. litestar_vite/templates/react-inertia/vite.config.ts.j2 +59 -0
  97. litestar_vite/templates/react-router/index.html.j2 +12 -0
  98. litestar_vite/templates/react-router/src/App.css.j2 +17 -0
  99. litestar_vite/templates/react-router/src/App.tsx.j2 +7 -0
  100. litestar_vite/templates/react-router/src/main.tsx.j2 +10 -0
  101. litestar_vite/templates/react-router/vite.config.ts.j2 +39 -0
  102. litestar_vite/templates/react-tanstack/index.html.j2 +12 -0
  103. litestar_vite/templates/react-tanstack/openapi-ts.config.ts.j2 +18 -0
  104. litestar_vite/templates/react-tanstack/src/App.css.j2 +17 -0
  105. litestar_vite/templates/react-tanstack/src/main.tsx.j2 +21 -0
  106. litestar_vite/templates/react-tanstack/src/routeTree.gen.ts.j2 +7 -0
  107. litestar_vite/templates/react-tanstack/src/routes/__root.tsx.j2 +9 -0
  108. litestar_vite/templates/react-tanstack/src/routes/books.tsx.j2 +9 -0
  109. litestar_vite/templates/react-tanstack/src/routes/index.tsx.j2 +9 -0
  110. litestar_vite/templates/react-tanstack/vite.config.ts.j2 +39 -0
  111. litestar_vite/templates/svelte/index.html.j2 +13 -0
  112. litestar_vite/templates/svelte/src/App.svelte.j2 +30 -0
  113. litestar_vite/templates/svelte/src/app.css.j2 +45 -0
  114. litestar_vite/templates/svelte/src/main.ts.j2 +8 -0
  115. litestar_vite/templates/svelte/src/vite-env.d.ts.j2 +2 -0
  116. litestar_vite/templates/svelte/svelte.config.js.j2 +5 -0
  117. litestar_vite/templates/svelte/vite.config.ts.j2 +39 -0
  118. litestar_vite/templates/svelte-inertia/index.html.j2 +14 -0
  119. litestar_vite/templates/svelte-inertia/resources/app.css.j2 +21 -0
  120. litestar_vite/templates/svelte-inertia/resources/main.ts.j2 +11 -0
  121. litestar_vite/templates/svelte-inertia/resources/pages/Home.svelte.j2 +43 -0
  122. litestar_vite/templates/svelte-inertia/resources/vite-env.d.ts.j2 +2 -0
  123. litestar_vite/templates/svelte-inertia/svelte.config.js.j2 +5 -0
  124. litestar_vite/templates/svelte-inertia/vite.config.ts.j2 +37 -0
  125. litestar_vite/templates/sveltekit/openapi-ts.config.ts.j2 +15 -0
  126. litestar_vite/templates/sveltekit/src/app.css.j2 +40 -0
  127. litestar_vite/templates/sveltekit/src/app.html.j2 +12 -0
  128. litestar_vite/templates/sveltekit/src/hooks.server.ts.j2 +55 -0
  129. litestar_vite/templates/sveltekit/src/routes/+layout.svelte.j2 +12 -0
  130. litestar_vite/templates/sveltekit/src/routes/+page.svelte.j2 +34 -0
  131. litestar_vite/templates/sveltekit/svelte.config.js.j2 +12 -0
  132. litestar_vite/templates/sveltekit/tsconfig.json.j2 +14 -0
  133. litestar_vite/templates/sveltekit/vite.config.ts.j2 +31 -0
  134. litestar_vite/templates/vue/env.d.ts.j2 +7 -0
  135. litestar_vite/templates/vue/index.html.j2 +13 -0
  136. litestar_vite/templates/vue/src/App.vue.j2 +28 -0
  137. litestar_vite/templates/vue/src/main.ts.j2 +5 -0
  138. litestar_vite/templates/vue/src/style.css.j2 +45 -0
  139. litestar_vite/templates/vue/vite.config.ts.j2 +39 -0
  140. litestar_vite/templates/vue-inertia/env.d.ts.j2 +7 -0
  141. litestar_vite/templates/vue-inertia/index.html.j2 +14 -0
  142. litestar_vite/templates/vue-inertia/package.json.j2 +49 -0
  143. litestar_vite/templates/vue-inertia/resources/main.ts.j2 +18 -0
  144. litestar_vite/templates/vue-inertia/resources/pages/Home.vue.j2 +22 -0
  145. litestar_vite/templates/vue-inertia/resources/ssr.ts.j2 +21 -0
  146. litestar_vite/templates/vue-inertia/resources/style.css.j2 +21 -0
  147. litestar_vite/templates/vue-inertia/vite.config.ts.j2 +59 -0
  148. litestar_vite-0.15.0rc2.dist-info/METADATA +230 -0
  149. litestar_vite-0.15.0rc2.dist-info/RECORD +151 -0
  150. {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0rc2.dist-info}/WHEEL +1 -1
  151. litestar_vite/template_engine.py +0 -103
  152. litestar_vite-0.1.1.dist-info/METADATA +0 -68
  153. litestar_vite-0.1.1.dist-info/RECORD +0 -11
  154. {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,78 @@
1
+ """Litestar-Vite exception classes."""
2
+
3
+ __all__ = [
4
+ "AssetNotFoundError",
5
+ "LitestarViteError",
6
+ "ManifestNotFoundError",
7
+ "MissingDependencyError",
8
+ "ViteExecutableNotFoundError",
9
+ "ViteExecutionError",
10
+ "ViteProcessError",
11
+ ]
12
+
13
+
14
+ class LitestarViteError(Exception):
15
+ """Base exception for Litestar-Vite related errors."""
16
+
17
+
18
+ class MissingDependencyError(LitestarViteError, ImportError):
19
+ """Raised when a package is not installed but required."""
20
+
21
+ def __init__(self, package: str, install_package: "str | None" = None) -> None:
22
+ """Initialize the exception.
23
+
24
+ Args:
25
+ package: The name of the missing package.
26
+ install_package: Optional alternative package name for installation.
27
+ """
28
+ super().__init__(
29
+ f"Package {package!r} is not installed but required. You can install it by running "
30
+ f"'pip install litestar-vite[{install_package or package}]' to install litestar-vite with the required extra "
31
+ f"or 'pip install {install_package or package}' to install the package separately"
32
+ )
33
+
34
+
35
+ class ViteExecutableNotFoundError(LitestarViteError):
36
+ """Raised when the vite executable is not found."""
37
+
38
+ def __init__(self, executable: str) -> None:
39
+ super().__init__(f"Executable {executable!r} not found.")
40
+
41
+
42
+ class ViteExecutionError(LitestarViteError):
43
+ """Raised when the vite execution fails."""
44
+
45
+ def __init__(self, command: list[str], return_code: int, stderr: str) -> None:
46
+ super().__init__(f"Command {command!r} failed with return code {return_code}.\nStderr: {stderr}")
47
+
48
+
49
+ class ManifestNotFoundError(LitestarViteError):
50
+ """Raised when the manifest file is not found."""
51
+
52
+ def __init__(self, manifest_path: str) -> None:
53
+ super().__init__(f"Vite manifest file not found at {manifest_path!r}. Did you forget to build your assets?")
54
+
55
+
56
+ class ViteProcessError(LitestarViteError):
57
+ """Raised when the Vite process fails to start or stop."""
58
+
59
+ def __init__(
60
+ self,
61
+ message: str,
62
+ command: list[str] | None = None,
63
+ exit_code: int | None = None,
64
+ stderr: str | None = None,
65
+ stdout: str | None = None,
66
+ ) -> None:
67
+ super().__init__(message)
68
+ self.command = command
69
+ self.exit_code = exit_code
70
+ self.stderr = stderr
71
+ self.stdout = stdout
72
+
73
+
74
+ class AssetNotFoundError(LitestarViteError):
75
+ """Raised when an asset is not found in the manifest."""
76
+
77
+ def __init__(self, file_path: str, manifest_path: str) -> None:
78
+ super().__init__(f"Asset {file_path!r} not found in manifest at {manifest_path!r}.")
@@ -0,0 +1,316 @@
1
+ """JavaScript runtime executors for Vite commands.
2
+
3
+ This module provides executor classes for different JavaScript runtimes
4
+ (Node.js/npm, Bun, Deno, Yarn, pnpm) to run Vite commands.
5
+ """
6
+
7
+ import os
8
+ import platform
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ from abc import ABC, abstractmethod
13
+ from importlib.util import find_spec
14
+ from pathlib import Path
15
+ from typing import Any, ClassVar, Protocol, runtime_checkable
16
+
17
+ from litestar.cli._utils import console
18
+
19
+ from litestar_vite.exceptions import ViteExecutableNotFoundError, ViteExecutionError
20
+
21
+
22
+ def _windows_create_new_process_group_flag() -> int:
23
+ """Return the Windows-only process creation flag for new process groups.
24
+
25
+ When available, ``subprocess.CREATE_NEW_PROCESS_GROUP`` is used as ``creationflags`` for long-lived dev servers.
26
+ This improves process lifecycle management on Windows (notably console signal / Ctrl+C behavior). On non-Windows
27
+ platforms the constant is not defined, so this returns ``0``.
28
+
29
+ Returns:
30
+ The ``creationflags`` value to start a new process group on Windows, otherwise ``0``.
31
+ """
32
+ try:
33
+ return subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore[attr-defined]
34
+ except AttributeError:
35
+ return 0
36
+
37
+
38
+ _create_new_process_group = _windows_create_new_process_group_flag()
39
+
40
+
41
+ def _popen_server_kwargs(cwd: Path) -> dict[str, Any]:
42
+ """Return Popen kwargs that keep server processes alive and grouped.
43
+
44
+ Returns:
45
+ Keyword arguments for ``subprocess.Popen`` suitable for long-lived dev servers.
46
+ """
47
+ kwargs: dict[str, Any] = {"cwd": cwd, "stdin": subprocess.PIPE, "stdout": None, "stderr": None}
48
+ if platform.system() == "Windows":
49
+ kwargs["shell"] = True
50
+ kwargs["creationflags"] = _create_new_process_group
51
+ else:
52
+ kwargs["shell"] = False
53
+ kwargs["start_new_session"] = True
54
+ return kwargs
55
+
56
+
57
+ class JSExecutor(ABC):
58
+ """Abstract base class for Javascript executors.
59
+
60
+ The default ``silent_flag`` matches npm-style CLIs (``--silent``). Executors that do not support a silent flag
61
+ (e.g., Deno) override it with an empty string.
62
+ """
63
+
64
+ bin_name: ClassVar[str]
65
+ silent_flag: ClassVar[str] = "--silent"
66
+
67
+ def __init__(self, executable_path: "Path | str | None" = None, *, silent: bool = False) -> None:
68
+ self.executable_path = executable_path
69
+ self.silent = silent
70
+
71
+ @abstractmethod
72
+ def install(self, cwd: Path) -> None:
73
+ """Install dependencies."""
74
+
75
+ @abstractmethod
76
+ def run(self, args: list[str], cwd: Path) -> "subprocess.Popen[Any]":
77
+ """Run a command."""
78
+
79
+ @abstractmethod
80
+ def execute(self, args: list[str], cwd: Path) -> None:
81
+ """Execute a command and wait for it to finish."""
82
+
83
+ def _resolve_executable(self) -> str:
84
+ """Return the executable path or raise if not found.
85
+
86
+ Returns:
87
+ Path to the resolved executable.
88
+
89
+ Raises:
90
+ ViteExecutableNotFoundError: If the binary cannot be located.
91
+ """
92
+ if self.executable_path:
93
+ return str(self.executable_path)
94
+ path = shutil.which(self.bin_name)
95
+ if path is None:
96
+ raise ViteExecutableNotFoundError(self.bin_name)
97
+ return path
98
+
99
+ def _apply_silent_flag(self, args: list[str]) -> list[str]:
100
+ """Apply silent flag to command args if silent mode is enabled.
101
+
102
+ The silent flag is inserted after 'run' in npm-style commands
103
+ (e.g., ['npm', 'run', 'dev'] -> ['npm', 'run', '--silent', 'dev']).
104
+
105
+ Args:
106
+ args: The command arguments.
107
+
108
+ Returns:
109
+ Modified args with silent flag inserted if applicable.
110
+ """
111
+ if not self.silent or not self.silent_flag:
112
+ return args
113
+
114
+ if args and args[0] == "run" and len(args) >= 2:
115
+ return [args[0], self.silent_flag, *args[1:]]
116
+
117
+ if "run" in args:
118
+ run_idx = args.index("run")
119
+ return [*args[: run_idx + 1], self.silent_flag, *args[run_idx + 1 :]]
120
+
121
+ return [*args, self.silent_flag]
122
+
123
+ @property
124
+ def start_command(self) -> list[str]:
125
+ """Get the default command to start the dev server (e.g., npm run start).
126
+
127
+ Returns:
128
+ The argv list used to start the dev server.
129
+ """
130
+ return [self.bin_name, "run", "start"]
131
+
132
+ @property
133
+ def build_command(self) -> list[str]:
134
+ """Get the default command to build for production (e.g., npm run build).
135
+
136
+ Returns:
137
+ The argv list used to build production assets.
138
+ """
139
+ return [self.bin_name, "run", "build"]
140
+
141
+
142
+ class CommandExecutor(JSExecutor):
143
+ """Generic command executor."""
144
+
145
+ def install(self, cwd: Path) -> None:
146
+ executable = self._resolve_executable()
147
+ command = [executable, "install"]
148
+ process = subprocess.run(command, cwd=cwd, shell=platform.system() == "Windows", check=False)
149
+ if process.returncode != 0:
150
+ raise ViteExecutionError(command, process.returncode, "package install failed")
151
+
152
+ def run(self, args: list[str], cwd: Path) -> "subprocess.Popen[Any]":
153
+ executable = self._resolve_executable()
154
+ args = self._apply_silent_flag(args)
155
+ command = args if args and Path(args[0]).name == Path(executable).name else [executable, *args]
156
+ return subprocess.Popen(command, **_popen_server_kwargs(cwd))
157
+
158
+ def execute(self, args: list[str], cwd: Path) -> None:
159
+ executable = self._resolve_executable()
160
+ args = self._apply_silent_flag(args)
161
+ command = args if args and Path(args[0]).name == Path(executable).name else [executable, *args]
162
+ process = subprocess.run(
163
+ command,
164
+ cwd=cwd,
165
+ shell=platform.system() == "Windows",
166
+ check=False,
167
+ stdin=subprocess.PIPE,
168
+ stdout=None,
169
+ stderr=subprocess.PIPE,
170
+ )
171
+ if process.returncode != 0:
172
+ stderr = process.stderr.decode() if process.stderr else ""
173
+ raise ViteExecutionError(command, process.returncode, stderr)
174
+
175
+
176
+ class NodeExecutor(CommandExecutor):
177
+ """Node.js executor."""
178
+
179
+ bin_name = "npm"
180
+
181
+
182
+ class BunExecutor(CommandExecutor):
183
+ """Bun executor."""
184
+
185
+ bin_name = "bun"
186
+
187
+
188
+ class DenoExecutor(CommandExecutor):
189
+ """Deno executor."""
190
+
191
+ bin_name = "deno"
192
+ silent_flag: ClassVar[str] = ""
193
+
194
+ def install(self, cwd: Path) -> None:
195
+ pass
196
+
197
+
198
+ class YarnExecutor(CommandExecutor):
199
+ """Yarn executor."""
200
+
201
+ bin_name = "yarn"
202
+
203
+
204
+ class PnpmExecutor(CommandExecutor):
205
+ """PNPM executor."""
206
+
207
+ bin_name = "pnpm"
208
+
209
+
210
+ class NodeenvExecutor(JSExecutor):
211
+ """Nodeenv executor.
212
+
213
+ This executor detects and uses nodeenv in a Python virtual environment.
214
+ It installs nodeenv if not present and uses the npm from within the virtualenv.
215
+ """
216
+
217
+ bin_name = "nodeenv"
218
+
219
+ @runtime_checkable
220
+ class _SupportsDetectNodeenv(Protocol):
221
+ detect_nodeenv: bool
222
+
223
+ def __init__(self, config: Any = None, *, silent: bool = False) -> None:
224
+ """Initialize NodeenvExecutor.
225
+
226
+ Args:
227
+ config: Optional ViteConfig for detecting nodeenv. Can be the new
228
+ ViteConfig or legacy config. Only used to check detect_nodeenv.
229
+ silent: Whether to suppress npm output with --silent flag.
230
+ """
231
+ super().__init__(None, silent=silent)
232
+ self.config = config
233
+ self._detect_nodeenv = bool(config.detect_nodeenv) if isinstance(config, self._SupportsDetectNodeenv) else False
234
+
235
+ def _get_nodeenv_command(self) -> str:
236
+ """Return the nodeenv executable to run.
237
+
238
+ Returns:
239
+ Absolute path to the nodeenv binary if present next to the Python
240
+ interpreter, otherwise the string ``"nodeenv"`` for PATH lookup.
241
+ """
242
+ candidate = Path(sys.executable).with_name("nodeenv")
243
+ if candidate.exists():
244
+ return str(candidate)
245
+ return "nodeenv"
246
+
247
+ def install_nodeenv(self, cwd: Path) -> None:
248
+ """Install nodeenv when available in the environment."""
249
+ if find_spec("nodeenv") is None:
250
+ console.print("[yellow]Nodeenv not found. Skipping installation.[/]")
251
+ return
252
+
253
+ install_dir = os.environ.get("VIRTUAL_ENV", sys.prefix)
254
+ console.rule("[yellow]Starting Nodeenv installation process[/]", align="left")
255
+
256
+ command = [self._get_nodeenv_command(), install_dir, "--force", "--quiet"]
257
+ subprocess.run(command, cwd=cwd, check=False)
258
+
259
+ def install(self, cwd: Path) -> None:
260
+ if self._detect_nodeenv:
261
+ self.install_nodeenv(cwd)
262
+
263
+ npm_path = self._find_npm_in_venv()
264
+ command = [npm_path, "install"]
265
+ subprocess.run(command, cwd=cwd, check=True)
266
+
267
+ def run(self, args: list[str], cwd: Path) -> "subprocess.Popen[Any]":
268
+ npm_path = self._find_npm_in_venv()
269
+ args = self._apply_silent_flag(args)
270
+ command = [npm_path, *args]
271
+ return subprocess.Popen(command, **_popen_server_kwargs(cwd))
272
+
273
+ def execute(self, args: list[str], cwd: Path) -> None:
274
+ npm_path = self._find_npm_in_venv()
275
+ args = self._apply_silent_flag(args)
276
+ command = [npm_path, *args]
277
+ process = subprocess.run(
278
+ command, cwd=cwd, shell=platform.system() == "Windows", check=False, capture_output=True
279
+ )
280
+ if process.returncode != 0:
281
+ raise ViteExecutionError(command, process.returncode, process.stderr.decode())
282
+
283
+ def _find_npm_in_venv(self) -> str:
284
+ """Locate npm within the active virtual environment or fall back to PATH.
285
+
286
+ Returns:
287
+ Path to ``npm`` inside the virtual environment when available, or
288
+ ``"npm"`` to defer to PATH resolution.
289
+ """
290
+ venv_path = os.environ.get("VIRTUAL_ENV", sys.prefix)
291
+ bin_dir = "Scripts" if platform.system() == "Windows" else "bin"
292
+ npm_path = Path(venv_path) / bin_dir / "npm"
293
+ if platform.system() == "Windows":
294
+ npm_path = npm_path.with_suffix(".cmd")
295
+
296
+ if npm_path.exists():
297
+ return str(npm_path)
298
+ return "npm"
299
+
300
+ @property
301
+ def start_command(self) -> list[str]:
302
+ """Get the default command to start the dev server using nodeenv npm.
303
+
304
+ Returns:
305
+ The argv list used to start the dev server.
306
+ """
307
+ return [self._find_npm_in_venv(), "run", "start"]
308
+
309
+ @property
310
+ def build_command(self) -> list[str]:
311
+ """Get the default command to build for production using nodeenv npm.
312
+
313
+ Returns:
314
+ The argv list used to build production assets.
315
+ """
316
+ return [self._find_npm_in_venv(), "run", "build"]
@@ -0,0 +1,9 @@
1
+ """SPA mode handler public API.
2
+
3
+ The SPA handler implementation lives in ``litestar_vite._handler.app``. This module exists as a stable import
4
+ location for users and tests.
5
+ """
6
+
7
+ from litestar_vite._handler.app import AppHandler
8
+
9
+ __all__ = ("AppHandler",)