@donkeylabs/adapter-sveltekit 2.0.14 → 2.0.16

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/src/vite.ts DELETED
@@ -1,729 +0,0 @@
1
- /**
2
- * Vite plugin for @donkeylabs/adapter-sveltekit dev server integration
3
- *
4
- * Supports two modes:
5
- * - `bun --bun run dev`: Single-process mode (in-process, one port)
6
- * - `bun run dev`: Subprocess mode (two processes, proxy)
7
- */
8
-
9
- import type { Plugin, ViteDevServer } from "vite";
10
- import { spawn, type ChildProcess, exec } from "node:child_process";
11
- import { resolve, join } from "node:path";
12
- import { watch, type FSWatcher, existsSync } from "node:fs";
13
- import { promisify } from "node:util";
14
-
15
- const execAsync = promisify(exec);
16
- import http from "node:http";
17
-
18
- export interface DevPluginOptions {
19
- /**
20
- * Path to your @donkeylabs/server entry file.
21
- * This file should export a configured AppServer instance.
22
- *
23
- * @default "./src/server/index.ts"
24
- */
25
- serverEntry?: string;
26
-
27
- /**
28
- * Port for the backend server (subprocess mode only).
29
- * @default 3001
30
- */
31
- backendPort?: number;
32
-
33
- /**
34
- * Watch server files and auto-regenerate types on changes.
35
- * @default true
36
- */
37
- watchTypes?: boolean;
38
-
39
- /**
40
- * Directory to watch for server file changes.
41
- * @default "./src/server"
42
- */
43
- watchDir?: string;
44
-
45
- /**
46
- * Enable hot reload for route files (dev mode only).
47
- * When a route file changes, the router will be reloaded without server restart.
48
- * @default true
49
- */
50
- hotReloadRoutes?: boolean;
51
-
52
- /**
53
- * Glob patterns for route files to watch for hot reload.
54
- * @default ["**\/routes\/**\/*.ts"]
55
- */
56
- routePatterns?: string[];
57
- }
58
-
59
- // Check if running with Bun runtime (bun --bun)
60
- const isBunRuntime = typeof globalThis.Bun !== "undefined";
61
-
62
- // Use globalThis to share server reference across module instances
63
- // This is needed because SvelteKit SSR loads a separate module instance
64
- declare global {
65
- var __donkeylabs_dev_server__: any;
66
- }
67
-
68
- /**
69
- * Get the global app server instance for SSR direct calls.
70
- * This allows hooks to access the server without HTTP.
71
- */
72
- export function getDevServer(): any {
73
- return globalThis.__donkeylabs_dev_server__;
74
- }
75
-
76
- function setDevServer(server: any) {
77
- globalThis.__donkeylabs_dev_server__ = server;
78
- }
79
-
80
- /**
81
- * Vite plugin that integrates @donkeylabs/server with the dev server.
82
- *
83
- * - With `bun --bun run dev`: Runs in-process (single port, recommended)
84
- * - With `bun run dev`: Spawns subprocess (two ports, fallback)
85
- *
86
- * @example
87
- * // vite.config.ts
88
- * import { donkeylabsDev } from "@donkeylabs/adapter-sveltekit/vite";
89
- *
90
- * export default defineConfig({
91
- * plugins: [
92
- * donkeylabsDev({ serverEntry: "./src/server/index.ts" }),
93
- * sveltekit()
94
- * ]
95
- * });
96
- */
97
- export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
98
- const {
99
- serverEntry = "./src/server/index.ts",
100
- backendPort = 3001,
101
- watchTypes = true,
102
- watchDir = "./src/server",
103
- hotReloadRoutes = true,
104
- routePatterns = ["**/routes/**/*.ts", "**/routes/**/*.js"],
105
- } = options;
106
-
107
- // State for subprocess mode
108
- let backendProcess: ChildProcess | null = null;
109
- let backendReady = false;
110
-
111
- // State for in-process mode
112
- let appServer: any = null;
113
- let serverReady = false;
114
- let viteServer: ViteDevServer | null = null;
115
-
116
- // State for file watcher
117
- let fileWatcher: FSWatcher | null = null;
118
- let isGenerating = false;
119
- let lastGenerationTime = 0;
120
- let debounceTimer: ReturnType<typeof setTimeout> | null = null;
121
-
122
- // State for hot reload
123
- let hotReloadTimer: ReturnType<typeof setTimeout> | null = null;
124
- const HOT_RELOAD_DEBOUNCE_MS = 100;
125
-
126
- const COOLDOWN_MS = 2000;
127
- const DEBOUNCE_MS = 500;
128
-
129
- // Patterns to ignore (generated files)
130
- const IGNORED_PATTERNS = [/schema\.ts$/, /\.d\.ts$/, /api\.ts$/];
131
-
132
- // Check if a file matches route patterns
133
- function isRouteFile(filename: string): boolean {
134
- return routePatterns.some((pattern) => {
135
- // Simple glob matching for common patterns
136
- const regexPattern = pattern
137
- .replace(/\*\*/g, ".*")
138
- .replace(/\*/g, "[^/]*")
139
- .replace(/\./g, "\\.");
140
- return new RegExp(regexPattern).test(filename);
141
- });
142
- }
143
-
144
- // Hot reload a route file
145
- async function hotReloadRoute(filepath: string) {
146
- if (!appServer || !serverReady || !viteServer || !hotReloadRoutes) return;
147
-
148
- console.log("\x1b[36m[donkeylabs-dev]\x1b[0m Hot reloading route:", filepath);
149
-
150
- try {
151
- // Invalidate the module in Vite's cache
152
- const mod = viteServer.moduleGraph.getModuleById(filepath);
153
- if (mod) {
154
- viteServer.moduleGraph.invalidateModule(mod);
155
- }
156
-
157
- // Re-import the module with cache busting
158
- const timestamp = Date.now();
159
- const moduleUrl = `${filepath}?t=${timestamp}`;
160
-
161
- // Import the fresh module
162
- const freshModule = await viteServer.ssrLoadModule(moduleUrl);
163
-
164
- // Find the router export (could be named 'router', 'default', or end with 'Router')
165
- let newRouter = freshModule.router || freshModule.default;
166
- if (!newRouter) {
167
- for (const key of Object.keys(freshModule)) {
168
- if (key.endsWith("Router") && typeof freshModule[key]?.getRoutes === "function") {
169
- newRouter = freshModule[key];
170
- break;
171
- }
172
- }
173
- }
174
-
175
- if (newRouter && typeof newRouter.getRoutes === "function") {
176
- // Get the router prefix
177
- const prefix = newRouter.getPrefix?.() || "";
178
- if (prefix) {
179
- appServer.reloadRouter(prefix, newRouter);
180
- console.log("\x1b[32m[donkeylabs-dev]\x1b[0m Route hot reload complete:", prefix);
181
- } else {
182
- // If no prefix, rebuild all routes
183
- appServer.rebuildRouteMap();
184
- console.log("\x1b[32m[donkeylabs-dev]\x1b[0m Route map rebuilt");
185
- }
186
- } else {
187
- console.warn("\x1b[33m[donkeylabs-dev]\x1b[0m No router export found in:", filepath);
188
- }
189
- } catch (err: any) {
190
- console.error("\x1b[31m[donkeylabs-dev]\x1b[0m Hot reload error:", err.message);
191
- }
192
- }
193
-
194
- function debouncedHotReload(filepath: string) {
195
- if (hotReloadTimer) clearTimeout(hotReloadTimer);
196
- hotReloadTimer = setTimeout(() => hotReloadRoute(filepath), HOT_RELOAD_DEBOUNCE_MS);
197
- }
198
-
199
- function shouldIgnoreFile(filename: string): boolean {
200
- return IGNORED_PATTERNS.some((pattern) => pattern.test(filename));
201
- }
202
-
203
- async function regenerateTypes() {
204
- const now = Date.now();
205
- if (now - lastGenerationTime < COOLDOWN_MS || isGenerating) return;
206
-
207
- isGenerating = true;
208
- lastGenerationTime = now;
209
- console.log("\x1b[36m[donkeylabs-dev]\x1b[0m Server files changed, regenerating types...");
210
-
211
- try {
212
- await execAsync("bun run gen:types");
213
- console.log("\x1b[32m[donkeylabs-dev]\x1b[0m Types regenerated successfully");
214
- } catch (e: any) {
215
- console.error("\x1b[31m[donkeylabs-dev]\x1b[0m Error regenerating types:", e.message);
216
- } finally {
217
- isGenerating = false;
218
- lastGenerationTime = Date.now();
219
- }
220
- }
221
-
222
- async function ensureTypesGenerated() {
223
- // Check if the client file exists (common locations)
224
- const clientPaths = [
225
- resolve(process.cwd(), "src/lib/api.ts"),
226
- resolve(process.cwd(), "src/api.ts"),
227
- ];
228
-
229
- const clientExists = clientPaths.some((p) => existsSync(p));
230
- if (clientExists) return;
231
-
232
- console.log("\x1b[36m[donkeylabs-dev]\x1b[0m Generated client not found, running initial type generation...");
233
- isGenerating = true;
234
-
235
- try {
236
- await execAsync("bun run gen:types");
237
- console.log("\x1b[32m[donkeylabs-dev]\x1b[0m Initial types generated successfully");
238
- } catch (e: any) {
239
- console.warn("\x1b[33m[donkeylabs-dev]\x1b[0m Initial type generation failed:", e.message);
240
- console.warn("\x1b[33m[donkeylabs-dev]\x1b[0m Run 'bun run gen:types' manually to generate types");
241
- } finally {
242
- isGenerating = false;
243
- lastGenerationTime = Date.now();
244
- }
245
- }
246
-
247
- function debouncedRegenerate() {
248
- if (debounceTimer) clearTimeout(debounceTimer);
249
- debounceTimer = setTimeout(regenerateTypes, DEBOUNCE_MS);
250
- }
251
-
252
- function startFileWatcher() {
253
- if (fileWatcher) return;
254
- if (!watchTypes && !hotReloadRoutes) return;
255
-
256
- const watchPath = resolve(process.cwd(), watchDir);
257
- try {
258
- fileWatcher = watch(watchPath, { recursive: true }, (_eventType, filename) => {
259
- if (!filename) return;
260
- if (!filename.endsWith(".ts") && !filename.endsWith(".js")) return;
261
- if (shouldIgnoreFile(filename)) return;
262
-
263
- const fullPath = join(watchPath, filename);
264
-
265
- // Check if this is a route file for hot reload
266
- if (hotReloadRoutes && isRouteFile(filename)) {
267
- debouncedHotReload(fullPath);
268
- }
269
-
270
- // Also trigger type regeneration
271
- if (watchTypes) {
272
- debouncedRegenerate();
273
- }
274
- });
275
-
276
- const features = [
277
- watchTypes ? "type generation" : null,
278
- hotReloadRoutes ? "hot reload" : null,
279
- ].filter(Boolean).join(", ");
280
- console.log(`\x1b[36m[donkeylabs-dev]\x1b[0m Watching ${watchDir} for ${features}...`);
281
- } catch (err) {
282
- console.warn(`\x1b[33m[donkeylabs-dev]\x1b[0m Could not watch ${watchDir}:`, err);
283
- }
284
- }
285
-
286
- function stopFileWatcher() {
287
- if (debounceTimer) clearTimeout(debounceTimer);
288
- if (hotReloadTimer) clearTimeout(hotReloadTimer);
289
- if (fileWatcher) {
290
- fileWatcher.close();
291
- fileWatcher = null;
292
- }
293
- }
294
-
295
- return {
296
- name: "donkeylabs-dev",
297
- enforce: "pre",
298
-
299
- // Read PORT env variable and configure Vite's server port
300
- config(config) {
301
- const envPort = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;
302
- if (envPort && !isNaN(envPort)) {
303
- console.log(`[donkeylabs-dev] Using PORT=${envPort} from environment`);
304
- return {
305
- server: {
306
- ...config.server,
307
- port: envPort,
308
- },
309
- };
310
- }
311
- },
312
-
313
- async configureServer(server: ViteDevServer) {
314
- const serverEntryResolved = resolve(process.cwd(), serverEntry);
315
-
316
- // Store vite server reference for hot reload
317
- viteServer = server;
318
-
319
- // Ensure types are generated on first start (if client file doesn't exist)
320
- await ensureTypesGenerated();
321
-
322
- // Start file watcher for auto type regeneration and hot reload
323
- startFileWatcher();
324
-
325
- if (isBunRuntime) {
326
- // ========== IN-PROCESS MODE (bun --bun run dev) ==========
327
- // Import and initialize server directly - no subprocess, no proxy
328
- console.log("[donkeylabs-dev] Starting in-process mode (Bun runtime detected)");
329
-
330
- // Log the actual URL once Vite's server starts listening
331
- server.httpServer?.on("listening", () => {
332
- const address = server.httpServer?.address();
333
- if (address && typeof address === "object") {
334
- const host = address.address === "::" || address.address === "0.0.0.0" ? "localhost" : address.address;
335
- console.log(`[donkeylabs-dev] Ready at http://${host}:${address.port}`);
336
- }
337
- });
338
-
339
- try {
340
- const serverModule = await import(/* @vite-ignore */ serverEntryResolved);
341
- appServer = serverModule.server || serverModule.default;
342
-
343
- if (!appServer) {
344
- throw new Error("No server export found in " + serverEntry);
345
- }
346
-
347
- // Initialize without starting HTTP server
348
- await appServer.initialize();
349
- serverReady = true;
350
- // Set global reference for SSR direct calls (uses globalThis for cross-module sharing)
351
- setDevServer(appServer);
352
- console.log("[donkeylabs-dev] Server initialized (in-process mode)");
353
- } catch (err) {
354
- console.error("[donkeylabs-dev] Failed to initialize server:", err);
355
- throw err;
356
- }
357
-
358
- // Return middleware setup function
359
- return () => {
360
- // In-process request handler
361
- const inProcessMiddleware = async (req: any, res: any, next: any) => {
362
- const url = req.url || "/";
363
- const urlObj = new URL(url, "http://localhost");
364
- const pathname = urlObj.pathname;
365
-
366
- // Handle SSE endpoint
367
- if (req.method === "GET" && pathname === "/sse") {
368
- if (!serverReady || !appServer) return next();
369
-
370
- const channels = urlObj.searchParams.get("channels")?.split(",").filter(Boolean) || [];
371
- const lastEventId = req.headers["last-event-id"] || undefined;
372
-
373
- const { client, response } = appServer.getCore().sse.addClient({ lastEventId });
374
-
375
- for (const channel of channels) {
376
- appServer.getCore().sse.subscribe(client.id, channel);
377
- }
378
-
379
- // Set SSE headers
380
- res.writeHead(200, {
381
- "Content-Type": "text/event-stream",
382
- "Cache-Control": "no-cache",
383
- "Connection": "keep-alive",
384
- "Access-Control-Allow-Origin": "*",
385
- });
386
-
387
- // Stream SSE data
388
- const reader = response.body?.getReader();
389
- let sseClosed = false;
390
-
391
- req.on("close", () => {
392
- sseClosed = true;
393
- reader?.cancel().catch(() => {});
394
- appServer.getCore().sse.removeClient(client.id);
395
- });
396
-
397
- if (reader) {
398
- const pump = async () => {
399
- try {
400
- while (!sseClosed) {
401
- const { done, value } = await reader.read();
402
- if (done || sseClosed) break;
403
- res.write(value);
404
- }
405
- } catch {
406
- // Connection closed
407
- }
408
- };
409
- pump();
410
- }
411
-
412
- return; // Don't call next()
413
- }
414
-
415
- // Handle API routes (GET or POST for route names like /routeName.action)
416
- if ((req.method === "GET" || req.method === "POST") && /^\/[a-zA-Z][a-zA-Z0-9_.]*$/.test(pathname)) {
417
- if (!serverReady || !appServer) return next();
418
-
419
- const routeName = pathname.slice(1);
420
- if (!appServer.hasRoute(routeName)) return next();
421
-
422
- // Build a proper Request object to pass to handleRequest
423
- const buildRequest = async (): Promise<Request> => {
424
- const fullUrl = `http://localhost${url}`;
425
- const headers = new Headers();
426
- for (const [key, value] of Object.entries(req.headers)) {
427
- if (typeof value === "string") {
428
- headers.set(key, value);
429
- } else if (Array.isArray(value)) {
430
- for (const v of value) headers.append(key, v);
431
- }
432
- }
433
-
434
- if (req.method === "POST") {
435
- // Collect body for POST
436
- const chunks: Buffer[] = [];
437
- for await (const chunk of req) {
438
- chunks.push(chunk);
439
- }
440
- const body = Buffer.concat(chunks);
441
- return new Request(fullUrl, {
442
- method: "POST",
443
- headers,
444
- body,
445
- });
446
- }
447
-
448
- return new Request(fullUrl, { method: "GET", headers });
449
- };
450
-
451
- try {
452
- const request = await buildRequest();
453
- const ip = req.socket?.remoteAddress || "127.0.0.1";
454
-
455
- // Use handleRequest which properly handles all handler types (typed, raw, stream, sse, html)
456
- const response = await appServer.handleRequest(
457
- request,
458
- routeName,
459
- ip,
460
- { corsHeaders: { "Access-Control-Allow-Origin": "*" } }
461
- );
462
-
463
- if (!response) {
464
- return next();
465
- }
466
-
467
- // Stream the response back
468
- res.statusCode = response.status;
469
- for (const [key, value] of response.headers) {
470
- res.setHeader(key, value);
471
- }
472
-
473
- // Flush headers immediately for streaming responses
474
- if (typeof res.flushHeaders === "function") {
475
- res.flushHeaders();
476
- }
477
-
478
- // Handle body streaming (non-blocking for continuous streams like MJPEG)
479
- if (response.body) {
480
- const reader = response.body.getReader();
481
- let closed = false;
482
-
483
- // Handle client disconnect
484
- req.on("close", () => {
485
- closed = true;
486
- reader.cancel().catch(() => {});
487
- });
488
-
489
- // Pump without awaiting - allows continuous streams
490
- const pump = async () => {
491
- try {
492
- while (!closed) {
493
- const { done, value } = await reader.read();
494
- if (done || closed) {
495
- if (!closed) res.end();
496
- break;
497
- }
498
- // Write and check if client is still connected
499
- const canContinue = res.write(value);
500
- if (!canContinue && !closed) {
501
- // Backpressure - wait for drain
502
- await new Promise<void>(resolve => res.once("drain", resolve));
503
- }
504
- }
505
- } catch {
506
- if (!closed) res.end();
507
- }
508
- };
509
- pump(); // Don't await - let it run in background
510
- } else {
511
- res.end();
512
- }
513
- } catch (err: any) {
514
- console.error("[donkeylabs-dev] Request error:", err);
515
- res.statusCode = err.status || 500;
516
- res.setHeader("Content-Type", "application/json");
517
- res.end(JSON.stringify({ error: err.message || "Internal error" }));
518
- }
519
-
520
- return; // Don't call next()
521
- }
522
-
523
- next();
524
- };
525
-
526
- // CORS preflight
527
- const corsMiddleware = (req: any, res: any, next: any) => {
528
- if (req.method === "OPTIONS") {
529
- res.setHeader("Access-Control-Allow-Origin", "*");
530
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
531
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
532
- res.statusCode = 204;
533
- res.end();
534
- return;
535
- }
536
- next();
537
- };
538
-
539
- // Add to front of middleware stack
540
- const stack = (server.middlewares as any).stack;
541
- if (stack && Array.isArray(stack)) {
542
- stack.unshift({ route: "", handle: corsMiddleware });
543
- stack.unshift({ route: "", handle: inProcessMiddleware });
544
- } else {
545
- server.middlewares.use(inProcessMiddleware);
546
- server.middlewares.use(corsMiddleware);
547
- }
548
- };
549
- } else {
550
- // ========== SUBPROCESS MODE (bun run dev) ==========
551
- // Spawn backend as separate process and proxy requests
552
- console.log(`[donkeylabs-dev] Starting subprocess mode (backend on port ${backendPort})`);
553
-
554
- const bootstrapCode = `
555
- const serverModule = await import("${serverEntryResolved}");
556
- const server = serverModule.server || serverModule.default;
557
-
558
- if (!server) {
559
- console.error("[donkeylabs-backend] No server export found");
560
- process.exit(1);
561
- }
562
-
563
- server.port = ${backendPort};
564
- await server.start();
565
- console.log("[donkeylabs-backend] Server ready on port ${backendPort}");
566
- `;
567
-
568
- backendProcess = spawn("bun", ["--eval", bootstrapCode], {
569
- stdio: ["pipe", "pipe", "pipe"],
570
- env: { ...process.env, NODE_ENV: "development" },
571
- });
572
-
573
- backendProcess.stdout?.on("data", (data: Buffer) => {
574
- const msg = data.toString().trim();
575
- if (msg) {
576
- console.log(msg);
577
- if (msg.includes("Server ready") || msg.includes("Server running")) {
578
- backendReady = true;
579
- }
580
- }
581
- });
582
-
583
- backendProcess.stderr?.on("data", (data: Buffer) => {
584
- const msg = data.toString().trim();
585
- if (msg) console.error(msg);
586
- });
587
-
588
- backendProcess.on("error", (err) => {
589
- console.error("[donkeylabs-dev] Failed to start backend:", err);
590
- });
591
-
592
- backendProcess.on("exit", (code) => {
593
- if (code !== 0 && code !== null) {
594
- console.error(`[donkeylabs-dev] Backend exited with code ${code}`);
595
- }
596
- backendProcess = null;
597
- backendReady = false;
598
- });
599
-
600
- server.httpServer?.on("close", () => {
601
- if (backendProcess) {
602
- backendProcess.kill();
603
- backendProcess = null;
604
- }
605
- });
606
-
607
- // Return middleware setup function
608
- return () => {
609
- const waitForBackend = new Promise<void>((resolve) => {
610
- const check = () => (backendReady ? resolve() : setTimeout(check, 100));
611
- setTimeout(check, 500);
612
- setTimeout(() => {
613
- if (!backendReady) {
614
- console.warn("[donkeylabs-dev] Backend startup timeout");
615
- resolve();
616
- }
617
- }, 10000);
618
- });
619
-
620
- // Proxy middleware - handles GET and POST for API routes
621
- const proxyMiddleware = (req: any, res: any, next: any) => {
622
- const url = req.url || "/";
623
- const urlObj = new URL(url, "http://localhost");
624
- const pathname = urlObj.pathname;
625
- // API routes are GET or POST to paths like /routeName.action
626
- const isApiRoute = (req.method === "GET" || req.method === "POST") && /^\/[a-zA-Z][a-zA-Z0-9_.]*$/.test(pathname);
627
-
628
- if (!isApiRoute) return next();
629
-
630
- waitForBackend.then(() => {
631
- let proxyAborted = false;
632
-
633
- const proxyReq = http.request(
634
- {
635
- hostname: "localhost",
636
- port: backendPort,
637
- path: url, // Include query string
638
- method: req.method,
639
- headers: { ...req.headers, host: `localhost:${backendPort}` },
640
- },
641
- (proxyRes) => {
642
- if (proxyAborted) return;
643
-
644
- res.setHeader("Access-Control-Allow-Origin", "*");
645
- res.statusCode = proxyRes.statusCode || 200;
646
- for (const [k, v] of Object.entries(proxyRes.headers)) {
647
- if (v) res.setHeader(k, v);
648
- }
649
-
650
- // Flush headers for streaming responses
651
- if (typeof res.flushHeaders === "function") {
652
- res.flushHeaders();
653
- }
654
-
655
- // Stream response back (works for binary/streaming responses)
656
- proxyRes.pipe(res);
657
-
658
- // Clean up on proxy response end
659
- proxyRes.on("end", () => {
660
- if (!proxyAborted) res.end();
661
- });
662
- }
663
- );
664
-
665
- // Handle client disconnect - abort proxy request
666
- req.on("close", () => {
667
- if (!proxyAborted) {
668
- proxyAborted = true;
669
- proxyReq.destroy();
670
- }
671
- });
672
-
673
- proxyReq.on("error", (err) => {
674
- if (proxyAborted) return; // Ignore errors after abort
675
- console.error(`[donkeylabs-dev] Proxy error:`, err.message);
676
- res.statusCode = 502;
677
- res.end(JSON.stringify({ error: "Backend unavailable" }));
678
- });
679
-
680
- // For POST, pipe the body; for GET, just end
681
- if (req.method === "POST") {
682
- req.pipe(proxyReq);
683
- } else {
684
- proxyReq.end();
685
- }
686
- });
687
- };
688
-
689
- const corsMiddleware = (req: any, res: any, next: any) => {
690
- if (req.method === "OPTIONS") {
691
- res.setHeader("Access-Control-Allow-Origin", "*");
692
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
693
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
694
- res.statusCode = 204;
695
- res.end();
696
- return;
697
- }
698
- next();
699
- };
700
-
701
- const stack = (server.middlewares as any).stack;
702
- if (stack && Array.isArray(stack)) {
703
- stack.unshift({ route: "", handle: corsMiddleware });
704
- stack.unshift({ route: "", handle: proxyMiddleware });
705
- } else {
706
- server.middlewares.use(proxyMiddleware);
707
- server.middlewares.use(corsMiddleware);
708
- }
709
- };
710
- }
711
- },
712
-
713
- async closeBundle() {
714
- stopFileWatcher();
715
- viteServer = null;
716
- if (backendProcess) {
717
- backendProcess.kill();
718
- backendProcess = null;
719
- }
720
- if (appServer) {
721
- await appServer.shutdown?.();
722
- appServer = null;
723
- setDevServer(null);
724
- }
725
- },
726
- };
727
- }
728
-
729
- export default donkeylabsDev;