litestar-vite-plugin 0.15.0-alpha.2 → 0.15.0-alpha.4

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.
package/README.md CHANGED
@@ -5,67 +5,138 @@ Litestar Vite connects the Litestar backend to a Vite toolchain. It supports SPA
5
5
  ## Features
6
6
 
7
7
  - One-port dev: proxies Vite HTTP + WS/HMR through Litestar by default; switch to two-port with `VITE_PROXY_MODE=direct`.
8
+ - SSR framework support: use `proxy_mode="ssr"` for Astro, Nuxt, SvelteKit - proxies everything except your API routes.
8
9
  - Production assets: reads Vite manifest from `public/manifest.json` (configurable) and serves under `asset_url`.
9
10
  - Type-safe frontends: optional OpenAPI/routes export + `@hey-api/openapi-ts` via the Vite plugin.
10
11
  - Inertia support: v2 protocol with session middleware and optional SPA mode.
11
12
 
12
- ## Quick start (React TanStack SPA)
13
+ ## Quick Start (SPA)
13
14
 
14
15
  ```bash
15
16
  pip install litestar-vite
16
- litestar assets init --template react-tanstack
17
- litestar assets install # installs frontend deps via configured executor
18
17
  ```
19
18
 
20
19
  ```python
20
+ import os
21
+ from pathlib import Path
21
22
  from litestar import Litestar
22
- from litestar_vite import VitePlugin, ViteConfig
23
+ from litestar_vite import VitePlugin, ViteConfig, PathConfig
23
24
 
24
- app = Litestar(plugins=[VitePlugin(config=ViteConfig(dev_mode=True))]) # SPA mode by default
25
+ DEV_MODE = os.getenv("VITE_DEV_MODE", "true").lower() in ("true", "1", "yes")
26
+
27
+ app = Litestar(
28
+ plugins=[VitePlugin(config=ViteConfig(
29
+ dev_mode=DEV_MODE,
30
+ paths=PathConfig(root=Path(__file__).parent),
31
+ ))]
32
+ )
25
33
  ```
26
34
 
27
35
  ```bash
28
- litestar run --reload # starts Litestar; Vite dev is proxied automatically
36
+ litestar run --reload # Vite dev server is proxied automatically
29
37
  ```
30
38
 
31
- Other templates: `react`, `vue`, `svelte`, `htmx`, `react-inertia`, `vue-inertia`, `svelte-inertia`, `angular`, `angular-cli`, `astro`, `nuxt`, `sveltekit` (see `litestar assets init --help`).
39
+ Scaffold a frontend: `litestar assets init --template vue` (or `react`, `svelte`, `htmx`, `react-inertia`, `vue-inertia`, `angular`, `astro`, `nuxt`, `sveltekit`).
32
40
 
33
41
  ## Template / HTMX
34
42
 
35
43
  ```python
44
+ from pathlib import Path
36
45
  from litestar import Litestar
37
46
  from litestar.contrib.jinja import JinjaTemplateEngine
38
- from litestar.template.config import TemplateConfig
39
- from litestar_vite import VitePlugin, ViteConfig
47
+ from litestar.template import TemplateConfig
48
+ from litestar_vite import VitePlugin, ViteConfig, PathConfig
49
+
50
+ here = Path(__file__).parent
40
51
 
41
52
  app = Litestar(
42
- template_config=TemplateConfig(engine=JinjaTemplateEngine(directory="templates")),
43
- plugins=[VitePlugin(config=ViteConfig(mode="template", dev_mode=True))],
53
+ template_config=TemplateConfig(directory=here / "templates", engine=JinjaTemplateEngine),
54
+ plugins=[VitePlugin(config=ViteConfig(
55
+ dev_mode=True,
56
+ paths=PathConfig(root=here),
57
+ ))],
44
58
  )
45
59
  ```
46
60
 
47
61
  ## Inertia (v2)
48
62
 
49
- Requires session middleware.
63
+ Requires session middleware (32-char secret).
50
64
 
