@buenojs/bueno 0.8.0

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.
Files changed (120) hide show
  1. package/.env.example +109 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +892 -0
  5. package/architecture.md +652 -0
  6. package/bun.lock +70 -0
  7. package/dist/cli/index.js +3233 -0
  8. package/dist/index.js +9014 -0
  9. package/package.json +77 -0
  10. package/src/cache/index.ts +795 -0
  11. package/src/cli/ARCHITECTURE.md +837 -0
  12. package/src/cli/bin.ts +10 -0
  13. package/src/cli/commands/build.ts +425 -0
  14. package/src/cli/commands/dev.ts +248 -0
  15. package/src/cli/commands/generate.ts +541 -0
  16. package/src/cli/commands/help.ts +55 -0
  17. package/src/cli/commands/index.ts +112 -0
  18. package/src/cli/commands/migration.ts +355 -0
  19. package/src/cli/commands/new.ts +804 -0
  20. package/src/cli/commands/start.ts +208 -0
  21. package/src/cli/core/args.ts +283 -0
  22. package/src/cli/core/console.ts +349 -0
  23. package/src/cli/core/index.ts +60 -0
  24. package/src/cli/core/prompt.ts +424 -0
  25. package/src/cli/core/spinner.ts +265 -0
  26. package/src/cli/index.ts +135 -0
  27. package/src/cli/templates/deploy.ts +295 -0
  28. package/src/cli/templates/docker.ts +307 -0
  29. package/src/cli/templates/index.ts +24 -0
  30. package/src/cli/utils/fs.ts +428 -0
  31. package/src/cli/utils/index.ts +8 -0
  32. package/src/cli/utils/strings.ts +197 -0
  33. package/src/config/env.ts +408 -0
  34. package/src/config/index.ts +506 -0
  35. package/src/config/loader.ts +329 -0
  36. package/src/config/merge.ts +285 -0
  37. package/src/config/types.ts +320 -0
  38. package/src/config/validation.ts +441 -0
  39. package/src/container/forward-ref.ts +143 -0
  40. package/src/container/index.ts +386 -0
  41. package/src/context/index.ts +360 -0
  42. package/src/database/index.ts +1142 -0
  43. package/src/database/migrations/index.ts +371 -0
  44. package/src/database/schema/index.ts +619 -0
  45. package/src/frontend/api-routes.ts +640 -0
  46. package/src/frontend/bundler.ts +643 -0
  47. package/src/frontend/console-client.ts +419 -0
  48. package/src/frontend/console-stream.ts +587 -0
  49. package/src/frontend/dev-server.ts +846 -0
  50. package/src/frontend/file-router.ts +611 -0
  51. package/src/frontend/frameworks/index.ts +106 -0
  52. package/src/frontend/frameworks/react.ts +85 -0
  53. package/src/frontend/frameworks/solid.ts +104 -0
  54. package/src/frontend/frameworks/svelte.ts +110 -0
  55. package/src/frontend/frameworks/vue.ts +92 -0
  56. package/src/frontend/hmr-client.ts +663 -0
  57. package/src/frontend/hmr.ts +728 -0
  58. package/src/frontend/index.ts +342 -0
  59. package/src/frontend/islands.ts +552 -0
  60. package/src/frontend/isr.ts +555 -0
  61. package/src/frontend/layout.ts +475 -0
  62. package/src/frontend/ssr/react.ts +446 -0
  63. package/src/frontend/ssr/solid.ts +523 -0
  64. package/src/frontend/ssr/svelte.ts +546 -0
  65. package/src/frontend/ssr/vue.ts +504 -0
  66. package/src/frontend/ssr.ts +699 -0
  67. package/src/frontend/types.ts +2274 -0
  68. package/src/health/index.ts +604 -0
  69. package/src/index.ts +410 -0
  70. package/src/lock/index.ts +587 -0
  71. package/src/logger/index.ts +444 -0
  72. package/src/logger/transports/index.ts +969 -0
  73. package/src/metrics/index.ts +494 -0
  74. package/src/middleware/built-in.ts +360 -0
  75. package/src/middleware/index.ts +94 -0
  76. package/src/modules/filters.ts +458 -0
  77. package/src/modules/guards.ts +405 -0
  78. package/src/modules/index.ts +1256 -0
  79. package/src/modules/interceptors.ts +574 -0
  80. package/src/modules/lazy.ts +418 -0
  81. package/src/modules/lifecycle.ts +478 -0
  82. package/src/modules/metadata.ts +90 -0
  83. package/src/modules/pipes.ts +626 -0
  84. package/src/router/index.ts +339 -0
  85. package/src/router/linear.ts +371 -0
  86. package/src/router/regex.ts +292 -0
  87. package/src/router/tree.ts +562 -0
  88. package/src/rpc/index.ts +1263 -0
  89. package/src/security/index.ts +436 -0
  90. package/src/ssg/index.ts +631 -0
  91. package/src/storage/index.ts +456 -0
  92. package/src/telemetry/index.ts +1097 -0
  93. package/src/testing/index.ts +1586 -0
  94. package/src/types/index.ts +236 -0
  95. package/src/types/optional-deps.d.ts +219 -0
  96. package/src/validation/index.ts +276 -0
  97. package/src/websocket/index.ts +1004 -0
  98. package/tests/integration/cli.test.ts +1016 -0
  99. package/tests/integration/fullstack.test.ts +234 -0
  100. package/tests/unit/cache.test.ts +174 -0
  101. package/tests/unit/cli-commands.test.ts +892 -0
  102. package/tests/unit/cli.test.ts +1258 -0
  103. package/tests/unit/container.test.ts +279 -0
  104. package/tests/unit/context.test.ts +221 -0
  105. package/tests/unit/database.test.ts +183 -0
  106. package/tests/unit/linear-router.test.ts +280 -0
  107. package/tests/unit/lock.test.ts +336 -0
  108. package/tests/unit/middleware.test.ts +184 -0
  109. package/tests/unit/modules.test.ts +142 -0
  110. package/tests/unit/pubsub.test.ts +257 -0
  111. package/tests/unit/regex-router.test.ts +265 -0
  112. package/tests/unit/router.test.ts +373 -0
  113. package/tests/unit/rpc.test.ts +1248 -0
  114. package/tests/unit/security.test.ts +174 -0
  115. package/tests/unit/telemetry.test.ts +371 -0
  116. package/tests/unit/test-cache.test.ts +110 -0
  117. package/tests/unit/test-database.test.ts +282 -0
  118. package/tests/unit/tree-router.test.ts +325 -0
  119. package/tests/unit/validation.test.ts +794 -0
  120. package/tsconfig.json +27 -0
