@float.js/core 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2163 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
12
+ import path from "path";
13
+ import { fileURLToPath } from "url";
14
+ var init_esm_shims = __esm({
15
+ "../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"() {
16
+ "use strict";
17
+ }
18
+ });
19
+
20
+ // src/devtools/index.ts
21
+ var devtools_exports = {};
22
+ __export(devtools_exports, {
23
+ createDevDashboard: () => createDevDashboard,
24
+ createRequestLogger: () => createRequestLogger,
25
+ dashboardState: () => dashboardState,
26
+ devtools: () => devtools
27
+ });
28
+ function createRequestLogger() {
29
+ return (req, res, next) => {
30
+ const startTime = Date.now();
31
+ const id = `req_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
32
+ const originalEnd = res.end.bind(res);
33
+ res.end = function(chunk, encoding, callback) {
34
+ const duration = Date.now() - startTime;
35
+ dashboardState.logRequest({
36
+ id,
37
+ method: req.method || "GET",
38
+ path: req.url || "/",
39
+ status: res.statusCode,
40
+ duration,
41
+ timestamp: /* @__PURE__ */ new Date()
42
+ });
43
+ return originalEnd(chunk, encoding, callback);
44
+ };
45
+ next();
46
+ };
47
+ }
48
+ function generateDashboardHTML(state) {
49
+ const metrics = state.getMetrics();
50
+ const memoryMB = Math.round(metrics.memoryUsage.heapUsed / 1024 / 1024);
51
+ const memoryTotal = Math.round(metrics.memoryUsage.heapTotal / 1024 / 1024);
52
+ const uptimeSeconds = Math.round(metrics.uptime / 1e3);
53
+ const uptimeFormatted = uptimeSeconds < 60 ? `${uptimeSeconds}s` : uptimeSeconds < 3600 ? `${Math.floor(uptimeSeconds / 60)}m ${uptimeSeconds % 60}s` : `${Math.floor(uptimeSeconds / 3600)}h ${Math.floor(uptimeSeconds % 3600 / 60)}m`;
54
+ const lastBuild = state.builds.length > 0 ? state.builds[0] : null;
55
+ const lastBuildTime = lastBuild ? new Date(lastBuild.timestamp).toLocaleTimeString() : "Never";
56
+ return `<!DOCTYPE html>
57
+ <html lang="en">
58
+ <head>
59
+ <meta charset="UTF-8">
60
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
61
+ <title>Float.js Dev Dashboard</title>
62
+ <link rel="preconnect" href="https://fonts.googleapis.com">
63
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
64
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
65
+ <style>
66
+ * { margin: 0; padding: 0; box-sizing: border-box; }
67
+
68
+ :root {
69
+ --bg-dark: #09090b;
70
+ --bg-card: #18181b;
71
+ --bg-card-hover: #1f1f23;
72
+ --bg-sidebar: #0f0f12;
73
+ --text-primary: #fafafa;
74
+ --text-secondary: #71717a;
75
+ --text-muted: #52525b;
76
+ --accent: #a855f7;
77
+ --accent-glow: rgba(168, 85, 247, 0.15);
78
+ --success: #22c55e;
79
+ --warning: #f59e0b;
80
+ --error: #ef4444;
81
+ --border: #27272a;
82
+ --border-subtle: #1f1f23;
83
+ }
84
+
85
+ body {
86
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
87
+ background: var(--bg-dark);
88
+ color: var(--text-primary);
89
+ min-height: 100vh;
90
+ display: flex;
91
+ }
92
+
93
+ /* Sidebar */
94
+ .sidebar {
95
+ width: 280px;
96
+ background: var(--bg-sidebar);
97
+ border-right: 1px solid var(--border);
98
+ display: flex;
99
+ flex-direction: column;
100
+ position: fixed;
101
+ height: 100vh;
102
+ z-index: 100;
103
+ }
104
+
105
+ .sidebar-header {
106
+ padding: 1.5rem;
107
+ border-bottom: 1px solid var(--border);
108
+ }
109
+
110
+ .logo {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 0.75rem;
114
+ }
115
+
116
+ .logo-icon {
117
+ width: 40px;
118
+ height: 40px;
119
+ background: linear-gradient(135deg, #a855f7 0%, #ec4899 50%, #f97316 100%);
120
+ border-radius: 12px;
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: center;
124
+ font-weight: 700;
125
+ font-size: 1.25rem;
126
+ box-shadow: 0 0 20px rgba(168, 85, 247, 0.3);
127
+ }
128
+
129
+ .logo-text {
130
+ display: flex;
131
+ flex-direction: column;
132
+ }
133
+
134
+ .logo-title {
135
+ font-weight: 700;
136
+ font-size: 1.125rem;
137
+ background: linear-gradient(135deg, #fff 0%, #a1a1aa 100%);
138
+ -webkit-background-clip: text;
139
+ -webkit-text-fill-color: transparent;
140
+ }
141
+
142
+ .logo-version {
143
+ font-size: 0.75rem;
144
+ color: var(--text-muted);
145
+ font-family: 'JetBrains Mono', monospace;
146
+ }
147
+
148
+ /* Status Cards */
149
+ .status-section {
150
+ padding: 1.5rem;
151
+ border-bottom: 1px solid var(--border);
152
+ }
153
+
154
+ .status-grid {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 0.75rem;
158
+ }
159
+
160
+ .status-item {
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: space-between;
164
+ padding: 0.75rem 1rem;
165
+ background: var(--bg-card);
166
+ border-radius: 10px;
167
+ border: 1px solid var(--border-subtle);
168
+ }
169
+
170
+ .status-label {
171
+ font-size: 0.8rem;
172
+ color: var(--text-secondary);
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 0.5rem;
176
+ }
177
+
178
+ .status-value {
179
+ font-size: 0.875rem;
180
+ font-weight: 600;
181
+ font-family: 'JetBrains Mono', monospace;
182
+ }
183
+
184
+ .status-value.connected {
185
+ color: var(--success);
186
+ }
187
+
188
+ .status-value.active {
189
+ color: var(--accent);
190
+ }
191
+
192
+ .status-dot {
193
+ width: 8px;
194
+ height: 8px;
195
+ border-radius: 50%;
196
+ animation: pulse 2s infinite;
197
+ }
198
+
199
+ .status-dot.green { background: var(--success); box-shadow: 0 0 8px var(--success); }
200
+ .status-dot.purple { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
201
+
202
+ @keyframes pulse {
203
+ 0%, 100% { opacity: 1; transform: scale(1); }
204
+ 50% { opacity: 0.7; transform: scale(0.95); }
205
+ }
206
+
207
+ /* Navigation */
208
+ .nav-section {
209
+ padding: 1rem;
210
+ flex: 1;
211
+ }
212
+
213
+ .nav-label {
214
+ font-size: 0.7rem;
215
+ text-transform: uppercase;
216
+ letter-spacing: 0.05em;
217
+ color: var(--text-muted);
218
+ padding: 0.5rem 1rem;
219
+ margin-bottom: 0.5rem;
220
+ }
221
+
222
+ .nav-item {
223
+ display: flex;
224
+ align-items: center;
225
+ gap: 0.75rem;
226
+ padding: 0.75rem 1rem;
227
+ border-radius: 10px;
228
+ color: var(--text-secondary);
229
+ text-decoration: none;
230
+ font-size: 0.875rem;
231
+ font-weight: 500;
232
+ transition: all 0.2s;
233
+ cursor: pointer;
234
+ margin-bottom: 0.25rem;
235
+ }
236
+
237
+ .nav-item:hover {
238
+ background: var(--bg-card);
239
+ color: var(--text-primary);
240
+ }
241
+
242
+ .nav-item.active {
243
+ background: var(--accent-glow);
244
+ color: var(--accent);
245
+ border: 1px solid rgba(168, 85, 247, 0.2);
246
+ }
247
+
248
+ .nav-icon {
249
+ font-size: 1.1rem;
250
+ }
251
+
252
+ .nav-badge {
253
+ margin-left: auto;
254
+ background: var(--bg-card);
255
+ padding: 0.125rem 0.5rem;
256
+ border-radius: 6px;
257
+ font-size: 0.7rem;
258
+ font-family: 'JetBrains Mono', monospace;
259
+ color: var(--text-muted);
260
+ }
261
+
262
+ /* Sidebar Footer */
263
+ .sidebar-footer {
264
+ padding: 1rem 1.5rem;
265
+ border-top: 1px solid var(--border);
266
+ }
267
+
268
+ .docs-link {
269
+ display: flex;
270
+ align-items: center;
271
+ justify-content: center;
272
+ gap: 0.5rem;
273
+ padding: 0.75rem;
274
+ background: linear-gradient(135deg, rgba(168, 85, 247, 0.1), rgba(236, 72, 153, 0.1));
275
+ border: 1px solid rgba(168, 85, 247, 0.2);
276
+ border-radius: 10px;
277
+ color: var(--accent);
278
+ text-decoration: none;
279
+ font-size: 0.875rem;
280
+ font-weight: 500;
281
+ transition: all 0.2s;
282
+ }
283
+
284
+ .docs-link:hover {
285
+ background: linear-gradient(135deg, rgba(168, 85, 247, 0.2), rgba(236, 72, 153, 0.2));
286
+ transform: translateY(-1px);
287
+ }
288
+
289
+ /* Main Content */
290
+ .main {
291
+ margin-left: 280px;
292
+ flex: 1;
293
+ padding: 2rem;
294
+ min-height: 100vh;
295
+ }
296
+
297
+ .page-header {
298
+ margin-bottom: 2rem;
299
+ }
300
+
301
+ .page-title {
302
+ font-size: 1.75rem;
303
+ font-weight: 700;
304
+ margin-bottom: 0.5rem;
305
+ }
306
+
307
+ .page-subtitle {
308
+ color: var(--text-secondary);
309
+ font-size: 0.9rem;
310
+ }
311
+
312
+ /* Metrics Grid */
313
+ .metrics-grid {
314
+ display: grid;
315
+ grid-template-columns: repeat(4, 1fr);
316
+ gap: 1rem;
317
+ margin-bottom: 2rem;
318
+ }
319
+
320
+ .metric-card {
321
+ background: var(--bg-card);
322
+ border: 1px solid var(--border-subtle);
323
+ border-radius: 16px;
324
+ padding: 1.5rem;
325
+ transition: all 0.3s;
326
+ position: relative;
327
+ overflow: hidden;
328
+ }
329
+
330
+ .metric-card::before {
331
+ content: '';
332
+ position: absolute;
333
+ top: 0;
334
+ left: 0;
335
+ right: 0;
336
+ height: 2px;
337
+ background: linear-gradient(90deg, var(--accent), #ec4899);
338
+ opacity: 0;
339
+ transition: opacity 0.3s;
340
+ }
341
+
342
+ .metric-card:hover {
343
+ border-color: var(--accent);
344
+ transform: translateY(-2px);
345
+ box-shadow: 0 8px 32px rgba(168, 85, 247, 0.1);
346
+ }
347
+
348
+ .metric-card:hover::before {
349
+ opacity: 1;
350
+ }
351
+
352
+ .metric-icon {
353
+ font-size: 1.5rem;
354
+ margin-bottom: 1rem;
355
+ }
356
+
357
+ .metric-value {
358
+ font-size: 2rem;
359
+ font-weight: 700;
360
+ font-family: 'JetBrains Mono', monospace;
361
+ margin-bottom: 0.25rem;
362
+ }
363
+
364
+ .metric-label {
365
+ font-size: 0.8rem;
366
+ color: var(--text-secondary);
367
+ text-transform: uppercase;
368
+ letter-spacing: 0.05em;
369
+ }
370
+
371
+ .metric-change {
372
+ position: absolute;
373
+ top: 1rem;
374
+ right: 1rem;
375
+ font-size: 0.75rem;
376
+ padding: 0.25rem 0.5rem;
377
+ border-radius: 6px;
378
+ font-weight: 500;
379
+ }
380
+
381
+ .metric-change.up { background: rgba(34, 197, 94, 0.1); color: var(--success); }
382
+ .metric-change.down { background: rgba(239, 68, 68, 0.1); color: var(--error); }
383
+
384
+ /* Sections */
385
+ .content-grid {
386
+ display: grid;
387
+ grid-template-columns: 1fr 1fr;
388
+ gap: 1.5rem;
389
+ }
390
+
391
+ .section {
392
+ background: var(--bg-card);
393
+ border: 1px solid var(--border-subtle);
394
+ border-radius: 16px;
395
+ overflow: hidden;
396
+ }
397
+
398
+ .section.full-width {
399
+ grid-column: 1 / -1;
400
+ }
401
+
402
+ .section-header {
403
+ padding: 1.25rem 1.5rem;
404
+ border-bottom: 1px solid var(--border);
405
+ display: flex;
406
+ align-items: center;
407
+ justify-content: space-between;
408
+ }
409
+
410
+ .section-title {
411
+ font-size: 1rem;
412
+ font-weight: 600;
413
+ display: flex;
414
+ align-items: center;
415
+ gap: 0.5rem;
416
+ }
417
+
418
+ .section-badge {
419
+ background: var(--bg-dark);
420
+ padding: 0.25rem 0.75rem;
421
+ border-radius: 8px;
422
+ font-size: 0.75rem;
423
+ color: var(--text-muted);
424
+ font-family: 'JetBrains Mono', monospace;
425
+ }
426
+
427
+ .section-content {
428
+ max-height: 400px;
429
+ overflow-y: auto;
430
+ }
431
+
432
+ /* Routes List */
433
+ .route-item {
434
+ display: flex;
435
+ align-items: center;
436
+ gap: 1rem;
437
+ padding: 1rem 1.5rem;
438
+ border-bottom: 1px solid var(--border-subtle);
439
+ transition: background 0.2s;
440
+ }
441
+
442
+ .route-item:last-child {
443
+ border-bottom: none;
444
+ }
445
+
446
+ .route-item:hover {
447
+ background: var(--bg-card-hover);
448
+ }
449
+
450
+ .route-type {
451
+ padding: 0.25rem 0.75rem;
452
+ border-radius: 6px;
453
+ font-size: 0.7rem;
454
+ font-weight: 600;
455
+ text-transform: uppercase;
456
+ letter-spacing: 0.05em;
457
+ min-width: 60px;
458
+ text-align: center;
459
+ }
460
+
461
+ .route-type.page { background: rgba(59, 130, 246, 0.15); color: #60a5fa; }
462
+ .route-type.api { background: rgba(34, 197, 94, 0.15); color: #4ade80; }
463
+ .route-type.layout { background: rgba(168, 85, 247, 0.15); color: #c084fc; }
464
+
465
+ .route-path {
466
+ font-family: 'JetBrains Mono', monospace;
467
+ font-size: 0.875rem;
468
+ flex: 1;
469
+ }
470
+
471
+ .route-file {
472
+ color: var(--text-muted);
473
+ font-size: 0.75rem;
474
+ font-family: 'JetBrains Mono', monospace;
475
+ }
476
+
477
+ /* Request Logs */
478
+ .log-item {
479
+ display: flex;
480
+ align-items: center;
481
+ gap: 1rem;
482
+ padding: 0.875rem 1.5rem;
483
+ border-bottom: 1px solid var(--border-subtle);
484
+ font-size: 0.875rem;
485
+ transition: background 0.2s;
486
+ }
487
+
488
+ .log-item:hover {
489
+ background: var(--bg-card-hover);
490
+ }
491
+
492
+ .log-method {
493
+ font-weight: 600;
494
+ padding: 0.25rem 0.5rem;
495
+ border-radius: 4px;
496
+ font-size: 0.7rem;
497
+ min-width: 50px;
498
+ text-align: center;
499
+ font-family: 'JetBrains Mono', monospace;
500
+ }
501
+
502
+ .log-method.GET { background: rgba(59, 130, 246, 0.15); color: #60a5fa; }
503
+ .log-method.POST { background: rgba(34, 197, 94, 0.15); color: #4ade80; }
504
+ .log-method.PUT { background: rgba(245, 158, 11, 0.15); color: #fbbf24; }
505
+ .log-method.DELETE { background: rgba(239, 68, 68, 0.15); color: #f87171; }
506
+
507
+ .log-status {
508
+ font-weight: 600;
509
+ font-family: 'JetBrains Mono', monospace;
510
+ font-size: 0.8rem;
511
+ }
512
+
513
+ .log-status.s2xx { color: var(--success); }
514
+ .log-status.s3xx { color: #60a5fa; }
515
+ .log-status.s4xx { color: var(--warning); }
516
+ .log-status.s5xx { color: var(--error); }
517
+
518
+ .log-path {
519
+ font-family: 'JetBrains Mono', monospace;
520
+ flex: 1;
521
+ color: var(--text-secondary);
522
+ }
523
+
524
+ .log-duration {
525
+ font-family: 'JetBrains Mono', monospace;
526
+ color: var(--text-muted);
527
+ font-size: 0.8rem;
528
+ }
529
+
530
+ .log-time {
531
+ color: var(--text-muted);
532
+ font-size: 0.75rem;
533
+ }
534
+
535
+ /* Empty State */
536
+ .empty-state {
537
+ text-align: center;
538
+ padding: 3rem;
539
+ color: var(--text-muted);
540
+ }
541
+
542
+ .empty-icon {
543
+ font-size: 2.5rem;
544
+ margin-bottom: 1rem;
545
+ opacity: 0.5;
546
+ }
547
+
548
+ /* Scrollbar */
549
+ ::-webkit-scrollbar { width: 6px; }
550
+ ::-webkit-scrollbar-track { background: transparent; }
551
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
552
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
553
+
554
+ /* Responsive */
555
+ @media (max-width: 1200px) {
556
+ .metrics-grid { grid-template-columns: repeat(2, 1fr); }
557
+ .content-grid { grid-template-columns: 1fr; }
558
+ }
559
+ </style>
560
+ </head>
561
+ <body>
562
+ <!-- Sidebar -->
563
+ <aside class="sidebar">
564
+ <div class="sidebar-header">
565
+ <div class="logo">
566
+ <div class="logo-icon">\u26A1</div>
567
+ <div class="logo-text">
568
+ <span class="logo-title">Float.js</span>
569
+ <span class="logo-version">v2.0.1</span>
570
+ </div>
571
+ </div>
572
+ </div>
573
+
574
+ <div class="status-section">
575
+ <div class="status-grid">
576
+ <div class="status-item">
577
+ <span class="status-label">
578
+ <span class="status-dot green"></span>
579
+ Estado
580
+ </span>
581
+ <span class="status-value connected">Conectado</span>
582
+ </div>
583
+ <div class="status-item">
584
+ <span class="status-label">
585
+ <span class="status-dot purple"></span>
586
+ HMR
587
+ </span>
588
+ <span class="status-value active">Activo</span>
589
+ </div>
590
+ <div class="status-item">
591
+ <span class="status-label">\xDAltimo build</span>
592
+ <span class="status-value">${lastBuildTime}</span>
593
+ </div>
594
+ <div class="status-item">
595
+ <span class="status-label">Uptime</span>
596
+ <span class="status-value">${uptimeFormatted}</span>
597
+ </div>
598
+ </div>
599
+ </div>
600
+
601
+ <nav class="nav-section">
602
+ <div class="nav-label">Dashboard</div>
603
+ <div class="nav-item active">
604
+ <span class="nav-icon">\u{1F4CA}</span>
605
+ Overview
606
+ </div>
607
+ <div class="nav-item">
608
+ <span class="nav-icon">\u{1F6E4}\uFE0F</span>
609
+ Routes
610
+ <span class="nav-badge">${state.routes.length}</span>
611
+ </div>
612
+ <div class="nav-item">
613
+ <span class="nav-icon">\u{1F4DD}</span>
614
+ Logs
615
+ <span class="nav-badge">${state.requestLogs.length}</span>
616
+ </div>
617
+ <div class="nav-item">
618
+ <span class="nav-icon">\u2699\uFE0F</span>
619
+ Settings
620
+ </div>
621
+ </nav>
622
+
623
+ <div class="sidebar-footer">
624
+ <a href="https://floatjs.dev/docs" target="_blank" class="docs-link">
625
+ <span>\u{1F4DA}</span>
626
+ Documentation
627
+ </a>
628
+ </div>
629
+ </aside>
630
+
631
+ <!-- Main Content -->
632
+ <main class="main">
633
+ <div class="page-header">
634
+ <h1 class="page-title">Dev Dashboard</h1>
635
+ <p class="page-subtitle">Monitor your Float.js application in real-time</p>
636
+ </div>
637
+
638
+ <!-- Metrics -->
639
+ <div class="metrics-grid">
640
+ <div class="metric-card">
641
+ <div class="metric-icon">\u{1F4E1}</div>
642
+ <div class="metric-value">${metrics.requests}</div>
643
+ <div class="metric-label">Total Requests</div>
644
+ </div>
645
+ <div class="metric-card">
646
+ <div class="metric-icon">\u26A1</div>
647
+ <div class="metric-value">${metrics.avgResponseTime}ms</div>
648
+ <div class="metric-label">Avg Response</div>
649
+ </div>
650
+ <div class="metric-card">
651
+ <div class="metric-icon">\u{1F4BE}</div>
652
+ <div class="metric-value">${memoryMB}/${memoryTotal}</div>
653
+ <div class="metric-label">Memory (MB)</div>
654
+ </div>
655
+ <div class="metric-card">
656
+ <div class="metric-icon">${metrics.errorRate > 0 ? "\u26A0\uFE0F" : "\u2705"}</div>
657
+ <div class="metric-value">${metrics.errorRate}%</div>
658
+ <div class="metric-label">Error Rate</div>
659
+ </div>
660
+ </div>
661
+
662
+ <!-- Content Grid -->
663
+ <div class="content-grid">
664
+ <!-- Routes -->
665
+ <div class="section">
666
+ <div class="section-header">
667
+ <span class="section-title">\u{1F6E4}\uFE0F Routes</span>
668
+ <span class="section-badge">${state.routes.length} registered</span>
669
+ </div>
670
+ <div class="section-content">
671
+ ${state.routes.length === 0 ? `
672
+ <div class="empty-state">
673
+ <div class="empty-icon">\u{1F6E4}\uFE0F</div>
674
+ <p>No routes registered</p>
675
+ </div>
676
+ ` : state.routes.map((route) => `
677
+ <div class="route-item">
678
+ <span class="route-type ${route.type}">${route.type}</span>
679
+ <span class="route-path">${route.path}</span>
680
+ <span class="route-file">${route.file.split("/").pop()}</span>
681
+ </div>
682
+ `).join("")}
683
+ </div>
684
+ </div>
685
+
686
+ <!-- Request Logs -->
687
+ <div class="section">
688
+ <div class="section-header">
689
+ <span class="section-title">\u{1F4DD} Request Logs</span>
690
+ <span class="section-badge">Live</span>
691
+ </div>
692
+ <div class="section-content">
693
+ ${state.requestLogs.length === 0 ? `
694
+ <div class="empty-state">
695
+ <div class="empty-icon">\u{1F4DD}</div>
696
+ <p>No requests yet</p>
697
+ </div>
698
+ ` : state.requestLogs.slice(0, 20).map((log) => {
699
+ const statusClass = log.status < 300 ? "s2xx" : log.status < 400 ? "s3xx" : log.status < 500 ? "s4xx" : "s5xx";
700
+ return `
701
+ <div class="log-item">
702
+ <span class="log-method ${log.method}">${log.method}</span>
703
+ <span class="log-status ${statusClass}">${log.status}</span>
704
+ <span class="log-path">${log.path}</span>
705
+ <span class="log-duration">${log.duration}ms</span>
706
+ </div>
707
+ `;
708
+ }).join("")}
709
+ </div>
710
+ </div>
711
+ </div>
712
+ </main>
713
+
714
+ <script>
715
+ // Auto-refresh every 3 seconds
716
+ setTimeout(() => location.reload(), 3000);
717
+ </script>
718
+ </body>
719
+ </html>`;
720
+ }
721
+ function generateAPIResponse(state) {
722
+ return JSON.stringify({
723
+ metrics: state.getMetrics(),
724
+ routes: state.routes,
725
+ builds: state.builds.slice(0, 10),
726
+ requestLogs: state.requestLogs.slice(0, 50)
727
+ });
728
+ }
729
+ function createDevDashboard(options = {}) {
730
+ const {
731
+ enabled = process.env.NODE_ENV !== "production",
732
+ path: path6 = "/__float",
733
+ auth
734
+ } = options;
735
+ if (!enabled) {
736
+ return (_req, _res, next) => next();
737
+ }
738
+ return (req, res, next) => {
739
+ const url = req.url || "";
740
+ if (!url.startsWith(path6)) {
741
+ return next();
742
+ }
743
+ if (auth) {
744
+ const authHeader = req.headers.authorization;
745
+ if (!authHeader || !authHeader.startsWith("Basic ")) {
746
+ res.setHeader("WWW-Authenticate", 'Basic realm="Float.js Dev Dashboard"');
747
+ res.statusCode = 401;
748
+ res.end("Unauthorized");
749
+ return;
750
+ }
751
+ const credentials = Buffer.from(authHeader.slice(6), "base64").toString();
752
+ const [username, password] = credentials.split(":");
753
+ if (username !== auth.username || password !== auth.password) {
754
+ res.statusCode = 401;
755
+ res.end("Invalid credentials");
756
+ return;
757
+ }
758
+ }
759
+ const subPath = url.slice(path6.length);
760
+ if (subPath === "" || subPath === "/") {
761
+ res.setHeader("Content-Type", "text/html");
762
+ res.end(generateDashboardHTML(dashboardState));
763
+ return;
764
+ }
765
+ if (subPath === "/api" || subPath === "/api/") {
766
+ res.setHeader("Content-Type", "application/json");
767
+ res.end(generateAPIResponse(dashboardState));
768
+ return;
769
+ }
770
+ if (subPath === "/api/routes") {
771
+ res.setHeader("Content-Type", "application/json");
772
+ res.end(JSON.stringify(dashboardState.routes));
773
+ return;
774
+ }
775
+ if (subPath === "/api/metrics") {
776
+ res.setHeader("Content-Type", "application/json");
777
+ res.end(JSON.stringify(dashboardState.getMetrics()));
778
+ return;
779
+ }
780
+ if (subPath === "/api/logs") {
781
+ res.setHeader("Content-Type", "application/json");
782
+ res.end(JSON.stringify(dashboardState.requestLogs.slice(0, 100)));
783
+ return;
784
+ }
785
+ if (subPath === "/api/clear" && req.method === "POST") {
786
+ dashboardState.clear();
787
+ res.setHeader("Content-Type", "application/json");
788
+ res.end(JSON.stringify({ success: true }));
789
+ return;
790
+ }
791
+ res.statusCode = 404;
792
+ res.end("Not found");
793
+ };
794
+ }
795
+ var DevDashboardState, dashboardState, devtools;
796
+ var init_devtools = __esm({
797
+ "src/devtools/index.ts"() {
798
+ "use strict";
799
+ init_esm_shims();
800
+ DevDashboardState = class {
801
+ routes = [];
802
+ builds = [];
803
+ requestLogs = [];
804
+ startTime = /* @__PURE__ */ new Date();
805
+ maxLogs = 100;
806
+ requestCount = 0;
807
+ totalResponseTime = 0;
808
+ errorCount = 0;
809
+ addRoute(route) {
810
+ const existing = this.routes.findIndex((r) => r.path === route.path);
811
+ if (existing >= 0) {
812
+ this.routes[existing] = route;
813
+ } else {
814
+ this.routes.push(route);
815
+ }
816
+ }
817
+ addBuild(build) {
818
+ this.builds.unshift(build);
819
+ if (this.builds.length > 20) {
820
+ this.builds.pop();
821
+ }
822
+ }
823
+ logRequest(log) {
824
+ this.requestLogs.unshift(log);
825
+ if (this.requestLogs.length > this.maxLogs) {
826
+ this.requestLogs.pop();
827
+ }
828
+ this.requestCount++;
829
+ this.totalResponseTime += log.duration;
830
+ if (log.status >= 400) {
831
+ this.errorCount++;
832
+ }
833
+ }
834
+ getMetrics() {
835
+ return {
836
+ requests: this.requestCount,
837
+ avgResponseTime: this.requestCount > 0 ? Math.round(this.totalResponseTime / this.requestCount) : 0,
838
+ errorRate: this.requestCount > 0 ? Math.round(this.errorCount / this.requestCount * 100) : 0,
839
+ activeConnections: 0,
840
+ // Updated by server
841
+ memoryUsage: process.memoryUsage(),
842
+ uptime: Date.now() - this.startTime.getTime()
843
+ };
844
+ }
845
+ clear() {
846
+ this.requestLogs = [];
847
+ this.requestCount = 0;
848
+ this.totalResponseTime = 0;
849
+ this.errorCount = 0;
850
+ }
851
+ };
852
+ dashboardState = new DevDashboardState();
853
+ devtools = {
854
+ dashboard: createDevDashboard,
855
+ logger: createRequestLogger,
856
+ state: dashboardState,
857
+ // Helpers for manual logging
858
+ logRoute: (route) => dashboardState.addRoute(route),
859
+ logBuild: (build) => dashboardState.addBuild(build),
860
+ logRequest: (log) => dashboardState.logRequest(log),
861
+ getMetrics: () => dashboardState.getMetrics(),
862
+ clear: () => dashboardState.clear()
863
+ };
864
+ }
865
+ });
866
+
867
+ // src/server/index.ts
868
+ init_esm_shims();
869
+
870
+ // src/server/dev-server.ts
871
+ init_esm_shims();
872
+ import http from "http";
873
+ import fs2 from "fs";
874
+ import path4 from "path";
875
+ import pc from "picocolors";
876
+ import chokidar from "chokidar";
877
+ import { WebSocketServer, WebSocket } from "ws";
878
+ import mime from "mime-types";
879
+
880
+ // src/router/index.ts
881
+ init_esm_shims();
882
+ import fg from "fast-glob";
883
+ import path2 from "path";
884
+ var DEFAULT_OPTIONS = {
885
+ appDir: "app",
886
+ basePath: "",
887
+ extensions: [".tsx", ".ts", ".jsx", ".js"]
888
+ };
889
+ function filePathToUrlPath(filePath, appDir) {
890
+ let urlPath = filePath.replace(new RegExp(`^${appDir}/`), "").replace(/\/?(page|layout|route|error|loading|not-found)\.(tsx?|jsx?)$/, "");
891
+ const params = [];
892
+ let isCatchAll = false;
893
+ let isOptionalCatchAll = false;
894
+ urlPath = urlPath.replace(/\[([^\]]+)\]/g, (_, param) => {
895
+ if (param.startsWith("...") && filePath.includes("[[")) {
896
+ isOptionalCatchAll = true;
897
+ const paramName = param.replace("...", "");
898
+ params.push(paramName);
899
+ return `*${paramName}?`;
900
+ }
901
+ if (param.startsWith("...")) {
902
+ isCatchAll = true;
903
+ const paramName = param.replace("...", "");
904
+ params.push(paramName);
905
+ return `*${paramName}`;
906
+ }
907
+ params.push(param);
908
+ return `:${param}`;
909
+ });
910
+ urlPath = "/" + urlPath;
911
+ urlPath = urlPath.replace(/\/+/g, "/").replace(/\/$/, "") || "/";
912
+ return { urlPath, params, isCatchAll, isOptionalCatchAll };
913
+ }
914
+ function getRouteType(filePath) {
915
+ const fileName = filePath.split("/").pop() || filePath;
916
+ if (fileName.match(/^route\.(tsx?|jsx?)$/)) return "api";
917
+ if (fileName.match(/^layout\.(tsx?|jsx?)$/)) return "layout";
918
+ if (fileName.match(/^error\.(tsx?|jsx?)$/)) return "error";
919
+ if (fileName.match(/^loading\.(tsx?|jsx?)$/)) return "loading";
920
+ return "page";
921
+ }
922
+ function findLayouts(routePath, allLayouts) {
923
+ const layouts = [];
924
+ const segments = routePath.split("/").filter(Boolean);
925
+ let currentPath = "";
926
+ if (allLayouts.has("/")) {
927
+ layouts.push(allLayouts.get("/"));
928
+ }
929
+ for (const segment of segments) {
930
+ currentPath += "/" + segment;
931
+ if (allLayouts.has(currentPath)) {
932
+ layouts.push(allLayouts.get(currentPath));
933
+ }
934
+ }
935
+ return layouts;
936
+ }
937
+ async function scanRoutes(rootDir, options = {}) {
938
+ const opts = { ...DEFAULT_OPTIONS, ...options };
939
+ const appDir = path2.join(rootDir, opts.appDir);
940
+ const extensions = opts.extensions.map((ext) => ext.replace(".", "")).join(",");
941
+ const pattern = `**/{page,layout,route,error,loading}.{${extensions}}`;
942
+ const files = await fg(pattern, {
943
+ cwd: appDir,
944
+ onlyFiles: true,
945
+ ignore: ["**/node_modules/**", "**/_*/**"]
946
+ });
947
+ const layoutMap = /* @__PURE__ */ new Map();
948
+ for (const file of files) {
949
+ const type = getRouteType(file);
950
+ if (type === "layout") {
951
+ const { urlPath } = filePathToUrlPath(file, "");
952
+ const layoutPath = urlPath === "/" ? "/" : urlPath.replace(/\/layout$/, "") || "/";
953
+ layoutMap.set(layoutPath, path2.join(appDir, file));
954
+ }
955
+ }
956
+ const routes = [];
957
+ for (const file of files) {
958
+ const type = getRouteType(file);
959
+ if (type === "layout" || type === "error" || type === "loading") {
960
+ continue;
961
+ }
962
+ const { urlPath, params, isCatchAll, isOptionalCatchAll } = filePathToUrlPath(file, "");
963
+ const route = {
964
+ path: opts.basePath + urlPath,
965
+ filePath: file,
966
+ absolutePath: path2.join(appDir, file),
967
+ type,
968
+ params,
969
+ isCatchAll,
970
+ isOptionalCatchAll,
971
+ layouts: type === "page" ? findLayouts(urlPath, layoutMap) : []
972
+ };
973
+ routes.push(route);
974
+ }
975
+ routes.sort((a, b) => {
976
+ if (a.isCatchAll !== b.isCatchAll) return a.isCatchAll ? 1 : -1;
977
+ if (a.params.length !== b.params.length) return a.params.length - b.params.length;
978
+ return a.path.localeCompare(b.path);
979
+ });
980
+ return routes;
981
+ }
982
+ function matchRoute(url, routes) {
983
+ const urlParts = url.split("/").filter(Boolean);
984
+ for (const route of routes) {
985
+ if (route.type !== "page" && route.type !== "api") continue;
986
+ const routeParts = route.path.split("/").filter(Boolean);
987
+ const params = {};
988
+ let matched = true;
989
+ let urlIndex = 0;
990
+ for (let i = 0; i < routeParts.length; i++) {
991
+ const routePart = routeParts[i];
992
+ if (routePart.startsWith("*")) {
993
+ const paramName = routePart.replace(/^\*/, "").replace(/\?$/, "");
994
+ params[paramName] = urlParts.slice(urlIndex).join("/");
995
+ return { route, params };
996
+ }
997
+ if (routePart.startsWith(":")) {
998
+ const paramName = routePart.slice(1);
999
+ if (urlIndex >= urlParts.length) {
1000
+ matched = false;
1001
+ break;
1002
+ }
1003
+ params[paramName] = urlParts[urlIndex];
1004
+ urlIndex++;
1005
+ continue;
1006
+ }
1007
+ if (urlParts[urlIndex] !== routePart) {
1008
+ matched = false;
1009
+ break;
1010
+ }
1011
+ urlIndex++;
1012
+ }
1013
+ if (matched && urlIndex === urlParts.length) {
1014
+ return { route, params };
1015
+ }
1016
+ }
1017
+ return { route: null, params: {} };
1018
+ }
1019
+
1020
+ // src/server/ssr.ts
1021
+ init_esm_shims();
1022
+ import React from "react";
1023
+ import { renderToPipeableStream, renderToString } from "react-dom/server";
1024
+ import { Writable } from "stream";
1025
+
1026
+ // src/build/transform.ts
1027
+ init_esm_shims();
1028
+ import * as esbuild from "esbuild";
1029
+ import fs from "fs";
1030
+ import path3 from "path";
1031
+ import { pathToFileURL } from "url";
1032
+ var moduleCache = /* @__PURE__ */ new Map();
1033
+ async function transformFile(filePath) {
1034
+ const absolutePath = path3.isAbsolute(filePath) ? filePath : path3.resolve(filePath);
1035
+ if (!fs.existsSync(absolutePath)) {
1036
+ throw new Error(`File not found: ${absolutePath}`);
1037
+ }
1038
+ const stats = fs.statSync(absolutePath);
1039
+ const mtime = stats.mtimeMs;
1040
+ const cached = moduleCache.get(absolutePath);
1041
+ if (cached && cached.mtime === mtime) {
1042
+ return cached.module;
1043
+ }
1044
+ const source = fs.readFileSync(absolutePath, "utf-8");
1045
+ const ext = path3.extname(absolutePath);
1046
+ const loader = getLoader(ext);
1047
+ const result = await esbuild.transform(source, {
1048
+ loader,
1049
+ jsx: "automatic",
1050
+ format: "esm",
1051
+ target: "node18",
1052
+ sourcemap: "inline",
1053
+ sourcefile: absolutePath
1054
+ });
1055
+ const tempDir = path3.join(process.cwd(), ".float", ".cache");
1056
+ fs.mkdirSync(tempDir, { recursive: true });
1057
+ const tempFile = path3.join(tempDir, `${path3.basename(absolutePath, ext)}_${Date.now()}.mjs`);
1058
+ let code = result.code;
1059
+ code = rewriteImports(code, path3.dirname(absolutePath));
1060
+ fs.writeFileSync(tempFile, code);
1061
+ try {
1062
+ const module = await import(pathToFileURL(tempFile).href);
1063
+ moduleCache.set(absolutePath, { module, mtime });
1064
+ setImmediate(() => {
1065
+ try {
1066
+ fs.unlinkSync(tempFile);
1067
+ } catch {
1068
+ }
1069
+ });
1070
+ return module;
1071
+ } catch (error) {
1072
+ try {
1073
+ fs.unlinkSync(tempFile);
1074
+ } catch {
1075
+ }
1076
+ throw error;
1077
+ }
1078
+ }
1079
+ function getLoader(ext) {
1080
+ switch (ext) {
1081
+ case ".ts":
1082
+ return "ts";
1083
+ case ".tsx":
1084
+ return "tsx";
1085
+ case ".jsx":
1086
+ return "jsx";
1087
+ case ".js":
1088
+ return "js";
1089
+ case ".mjs":
1090
+ return "js";
1091
+ case ".json":
1092
+ return "json";
1093
+ case ".css":
1094
+ return "css";
1095
+ default:
1096
+ return "ts";
1097
+ }
1098
+ }
1099
+ function rewriteImports(code, baseDir) {
1100
+ const importRegex = /from\s+['"](\.[^'"]+)['"]/g;
1101
+ return code.replace(importRegex, (match, importPath) => {
1102
+ let resolvedPath = path3.resolve(baseDir, importPath);
1103
+ const extensions = [".tsx", ".ts", ".jsx", ".js", ".mjs", ""];
1104
+ let found = false;
1105
+ for (const ext of extensions) {
1106
+ const tryPath = resolvedPath + ext;
1107
+ if (fs.existsSync(tryPath)) {
1108
+ resolvedPath = tryPath;
1109
+ found = true;
1110
+ break;
1111
+ }
1112
+ const indexPath = path3.join(resolvedPath, `index${ext}`);
1113
+ if (fs.existsSync(indexPath)) {
1114
+ resolvedPath = indexPath;
1115
+ found = true;
1116
+ break;
1117
+ }
1118
+ }
1119
+ if (!found) {
1120
+ return match;
1121
+ }
1122
+ return `from '${pathToFileURL(resolvedPath).href}'`;
1123
+ });
1124
+ }
1125
+
1126
+ // src/server/ssr.ts
1127
+ async function renderPage(route, params, options = {}) {
1128
+ const { hmrScript = "", isDev = false, streaming = false } = options;
1129
+ void streaming;
1130
+ try {
1131
+ const pageModule = await transformFile(route.absolutePath);
1132
+ const PageComponent = pageModule.default;
1133
+ if (!PageComponent) {
1134
+ throw new Error(`No default export found in ${route.filePath}`);
1135
+ }
1136
+ const layouts = await Promise.all(
1137
+ route.layouts.map(async (layoutPath) => {
1138
+ const layoutModule = await transformFile(layoutPath);
1139
+ return layoutModule.default;
1140
+ })
1141
+ );
1142
+ const metadata = pageModule.metadata || {};
1143
+ const generateMetadata = pageModule.generateMetadata;
1144
+ let pageMetadata = metadata;
1145
+ if (generateMetadata) {
1146
+ pageMetadata = await generateMetadata({ params });
1147
+ }
1148
+ const props = {
1149
+ params,
1150
+ searchParams: {}
1151
+ };
1152
+ let element = React.createElement(PageComponent, props);
1153
+ for (let i = layouts.length - 1; i >= 0; i--) {
1154
+ const Layout = layouts[i];
1155
+ if (Layout) {
1156
+ element = React.createElement(Layout, { children: element });
1157
+ }
1158
+ }
1159
+ const content = renderToString(element);
1160
+ const html = generateHtmlDocument({
1161
+ content,
1162
+ metadata: pageMetadata,
1163
+ hmrScript: isDev ? hmrScript : "",
1164
+ isDev
1165
+ });
1166
+ return html;
1167
+ } catch (error) {
1168
+ console.error("SSR Error:", error);
1169
+ throw error;
1170
+ }
1171
+ }
1172
+ async function renderPageStream(route, params, _options = {}) {
1173
+ const pageModule = await transformFile(route.absolutePath);
1174
+ const PageComponent = pageModule.default;
1175
+ const props = { params, searchParams: {} };
1176
+ const element = React.createElement(PageComponent, props);
1177
+ return new Promise((resolve, reject) => {
1178
+ let html = "";
1179
+ const writable = new Writable({
1180
+ write(chunk, _encoding, callback) {
1181
+ html += chunk.toString();
1182
+ callback();
1183
+ },
1184
+ final(callback) {
1185
+ callback();
1186
+ }
1187
+ });
1188
+ const { pipe, abort } = renderToPipeableStream(element, {
1189
+ onShellReady() {
1190
+ pipe(writable);
1191
+ },
1192
+ onShellError(error) {
1193
+ reject(error);
1194
+ },
1195
+ onAllReady() {
1196
+ resolve(writable);
1197
+ },
1198
+ onError(error) {
1199
+ console.error("Streaming error:", error);
1200
+ }
1201
+ });
1202
+ setTimeout(() => abort(), 1e4);
1203
+ });
1204
+ }
1205
+ function generateHtmlDocument(options) {
1206
+ const { content, metadata, hmrScript, isDev, styles = "", scripts = [] } = options;
1207
+ let title = "Float.js App";
1208
+ if (metadata.title) {
1209
+ if (typeof metadata.title === "string") {
1210
+ title = metadata.title;
1211
+ } else if (typeof metadata.title === "object" && metadata.title.default) {
1212
+ title = metadata.title.default;
1213
+ }
1214
+ }
1215
+ const description = metadata.description || "";
1216
+ const charset = metadata.charset || "utf-8";
1217
+ const viewport = metadata.viewport || "width=device-width, initial-scale=1";
1218
+ const metaTags = generateMetaTags(metadata);
1219
+ return `<!DOCTYPE html>
1220
+ <html lang="${metadata.lang || "en"}">
1221
+ <head>
1222
+ <meta charset="${charset}">
1223
+ <meta name="viewport" content="${viewport}">
1224
+ <title>${escapeHtml(title)}</title>
1225
+ ${description ? `<meta name="description" content="${escapeHtml(description)}">` : ""}
1226
+ ${metaTags}
1227
+ <meta name="generator" content="Float.js">
1228
+ <style>
1229
+ /* Float.js Base Styles */
1230
+ *, *::before, *::after { box-sizing: border-box; }
1231
+ html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
1232
+ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
1233
+ ${styles}
1234
+ </style>
1235
+ ${isDev ? `
1236
+ <style>
1237
+ /* Dev mode indicator */
1238
+ body::after {
1239
+ content: 'DEV';
1240
+ position: fixed;
1241
+ bottom: 8px;
1242
+ right: 8px;
1243
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1244
+ color: white;
1245
+ font-size: 10px;
1246
+ font-weight: bold;
1247
+ padding: 4px 8px;
1248
+ border-radius: 4px;
1249
+ z-index: 99999;
1250
+ font-family: monospace;
1251
+ }
1252
+ </style>
1253
+ ` : ""}
1254
+ </head>
1255
+ <body>
1256
+ <div id="__float">${content}</div>
1257
+ ${hmrScript}
1258
+ ${scripts.map((src) => `<script src="${src}"></script>`).join("\n ")}
1259
+ </body>
1260
+ </html>`;
1261
+ }
1262
+ function generateMetaTags(metadata) {
1263
+ const tags = [];
1264
+ if (metadata.openGraph) {
1265
+ const og = metadata.openGraph;
1266
+ if (og.title) tags.push(`<meta property="og:title" content="${escapeHtml(og.title)}">`);
1267
+ if (og.description) tags.push(`<meta property="og:description" content="${escapeHtml(og.description)}">`);
1268
+ if (og.image) tags.push(`<meta property="og:image" content="${escapeHtml(og.image)}">`);
1269
+ if (og.url) tags.push(`<meta property="og:url" content="${escapeHtml(og.url)}">`);
1270
+ if (og.type) tags.push(`<meta property="og:type" content="${escapeHtml(og.type)}">`);
1271
+ }
1272
+ if (metadata.twitter) {
1273
+ const tw = metadata.twitter;
1274
+ if (tw.card) tags.push(`<meta name="twitter:card" content="${escapeHtml(tw.card)}">`);
1275
+ if (tw.title) tags.push(`<meta name="twitter:title" content="${escapeHtml(tw.title)}">`);
1276
+ if (tw.description) tags.push(`<meta name="twitter:description" content="${escapeHtml(tw.description)}">`);
1277
+ if (tw.image) tags.push(`<meta name="twitter:image" content="${escapeHtml(tw.image)}">`);
1278
+ }
1279
+ if (metadata.robots) {
1280
+ const robots = typeof metadata.robots === "string" ? metadata.robots : Object.entries(metadata.robots).map(([k, v]) => v ? k : `no${k}`).join(", ");
1281
+ tags.push(`<meta name="robots" content="${escapeHtml(robots)}">`);
1282
+ }
1283
+ if (metadata.icons) {
1284
+ const icons = metadata.icons;
1285
+ if (icons.icon) tags.push(`<link rel="icon" href="${escapeHtml(icons.icon)}">`);
1286
+ if (icons.apple) tags.push(`<link rel="apple-touch-icon" href="${escapeHtml(icons.apple)}">`);
1287
+ }
1288
+ if (metadata.canonical) {
1289
+ tags.push(`<link rel="canonical" href="${escapeHtml(metadata.canonical)}">`);
1290
+ }
1291
+ return tags.join("\n ");
1292
+ }
1293
+ function escapeHtml(text) {
1294
+ return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1295
+ }
1296
+
1297
+ // src/client/float-indicator.ts
1298
+ init_esm_shims();
1299
+ var FLOAT_INDICATOR_SCRIPT = `
1300
+ <script id="__float-indicator">
1301
+ (function() {
1302
+ var isConnected = false;
1303
+ var isOpen = false;
1304
+ var buildTime = null;
1305
+ var startTime = Date.now();
1306
+
1307
+ var indicator = document.createElement('div');
1308
+ indicator.id = '__float-dev-indicator';
1309
+ indicator.innerHTML = '<div class="float-btn" id="float-btn"><svg width="16" height="16" viewBox="0 0 200 200" fill="none"><path d="M50 145C50 136.716 56.7157 130 65 130H105C113.284 130 120 136.716 120 145C120 153.284 113.284 160 105 160H65C56.7157 160 50 153.284 50 145Z" fill="#3B82F6"/><path d="M50 100C50 91.7157 56.7157 85 65 85H135C143.284 85 150 91.7157 150 100C150 108.284 143.284 115 135 115H65C56.7157 115 50 108.284 50 100Z" fill="#6366F1"/><path d="M50 55C50 46.7157 56.7157 40 65 40H155C163.284 40 170 46.7157 170 55C170 63.2843 163.284 70 155 70H65C56.7157 70 50 63.2843 50 55Z" fill="#8B5CF6"/></svg><span class="float-dot" id="float-dot"></span></div><div class="float-panel" id="float-panel"><div class="float-panel-header"><svg width="20" height="20" viewBox="0 0 200 200" fill="none"><path d="M50 145C50 136.716 56.7157 130 65 130H105C113.284 130 120 136.716 120 145C120 153.284 113.284 160 105 160H65C56.7157 160 50 153.284 50 145Z" fill="#3B82F6"/><path d="M50 100C50 91.7157 56.7157 85 65 85H135C143.284 85 150 91.7157 150 100C150 108.284 143.284 115 135 115H65C56.7157 115 50 108.284 50 100Z" fill="#6366F1"/><path d="M50 55C50 46.7157 56.7157 40 65 40H155C163.284 40 170 46.7157 170 55C170 63.2843 163.284 70 155 70H65C56.7157 70 50 63.2843 50 55Z" fill="#8B5CF6"/></svg><span>Float.js</span><span class="float-version">v2.0.1</span></div><div class="float-panel-body"><div class="float-row"><span class="float-label">Estado</span><span class="float-value" id="float-state">Conectando...</span></div><div class="float-row"><span class="float-label">HMR</span><span class="float-value" id="float-hmr">\u2014</span></div><div class="float-row"><span class="float-label">Build</span><span class="float-value" id="float-build">\u2014</span></div></div><div class="float-panel-footer"><a href="/__float" class="float-link">Dashboard</a><a href="https://floatjs.dev/docs" target="_blank" class="float-link">Docs</a></div></div>';
1310
+
1311
+ var styles = document.createElement('style');
1312
+ styles.textContent = '#__float-dev-indicator{position:fixed;bottom:16px;left:16px;z-index:99999;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:13px}.float-btn{display:flex;align-items:center;gap:6px;padding:8px 10px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 1px 2px rgba(0,0,0,0.05);cursor:pointer;transition:all 0.15s}.float-btn:hover{border-color:#d1d5db;box-shadow:0 2px 8px rgba(0,0,0,0.08)}.float-dot{width:8px;height:8px;border-radius:50%;background:#d1d5db;transition:background 0.2s}.float-dot.connected{background:#22c55e}.float-dot.error{background:#ef4444}.float-dot.building{background:#eab308;animation:blink 0.8s infinite}@keyframes blink{0%,100%{opacity:1}50%{opacity:0.4}}.float-panel{position:absolute;bottom:48px;left:0;width:240px;background:#fff;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 4px 16px rgba(0,0,0,0.1);opacity:0;visibility:hidden;transform:translateY(8px);transition:all 0.2s ease}.float-panel.open{opacity:1;visibility:visible;transform:translateY(0)}.float-panel-header{display:flex;align-items:center;gap:8px;padding:12px 14px;border-bottom:1px solid #f3f4f6;font-weight:600;color:#111827}.float-version{margin-left:auto;font-size:11px;font-weight:400;color:#9ca3af}.float-panel-body{padding:8px 0}.float-row{display:flex;justify-content:space-between;padding:8px 14px}.float-label{color:#6b7280}.float-value{color:#111827;font-weight:500}.float-value.success{color:#16a34a}.float-value.error{color:#dc2626}.float-panel-footer{display:flex;gap:8px;padding:12px 14px;border-top:1px solid #f3f4f6}.float-link{flex:1;text-align:center;padding:8px;background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;color:#374151;text-decoration:none;font-size:12px;font-weight:500;transition:all 0.15s}.float-link:hover{background:#f3f4f6;border-color:#d1d5db}';
1313
+
1314
+ document.head.appendChild(styles);
1315
+ document.body.appendChild(indicator);
1316
+
1317
+ var btn = document.getElementById('float-btn');
1318
+ var panel = document.getElementById('float-panel');
1319
+ var dot = document.getElementById('float-dot');
1320
+ var stateEl = document.getElementById('float-state');
1321
+ var hmrEl = document.getElementById('float-hmr');
1322
+ var buildEl = document.getElementById('float-build');
1323
+
1324
+ btn.addEventListener('click', function(e) {
1325
+ e.stopPropagation();
1326
+ isOpen = !isOpen;
1327
+ panel.classList.toggle('open', isOpen);
1328
+ });
1329
+
1330
+ document.addEventListener('click', function(e) {
1331
+ if (!indicator.contains(e.target) && isOpen) {
1332
+ isOpen = false;
1333
+ panel.classList.remove('open');
1334
+ }
1335
+ });
1336
+
1337
+ function updateStatus(state) {
1338
+ dot.className = 'float-dot ' + state;
1339
+ if (state === 'connected') {
1340
+ stateEl.textContent = 'Conectado';
1341
+ stateEl.className = 'float-value success';
1342
+ hmrEl.textContent = 'Activo';
1343
+ hmrEl.className = 'float-value success';
1344
+ } else if (state === 'building') {
1345
+ stateEl.textContent = 'Compilando...';
1346
+ stateEl.className = 'float-value';
1347
+ } else if (state === 'error') {
1348
+ stateEl.textContent = 'Error';
1349
+ stateEl.className = 'float-value error';
1350
+ } else {
1351
+ stateEl.textContent = 'Desconectado';
1352
+ stateEl.className = 'float-value';
1353
+ hmrEl.textContent = 'Inactivo';
1354
+ hmrEl.className = 'float-value';
1355
+ }
1356
+ }
1357
+
1358
+ var wsUrl = 'ws://' + window.location.hostname + ':' + (parseInt(window.location.port) + 1);
1359
+ var ws;
1360
+ var reconnectAttempts = 0;
1361
+
1362
+ function connect() {
1363
+ ws = new WebSocket(wsUrl);
1364
+
1365
+ ws.onopen = function() {
1366
+ isConnected = true;
1367
+ reconnectAttempts = 0;
1368
+ buildTime = new Date();
1369
+ updateStatus('connected');
1370
+ buildEl.textContent = 'Ahora';
1371
+ };
1372
+
1373
+ ws.onmessage = function(event) {
1374
+ try {
1375
+ var data = JSON.parse(event.data);
1376
+ if (data.type === 'reload') window.location.reload();
1377
+ if (data.type === 'building') updateStatus('building');
1378
+ if (data.type === 'update' || data.type === 'clear-errors') {
1379
+ buildTime = new Date();
1380
+ buildEl.textContent = 'Ahora';
1381
+ updateStatus('connected');
1382
+ }
1383
+ if (data.type === 'error') updateStatus('error');
1384
+ } catch (e) {}
1385
+ };
1386
+
1387
+ ws.onclose = function() {
1388
+ isConnected = false;
1389
+ updateStatus('');
1390
+ var delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 10000);
1391
+ reconnectAttempts++;
1392
+ setTimeout(connect, delay);
1393
+ };
1394
+
1395
+ ws.onerror = function() { ws.close(); };
1396
+ }
1397
+
1398
+ connect();
1399
+
1400
+ setInterval(function() {
1401
+ if (buildTime && isConnected) {
1402
+ var sec = Math.floor((Date.now() - buildTime) / 1000);
1403
+ if (sec < 5) buildEl.textContent = 'Ahora';
1404
+ else if (sec < 60) buildEl.textContent = sec + 's';
1405
+ else buildEl.textContent = Math.floor(sec / 60) + 'm';
1406
+ }
1407
+ }, 1000);
1408
+ })();
1409
+ </script>
1410
+ `;
1411
+
1412
+ // src/client/error-overlay.ts
1413
+ init_esm_shims();
1414
+ var FLOAT_ERROR_OVERLAY = `
1415
+ <script id="__float-error-overlay">
1416
+ (function() {
1417
+ window.__FLOAT_SHOW_ERROR = function(error) {
1418
+ // Remove existing overlay
1419
+ const existing = document.getElementById('__float-error-overlay');
1420
+ if (existing) existing.remove();
1421
+
1422
+ const overlay = document.createElement('div');
1423
+ overlay.id = '__float-error-overlay';
1424
+ overlay.innerHTML = \`
1425
+ <div class="feo-backdrop"></div>
1426
+ <div class="feo-container">
1427
+ <div class="feo-header">
1428
+ <div class="feo-icon">
1429
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1430
+ <circle cx="12" cy="12" r="10"/>
1431
+ <line x1="12" y1="8" x2="12" y2="12"/>
1432
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
1433
+ </svg>
1434
+ </div>
1435
+ <div class="feo-title-group">
1436
+ <h1 class="feo-title">\${error.type || 'Error'}</h1>
1437
+ <p class="feo-subtitle">\${error.file || ''}</p>
1438
+ </div>
1439
+ <button class="feo-close" onclick="this.closest('#__float-error-overlay').remove()">
1440
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1441
+ <line x1="18" y1="6" x2="6" y2="18"/>
1442
+ <line x1="6" y1="6" x2="18" y2="18"/>
1443
+ </svg>
1444
+ </button>
1445
+ </div>
1446
+ <div class="feo-content">
1447
+ <div class="feo-message">\${error.message}</div>
1448
+ \${error.stack ? \`<pre class="feo-stack">\${error.stack}</pre>\` : ''}
1449
+ \${error.frame ? \`
1450
+ <div class="feo-frame">
1451
+ <div class="feo-frame-header">
1452
+ <span class="feo-frame-file">\${error.file || 'source'}</span>
1453
+ \${error.line ? \`<span class="feo-frame-line">:\${error.line}\${error.column ? ':' + error.column : ''}</span>\` : ''}
1454
+ </div>
1455
+ <pre class="feo-frame-code">\${error.frame}</pre>
1456
+ </div>
1457
+ \` : ''}
1458
+ </div>
1459
+ <div class="feo-footer">
1460
+ <div class="feo-hint">
1461
+ <span class="feo-hint-icon">\u{1F4A1}</span>
1462
+ <span>\${error.hint || 'Revisa el c\xF3digo y guarda para recargar autom\xE1ticamente'}</span>
1463
+ </div>
1464
+ <div class="feo-actions">
1465
+ <button class="feo-btn feo-btn-secondary" onclick="navigator.clipboard.writeText(document.querySelector('.feo-stack')?.textContent || '')">
1466
+ Copiar error
1467
+ </button>
1468
+ <button class="feo-btn feo-btn-primary" onclick="window.location.reload()">
1469
+ Reintentar
1470
+ </button>
1471
+ </div>
1472
+ </div>
1473
+ </div>
1474
+ \`;
1475
+
1476
+ const styles = document.createElement('style');
1477
+ styles.textContent = \`
1478
+ #__float-error-overlay {
1479
+ position: fixed;
1480
+ inset: 0;
1481
+ z-index: 999999;
1482
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1483
+ display: flex;
1484
+ align-items: center;
1485
+ justify-content: center;
1486
+ padding: 20px;
1487
+ }
1488
+
1489
+ .feo-backdrop {
1490
+ position: absolute;
1491
+ inset: 0;
1492
+ background: rgba(0, 0, 0, 0.85);
1493
+ backdrop-filter: blur(8px);
1494
+ }
1495
+
1496
+ .feo-container {
1497
+ position: relative;
1498
+ width: 100%;
1499
+ max-width: 800px;
1500
+ max-height: 90vh;
1501
+ background: linear-gradient(145deg, #1a1a1a 0%, #0d0d0d 100%);
1502
+ border-radius: 20px;
1503
+ border: 1px solid rgba(239, 68, 68, 0.3);
1504
+ box-shadow:
1505
+ 0 0 0 1px rgba(239, 68, 68, 0.1),
1506
+ 0 20px 60px rgba(239, 68, 68, 0.2),
1507
+ 0 0 100px rgba(239, 68, 68, 0.1);
1508
+ overflow: hidden;
1509
+ display: flex;
1510
+ flex-direction: column;
1511
+ }
1512
+
1513
+ .feo-header {
1514
+ padding: 24px;
1515
+ display: flex;
1516
+ align-items: flex-start;
1517
+ gap: 16px;
1518
+ background: linear-gradient(135deg, rgba(239, 68, 68, 0.15) 0%, transparent 100%);
1519
+ border-bottom: 1px solid rgba(255,255,255,0.05);
1520
+ }
1521
+
1522
+ .feo-icon {
1523
+ width: 48px;
1524
+ height: 48px;
1525
+ background: rgba(239, 68, 68, 0.2);
1526
+ border-radius: 12px;
1527
+ display: flex;
1528
+ align-items: center;
1529
+ justify-content: center;
1530
+ flex-shrink: 0;
1531
+ }
1532
+
1533
+ .feo-icon svg {
1534
+ width: 28px;
1535
+ height: 28px;
1536
+ color: #ef4444;
1537
+ }
1538
+
1539
+ .feo-title-group {
1540
+ flex: 1;
1541
+ min-width: 0;
1542
+ }
1543
+
1544
+ .feo-title {
1545
+ margin: 0;
1546
+ font-size: 20px;
1547
+ font-weight: 600;
1548
+ color: #ef4444;
1549
+ }
1550
+
1551
+ .feo-subtitle {
1552
+ margin: 4px 0 0;
1553
+ font-size: 13px;
1554
+ color: rgba(255,255,255,0.5);
1555
+ white-space: nowrap;
1556
+ overflow: hidden;
1557
+ text-overflow: ellipsis;
1558
+ }
1559
+
1560
+ .feo-close {
1561
+ width: 36px;
1562
+ height: 36px;
1563
+ background: rgba(255,255,255,0.05);
1564
+ border: none;
1565
+ border-radius: 10px;
1566
+ cursor: pointer;
1567
+ display: flex;
1568
+ align-items: center;
1569
+ justify-content: center;
1570
+ transition: background 0.2s;
1571
+ }
1572
+
1573
+ .feo-close:hover {
1574
+ background: rgba(255,255,255,0.1);
1575
+ }
1576
+
1577
+ .feo-close svg {
1578
+ width: 18px;
1579
+ height: 18px;
1580
+ color: rgba(255,255,255,0.5);
1581
+ }
1582
+
1583
+ .feo-content {
1584
+ padding: 24px;
1585
+ overflow-y: auto;
1586
+ flex: 1;
1587
+ }
1588
+
1589
+ .feo-message {
1590
+ font-size: 16px;
1591
+ color: #fca5a5;
1592
+ line-height: 1.6;
1593
+ margin-bottom: 20px;
1594
+ }
1595
+
1596
+ .feo-stack {
1597
+ background: rgba(0,0,0,0.4);
1598
+ border-radius: 12px;
1599
+ padding: 16px;
1600
+ margin: 0 0 20px;
1601
+ font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
1602
+ font-size: 12px;
1603
+ color: rgba(255,255,255,0.7);
1604
+ line-height: 1.8;
1605
+ overflow-x: auto;
1606
+ white-space: pre-wrap;
1607
+ word-break: break-all;
1608
+ }
1609
+
1610
+ .feo-frame {
1611
+ background: rgba(0,0,0,0.3);
1612
+ border-radius: 12px;
1613
+ overflow: hidden;
1614
+ border: 1px solid rgba(255,255,255,0.05);
1615
+ }
1616
+
1617
+ .feo-frame-header {
1618
+ padding: 12px 16px;
1619
+ background: rgba(255,255,255,0.03);
1620
+ border-bottom: 1px solid rgba(255,255,255,0.05);
1621
+ font-size: 12px;
1622
+ }
1623
+
1624
+ .feo-frame-file {
1625
+ color: #f59e0b;
1626
+ }
1627
+
1628
+ .feo-frame-line {
1629
+ color: rgba(255,255,255,0.4);
1630
+ }
1631
+
1632
+ .feo-frame-code {
1633
+ margin: 0;
1634
+ padding: 16px;
1635
+ font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
1636
+ font-size: 13px;
1637
+ color: rgba(255,255,255,0.8);
1638
+ line-height: 1.6;
1639
+ overflow-x: auto;
1640
+ }
1641
+
1642
+ .feo-footer {
1643
+ padding: 20px 24px;
1644
+ background: rgba(0,0,0,0.3);
1645
+ border-top: 1px solid rgba(255,255,255,0.05);
1646
+ display: flex;
1647
+ align-items: center;
1648
+ justify-content: space-between;
1649
+ gap: 16px;
1650
+ flex-wrap: wrap;
1651
+ }
1652
+
1653
+ .feo-hint {
1654
+ display: flex;
1655
+ align-items: center;
1656
+ gap: 8px;
1657
+ font-size: 13px;
1658
+ color: rgba(255,255,255,0.5);
1659
+ }
1660
+
1661
+ .feo-hint-icon {
1662
+ font-size: 16px;
1663
+ }
1664
+
1665
+ .feo-actions {
1666
+ display: flex;
1667
+ gap: 10px;
1668
+ }
1669
+
1670
+ .feo-btn {
1671
+ padding: 10px 20px;
1672
+ border-radius: 10px;
1673
+ font-size: 13px;
1674
+ font-weight: 500;
1675
+ cursor: pointer;
1676
+ transition: all 0.2s;
1677
+ border: none;
1678
+ }
1679
+
1680
+ .feo-btn-secondary {
1681
+ background: rgba(255,255,255,0.1);
1682
+ color: white;
1683
+ }
1684
+
1685
+ .feo-btn-secondary:hover {
1686
+ background: rgba(255,255,255,0.15);
1687
+ }
1688
+
1689
+ .feo-btn-primary {
1690
+ background: linear-gradient(135deg, #6366f1, #8b5cf6);
1691
+ color: white;
1692
+ }
1693
+
1694
+ .feo-btn-primary:hover {
1695
+ transform: translateY(-1px);
1696
+ box-shadow: 0 4px 20px rgba(139, 92, 246, 0.4);
1697
+ }
1698
+ \`;
1699
+
1700
+ overlay.appendChild(styles);
1701
+ document.body.appendChild(overlay);
1702
+ };
1703
+
1704
+ window.__FLOAT_HIDE_ERROR = function() {
1705
+ const overlay = document.getElementById('__float-error-overlay');
1706
+ if (overlay) overlay.remove();
1707
+ };
1708
+
1709
+ // Global error handler
1710
+ window.addEventListener('error', (event) => {
1711
+ window.__FLOAT_SHOW_ERROR({
1712
+ type: 'Runtime Error',
1713
+ message: event.message,
1714
+ file: event.filename,
1715
+ line: event.lineno,
1716
+ column: event.colno,
1717
+ stack: event.error?.stack
1718
+ });
1719
+ });
1720
+
1721
+ window.addEventListener('unhandledrejection', (event) => {
1722
+ window.__FLOAT_SHOW_ERROR({
1723
+ type: 'Unhandled Promise Rejection',
1724
+ message: String(event.reason),
1725
+ stack: event.reason?.stack
1726
+ });
1727
+ });
1728
+ })();
1729
+ </script>
1730
+ `;
1731
+
1732
+ // src/server/dev-server.ts
1733
+ async function createDevServer(options) {
1734
+ const { port, host, open } = options;
1735
+ const rootDir = process.cwd();
1736
+ const publicDir = path4.join(rootDir, "public");
1737
+ let routes = [];
1738
+ let server = null;
1739
+ let wss = null;
1740
+ const clients = /* @__PURE__ */ new Set();
1741
+ async function refreshRoutes() {
1742
+ try {
1743
+ routes = await scanRoutes(rootDir);
1744
+ console.log(pc.dim(` \u{1F4C1} Found ${routes.length} routes`));
1745
+ } catch (error) {
1746
+ console.error(pc.red("Failed to scan routes:"), error);
1747
+ }
1748
+ }
1749
+ function notifyClients(type, data) {
1750
+ const message = JSON.stringify({ type, data, error: data?.error, timestamp: Date.now() });
1751
+ clients.forEach((client) => {
1752
+ if (client.readyState === WebSocket.OPEN) {
1753
+ client.send(message);
1754
+ }
1755
+ });
1756
+ }
1757
+ const hmrClientScript = `
1758
+ <script>
1759
+ (function() {
1760
+ const ws = new WebSocket('ws://${host}:${port + 1}');
1761
+
1762
+ ws.onmessage = function(event) {
1763
+ const data = JSON.parse(event.data);
1764
+
1765
+ // Update Float indicator if available
1766
+ if (window.__FLOAT_UPDATE_STATUS) {
1767
+ window.__FLOAT_UPDATE_STATUS(data);
1768
+ }
1769
+
1770
+ if (data.type === 'reload') {
1771
+ console.log('[Float HMR] Reloading...');
1772
+ window.location.reload();
1773
+ }
1774
+ if (data.type === 'update') {
1775
+ console.log('[Float HMR] Update received:', data.data);
1776
+ }
1777
+ if (data.type === 'error' && window.__FLOAT_SHOW_ERROR) {
1778
+ window.__FLOAT_SHOW_ERROR(data.error);
1779
+ }
1780
+ if (data.type === 'clear-errors' && window.__FLOAT_CLEAR_ERRORS) {
1781
+ window.__FLOAT_CLEAR_ERRORS();
1782
+ }
1783
+ };
1784
+
1785
+ ws.onopen = function() {
1786
+ console.log('[Float HMR] Connected');
1787
+ if (window.__FLOAT_SET_CONNECTED) {
1788
+ window.__FLOAT_SET_CONNECTED(true);
1789
+ }
1790
+ };
1791
+
1792
+ ws.onclose = function() {
1793
+ console.log('[Float HMR] Disconnected. Attempting reconnect...');
1794
+ if (window.__FLOAT_SET_CONNECTED) {
1795
+ window.__FLOAT_SET_CONNECTED(false);
1796
+ }
1797
+ setTimeout(() => window.location.reload(), 1000);
1798
+ };
1799
+ })();
1800
+ </script>
1801
+ ${FLOAT_INDICATOR_SCRIPT}
1802
+ ${FLOAT_ERROR_OVERLAY}
1803
+ `;
1804
+ async function handleRequest(req, res) {
1805
+ const url = new URL(req.url || "/", `http://${host}:${port}`);
1806
+ const pathname = url.pathname;
1807
+ console.log(pc.dim(` ${req.method} ${pathname}`));
1808
+ try {
1809
+ const publicFilePath = path4.join(publicDir, pathname);
1810
+ if (fs2.existsSync(publicFilePath) && fs2.statSync(publicFilePath).isFile()) {
1811
+ const content = fs2.readFileSync(publicFilePath);
1812
+ const contentType = mime.lookup(publicFilePath) || "application/octet-stream";
1813
+ res.writeHead(200, { "Content-Type": contentType });
1814
+ res.end(content);
1815
+ return;
1816
+ }
1817
+ if (pathname.startsWith("/_float/")) {
1818
+ res.writeHead(200, { "Content-Type": "application/javascript" });
1819
+ res.end("// Float.js internal asset");
1820
+ return;
1821
+ }
1822
+ if (pathname === "/__float" || pathname.startsWith("/__float/")) {
1823
+ const { dashboardState: dashboardState2 } = await Promise.resolve().then(() => (init_devtools(), devtools_exports));
1824
+ dashboardState2.routes = routes.map((r) => ({
1825
+ path: r.path,
1826
+ type: r.type,
1827
+ file: r.filePath || r.absolutePath,
1828
+ hasLayout: r.layouts && r.layouts.length > 0
1829
+ }));
1830
+ const { createDevDashboard: createDevDashboard2 } = await Promise.resolve().then(() => (init_devtools(), devtools_exports));
1831
+ const handler = createDevDashboard2({ path: "/__float" });
1832
+ handler(req, res, () => {
1833
+ res.writeHead(404);
1834
+ res.end("Not Found");
1835
+ });
1836
+ return;
1837
+ }
1838
+ const { route, params } = matchRoute(pathname, routes);
1839
+ if (!route) {
1840
+ res.writeHead(404, { "Content-Type": "text/html" });
1841
+ res.end(create404Page(pathname));
1842
+ return;
1843
+ }
1844
+ if (route.type === "api") {
1845
+ await handleApiRoute(req, res, route, params);
1846
+ return;
1847
+ }
1848
+ const html = await renderPage(route, params, {
1849
+ hmrScript: hmrClientScript,
1850
+ isDev: true
1851
+ });
1852
+ res.writeHead(200, {
1853
+ "Content-Type": "text/html; charset=utf-8",
1854
+ "Cache-Control": "no-cache"
1855
+ });
1856
+ res.end(html);
1857
+ } catch (error) {
1858
+ console.error(pc.red("Request error:"), error);
1859
+ res.writeHead(500, { "Content-Type": "text/html" });
1860
+ res.end(createErrorPage(error));
1861
+ }
1862
+ }
1863
+ async function handleApiRoute(req, res, route, params) {
1864
+ try {
1865
+ const module = await transformFile(route.absolutePath);
1866
+ const method = req.method?.toUpperCase() || "GET";
1867
+ const url = new URL(req.url || "/", `http://${host}:${port}`);
1868
+ const body = await getRequestBody(req);
1869
+ const request = new Request(url.toString(), {
1870
+ method,
1871
+ headers: Object.fromEntries(
1872
+ Object.entries(req.headers).filter(([_, v]) => v !== void 0)
1873
+ ),
1874
+ body: method !== "GET" && method !== "HEAD" ? body : void 0
1875
+ });
1876
+ const handler = module[method] || module.default;
1877
+ if (!handler) {
1878
+ res.writeHead(405, { "Content-Type": "application/json" });
1879
+ res.end(JSON.stringify({ error: "Method not allowed" }));
1880
+ return;
1881
+ }
1882
+ const response = await handler(request, { params });
1883
+ res.writeHead(response.status, Object.fromEntries(response.headers));
1884
+ const responseBody = await response.text();
1885
+ res.end(responseBody);
1886
+ } catch (error) {
1887
+ console.error(pc.red("API route error:"), error);
1888
+ res.writeHead(500, { "Content-Type": "application/json" });
1889
+ res.end(JSON.stringify({ error: "Internal server error" }));
1890
+ }
1891
+ }
1892
+ async function start() {
1893
+ await refreshRoutes();
1894
+ server = http.createServer(handleRequest);
1895
+ wss = new WebSocketServer({ port: port + 1 });
1896
+ wss.on("connection", (ws) => {
1897
+ clients.add(ws);
1898
+ ws.on("close", () => clients.delete(ws));
1899
+ });
1900
+ const watcher = chokidar.watch(
1901
+ [
1902
+ path4.join(rootDir, "app/**/*.{ts,tsx,js,jsx}"),
1903
+ path4.join(rootDir, "components/**/*.{ts,tsx,js,jsx}"),
1904
+ path4.join(rootDir, "lib/**/*.{ts,tsx,js,jsx}")
1905
+ ],
1906
+ {
1907
+ ignored: /node_modules/,
1908
+ persistent: true
1909
+ }
1910
+ );
1911
+ watcher.on("change", async (filePath) => {
1912
+ console.log(pc.yellow(`
1913
+ \u26A1 File changed: ${path4.relative(rootDir, filePath)}`));
1914
+ if (filePath.includes("/app/")) {
1915
+ await refreshRoutes();
1916
+ }
1917
+ notifyClients("reload");
1918
+ });
1919
+ watcher.on("add", async (filePath) => {
1920
+ if (filePath.includes("/app/")) {
1921
+ console.log(pc.green(`
1922
+ \u2795 File added: ${path4.relative(rootDir, filePath)}`));
1923
+ await refreshRoutes();
1924
+ notifyClients("reload");
1925
+ }
1926
+ });
1927
+ watcher.on("unlink", async (filePath) => {
1928
+ if (filePath.includes("/app/")) {
1929
+ console.log(pc.red(`
1930
+ \u2796 File removed: ${path4.relative(rootDir, filePath)}`));
1931
+ await refreshRoutes();
1932
+ notifyClients("reload");
1933
+ }
1934
+ });
1935
+ return new Promise((resolve, reject) => {
1936
+ server.listen(port, host, () => {
1937
+ console.log(pc.green(` \u2705 Server running at ${pc.cyan(`http://${host}:${port}`)}`));
1938
+ console.log(pc.dim(` \u26A1 HMR enabled on ws://${host}:${port + 1}
1939
+ `));
1940
+ console.log(pc.bold(" Routes:"));
1941
+ routes.forEach((route) => {
1942
+ if (route.type === "page") {
1943
+ console.log(pc.dim(` ${pc.green("\u25CF")} ${route.path}`));
1944
+ } else if (route.type === "api") {
1945
+ console.log(pc.dim(` ${pc.blue("\u25C6")} ${route.path} (API)`));
1946
+ }
1947
+ });
1948
+ console.log("");
1949
+ if (open) {
1950
+ import("child_process").then(({ exec }) => {
1951
+ exec(`open http://${host}:${port}`);
1952
+ });
1953
+ }
1954
+ resolve();
1955
+ });
1956
+ server.on("error", reject);
1957
+ });
1958
+ }
1959
+ async function stop() {
1960
+ return new Promise((resolve) => {
1961
+ wss?.close();
1962
+ server?.close(() => resolve());
1963
+ });
1964
+ }
1965
+ async function restart() {
1966
+ await stop();
1967
+ await start();
1968
+ }
1969
+ return { start, stop, restart };
1970
+ }
1971
+ function getRequestBody(req) {
1972
+ return new Promise((resolve, reject) => {
1973
+ let body = "";
1974
+ req.on("data", (chunk) => body += chunk);
1975
+ req.on("end", () => resolve(body));
1976
+ req.on("error", reject);
1977
+ });
1978
+ }
1979
+ function create404Page(pathname) {
1980
+ return `
1981
+ <!DOCTYPE html>
1982
+ <html lang="en">
1983
+ <head>
1984
+ <meta charset="UTF-8">
1985
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1986
+ <title>404 - Not Found | Float.js</title>
1987
+ <style>
1988
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1989
+ body {
1990
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1991
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
1992
+ color: white;
1993
+ min-height: 100vh;
1994
+ display: flex;
1995
+ align-items: center;
1996
+ justify-content: center;
1997
+ }
1998
+ .container { text-align: center; padding: 2rem; }
1999
+ h1 { font-size: 8rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2000
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
2001
+ h2 { font-size: 1.5rem; margin: 1rem 0; opacity: 0.8; }
2002
+ code { background: rgba(255,255,255,0.1); padding: 0.5rem 1rem; border-radius: 0.5rem; }
2003
+ .tip { margin-top: 2rem; opacity: 0.6; font-size: 0.9rem; }
2004
+ </style>
2005
+ </head>
2006
+ <body>
2007
+ <div class="container">
2008
+ <h1>404</h1>
2009
+ <h2>Page Not Found</h2>
2010
+ <code>${pathname}</code>
2011
+ <p class="tip">Create <code>app${pathname === "/" ? "" : pathname}/page.tsx</code> to add this route</p>
2012
+ </div>
2013
+ </body>
2014
+ </html>`;
2015
+ }
2016
+ function createErrorPage(error) {
2017
+ return `
2018
+ <!DOCTYPE html>
2019
+ <html lang="en">
2020
+ <head>
2021
+ <meta charset="UTF-8">
2022
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2023
+ <title>Error | Float.js</title>
2024
+ <style>
2025
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2026
+ body {
2027
+ font-family: 'Monaco', 'Menlo', monospace;
2028
+ background: #1a1a1a;
2029
+ color: #ff6b6b;
2030
+ min-height: 100vh;
2031
+ padding: 2rem;
2032
+ }
2033
+ .header { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
2034
+ .icon { font-size: 2rem; }
2035
+ h1 { font-size: 1.5rem; }
2036
+ .message { color: #ffa94d; font-size: 1.25rem; margin-bottom: 1rem; }
2037
+ .stack {
2038
+ background: #2d2d2d;
2039
+ padding: 1rem;
2040
+ border-radius: 0.5rem;
2041
+ overflow-x: auto;
2042
+ color: #888;
2043
+ font-size: 0.875rem;
2044
+ line-height: 1.6;
2045
+ }
2046
+ </style>
2047
+ </head>
2048
+ <body>
2049
+ <div class="header">
2050
+ <span class="icon">\u26A0\uFE0F</span>
2051
+ <h1>Server Error</h1>
2052
+ </div>
2053
+ <p class="message">${escapeHtml2(error.message)}</p>
2054
+ <pre class="stack">${escapeHtml2(error.stack || "")}</pre>
2055
+ </body>
2056
+ </html>`;
2057
+ }
2058
+ function escapeHtml2(text) {
2059
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
2060
+ }
2061
+
2062
+ // src/server/prod-server.ts
2063
+ init_esm_shims();
2064
+ import http2 from "http";
2065
+ import fs3 from "fs";
2066
+ import path5 from "path";
2067
+ import pc2 from "picocolors";
2068
+ import mime2 from "mime-types";
2069
+ var cachedRoutes = [];
2070
+ var pageCache = /* @__PURE__ */ new Map();
2071
+ async function startProductionServer(options) {
2072
+ const { port, host } = options;
2073
+ const rootDir = process.cwd();
2074
+ const distDir = path5.join(rootDir, ".float");
2075
+ const publicDir = path5.join(rootDir, "public");
2076
+ const manifestPath = path5.join(distDir, "routes-manifest.json");
2077
+ if (fs3.existsSync(manifestPath)) {
2078
+ const manifest = JSON.parse(fs3.readFileSync(manifestPath, "utf-8"));
2079
+ cachedRoutes = manifest.routes;
2080
+ console.log(pc2.dim(` \u{1F4E6} Loaded ${cachedRoutes.length} routes from manifest`));
2081
+ } else {
2082
+ console.error(pc2.red(" \u274C No build manifest found. Run `float build` first."));
2083
+ process.exit(1);
2084
+ }
2085
+ const pagesDir = path5.join(distDir, "pages");
2086
+ if (fs3.existsSync(pagesDir)) {
2087
+ const prerenderedFiles = fs3.readdirSync(pagesDir, { recursive: true });
2088
+ for (const file of prerenderedFiles) {
2089
+ if (file.endsWith(".html")) {
2090
+ const routePath = "/" + file.replace(/\.html$/, "").replace(/index$/, "");
2091
+ const content = fs3.readFileSync(path5.join(pagesDir, file), "utf-8");
2092
+ pageCache.set(routePath, content);
2093
+ }
2094
+ }
2095
+ console.log(pc2.dim(` \u{1F4C4} Loaded ${pageCache.size} pre-rendered pages`));
2096
+ }
2097
+ const server = http2.createServer(async (req, res) => {
2098
+ const url = new URL(req.url || "/", `http://${host}:${port}`);
2099
+ const pathname = url.pathname;
2100
+ try {
2101
+ const staticPath = path5.join(distDir, "static", pathname);
2102
+ if (fs3.existsSync(staticPath) && fs3.statSync(staticPath).isFile()) {
2103
+ const content = fs3.readFileSync(staticPath);
2104
+ const contentType = mime2.lookup(staticPath) || "application/octet-stream";
2105
+ res.writeHead(200, {
2106
+ "Content-Type": contentType,
2107
+ "Cache-Control": "public, max-age=31536000, immutable"
2108
+ });
2109
+ res.end(content);
2110
+ return;
2111
+ }
2112
+ const publicFilePath = path5.join(publicDir, pathname);
2113
+ if (fs3.existsSync(publicFilePath) && fs3.statSync(publicFilePath).isFile()) {
2114
+ const content = fs3.readFileSync(publicFilePath);
2115
+ const contentType = mime2.lookup(publicFilePath) || "application/octet-stream";
2116
+ res.writeHead(200, { "Content-Type": contentType });
2117
+ res.end(content);
2118
+ return;
2119
+ }
2120
+ const cachedPage = pageCache.get(pathname) || pageCache.get(pathname + "/");
2121
+ if (cachedPage) {
2122
+ res.writeHead(200, {
2123
+ "Content-Type": "text/html; charset=utf-8",
2124
+ "Cache-Control": "public, s-maxage=3600, stale-while-revalidate"
2125
+ });
2126
+ res.end(cachedPage);
2127
+ return;
2128
+ }
2129
+ const { route, params } = matchRoute(pathname, cachedRoutes);
2130
+ if (!route) {
2131
+ res.writeHead(404, { "Content-Type": "text/html" });
2132
+ res.end("<h1>404 - Not Found</h1>");
2133
+ return;
2134
+ }
2135
+ if (route.type === "api") {
2136
+ res.writeHead(200, { "Content-Type": "application/json" });
2137
+ res.end(JSON.stringify({ message: "API route" }));
2138
+ return;
2139
+ }
2140
+ const html = await renderPage(route, params, { isDev: false });
2141
+ res.writeHead(200, {
2142
+ "Content-Type": "text/html; charset=utf-8",
2143
+ "Cache-Control": "public, s-maxage=60, stale-while-revalidate=30"
2144
+ });
2145
+ res.end(html);
2146
+ } catch (error) {
2147
+ console.error(pc2.red("Request error:"), error);
2148
+ res.writeHead(500, { "Content-Type": "text/html" });
2149
+ res.end("<h1>500 - Internal Server Error</h1>");
2150
+ }
2151
+ });
2152
+ server.listen(port, host, () => {
2153
+ console.log(pc2.green(` \u2705 Production server running at ${pc2.cyan(`http://${host}:${port}`)}
2154
+ `));
2155
+ });
2156
+ }
2157
+ export {
2158
+ createDevServer,
2159
+ renderPage,
2160
+ renderPageStream,
2161
+ startProductionServer
2162
+ };
2163
+ //# sourceMappingURL=index.js.map