51
65
  ```python
66
+ import os
67
+ from pathlib import Path
52
68
  from litestar import Litestar
53
69
  from litestar.middleware.session.client_side import CookieBackendConfig
54
- from litestar_vite import VitePlugin, ViteConfig
55
- from litestar_vite.inertia import InertiaPlugin
56
- from litestar_vite.inertia.config import InertiaConfig
70
+ from litestar_vite import VitePlugin, ViteConfig, PathConfig
71
+ from litestar_vite.inertia import InertiaConfig
72
+
73
+ here = Path(__file__).parent
74
+ SECRET_KEY = os.environ.get("SECRET_KEY", "development-only-secret-32-chars")
75
+ session = CookieBackendConfig(secret=SECRET_KEY.encode("utf-8"))
76
+
77
+ app = Litestar(
78
+ middleware=[session.middleware],
79
+ plugins=[VitePlugin(config=ViteConfig(
80
+ dev_mode=True,
81
+ paths=PathConfig(root=here),
82
+ inertia=InertiaConfig(root_template="index.html"),
83
+ ))],
84
+ )
85
+ ```
86
+
87
+ ## Meta-frameworks (Astro, Nuxt, SvelteKit)
88
+
89
+ Use `proxy_mode="ssr"` to proxy non-API routes to the framework's dev server:
90
+
91
+ ```python
92
+ import os
93
+ from pathlib import Path
94
+ from litestar import Litestar
95
+ from litestar_vite import VitePlugin, ViteConfig, PathConfig, RuntimeConfig
57
96
 
58
- session_backend = CookieBackendConfig(secret="dev-secret")
97
+ here = Path(__file__).parent
98
+ DEV_MODE = os.getenv("VITE_DEV_MODE", "true").lower() in ("true", "1", "yes")
59
99
 
60
100
  app = Litestar(
61
- middleware=[session_backend.middleware],
62
101
  plugins=[
63
- VitePlugin(config=ViteConfig(mode="template", inertia=True, dev_mode=True)),
64
- InertiaPlugin(InertiaConfig()),
102
+ VitePlugin(config=ViteConfig(
103
+ dev_mode=DEV_MODE,
104
+ paths=PathConfig(root=here),
105
+ runtime=RuntimeConfig(proxy_mode="ssr"),
106
+ ))
65
107
  ],
66
108
  )
67
109
  ```
68
110
 
111
+ ### Proxy Modes
112
+
113
+ | Mode | Alias | Use Case |
114
+ |------|-------|----------|
115
+ | `vite` | - | SPAs - proxies Vite assets only (default) |
116
+ | `direct` | - | Two-port dev - expose Vite port directly |
117
+ | `proxy` | `ssr` | Meta-frameworks - proxies everything except API routes |
118
+
119
+ ### Production Deployment
120
+
121
+ **Astro (static):** Astro generates static HTML by default. Build and serve with Litestar:
122
+
123
+ ```bash
124
+ litestar --app-dir examples/astro assets install
125
+ litestar --app-dir examples/astro assets build
126
+ VITE_DEV_MODE=false litestar --app-dir examples/astro run
127
+ ```
128
+
129
+ **Nuxt/SvelteKit (SSR):** These run their own Node servers. Deploy as two services:
130
+
131
+ ```bash
132
+ # Terminal 1: SSR server
133
+ litestar --app-dir examples/nuxt assets build
134
+ litestar --app-dir examples/nuxt assets serve
135
+
136
+ # Terminal 2: Litestar API
137
+ VITE_DEV_MODE=false litestar --app-dir examples/nuxt run --port 8001
138
+ ```
139
+
69
140
  ## Type generation
70
141
 