@@ -0,0 +1,663 @@
1
+ /**
2
+ * HMR Client Script
3
+ *
4
+ * This script is injected into HTML pages to enable Hot Module Replacement.
5
+ * It handles WebSocket communication, module updates, and framework-specific HMR.
6
+ *
7
+ * The script is kept minimal (< 5KB) for fast injection.
8
+ *
9
+ * @module frontend/hmr-client
10
+ */
11
+
12
+ // Client script as a string for injection into HTML
13
+ export const HMR_CLIENT_SCRIPT = `
14
+ (function() {
15
+ 'use strict';
16
+
17
+ // ============= Configuration =============
18
+ const HMR_CONFIG = {
19
+ reconnectInterval: 1000,
20
+ maxReconnectAttempts: 10,
21
+ heartbeatInterval: 30000,
22
+ };
23
+
24
+ // ============= State =============
25
+ let ws = null;
26
+ let clientId = null;
27
+ let reconnectAttempts = 0;
28
+ let reconnectTimer = null;
29
+ let heartbeatTimer = null;
30
+ let subscribedFiles = new Set();
31
+ let moduleCache = new Map();
32
+ let isConnecting = false;
33
+
34
+ // ============= Framework Detection =============
35
+ const framework = detectFramework();
36
+
37
+ function detectFramework() {
38
+ if (typeof window === 'undefined') return 'unknown';
39
+
40
+ // Check for React
41
+ if (window.React || document.querySelector('[data-reactroot]') ||
42
+ document.querySelector('[data-reactid]')) {
43
+ return 'react';
44
+ }
45
+
46
+ // Check for Vue
47
+ if (window.Vue || document.querySelector('[data-v-]') ||
48
+ document.querySelector('[data-vue-app]')) {
49
+ return 'vue';
50
+ }
51
+
52
+ // Check for Svelte
53
+ if (window.__SVELTE_HMR__ || document.querySelector('[data-svelte]')) {
54
+ return 'svelte';
55
+ }
56
+
57
+ // Check for Solid
58
+ if (window.Solid$$ || document.querySelector('[data-solid]')) {
59
+ return 'solid';
60
+ }
61
+
62
+ return 'unknown';
63
+ }
64
+
65
+ // ============= WebSocket Connection =============
66
+ function connect() {
67
+ if (isConnecting || (ws && ws.readyState === WebSocket.OPEN)) {
68
+ return;
69
+ }
70
+
71
+ isConnecting = true;
72
+
73
+ // Build WebSocket URL
74
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
75
+ const host = window.location.hostname;
76
+ const port = getHMRPort();
77
+ const url = protocol + '//' + host + ':' + port + '/_hmr';
78
+
79
+ try {
80
+ ws = new WebSocket(url);
81
+
82
+ ws.onopen = handleOpen;
83
+ ws.onclose = handleClose;
84
+ ws.onerror = handleError;
85
+ ws.onmessage = handleMessage;
86
+ } catch (e) {
87
+ console.error('[HMR] Failed to create WebSocket:', e);
88
+ isConnecting = false;
89
+ scheduleReconnect();
90
+ }
91
+ }
92
+
93
+ function getHMRPort() {
94
+ // Try to get port from script tag or default to dev server port + 1
95
+ const scripts = document.querySelectorAll('script[data-hmr-port]');
96
+ if (scripts.length > 0) {
97
+ return parseInt(scripts[0].getAttribute('data-hmr-port'), 10);
98
+ }
99
+ return parseInt(window.location.port || '3000', 10) + 1;
100
+ }
101
+
102
+ function handleOpen() {
103
+ isConnecting = false;
104
+ reconnectAttempts = 0;
105
+ console.log('[HMR] Connected');
106
+
107
+ // Start heartbeat
108
+ startHeartbeat();
109
+
110
+ // Re-subscribe to files
111
+ subscribedFiles.forEach(function(fileId) {
112
+ sendMessage({ type: 'subscribe', fileId: fileId });
113
+ });
114
+ }
115
+
116
+ function handleClose(event) {
117
+ isConnecting = false;
118
+ stopHeartbeat();
119
+
120
+ if (event.code !== 1000) {
121
+ console.log('[HMR] Connection closed, attempting to reconnect...');
122
+ scheduleReconnect();
123
+ }
124
+ }
125
+
126
+ function handleError(error) {
127
+ isConnecting = false;
128
+ console.error('[HMR] WebSocket error:', error);
129
+ }
130
+
131
+ function scheduleReconnect() {
132
+ if (reconnectTimer) {
133
+ clearTimeout(reconnectTimer);
134
+ }
135
+
136
+ if (reconnectAttempts >= HMR_CONFIG.maxReconnectAttempts) {
137
+ console.error('[HMR] Max reconnect attempts reached. Please refresh the page.');
138
+ showOverlay({
139
+ message: 'HMR connection lost. Please refresh the page.',
140
+ type: 'error'
141
+ });
142
+ return;
143
+ }
144
+
145
+ reconnectAttempts++;
146
+ var delay = HMR_CONFIG.reconnectInterval * reconnectAttempts;
147
+
148
+ reconnectTimer = setTimeout(function() {
149
+ console.log('[HMR] Reconnecting... (attempt ' + reconnectAttempts + ')');
150
+ connect();
151
+ }, delay);
152
+ }
153
+
154
+ // ============= Heartbeat =============
155
+ function startHeartbeat() {
156
+ stopHeartbeat();
157
+ heartbeatTimer = setInterval(function() {
158
+ if (ws && ws.readyState === WebSocket.OPEN) {
159
+ sendMessage({ type: 'ping' });
160
+ }
161
+ }, HMR_CONFIG.heartbeatInterval);
162
+ }
163
+
164
+ function stopHeartbeat() {
165
+ if (heartbeatTimer) {
166
+ clearInterval(heartbeatTimer);
167
+ heartbeatTimer = null;
168
+ }
169
+ }
170
+
171
+ // ============= Message Handling =============
172
+ function sendMessage(message) {
173
+ if (ws && ws.readyState === WebSocket.OPEN) {
174
+ ws.send(JSON.stringify(message));
175
+ }
176
+ }
177
+
178
+ function handleMessage(event) {
179
+ try {
180
+ var message = JSON.parse(event.data);
181
+
182
+ switch (message.type) {
183
+ case 'connected':
184
+ clientId = message.clientId;
185
+ console.log('[HMR] Client ID:', clientId);
186
+ break;
187
+
188
+ case 'pong':
189
+ // Heartbeat response
190
+ break;
191
+
192
+ case 'update':
193
+ handleUpdate(message);
194
+ break;
195
+
196
+ case 'reload':
197
+ handleReload(message);
198
+ break;
199
+
200
+ case 'error':
201
+ handleError(message);
202
+ break;
203
+ }
204
+ } catch (e) {
205
+ console.error('[HMR] Failed to parse message:', e);
206
+ }
207
+ }
208
+
209
+ // ============= Update Handling =============
210
+ function handleUpdate(message) {
211
+ console.log('[HMR] Update received:', message.fileId);
212
+
213
+ // Hide any existing error overlay
214
+ hideOverlay();
215
+
216
+ var changes = message.changes || [];
217
+ var hasCSSUpdate = changes.some(function(file) {
218
+ return file.endsWith('.css') || file.endsWith('.scss') ||
219
+ file.endsWith('.sass') || file.endsWith('.less');
220
+ });
221
+
222
+ if (hasCSSUpdate) {
223
+ // Handle CSS updates without flash
224
+ updateCSS(changes);
225
+ }
226
+
227
+ // Check if we can do hot update
228
+ if (canHotUpdate(changes)) {
229
+ performHotUpdate(message);
230
+ } else {
231
+ // Fall back to full reload
232
+ console.log('[HMR] Cannot hot update, reloading page...');
233
+ window.location.reload();
234
+ }
235
+ }
236
+
237
+ function handleReload(message) {
238
+ console.log('[HMR] Full reload requested');
239
+ hideOverlay();
240
+ window.location.reload();
241
+ }
242
+
243
+ function handleError(message) {
244
+ console.error('[HMR] Error:', message.error);
245
+ showOverlay({
246
+ message: message.error.message,
247
+ stack: message.error.stack,
248
+ file: message.error.file,
249
+ line: message.error.line,
250
+ column: message.error.column,
251
+ type: 'error'
252
+ });
253
+ }
254
+
255
+ // ============= CSS Hot Update =============
256
+ function updateCSS(changedFiles) {
257
+ var links = document.querySelectorAll('link[rel="stylesheet"]');
258
+
259
+ links.forEach(function(link) {
260
+ var href = link.getAttribute('href');
261
+ if (!href) return;
262
+
263
+ // Check if this stylesheet is affected
264
+ var isAffected = changedFiles.some(function(file) {
265
+ return href.includes(file.replace(/^.*\\//, '')) ||
266
+ file.includes(href.replace(/^.*\\//, ''));
267
+ });
268
+
269
+ if (isAffected) {
270
+ // Add timestamp to force reload
271
+ var newHref = href.split('?')[0] + '?v=' + Date.now();
272
+
273
+ // Create new link and swap
274
+ var newLink = document.createElement('link');
275
+ newLink.rel = 'stylesheet';
276
+ newLink.href = newHref;
277
+
278
+ newLink.onload = function() {
279
+ link.remove();
280
+ };
281
+
282
+ newLink.onerror = function() {
283
+ console.error('[HMR] Failed to reload CSS:', newHref);
284
+ link.remove();
285
+ };
286
+
287
+ link.parentNode.insertBefore(newLink, link);
288
+ }
289
+ });
290
+ }
291
+
292
+ // ============= Hot Update Logic =============
293
+ function canHotUpdate(changes) {
294
+ // Check if all changed files can be hot updated
295
+ return changes.every(function(file) {
296
+ var ext = file.split('.').pop().toLowerCase();
297
+
298
+ // CSS files can always be hot updated
299
+ if (ext === 'css' || ext === 'scss' || ext === 'sass' || ext === 'less') {
300
+ return true;
301
+ }
302
+
303
+ // JS/TS files need HMR boundary
304
+ if (ext === 'js' || ext === 'jsx' || ext === 'ts' || ext === 'tsx') {
305
+ return hasHMRBoundary(file);
306
+ }
307
+
308
+ // Framework-specific files
309
+ if (ext === 'vue' || ext === 'svelte') {
310
+ return true; // Vue and Svelte have built-in HMR
311
+ }
312
+
313
+ return false;
314
+ });
315
+ }
316
+
317
+ function hasHMRBoundary(file) {
318
+ // Check if the module accepts hot updates
319
+ // This is a simplified check - in production, we'd track this from the server
320
+ return true; // For now, assume all modules can be hot updated
321
+ }
322
+
323
+ function performHotUpdate(message) {
324
+ var changes = message.changes || [];
325
+
326
+ // Framework-specific update handling
327
+ switch (framework) {
328
+ case 'react':
329
+ performReactUpdate(message);
330
+ break;
331
+ case 'vue':
332
+ performVueUpdate(message);
333
+ break;
334
+ case 'svelte':
335
+ performSvelteUpdate(message);
336
+ break;
337
+ case 'solid':
338
+ performSolidUpdate(message);
339
+ break;
340
+ default:
341
+ // Generic update - reload scripts
342
+ performGenericUpdate(message);
343
+ }
344
+ }
345
+
346
+ // ============= React Fast Refresh =============
347
+ function performReactUpdate(message) {
348
+ if (!window.__HMR_REACT_REFRESH__) {
349
+ // React Refresh not available, fall back to reload
350
+ window.location.reload();
351
+ return;
352
+ }
353
+
354
+ console.log('[HMR] Applying React Fast Refresh...');
355
+
356
+ // Signal to React Refresh that an update is coming
357
+ if (window.__REACT_REFRESH__) {
358
+ try {
359
+ changes.forEach(function(file) {
360
+ // Invalidate the module
361
+ invalidateModule(file);
362
+ });
363
+
364
+ // Trigger React Refresh
365
+ window.__REACT_REFRESH__.performReactRefresh();
366
+ } catch (e) {
367
+ console.error('[HMR] React Fast Refresh failed:', e);
368
+ window.location.reload();
369
+ }
370
+ } else {
371
+ // Fallback: reload the page
372
+ window.location.reload();
373
+ }
374
+ }
375
+
376
+ // ============= Vue HMR =============
377
+ function performVueUpdate(message) {
378
+ console.log('[HMR] Applying Vue HMR...');
379
+
380
+ // Vue's HMR is handled by vue-loader and vue-hot-reload-api
381
+ if (window.__VUE_HMR__) {
382
+ try {
383
+ message.changes.forEach(function(file) {
384
+ if (file.endsWith('.vue')) {
385
+ window.__VUE_HMR__.rerender(file);
386
+ }
387
+ });
388
+ } catch (e) {
389
+ console.error('[HMR] Vue HMR failed:', e);
390
+ window.location.reload();
391
+ }
392
+ } else {
393
+ window.location.reload();
394
+ }
395
+ }
396
+
397
+ // ============= Svelte HMR =============
398
+ function performSvelteUpdate(message) {
399
+ console.log('[HMR] Applying Svelte HMR...');
400
+
401
+ if (window.__SVELTE_HMR__) {
402
+ try {
403
+ message.changes.forEach(function(file) {
404
+ if (file.endsWith('.svelte')) {
405
+ // Svelte HMR preserves component state
406
+ window.__SVELTE_HMR__.update(file);
407
+ }
408
+ });
409
+ } catch (e) {
410
+ console.error('[HMR] Svelte HMR failed:', e);
411
+ window.location.reload();
412
+ }
413
+ } else {
414
+ window.location.reload();
415
+ }
416
+ }
417
+
418
+ // ============= Solid HMR =============
419
+ function performSolidUpdate(message) {
420
+ console.log('[HMR] Applying Solid HMR...');
421
+
422
+ if (window.__SOLID_HMR__) {
423
+ try {
424
+ message.changes.forEach(function(file) {
425
+ invalidateModule(file);
426
+ });
427
+
428
+ window.__SOLID_HMR__.update();
429
+ } catch (e) {
430
+ console.error('[HMR] Solid HMR failed:', e);
431
+ window.location.reload();
432
+ }
433
+ } else {
434
+ window.location.reload();
435
+ }
436
+ }
437
+
438
+ // ============= Generic Update =============
439
+ function performGenericUpdate(message) {
440
+ console.log('[HMR] Performing generic update...');
441
+
442
+ // For unknown frameworks, reload scripts
443
+ message.changes.forEach(function(file) {
444
+ invalidateModule(file);
445
+ });
446
+
447
+ // Reload the page as a fallback
448
+ window.location.reload();
449
+ }
450
+
451
+ // ============= Module Management =============
452
+ function invalidateModule(fileId) {
453
+ moduleCache.delete(fileId);
454
+
455
+ // Find and reload script tags
456
+ var scripts = document.querySelectorAll('script[src]');
457
+ scripts.forEach(function(script) {
458
+ var src = script.getAttribute('src');
459
+ if (src && src.includes(fileId)) {
460
+ reloadScript(script);
461
+ }
462
+ });
463
+ }
464
+
465
+ function reloadScript(oldScript) {
466
+ var src = oldScript.getAttribute('src');
467
+ var newSrc = src.split('?')[0] + '?v=' + Date.now();
468
+
469
+ var newScript = document.createElement('script');
470
+ newScript.src = newSrc;
471
+ newScript.type = oldScript.type || 'text/javascript';
472
+ newScript.async = false;
473
+
474
+ // Copy attributes
475
+ Array.from(oldScript.attributes).forEach(function(attr) {
476
+ if (attr.name !== 'src') {
477
+ newScript.setAttribute(attr.name, attr.value);
478
+ }
479
+ });
480
+
481
+ oldScript.parentNode.replaceChild(newScript, oldScript);
482
+ }
483
+
484
+ // ============= Error Overlay =============
485
+ function showOverlay(options) {
486
+ // Remove existing overlay
487
+ hideOverlay();
488
+
489
+ var overlay = document.createElement('div');
490
+ overlay.id = '__hmr-overlay__';
491
+ overlay.style.cssText = [
492
+ 'position: fixed',
493
+ 'top: 0',
494
+ 'left: 0',
495
+ 'right: 0',
496
+ 'bottom: 0',
497
+ 'background: rgba(0, 0, 0, 0.85)',
498
+ 'z-index: 99999',
499
+ 'display: flex',
500
+ 'align-items: center',
501
+ 'justify-content: center',
502
+ 'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
503
+ 'color: white',
504
+ 'padding: 20px'
505
+ ].join(';');
506
+
507
+ var content = document.createElement('div');
508
+ content.style.cssText = [
509
+ 'max-width: 800px',
510
+ 'max-height: 80vh',
511
+ 'overflow: auto',
512
+ 'background: #1a1a1a',
513
+ 'border-radius: 8px',
514
+ 'padding: 20px',
515
+ 'box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5)'
516
+ ].join(';');
517
+
518
+ var title = document.createElement('h2');
519
+ title.style.cssText = 'color: #ff5555; margin: 0 0 15px 0; font-size: 18px;';
520
+ title.textContent = 'HMR Error';
521
+
522
+ var message = document.createElement('pre');
523
+ message.style.cssText = [
524
+ 'background: #282828',
525
+ 'padding: 15px',
526
+ 'border-radius: 4px',
527
+ 'overflow-x: auto',
528
+ 'font-size: 13px',
529
+ 'line-height: 1.5',
530
+ 'white-space: pre-wrap',
531
+ 'word-break: break-word'
532
+ ].join(';');
533
+ message.textContent = options.message;
534
+
535
+ content.appendChild(title);
536
+ content.appendChild(message);
537
+
538
+ if (options.stack) {
539
+ var stack = document.createElement('pre');
540
+ stack.style.cssText = [
541
+ 'background: #282828',
542
+ 'padding: 15px',
543
+ 'border-radius: 4px',
544
+ 'margin-top: 10px',
545
+ 'font-size: 12px',
546
+ 'color: #888',
547
+ 'overflow-x: auto'
548
+ ].join(';');
549
+ stack.textContent = options.stack;
550
+ content.appendChild(stack);
551
+ }
552
+
553
+ if (options.file) {
554
+ var file = document.createElement('div');
555
+ file.style.cssText = 'margin-top: 15px; color: #888; font-size: 12px;';
556
+ file.textContent = 'File: ' + options.file +
557
+ (options.line ? ':' + options.line + (options.column ? ':' + options.column : '') : '');
558
+ content.appendChild(file);
559
+ }
560
+
561
+ var closeBtn = document.createElement('button');
562
+ closeBtn.style.cssText = [
563
+ 'position: absolute',
564
+ 'top: 10px',
565
+ 'right: 10px',
566
+ 'background: transparent',
567
+ 'border: none',
568
+ 'color: #888',
569
+ 'font-size: 20px',
570
+ 'cursor: pointer',
571
+ 'padding: 5px'
572
+ ].join(';');
573
+ closeBtn.textContent = '×';
574
+ closeBtn.onclick = hideOverlay;
575
+
576
+ overlay.style.position = 'relative';
577
+ overlay.appendChild(closeBtn);
578
+ overlay.appendChild(content);
579
+
580
+ document.body.appendChild(overlay);
581
+ }
582
+
583
+ function hideOverlay() {
584
+ var overlay = document.getElementById('__hmr-overlay__');
585
+ if (overlay) {
586
+ overlay.remove();
587
+ }
588
+ }
589
+
590
+ // ============= Subscription Management =============
591
+ function subscribe(fileId) {
592
+ subscribedFiles.add(fileId);
593
+ sendMessage({ type: 'subscribe', fileId: fileId });
594
+ }
595
+
596
+ function unsubscribe(fileId) {
597
+ subscribedFiles.delete(fileId);
598
+ sendMessage({ type: 'unsubscribe', fileId: fileId });
599
+ }
600
+
601
+ // ============= Initialization =============
602
+ function init() {
603
+ // Connect to HMR server
604
+ connect();
605
+
606
+ // Subscribe to current page
607
+ var currentFile = window.location.pathname;
608
+ subscribe(currentFile);
609
+
610
+ // Expose HMR API
611
+ window.__HMR__ = {
612
+ subscribe: subscribe,
613
+ unsubscribe: unsubscribe,
614
+ connect: connect,
615
+ clientId: function() { return clientId; },
616
+ framework: framework
617
+ };
618
+
619
+ console.log('[HMR] Client initialized (framework: ' + framework + ')');
620
+ }
621
+
622
+ // Start when DOM is ready
623
+ if (document.readyState === 'complete') {
624
+ init();
625
+ } else {
626
+ document.addEventListener('DOMContentLoaded', init);
627
+ }
628
+ })();
629
+ `;
630
+
631
+ /**
632
+ * Get the HMR client script with optional configuration
633
+ */
634
+ export function getHMRClientScript(options?: {
635
+ port?: number;
636
+ }): string {
637
+ if (options?.port) {
638
+ return HMR_CLIENT_SCRIPT.replace(
639
+ "return parseInt(window.location.port || '3000', 10) + 1;",
640
+ `return ${options.port};`
641
+ );
642
+ }
643
+ return HMR_CLIENT_SCRIPT;
644
+ }
645
+
646
+ /**
647
+ * Inject HMR client script into HTML content
648
+ */
649
+ export function injectHMRScript(html: string, port?: number): string {
650
+ const script = getHMRClientScript({ port });
651
+
652
+ // Find the </head> or </body> tag to inject before
653
+ const headMatch = html.match(/<\/head>/i);
654
+ const bodyMatch = html.match(/<\/body>/i);
655
+
656
+ const injectionPoint = headMatch ? headMatch.index! + headMatch[0].length :
657
+ bodyMatch ? bodyMatch.index! + bodyMatch[0].length :
658
+ html.length;
659
+
660
+ const scriptTag = `<script data-hmr-port="${port || ''}">${script}</script>`;
661
+
662
+ return html.slice(0, injectionPoint) + scriptTag + html.slice(injectionPoint);
663
+ }