kilatjs 0.1.0 → 0.1.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/dist/cli.js ADDED
@@ -0,0 +1,1335 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true,
9
+ configurable: true,
10
+ set: (newValue) => all[name] = () => newValue
11
+ });
12
+ };
13
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
14
+
15
+ // src/server/live-reload.ts
16
+ function startLiveReload(port = 35729) {
17
+ if (liveReloadServer)
18
+ return;
19
+ liveReloadServer = Bun.serve({
20
+ port,
21
+ fetch(req, server) {
22
+ if (server.upgrade(req, { data: {} })) {
23
+ return;
24
+ }
25
+ return new Response("Live Reload Server", { status: 200 });
26
+ },
27
+ websocket: {
28
+ open(ws) {
29
+ clients.add(ws);
30
+ },
31
+ close(ws) {
32
+ clients.delete(ws);
33
+ },
34
+ message() {}
35
+ }
36
+ });
37
+ }
38
+ function notifyReload() {
39
+ const message = JSON.stringify({ type: "reload" });
40
+ for (const client of clients) {
41
+ try {
42
+ client.send(message);
43
+ } catch {
44
+ clients.delete(client);
45
+ }
46
+ }
47
+ }
48
+ function getLiveReloadScript(port = 35729) {
49
+ return `
50
+ <script>
51
+ (function() {
52
+ const ws = new WebSocket('ws://localhost:${port}');
53
+ ws.onmessage = function(e) {
54
+ const data = JSON.parse(e.data);
55
+ if (data.type === 'reload') {
56
+ console.log('[KilatJS] Reloading...');
57
+ location.reload();
58
+ }
59
+ };
60
+ ws.onclose = function() {
61
+ console.log('[KilatJS] Live reload disconnected. Reconnecting...');
62
+ setTimeout(function() { location.reload(); }, 1000);
63
+ };
64
+ })();
65
+ </script>`;
66
+ }
67
+ async function watchDirectory(dir, onChange) {
68
+ const fileTimestamps = new Map;
69
+ let timeout = null;
70
+ let lastChange = 0;
71
+ const triggerChange = (filename) => {
72
+ const now = Date.now();
73
+ if (now - lastChange < 100)
74
+ return;
75
+ lastChange = now;
76
+ if (timeout)
77
+ clearTimeout(timeout);
78
+ timeout = setTimeout(() => {
79
+ onChange();
80
+ }, 50);
81
+ };
82
+ async function scanFiles() {
83
+ const timestamps = new Map;
84
+ try {
85
+ const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,css}");
86
+ for await (const file of glob.scan({ cwd: dir, absolute: true })) {
87
+ try {
88
+ const stat = Bun.file(file);
89
+ const lastModified = (await stat.stat())?.mtime?.getTime() || 0;
90
+ timestamps.set(file, lastModified);
91
+ } catch {}
92
+ }
93
+ } catch (error) {
94
+ console.warn(`⚠️ Could not scan directory ${dir}:`, error);
95
+ }
96
+ return timestamps;
97
+ }
98
+ const initialFiles = await scanFiles();
99
+ for (const [file, time] of initialFiles) {
100
+ fileTimestamps.set(file, time);
101
+ }
102
+ const pollInterval = setInterval(async () => {
103
+ const currentFiles = await scanFiles();
104
+ for (const [file, time] of currentFiles) {
105
+ const previousTime = fileTimestamps.get(file);
106
+ if (previousTime === undefined || previousTime < time) {
107
+ fileTimestamps.set(file, time);
108
+ if (previousTime !== undefined) {
109
+ const relativePath = file.replace(dir + "/", "");
110
+ triggerChange(relativePath);
111
+ }
112
+ }
113
+ }
114
+ for (const file of fileTimestamps.keys()) {
115
+ if (!currentFiles.has(file)) {
116
+ fileTimestamps.delete(file);
117
+ const relativePath = file.replace(dir + "/", "");
118
+ triggerChange(relativePath);
119
+ }
120
+ }
121
+ }, 500);
122
+ globalThis.__kilatWatchIntervals = globalThis.__kilatWatchIntervals || [];
123
+ globalThis.__kilatWatchIntervals.push(pollInterval);
124
+ }
125
+ function stopLiveReload() {
126
+ if (liveReloadServer) {
127
+ liveReloadServer.stop();
128
+ liveReloadServer = null;
129
+ }
130
+ clients.clear();
131
+ }
132
+ var clients, liveReloadServer = null;
133
+ var init_live_reload = __esm(() => {
134
+ clients = new Set;
135
+ });
136
+
137
+ // src/adapters/react.ts
138
+ import { renderToStaticMarkup } from "react-dom/server";
139
+ import React from "react";
140
+
141
+ class ReactAdapter {
142
+ static async renderToString(component, props) {
143
+ try {
144
+ const element = React.createElement(component, props);
145
+ return renderToStaticMarkup(element);
146
+ } catch (error) {
147
+ console.error("Error rendering React component:", error);
148
+ throw error;
149
+ }
150
+ }
151
+ static createDocument(html, meta = {}, config, options = {}) {
152
+ const title = meta.title || "KilatJS App";
153
+ const description = meta.description || "";
154
+ const robots = meta.robots || "index,follow";
155
+ let metaTags = "";
156
+ metaTags += `<meta charset="utf-8" />
157
+ `;
158
+ metaTags += `<meta name="viewport" content="width=device-width, initial-scale=1" />
159
+ `;
160
+ metaTags += `<meta name="robots" content="${robots}" />
161
+ `;
162
+ if (description) {
163
+ metaTags += `<meta name="description" content="${this.escapeHtml(description)}" />
164
+ `;
165
+ }
166
+ if (meta.canonical) {
167
+ metaTags += `<link rel="canonical" href="${this.escapeHtml(meta.canonical)}" />
168
+ `;
169
+ }
170
+ metaTags += `<meta property="og:title" content="${this.escapeHtml(meta.ogTitle || title)}" />
171
+ `;
172
+ if (meta.ogDescription || description) {
173
+ metaTags += `<meta property="og:description" content="${this.escapeHtml(meta.ogDescription || description)}" />
174
+ `;
175
+ }
176
+ if (meta.ogImage) {
177
+ metaTags += `<meta property="og:image" content="${this.escapeHtml(meta.ogImage)}" />
178
+ `;
179
+ }
180
+ const twitterCard = meta.twitterCard || "summary";
181
+ metaTags += `<meta name="twitter:card" content="${twitterCard}" />
182
+ `;
183
+ const reservedKeys = [
184
+ "title",
185
+ "description",
186
+ "robots",
187
+ "canonical",
188
+ "ogTitle",
189
+ "ogDescription",
190
+ "ogImage",
191
+ "twitterCard"
192
+ ];
193
+ Object.entries(meta).forEach(([key, value]) => {
194
+ if (!reservedKeys.includes(key) && typeof value === "string") {
195
+ metaTags += `<meta name="${this.escapeHtml(key)}" content="${this.escapeHtml(value)}" />
196
+ `;
197
+ }
198
+ });
199
+ const cssUrl = config?.dev ? `/styles.css?v=${Date.now()}` : "/styles.css";
200
+ metaTags += `<link rel="stylesheet" href="${cssUrl}" />
201
+ `;
202
+ metaTags += `<link rel="preconnect" href="https://fonts.googleapis.com" />
203
+ `;
204
+ metaTags += `<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
205
+ `;
206
+ metaTags += `<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
207
+ `;
208
+ const liveReloadScript = config?.dev ? getLiveReloadScript() : "";
209
+ const clientScriptTag = options.clientScript ? `<script>(${options.clientScript.toString()})()</script>` : "";
210
+ return `<!DOCTYPE html>
211
+ <html lang="en">
212
+ <head>
213
+ <title>${this.escapeHtml(title)}</title>
214
+ ${metaTags}
215
+ </head>
216
+ <body>
217
+ <div id="root">
218
+ ${html}
219
+ </div>
220
+ ${liveReloadScript}
221
+ ${clientScriptTag}
222
+ </body>
223
+ </html>`;
224
+ }
225
+ static escapeHtml(str) {
226
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
227
+ }
228
+ }
229
+ var init_react = __esm(() => {
230
+ init_live_reload();
231
+ });
232
+
233
+ // src/adapters/htmx.ts
234
+ class HTMXAdapter {
235
+ static async renderToString(template, props) {
236
+ try {
237
+ const html = await template(props);
238
+ return html;
239
+ } catch (error) {
240
+ console.error("Error rendering HTMX template:", error);
241
+ throw error;
242
+ }
243
+ }
244
+ static isHTMXRequest(request) {
245
+ return request.headers.get("HX-Request") === "true";
246
+ }
247
+ static isBoostedRequest(request) {
248
+ return request.headers.get("HX-Boosted") === "true";
249
+ }
250
+ static getCurrentUrl(request) {
251
+ return request.headers.get("HX-Current-URL");
252
+ }
253
+ static getTrigger(request) {
254
+ return request.headers.get("HX-Trigger");
255
+ }
256
+ static getTriggerName(request) {
257
+ return request.headers.get("HX-Trigger-Name");
258
+ }
259
+ static getTarget(request) {
260
+ return request.headers.get("HX-Target");
261
+ }
262
+ static createResponse(html, options = {}) {
263
+ const headers = new Headers({
264
+ "Content-Type": "text/html; charset=utf-8"
265
+ });
266
+ if (options.retarget) {
267
+ headers.set("HX-Retarget", options.retarget);
268
+ }
269
+ if (options.reswap) {
270
+ headers.set("HX-Reswap", options.reswap);
271
+ }
272
+ if (options.trigger) {
273
+ headers.set("HX-Trigger", options.trigger);
274
+ }
275
+ if (options.triggerAfterSettle) {
276
+ headers.set("HX-Trigger-After-Settle", options.triggerAfterSettle);
277
+ }
278
+ if (options.triggerAfterSwap) {
279
+ headers.set("HX-Trigger-After-Swap", options.triggerAfterSwap);
280
+ }
281
+ if (options.redirect) {
282
+ headers.set("HX-Redirect", options.redirect);
283
+ }
284
+ if (options.refresh) {
285
+ headers.set("HX-Refresh", "true");
286
+ }
287
+ if (options.pushUrl) {
288
+ headers.set("HX-Push-Url", options.pushUrl);
289
+ }
290
+ if (options.replaceUrl) {
291
+ headers.set("HX-Replace-Url", options.replaceUrl);
292
+ }
293
+ return new Response(html, { headers, status: options.status || 200 });
294
+ }
295
+ static redirectResponse(url) {
296
+ return new Response(null, {
297
+ headers: {
298
+ "HX-Redirect": url
299
+ }
300
+ });
301
+ }
302
+ static refreshResponse() {
303
+ return new Response(null, {
304
+ headers: {
305
+ "HX-Refresh": "true"
306
+ }
307
+ });
308
+ }
309
+ static createDocument(html, meta = {}, config) {
310
+ const title = meta.title || "KilatJS App";
311
+ const description = meta.description || "";
312
+ const robots = meta.robots || "index,follow";
313
+ let metaTags = "";
314
+ metaTags += `<meta charset="utf-8" />
315
+ `;
316
+ metaTags += `<meta name="viewport" content="width=device-width, initial-scale=1" />
317
+ `;
318
+ metaTags += `<meta name="robots" content="${robots}" />
319
+ `;
320
+ if (description) {
321
+ metaTags += `<meta name="description" content="${this.escapeHtml(description)}" />
322
+ `;
323
+ }
324
+ if (meta.canonical) {
325
+ metaTags += `<link rel="canonical" href="${this.escapeHtml(meta.canonical)}" />
326
+ `;
327
+ }
328
+ metaTags += `<meta property="og:title" content="${this.escapeHtml(meta.ogTitle || title)}" />
329
+ `;
330
+ if (meta.ogDescription || description) {
331
+ metaTags += `<meta property="og:description" content="${this.escapeHtml(meta.ogDescription || description)}" />
332
+ `;
333
+ }
334
+ if (meta.ogImage) {
335
+ metaTags += `<meta property="og:image" content="${this.escapeHtml(meta.ogImage)}" />
336
+ `;
337
+ }
338
+ const twitterCard = meta.twitterCard || "summary";
339
+ metaTags += `<meta name="twitter:card" content="${twitterCard}" />
340
+ `;
341
+ const reservedKeys = [
342
+ "title",
343
+ "description",
344
+ "robots",
345
+ "canonical",
346
+ "ogTitle",
347
+ "ogDescription",
348
+ "ogImage",
349
+ "twitterCard"
350
+ ];
351
+ Object.entries(meta).forEach(([key, value]) => {
352
+ if (!reservedKeys.includes(key) && typeof value === "string") {
353
+ metaTags += `<meta name="${this.escapeHtml(key)}" content="${this.escapeHtml(value)}" />
354
+ `;
355
+ }
356
+ });
357
+ metaTags += `<link rel="stylesheet" href="/styles.css" />
358
+ `;
359
+ metaTags += `<script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>
360
+ `;
361
+ metaTags += `<link rel="preconnect" href="https://fonts.googleapis.com" />
362
+ `;
363
+ metaTags += `<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
364
+ `;
365
+ metaTags += `<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
366
+ `;
367
+ return `<!DOCTYPE html>
368
+ <html lang="en">
369
+ <head>
370
+ <title>${this.escapeHtml(title)}</title>
371
+ ${metaTags}
372
+ </head>
373
+ <body hx-boost="true">
374
+ <div id="root">
375
+ ${html}
376
+ </div>
377
+ ${config?.dev ? getLiveReloadScript() : ""}
378
+ </body>
379
+ </html>`;
380
+ }
381
+ static escapeHtml(str) {
382
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
383
+ }
384
+ }
385
+ var init_htmx = __esm(() => {
386
+ init_live_reload();
387
+ });
388
+
389
+ // src/core/router.ts
390
+ var Router;
391
+ var init_router = __esm(() => {
392
+ init_react();
393
+ init_htmx();
394
+ Router = class Router {
395
+ routes = new Map;
396
+ config;
397
+ staticPaths = new Map;
398
+ fsRouter;
399
+ routeCache = new Map;
400
+ preloadedRoutes = new Map;
401
+ routePatterns = [];
402
+ apiRoutes = new Map;
403
+ static NOT_FOUND_RESPONSE = new Response("404 Not Found", { status: 404 });
404
+ static METHOD_NOT_ALLOWED_RESPONSE = new Response(JSON.stringify({ error: "Method Not Allowed" }), { status: 405, headers: { "Content-Type": "application/json" } });
405
+ static INTERNAL_ERROR_RESPONSE = new Response(JSON.stringify({ error: "Internal Server Error" }), { status: 500, headers: { "Content-Type": "application/json" } });
406
+ contextPool = [];
407
+ constructor(config) {
408
+ this.config = config;
409
+ this.fsRouter = new Bun.FileSystemRouter({
410
+ dir: config.routesDir,
411
+ style: "nextjs",
412
+ origin: `http://${config.hostname || "localhost"}:${config.port || 3000}`
413
+ });
414
+ }
415
+ getRoutes() {
416
+ return this.routes;
417
+ }
418
+ getStaticPaths() {
419
+ return this.staticPaths;
420
+ }
421
+ async loadRoutes(silent = false) {
422
+ this.fsRouter.reload();
423
+ this.routeCache.clear();
424
+ this.preloadedRoutes.clear();
425
+ this.routePatterns.length = 0;
426
+ this.apiRoutes.clear();
427
+ this.staticHtmlFiles.clear();
428
+ await this.preloadAllRoutes(silent);
429
+ if (!silent) {
430
+ console.log("\uD83D\uDD04 FileSystemRouter initialized with", this.preloadedRoutes.size, "routes");
431
+ }
432
+ }
433
+ async preloadAllRoutes(silent = false) {
434
+ await this.scanAndPreloadRoutes(this.config.routesDir, "", silent);
435
+ }
436
+ staticHtmlFiles = new Map;
437
+ async scanAndPreloadRoutes(dir, _basePath, _silent = false) {
438
+ try {
439
+ const proc = Bun.spawn(["find", dir, "-name", "*.ts", "-o", "-name", "*.tsx", "-o", "-name", "*.js", "-o", "-name", "*.jsx", "-o", "-name", "*.html"], {
440
+ stdout: "pipe"
441
+ });
442
+ const output = await new Response(proc.stdout).text();
443
+ const files = output.trim().split(`
444
+ `).filter(Boolean);
445
+ for (const filePath of files) {
446
+ const relativePath = filePath.replace(dir, "");
447
+ const isHtml = filePath.endsWith(".html");
448
+ let routePath = relativePath.replace(/\.(tsx?|jsx?|html)$/, "");
449
+ if (routePath.endsWith("/index")) {
450
+ routePath = routePath.slice(0, -6) || "/";
451
+ }
452
+ if (!routePath.startsWith("/")) {
453
+ routePath = "/" + routePath;
454
+ }
455
+ if (isHtml) {
456
+ this.staticHtmlFiles.set(routePath, filePath);
457
+ continue;
458
+ }
459
+ const routeType = this.getRouteType(routePath);
460
+ try {
461
+ const routeExports = await import(filePath);
462
+ this.preloadedRoutes.set(routePath, routeExports);
463
+ if (routeType === "api") {
464
+ this.apiRoutes.set(routePath, routeExports);
465
+ }
466
+ if (routePath.includes("[")) {
467
+ const pattern = this.createRoutePattern(routePath);
468
+ if (pattern) {
469
+ this.routePatterns.push({
470
+ pattern: pattern.regex,
471
+ filePath,
472
+ paramNames: pattern.paramNames,
473
+ routeType
474
+ });
475
+ }
476
+ }
477
+ } catch (error) {
478
+ console.warn(`Failed to preload route ${filePath}:`, error);
479
+ }
480
+ }
481
+ } catch (error) {
482
+ console.warn("Failed to scan routes:", error);
483
+ }
484
+ }
485
+ getStaticHtmlFile(path) {
486
+ return this.staticHtmlFiles.get(path);
487
+ }
488
+ createRoutePattern(routePath) {
489
+ const paramNames = [];
490
+ let pattern = routePath.replace(/[.*+?^${}|\\]/g, "\\$&");
491
+ pattern = pattern.replace(/\\?\[([^\]]+)\\?\]/g, (_match, paramName) => {
492
+ paramNames.push(paramName);
493
+ return "([^/]+)";
494
+ });
495
+ try {
496
+ const regex = new RegExp(`^${pattern}$`);
497
+ return { regex, paramNames };
498
+ } catch (error) {
499
+ console.warn(`Failed to create pattern for ${routePath}:`, error);
500
+ return null;
501
+ }
502
+ }
503
+ getRouteType(pathname) {
504
+ if (pathname.startsWith("/api"))
505
+ return "api";
506
+ if (pathname.includes("[") || pathname.includes(":"))
507
+ return "dynamic";
508
+ return "static";
509
+ }
510
+ matchRoute(path) {
511
+ if (this.routeCache.has(path)) {
512
+ return this.routeCache.get(path);
513
+ }
514
+ let match = null;
515
+ if (this.preloadedRoutes.has(path)) {
516
+ match = {
517
+ route: path,
518
+ params: {},
519
+ exports: this.preloadedRoutes.get(path),
520
+ routeType: this.getRouteType(path)
521
+ };
522
+ }
523
+ if (!match) {
524
+ for (const routePattern of this.routePatterns) {
525
+ const regexMatch = path.match(routePattern.pattern);
526
+ if (regexMatch) {
527
+ const params = {};
528
+ routePattern.paramNames.forEach((name, index) => {
529
+ params[name] = regexMatch[index + 1];
530
+ });
531
+ let routePath = routePattern.filePath.replace(this.config.routesDir, "").replace(/\.(tsx?|jsx?)$/, "");
532
+ if (!routePath.startsWith("/")) {
533
+ routePath = "/" + routePath;
534
+ }
535
+ const exports = this.preloadedRoutes.get(routePath);
536
+ if (exports) {
537
+ match = {
538
+ route: path,
539
+ params,
540
+ exports,
541
+ routeType: routePattern.routeType
542
+ };
543
+ break;
544
+ }
545
+ }
546
+ }
547
+ }
548
+ this.routeCache.set(path, match);
549
+ return match;
550
+ }
551
+ async handleRequest(request) {
552
+ const url = request.url;
553
+ const pathStart = url.indexOf("/", 8);
554
+ const pathEnd = url.indexOf("?", pathStart);
555
+ const path = pathEnd === -1 ? url.slice(pathStart) : url.slice(pathStart, pathEnd);
556
+ if (path.startsWith("/api/")) {
557
+ return this.handleApiRouteFast(request, path);
558
+ }
559
+ if (path.endsWith(".css") || path === "/favicon.ico") {
560
+ const staticResponse = await this.handleStaticFile(path);
561
+ if (staticResponse)
562
+ return staticResponse;
563
+ }
564
+ if (!this.config.dev) {
565
+ const outDir = this.config.outDir || "./dist";
566
+ const filePath = `${outDir}${path === "/" ? "/index.html" : path}`;
567
+ const file = Bun.file(filePath);
568
+ if (await file.exists()) {
569
+ return new Response(file);
570
+ }
571
+ }
572
+ const htmlFilePath = this.staticHtmlFiles.get(path);
573
+ if (htmlFilePath) {
574
+ const file = Bun.file(htmlFilePath);
575
+ if (await file.exists()) {
576
+ return new Response(file, {
577
+ headers: {
578
+ "Content-Type": "text/html; charset=utf-8",
579
+ "Cache-Control": this.config.dev ? "no-cache" : "public, max-age=3600"
580
+ }
581
+ });
582
+ }
583
+ }
584
+ const match = this.matchRoute(path);
585
+ if (!match) {
586
+ return Router.NOT_FOUND_RESPONSE;
587
+ }
588
+ const { params, exports, routeType } = match;
589
+ const context = this.getContextFromPool(request, params, url);
590
+ if (request.method !== "GET" && request.method !== "HEAD") {
591
+ const actionHandler = exports[request.method];
592
+ if (actionHandler && typeof actionHandler === "function") {
593
+ try {
594
+ const result = await actionHandler(context);
595
+ this.returnContextToPool(context);
596
+ return result;
597
+ } catch (error) {
598
+ this.returnContextToPool(context);
599
+ console.error(`Error handling ${request.method}:`, error);
600
+ return Router.INTERNAL_ERROR_RESPONSE;
601
+ }
602
+ }
603
+ this.returnContextToPool(context);
604
+ return Router.METHOD_NOT_ALLOWED_RESPONSE;
605
+ }
606
+ try {
607
+ const data = exports.load ? await exports.load(context) : {};
608
+ if (data instanceof Response) {
609
+ this.returnContextToPool(context);
610
+ return data;
611
+ }
612
+ const meta = exports.meta || {};
613
+ const html = await this.renderPage(exports.default, { data, params, state: context.state }, exports.ui, meta, { clientScript: exports.clientScript });
614
+ const cacheControl = routeType === "dynamic" ? "no-cache" : "public, max-age=3600";
615
+ this.returnContextToPool(context);
616
+ return new Response(html, {
617
+ headers: {
618
+ "Content-Type": "text/html; charset=utf-8",
619
+ "Cache-Control": cacheControl
620
+ }
621
+ });
622
+ } catch (error) {
623
+ this.returnContextToPool(context);
624
+ if (error instanceof Response)
625
+ return error;
626
+ console.error("Error rendering page:", error);
627
+ return Router.INTERNAL_ERROR_RESPONSE;
628
+ }
629
+ }
630
+ async handleApiRouteFast(request, path) {
631
+ let exports = this.apiRoutes.get(path);
632
+ let params = {};
633
+ if (!exports) {
634
+ for (const routePattern of this.routePatterns) {
635
+ if (routePattern.routeType === "api") {
636
+ const regexMatch = path.match(routePattern.pattern);
637
+ if (regexMatch) {
638
+ routePattern.paramNames.forEach((name, index) => {
639
+ params[name] = regexMatch[index + 1];
640
+ });
641
+ for (const [preloadedPath, preloadedExports] of this.preloadedRoutes.entries()) {
642
+ if (preloadedPath.includes("[") && preloadedPath.startsWith("/api/")) {
643
+ exports = preloadedExports;
644
+ break;
645
+ }
646
+ }
647
+ if (exports)
648
+ break;
649
+ }
650
+ }
651
+ }
652
+ }
653
+ if (!exports)
654
+ return Router.NOT_FOUND_RESPONSE;
655
+ const handler = exports[request.method] || exports.default;
656
+ if (!handler || typeof handler !== "function") {
657
+ return Router.METHOD_NOT_ALLOWED_RESPONSE;
658
+ }
659
+ try {
660
+ const context = this.getMinimalApiContext(request, params);
661
+ const response = await handler(context);
662
+ if (response instanceof Response)
663
+ return response;
664
+ return new Response(JSON.stringify(response), {
665
+ headers: { "Content-Type": "application/json" }
666
+ });
667
+ } catch (error) {
668
+ console.error(`API Error [${request.method} ${path}]:`, error);
669
+ return Router.INTERNAL_ERROR_RESPONSE;
670
+ }
671
+ }
672
+ getContextFromPool(request, params, url) {
673
+ if (this.contextPool.length > 0) {
674
+ const context = this.contextPool.pop();
675
+ context.request = request;
676
+ context.params = params;
677
+ context.query = new URLSearchParams(url.split("?")[1] || "");
678
+ context.state = {};
679
+ return context;
680
+ }
681
+ return {
682
+ request,
683
+ params,
684
+ query: new URLSearchParams(url.split("?")[1] || ""),
685
+ state: {}
686
+ };
687
+ }
688
+ returnContextToPool(context) {
689
+ if (this.contextPool.length < 100) {
690
+ this.contextPool.push(context);
691
+ }
692
+ }
693
+ getMinimalApiContext(request, params = {}) {
694
+ return {
695
+ request,
696
+ params,
697
+ query: new URLSearchParams(request.url.split("?")[1] || ""),
698
+ state: {}
699
+ };
700
+ }
701
+ async handleStaticFile(path) {
702
+ if (path.endsWith(".css")) {
703
+ const cssPath = this.config.tailwind?.cssPath || "./styles.css";
704
+ try {
705
+ const cssFile = Bun.file(cssPath);
706
+ if (await cssFile.exists()) {
707
+ return new Response(cssFile, {
708
+ headers: {
709
+ "Content-Type": "text/css",
710
+ "Cache-Control": this.config.dev ? "no-cache" : "public, max-age=3600"
711
+ }
712
+ });
713
+ }
714
+ } catch {}
715
+ }
716
+ if (path === "/favicon.ico") {
717
+ return new Response(null, { status: 204 });
718
+ }
719
+ return null;
720
+ }
721
+ async renderPage(PageComponent, props, uiFramework = "react", meta = {}, options = {}) {
722
+ switch (uiFramework) {
723
+ case "react":
724
+ const reactContent = await ReactAdapter.renderToString(PageComponent, props);
725
+ return ReactAdapter.createDocument(reactContent, meta, this.config, {
726
+ clientScript: options.clientScript
727
+ });
728
+ case "htmx":
729
+ const htmxContent = await HTMXAdapter.renderToString(PageComponent, props);
730
+ return HTMXAdapter.createDocument(htmxContent, meta, this.config);
731
+ default:
732
+ throw new Error(`Unsupported UI framework: ${uiFramework}`);
733
+ }
734
+ }
735
+ };
736
+ });
737
+
738
+ // src/server/server.ts
739
+ class KilatServer {
740
+ router;
741
+ config;
742
+ appDir;
743
+ routesDir;
744
+ constructor(config) {
745
+ this.config = config;
746
+ const { appDir, routesDir } = this.resolvePaths(config);
747
+ this.appDir = appDir;
748
+ this.routesDir = routesDir;
749
+ this.router = new Router({ ...config, routesDir: this.routesDir });
750
+ }
751
+ resolvePaths(config) {
752
+ const cwd = process.cwd();
753
+ const appDir = config.appDir.startsWith("/") ? config.appDir : `${cwd}/${config.appDir}`;
754
+ const routesPath = `${appDir}/routes`;
755
+ const pagesPath = `${appDir}/pages`;
756
+ const checkRoutes = Bun.spawnSync(["test", "-d", routesPath]);
757
+ if (checkRoutes.exitCode === 0) {
758
+ return { appDir, routesDir: routesPath };
759
+ }
760
+ const checkPages = Bun.spawnSync(["test", "-d", pagesPath]);
761
+ if (checkPages.exitCode === 0) {
762
+ return { appDir, routesDir: pagesPath };
763
+ }
764
+ console.warn(`⚠️ No routes/ or pages/ folder found in ${appDir}, defaulting to routes/`);
765
+ return { appDir, routesDir: routesPath };
766
+ }
767
+ async start() {
768
+ const config = this.config;
769
+ const router = this.router;
770
+ if (config.tailwind?.enabled) {
771
+ await this.runTailwind(false, config.dev);
772
+ if (config.dev) {
773
+ this.runTailwind(true, true);
774
+ }
775
+ }
776
+ await router.loadRoutes(config.dev);
777
+ const routes = {};
778
+ const cssPath = config.tailwind?.cssPath || "./styles.css";
779
+ routes["/styles.css"] = async () => {
780
+ const file = Bun.file(cssPath);
781
+ if (await file.exists()) {
782
+ return new Response(file, {
783
+ headers: { "Content-Type": "text/css" }
784
+ });
785
+ }
786
+ return new Response("", { headers: { "Content-Type": "text/css" } });
787
+ };
788
+ const server = Bun.serve({
789
+ port: config.port || 3000,
790
+ hostname: config.hostname || "localhost",
791
+ development: config.dev ? {
792
+ hmr: true,
793
+ console: true
794
+ } : false,
795
+ routes,
796
+ async fetch(request) {
797
+ return router.handleRequest(request);
798
+ }
799
+ });
800
+ if (config.dev) {
801
+ console.log(`
802
+ ⚡ KilatJS Dev Server
803
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
804
+ ➜ http://${server.hostname}:${server.port}
805
+ ➜ HMR + Live Reload enabled
806
+ ➜ Edit files and see changes instantly
807
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
808
+ `);
809
+ startLiveReload();
810
+ watchDirectory(this.routesDir, async () => {
811
+ await router.loadRoutes(true);
812
+ notifyReload();
813
+ });
814
+ const componentsDir = `${this.appDir}/components`;
815
+ const checkComponents = Bun.spawnSync(["test", "-d", componentsDir]);
816
+ if (checkComponents.exitCode === 0) {
817
+ watchDirectory(componentsDir, async () => {
818
+ await router.loadRoutes(true);
819
+ notifyReload();
820
+ });
821
+ }
822
+ } else {
823
+ console.log(`
824
+ \uD83D\uDE80 KilatJS Production Server
825
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
826
+ ➜ http://${server.hostname}:${server.port}
827
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
828
+ `);
829
+ }
830
+ return server;
831
+ }
832
+ async buildStatic() {
833
+ console.log(`
834
+ \uD83D\uDD28 KilatJS Production Build
835
+ `);
836
+ if (this.config.tailwind?.enabled) {
837
+ await this.runTailwind(false, false);
838
+ }
839
+ await this.router.loadRoutes(true);
840
+ await this.ensureDir(this.config.outDir);
841
+ await this.displayRouteAnalysis();
842
+ await this.copyStaticAssets();
843
+ await this.generateProductionServer();
844
+ console.log(`
845
+ ✅ Build Complete!
846
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
847
+ Output: ${this.config.outDir}
848
+
849
+ Start: bun ${this.config.outDir}/server.js
850
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
851
+ `);
852
+ }
853
+ async displayRouteAnalysis() {
854
+ console.log("\uD83D\uDCC4 Routes Analysis:");
855
+ console.log("─────────────────────────────────────────────────────────");
856
+ const routes = [];
857
+ try {
858
+ const proc = Bun.spawn(["find", this.routesDir, "-name", "*.ts", "-o", "-name", "*.tsx", "-o", "-name", "*.js", "-o", "-name", "*.jsx"], {
859
+ stdout: "pipe"
860
+ });
861
+ const output = await new Response(proc.stdout).text();
862
+ const files = output.trim().split(`
863
+ `).filter(Boolean);
864
+ for (const filePath of files) {
865
+ const relativePath = filePath.replace(this.routesDir, "");
866
+ let routePath = relativePath.replace(/\.(tsx?|jsx?)$/, "");
867
+ if (routePath.endsWith("/index")) {
868
+ routePath = routePath.slice(0, -6) || "/";
869
+ }
870
+ if (!routePath.startsWith("/")) {
871
+ routePath = "/" + routePath;
872
+ }
873
+ let type;
874
+ if (routePath.startsWith("/api")) {
875
+ type = "API";
876
+ } else if (routePath.includes("[")) {
877
+ type = "Dynamic SSR";
878
+ } else {
879
+ type = "SSR";
880
+ }
881
+ const shortFile = relativePath.replace(/^\//, "");
882
+ routes.push({ path: routePath, type, file: shortFile });
883
+ }
884
+ } catch (error) {
885
+ console.warn("Failed to analyze routes:", error);
886
+ }
887
+ routes.sort((a, b) => a.path.localeCompare(b.path));
888
+ const maxPathLen = Math.max(...routes.map((r) => r.path.length), 10);
889
+ const maxTypeLen = Math.max(...routes.map((r) => r.type.length), 6);
890
+ for (const route of routes) {
891
+ const typeIcon = route.type === "API" ? "⚡" : route.type === "Dynamic SSR" ? "\uD83D\uDD04" : "\uD83D\uDCC4";
892
+ const paddedPath = route.path.padEnd(maxPathLen);
893
+ const paddedType = route.type.padEnd(maxTypeLen);
894
+ console.log(` ${typeIcon} ${paddedPath} ${paddedType} ${route.file}`);
895
+ }
896
+ console.log("─────────────────────────────────────────────────────────");
897
+ const apiCount = routes.filter((r) => r.type === "API").length;
898
+ const dynamicCount = routes.filter((r) => r.type === "Dynamic SSR").length;
899
+ const ssrCount = routes.filter((r) => r.type === "SSR").length;
900
+ console.log(` Total: ${routes.length} routes (${ssrCount} SSR, ${dynamicCount} Dynamic, ${apiCount} API)
901
+ `);
902
+ }
903
+ async generateProductionServer() {
904
+ console.log(`
905
+ ⚙️ Generating production server with FileSystemRouter...`);
906
+ const srcOutDir = `${this.config.outDir}/src`;
907
+ await this.copyDir(this.appDir, srcOutDir);
908
+ console.log(` ✓ src/ (copied with all components and routes)`);
909
+ const serverCode = `#!/usr/bin/env bun
910
+ /**
911
+ * KilatJS Production Server with FileSystemRouter
912
+ * Generated at: ${new Date().toISOString()}
913
+ *
914
+ * Uses Bun's built-in FileSystemRouter for optimal performance
915
+ */
916
+
917
+ const PORT = process.env.PORT || ${this.config.port || 3000};
918
+ const HOST = process.env.HOST || "${this.config.hostname || "localhost"}";
919
+ const ROOT = import.meta.dir;
920
+
921
+ // Initialize FileSystemRouter
922
+ const fsRouter = new Bun.FileSystemRouter({
923
+ dir: ROOT + "/src/routes",
924
+ style: "nextjs",
925
+ origin: \`http://\${HOST}:\${PORT}\`,
926
+ });
927
+
928
+ function getRouteType(pathname) {
929
+ if (pathname.startsWith("/api")) return "api";
930
+ if (pathname.includes("[") || pathname.includes(":")) return "dynamic";
931
+ return "static";
932
+ }
933
+
934
+ // React SSR support
935
+ let React, ReactDOMServer;
936
+ try {
937
+ React = await import("react");
938
+ ReactDOMServer = await import("react-dom/server");
939
+ } catch (e) {
940
+ console.warn("React not available for SSR");
941
+ }
942
+
943
+ async function renderPage(Component, props, meta = {}) {
944
+ if (!ReactDOMServer || !React) {
945
+ return "<html><body>React not available for SSR</body></html>";
946
+ }
947
+
948
+ const content = ReactDOMServer.renderToString(React.createElement(Component, props));
949
+ return \`<!DOCTYPE html>
950
+ <html lang="en">
951
+ <head>
952
+ <meta charset="utf-8" />
953
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
954
+ <title>\${meta.title || "KilatJS App"}</title>
955
+ \${meta.description ? \`<meta name="description" content="\${meta.description}" />\` : ""}
956
+ <link rel="stylesheet" href="/styles.css" />
957
+ </head>
958
+ <body>
959
+ <div id="root">\${content}</div>
960
+ </body>
961
+ </html>\`;
962
+ }
963
+
964
+ console.log(\`
965
+ \uD83D\uDE80 KilatJS Production Server (FileSystemRouter)
966
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
967
+ http://\${HOST}:\${PORT}
968
+ Using Bun FileSystemRouter for optimal performance
969
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
970
+ \`);
971
+
972
+ Bun.serve({
973
+ port: PORT,
974
+ hostname: HOST,
975
+ async fetch(req) {
976
+ const url = new URL(req.url);
977
+ const path = url.pathname;
978
+
979
+ // 1. Try static files first
980
+ const staticFile = Bun.file(ROOT + (path === "/" ? "/index.html" : path));
981
+ if (await staticFile.exists()) {
982
+ return new Response(staticFile);
983
+ }
984
+
985
+ // 2. Try index.html for directories
986
+ const indexFile = Bun.file(ROOT + path + "/index.html");
987
+ if (await indexFile.exists()) {
988
+ return new Response(indexFile);
989
+ }
990
+
991
+ // 3. Use FileSystemRouter for dynamic routes
992
+ const match = fsRouter.match(path);
993
+ if (!match) {
994
+ console.log(\`No route match found for: \${path}\`);
995
+ return new Response("404 Not Found", { status: 404 });
996
+ }
997
+
998
+ // Use relative path for cleaner logs
999
+ const relativePath = match.filePath.replace(ROOT + "/", "");
1000
+ console.log(\`Route matched: \${path} -> \${relativePath}\`);
1001
+
1002
+ try {
1003
+ // Dynamically import the route
1004
+ const routeExports = await import(match.filePath);
1005
+ const routeType = getRouteType(match.pathname);
1006
+ const params = match.params || {};
1007
+ const ctx = {
1008
+ request: req,
1009
+ params,
1010
+ query: url.searchParams,
1011
+ state: {}
1012
+ };
1013
+
1014
+ // API routes
1015
+ if (routeType === "api") {
1016
+ const handler = routeExports[req.method] || routeExports.default;
1017
+ if (!handler) {
1018
+ return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
1019
+ status: 405,
1020
+ headers: { "Content-Type": "application/json" }
1021
+ });
1022
+ }
1023
+
1024
+ const result = await handler(ctx);
1025
+ if (result instanceof Response) return result;
1026
+
1027
+ return new Response(JSON.stringify(result), {
1028
+ headers: { "Content-Type": "application/json" }
1029
+ });
1030
+ }
1031
+
1032
+ // Page routes - handle HTTP methods
1033
+ if (req.method !== "GET" && req.method !== "HEAD") {
1034
+ const handler = routeExports[req.method];
1035
+ if (handler) return await handler(ctx);
1036
+ return new Response("Method Not Allowed", { status: 405 });
1037
+ }
1038
+
1039
+ // GET requests - SSR
1040
+ const data = routeExports.load ? await routeExports.load(ctx) : {};
1041
+ if (data instanceof Response) return data;
1042
+
1043
+ const html = await renderPage(
1044
+ routeExports.default,
1045
+ { data, params, state: {} },
1046
+ routeExports.meta || {}
1047
+ );
1048
+
1049
+ const cacheControl = routeType === "dynamic" ? "no-cache" : "public, max-age=3600";
1050
+
1051
+ return new Response(html, {
1052
+ headers: {
1053
+ "Content-Type": "text/html; charset=utf-8",
1054
+ "Cache-Control": cacheControl
1055
+ }
1056
+ });
1057
+
1058
+ } catch (error) {
1059
+ if (error instanceof Response) return error;
1060
+ console.error("Route error:", error);
1061
+ return new Response("Internal Server Error", { status: 500 });
1062
+ }
1063
+ }
1064
+ });
1065
+ `;
1066
+ const serverPath = `${this.config.outDir}/server.js`;
1067
+ await Bun.write(serverPath, serverCode);
1068
+ console.log(` ✓ server.js (FileSystemRouter-based)`);
1069
+ }
1070
+ async runTailwind(watch, silent = false) {
1071
+ const { inputPath, cssPath } = this.config.tailwind || {};
1072
+ if (!inputPath || !cssPath) {
1073
+ if (!silent)
1074
+ console.warn("⚠️ Tailwind enabled but inputPath or cssPath missing");
1075
+ return;
1076
+ }
1077
+ const resolvedInputPath = inputPath.startsWith("/") ? inputPath : `${process.cwd()}/${inputPath}`;
1078
+ const resolvedCssPath = cssPath.startsWith("/") ? cssPath : `${process.cwd()}/${cssPath}`;
1079
+ const args = ["bunx", "@tailwindcss/cli", "-i", resolvedInputPath, "-o", resolvedCssPath];
1080
+ if (watch) {
1081
+ args.push("--watch");
1082
+ }
1083
+ if (!silent) {
1084
+ console.log(`\uD83C\uDFA8 ${watch ? "Watching" : "Building"} Tailwind CSS...`);
1085
+ }
1086
+ try {
1087
+ const proc = Bun.spawn(args, {
1088
+ stdout: silent ? "pipe" : "inherit",
1089
+ stderr: silent ? "pipe" : "inherit",
1090
+ cwd: process.cwd()
1091
+ });
1092
+ if (!watch) {
1093
+ await proc.exited;
1094
+ }
1095
+ } catch (error) {
1096
+ if (!silent)
1097
+ console.error("Failed to run Tailwind:", error);
1098
+ }
1099
+ }
1100
+ async copyStaticAssets() {
1101
+ console.log(`
1102
+ \uD83D\uDCE6 Copying static assets...`);
1103
+ const cssPath = this.config.tailwind?.cssPath || "./styles.css";
1104
+ const cssFile = Bun.file(cssPath);
1105
+ if (await cssFile.exists()) {
1106
+ const outputCssPath = `${this.config.outDir}/styles.css`;
1107
+ await Bun.write(outputCssPath, cssFile);
1108
+ console.log(` ✓ styles.css`);
1109
+ }
1110
+ if (this.config.publicDir) {
1111
+ const publicFile = Bun.file(this.config.publicDir);
1112
+ if (await publicFile.exists()) {
1113
+ await this.copyDir(this.config.publicDir, this.config.outDir);
1114
+ console.log(` ✓ public assets`);
1115
+ }
1116
+ }
1117
+ }
1118
+ async copyDir(src, dest) {
1119
+ try {
1120
+ const proc = Bun.spawn(["cp", "-r", src, dest], {
1121
+ stdout: "pipe",
1122
+ stderr: "pipe"
1123
+ });
1124
+ await proc.exited;
1125
+ } catch (error) {
1126
+ console.warn(`Failed to copy ${src} to ${dest}:`, error);
1127
+ }
1128
+ }
1129
+ async ensureDir(dir) {
1130
+ try {
1131
+ const proc = Bun.spawn(["mkdir", "-p", dir], {
1132
+ stdout: "pipe",
1133
+ stderr: "pipe"
1134
+ });
1135
+ await proc.exited;
1136
+ } catch (error) {
1137
+ console.warn(`Failed to create directory ${dir}:`, error);
1138
+ }
1139
+ }
1140
+ }
1141
+ var init_server = __esm(() => {
1142
+ init_router();
1143
+ init_live_reload();
1144
+ });
1145
+ // src/index.ts
1146
+ var exports_src = {};
1147
+ __export(exports_src, {
1148
+ stopLiveReload: () => stopLiveReload,
1149
+ startLiveReload: () => startLiveReload,
1150
+ startDevServer: () => startDevServer,
1151
+ runCLI: () => runCLI,
1152
+ notifyReload: () => notifyReload,
1153
+ defineMeta: () => defineMeta,
1154
+ defineLoader: () => defineLoader,
1155
+ defineConfig: () => defineConfig,
1156
+ defaultConfig: () => defaultConfig,
1157
+ default: () => src_default,
1158
+ createKilat: () => createKilat,
1159
+ buildStatic: () => buildStatic,
1160
+ Router: () => Router,
1161
+ ReactAdapter: () => ReactAdapter,
1162
+ KilatServer: () => KilatServer,
1163
+ HTMXAdapter: () => HTMXAdapter
1164
+ });
1165
+ function createKilat(config = {}) {
1166
+ const tailwindConfig = {
1167
+ enabled: config.tailwind?.enabled ?? defaultConfig.tailwind?.enabled ?? false,
1168
+ inputPath: config.tailwind?.inputPath,
1169
+ cssPath: config.tailwind?.cssPath ?? defaultConfig.tailwind?.cssPath ?? "./styles.css",
1170
+ configPath: config.tailwind?.configPath ?? defaultConfig.tailwind?.configPath
1171
+ };
1172
+ const finalConfig = {
1173
+ ...defaultConfig,
1174
+ ...config,
1175
+ tailwind: tailwindConfig
1176
+ };
1177
+ return new KilatServer(finalConfig);
1178
+ }
1179
+ async function startDevServer(config = {}) {
1180
+ const server = createKilat({ ...config, dev: true });
1181
+ return server.start();
1182
+ }
1183
+ async function buildStatic(config = {}) {
1184
+ const server = createKilat(config);
1185
+ return server.buildStatic();
1186
+ }
1187
+ function defineLoader(loader) {
1188
+ return loader;
1189
+ }
1190
+ function defineConfig(config) {
1191
+ return config;
1192
+ }
1193
+ function defineMeta(meta) {
1194
+ return meta;
1195
+ }
1196
+ async function runCLI(args = process.argv.slice(2)) {
1197
+ const command = args[0];
1198
+ if (!command) {
1199
+ console.error("Please specify a command: dev or build");
1200
+ process.exit(1);
1201
+ }
1202
+ async function loadConfig() {
1203
+ const configPath = `${process.cwd()}/kilat.config.ts`;
1204
+ const file = Bun.file(configPath);
1205
+ if (!await file.exists()) {
1206
+ console.warn("⚠️ No kilat.config.ts found, using defaults");
1207
+ return {};
1208
+ }
1209
+ try {
1210
+ const configModule = await import(configPath);
1211
+ return configModule.default || configModule;
1212
+ } catch (error) {
1213
+ console.error("Failed to load config:", error);
1214
+ return {};
1215
+ }
1216
+ }
1217
+ const config = await loadConfig();
1218
+ switch (command) {
1219
+ case "dev":
1220
+ const isProduction = args.includes("--production");
1221
+ const devServer = createKilat({ ...config, dev: !isProduction });
1222
+ await devServer.start();
1223
+ break;
1224
+ case "build":
1225
+ await buildStatic(config);
1226
+ break;
1227
+ case "serve":
1228
+ const serveOutDir = config.outDir || "./dist";
1229
+ const serverPath = `${process.cwd()}/${serveOutDir}/server.js`;
1230
+ const serverFile = Bun.file(serverPath);
1231
+ if (!await serverFile.exists()) {
1232
+ console.error(`❌ Production server not found at ${serveOutDir}/server.js`);
1233
+ console.error(` Run 'kilat build' first to generate the production build.`);
1234
+ process.exit(1);
1235
+ }
1236
+ const serveProc = Bun.spawn(["bun", serverPath], {
1237
+ stdio: ["inherit", "inherit", "inherit"],
1238
+ cwd: process.cwd()
1239
+ });
1240
+ await serveProc.exited;
1241
+ break;
1242
+ case "preview":
1243
+ const outDir = config.outDir || "./dist";
1244
+ const port = config.port || 3000;
1245
+ const hostname = config.hostname || "localhost";
1246
+ const root = `${process.cwd()}/${outDir}`;
1247
+ console.log(`
1248
+ \uD83D\uDD0D Preview server running:`);
1249
+ console.log(` ➜ http://${hostname}:${port}`);
1250
+ console.log(` Serving: ${outDir}
1251
+ `);
1252
+ Bun.serve({
1253
+ port,
1254
+ hostname,
1255
+ async fetch(req) {
1256
+ const url = new URL(req.url);
1257
+ let path = url.pathname;
1258
+ const filePath = `${root}${path}`;
1259
+ let file = Bun.file(filePath);
1260
+ if (await file.exists()) {
1261
+ return new Response(file);
1262
+ }
1263
+ const indexHtml = `${filePath}/index.html`;
1264
+ file = Bun.file(indexHtml);
1265
+ if (await file.exists()) {
1266
+ return new Response(file);
1267
+ }
1268
+ const rootIndex = `${root}/index.html`;
1269
+ file = Bun.file(rootIndex);
1270
+ if (await file.exists()) {
1271
+ return new Response(file);
1272
+ }
1273
+ return new Response("404 Not Found", { status: 404 });
1274
+ }
1275
+ });
1276
+ break;
1277
+ default:
1278
+ console.error(`Unknown command: ${command}`);
1279
+ console.log("Available commands: dev, build, serve, preview");
1280
+ process.exit(1);
1281
+ }
1282
+ }
1283
+ var defaultConfig, Kilat, src_default;
1284
+ var init_src = __esm(() => {
1285
+ init_router();
1286
+ init_server();
1287
+ init_react();
1288
+ init_htmx();
1289
+ init_live_reload();
1290
+ init_server();
1291
+ defaultConfig = {
1292
+ appDir: "./src",
1293
+ outDir: "./dist",
1294
+ port: 3000,
1295
+ hostname: "localhost",
1296
+ dev: false,
1297
+ tailwind: {
1298
+ enabled: false,
1299
+ cssPath: "./styles.css"
1300
+ }
1301
+ };
1302
+ Kilat = {
1303
+ createKilat,
1304
+ startDevServer,
1305
+ buildStatic,
1306
+ defineConfig,
1307
+ defineLoader,
1308
+ defineMeta,
1309
+ runCLI,
1310
+ defaultConfig
1311
+ };
1312
+ src_default = Kilat;
1313
+ });
1314
+
1315
+ // src/cli.ts
1316
+ var args = process.argv.slice(2);
1317
+ var command = args[0];
1318
+ var isHotMode = process.env.__KILAT_HOT === "1";
1319
+ var isProduction = args.includes("--production");
1320
+ if (command === "dev" && !isHotMode && !isProduction) {
1321
+ const scriptPath = import.meta.path;
1322
+ const proc = Bun.spawn(["bun", "--hot", scriptPath, ...args], {
1323
+ stdio: ["inherit", "inherit", "inherit"],
1324
+ env: { ...process.env, __KILAT_HOT: "1" },
1325
+ cwd: process.cwd()
1326
+ });
1327
+ const exitCode = await proc.exited;
1328
+ process.exit(exitCode);
1329
+ } else {
1330
+ const { runCLI: runCLI2 } = await Promise.resolve().then(() => (init_src(), exports_src));
1331
+ runCLI2(args).catch((error) => {
1332
+ console.error("Fatal error:", error);
1333
+ process.exit(1);
1334
+ });
1335
+ }