litestar-vite-plugin 0.13.2 → 0.15.0-alpha.1
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 +64 -108
- package/dist/js/astro.d.ts +157 -0
- package/dist/js/astro.js +70 -0
- package/dist/js/dev-server-index.html +136 -142
- package/dist/js/helpers/csrf.d.ts +76 -0
- package/dist/js/helpers/csrf.js +114 -0
- package/dist/js/helpers/index.d.ts +24 -0
- package/dist/js/helpers/index.js +26 -0
- package/dist/js/helpers/routes.d.ts +140 -0
- package/dist/js/helpers/routes.js +280 -0
- package/dist/js/index.d.ts +86 -1
- package/dist/js/index.js +428 -58
- package/dist/js/inertia-helpers/helpers/csrf.d.ts +76 -0
- package/dist/js/inertia-helpers/helpers/csrf.js +114 -0
- package/dist/js/inertia-helpers/helpers/index.d.ts +24 -0
- package/dist/js/inertia-helpers/helpers/index.js +26 -0
- package/dist/js/inertia-helpers/helpers/routes.d.ts +140 -0
- package/dist/js/inertia-helpers/helpers/routes.js +280 -0
- package/dist/js/inertia-helpers/inertia-helpers/index.d.ts +33 -0
- package/dist/js/inertia-helpers/inertia-helpers/index.js +47 -0
- package/dist/js/install-hint.d.ts +1 -0
- package/dist/js/install-hint.js +21 -0
- package/dist/js/litestar-meta.d.ts +18 -0
- package/dist/js/litestar-meta.js +84 -0
- package/dist/js/nuxt.d.ts +193 -0
- package/dist/js/nuxt.js +197 -0
- package/dist/js/sveltekit.d.ts +158 -0
- package/dist/js/sveltekit.js +168 -0
- package/package.json +28 -8
- package/tools/clean.js +0 -0
- package/dist/js/inertia-helpers/index.d.ts +0 -20
- package/dist/js/inertia-helpers/index.js +0 -138
package/dist/js/index.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
1
3
|
import fs from "node:fs";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { promisify } from "node:util";
|
|
4
7
|
import colors from "picocolors";
|
|
5
8
|
import { loadEnv } from "vite";
|
|
6
9
|
import fullReload from "vite-plugin-full-reload";
|
|
10
|
+
import { resolveInstallHint } from "./install-hint.js";
|
|
11
|
+
import { checkBackendAvailability, loadLitestarMeta } from "./litestar-meta.js";
|
|
12
|
+
const execAsync = promisify(exec);
|
|
7
13
|
let exitHandlersBound = false;
|
|
8
14
|
const refreshPaths = ["src/**", "resources/**", "assets/**"].filter((path2) => fs.existsSync(path2.replace(/\*\*$/, "")));
|
|
9
15
|
function litestar(config) {
|
|
10
16
|
const pluginConfig = resolvePluginConfig(config);
|
|
11
|
-
|
|
17
|
+
const plugins = [resolveLitestarPlugin(pluginConfig), ...resolveFullReloadConfig(pluginConfig)];
|
|
18
|
+
if (pluginConfig.types !== false && pluginConfig.types.enabled) {
|
|
19
|
+
plugins.push(resolveTypeGenerationPlugin(pluginConfig.types));
|
|
20
|
+
}
|
|
21
|
+
return plugins;
|
|
12
22
|
}
|
|
13
23
|
async function findIndexHtmlPath(server, pluginConfig) {
|
|
14
24
|
if (!pluginConfig.autoDetectIndex) {
|
|
@@ -36,6 +46,7 @@ function resolveLitestarPlugin(pluginConfig) {
|
|
|
36
46
|
let viteDevServerUrl;
|
|
37
47
|
let resolvedConfig;
|
|
38
48
|
let userConfig;
|
|
49
|
+
let litestarMeta = {};
|
|
39
50
|
const defaultAliases = {
|
|
40
51
|
"@": `/${pluginConfig.resourceDirectory.replace(/^\/+/, "").replace(/\/+$/, "")}/`
|
|
41
52
|
};
|
|
@@ -46,11 +57,12 @@ function resolveLitestarPlugin(pluginConfig) {
|
|
|
46
57
|
userConfig = config;
|
|
47
58
|
const ssr = !!userConfig.build?.ssr;
|
|
48
59
|
const env = loadEnv(mode, userConfig.envDir || process.cwd(), "");
|
|
49
|
-
const assetUrl = env.ASSET_URL || pluginConfig.assetUrl;
|
|
60
|
+
const assetUrl = normalizeAssetUrl(env.ASSET_URL || pluginConfig.assetUrl);
|
|
50
61
|
const serverConfig = command === "serve" ? resolveDevelopmentEnvironmentServerConfig(pluginConfig.detectTls) ?? resolveEnvironmentServerConfig(env) : void 0;
|
|
62
|
+
const devBase = pluginConfig.assetUrl.startsWith("/") ? pluginConfig.assetUrl : pluginConfig.assetUrl.replace(/\/+$/, "");
|
|
51
63
|
ensureCommandShouldRunInEnvironment(command, env);
|
|
52
64
|
return {
|
|
53
|
-
base: userConfig.base ?? (command === "build" ? resolveBase(pluginConfig, assetUrl) :
|
|
65
|
+
base: userConfig.base ?? (command === "build" ? resolveBase(pluginConfig, assetUrl) : devBase),
|
|
54
66
|
publicDir: userConfig.publicDir ?? false,
|
|
55
67
|
clearScreen: false,
|
|
56
68
|
build: {
|
|
@@ -64,6 +76,27 @@ function resolveLitestarPlugin(pluginConfig) {
|
|
|
64
76
|
},
|
|
65
77
|
server: {
|
|
66
78
|
origin: userConfig.server?.origin ?? "__litestar_vite_placeholder__",
|
|
79
|
+
// Auto-configure HMR to use a path that routes through Litestar proxy
|
|
80
|
+
// Note: Vite automatically prepends `base` to `hmr.path`, so we just use "vite-hmr"
|
|
81
|
+
// Result: base="/static/" + path="vite-hmr" = "/static/vite-hmr"
|
|
82
|
+
hmr: userConfig.server?.hmr === false ? false : {
|
|
83
|
+
path: "vite-hmr",
|
|
84
|
+
...serverConfig?.hmr ?? {},
|
|
85
|
+
...userConfig.server?.hmr === true ? {} : userConfig.server?.hmr
|
|
86
|
+
},
|
|
87
|
+
// Auto-configure proxy to forward API requests to Litestar backend
|
|
88
|
+
// This allows the app to work when accessing Vite directly (not through Litestar proxy)
|
|
89
|
+
// Only proxies /api and /schema routes - everything else is handled by Vite
|
|
90
|
+
proxy: userConfig.server?.proxy ?? (env.APP_URL ? {
|
|
91
|
+
"/api": {
|
|
92
|
+
target: env.APP_URL,
|
|
93
|
+
changeOrigin: true
|
|
94
|
+
},
|
|
95
|
+
"/schema": {
|
|
96
|
+
target: env.APP_URL,
|
|
97
|
+
changeOrigin: true
|
|
98
|
+
}
|
|
99
|
+
} : void 0),
|
|
67
100
|
...process.env.VITE_ALLOW_REMOTE ? {
|
|
68
101
|
host: userConfig.server?.host ?? "0.0.0.0",
|
|
69
102
|
port: userConfig.server?.port ?? (env.VITE_PORT ? Number.parseInt(env.VITE_PORT) : 5173),
|
|
@@ -71,10 +104,6 @@ function resolveLitestarPlugin(pluginConfig) {
|
|
|
71
104
|
} : void 0,
|
|
72
105
|
...serverConfig ? {
|
|
73
106
|
host: userConfig.server?.host ?? serverConfig.host,
|
|
74
|
-
hmr: userConfig.server?.hmr === false ? false : {
|
|
75
|
-
...serverConfig.hmr,
|
|
76
|
-
...userConfig.server?.hmr === true ? {} : userConfig.server?.hmr
|
|
77
|
-
},
|
|
78
107
|
https: userConfig.server?.https ?? serverConfig.https
|
|
79
108
|
} : void 0
|
|
80
109
|
},
|
|
@@ -97,7 +126,7 @@ function resolveLitestarPlugin(pluginConfig) {
|
|
|
97
126
|
// appType: 'spa', // Try adding this - might simplify things if appropriate
|
|
98
127
|
};
|
|
99
128
|
},
|
|
100
|
-
configResolved(config) {
|
|
129
|
+
async configResolved(config) {
|
|
101
130
|
resolvedConfig = config;
|
|
102
131
|
if (resolvedConfig.command === "serve" && resolvedConfig.base && !resolvedConfig.base.endsWith("/")) {
|
|
103
132
|
resolvedConfig = {
|
|
@@ -105,6 +134,8 @@ function resolveLitestarPlugin(pluginConfig) {
|
|
|
105
134
|
base: `${resolvedConfig.base}/`
|
|
106
135
|
};
|
|
107
136
|
}
|
|
137
|
+
const hint = pluginConfig.types !== false ? pluginConfig.types.routesPath : void 0;
|
|
138
|
+
litestarMeta = await loadLitestarMeta(resolvedConfig, hint);
|
|
108
139
|
},
|
|
109
140
|
transform(code, id) {
|
|
110
141
|
if (resolvedConfig.command === "serve" && code.includes("__litestar_vite_placeholder__")) {
|
|
@@ -116,6 +147,9 @@ function resolveLitestarPlugin(pluginConfig) {
|
|
|
116
147
|
async configureServer(server) {
|
|
117
148
|
const envDir = resolvedConfig.envDir || process.cwd();
|
|
118
149
|
const appUrl = loadEnv(resolvedConfig.mode, envDir, "APP_URL").APP_URL ?? "undefined";
|
|
150
|
+
if (pluginConfig.hotFile && !path.isAbsolute(pluginConfig.hotFile)) {
|
|
151
|
+
pluginConfig.hotFile = path.resolve(server.config.root, pluginConfig.hotFile);
|
|
152
|
+
}
|
|
119
153
|
const initialIndexPath = await findIndexHtmlPath(server, pluginConfig);
|
|
120
154
|
server.httpServer?.once("listening", () => {
|
|
121
155
|
const address = server.httpServer?.address();
|
|
@@ -124,9 +158,11 @@ function resolveLitestarPlugin(pluginConfig) {
|
|
|
124
158
|
viteDevServerUrl = userConfig.server?.origin ? userConfig.server.origin : resolveDevServerUrl(address, server.config, userConfig);
|
|
125
159
|
fs.mkdirSync(path.dirname(pluginConfig.hotFile), { recursive: true });
|
|
126
160
|
fs.writeFileSync(pluginConfig.hotFile, viteDevServerUrl);
|
|
127
|
-
setTimeout(() => {
|
|
161
|
+
setTimeout(async () => {
|
|
162
|
+
const version = litestarMeta.litestarVersion ?? process.env.LITESTAR_VERSION ?? "unknown";
|
|
163
|
+
const backendStatus = await checkBackendAvailability(appUrl);
|
|
128
164
|
resolvedConfig.logger.info(`
|
|
129
|
-
${colors.red(`${colors.bold("LITESTAR")} ${
|
|
165
|
+
${colors.red(`${colors.bold("LITESTAR")} ${version}`)} ${colors.dim("plugin")} ${colors.bold(`v${pluginVersion()}`)}`);
|
|
130
166
|
resolvedConfig.logger.info("");
|
|
131
167
|
if (initialIndexPath) {
|
|
132
168
|
resolvedConfig.logger.info(
|
|
@@ -136,8 +172,42 @@ function resolveLitestarPlugin(pluginConfig) {
|
|
|
136
172
|
resolvedConfig.logger.info(` ${colors.green("\u279C")} ${colors.bold("Index Mode")}: Litestar (Plugin will serve placeholder for /index.html)`);
|
|
137
173
|
}
|
|
138
174
|
resolvedConfig.logger.info(` ${colors.green("\u279C")} ${colors.bold("Dev Server")}: ${colors.cyan(viteDevServerUrl)}`);
|
|
139
|
-
|
|
175
|
+
if (backendStatus.available) {
|
|
176
|
+
resolvedConfig.logger.info(
|
|
177
|
+
` ${colors.green("\u279C")} ${colors.bold("App URL")}: ${colors.cyan(appUrl.replace(/:(\d+)/, (_, port) => `:${colors.bold(port)}`))} ${colors.green("\u2713")}`
|
|
178
|
+
);
|
|
179
|
+
} else {
|
|
180
|
+
resolvedConfig.logger.info(
|
|
181
|
+
` ${colors.yellow("\u279C")} ${colors.bold("App URL")}: ${colors.cyan(appUrl.replace(/:(\d+)/, (_, port) => `:${colors.bold(port)}`))} ${colors.yellow("\u26A0")}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
140
184
|
resolvedConfig.logger.info(` ${colors.green("\u279C")} ${colors.bold("Assets Base")}: ${colors.cyan(resolvedConfig.base)}`);
|
|
185
|
+
if (pluginConfig.types !== false && pluginConfig.types.enabled) {
|
|
186
|
+
const openapiExists = fs.existsSync(path.resolve(process.cwd(), pluginConfig.types.openapiPath));
|
|
187
|
+
const routesExists = fs.existsSync(path.resolve(process.cwd(), pluginConfig.types.routesPath));
|
|
188
|
+
if (openapiExists || routesExists) {
|
|
189
|
+
resolvedConfig.logger.info(` ${colors.green("\u279C")} ${colors.bold("Type Gen")}: ${colors.green("enabled")} ${colors.dim(`\u2192 ${pluginConfig.types.output}`)}`);
|
|
190
|
+
} else {
|
|
191
|
+
resolvedConfig.logger.info(` ${colors.yellow("\u279C")} ${colors.bold("Type Gen")}: ${colors.yellow("waiting")} ${colors.dim("(no schema files yet)")}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!backendStatus.available) {
|
|
195
|
+
resolvedConfig.logger.info("");
|
|
196
|
+
resolvedConfig.logger.info(` ${colors.yellow("\u26A0")} ${colors.bold("Backend Status")}`);
|
|
197
|
+
if (backendStatus.error === "APP_URL not configured") {
|
|
198
|
+
resolvedConfig.logger.info(` ${colors.dim("APP_URL environment variable is not set.")}`);
|
|
199
|
+
resolvedConfig.logger.info(` ${colors.dim("Set APP_URL in your .env file or environment.")}`);
|
|
200
|
+
} else {
|
|
201
|
+
resolvedConfig.logger.info(` ${colors.dim(backendStatus.error ?? "Backend not available")}`);
|
|
202
|
+
resolvedConfig.logger.info("");
|
|
203
|
+
resolvedConfig.logger.info(` ${colors.bold("To start your Litestar app:")}`);
|
|
204
|
+
resolvedConfig.logger.info(` ${colors.cyan("litestar run")} ${colors.dim("or")} ${colors.cyan("uvicorn app:app --reload")}`);
|
|
205
|
+
}
|
|
206
|
+
resolvedConfig.logger.info("");
|
|
207
|
+
resolvedConfig.logger.info(` ${colors.dim("The Vite dev server is running and will serve assets.")}`);
|
|
208
|
+
resolvedConfig.logger.info(` ${colors.dim("Start your Litestar backend to view the full application.")}`);
|
|
209
|
+
}
|
|
210
|
+
resolvedConfig.logger.info("");
|
|
141
211
|
}, 100);
|
|
142
212
|
}
|
|
143
213
|
});
|
|
@@ -153,53 +223,49 @@ function resolveLitestarPlugin(pluginConfig) {
|
|
|
153
223
|
process.on("SIGHUP", () => process.exit());
|
|
154
224
|
exitHandlersBound = true;
|
|
155
225
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
next(e);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (!indexPath && req.url === "/index.html") {
|
|
175
|
-
try {
|
|
176
|
-
const placeholderPath = path.join(dirname(), "dev-server-index.html");
|
|
177
|
-
const placeholderContent = await fs.promises.readFile(placeholderPath, "utf-8");
|
|
178
|
-
res.statusCode = 200;
|
|
179
|
-
res.setHeader("Content-Type", "text/html");
|
|
180
|
-
res.end(placeholderContent.replace(/{{ APP_URL }}/g, appUrl));
|
|
181
|
-
} catch (e) {
|
|
182
|
-
resolvedConfig.logger.error(`Error serving placeholder index.html: ${e instanceof Error ? e.message : e}`);
|
|
183
|
-
res.statusCode = 404;
|
|
184
|
-
res.end("Not Found (Error loading placeholder)");
|
|
185
|
-
}
|
|
226
|
+
server.middlewares.use(async (req, res, next) => {
|
|
227
|
+
const indexPath = await findIndexHtmlPath(server, pluginConfig);
|
|
228
|
+
if (indexPath && (req.url === "/" || req.url === "/index.html")) {
|
|
229
|
+
const currentUrl = req.url;
|
|
230
|
+
try {
|
|
231
|
+
const htmlContent = await fs.promises.readFile(indexPath, "utf-8");
|
|
232
|
+
const transformedHtml = await server.transformIndexHtml(req.originalUrl ?? currentUrl, htmlContent, req.originalUrl);
|
|
233
|
+
res.statusCode = 200;
|
|
234
|
+
res.setHeader("Content-Type", "text/html");
|
|
235
|
+
res.end(transformedHtml);
|
|
236
|
+
return;
|
|
237
|
+
} catch (e) {
|
|
238
|
+
resolvedConfig.logger.error(`Error serving index.html from ${indexPath}: ${e instanceof Error ? e.message : e}`);
|
|
239
|
+
next(e);
|
|
186
240
|
return;
|
|
187
241
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
242
|
+
}
|
|
243
|
+
if (!indexPath && req.url === "/index.html") {
|
|
244
|
+
try {
|
|
245
|
+
const placeholderPath = path.join(dirname(), "dev-server-index.html");
|
|
246
|
+
const placeholderContent = await fs.promises.readFile(placeholderPath, "utf-8");
|
|
247
|
+
res.statusCode = 200;
|
|
248
|
+
res.setHeader("Content-Type", "text/html");
|
|
249
|
+
res.end(placeholderContent.replace(/{{ APP_URL }}/g, appUrl));
|
|
250
|
+
} catch (e) {
|
|
251
|
+
resolvedConfig.logger.error(`Error serving placeholder index.html: ${e instanceof Error ? e.message : e}`);
|
|
252
|
+
res.statusCode = 404;
|
|
253
|
+
res.end("Not Found (Error loading placeholder)");
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
next();
|
|
258
|
+
});
|
|
191
259
|
}
|
|
192
260
|
};
|
|
193
261
|
}
|
|
194
262
|
function ensureCommandShouldRunInEnvironment(command, env) {
|
|
195
|
-
const
|
|
263
|
+
const allowedDevModes = ["dev", "development", "local", "docker"];
|
|
196
264
|
if (command === "build" || env.LITESTAR_BYPASS_ENV_CHECK === "1") {
|
|
197
265
|
return;
|
|
198
266
|
}
|
|
199
|
-
if (typeof env.LITESTAR_MODE !== "undefined" &&
|
|
200
|
-
throw Error(
|
|
201
|
-
"You should only run Vite dev server when Litestar is development mode. You should build your assets for production instead. To disable this ENV check you may set LITESTAR_BYPASS_ENV_CHECK=1"
|
|
202
|
-
);
|
|
267
|
+
if (typeof env.LITESTAR_MODE !== "undefined" && !allowedDevModes.includes(env.LITESTAR_MODE)) {
|
|
268
|
+
throw Error("Run the Vite dev server only in development. Set LITESTAR_MODE=dev/development/local/docker or set LITESTAR_BYPASS_ENV_CHECK=1 to skip this check.");
|
|
203
269
|
}
|
|
204
270
|
if (typeof env.CI !== "undefined") {
|
|
205
271
|
throw Error(
|
|
@@ -207,9 +273,6 @@ function ensureCommandShouldRunInEnvironment(command, env) {
|
|
|
207
273
|
);
|
|
208
274
|
}
|
|
209
275
|
}
|
|
210
|
-
function litestarVersion() {
|
|
211
|
-
return "";
|
|
212
|
-
}
|
|
213
276
|
function pluginVersion() {
|
|
214
277
|
try {
|
|
215
278
|
return JSON.parse(fs.readFileSync(path.join(dirname(), "../package.json")).toString())?.version;
|
|
@@ -217,10 +280,26 @@ function pluginVersion() {
|
|
|
217
280
|
return "";
|
|
218
281
|
}
|
|
219
282
|
}
|
|
283
|
+
function loadPythonDefaults() {
|
|
284
|
+
const configPath = process.env.LITESTAR_VITE_CONFIG_PATH;
|
|
285
|
+
if (!configPath) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
if (!fs.existsSync(configPath)) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
const data = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
293
|
+
return data;
|
|
294
|
+
} catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
220
298
|
function resolvePluginConfig(config) {
|
|
221
299
|
if (typeof config === "undefined") {
|
|
222
300
|
throw new Error("litestar-vite-plugin: missing configuration.");
|
|
223
301
|
}
|
|
302
|
+
const pythonDefaults = loadPythonDefaults();
|
|
224
303
|
const resolvedConfig = typeof config === "string" || Array.isArray(config) ? { input: config, ssr: config } : config;
|
|
225
304
|
if (typeof resolvedConfig.input === "undefined") {
|
|
226
305
|
throw new Error('litestar-vite-plugin: missing configuration for "input".');
|
|
@@ -243,18 +322,51 @@ function resolvePluginConfig(config) {
|
|
|
243
322
|
if (resolvedConfig.refresh === true) {
|
|
244
323
|
resolvedConfig.refresh = [{ paths: refreshPaths }];
|
|
245
324
|
}
|
|
325
|
+
let typesConfig = false;
|
|
326
|
+
if (typeof resolvedConfig.types === "undefined" && pythonDefaults?.types) {
|
|
327
|
+
typesConfig = {
|
|
328
|
+
enabled: pythonDefaults.types.enabled,
|
|
329
|
+
output: pythonDefaults.types.output,
|
|
330
|
+
openapiPath: pythonDefaults.types.openapiPath,
|
|
331
|
+
routesPath: pythonDefaults.types.routesPath,
|
|
332
|
+
generateZod: pythonDefaults.types.generateZod,
|
|
333
|
+
generateSdk: pythonDefaults.types.generateSdk,
|
|
334
|
+
debounce: 300
|
|
335
|
+
};
|
|
336
|
+
} else if (resolvedConfig.types === true || typeof resolvedConfig.types === "undefined") {
|
|
337
|
+
typesConfig = {
|
|
338
|
+
enabled: true,
|
|
339
|
+
output: "src/generated/types",
|
|
340
|
+
openapiPath: "src/generated/openapi.json",
|
|
341
|
+
routesPath: "src/generated/routes.json",
|
|
342
|
+
generateZod: false,
|
|
343
|
+
generateSdk: false,
|
|
344
|
+
debounce: 300
|
|
345
|
+
};
|
|
346
|
+
} else if (typeof resolvedConfig.types === "object" && resolvedConfig.types !== null) {
|
|
347
|
+
typesConfig = {
|
|
348
|
+
enabled: resolvedConfig.types.enabled ?? true,
|
|
349
|
+
output: resolvedConfig.types.output ?? "src/generated/types",
|
|
350
|
+
openapiPath: resolvedConfig.types.openapiPath ?? "src/generated/openapi.json",
|
|
351
|
+
routesPath: resolvedConfig.types.routesPath ?? "src/generated/routes.json",
|
|
352
|
+
generateZod: resolvedConfig.types.generateZod ?? false,
|
|
353
|
+
generateSdk: resolvedConfig.types.generateSdk ?? false,
|
|
354
|
+
debounce: resolvedConfig.types.debounce ?? 300
|
|
355
|
+
};
|
|
356
|
+
}
|
|
246
357
|
return {
|
|
247
358
|
input: resolvedConfig.input,
|
|
248
|
-
assetUrl: resolvedConfig.assetUrl ?? "static",
|
|
249
|
-
resourceDirectory: resolvedConfig.resourceDirectory ?? "resources",
|
|
250
|
-
bundleDirectory: resolvedConfig.bundleDirectory ?? "public",
|
|
359
|
+
assetUrl: normalizeAssetUrl(resolvedConfig.assetUrl ?? pythonDefaults?.assetUrl ?? "/static/"),
|
|
360
|
+
resourceDirectory: resolvedConfig.resourceDirectory ?? pythonDefaults?.resourceDir ?? "resources",
|
|
361
|
+
bundleDirectory: resolvedConfig.bundleDirectory ?? pythonDefaults?.bundleDir ?? "public",
|
|
251
362
|
ssr: resolvedConfig.ssr ?? resolvedConfig.input,
|
|
252
|
-
ssrOutputDirectory: resolvedConfig.ssrOutputDirectory ?? path.join(resolvedConfig.resourceDirectory ?? "resources", "bootstrap/ssr"),
|
|
363
|
+
ssrOutputDirectory: resolvedConfig.ssrOutputDirectory ?? pythonDefaults?.ssrOutDir ?? path.join(resolvedConfig.resourceDirectory ?? pythonDefaults?.resourceDir ?? "resources", "bootstrap/ssr"),
|
|
253
364
|
refresh: resolvedConfig.refresh ?? false,
|
|
254
365
|
hotFile: resolvedConfig.hotFile ?? path.join(resolvedConfig.bundleDirectory ?? "public", "hot"),
|
|
255
366
|
detectTls: resolvedConfig.detectTls ?? false,
|
|
256
367
|
autoDetectIndex: resolvedConfig.autoDetectIndex ?? true,
|
|
257
|
-
transformOnServe: resolvedConfig.transformOnServe ?? ((code) => code)
|
|
368
|
+
transformOnServe: resolvedConfig.transformOnServe ?? ((code) => code),
|
|
369
|
+
types: typesConfig
|
|
258
370
|
};
|
|
259
371
|
}
|
|
260
372
|
function resolveBase(config, assetUrl) {
|
|
@@ -294,6 +406,248 @@ function resolveFullReloadConfig({ refresh: config }) {
|
|
|
294
406
|
return plugin;
|
|
295
407
|
});
|
|
296
408
|
}
|
|
409
|
+
function debounce(func, wait) {
|
|
410
|
+
let timeout = null;
|
|
411
|
+
return (...args) => {
|
|
412
|
+
if (timeout) {
|
|
413
|
+
clearTimeout(timeout);
|
|
414
|
+
}
|
|
415
|
+
timeout = setTimeout(() => func(...args), wait);
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
async function emitRouteTypes(routesPath, outputDir) {
|
|
419
|
+
const contents = await fs.promises.readFile(routesPath, "utf-8");
|
|
420
|
+
const json = JSON.parse(contents);
|
|
421
|
+
const outDir = path.resolve(process.cwd(), outputDir);
|
|
422
|
+
await fs.promises.mkdir(outDir, { recursive: true });
|
|
423
|
+
const outFile = path.join(outDir, "routes.ts");
|
|
424
|
+
const banner = `// AUTO-GENERATED by litestar-vite. Do not edit.
|
|
425
|
+
/* eslint-disable */
|
|
426
|
+
|
|
427
|
+
`;
|
|
428
|
+
const routesData = json.routes || json;
|
|
429
|
+
const routeNames = Object.keys(routesData);
|
|
430
|
+
const routeNameType = routeNames.length > 0 ? routeNames.map((n) => `"${n}"`).join(" | ") : "never";
|
|
431
|
+
const routeParamTypes = [];
|
|
432
|
+
for (const [name, data] of Object.entries(routesData)) {
|
|
433
|
+
const routeData = data;
|
|
434
|
+
if (routeData.parameters && routeData.parameters.length > 0) {
|
|
435
|
+
const params = routeData.parameters.map((p) => `${p}: string | number`).join("; ");
|
|
436
|
+
routeParamTypes.push(` "${name}": { ${params} }`);
|
|
437
|
+
} else {
|
|
438
|
+
routeParamTypes.push(` "${name}": Record<string, never>`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const body = `/**
|
|
442
|
+
* AUTO-GENERATED by litestar-vite.
|
|
443
|
+
*
|
|
444
|
+
* Exports:
|
|
445
|
+
* - routesMeta: full route metadata
|
|
446
|
+
* - routes: name -> uri map
|
|
447
|
+
* - serverRoutes: alias of routes for clarity in apps
|
|
448
|
+
* - route(): type-safe URL generator
|
|
449
|
+
* - hasRoute(): type guard
|
|
450
|
+
* - csrf helpers re-exported from litestar-vite-plugin/helpers
|
|
451
|
+
*
|
|
452
|
+
* @see https://litestar-vite.litestar.dev/
|
|
453
|
+
*/
|
|
454
|
+
export const routesMeta = ${JSON.stringify(json, null, 2)} as const
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Route name to URI mapping.
|
|
458
|
+
*/
|
|
459
|
+
export const routes = ${JSON.stringify(Object.fromEntries(Object.entries(routesData).map(([name, data]) => [name, data.uri])), null, 2)} as const
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Alias for server-injected route map (more descriptive for consumers).
|
|
463
|
+
*/
|
|
464
|
+
export const serverRoutes = routes
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* All available route names.
|
|
468
|
+
*/
|
|
469
|
+
export type RouteName = ${routeNameType}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Parameter types for each route.
|
|
473
|
+
*/
|
|
474
|
+
export interface RouteParams {
|
|
475
|
+
${routeParamTypes.join("\n")}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Generate a URL for a named route with type-safe parameters.
|
|
480
|
+
*
|
|
481
|
+
* @param name - The route name
|
|
482
|
+
* @param params - Route parameters (required if route has path parameters)
|
|
483
|
+
* @returns The generated URL
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* \`\`\`ts
|
|
487
|
+
* import { route } from '@/generated/routes'
|
|
488
|
+
*
|
|
489
|
+
* // Route without parameters
|
|
490
|
+
* route('home') // "/"
|
|
491
|
+
*
|
|
492
|
+
* // Route with parameters
|
|
493
|
+
* route('user:detail', { user_id: 123 }) // "/users/123"
|
|
494
|
+
* \`\`\`
|
|
495
|
+
*/
|
|
496
|
+
export function route<T extends RouteName>(
|
|
497
|
+
name: T,
|
|
498
|
+
...args: RouteParams[T] extends Record<string, never> ? [] : [params: RouteParams[T]]
|
|
499
|
+
): string {
|
|
500
|
+
let uri = routes[name] as string
|
|
501
|
+
const params = args[0] as Record<string, string | number> | undefined
|
|
502
|
+
|
|
503
|
+
if (params) {
|
|
504
|
+
for (const [key, value] of Object.entries(params)) {
|
|
505
|
+
// Handle both {param} and {param:type} syntax
|
|
506
|
+
uri = uri.replace(new RegExp(\`\\\\{\${key}(?::[^}]+)?\\\\}\`, "g"), String(value))
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return uri
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Check if a route name exists.
|
|
515
|
+
*/
|
|
516
|
+
export function hasRoute(name: string): name is RouteName {
|
|
517
|
+
return name in routes
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
declare global {
|
|
521
|
+
interface Window {
|
|
522
|
+
/**
|
|
523
|
+
* Fully-typed route metadata injected by Litestar.
|
|
524
|
+
*/
|
|
525
|
+
__LITESTAR_ROUTES__?: typeof routesMeta
|
|
526
|
+
/**
|
|
527
|
+
* Simple route map (name -> uri) for legacy consumers.
|
|
528
|
+
*/
|
|
529
|
+
routes?: typeof routes
|
|
530
|
+
serverRoutes?: typeof serverRoutes
|
|
531
|
+
}
|
|
532
|
+
// eslint-disable-next-line no-var
|
|
533
|
+
var routes: typeof routes | undefined
|
|
534
|
+
var serverRoutes: typeof serverRoutes | undefined
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Re-export helper functions from litestar-vite-plugin
|
|
538
|
+
// These work with the routes defined above
|
|
539
|
+
export { getCsrfToken, csrfHeaders, csrfFetch } from "litestar-vite-plugin/helpers"
|
|
540
|
+
`;
|
|
541
|
+
await fs.promises.writeFile(outFile, `${banner}${body}`, "utf-8");
|
|
542
|
+
}
|
|
543
|
+
function resolveTypeGenerationPlugin(typesConfig) {
|
|
544
|
+
let lastTypesHash = null;
|
|
545
|
+
let lastRoutesHash = null;
|
|
546
|
+
let server = null;
|
|
547
|
+
let isGenerating = false;
|
|
548
|
+
let resolvedConfig = null;
|
|
549
|
+
async function runTypeGeneration() {
|
|
550
|
+
if (isGenerating) {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
isGenerating = true;
|
|
554
|
+
const startTime = Date.now();
|
|
555
|
+
try {
|
|
556
|
+
const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
|
|
557
|
+
const routesPath = path.resolve(process.cwd(), typesConfig.routesPath);
|
|
558
|
+
let generated = false;
|
|
559
|
+
if (fs.existsSync(openapiPath)) {
|
|
560
|
+
if (resolvedConfig) {
|
|
561
|
+
resolvedConfig.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("generating TypeScript types...")}`);
|
|
562
|
+
}
|
|
563
|
+
const args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", typesConfig.output];
|
|
564
|
+
if (typesConfig.generateZod) {
|
|
565
|
+
args.push("--plugins", "@hey-api/schemas", "@hey-api/types");
|
|
566
|
+
}
|
|
567
|
+
if (typesConfig.generateSdk) {
|
|
568
|
+
args.push("--client", "fetch");
|
|
569
|
+
}
|
|
570
|
+
await execAsync(`npx ${args.join(" ")}`, {
|
|
571
|
+
cwd: process.cwd()
|
|
572
|
+
});
|
|
573
|
+
generated = true;
|
|
574
|
+
} else if (resolvedConfig) {
|
|
575
|
+
resolvedConfig.logger.warn(`${colors.cyan("litestar-vite")} ${colors.yellow("OpenAPI schema not found:")} ${typesConfig.openapiPath}`);
|
|
576
|
+
}
|
|
577
|
+
if (fs.existsSync(routesPath)) {
|
|
578
|
+
await emitRouteTypes(routesPath, typesConfig.output);
|
|
579
|
+
generated = true;
|
|
580
|
+
}
|
|
581
|
+
if (generated && resolvedConfig) {
|
|
582
|
+
const duration = Date.now() - startTime;
|
|
583
|
+
resolvedConfig.logger.info(`${colors.cyan("litestar-vite")} ${colors.green("TypeScript artifacts updated")} ${colors.dim(`in ${duration}ms`)}`);
|
|
584
|
+
}
|
|
585
|
+
if (generated && server) {
|
|
586
|
+
server.ws.send({
|
|
587
|
+
type: "custom",
|
|
588
|
+
event: "litestar:types-updated",
|
|
589
|
+
data: {
|
|
590
|
+
output: typesConfig.output,
|
|
591
|
+
timestamp: Date.now()
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
return true;
|
|
596
|
+
} catch (error) {
|
|
597
|
+
if (resolvedConfig) {
|
|
598
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
599
|
+
if (message.includes("not found") || message.includes("ENOENT")) {
|
|
600
|
+
resolvedConfig.logger.warn(`${colors.cyan("litestar-vite")} ${colors.yellow("@hey-api/openapi-ts not installed")} - run: ${resolveInstallHint()}`);
|
|
601
|
+
} else {
|
|
602
|
+
resolvedConfig.logger.error(`${colors.cyan("litestar-vite")} ${colors.red("type generation failed:")} ${message}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return false;
|
|
606
|
+
} finally {
|
|
607
|
+
isGenerating = false;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const debouncedRunTypeGeneration = debounce(runTypeGeneration, typesConfig.debounce);
|
|
611
|
+
return {
|
|
612
|
+
name: "litestar-vite-types",
|
|
613
|
+
enforce: "pre",
|
|
614
|
+
configResolved(config) {
|
|
615
|
+
resolvedConfig = config;
|
|
616
|
+
},
|
|
617
|
+
configureServer(devServer) {
|
|
618
|
+
server = devServer;
|
|
619
|
+
if (typesConfig.enabled) {
|
|
620
|
+
resolvedConfig?.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("watching for schema changes:")} ${colors.yellow(typesConfig.openapiPath)}`);
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
async handleHotUpdate({ file }) {
|
|
624
|
+
if (!typesConfig.enabled) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const relativePath = path.relative(process.cwd(), file);
|
|
628
|
+
const openapiPath = typesConfig.openapiPath.replace(/^\.\//, "");
|
|
629
|
+
const routesPath = typesConfig.routesPath.replace(/^\.\//, "");
|
|
630
|
+
if (relativePath === openapiPath || relativePath === routesPath || file.endsWith(openapiPath) || file.endsWith(routesPath)) {
|
|
631
|
+
if (resolvedConfig) {
|
|
632
|
+
resolvedConfig.logger.info(`${colors.cyan("litestar-vite")} ${colors.dim("schema changed:")} ${colors.yellow(relativePath)}`);
|
|
633
|
+
}
|
|
634
|
+
const newHash = await hashFile(file);
|
|
635
|
+
if (relativePath === openapiPath) {
|
|
636
|
+
if (lastTypesHash === newHash) return;
|
|
637
|
+
lastTypesHash = newHash;
|
|
638
|
+
} else {
|
|
639
|
+
if (lastRoutesHash === newHash) return;
|
|
640
|
+
lastRoutesHash = newHash;
|
|
641
|
+
}
|
|
642
|
+
debouncedRunTypeGeneration();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
async function hashFile(filePath) {
|
|
648
|
+
const content = await fs.promises.readFile(filePath);
|
|
649
|
+
return createHash("sha1").update(content).digest("hex");
|
|
650
|
+
}
|
|
297
651
|
function resolveDevServerUrl(address, config, userConfig) {
|
|
298
652
|
const configHmrProtocol = typeof config.server.hmr === "object" ? config.server.hmr.protocol : null;
|
|
299
653
|
const clientProtocol = configHmrProtocol ? configHmrProtocol === "wss" ? "https" : "http" : null;
|
|
@@ -303,7 +657,10 @@ function resolveDevServerUrl(address, config, userConfig) {
|
|
|
303
657
|
const configHost = typeof config.server.host === "string" ? config.server.host : null;
|
|
304
658
|
const remoteHost = process.env.VITE_ALLOW_REMOTE && !userConfig.server?.host ? "localhost" : null;
|
|
305
659
|
const serverAddress = isIpv6(address) ? `[${address.address}]` : address.address;
|
|
306
|
-
|
|
660
|
+
let host = configHmrHost ?? remoteHost ?? configHost ?? serverAddress;
|
|
661
|
+
if (host === "0.0.0.0") {
|
|
662
|
+
host = "127.0.0.1";
|
|
663
|
+
}
|
|
307
664
|
const configHmrClientPort = typeof config.server.hmr === "object" ? config.server.hmr.clientPort : null;
|
|
308
665
|
const port = configHmrClientPort ?? address.port;
|
|
309
666
|
return `${protocol}://${host}:${port}`;
|
|
@@ -403,6 +760,19 @@ function dirname() {
|
|
|
403
760
|
return path.resolve(process.cwd(), "src/js/src");
|
|
404
761
|
}
|
|
405
762
|
}
|
|
763
|
+
function normalizeAssetUrl(url) {
|
|
764
|
+
const trimmed = url.trim();
|
|
765
|
+
if (trimmed === "") {
|
|
766
|
+
return "static";
|
|
767
|
+
}
|
|
768
|
+
const isExternal = trimmed.startsWith("http://") || trimmed.startsWith("https://");
|
|
769
|
+
if (isExternal) {
|
|
770
|
+
return trimmed.replace(/\/+$/, "");
|
|
771
|
+
}
|
|
772
|
+
const withLeading = trimmed.startsWith("/") ? `/${trimmed.replace(/^\/+/, "")}` : trimmed;
|
|
773
|
+
const withTrailing = withLeading.endsWith("/") ? withLeading : `${withLeading}/`;
|
|
774
|
+
return withTrailing;
|
|
775
|
+
}
|
|
406
776
|
export {
|
|
407
777
|
litestar as default,
|
|
408
778
|
refreshPaths
|