juxscript 1.1.118 → 1.1.120

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "totalComponents": 69,
3
- "generatedAt": "2026-02-13T04:58:53.712Z",
3
+ "generatedAt": "2026-02-13T05:01:41.327Z",
4
4
  "components": [
5
5
  {
6
6
  "file": "alert.js",
@@ -13,7 +13,7 @@ export class JuxCompiler {
13
13
  this.config = config;
14
14
  this.srcDir = config.srcDir || './jux';
15
15
  this.distDir = config.distDir || './.jux-dist';
16
- this.publicDir = config.publicDir || './public';
16
+ this.publicDir = config.publicDir || './public'; // ✅ Configurable public path
17
17
  this.defaults = config.defaults || {};
18
18
  this.paths = config.paths || {};
19
19
  this._juxscriptExports = null;
@@ -53,56 +53,24 @@ export class JuxCompiler {
53
53
  return null;
54
54
  }
55
55
 
56
- /**
57
- * Recursively scan for .jux and .js files
58
- */
59
- scanFiles(dir = this.srcDir, baseDir = this.srcDir) {
60
- const views = [];
61
- const dataModules = [];
62
- const sharedModules = [];
56
+ scanFiles() {
57
+ const files = fs.readdirSync(this.srcDir)
58
+ .filter(f => (f.endsWith('.jux') || f.endsWith('.js')) && !this.isAssetFile(f));
63
59
 
64
- const entries = fs.readdirSync(dir, { withFileTypes: true });
60
+ const views = [], dataModules = [], sharedModules = [];
65
61
 
66
- for (const entry of entries) {
67
- // Skip hidden files/folders
68
- if (entry.name.startsWith('.')) continue;
62
+ files.forEach(file => {
63
+ const content = fs.readFileSync(path.join(this.srcDir, file), 'utf8');
64
+ const name = file.replace(/\.[^/.]+$/, '');
69
65
 
70
- const fullPath = path.join(dir, entry.name);
71
- const relativePath = path.relative(baseDir, fullPath);
72
-
73
- if (entry.isDirectory()) {
74
- // Recurse into subdirectories
75
- const nested = this.scanFiles(fullPath, baseDir);
76
- views.push(...nested.views);
77
- dataModules.push(...nested.dataModules);
78
- sharedModules.push(...nested.sharedModules);
79
- } else if ((entry.name.endsWith('.jux') || entry.name.endsWith('.js')) && !this.isAssetFile(entry.name)) {
80
- const content = fs.readFileSync(fullPath, 'utf8');
81
-
82
- // ✅ Generate name from folder structure
83
- // Example: abc/juxabc.jux -> abc_juxabc
84
- const nameFromPath = relativePath
85
- .replace(/\.[^/.]+$/, '') // Remove extension
86
- .replace(/\\/g, '/') // Normalize Windows paths
87
- .replace(/\//g, '_') // Folder separator -> underscore
88
- .replace(/[^a-zA-Z0-9_]/g, '_'); // Sanitize
89
-
90
- const module = {
91
- name: nameFromPath,
92
- file: relativePath,
93
- fullPath: fullPath,
94
- content: content
95
- };
96
-
97
- if (entry.name.includes('data')) {
98
- dataModules.push(module);
99
- } else if (/export\s+(function|const|let|var|class)\s+/.test(content)) {
100
- sharedModules.push(module);
101
- } else {
102
- views.push(module);
103
- }
66
+ if (file.includes('data')) {
67
+ dataModules.push({ name, file, content });
68
+ } else if (/export\s+(function|const|let|var|class)\s+/.test(content)) {
69
+ sharedModules.push({ name, file, content });
70
+ } else {
71
+ views.push({ name, file, content });
104
72
  }
105
- }
73
+ });
106
74
 
107
75
  return { views, dataModules, sharedModules };
108
76
  }
@@ -203,220 +171,7 @@ export class JuxCompiler {
203
171
  }
204
172
 
205
173
  /**
206
- * Generate routes based on folder structure
207
- */
208
- _generateRouter(views) {
209
- let routeMap = '';
210
-
211
- views.forEach(v => {
212
- // ✅ Generate route from folder structure
213
- // abc/juxabc.jux -> /abc/juxabc
214
- // index.jux -> /
215
- // abc/index.jux -> /abc
216
-
217
- const routePath = this._generateRoutePath(v.file);
218
- const functionName = this._generateFunctionName(v.name);
219
-
220
- routeMap += ` '${routePath}': render${functionName},\n`;
221
- });
222
-
223
- return `
224
- // --- JUX SOURCE LOADER ---
225
- var __juxSources = null;
226
- async function __juxLoadSources() {
227
- if (__juxSources) return __juxSources;
228
- try {
229
- var res = await fetch('/__jux_sources.json');
230
- __juxSources = await res.json();
231
- } catch (e) {
232
- __juxSources = {};
233
- }
234
- return __juxSources;
235
- }
236
-
237
- function __juxFindSource(stack) {
238
- var match = stack.match(/render([A-Z][a-zA-Z0-9_]*)/);
239
- if (match) {
240
- var funcName = match[1];
241
- for (var file in __juxSources || {}) {
242
- var normalized = __juxSources[file].name
243
- .split('_')
244
- .map(s => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())
245
- .join('');
246
-
247
- if (normalized === funcName) {
248
- return { file: file, source: __juxSources[file], viewName: funcName };
249
- }
250
- }
251
- }
252
- return null;
253
- }
254
-
255
- // --- JUX RUNTIME ERROR OVERLAY ---
256
- var __juxErrorOverlay = {
257
- styles: \`
258
- #__jux-error-overlay {
259
- position: fixed; inset: 0; z-index: 99999;
260
- background: rgba(0, 0, 0, 0.4);
261
- display: flex; align-items: center; justify-content: center;
262
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
263
- opacity: 0; transition: opacity 0.2s ease-out; backdrop-filter: blur(2px);
264
- }
265
- #__jux-error-overlay.visible { opacity: 1; }
266
- #__jux-error-overlay * { box-sizing: border-box; }
267
- .__jux-modal {
268
- background: #f8f9fa; border-radius: 4px;
269
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
270
- max-width: 80vw; width: 90%; max-height: 90vh;
271
- overflow: hidden; display: flex; flex-direction: column;
272
- transform: translateY(10px); transition: transform 0.2s ease-out;
273
- }
274
- #__jux-error-overlay.visible .__jux-modal { transform: translateY(0); }
275
- .__jux-header { background: #fff; padding: 20px 24px; border-bottom: 1px solid #e5e7eb; }
276
- .__jux-header h3 { margin: 0 0 6px; font-weight: 600; font-size: 11px; color: #9ca3af; text-transform: uppercase; }
277
- .__jux-header h1 { margin: 0 0 8px; font-size: 18px; font-weight: 600; color: #dc2626; line-height: 1.3; }
278
- .__jux-header .file-info { color: #6b7280; font-size: 13px; }
279
- .__jux-header .file-info strong { color: #dc2626; font-weight: 600; }
280
- .__jux-source { padding: 16px 24px; overflow: auto; flex: 1; background: #f8f9fa; }
281
- .__jux-code { background: #fff; border-radius: 8px; overflow: hidden; border: 1px solid #e5e7eb; }
282
- .__jux-code-line { display: flex; font-size: 13px; line-height: 1.7; }
283
- .__jux-code-line.error { background: #fef2f2; }
284
- .__jux-code-line.error .__jux-line-code { color: #dc2626; font-weight: 500; }
285
- .__jux-code-line.context { background: #fefce8; }
286
- .__jux-line-num { min-width: 44px; padding: 4px 12px; text-align: right; color: #9ca3af; background: #f9fafb; border-right: 1px solid #e5e7eb; font-size: 12px; }
287
- .__jux-code-line.error .__jux-line-num { background: #fef2f2; color: #dc2626; }
288
- .__jux-line-code { flex: 1; padding: 4px 16px; color: #374151; white-space: pre; overflow-x: auto; }
289
- .__jux-footer { padding: 16px 24px; background: #fff; border-top: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center; }
290
- .__jux-tip { color: #6b7280; font-size: 12px; }
291
- .__jux-dismiss { background: #f3f4f6; color: #374151; border: 1px solid #d1d5db; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; }
292
- .__jux-dismiss:hover { background: #e5e7eb; }
293
- .__jux-no-source { color: #6b7280; padding: 24px; text-align: center; }
294
- .__jux-no-source pre { background: #fff; padding: 16px; border-radius: 8px; margin-top: 16px; font-size: 11px; color: #6b7280; overflow-x: auto; text-align: left; border: 1px solid #e5e7eb; }
295
- \`,
296
-
297
- show: async function(error, title) {
298
- title = title || 'Runtime Error';
299
- var existing = document.getElementById('__jux-error-overlay');
300
- if (existing) existing.remove();
301
- await __juxLoadSources();
302
-
303
- var overlay = document.createElement('div');
304
- overlay.id = '__jux-error-overlay';
305
- var stack = error && error.stack ? error.stack : '';
306
- var msg = error && error.message ? error.message : String(error);
307
- var found = __juxFindSource(stack);
308
- var sourceHtml = '', fileInfo = '';
309
-
310
- if (found && found.source && found.source.lines) {
311
- var lines = found.source.lines;
312
- fileInfo = '<span class="file-info">in <strong>' + found.file + '</strong></span>';
313
- var errorLineIndex = -1;
314
- var errorMethod = msg.match(/\\.([a-zA-Z]+)\\s*is not a function/);
315
- if (errorMethod) {
316
- for (var i = 0; i < lines.length; i++) {
317
- if (lines[i].indexOf('.' + errorMethod[1]) > -1) { errorLineIndex = i; break; }
318
- }
319
- }
320
- if (errorLineIndex === -1) {
321
- for (var i = 0; i < lines.length; i++) {
322
- if (lines[i].indexOf('throw ') > -1) { errorLineIndex = i; break; }
323
- }
324
- }
325
- var contextStart = Math.max(0, errorLineIndex - 3);
326
- var contextEnd = Math.min(lines.length - 1, errorLineIndex + 5);
327
- if (errorLineIndex === -1) { contextStart = 0; contextEnd = Math.min(14, lines.length - 1); }
328
-
329
- for (var i = contextStart; i <= contextEnd; i++) {
330
- var isError = (i === errorLineIndex);
331
- var isContext = !isError && errorLineIndex > -1 && Math.abs(i - errorLineIndex) <= 2;
332
- var lineClass = isError ? ' error' : (isContext ? ' context' : '');
333
- var lineCode = lines[i].replace(/</g, '&lt;').replace(/>/g, '&gt;') || ' ';
334
- sourceHtml += '<div class="__jux-code-line' + lineClass + '"><span class="__jux-line-num">' + (i + 1) + '</span><span class="__jux-line-code">' + lineCode + '</span></div>';
335
- }
336
- } else {
337
- sourceHtml = '<div class="__jux-no-source"><p>Could not locate source file.</p><pre>' + stack + '</pre></div>';
338
- }
339
-
340
- overlay.innerHTML = '<style>' + this.styles + '</style><div class="__jux-modal"><div class="__jux-header"><h3>' + title + '</h3><h1>' + msg + '</h1>' + fileInfo + '</div><div class="__jux-source"><div class="__jux-code">' + sourceHtml + '</div></div><div class="__jux-footer"><span class="__jux-tip">💡 Fix the error and save to reload</span><button class="__jux-dismiss" onclick="document.getElementById(\\'__jux-error-overlay\\').remove()">Dismiss</button></div></div>';
341
- document.body.appendChild(overlay);
342
- requestAnimationFrame(function() { overlay.classList.add('visible'); });
343
- console.error(title + ':', error);
344
- }
345
- };
346
-
347
- window.addEventListener('error', function(e) { __juxErrorOverlay.show(e.error || new Error(e.message), 'Uncaught Error'); }, true);
348
- window.addEventListener('unhandledrejection', function(e) { __juxErrorOverlay.show(e.reason || new Error('Promise rejected'), 'Unhandled Promise Rejection'); }, true);
349
-
350
- // --- JUX ROUTER ---
351
- const routes = {\n${routeMap}};
352
-
353
- async function navigate(path) {
354
- const view = routes[path];
355
- if (!view) {
356
- document.getElementById('app').innerHTML = '<h1 style="padding:40px;">404 - Not Found</h1>';
357
- return;
358
- }
359
- document.getElementById('app').innerHTML = '';
360
- var overlay = document.getElementById('__jux-error-overlay');
361
- if (overlay) overlay.remove();
362
- try { await view(); } catch (err) { __juxErrorOverlay.show(err, 'Jux Render Error'); }
363
- }
364
-
365
- document.addEventListener('click', e => {
366
- const a = e.target.closest('a');
367
- if (!a || a.dataset.router === 'false') return;
368
- try { if (new URL(a.href, location.origin).origin !== location.origin) return; } catch { return; }
369
- e.preventDefault();
370
- history.pushState({}, '', a.href);
371
- navigate(new URL(a.href, location.origin).pathname);
372
- });
373
-
374
- window.addEventListener('popstate', () => navigate(location.pathname));
375
- navigate(location.pathname);
376
- `;
377
- }
378
-
379
- /**
380
- * ✅ Generate route path from file path
381
- * Examples:
382
- * index.jux -> /
383
- * abc/juxabc.jux -> /abc/juxabc
384
- * abc/index.jux -> /abc
385
- */
386
- _generateRoutePath(filePath) {
387
- // Normalize separators
388
- const normalized = filePath.replace(/\\/g, '/');
389
-
390
- // Remove extension
391
- const withoutExt = normalized.replace(/\.[^/.]+$/, '');
392
-
393
- // Handle index files
394
- if (withoutExt === 'index') {
395
- return '/';
396
- }
397
-
398
- // Remove trailing /index
399
- const cleaned = withoutExt.replace(/\/index$/, '');
400
-
401
- // Ensure leading slash
402
- return '/' + cleaned.toLowerCase();
403
- }
404
-
405
- /**
406
- * ✅ Generate PascalCase function name from sanitized name
407
- * Examples:
408
- * abc_juxabc -> AbcJuxabc
409
- * index -> Index
410
- */
411
- _generateFunctionName(name) {
412
- return name
413
- .split('_')
414
- .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
415
- .join('');
416
- }
417
-
418
- /**
419
- * ✅ Generate entry point with nested folder support
174
+ * Generate entry point without layout/theme logic
420
175
  */
421
176
  generateEntryPoint(views, dataModules, sharedModules) {
422
177
  let entry = `// Auto-generated JUX entry point\n\n`;
@@ -454,7 +209,7 @@ navigate(location.pathname);
454
209
  entry += `\n// --- VIEW FUNCTIONS ---\n`;
455
210
 
456
211
  views.forEach(v => {
457
- const functionName = this._generateFunctionName(v.name);
212
+ const capitalized = v.name.charAt(0).toUpperCase() + v.name.slice(1);
458
213
  allIssues.push(...this.validateViewCode(v.name, v.content));
459
214
 
460
215
  sourceSnapshot[v.file] = {
@@ -467,7 +222,7 @@ navigate(location.pathname);
467
222
  let viewCode = this.removeImports(v.content).replace(/^\s*export\s+default\s+.*$/gm, '');
468
223
  const asyncPrefix = viewCode.includes('await ') ? 'async ' : '';
469
224
 
470
- entry += `\n${asyncPrefix}function render${functionName}() {\n${viewCode}\n}\n`;
225
+ entry += `\n${asyncPrefix}function render${capitalized}() {\n${viewCode}\n}\n`;
471
226
  });
472
227
 
473
228
  dataModules.forEach(m => {
@@ -502,17 +257,10 @@ navigate(location.pathname);
502
257
 
503
258
  _generateRouter(views) {
504
259
  let routeMap = '';
505
-
506
260
  views.forEach(v => {
507
- // Generate route from folder structure
508
- // abc/juxabc.jux -> /abc/juxabc
509
- // index.jux -> /
510
- // abc/index.jux -> /abc
511
-
512
- const routePath = this._generateRoutePath(v.file);
513
- const functionName = this._generateFunctionName(v.name);
514
-
515
- routeMap += ` '${routePath}': render${functionName},\n`;
261
+ const cap = v.name.charAt(0).toUpperCase() + v.name.slice(1);
262
+ if (v.name.toLowerCase() === 'index') routeMap += ` '/': render${cap},\n`;
263
+ routeMap += ` '/${v.name.toLowerCase()}': render${cap},\n`;
516
264
  });
517
265
 
518
266
  return `
@@ -530,17 +278,12 @@ async function __juxLoadSources() {
530
278
  }
531
279
 
532
280
  function __juxFindSource(stack) {
533
- var match = stack.match(/render([A-Z][a-zA-Z0-9_]*)/);
281
+ var match = stack.match(/render(\\w+)/);
534
282
  if (match) {
535
- var funcName = match[1];
283
+ var viewName = match[1].toLowerCase();
536
284
  for (var file in __juxSources || {}) {
537
- var normalized = __juxSources[file].name
538
- .split('_')
539
- .map(s => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())
540
- .join('');
541
-
542
- if (normalized === funcName) {
543
- return { file: file, source: __juxSources[file], viewName: funcName };
285
+ if (__juxSources[file].name.toLowerCase() === viewName) {
286
+ return { file: file, source: __juxSources[file], viewName: match[1] };
544
287
  }
545
288
  }
546
289
  }
@@ -671,6 +414,94 @@ navigate(location.pathname);
671
414
  `;
672
415
  }
673
416
 
417
+ async build() {
418
+ console.log('🚀 JUX Build\n');
419
+
420
+ const juxscriptPath = this.findJuxscriptPath();
421
+ if (!juxscriptPath) {
422
+ console.error('❌ Could not locate juxscript package');
423
+ return { success: false, errors: [{ message: 'juxscript not found' }], warnings: [] };
424
+ }
425
+ console.log(`📦 Using: ${juxscriptPath}`);
426
+
427
+ await this.loadJuxscriptExports();
428
+
429
+ if (fs.existsSync(this.distDir)) {
430
+ fs.rmSync(this.distDir, { recursive: true, force: true });
431
+ }
432
+ fs.mkdirSync(this.distDir, { recursive: true });
433
+
434
+ // ✅ Copy public folder if exists
435
+ this.copyPublicFolder();
436
+
437
+ const { views, dataModules, sharedModules } = this.scanFiles();
438
+ console.log(`📁 Found ${views.length} views, ${sharedModules.length} shared, ${dataModules.length} data`);
439
+
440
+ // Copy data/shared modules to dist
441
+ const juxDistDir = path.join(this.distDir, 'jux');
442
+ fs.mkdirSync(juxDistDir, { recursive: true });
443
+ [...dataModules, ...sharedModules].forEach(m => {
444
+ fs.writeFileSync(path.join(juxDistDir, m.file), m.content);
445
+ });
446
+
447
+ const entryContent = this.generateEntryPoint(views, dataModules, sharedModules);
448
+ const entryPath = path.join(this.distDir, 'entry.js');
449
+ fs.writeFileSync(entryPath, entryContent);
450
+
451
+ const snapshotPath = path.join(this.distDir, '__jux_sources.json');
452
+ fs.writeFileSync(snapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
453
+ console.log(`📸 Source snapshot written`);
454
+
455
+ const validation = this.reportValidationIssues();
456
+ if (!validation.isValid) {
457
+ console.log('🛑 BUILD FAILED\n');
458
+ return { success: false, errors: validation.errors, warnings: validation.warnings };
459
+ }
460
+
461
+ try {
462
+ await esbuild.build({
463
+ entryPoints: [entryPath],
464
+ bundle: true,
465
+ outfile: path.join(this.distDir, 'bundle.js'),
466
+ format: 'esm',
467
+ platform: 'browser',
468
+ target: 'esnext',
469
+ sourcemap: true,
470
+ loader: { '.jux': 'js', '.css': 'empty' },
471
+ plugins: [{
472
+ name: 'juxscript-resolver',
473
+ setup: (build) => {
474
+ build.onResolve({ filter: /^juxscript$/ }, () => ({ path: juxscriptPath }));
475
+ }
476
+ }],
477
+ });
478
+ console.log('✅ esbuild complete');
479
+ } catch (err) {
480
+ console.error('❌ esbuild failed:', err);
481
+ return { success: false, errors: [{ message: err.message }], warnings: [] };
482
+ }
483
+
484
+ const html = `<!DOCTYPE html>
485
+ <html lang="en">
486
+ <head>
487
+ <meta charset="UTF-8">
488
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
489
+ <title>JUX App</title>
490
+ <script type="module" src="./bundle.js"></script>
491
+ </head>
492
+ <body>
493
+ <div id="app"></div>
494
+ </body>
495
+ </html>`;
496
+ fs.writeFileSync(path.join(this.distDir, 'index.html'), html);
497
+
498
+ fs.unlinkSync(entryPath);
499
+ fs.rmSync(juxDistDir, { recursive: true, force: true });
500
+
501
+ console.log(`\n✅ Build Complete!\n`);
502
+ return { success: true, errors: [], warnings: validation.warnings };
503
+ }
504
+
674
505
  /**
675
506
  * Copy public folder contents to dist
676
507
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.118",
3
+ "version": "1.1.120",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",