71
142
  ```python
@@ -94,5 +165,5 @@ litestar assets generate-types # one-off or CI
94
165
  ## Links
95
166
 
96
167
  - Docs: <https://litestar-org.github.io/litestar-vite/>
97
- - Examples: `examples/` (basic, inertia, spa-react)
168
+ - Examples: `examples/` (react, vue, svelte, react-inertia, vue-inertia, astro, nuxt, sveltekit, htmx)
98
169
  - Issues: <https://github.com/litestar-org/litestar-vite/issues/>
@@ -17,7 +17,10 @@
17
17
  * integrations: [
18
18
  * litestar({
19
19
  * apiProxy: 'http://localhost:8000',
20
- * typesPath: './src/generated/api',
20
+ * types: {
21
+ * enabled: true,
22
+ * output: 'src/generated/api',
23
+ * },
21
24
  * }),
22
25
  * ],
23
26
  * });
@@ -31,6 +34,31 @@ import type { Plugin, ViteDevServer } from "vite";
31
34
  * This is a minimal type definition to avoid requiring astro as a dependency.
32
35
  * When using this integration, Astro will be available in the project.
33
36
  */
37
+ /**
38
+ * Astro config for updateConfig - partial types we support.
39
+ */
40
+ interface AstroConfigPartial {
41
+ server?: {
42
+ port?: number;
43
+ host?: string | boolean;
44
+ };
45
+ vite?: {
46
+ plugins?: Plugin[];
47
+ server?: {
48
+ port?: number;
49
+ strictPort?: boolean;
50
+ proxy?: Record<string, unknown>;
51
+ };
52
+ };
53
+ }
54
+ /**
55
+ * AddressInfo from Node.js net module.
56
+ */
57
+ interface AddressInfo {
58
+ address: string;
59
+ family: string;
60
+ port: number;
61
+ }
34
62
  interface AstroIntegration {
35
63
  name: string;
36
64
  hooks: {
@@ -38,17 +66,17 @@ interface AstroIntegration {
38
66
  config: unknown;
39
67
  command: "dev" | "build" | "preview" | "sync";
40
68
  isRestart: boolean;
41
- updateConfig: (newConfig: {
42
- vite?: {
43
- plugins?: Plugin[];
44
- };
45
- }) => unknown;
69
+ updateConfig: (newConfig: AstroConfigPartial) => unknown;
46
70
  logger: AstroIntegrationLogger;
47
71
  }) => void | Promise<void>;
48
72
  "astro:server:setup"?: (options: {
49
73
  server: ViteDevServer;
50
74
  logger: AstroIntegrationLogger;
51
75
  }) => void | Promise<void>;
76
+ "astro:server:start"?: (options: {
77
+ address: AddressInfo;
78
+ logger: AstroIntegrationLogger;
79
+ }) => void | Promise<void>;
52
80
  "astro:build:start"?: (options: {
53
81
  logger: AstroIntegrationLogger;
54
82
  }) => void | Promise<void>;
@@ -62,6 +90,48 @@ interface AstroIntegrationLogger {
62
90
  warn: (message: string) => void;
63
91
  error: (message: string) => void;
64
92
  }
93
+ /**
94
+ * Configuration for TypeScript type generation in Astro.
95
+ */
96
+ export interface AstroTypesConfig {
97
+ /**
98
+ * Enable type generation.
99
+ *
100
+ * @default false
101
+ */
102
+ enabled?: boolean;
103
+ /**
104
+ * Path to output generated TypeScript types.
105
+ * Relative to the Astro project root.
106
+ *
107
+ * @default 'src/types/api'
108
+ */
109
+ output?: string;
110
+ /**
111
+ * Path where the OpenAPI schema is exported by Litestar.
112
+ *
113
+ * @default 'openapi.json'
114
+ */
115
+ openapiPath?: string;
116
+ /**
117
+ * Path where route metadata is exported by Litestar.
118
+ *
119
+ * @default 'routes.json'
120
+ */
121
+ routesPath?: string;
122
+ /**
123
+ * Generate Zod schemas in addition to TypeScript types.
124
+ *
125
+ * @default false
126
+ */
127
+ generateZod?: boolean;
128
+ /**
129
+ * Debounce time in milliseconds for type regeneration.
130
+ *
131
+ * @default 300
132
+ */
133
+ debounce?: number;
134
+ }
65
135
  /**
66
136
  * Configuration options for the Litestar Astro integration.
67
137
  */
@@ -82,25 +152,14 @@ export interface LitestarAstroConfig {
82
152
  */
83
153
  apiPrefix?: string;
84
154
  /**
85
- * Path where TypeScript types are generated.
86
- * This should match the output path configured in your Litestar ViteConfig.
155
+ * Enable and configure TypeScript type generation.
87
156
  *
88
- * @example './src/generated/api'
89
- * @default './src/types/api'
90
- */
91
- typesPath?: string;
92
- /**
93
- * Path to the OpenAPI schema file exported by Litestar.
157
+ * When set to `true`, enables type generation with default settings.
158
+ * When set to an AstroTypesConfig object, enables type generation with custom settings.
94
159
  *
95
- * @default 'openapi.json'
96
- */
97
- openapiPath?: string;
98
- /**
99
- * Path to the routes metadata file exported by Litestar.
100
- *
101
- * @default 'routes.json'
160
+ * @default false
102
161
  */
103
- routesPath?: string;
162
+ types?: boolean | AstroTypesConfig;
104
163
  /**
105
164
  * Enable verbose logging for debugging.
106
165
  *
@@ -128,7 +187,10 @@ export interface LitestarAstroConfig {
128
187
  * litestar({
129
188
  * apiProxy: 'http://localhost:8000',
130
189
  * apiPrefix: '/api',
131
- * typesPath: './src/generated/api',
190
+ * types: {
191
+ * enabled: true,
192
+ * output: 'src/generated/api',
193
+ * },
132
194
  * }),
133
195
  * ],
134
196
  * });
package/dist/js/astro.js CHANGED
@@ -1,11 +1,65 @@
1
+ import { exec } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+ import colors from "picocolors";
6
+ import { resolveInstallHint } from "./install-hint.js";
7
+ import { debounce } from "./shared/debounce.js";
8
+ const execAsync = promisify(exec);
1
9
  function resolveConfig(config = {}) {
10
+ const runtimeConfigPath = process.env.LITESTAR_VITE_CONFIG_PATH;
11
+ let hotFile;
12
+ let proxyMode = "vite";
13
+ let port;
14
+ const envPort = process.env.VITE_PORT;
15
+ if (envPort) {
16
+ port = Number.parseInt(envPort, 10);
17
+ if (Number.isNaN(port)) {
18
+ port = void 0;
19
+ }
20
+ }
21
+ if (runtimeConfigPath && fs.existsSync(runtimeConfigPath)) {
22
+ try {
23
+ const json = JSON.parse(fs.readFileSync(runtimeConfigPath, "utf-8"));
24
+ const bundleDir = json.bundleDir ?? "public";
25
+ const hot = json.hotFile ?? "hot";
26
+ hotFile = path.resolve(process.cwd(), bundleDir, hot);
27
+ proxyMode = json.proxyMode ?? "vite";
28
+ if (json.port !== void 0) {
29
+ port = json.port;
30
+ }
31
+ } catch {
32
+ hotFile = void 0;
33
+ }
34
+ }
35
+ let typesConfig = false;
36
+ if (config.types === true) {
37
+ typesConfig = {
38
+ enabled: true,
39
+ output: "src/types/api",
40
+ openapiPath: "openapi.json",
41
+ routesPath: "routes.json",
42
+ generateZod: false,
43
+ debounce: 300
44
+ };
45
+ } else if (typeof config.types === "object" && config.types !== null) {
46
+ typesConfig = {
47
+ enabled: config.types.enabled ?? true,
48
+ output: config.types.output ?? "src/types/api",
49
+ openapiPath: config.types.openapiPath ?? "openapi.json",
50
+ routesPath: config.types.routesPath ?? "routes.json",
51
+ generateZod: config.types.generateZod ?? false,
52
+ debounce: config.types.debounce ?? 300
53
+ };
54
+ }
2
55
  return {
3
56
  apiProxy: config.apiProxy ?? "http://localhost:8000",
4
57
  apiPrefix: config.apiPrefix ?? "/api",
5
- typesPath: config.typesPath ?? "./src/types/api",
6
- openapiPath: config.openapiPath ?? "openapi.json",
7
- routesPath: config.routesPath ?? "routes.json",
8
- verbose: config.verbose ?? false
58
+ types: typesConfig,
59
+ verbose: config.verbose ?? false,
60
+ hotFile,
61
+ proxyMode,
62
+ port
9
63
  };
10
64
  }
11
65
  function createProxyPlugin(config) {
@@ -14,6 +68,12 @@ function createProxyPlugin(config) {
14
68
  config() {
15
69
  return {
16
70
  server: {
71
+ // Set the port from Python config/env to ensure Astro uses the expected port
72
+ // strictPort: true prevents Astro from auto-incrementing to a different port
73
+ ...config.port !== void 0 ? {
74
+ port: config.port,
75
+ strictPort: true
76
+ } : {},
17
77
  proxy: {
18
78
  [config.apiPrefix]: {
19
79
  target: config.apiProxy,
@@ -26,23 +86,240 @@ function createProxyPlugin(config) {
26
86
  }
27
87
  };
28
88
  }
89
+ async function emitRouteTypes(routesPath, outputDir) {
90
+ const contents = await fs.promises.readFile(routesPath, "utf-8");
91
+ const json = JSON.parse(contents);
92
+ const outDir = path.resolve(process.cwd(), outputDir);
93
+ await fs.promises.mkdir(outDir, { recursive: true });
94
+ const outFile = path.join(outDir, "routes.ts");
95
+ const banner = `// AUTO-GENERATED by litestar-vite. Do not edit.
96
+ /* eslint-disable */
97
+
98
+ `;
99
+ const routesData = json.routes || json;
100
+ const routeNames = Object.keys(routesData);
101
+ const routeNameType = routeNames.length > 0 ? routeNames.map((n) => `"${n}"`).join(" | ") : "never";
102
+ const routeParamTypes = [];
103
+ for (const [name, data] of Object.entries(routesData)) {
104
+ const routeData = data;
105
+ if (routeData.parameters && routeData.parameters.length > 0) {
106
+ const params = routeData.parameters.map((p) => `${p}: string | number`).join("; ");
107
+ routeParamTypes.push(` "${name}": { ${params} }`);
108
+ } else {
109
+ routeParamTypes.push(` "${name}": Record<string, never>`);
110
+ }
111
+ }
112
+ const body = `/**
113
+ * AUTO-GENERATED by litestar-vite.
114
+ *
115
+ * Exports:
116
+ * - routesMeta: full route metadata
117
+ * - routes: name -> uri map
118
+ * - serverRoutes: alias of routes for clarity in apps
119
+ * - route(): type-safe URL generator
120
+ * - hasRoute(): type guard
121
+ * - csrf helpers re-exported from litestar-vite-plugin/helpers
122
+ *
123
+ * @see https://litestar-vite.litestar.dev/
124
+ */
125
+ export const routesMeta = ${JSON.stringify(json, null, 2)} as const
126
+
127
+ /**
128
+ * Route name to URI mapping.
129
+ */
130
+ export const routes = ${JSON.stringify(Object.fromEntries(Object.entries(routesData).map(([name, data]) => [name, data.uri])), null, 2)} as const
131
+
132
+ /**
133
+ * Alias for server-injected route map (more descriptive for consumers).
134
+ */
135
+ export const serverRoutes = routes
136
+
137
+ /**
138
+ * All available route names.
139
+ */
140
+ export type RouteName = ${routeNameType}
141
+
142
+ /**
143
+ * Parameter types for each route.
144
+ */
145
+ export interface RouteParams {
146
+ ${routeParamTypes.join("\n")}
147
+ }
148
+
149
+ /**
150
+ * Generate a URL for a named route with type-safe parameters.
151
+ *
152
+ * @param name - The route name
153
+ * @param params - Route parameters (required if route has path parameters)
154
+ * @returns The generated URL
155
+ *
156
+ * @example
157
+ * \`\`\`ts
158
+ * import { route } from '@/generated/routes'
159
+ *
160
+ * // Route without parameters
161
+ * route('home') // "/"
162
+ *
163
+ * // Route with parameters
164
+ * route('user:detail', { user_id: 123 }) // "/users/123"
165
+ * \`\`\`
166
+ */
167
+ export function route<T extends RouteName>(
168
+ name: T,
169
+ ...args: RouteParams[T] extends Record<string, never> ? [] : [params: RouteParams[T]]
170
+ ): string {
171
+ let uri = routes[name] as string
172
+ const params = args[0] as Record<string, string | number> | undefined
173
+
174
+ if (params) {
175
+ for (const [key, value] of Object.entries(params)) {
176
+ // Handle both {param} and {param:type} syntax
177
+ uri = uri.replace(new RegExp(\`\\\\{\${key}(?::[^}]+)?\\\\}\`, "g"), String(value))
178
+ }
179
+ }
180
+
181
+ return uri
182
+ }
183
+
184
+ /**
185
+ * Check if a route name exists.
186
+ */
187
+ export function hasRoute(name: string): name is RouteName {
188
+ return name in routes
189
+ }
190
+
191
+ declare global {
192
+ interface Window {
193
+ /**
194
+ * Fully-typed route metadata injected by Litestar.
195
+ */
196
+ __LITESTAR_ROUTES__?: typeof routesMeta
197
+ /**
198
+ * Simple route map (name -> uri) for legacy consumers.
199
+ */
200
+ routes?: typeof routes
201
+ serverRoutes?: typeof serverRoutes
202
+ }
203
+ // eslint-disable-next-line no-var
204
+ var routes: typeof routes | undefined
205
+ var serverRoutes: typeof serverRoutes | undefined
206
+ }
207
+
208
+ // Re-export helper functions from litestar-vite-plugin
209
+ // These work with the routes defined above
210
+ export { getCsrfToken, csrfHeaders, csrfFetch } from "litestar-vite-plugin/helpers"
211
+ `;
212
+ await fs.promises.writeFile(outFile, `${banner}${body}`, "utf-8");
213
+ }
214
+ function createTypeGenerationPlugin(typesConfig) {
215
+ let server = null;
216
+ let isGenerating = false;
217
+ async function runTypeGeneration() {
218
+ if (isGenerating) {
219
+ return false;
220
+ }
221
+ isGenerating = true;
222
+ const startTime = Date.now();
223
+ try {
224
+ const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
225
+ if (!fs.existsSync(openapiPath)) {
226
+ console.log(colors.cyan("[litestar-astro]"), colors.yellow("OpenAPI schema not found:"), typesConfig.openapiPath);
227
+ return false;
228
+ }
229
+ console.log(colors.cyan("[litestar-astro]"), colors.dim("Generating TypeScript types..."));
230
+ const args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", typesConfig.output];
231
+ if (typesConfig.generateZod) {
232
+ args.push("--plugins", "zod", "@hey-api/typescript");
233
+ }
234
+ await execAsync(`npx ${args.join(" ")}`, {
235
+ cwd: process.cwd()
236
+ });
237
+ const routesPath = path.resolve(process.cwd(), typesConfig.routesPath);
238
+ if (fs.existsSync(routesPath)) {
239
+ await emitRouteTypes(routesPath, typesConfig.output);
240
+ }
241
+ const duration = Date.now() - startTime;
242
+ console.log(colors.cyan("[litestar-astro]"), colors.green("Types generated"), colors.dim(`in ${duration}ms`));
243
+ if (server) {
244
+ server.ws.send({
245
+ type: "custom",
246
+ event: "litestar:types-updated",
247
+ data: {
248
+ output: typesConfig.output,
249
+ timestamp: Date.now()
250
+ }
251
+ });
252
+ }
253
+ return true;
254
+ } catch (error) {
255
+ const message = error instanceof Error ? error.message : String(error);
256
+ if (message.includes("not found") || message.includes("ENOENT")) {
257
+ console.log(colors.cyan("[litestar-astro]"), colors.yellow("@hey-api/openapi-ts not installed"), "- run:", resolveInstallHint());
258
+ } else {
259
+ console.error(colors.cyan("[litestar-astro]"), colors.red("Type generation failed:"), message);
260
+ }
261
+ return false;
262
+ } finally {
263
+ isGenerating = false;
264
+ }
265
+ }
266
+ const debouncedRunTypeGeneration = debounce(runTypeGeneration, typesConfig.debounce);
267
+ return {
268
+ name: "litestar-astro-types",
269
+ enforce: "pre",
270
+ configureServer(devServer) {
271
+ server = devServer;
272
+ console.log(colors.cyan("[litestar-astro]"), colors.dim("Watching for schema changes:"), colors.yellow(typesConfig.openapiPath));
273
+ },
274
+ handleHotUpdate({ file }) {
275
+ if (!typesConfig.enabled) {
276
+ return;
277
+ }
278
+ const relativePath = path.relative(process.cwd(), file);
279
+ const openapiPath = typesConfig.openapiPath.replace(/^\.\//, "");
280
+ const routesPath = typesConfig.routesPath.replace(/^\.\//, "");
281
+ if (relativePath === openapiPath || relativePath === routesPath || file.endsWith(openapiPath) || file.endsWith(routesPath)) {
282
+ console.log(colors.cyan("[litestar-astro]"), colors.dim("Schema changed:"), colors.yellow(relativePath));
283
+ debouncedRunTypeGeneration();
284
+ }
285
+ }
286
+ };
287
+ }
29
288
  function litestarAstro(userConfig = {}) {
30
289
  const config = resolveConfig(userConfig);
31
290
  return {
32
291
  name: "litestar-vite",
33
292
  hooks: {
34
- "astro:config:setup": ({ updateConfig, logger }) => {
293
+ "astro:config:setup": ({ updateConfig, logger, command }) => {
35
294
  if (config.verbose) {
36
295
  logger.info("Configuring Litestar integration");
37
296
  logger.info(` API Proxy: ${config.apiProxy}`);
38
297
  logger.info(` API Prefix: ${config.apiPrefix}`);
39
- logger.info(` Types Path: ${config.typesPath}`);
298
+ if (config.types !== false) {
299
+ logger.info(` Types Output: ${config.types.output}`);
300
+ }
301
+ if (config.port !== void 0) {
302
+ logger.info(` Port: ${config.port}`);
303
+ }
304
+ }
305
+ const plugins = [createProxyPlugin(config)];
306
+ if (config.types !== false && config.types.enabled) {
307
+ plugins.push(createTypeGenerationPlugin(config.types));
40
308
  }
41
- updateConfig({
309
+ const configUpdate = {
42
310
  vite: {
43
- plugins: [createProxyPlugin(config)]
311
+ plugins
44
312
  }
45
- });
313
+ };
314
+ if (command === "dev" && config.port !== void 0) {
315
+ configUpdate.server = {
316
+ port: config.port
317
+ };
318
+ if (config.verbose) {
319
+ logger.info(`Setting Astro server port to ${config.port}`);
320
+ }
321
+ }
322
+ updateConfig(configUpdate);
46
323
  logger.info(`Litestar integration configured - proxying ${config.apiPrefix}/* to ${config.apiProxy}`);
47
324
  },
48
325
  "astro:server:setup": ({ server, logger }) => {
@@ -58,6 +335,20 @@ function litestarAstro(userConfig = {}) {
58
335
  });
59
336
  }
60
337
  },
338
+ // Write hotfile AFTER server starts listening (astro:server:start fires after listen())
339
+ // Always write hotfile - proxy mode needs it for dynamic target discovery
340
+ "astro:server:start": ({ address, logger }) => {
341
+ if (config.hotFile) {
342
+ const rawAddr = address.address;
343
+ const host = rawAddr === "::" || rawAddr === "::1" || rawAddr === "0.0.0.0" || rawAddr === "127.0.0.1" ? "localhost" : rawAddr;
344
+ const url = `http://${host}:${address.port}`;
345
+ fs.mkdirSync(path.dirname(config.hotFile), { recursive: true });
346
+ fs.writeFileSync(config.hotFile, url);
347
+ if (config.verbose) {
348
+ logger.info(`Hotfile written: ${config.hotFile} -> ${url}`);
349
+ }
350
+ }
351
+ },
61
352
  "astro:build:start": ({ logger }) => {
62
353
  logger.info("Building with Litestar integration");
63
354
  logger.info(` Make sure your Litestar backend is accessible at: ${config.apiProxy}`);
@@ -20,5 +20,5 @@
20
20
  *
21
21
  * @module
22
22
  */
23
- export { getCsrfToken, csrfHeaders, csrfFetch } from "./csrf.js";
24
- export { route, getRoutes, toRoute, currentRoute, isRoute, isCurrentRoute, getRelativeUrlPath, type RouteDefinition, type RoutesMap, } from "./routes.js";
23
+ export { csrfFetch, csrfHeaders, getCsrfToken } from "./csrf.js";
24
+ export { currentRoute, getRelativeUrlPath, getRoutes, isCurrentRoute, isRoute, LITESTAR, type LitestarHelpers, type RouteDefinition, type RoutesMap, route, toRoute, } from "./routes.js";
@@ -21,6 +21,6 @@
21
21
  * @module
22
22
  */
23
23
  // CSRF utilities
24
- export { getCsrfToken, csrfHeaders, csrfFetch } from "./csrf.js";
24
+ export { csrfFetch, csrfHeaders, getCsrfToken } from "./csrf.js";
25
25
  // Route utilities
26
- export { route, getRoutes, toRoute, currentRoute, isRoute, isCurrentRoute, getRelativeUrlPath, } from "./routes.js";
26
+ export { currentRoute, getRelativeUrlPath, getRoutes, isCurrentRoute, isRoute, LITESTAR, route, toRoute, } from "./routes.js";