litestar-vite-plugin 0.15.0-alpha.7 → 0.15.0-beta.2

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
@@ -38,6 +38,23 @@ litestar run --reload # Vite dev server is proxied automatically
38
38
 
39
39
  Scaffold a frontend: `litestar assets init --template vue` (or `react`, `svelte`, `htmx`, `react-inertia`, `vue-inertia`, `angular`, `astro`, `nuxt`, `sveltekit`).
40
40
 
41
+ ## Development
42
+
43
+ To contribute or run the development project:
44
+
45
+ ```bash
46
+ # Install all dependencies and build packages
47
+ make install && make build
48
+
49
+ # Install frontend dependencies for an example
50
+ uv run litestar --app-dir examples/vue-inertia assets install
51
+
52
+ # Run the development server
53
+ uv run litestar --app-dir examples/vue-inertia run
54
+ ```
55
+
56
+ Replace `vue-inertia` with any other example: `vue`, `react`, `svelte`, `react-inertia`, `htmx`, `angular`, `astro`, `nuxt`, or `sveltekit`.
57
+
41
58
  ## Template / HTMX
42
59
 
43
60
  ```python
@@ -160,10 +177,11 @@ litestar assets generate-types # one-off or CI
160
177
 
161
178
  - Prints Python vs Vite config snapshot (asset URLs, bundle/hot paths, ports, modes).
162
179
  - Flags missing hot file (dev proxy), missing manifest (prod), type-gen exports, env/config mismatches, and plugin install issues.
163
- - `--fix` can rewrite simple vite.config values (assetUrl, bundleDirectory, hotFile, type paths) after creating a backup.
180
+ - `--fix` can rewrite simple vite.config values (assetUrl, bundleDir, hotFile, type paths) after creating a backup.
164
181
 
165
182
  ## Links
166
183
 
167
184
  - Docs: <https://litestar-org.github.io/litestar-vite/>
168
185
  - Examples: `examples/` (react, vue, svelte, react-inertia, vue-inertia, astro, nuxt, sveltekit, htmx)
186
+ - Real-world example: [litestar-fullstack](https://github.com/litestar-org/litestar-fullstack) - Full-featured application using litestar-vite
169
187
  - Issues: <https://github.com/litestar-org/litestar-vite/issues/>
package/dist/js/astro.js CHANGED
@@ -1,11 +1,6 @@
1
- import { exec } from "node:child_process";
2
1
  import fs from "node:fs";
3
2
  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);
3
+ import { createTypeGenerationPlugin } from "./shared/create-type-gen-plugin.js";
9
4
  function resolveConfig(config = {}) {
10
5
  const runtimeConfigPath = process.env.LITESTAR_VITE_CONFIG_PATH;
11
6
  let hotFile;
@@ -91,229 +86,6 @@ function createProxyPlugin(config) {
91
86
  }
92
87
  };
93
88
  }
94
- async function emitRouteTypes(routesPath, outputDir) {
95
- const contents = await fs.promises.readFile(routesPath, "utf-8");
96
- const json = JSON.parse(contents);
97
- const outDir = path.resolve(process.cwd(), outputDir);
98
- await fs.promises.mkdir(outDir, { recursive: true });
99
- const outFile = path.join(outDir, "routes.ts");
100
- const banner = `// AUTO-GENERATED by litestar-vite. Do not edit.
101
- /* eslint-disable */
102
-
103
- `;
104
- const routesData = json.routes || json;
105
- const routeNames = Object.keys(routesData);
106
- const routeNameType = routeNames.length > 0 ? routeNames.map((n) => `"${n}"`).join(" | ") : "never";
107
- const routeParamTypes = [];
108
- for (const [name, data] of Object.entries(routesData)) {
109
- const routeData = data;
110
- if (routeData.parameters && routeData.parameters.length > 0) {
111
- const params = routeData.parameters.map((p) => `${p}: string | number`).join("; ");
112
- routeParamTypes.push(` "${name}": { ${params} }`);
113
- } else {
114
- routeParamTypes.push(` "${name}": Record<string, never>`);
115
- }
116
- }
117
- const body = `/**
118
- * AUTO-GENERATED by litestar-vite.
119
- *
120
- * Exports:
121
- * - routesMeta: full route metadata
122
- * - routes: name -> uri map
123
- * - serverRoutes: alias of routes for clarity in apps
124
- * - route(): type-safe URL generator
125
- * - hasRoute(): type guard
126
- * - csrf helpers re-exported from litestar-vite-plugin/helpers
127
- *
128
- * @see https://litestar-vite.litestar.dev/
129
- */
130
- export const routesMeta = ${JSON.stringify(json, null, 2)} as const
131
-
132
- /**
133
- * Route name to URI mapping.
134
- */
135
- export const routes = ${JSON.stringify(Object.fromEntries(Object.entries(routesData).map(([name, data]) => [name, data.uri])), null, 2)} as const
136
-
137
- /**
138
- * Alias for server-injected route map (more descriptive for consumers).
139
- */
140
- export const serverRoutes = routes
141
-
142
- /**
143
- * All available route names.
144
- */
145
- export type RouteName = ${routeNameType}
146
-
147
- /**
148
- * Parameter types for each route.
149
- */
150
- export interface RouteParams {
151
- ${routeParamTypes.join("\n")}
152
- }
153
-
154
- /**
155
- * Generate a URL for a named route with type-safe parameters.
156
- *
157
- * @param name - The route name
158
- * @param params - Route parameters (required if route has path parameters)
159
- * @returns The generated URL
160
- *
161
- * @example
162
- * \`\`\`ts
163
- * import { route } from '@/generated/routes'
164
- *
165
- * // Route without parameters
166
- * route('home') // "/"
167
- *
168
- * // Route with parameters
169
- * route('user:detail', { user_id: 123 }) // "/users/123"
170
- * \`\`\`
171
- */
172
- export function route<T extends RouteName>(
173
- name: T,
174
- ...args: RouteParams[T] extends Record<string, never> ? [] : [params: RouteParams[T]]
175
- ): string {
176
- let uri = routes[name] as string
177
- const params = args[0] as Record<string, string | number> | undefined
178
-
179
- if (params) {
180
- for (const [key, value] of Object.entries(params)) {
181
- // Handle both {param} and {param:type} syntax
182
- uri = uri.replace(new RegExp(\`\\\\{\${key}(?::[^}]+)?\\\\}\`, "g"), String(value))
183
- }
184
- }
185
-
186
- return uri
187
- }
188
-
189
- /**
190
- * Check if a route name exists.
191
- */
192
- export function hasRoute(name: string): name is RouteName {
193
- return name in routes
194
- }
195
-
196
- declare global {
197
- interface Window {
198
- /**
199
- * Fully-typed route metadata injected by Litestar.
200
- */
201
- __LITESTAR_ROUTES__?: typeof routesMeta
202
- /**
203
- * Simple route map (name -> uri) for legacy consumers.
204
- */
205
- routes?: typeof routes
206
- serverRoutes?: typeof serverRoutes
207
- }
208
- // eslint-disable-next-line no-var
209
- var routes: typeof routes | undefined
210
- var serverRoutes: typeof serverRoutes | undefined
211
- }
212
-
213
- // Re-export helper functions from litestar-vite-plugin
214
- // These work with the routes defined above
215
- export { getCsrfToken, csrfHeaders, csrfFetch } from "litestar-vite-plugin/helpers"
216
- `;
217
- await fs.promises.writeFile(outFile, `${banner}${body}`, "utf-8");
218
- }
219
- function createTypeGenerationPlugin(typesConfig) {
220
- let server = null;
221
- let isGenerating = false;
222
- async function runTypeGeneration() {
223
- if (isGenerating) {
224
- return false;
225
- }
226
- isGenerating = true;
227
- const startTime = Date.now();
228
- try {
229
- const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
230
- if (!fs.existsSync(openapiPath)) {
231
- console.log(colors.cyan("[litestar-astro]"), colors.yellow("OpenAPI schema not found:"), typesConfig.openapiPath);
232
- return false;
233
- }
234
- console.log(colors.cyan("[litestar-astro]"), colors.dim("Generating TypeScript types..."));
235
- const projectRoot = process.cwd();
236
- const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
237
- const configPath = candidates.find((p) => fs.existsSync(p)) || null;
238
- let args;
239
- if (configPath) {
240
- console.log(colors.cyan("[litestar-astro]"), colors.dim("Using config:"), configPath);
241
- args = ["@hey-api/openapi-ts", "--file", configPath];
242
- } else {
243
- args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", typesConfig.output];
244
- const plugins = ["@hey-api/typescript", "@hey-api/schemas"];
245
- if (typesConfig.generateSdk) {
246
- plugins.push("@hey-api/sdk", "@hey-api/client-fetch");
247
- }
248
- if (typesConfig.generateZod) {
249
- plugins.push("zod");
250
- }
251
- if (plugins.length) {
252
- args.push("--plugins", ...plugins);
253
- }
254
- }
255
- await execAsync(`npx ${args.join(" ")}`, {
256
- cwd: projectRoot
257
- });
258
- const routesPath = path.resolve(process.cwd(), typesConfig.routesPath);
259
- if (fs.existsSync(routesPath)) {
260
- await emitRouteTypes(routesPath, typesConfig.output);
261
- }
262
- const duration = Date.now() - startTime;
263
- console.log(colors.cyan("[litestar-astro]"), colors.green("Types generated"), colors.dim(`in ${duration}ms`));
264
- if (server) {
265
- server.ws.send({
266
- type: "custom",
267
- event: "litestar:types-updated",
268
- data: {
269
- output: typesConfig.output,
270
- timestamp: Date.now()
271
- }
272
- });
273
- }
274
- return true;
275
- } catch (error) {
276
- const message = error instanceof Error ? error.message : String(error);
277
- if (message.includes("not found") || message.includes("ENOENT")) {
278
- console.log(colors.cyan("[litestar-astro]"), colors.yellow("@hey-api/openapi-ts not installed"), "- run:", resolveInstallHint());
279
- } else {
280
- console.error(colors.cyan("[litestar-astro]"), colors.red("Type generation failed:"), message);
281
- }
282
- return false;
283
- } finally {
284
- isGenerating = false;
285
- }
286
- }
287
- const debouncedRunTypeGeneration = debounce(runTypeGeneration, typesConfig.debounce);
288
- return {
289
- name: "litestar-astro-types",
290
- enforce: "pre",
291
- configureServer(devServer) {
292
- server = devServer;
293
- console.log(colors.cyan("[litestar-astro]"), colors.dim("Watching for schema changes:"), colors.yellow(typesConfig.openapiPath));
294
- },
295
- async buildStart() {
296
- if (typesConfig.enabled) {
297
- const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
298
- if (fs.existsSync(openapiPath)) {
299
- await runTypeGeneration();
300
- }
301
- }
302
- },
303
- handleHotUpdate({ file }) {
304
- if (!typesConfig.enabled) {
305
- return;
306
- }
307
- const relativePath = path.relative(process.cwd(), file);
308
- const openapiPath = typesConfig.openapiPath.replace(/^\.\//, "");
309
- const routesPath = typesConfig.routesPath.replace(/^\.\//, "");
310
- if (relativePath === openapiPath || relativePath === routesPath || file.endsWith(openapiPath) || file.endsWith(routesPath)) {
311
- console.log(colors.cyan("[litestar-astro]"), colors.dim("Schema changed:"), colors.yellow(relativePath));
312
- debouncedRunTypeGeneration();
313
- }
314
- }
315
- };
316
- }
317
89
  function litestarAstro(userConfig = {}) {
318
90
  const config = resolveConfig(userConfig);
319
91
  return {
@@ -333,7 +105,13 @@ function litestarAstro(userConfig = {}) {
333
105
  }
334
106
  const plugins = [createProxyPlugin(config)];
335
107
  if (config.types !== false && config.types.enabled) {
336
- plugins.push(createTypeGenerationPlugin(config.types));
108
+ plugins.push(
109
+ createTypeGenerationPlugin(config.types, {
110
+ frameworkName: "litestar-astro",
111
+ pluginName: "litestar-astro-types",
112
+ clientPlugin: "@hey-api/client-fetch"
113
+ })
114
+ );
337
115
  }
338
116
  const configUpdate = {
339
117
  vite: {
@@ -63,6 +63,11 @@ interface Dir {
63
63
  match: (a: Attr) => boolean;
64
64
  create: (el: Element, a: Attr) => Handler | null;
65
65
  }
66
- export declare function setDebug(_on: boolean): void;
66
+ /**
67
+ * Enable or disable debug logging for the HTMX extension.
68
+ *
69
+ * @param on - Whether to enable debug logging.
70
+ */
71
+ export declare function setDebug(on: boolean): void;
67
72
  export declare function addDirective(dir: Dir): void;
68
73
  export {};
@@ -28,7 +28,7 @@
28
28
  * @module
29
29
  */
30
30
  import { getCsrfToken } from "./csrf.js";
31
- const DEBUG = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
31
+ let debug = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
32
32
  const cache = new Map();
33
33
  const memoStore = new WeakMap();
34
34
  // =============================================================================
@@ -68,7 +68,7 @@ export function registerHtmxExtension() {
68
68
  return [];
69
69
  },
70
70
  });
71
- if (DEBUG)
71
+ if (debug)
72
72
  console.log("[litestar] htmx extension registered");
73
73
  }
74
74
  // =============================================================================
@@ -483,8 +483,13 @@ function removeBetween(start, end) {
483
483
  // =============================================================================
484
484
  // Public API
485
485
  // =============================================================================
486
- export function setDebug(_on) {
487
- // Debug flag is const, this is a no-op placeholder
486
+ /**
487
+ * Enable or disable debug logging for the HTMX extension.
488
+ *
489
+ * @param on - Whether to enable debug logging.
490
+ */
491
+ export function setDebug(on) {
492
+ debug = on;
488
493
  }
489
494
  export function addDirective(dir) {
490
495
  directives.push(dir);
@@ -54,6 +54,16 @@ export interface TypesConfig {
54
54
  * @default false
55
55
  */
56
56
  generateSdk?: boolean;
57
+ /**
58
+ * Register route() function globally on window object.
59
+ *
60
+ * When true, the generated routes.ts will include code that registers
61
+ * the type-safe route() function on `window.route`, similar to Laravel's
62
+ * Ziggy library. This allows using route() without imports.
63
+ *
64
+ * @default false
65
+ */
66
+ globalRoute?: boolean;
57
67
  /**
58
68
  * Debounce time in milliseconds for type regeneration.
59
69
  * Prevents regeneration from running too frequently when
@@ -79,7 +89,7 @@ export interface PluginConfig {
79
89
  *
80
90
  * @default 'public/dist'
81
91
  */
82
- bundleDirectory?: string;
92
+ bundleDir?: string;
83
93
  /**
84
94
  * Vite's public directory for static, unprocessed assets.
85
95
  * Mirrors Vite's `publicDir` option.
@@ -90,13 +100,13 @@ export interface PluginConfig {
90
100
  /**
91
101
  * Litestar's public assets directory. These are the assets that Vite will serve when developing.
92
102
  *
93
- * @default 'resources'
103
+ * @default 'src'
94
104
  */
95
- resourceDirectory?: string;
105
+ resourceDir?: string;
96
106
  /**
97
107
  * The path to the "hot" file.
98
108
  *
99
- * @default `${bundleDirectory}/hot`
109
+ * @default `${bundleDir}/hot`
100
110
  */
101
111
  hotFile?: string;
102
112
  /**
@@ -106,9 +116,9 @@ export interface PluginConfig {
106
116
  /**
107
117
  * The directory where the SSR bundle should be written.
108
118
  *
109
- * @default '${bundleDirectory}/bootstrap/ssr'
119
+ * @default '${bundleDir}/bootstrap/ssr'
110
120
  */
111
- ssrOutputDirectory?: string;
121
+ ssrOutDir?: string;
112
122
  /**
113
123
  * Configuration for performing full page refresh on python (or other) file changes.
114
124
  *
@@ -128,6 +138,18 @@ export interface PluginConfig {
128
138
  * @default true
129
139
  */
130
140
  autoDetectIndex?: boolean;
141
+ /**
142
+ * Enable Inertia mode, which disables index.html auto-detection.
143
+ *
144
+ * In Inertia apps, the backend (Litestar) serves all HTML responses.
145
+ * When enabled, direct access to the Vite dev server will show a placeholder
146
+ * page directing users to access the app through the backend.
147
+ *
148
+ * Auto-detected from `.litestar.json` when mode is "inertia".
149
+ *
150
+ * @default false (auto-detected from .litestar.json)
151
+ */
152
+ inertiaMode?: boolean;
131
153
  /**
132
154
  * Transform the code while serving.
133
155
  */
@@ -193,8 +215,51 @@ interface RefreshConfig {
193
215
  paths: string[];
194
216
  config?: FullReloadConfig;
195
217
  }
218
+ /**
219
+ * Bridge schema for `.litestar.json` - the shared configuration contract
220
+ * between Python (Litestar) and TypeScript (Vite plugin).
221
+ *
222
+ * Python writes this file on startup; TypeScript reads it as defaults.
223
+ * Field names use camelCase (JavaScript convention) and match exactly
224
+ * between the JSON file and this TypeScript interface.
225
+ *
226
+ * Precedence: vite.config.ts > .litestar.json > hardcoded defaults
227
+ */
228
+ export interface BridgeSchema {
229
+ assetUrl: string;
230
+ bundleDir: string;
231
+ resourceDir: string;
232
+ publicDir: string;
233
+ hotFile: string;
234
+ manifest: string;
235
+ mode: "spa" | "inertia" | "ssr" | "hybrid";
236
+ proxyMode: "vite_proxy" | "vite_direct" | "external_proxy";
237
+ host: string;
238
+ port: number;
239
+ protocol: "http" | "https";
240
+ ssrEnabled: boolean;
241
+ ssrOutDir: string | null;
242
+ types: {
243
+ enabled: boolean;
244
+ output: string;
245
+ openapiPath: string;
246
+ routesPath: string;
247
+ pagePropsPath?: string;
248
+ generateZod: boolean;
249
+ generateSdk: boolean;
250
+ globalRoute: boolean;
251
+ } | null;
252
+ executor: "node" | "bun" | "deno" | "yarn" | "pnpm";
253
+ logging: {
254
+ level: "quiet" | "normal" | "verbose";
255
+ showPathsAbsolute: boolean;
256
+ suppressNpmOutput: boolean;
257
+ suppressViteBanner: boolean;
258
+ timestamps: boolean;
259
+ } | null;
260
+ litestarVersion: string;
261
+ }
196
262
  type DevServerUrl = `${"http" | "https"}://${string}:${number}`;
197
- export declare const refreshPaths: string[];
198
263
  /**
199
264
  * Litestar plugin for Vite.
200
265
  *