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