juxscript 1.0.20 → 1.0.21

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 (76) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +143 -92
  3. package/lib/components/badge.ts +93 -94
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +40 -131
  7. package/lib/components/card.ts +57 -79
  8. package/lib/components/charts/areachart.ts +315 -0
  9. package/lib/components/charts/barchart.ts +421 -0
  10. package/lib/components/charts/doughnutchart.ts +263 -0
  11. package/lib/components/charts/lib/BaseChart.ts +402 -0
  12. package/lib/components/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
  13. package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
  14. package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
  15. package/lib/components/checkbox.ts +255 -204
  16. package/lib/components/code.ts +31 -78
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +180 -147
  20. package/lib/components/dialog.ts +218 -221
  21. package/lib/components/divider.ts +63 -87
  22. package/lib/components/docs-data.json +498 -2404
  23. package/lib/components/dropdown.ts +191 -236
  24. package/lib/components/element.ts +196 -145
  25. package/lib/components/fileupload.ts +253 -167
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +31 -97
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +51 -114
  30. package/lib/components/icon.ts +33 -120
  31. package/lib/components/icons.ts +2 -1
  32. package/lib/components/include.ts +76 -3
  33. package/lib/components/input.ts +155 -407
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +358 -261
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +63 -152
  38. package/lib/components/modal.ts +42 -129
  39. package/lib/components/nav.ts +79 -101
  40. package/lib/components/paragraph.ts +38 -102
  41. package/lib/components/progress.ts +108 -166
  42. package/lib/components/radio.ts +283 -234
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +189 -199
  45. package/lib/components/sidebar.ts +110 -141
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +254 -183
  48. package/lib/components/table.ts +1078 -208
  49. package/lib/components/tabs.ts +42 -106
  50. package/lib/components/theme-toggle.ts +73 -165
  51. package/lib/components/tooltip.ts +85 -316
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +67 -41
  54. package/machinery/build.js +466 -0
  55. package/machinery/compiler.js +354 -105
  56. package/machinery/server.js +23 -100
  57. package/machinery/watcher.js +153 -130
  58. package/package.json +1 -1
  59. package/presets/base.css +1166 -0
  60. package/presets/notion.css +2 -1975
  61. package/lib/adapters/base-adapter.js +0 -35
  62. package/lib/adapters/index.js +0 -33
  63. package/lib/adapters/mysql-adapter.js +0 -65
  64. package/lib/adapters/postgres-adapter.js +0 -70
  65. package/lib/adapters/sqlite-adapter.js +0 -56
  66. package/lib/components/areachart.ts +0 -1128
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1322
  69. package/lib/components/doughnutchart.ts +0 -1259
  70. package/lib/components/footer.ts +0 -165
  71. package/lib/components/header.ts +0 -187
  72. package/lib/components/layout.ts +0 -239
  73. package/lib/components/main.ts +0 -137
  74. package/lib/layouts/default.jux +0 -8
  75. package/lib/layouts/figma.jux +0 -0
  76. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -19,111 +19,6 @@ function generateImportMapScript() {
19
19
  </script>`;
20
20
  }
21
21
 
22
- /**
23
- * Compile a .jux file to .js and .html
24
- *
25
- * @param {string} juxFilePath - Path to the .jux file
26
- * @param {Object} options - Compilation options
27
- * @param {string} options.distDir - Output directory
28
- * @param {string} options.projectRoot - Project root directory
29
- * @param {boolean} options.isServe - Whether serving for development
30
- * @returns {Promise<{jsOutputPath: string, htmlOutputPath: string}>}
31
- */
32
- export async function compileJuxFile(juxFilePath, options = {}) {
33
- const { distDir, projectRoot, isServe = false } = options;
34
-
35
- const relativePath = path.relative(projectRoot, juxFilePath);
36
- const parsedPath = path.parse(relativePath);
37
-
38
- // Output paths
39
- const outputDir = path.join(distDir, parsedPath.dir);
40
- const jsOutputPath = path.join(outputDir, `${parsedPath.name}.js`);
41
- const htmlOutputPath = path.join(outputDir, `${parsedPath.name}.html`);
42
-
43
- // Ensure output directory exists
44
- if (!fs.existsSync(outputDir)) {
45
- fs.mkdirSync(outputDir, { recursive: true });
46
- }
47
-
48
- console.log(`📝 Compiling: ${relativePath}`);
49
-
50
- // Read the .jux file
51
- const juxContent = fs.readFileSync(juxFilePath, 'utf-8');
52
-
53
- // Calculate depth for relative paths
54
- const depth = parsedPath.dir.split(path.sep).filter(p => p).length;
55
- const libPath = depth === 0 ? './lib/jux.js' : '../'.repeat(depth) + 'lib/jux.js';
56
-
57
- // Transform imports
58
- let transformedContent = juxContent;
59
-
60
- // Replace common import patterns with calculated path
61
- transformedContent = transformedContent.replace(
62
- /from\s+['"]\.\.?\/lib\/jux\.js['"]/g,
63
- `from '${libPath}'`
64
- );
65
-
66
- // Only inject import if:
67
- // 1. File is not empty (ignoring whitespace and comments)
68
- // 2. File uses 'jux.' but has no import statement
69
- const contentWithoutComments = transformedContent
70
- .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
71
- .replace(/\/\/.*/g, '') // Remove line comments
72
- .trim();
73
-
74
- const hasContent = contentWithoutComments.length > 0;
75
- const usesJux = /\bjux\./g.test(contentWithoutComments);
76
- const hasImport = /import\s+.*from/.test(transformedContent);
77
-
78
- if (hasContent && usesJux && !hasImport) {
79
- transformedContent = `import { jux } from '${libPath}';\n\n${transformedContent}`;
80
- }
81
-
82
- // Write the transformed JS
83
- fs.writeFileSync(jsOutputPath, transformedContent);
84
-
85
- console.log(` ✓ JS: ${path.relative(projectRoot, jsOutputPath)}`);
86
-
87
- // Generate HTML with import map and correct script path
88
- const scriptPath = `./${parsedPath.name}.js`;
89
- const importMapScript = generateImportMapScript();
90
-
91
- const html = `<!DOCTYPE html>
92
- <html lang="en">
93
- <head>
94
- <meta charset="UTF-8">
95
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
96
- <title>${parsedPath.name}</title>
97
- </head>
98
- <body data-theme="">
99
- <!-- App container -->
100
- <div id="app" data-jux-page="${parsedPath.name}"></div>
101
-
102
- ${importMapScript}
103
- <script type="module" src="${scriptPath}"></script>
104
- ${isServe ? `
105
- <!-- Hot reload -->
106
- <script type="module">
107
- const ws = new WebSocket('ws://localhost:3001');
108
- ws.onmessage = (msg) => {
109
- const data = JSON.parse(msg.data);
110
- if (data.type === 'reload') {
111
- console.log('🔄 Reloading page...');
112
- location.reload();
113
- }
114
- };
115
- ws.onerror = () => console.warn('⚠️ WebSocket connection failed');
116
- </script>
117
- ` : ''}
118
- </body>
119
- </html>`;
120
-
121
- fs.writeFileSync(htmlOutputPath, html);
122
- console.log(` ✓ HTML: ${path.relative(projectRoot, htmlOutputPath)}`);
123
-
124
- return { jsOutputPath, htmlOutputPath };
125
- }
126
-
127
22
  /**
128
23
  * Copy and build the JUX library from TypeScript to JavaScript
129
24
  *
@@ -406,3 +301,357 @@ function findProjectFiles(dir, extensions, fileList = [], rootDir = dir, exclude
406
301
 
407
302
  return fileList;
408
303
  }
304
+
305
+ /**
306
+ * Bundle all .jux files into a single router-based main.js
307
+ *
308
+ * @param {string} projectRoot - Source directory (jux/)
309
+ * @param {string} distDir - Destination directory (jux-dist/)
310
+ * @param {Object} options - Bundle options
311
+ * @param {string} options.routePrefix - Route prefix (e.g., '/experiments')
312
+ * @returns {Promise<string>} - Returns the generated filename (e.g., 'main.1234567890.js')
313
+ */
314
+ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {}) {
315
+ const startTime = performance.now();
316
+ const { routePrefix = '' } = options;
317
+
318
+ console.log('🔀 Bundling .jux files into router...');
319
+
320
+ const juxFiles = findFiles(projectRoot, '.jux');
321
+
322
+ if (juxFiles.length === 0) {
323
+ console.log(' No .jux files found');
324
+ return;
325
+ }
326
+
327
+ console.log(` Found ${juxFiles.length} .jux file(s)`);
328
+
329
+ const fileTimings = [];
330
+ const views = [];
331
+ const routes = [];
332
+ const sharedModules = new Map(); // Track shared .jux modules
333
+
334
+ for (const juxFile of juxFiles) {
335
+ const fileStartTime = performance.now();
336
+
337
+ const relativePath = path.relative(projectRoot, juxFile);
338
+ const parsedPath = path.parse(relativePath);
339
+
340
+ const rawFunctionName = parsedPath.dir
341
+ ? `${parsedPath.dir.replace(/\//g, '_')}_${parsedPath.name}`
342
+ : parsedPath.name;
343
+
344
+ const cleanFunctionName = rawFunctionName
345
+ .replace(/[-_]/g, ' ')
346
+ .split(' ')
347
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
348
+ .join('');
349
+
350
+ const routePath = routePrefix + '/' + (parsedPath.dir ? `${parsedPath.dir}/` : '') + parsedPath.name;
351
+ const cleanRoutePath = routePath.replace(/\/+/g, '/');
352
+
353
+ const juxContent = fs.readFileSync(juxFile, 'utf-8');
354
+
355
+ // Check if this file exports components (shared module)
356
+ const hasExports = /export\s+(const|let|function|class|{)/.test(juxContent);
357
+
358
+ if (hasExports) {
359
+ // Extract exports to shared modules section
360
+ sharedModules.set(relativePath, extractSharedModule(juxContent, rawFunctionName));
361
+ }
362
+
363
+ const viewFunction = transformJuxToViewFunction(juxContent, rawFunctionName, parsedPath.name, relativePath, sharedModules);
364
+
365
+ views.push(viewFunction);
366
+ routes.push({ path: cleanRoutePath, functionName: cleanFunctionName });
367
+
368
+ const fileTime = performance.now() - fileStartTime;
369
+ fileTimings.push({ file: relativePath, time: fileTime });
370
+
371
+ const exportNote = hasExports ? ' [+exports]' : '';
372
+ console.log(` ✓ ${relativePath} → ${cleanFunctionName}()${exportNote} (${fileTime.toFixed(1)}ms)`);
373
+ }
374
+
375
+ // ✅ Show slowest files if any took >50ms
376
+ const slowFiles = fileTimings.filter(f => f.time > 50).sort((a, b) => b.time - a.time);
377
+ if (slowFiles.length > 0) {
378
+ console.log(`\n ⚠️ Slowest files:`);
379
+ slowFiles.slice(0, 3).forEach(f => {
380
+ console.log(` ${f.file}: ${f.time.toFixed(0)}ms`);
381
+ });
382
+ }
383
+
384
+ const bundleStartTime = performance.now();
385
+ const routerCode = generateRouterBundle(views, routes, sharedModules);
386
+ const bundleGenTime = performance.now() - bundleStartTime;
387
+
388
+ // ✅ Use fixed filename (no timestamp)
389
+ const mainJsFilename = 'main.js';
390
+ const mainJsPath = path.join(distDir, mainJsFilename);
391
+
392
+ // Write to dist/main.js
393
+ const writeStartTime = performance.now();
394
+ fs.writeFileSync(mainJsPath, routerCode);
395
+ const writeTime = performance.now() - writeStartTime;
396
+
397
+ const totalTime = performance.now() - startTime;
398
+
399
+ console.log(` ✓ Generated: ${path.relative(projectRoot, mainJsPath)}`);
400
+ console.log(`\n 📊 Bundle Statistics:`);
401
+ console.log(` Files processed: ${juxFiles.length}`);
402
+ console.log(` Bundle size: ${(routerCode.length / 1024).toFixed(1)} KB`);
403
+ console.log(` Code generation: ${bundleGenTime.toFixed(0)}ms`);
404
+ console.log(` File write: ${writeTime.toFixed(0)}ms`);
405
+ console.log(` Total bundle time: ${totalTime.toFixed(0)}ms`);
406
+ console.log('✅ Router bundle complete\n');
407
+
408
+ // ✅ Return the filename so index.html can reference it
409
+ return mainJsFilename;
410
+ }
411
+
412
+ /**
413
+ * Extract shared module exports from a .jux file
414
+ *
415
+ * @param {string} juxContent - Original .jux file content
416
+ * @param {string} moduleName - Module identifier
417
+ * @returns {string} Shared module code
418
+ */
419
+ function extractSharedModule(juxContent, moduleName) {
420
+ // Remove string literals temporarily to avoid false matches
421
+ const strings = [];
422
+
423
+ // Handle template literals, single quotes, and double quotes separately
424
+ let code = juxContent
425
+ .replace(/`(?:\\.|[^`\\])*`/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; })
426
+ .replace(/"(?:\\.|[^"\\])*"/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; })
427
+ .replace(/'(?:\\.|[^'\\])*'/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; });
428
+
429
+ // Remove ALL imports - including _dev-imports.js
430
+ code = code.replace(/import\s+\{[^}]+\}\s+from\s+__STRING_\d+__;?\s*/g, '');
431
+ code = code.replace(/import\s+\*\s+as\s+\w+\s+from\s+__STRING_\d+__;?\s*/g, '');
432
+ code = code.replace(/import\s+__STRING_\d+__;?\s*/g, '');
433
+
434
+ // Convert exports to declarations (keep function keyword for functions)
435
+ code = code.replace(/export\s+const\s+/g, 'const ');
436
+ code = code.replace(/export\s+let\s+/g, 'let ');
437
+ code = code.replace(/export\s+function\s+/g, 'function ');
438
+ code = code.replace(/export\s+class\s+/g, 'class ');
439
+ code = code.replace(/export\s+\{([^}]+)\}\s*;?\s*/g, '');
440
+
441
+ // Restore string literals
442
+ code = code.replace(/__STRING_(\d+)__/g, (_, index) => strings[index]);
443
+
444
+ return code;
445
+ }
446
+
447
+ /**
448
+ * Transform .jux file content into a view function
449
+ *
450
+ * @param {string} juxContent - Original .jux file content
451
+ * @param {string} functionName - Function name for the view
452
+ * @param {string} pageName - Page name for data attribute
453
+ * @param {string} relativePath - Relative path of the .jux file
454
+ * @param {Map} sharedModules - Map of shared module paths to their code
455
+ * @returns {string} View function code
456
+ */
457
+ function transformJuxToViewFunction(juxContent, functionName, pageName, relativePath, sharedModules) {
458
+ // Remove string literals temporarily to avoid false matches
459
+ const strings = [];
460
+
461
+ // Handle template literals, single quotes, and double quotes separately
462
+ let code = juxContent
463
+ .replace(/`(?:\\.|[^`\\])*`/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; })
464
+ .replace(/"(?:\\.|[^"\\])*"/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; })
465
+ .replace(/'(?:\\.|[^'\\])*'/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; });
466
+
467
+ // Remove ALL imports - now matching against placeholders
468
+ code = code.replace(/import\s+\{[^}]+\}\s+from\s+__STRING_\d+__;?\s*/g, '');
469
+ code = code.replace(/import\s+\*\s+as\s+\w+\s+from\s+__STRING_\d+__;?\s*/g, '');
470
+ code = code.replace(/import\s+__STRING_\d+__;?\s*/g, '');
471
+
472
+ // Handle exports - convert named exports to const declarations
473
+ code = code.replace(/export\s+const\s+(\w+)\s*=/g, 'const $1 =');
474
+ code = code.replace(/export\s+let\s+(\w+)\s*=/g, 'let $1 =');
475
+ code = code.replace(/export\s+function\s+(\w+)/g, 'const $1 = function');
476
+ code = code.replace(/export\s+class\s+/g, 'class ');
477
+ code = code.replace(/export\s+default\s+/g, '');
478
+ code = code.replace(/export\s+\{([^}]+)\}\s*;?\s*/g, '');
479
+
480
+ // Only replace .renderTo(container) calls - leave explicit render targets alone
481
+ code = code.replace(/\.renderTo\(container\)/g, '.render("#app")');
482
+
483
+ // Only replace empty .render() calls (no arguments)
484
+ code = code.replace(/\.render\(\s*\)/g, '.render("#app")');
485
+
486
+ // Restore string literals
487
+ code = code.replace(/__STRING_(\d+)__/g, (_, index) => strings[index]);
488
+
489
+ const cleanName = functionName
490
+ .replace(/[-_]/g, ' ')
491
+ .split(' ')
492
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
493
+ .join('');
494
+
495
+ return `
496
+ // View: ${cleanName}
497
+ function ${cleanName}() {
498
+ ${code}
499
+
500
+ return document.getElementById('app');
501
+ }`;
502
+ }
503
+
504
+ /**
505
+ * Generate complete router bundle with all views and routing logic
506
+ *
507
+ * @param {string[]} views - Array of view function code
508
+ * @param {Array<{path: string, functionName: string}>} routes - Route definitions
509
+ * @param {Map} sharedModules - Map of shared module code
510
+ * @returns {string} Complete bundle code
511
+ */
512
+ function generateRouterBundle(views, routes, sharedModules = new Map()) {
513
+ const libImport = './lib/jux.js';
514
+
515
+ // Generate shared modules section
516
+ const sharedModulesCode = Array.from(sharedModules.values())
517
+ .filter(code => code.trim())
518
+ .join('\n\n');
519
+
520
+ // Filter out empty views (shared modules)
521
+ const viewsCode = views.filter(v => v.trim()).join('\n\n');
522
+
523
+ const routeTable = routes
524
+ .map(r => ` '${r.path}': ${r.functionName}`)
525
+ .join(',\n');
526
+
527
+ return `// Generated Jux Router Bundle
528
+ import { jux, state } from '${libImport}';
529
+
530
+ // ═══════════════════════════════════════════════════════════════════
531
+ // SHARED MODULES
532
+ // ═══════════════════════════════════════════════════════════════════
533
+
534
+ ${sharedModulesCode}
535
+
536
+ // ═══════════════════════════════════════════════════════════════════
537
+ // VIEW FUNCTIONS
538
+ // ═══════════════════════════════════════════════════════════════════
539
+
540
+ ${viewsCode}
541
+
542
+ // ═══════════════════════════════════════════════════════════════════
543
+ // 404 VIEW
544
+ // ═══════════════════════════════════════════════════════════════════
545
+
546
+ function JuxNotFound() {
547
+ jux.heading(1, { text: '404 - Page Not Found' }).render('#app');
548
+ jux.paragraph('404-msg')
549
+ .text('The page you are looking for does not exist.')
550
+ .render('#app');
551
+
552
+ return document.getElementById('app');
553
+ }
554
+
555
+ // ═══════════════════════════════════════════════════════════════════
556
+ // 403 VIEW
557
+ // ═══════════════════════════════════════════════════════════════════
558
+
559
+ function JuxForbidden() {
560
+ jux.heading(1, { text: '403 - Forbidden' }).render('#app');
561
+ jux.paragraph('403-msg')
562
+ .text('You are not authorized to view this page.')
563
+ .render('#app');
564
+
565
+ return document.getElementById('app');
566
+ }
567
+
568
+ // ═══════════════════════════════════════════════════════════════════
569
+ // ROUTE TABLE
570
+ // ═══════════════════════════════════════════════════════════════════
571
+
572
+ const routes = {
573
+ ${routeTable}
574
+ };
575
+
576
+ // ═══════════════════════════════════════════════════════════════════
577
+ // ROUTER CORE
578
+ // ═══════════════════════════════════════════════════════════════════
579
+
580
+ const app = document.getElementById('app');
581
+
582
+ function render() {
583
+ const path = location.pathname;
584
+ const view = routes[path] || JuxNotFound;
585
+
586
+ app.innerHTML = '';
587
+ app.removeAttribute('data-jux-page');
588
+
589
+ view();
590
+
591
+ const pageName = Object.entries(routes).find(([p, v]) => v === view)?.[0] || 'not-found';
592
+ app.setAttribute('data-jux-page', pageName.replace(/^\\\//, '').replace(/\\\//g, '-'));
593
+ }
594
+
595
+ document.addEventListener('click', e => {
596
+ const a = e.target.closest('a');
597
+ if (!a) return;
598
+
599
+ if (a.dataset.router === 'false') return;
600
+
601
+ const url = new URL(a.href);
602
+ if (url.origin !== location.origin) return;
603
+
604
+ e.preventDefault();
605
+ history.pushState({}, '', url.pathname);
606
+ render();
607
+ });
608
+
609
+ window.addEventListener('popstate', render);
610
+
611
+ render();
612
+ `;
613
+
614
+ }
615
+
616
+ /**
617
+ * Generate a unified index.html for router bundle
618
+ *
619
+ * @param {string} distDir - Destination directory
620
+ * @param {Array<{path: string, functionName: string}>} routes - Route definitions
621
+ * @param {string} mainJsFilename - The generated main.js filename (e.g., 'main.1234567890.js')
622
+ */
623
+ export function generateIndexHtml(distDir, routes, mainJsFilename = 'main.js') {
624
+ console.log('📄 Generating index.html...');
625
+
626
+ // Generate navigation links
627
+ const navLinks = routes
628
+ .map(r => ` <a href="${r.path}">${r.functionName.replace(/_/g, ' ')}</a>`)
629
+ .join(' |\n');
630
+
631
+ const importMapScript = generateImportMapScript();
632
+
633
+ const html = `<!DOCTYPE html>
634
+ <html lang="en">
635
+ <head>
636
+ <meta charset="UTF-8">
637
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
638
+ <title>Jux Application</title>
639
+ </head>
640
+ <body data-theme="">
641
+ <!-- Navigation -->
642
+ <nav style="padding: 20px; background: #f5f5f5; border-bottom: 2px solid #ddd;">
643
+ ${navLinks}
644
+ </nav>
645
+ <!-- App container - router renders here -->
646
+ <div id="app"></div>
647
+ ${importMapScript}
648
+ <script type="module" src="/${mainJsFilename}"></script>
649
+ </body>
650
+ </html>`;
651
+
652
+ const indexPath = path.join(distDir, 'index.html');
653
+ fs.writeFileSync(indexPath, html);
654
+
655
+ console.log(` ✓ Generated: index.html (references ${mainJsFilename})`);
656
+ console.log('✅ Index HTML complete\n');
657
+ }
@@ -3,15 +3,12 @@ import http from 'http';
3
3
  import path from 'path';
