litestar-vite-plugin 0.15.0-beta.1 → 0.15.0-beta.3

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
@@ -166,4 +183,5 @@ litestar assets generate-types # one-off or CI
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);
@@ -260,7 +260,6 @@ export interface BridgeSchema {
260
260
  litestarVersion: string;
261
261
  }
262
262
  type DevServerUrl = `${"http" | "https"}://${string}:${number}`;
263
- export declare const refreshPaths: string[];
264
263
  /**
265
264
  * Litestar plugin for Vite.
266
265
  *
package/dist/js/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { exec } from "node:child_process";
2
- import { createHash } from "node:crypto";
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
4
  import { fileURLToPath } from "node:url";
@@ -10,6 +9,7 @@ import fullReload from "vite-plugin-full-reload";
10
9
  import { resolveInstallHint, resolvePackageExecutor } from "./install-hint.js";
11
10
  import { checkBackendAvailability, loadLitestarMeta } from "./litestar-meta.js";
12
11
  import { debounce } from "./shared/debounce.js";
12
+ import { emitRouteTypes } from "./shared/emit-route-types.js";
13
13
  import { formatPath } from "./shared/format-path.js";
14
14
  import { createLogger } from "./shared/logger.js";
15
15
  const execAsync = promisify(exec);
@@ -367,13 +367,6 @@ function ensureCommandShouldRunInEnvironment(command, env, mode) {
367
367
  );
368
368
  }
369
369
  }
370
- function _pluginVersion() {
371
- try {
372
- return JSON.parse(fs.readFileSync(path.join(dirname(), "../package.json")).toString())?.version;
373
- } catch {
374
- return "";
375
- }
376
- }
377
370
  function loadPythonDefaults() {
378
371
  const isTestEnv = Boolean(process.env.VITEST || process.env.VITE_TEST || process.env.NODE_ENV === "test");
379
372
  let configPath = process.env.LITESTAR_VITE_CONFIG_PATH;
@@ -587,11 +580,15 @@ function resolveInput(config, ssr) {
587
580
  }
588
581
  return config.input;
589
582
  }
583
+ function isAbsolutePath(path2) {
584
+ return path2.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(path2);
585
+ }
590
586
  function resolveOutDir(config, ssr) {
591
- if (ssr) {
592
- return config.ssrOutDir.replace(/^\/+/, "").replace(/\/+$/, "");
587
+ const dir = ssr ? config.ssrOutDir : config.bundleDir;
588
+ if (isAbsolutePath(dir)) {
589
+ return dir.replace(/[\\/]+$/, "");
593
590
  }
594
- return config.bundleDir.replace(/^\/+/, "").replace(/\/+$/, "");
591
+ return dir.replace(/^\/+/, "").replace(/\/+$/, "");
595
592
  }
596
593
  function resolveFullReloadConfig({ refresh: config }) {
597
594
  if (typeof config === "boolean") {
@@ -760,139 +757,6 @@ declare module "litestar-vite/inertia" {
760
757
  `;
761
758
  await fs.promises.writeFile(outFile, body, "utf-8");
762
759
  }
763
- async function emitRouteTypes(routesPath, outputDir, globalRoute = false) {
764
- const contents = await fs.promises.readFile(routesPath, "utf-8");
765
- const json = JSON.parse(contents);
766
- const outDir = path.resolve(process.cwd(), outputDir);
767
- await fs.promises.mkdir(outDir, { recursive: true });
768
- const outFile = path.join(outDir, "routes.ts");
769
- const banner = `// AUTO-GENERATED by litestar-vite. Do not edit.
770
- /* eslint-disable */
771
-
772
- `;
773
- const routesData = json.routes || json;
774
- const routeNames = Object.keys(routesData);
775
- const routeNameType = routeNames.length > 0 ? routeNames.map((n) => `"${n}"`).join(" | ") : "never";
776
- const routeParamTypes = [];
777
- for (const [name, data] of Object.entries(routesData)) {
778
- const routeData = data;
779
- if (routeData.parameters && routeData.parameters.length > 0) {
780
- const params = routeData.parameters.map((p) => `${p}: string | number`).join("; ");
781
- routeParamTypes.push(` "${name}": { ${params} }`);
782
- } else {
783
- routeParamTypes.push(` "${name}": Record<string, never>`);
784
- }
785
- }
786
- const body = `/**
787
- * AUTO-GENERATED by litestar-vite.
788
- *
789
- * Exports:
790
- * - routesMeta: full route metadata
791
- * - routes: name -> uri map
792
- * - serverRoutes: alias of routes for clarity in apps
793
- * - route(): type-safe URL generator
794
- * - hasRoute(): type guard
795
- * - csrf helpers re-exported from litestar-vite-plugin/helpers
796
- *
797
- * @see https://litestar-vite.litestar.dev/
798
- */
799
- export const routesMeta = ${JSON.stringify(json, null, 2)} as const
800
-
801
- /**
802
- * Route name to URI mapping.
803
- */
804
- export const routes = ${JSON.stringify(Object.fromEntries(Object.entries(routesData).map(([name, data]) => [name, data.uri])), null, 2)} as const
805
-
806
- /**
807
- * Alias for server-injected route map (more descriptive for consumers).
808
- */
809
- export const serverRoutes = routes
810
-
811
- /**
812
- * All available route names.
813
- */
814
- export type RouteName = ${routeNameType}
815
-
816
- /**
817
- * Parameter types for each route.
818
- */
819
- export interface RouteParams {
820
- ${routeParamTypes.join("\n")}
821
- }
822
-
823
- /**
824
- * Generate a URL for a named route with type-safe parameters.
825
- *
826
- * @param name - The route name
827
- * @param params - Route parameters (required if route has path parameters)
828
- * @returns The generated URL
829
- *
830
- * @example
831
- * \`\`\`ts
832
- * import { route } from '@/generated/routes'
833
- *
834
- * // Route without parameters
835
- * route('home') // "/"
836
- *
837
- * // Route with parameters
838
- * route('user:detail', { user_id: 123 }) // "/users/123"
839
- * \`\`\`
840
- */
841
- export function route<T extends RouteName>(
842
- name: T,
843
- ...args: RouteParams[T] extends Record<string, never> ? [] : [params: RouteParams[T]]
844
- ): string {
845
- let uri = routes[name] as string
846
- const params = args[0] as Record<string, string | number> | undefined
847
-
848
- if (params) {
849
- for (const [key, value] of Object.entries(params)) {
850
- // Handle both {param} and {param:type} syntax
851
- uri = uri.replace(new RegExp(\`\\\\{\${key}(?::[^}]+)?\\\\}\`, "g"), String(value))
852
- }
853
- }
854
-
855
- return uri
856
- }
857
-
858
- /**
859
- * Check if a route name exists.
860
- */
861
- export function hasRoute(name: string): name is RouteName {
862
- return name in routes
863
- }
864
-
865
- declare global {
866
- interface Window {
867
- /**
868
- * Fully-typed route metadata injected by Litestar.
869
- */
870
- __LITESTAR_ROUTES__?: typeof routesMeta
871
- /**
872
- * Simple route map (name -> uri) for legacy consumers.
873
- */
874
- routes?: Record<string, string>
875
- serverRoutes?: Record<string, string>
876
- /**
877
- * Global route helper (available when globalRoute=true in TypeGenConfig).
878
- * @see route
879
- */
880
- route?: typeof route
881
- }
882
- }
883
-
884
- // Re-export helper functions from litestar-vite-plugin
885
- // These work with the routes defined above
886
- export { getCsrfToken, csrfHeaders, csrfFetch } from "litestar-vite-plugin/helpers"
887
- ${globalRoute ? `
888
- // Register route() globally on window for Laravel/Ziggy-style usage
889
- if (typeof window !== "undefined") {
890
- window.route = route
891
- }
892
- ` : ""}
893
- `;
894
- await fs.promises.writeFile(outFile, `${banner}${body}`, "utf-8");
895
- }
896
760
  function resolveTypeGenerationPlugin(typesConfig, executor, hasPythonConfig) {
897
761
  let lastTypesHash = null;
898
762
  let lastRoutesHash = null;
@@ -953,7 +817,7 @@ function resolveTypeGenerationPlugin(typesConfig, executor, hasPythonConfig) {
953
817
  generated = true;
954
818
  }
955
819
  if (fs.existsSync(routesPath)) {
956
- await emitRouteTypes(routesPath, typesConfig.output, typesConfig.globalRoute ?? false);
820
+ await emitRouteTypes(routesPath, typesConfig.output, { globalRoute: typesConfig.globalRoute ?? false });
957
821
  generated = true;
958
822
  }
959
823
  if (fs.existsSync(pagePropsPath)) {
@@ -1054,7 +918,7 @@ Solutions:
1054
918
  if (resolvedConfig) {
1055
919
  resolvedConfig.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("schema changed:")} ${colors.yellow(relativePath)}`);
1056
920
  }
1057
- const newHash = await hashFile(file);
921
+ const newHash = await getFileMtime(file);
1058
922
  if (isOpenapi) {
1059
923
  if (lastTypesHash === newHash) return;
1060
924
  lastTypesHash = newHash;
@@ -1070,9 +934,9 @@ Solutions:
1070
934
  }
1071
935
  };
1072
936
  }
1073
- async function hashFile(filePath) {
1074
- const content = await fs.promises.readFile(filePath);
1075
- return createHash("sha1").update(content).digest("hex");
937
+ async function getFileMtime(filePath) {
938
+ const stat = await fs.promises.stat(filePath);
939
+ return stat.mtimeMs.toString();
1076
940
  }
1077
941
  function resolveDevServerUrl(address, config, userConfig) {
1078
942
  const configHmrProtocol = typeof config.server.hmr === "object" ? config.server.hmr.protocol : null;
@@ -1200,6 +1064,5 @@ function normalizeAssetUrl(url) {
1200
1064
  return withTrailing;
1201
1065
  }
1202
1066
  export {
1203
- litestar as default,
1204
- refreshPaths
1067
+ litestar as default
1205
1068
  };