litestar-vite-plugin 0.15.0-beta.1 → 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 +18 -0
- package/dist/js/astro.js +8 -230
- package/dist/js/helpers/htmx.d.ts +6 -1
- package/dist/js/helpers/htmx.js +9 -4
- package/dist/js/index.d.ts +0 -1
- package/dist/js/index.js +14 -151
- package/dist/js/nuxt.js +9 -229
- package/dist/js/shared/create-type-gen-plugin.d.ts +99 -0
- package/dist/js/shared/create-type-gen-plugin.js +110 -0
- package/dist/js/shared/emit-route-types.d.ts +41 -0
- package/dist/js/shared/emit-route-types.js +151 -0
- package/dist/js/sveltekit.js +9 -229
- package/package.json +2 -2
package/dist/js/nuxt.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
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
3
|
import colors from "picocolors";
|
|
6
|
-
import {
|
|
7
|
-
import { debounce } from "./shared/debounce.js";
|
|
8
|
-
const execAsync = promisify(exec);
|
|
4
|
+
import { createTypeGenerationPlugin } from "./shared/create-type-gen-plugin.js";
|
|
9
5
|
function normalizeHost(host) {
|
|
10
6
|
if (host === "::" || host === "::1" || host === "0.0.0.0") {
|
|
11
7
|
return "localhost";
|
|
@@ -151,234 +147,18 @@ function createProxyPlugin(config) {
|
|
|
151
147
|
}
|
|
152
148
|
};
|
|
153
149
|
}
|
|
154
|
-
async function emitRouteTypes(routesPath, outputDir) {
|
|
155
|
-
const contents = await fs.promises.readFile(routesPath, "utf-8");
|
|
156
|
-
const json = JSON.parse(contents);
|
|
157
|
-
const outDir = path.resolve(process.cwd(), outputDir);
|
|
158
|
-
await fs.promises.mkdir(outDir, { recursive: true });
|
|
159
|
-
const outFile = path.join(outDir, "routes.ts");
|
|
160
|
-
const banner = `// AUTO-GENERATED by litestar-vite. Do not edit.
|
|
161
|
-
/* eslint-disable */
|
|
162
|
-
|
|
163
|
-
`;
|
|
164
|
-
const routesData = json.routes || json;
|
|
165
|
-
const routeNames = Object.keys(routesData);
|
|
166
|
-
const routeNameType = routeNames.length > 0 ? routeNames.map((n) => `"${n}"`).join(" | ") : "never";
|
|
167
|
-
const routeParamTypes = [];
|
|
168
|
-
for (const [name, data] of Object.entries(routesData)) {
|
|
169
|
-
const routeData = data;
|
|
170
|
-
if (routeData.parameters && routeData.parameters.length > 0) {
|
|
171
|
-
const params = routeData.parameters.map((p) => `${p}: string | number`).join("; ");
|
|
172
|
-
routeParamTypes.push(` "${name}": { ${params} }`);
|
|
173
|
-
} else {
|
|
174
|
-
routeParamTypes.push(` "${name}": Record<string, never>`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const body = `/**
|
|
178
|
-
* AUTO-GENERATED by litestar-vite.
|
|
179
|
-
*
|
|
180
|
-
* Exports:
|
|
181
|
-
* - routesMeta: full route metadata
|
|
182
|
-
* - routes: name -> uri map
|
|
183
|
-
* - serverRoutes: alias of routes for clarity in apps
|
|
184
|
-
* - route(): type-safe URL generator
|
|
185
|
-
* - hasRoute(): type guard
|
|
186
|
-
* - csrf helpers re-exported from litestar-vite-plugin/helpers
|
|
187
|
-
*
|
|
188
|
-
* @see https://litestar-vite.litestar.dev/
|
|
189
|
-
*/
|
|
190
|
-
export const routesMeta = ${JSON.stringify(json, null, 2)} as const
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Route name to URI mapping.
|
|
194
|
-
*/
|
|
195
|
-
export const routes = ${JSON.stringify(Object.fromEntries(Object.entries(routesData).map(([name, data]) => [name, data.uri])), null, 2)} as const
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Alias for server-injected route map (more descriptive for consumers).
|
|
199
|
-
*/
|
|
200
|
-
export const serverRoutes = routes
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* All available route names.
|
|
204
|
-
*/
|
|
205
|
-
export type RouteName = ${routeNameType}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Parameter types for each route.
|
|
209
|
-
*/
|
|
210
|
-
export interface RouteParams {
|
|
211
|
-
${routeParamTypes.join("\n")}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Generate a URL for a named route with type-safe parameters.
|
|
216
|
-
*
|
|
217
|
-
* @param name - The route name
|
|
218
|
-
* @param params - Route parameters (required if route has path parameters)
|
|
219
|
-
* @returns The generated URL
|
|
220
|
-
*
|
|
221
|
-
* @example
|
|
222
|
-
* \`\`\`ts
|
|
223
|
-
* import { route } from '@/generated/routes'
|
|
224
|
-
*
|
|
225
|
-
* // Route without parameters
|
|
226
|
-
* route('home') // "/"
|
|
227
|
-
*
|
|
228
|
-
* // Route with parameters
|
|
229
|
-
* route('user:detail', { user_id: 123 }) // "/users/123"
|
|
230
|
-
* \`\`\`
|
|
231
|
-
*/
|
|
232
|
-
export function route<T extends RouteName>(
|
|
233
|
-
name: T,
|
|
234
|
-
...args: RouteParams[T] extends Record<string, never> ? [] : [params: RouteParams[T]]
|
|
235
|
-
): string {
|
|
236
|
-
let uri = routes[name] as string
|
|
237
|
-
const params = args[0] as Record<string, string | number> | undefined
|
|
238
|
-
|
|
239
|
-
if (params) {
|
|
240
|
-
for (const [key, value] of Object.entries(params)) {
|
|
241
|
-
// Handle both {param} and {param:type} syntax
|
|
242
|
-
uri = uri.replace(new RegExp(\`\\\\{\${key}(?::[^}]+)?\\\\}\`, "g"), String(value))
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return uri
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Check if a route name exists.
|
|
251
|
-
*/
|
|
252
|
-
export function hasRoute(name: string): name is RouteName {
|
|
253
|
-
return name in routes
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
declare global {
|
|
257
|
-
interface Window {
|
|
258
|
-
/**
|
|
259
|
-
* Fully-typed route metadata injected by Litestar.
|
|
260
|
-
*/
|
|
261
|
-
__LITESTAR_ROUTES__?: typeof routesMeta
|
|
262
|
-
/**
|
|
263
|
-
* Simple route map (name -> uri) for legacy consumers.
|
|
264
|
-
*/
|
|
265
|
-
routes?: typeof routes
|
|
266
|
-
serverRoutes?: typeof serverRoutes
|
|
267
|
-
}
|
|
268
|
-
// eslint-disable-next-line no-var
|
|
269
|
-
var routes: typeof routes | undefined
|
|
270
|
-
var serverRoutes: typeof serverRoutes | undefined
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Re-export helper functions from litestar-vite-plugin
|
|
274
|
-
// These work with the routes defined above
|
|
275
|
-
export { getCsrfToken, csrfHeaders, csrfFetch } from "litestar-vite-plugin/helpers"
|
|
276
|
-
`;
|
|
277
|
-
await fs.promises.writeFile(outFile, `${banner}${body}`, "utf-8");
|
|
278
|
-
}
|
|
279
|
-
function createTypeGenerationPlugin(typesConfig, executor) {
|
|
280
|
-
let server = null;
|
|
281
|
-
let isGenerating = false;
|
|
282
|
-
async function runTypeGeneration() {
|
|
283
|
-
if (isGenerating) {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
isGenerating = true;
|
|
287
|
-
const startTime = Date.now();
|
|
288
|
-
try {
|
|
289
|
-
const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
|
|
290
|
-
if (!fs.existsSync(openapiPath)) {
|
|
291
|
-
console.log(colors.cyan("[litestar-nuxt]"), colors.yellow("OpenAPI schema not found:"), typesConfig.openapiPath);
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
console.log(colors.cyan("[litestar-nuxt]"), colors.dim("Generating TypeScript types..."));
|
|
295
|
-
const projectRoot = process.cwd();
|
|
296
|
-
const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
|
|
297
|
-
const configPath = candidates.find((p) => fs.existsSync(p)) || null;
|
|
298
|
-
let args;
|
|
299
|
-
if (configPath) {
|
|
300
|
-
console.log(colors.cyan("[litestar-nuxt]"), colors.dim("Using config:"), configPath);
|
|
301
|
-
args = ["@hey-api/openapi-ts", "--file", configPath];
|
|
302
|
-
} else {
|
|
303
|
-
args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", typesConfig.output];
|
|
304
|
-
const plugins = ["@hey-api/typescript", "@hey-api/schemas"];
|
|
305
|
-
if (typesConfig.generateSdk) {
|
|
306
|
-
plugins.push("@hey-api/sdk", "@hey-api/client-nuxt");
|
|
307
|
-
}
|
|
308
|
-
if (typesConfig.generateZod) {
|
|
309
|
-
plugins.push("zod");
|
|
310
|
-
}
|
|
311
|
-
if (plugins.length) {
|
|
312
|
-
args.push("--plugins", ...plugins);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
await execAsync(resolvePackageExecutor(args.join(" "), executor), {
|
|
316
|
-
cwd: projectRoot
|
|
317
|
-
});
|
|
318
|
-
const routesPath = path.resolve(process.cwd(), typesConfig.routesPath);
|
|
319
|
-
if (fs.existsSync(routesPath)) {
|
|
320
|
-
await emitRouteTypes(routesPath, typesConfig.output);
|
|
321
|
-
}
|
|
322
|
-
const duration = Date.now() - startTime;
|
|
323
|
-
console.log(colors.cyan("[litestar-nuxt]"), colors.green("Types generated"), colors.dim(`in ${duration}ms`));
|
|
324
|
-
if (server) {
|
|
325
|
-
server.ws.send({
|
|
326
|
-
type: "custom",
|
|
327
|
-
event: "litestar:types-updated",
|
|
328
|
-
data: {
|
|
329
|
-
output: typesConfig.output,
|
|
330
|
-
timestamp: Date.now()
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
return true;
|
|
335
|
-
} catch (error) {
|
|
336
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
337
|
-
if (message.includes("not found") || message.includes("ENOENT")) {
|
|
338
|
-
console.log(colors.cyan("[litestar-nuxt]"), colors.yellow("@hey-api/openapi-ts not installed"), "- run:", resolveInstallHint());
|
|
339
|
-
} else {
|
|
340
|
-
console.error(colors.cyan("[litestar-nuxt]"), colors.red("Type generation failed:"), message);
|
|
341
|
-
}
|
|
342
|
-
return false;
|
|
343
|
-
} finally {
|
|
344
|
-
isGenerating = false;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
const debouncedRunTypeGeneration = debounce(runTypeGeneration, typesConfig.debounce);
|
|
348
|
-
return {
|
|
349
|
-
name: "litestar-nuxt-types",
|
|
350
|
-
enforce: "pre",
|
|
351
|
-
configureServer(devServer) {
|
|
352
|
-
server = devServer;
|
|
353
|
-
console.log(colors.cyan("[litestar-nuxt]"), colors.dim("Watching for schema changes:"), colors.yellow(typesConfig.openapiPath));
|
|
354
|
-
},
|
|
355
|
-
async buildStart() {
|
|
356
|
-
if (typesConfig.enabled) {
|
|
357
|
-
const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
|
|
358
|
-
if (fs.existsSync(openapiPath)) {
|
|
359
|
-
await runTypeGeneration();
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
},
|
|
363
|
-
handleHotUpdate({ file }) {
|
|
364
|
-
if (!typesConfig.enabled) {
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
const relativePath = path.relative(process.cwd(), file);
|
|
368
|
-
const openapiPath = typesConfig.openapiPath.replace(/^\.\//, "");
|
|
369
|
-
const routesPath = typesConfig.routesPath.replace(/^\.\//, "");
|
|
370
|
-
if (relativePath === openapiPath || relativePath === routesPath || file.endsWith(openapiPath) || file.endsWith(routesPath)) {
|
|
371
|
-
console.log(colors.cyan("[litestar-nuxt]"), colors.dim("Schema changed:"), colors.yellow(relativePath));
|
|
372
|
-
debouncedRunTypeGeneration();
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
150
|
function litestarPlugins(userConfig = {}) {
|
|
378
151
|
const config = resolveConfig(userConfig);
|
|
379
152
|
const plugins = [createProxyPlugin(config)];
|
|
380
153
|
if (config.types !== false && config.types.enabled) {
|
|
381
|
-
plugins.push(
|
|
154
|
+
plugins.push(
|
|
155
|
+
createTypeGenerationPlugin(config.types, {
|
|
156
|
+
frameworkName: "litestar-nuxt",
|
|
157
|
+
pluginName: "litestar-nuxt-types",
|
|
158
|
+
clientPlugin: "@hey-api/client-nuxt",
|
|
159
|
+
executor: config.executor
|
|
160
|
+
})
|
|
161
|
+
);
|
|
382
162
|
}
|
|
383
163
|
return plugins;
|
|
384
164
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type generation Vite plugin.
|
|
3
|
+
*
|
|
4
|
+
* Creates a Vite plugin that watches for OpenAPI schema and route metadata changes
|
|
5
|
+
* and regenerates TypeScript types using @hey-api/openapi-ts.
|
|
6
|
+
* Used by Astro, Nuxt, and SvelteKit integrations.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { Plugin } from "vite";
|
|
11
|
+
/**
|
|
12
|
+
* Base configuration for type generation.
|
|
13
|
+
*/
|
|
14
|
+
export interface BaseTypesConfig {
|
|
15
|
+
/**
|
|
16
|
+
* Enable type generation.
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Path to output generated TypeScript types.
|
|
22
|
+
*/
|
|
23
|
+
output?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Path where the OpenAPI schema is exported by Litestar.
|
|
26
|
+
* @default 'openapi.json'
|
|
27
|
+
*/
|
|
28
|
+
openapiPath?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Path where route metadata is exported by Litestar.
|
|
31
|
+
* @default 'routes.json'
|
|
32
|
+
*/
|
|
33
|
+
routesPath?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Generate Zod schemas in addition to TypeScript types.
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
generateZod?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Generate SDK client functions for API calls.
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
generateSdk?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Debounce time in milliseconds for type regeneration.
|
|
46
|
+
* @default 300
|
|
47
|
+
*/
|
|
48
|
+
debounce?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Required version of types config (all fields defined).
|
|
52
|
+
*/
|
|
53
|
+
export interface RequiredTypesConfig {
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
output: string;
|
|
56
|
+
openapiPath: string;
|
|
57
|
+
routesPath: string;
|
|
58
|
+
generateZod: boolean;
|
|
59
|
+
generateSdk: boolean;
|
|
60
|
+
debounce: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Options for creating the type generation plugin.
|
|
64
|
+
*/
|
|
65
|
+
export interface TypeGenPluginOptions {
|
|
66
|
+
/**
|
|
67
|
+
* Framework name for logging (e.g., "litestar-astro", "litestar-nuxt").
|
|
68
|
+
*/
|
|
69
|
+
frameworkName: string;
|
|
70
|
+
/**
|
|
71
|
+
* Vite plugin name.
|
|
72
|
+
*/
|
|
73
|
+
pluginName: string;
|
|
74
|
+
/**
|
|
75
|
+
* The @hey-api client plugin to use when generating SDK.
|
|
76
|
+
* @default '@hey-api/client-fetch'
|
|
77
|
+
*/
|
|
78
|
+
clientPlugin?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Optional executor for running npx/bunx/pnpm dlx commands.
|
|
81
|
+
*/
|
|
82
|
+
executor?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create a Vite plugin for type generation from OpenAPI schemas.
|
|
86
|
+
*
|
|
87
|
+
* @param typesConfig - The type generation configuration
|
|
88
|
+
* @param options - Plugin creation options
|
|
89
|
+
* @returns A Vite plugin that watches for schema changes and regenerates types
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* const plugin = createTypeGenerationPlugin(
|
|
94
|
+
* { enabled: true, output: 'src/generated' },
|
|
95
|
+
* { frameworkName: 'litestar-astro', pluginName: 'litestar-astro-types' }
|
|
96
|
+
* )
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function createTypeGenerationPlugin(typesConfig: RequiredTypesConfig, options: TypeGenPluginOptions): Plugin;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import colors from "picocolors";
|
|
6
|
+
import { resolveInstallHint, resolvePackageExecutor } from "../install-hint.js";
|
|
7
|
+
import { debounce } from "./debounce.js";
|
|
8
|
+
import { emitRouteTypes } from "./emit-route-types.js";
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
function createTypeGenerationPlugin(typesConfig, options) {
|
|
11
|
+
const { frameworkName, pluginName, clientPlugin = "@hey-api/client-fetch", executor } = options;
|
|
12
|
+
let server = null;
|
|
13
|
+
let isGenerating = false;
|
|
14
|
+
async function runTypeGeneration() {
|
|
15
|
+
if (isGenerating) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
isGenerating = true;
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
try {
|
|
21
|
+
const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
|
|
22
|
+
if (!fs.existsSync(openapiPath)) {
|
|
23
|
+
console.log(colors.cyan(`[${frameworkName}]`), colors.yellow("OpenAPI schema not found:"), typesConfig.openapiPath);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
console.log(colors.cyan(`[${frameworkName}]`), colors.dim("Generating TypeScript types..."));
|
|
27
|
+
const projectRoot = process.cwd();
|
|
28
|
+
const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
|
|
29
|
+
const configPath = candidates.find((p) => fs.existsSync(p)) || null;
|
|
30
|
+
let args;
|
|
31
|
+
if (configPath) {
|
|
32
|
+
console.log(colors.cyan(`[${frameworkName}]`), colors.dim("Using config:"), configPath);
|
|
33
|
+
args = ["@hey-api/openapi-ts", "--file", configPath];
|
|
34
|
+
} else {
|
|
35
|
+
args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", typesConfig.output];
|
|
36
|
+
const plugins = ["@hey-api/typescript", "@hey-api/schemas"];
|
|
37
|
+
if (typesConfig.generateSdk) {
|
|
38
|
+
plugins.push("@hey-api/sdk", clientPlugin);
|
|
39
|
+
}
|
|
40
|
+
if (typesConfig.generateZod) {
|
|
41
|
+
plugins.push("zod");
|
|
42
|
+
}
|
|
43
|
+
if (plugins.length) {
|
|
44
|
+
args.push("--plugins", ...plugins);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const command = executor ? resolvePackageExecutor(args.join(" "), executor) : `npx ${args.join(" ")}`;
|
|
48
|
+
await execAsync(command, { cwd: projectRoot });
|
|
49
|
+
const routesPath = path.resolve(process.cwd(), typesConfig.routesPath);
|
|
50
|
+
if (fs.existsSync(routesPath)) {
|
|
51
|
+
await emitRouteTypes(routesPath, typesConfig.output, { declareGlobalVars: true });
|
|
52
|
+
}
|
|
53
|
+
const duration = Date.now() - startTime;
|
|
54
|
+
console.log(colors.cyan(`[${frameworkName}]`), colors.green("Types generated"), colors.dim(`in ${duration}ms`));
|
|
55
|
+
if (server) {
|
|
56
|
+
server.ws.send({
|
|
57
|
+
type: "custom",
|
|
58
|
+
event: "litestar:types-updated",
|
|
59
|
+
data: {
|
|
60
|
+
output: typesConfig.output,
|
|
61
|
+
timestamp: Date.now()
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
if (message.includes("not found") || message.includes("ENOENT")) {
|
|
69
|
+
console.log(colors.cyan(`[${frameworkName}]`), colors.yellow("@hey-api/openapi-ts not installed"), "- run:", resolveInstallHint());
|
|
70
|
+
} else {
|
|
71
|
+
console.error(colors.cyan(`[${frameworkName}]`), colors.red("Type generation failed:"), message);
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
} finally {
|
|
75
|
+
isGenerating = false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const debouncedRunTypeGeneration = debounce(runTypeGeneration, typesConfig.debounce);
|
|
79
|
+
return {
|
|
80
|
+
name: pluginName,
|
|
81
|
+
enforce: "pre",
|
|
82
|
+
configureServer(devServer) {
|
|
83
|
+
server = devServer;
|
|
84
|
+
console.log(colors.cyan(`[${frameworkName}]`), colors.dim("Watching for schema changes:"), colors.yellow(typesConfig.openapiPath));
|
|
85
|
+
},
|
|
86
|
+
async buildStart() {
|
|
87
|
+
if (typesConfig.enabled) {
|
|
88
|
+
const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
|
|
89
|
+
if (fs.existsSync(openapiPath)) {
|
|
90
|
+
await runTypeGeneration();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
handleHotUpdate({ file }) {
|
|
95
|
+
if (!typesConfig.enabled) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const relativePath = path.relative(process.cwd(), file);
|
|
99
|
+
const openapiPath = typesConfig.openapiPath.replace(/^\.\//, "");
|
|
100
|
+
const routesPath = typesConfig.routesPath.replace(/^\.\//, "");
|
|
101
|
+
if (relativePath === openapiPath || relativePath === routesPath || file.endsWith(openapiPath) || file.endsWith(routesPath)) {
|
|
102
|
+
console.log(colors.cyan(`[${frameworkName}]`), colors.dim("Schema changed:"), colors.yellow(relativePath));
|
|
103
|
+
debouncedRunTypeGeneration();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
createTypeGenerationPlugin
|
|
110
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared route type generation utility.
|
|
3
|
+
*
|
|
4
|
+
* Generates TypeScript types from routes.json metadata for type-safe routing.
|
|
5
|
+
* Used by the main litestar plugin and framework integrations (Astro, Nuxt, SvelteKit).
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Options for emitRouteTypes.
|
|
11
|
+
*/
|
|
12
|
+
export interface EmitRouteTypesOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Whether to register route() on window for global access.
|
|
15
|
+
* Only used by the main plugin.
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
globalRoute?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Whether to declare global `var routes` and `var serverRoutes`.
|
|
21
|
+
* Used by framework integrations for SSR compatibility.
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
declareGlobalVars?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate TypeScript route types from routes.json metadata.
|
|
28
|
+
*
|
|
29
|
+
* Creates a routes.ts file with:
|
|
30
|
+
* - routesMeta: full route metadata
|
|
31
|
+
* - routes: name -> uri map
|
|
32
|
+
* - serverRoutes: alias of routes
|
|
33
|
+
* - route(): type-safe URL generator
|
|
34
|
+
* - hasRoute(): type guard
|
|
35
|
+
* - CSRF helpers re-exported from litestar-vite-plugin/helpers
|
|
36
|
+
*
|
|
37
|
+
* @param routesPath - Path to routes.json file
|
|
38
|
+
* @param outputDir - Output directory for routes.ts
|
|
39
|
+
* @param options - Generation options
|
|
40
|
+
*/
|
|
41
|
+
export declare function emitRouteTypes(routesPath: string, outputDir: string, options?: EmitRouteTypesOptions): Promise<void>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
async function emitRouteTypes(routesPath, outputDir, options = {}) {
|
|
4
|
+
const { globalRoute = false, declareGlobalVars = false } = options;
|
|
5
|
+
const contents = await fs.promises.readFile(routesPath, "utf-8");
|
|
6
|
+
const json = JSON.parse(contents);
|
|
7
|
+
const outDir = path.resolve(process.cwd(), outputDir);
|
|
8
|
+
await fs.promises.mkdir(outDir, { recursive: true });
|
|
9
|
+
const outFile = path.join(outDir, "routes.ts");
|
|
10
|
+
const banner = `// AUTO-GENERATED by litestar-vite. Do not edit.
|
|
11
|
+
/* eslint-disable */
|
|
12
|
+
|
|
13
|
+
`;
|
|
14
|
+
const routesData = json.routes || json;
|
|
15
|
+
const routeNames = Object.keys(routesData);
|
|
16
|
+
const routeNameType = routeNames.length > 0 ? routeNames.map((n) => `"${n}"`).join(" | ") : "never";
|
|
17
|
+
const routeParamTypes = [];
|
|
18
|
+
for (const [name, data] of Object.entries(routesData)) {
|
|
19
|
+
if (data.parameters && data.parameters.length > 0) {
|
|
20
|
+
const params = data.parameters.map((p) => `${p}: string | number`).join("; ");
|
|
21
|
+
routeParamTypes.push(` "${name}": { ${params} }`);
|
|
22
|
+
} else {
|
|
23
|
+
routeParamTypes.push(` "${name}": Record<string, never>`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
let globalDeclarations = `declare global {
|
|
27
|
+
interface Window {
|
|
28
|
+
/**
|
|
29
|
+
* Fully-typed route metadata injected by Litestar.
|
|
30
|
+
*/
|
|
31
|
+
__LITESTAR_ROUTES__?: typeof routesMeta
|
|
32
|
+
/**
|
|
33
|
+
* Simple route map (name -> uri) for legacy consumers.
|
|
34
|
+
*/
|
|
35
|
+
routes?: ${declareGlobalVars ? "typeof routes" : "Record<string, string>"}
|
|
36
|
+
serverRoutes?: ${declareGlobalVars ? "typeof serverRoutes" : "Record<string, string>"}`;
|
|
37
|
+
if (globalRoute) {
|
|
38
|
+
globalDeclarations += `
|
|
39
|
+
/**
|
|
40
|
+
* Global route helper (available when globalRoute=true in TypeGenConfig).
|
|
41
|
+
* @see route
|
|
42
|
+
*/
|
|
43
|
+
route?: typeof route`;
|
|
44
|
+
}
|
|
45
|
+
globalDeclarations += `
|
|
46
|
+
}`;
|
|
47
|
+
if (declareGlobalVars) {
|
|
48
|
+
globalDeclarations += `
|
|
49
|
+
// eslint-disable-next-line no-var
|
|
50
|
+
var routes: typeof routes | undefined
|
|
51
|
+
var serverRoutes: typeof serverRoutes | undefined`;
|
|
52
|
+
}
|
|
53
|
+
globalDeclarations += `
|
|
54
|
+
}`;
|
|
55
|
+
const globalRouteRegistration = globalRoute ? `
|
|
56
|
+
|
|
57
|
+
// Register route() globally on window for Laravel/Ziggy-style usage
|
|
58
|
+
if (typeof window !== "undefined") {
|
|
59
|
+
window.route = route
|
|
60
|
+
}
|
|
61
|
+
` : "";
|
|
62
|
+
const body = `/**
|
|
63
|
+
* AUTO-GENERATED by litestar-vite.
|
|
64
|
+
*
|
|
65
|
+
* Exports:
|
|
66
|
+
* - routesMeta: full route metadata
|
|
67
|
+
* - routes: name -> uri map
|
|
68
|
+
* - serverRoutes: alias of routes for clarity in apps
|
|
69
|
+
* - route(): type-safe URL generator
|
|
70
|
+
* - hasRoute(): type guard
|
|
71
|
+
* - csrf helpers re-exported from litestar-vite-plugin/helpers
|
|
72
|
+
*
|
|
73
|
+
* @see https://litestar-vite.litestar.dev/
|
|
74
|
+
*/
|
|
75
|
+
export const routesMeta = ${JSON.stringify(json, null, 2)} as const
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Route name to URI mapping.
|
|
79
|
+
*/
|
|
80
|
+
export const routes = ${JSON.stringify(Object.fromEntries(Object.entries(routesData).map(([name, data]) => [name, data.uri])), null, 2)} as const
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Alias for server-injected route map (more descriptive for consumers).
|
|
84
|
+
*/
|
|
85
|
+
export const serverRoutes = routes
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* All available route names.
|
|
89
|
+
*/
|
|
90
|
+
export type RouteName = ${routeNameType}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parameter types for each route.
|
|
94
|
+
*/
|
|
95
|
+
export interface RouteParams {
|
|
96
|
+
${routeParamTypes.join("\n")}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate a URL for a named route with type-safe parameters.
|
|
101
|
+
*
|
|
102
|
+
* @param name - The route name
|
|
103
|
+
* @param params - Route parameters (required if route has path parameters)
|
|
104
|
+
* @returns The generated URL
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* \`\`\`ts
|
|
108
|
+
* import { route } from '@/generated/routes'
|
|
109
|
+
*
|
|
110
|
+
* // Route without parameters
|
|
111
|
+
* route('home') // "/"
|
|
112
|
+
*
|
|
113
|
+
* // Route with parameters
|
|
114
|
+
* route('user:detail', { user_id: 123 }) // "/users/123"
|
|
115
|
+
* \`\`\`
|
|
116
|
+
*/
|
|
117
|
+
export function route<T extends RouteName>(
|
|
118
|
+
name: T,
|
|
119
|
+
...args: RouteParams[T] extends Record<string, never> ? [] : [params: RouteParams[T]]
|
|
120
|
+
): string {
|
|
121
|
+
let uri = routes[name] as string
|
|
122
|
+
const params = args[0] as Record<string, string | number> | undefined
|
|
123
|
+
|
|
124
|
+
if (params) {
|
|
125
|
+
for (const [key, value] of Object.entries(params)) {
|
|
126
|
+
// Handle both {param} and {param:type} syntax
|
|
127
|
+
uri = uri.replace(new RegExp(\`\\\\{\${key}(?::[^}]+)?\\\\}\`, "g"), String(value))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return uri
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if a route name exists.
|
|
136
|
+
*/
|
|
137
|
+
export function hasRoute(name: string): name is RouteName {
|
|
138
|
+
return name in routes
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
${globalDeclarations}
|
|
142
|
+
|
|
143
|
+
// Re-export helper functions from litestar-vite-plugin
|
|
144
|
+
// These work with the routes defined above
|
|
145
|
+
export { getCsrfToken, csrfHeaders, csrfFetch } from "litestar-vite-plugin/helpers"
|
|
146
|
+
${globalRouteRegistration}`;
|
|
147
|
+
await fs.promises.writeFile(outFile, `${banner}${body}`, "utf-8");
|
|
148
|
+
}
|
|
149
|
+
export {
|
|
150
|
+
emitRouteTypes
|
|
151
|
+
};
|