4
4
  import fs from 'fs';
5
5
  import { fileURLToPath } from 'url';
6
- import initSqlJs from 'sql.js';
7
6
  import { startWatcher } from './watcher.js';
8
7
  import { WebSocketServer } from 'ws';
9
8
 
10
9
  const __filename = fileURLToPath(import.meta.url);
11
10
  const __dirname = path.dirname(__filename);
12
11
 
13
- let db = null;
14
-
15
12
  async function serve(port = 3000, distDir = './jux-dist') {
16
13
  const app = express();
17
14
  const absoluteDistDir = path.resolve(distDir);
@@ -25,29 +22,16 @@ async function serve(port = 3000, distDir = './jux-dist') {
25
22
  process.exit(1);
26
23
  }
27
24
 
28
- // API endpoint for SQL queries
29
- app.post('/api/query', async (req, res) => {
30
- try {
31
- const { sql, params = [] } = req.body;
32
-
33
- if (!db) {
34
- return res.status(500).json({ error: 'Database not initialized' });
35
- }
36
-
37
- const stmt = db.prepare(sql);
38
- stmt.bind(params);
25
+ // Strong cache prevention for main.js
26
+ app.use((req, res, next) => {
27
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
28
+ res.setHeader('Pragma', 'no-cache');
29
+ res.setHeader('Expires', '0');
39
30
 
40
- const rows = [];
41
- while (stmt.step()) {
42
- rows.push(stmt.getAsObject());
43
- }
44
- stmt.free();
31
+ // Add ETag prevention
32
+ res.setHeader('ETag', 'W/"' + Date.now() + '"');
45
33
 
46
- res.json({ data: rows, rowCount: rows.length });
47
- } catch (error) {
48
- console.error('Query error:', error.message);
49
- res.status(400).json({ error: error.message });
50
- }
34
+ next();
51
35
  });
52
36
 
53
37
  // Disable caching in dev mode
@@ -58,74 +42,29 @@ async function serve(port = 3000, distDir = './jux-dist') {
58
42
  next();
59
43
  });
60
44
 
61
- // Serve HTML files with clean URLs
62
- const heyPath = path.join(absoluteDistDir, 'hey.html');
63
- const indexPath = path.join(absoluteDistDir, 'index.html');
64
-
65
- app.use((req, res, next) => {
66
- let requestPath = req.path.endsWith('/') && req.path.length > 1
67
- ? req.path.slice(0, -1)
68
- : req.path;
69
-
70
- // Root path - serve hey.html or index.html
71
- if (requestPath === '/') {
72
- if (fs.existsSync(heyPath)) {
73
- return res.sendFile(heyPath);
74
- }
75
- if (fs.existsSync(indexPath)) {
76
- return res.sendFile(indexPath);
77
- }
78
- }
79
-
80
- // Try to serve as HTML file
81
- const htmlPath = path.join(absoluteDistDir, requestPath + '.html');
82
- if (fs.existsSync(htmlPath) && fs.statSync(htmlPath).isFile()) {
83
- return res.sendFile(htmlPath);
84
- }
85
-
86
- // Try to serve index.html in directory
87
- const indexInDirPath = path.join(absoluteDistDir, requestPath, 'index.html');
88
- if (fs.existsSync(indexInDirPath)) {
89
- return res.sendFile(indexInDirPath);
90
- }
91
-
92
- next();
93
- });
94
-
95
45
  // Serve static files (CSS, JS, images, etc.)
96
46
  app.use(express.static(absoluteDistDir));
97
47
 
98
- // Enhanced 404 handler with debug info
99
- app.use((req, res) => {
100
- const notFoundPath = path.join(absoluteDistDir, '404.html');
101
- const requestedPath = path.join(absoluteDistDir, req.path);
102
- const fileType = path.extname(req.path) || 'directory';
103
-
104
- // Log to console for debugging
105
- console.log(`❌ 404: ${req.path}`);
106
- console.log(` Looked for: ${requestedPath}`);
107
- console.log(` Type: ${fileType}`);
108
- console.log(` Referer: ${req.get('referer') || 'direct'}`);
109
-
110
- // If custom 404.html exists and this isn't already /404
111
- if (fs.existsSync(notFoundPath) && req.path !== '/404') {
112
- const debugUrl = `/404?path=${encodeURIComponent(req.path)}&type=${fileType}&from=${encodeURIComponent(req.get('referer') || 'direct')}`;
113
- return res.redirect(debugUrl);
114
- }
115
-
116
- // Serve custom 404 page
117
- if (fs.existsSync(notFoundPath)) {
118
- return res.status(404).sendFile(notFoundPath);
48
+ // SPA fallback: only for requests that accept HTML (not static files)
49
+ app.get('*', (req, res) => {
50
+ // If request accepts HTML, serve index.html
51
+ if (req.accepts('html')) {
52
+ const indexPath = path.join(absoluteDistDir, 'index.html');
53
+ if (fs.existsSync(indexPath)) {
54
+ res.sendFile(indexPath);
55
+ } else {
56
+ res.status(404).send('index.html not found. Run `npx jux bundle` first.');
57
+ }
58
+ } else {
59
+ // For non-HTML requests (like .js, .css), return 404
60
+ res.status(404).send('Not found');
119
61
  }
120
-
121
- // Fallback: minimal 404 response
122
- res.status(404).send('<h1>404 - Not Found</h1>');
123
62
  });
124
63
 
125
- // Create HTTP server (wrap Express app)
64
+ // Create HTTP server
126
65
  const server = http.createServer(app);
127
66
 
128
- // WebSocket server on separate port 3001
67
+ // WebSocket server for hot reload
129
68
  const wss = new WebSocketServer({ port: 3001 });
130
69
  const clients = [];
131
70
 
@@ -165,7 +104,6 @@ async function serve(port = 3000, distDir = './jux-dist') {
165
104
  console.log('\n\n👋 Shutting down server...');
166
105
  wss.close();
167
106
  server.close();
168
- if (db) db = null;
169
107
  process.exit(0);
170
108
  };
171
109
 
@@ -175,21 +113,6 @@ async function serve(port = 3000, distDir = './jux-dist') {
175
113
  return { server };
176
114
  }
177
115
 
178
- async function initDatabase() {
179
- const SQL = await initSqlJs();
180
- const dbPath = path.join(__dirname, '../db/jux.db');
181
-
182
- if (fs.existsSync(dbPath)) {
183
- const buffer = fs.readFileSync(dbPath);
184
- db = new SQL.Database(buffer);
185
- console.log('📊 Database loaded:', dbPath);
186
- } else {
187
- db = new SQL.Database();
188
- console.log('📊 Using in-memory database');
189
- }
190
- }
191
-
192
116
  export async function start(port = 3000) {
193
- await initDatabase();
194
117
  return serve(port, './jux-dist');
195
118
  }