juxscript 1.1.117 → 1.1.118

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:55:13.466Z",
3
+ "generatedAt": "2026-02-13T04:58:53.712Z",
4
4
  "components": [
5
5
  {
6
6
  "file": "alert.js",
@@ -203,160 +203,20 @@ export class JuxCompiler {
203
203
  }
204
204
 
205
205
  /**
206
- * ✅ Generate route path from file path
207
- * Examples:
208
- * index.jux -> /
209
- * abc/juxabc.jux -> /abc/juxabc
210
- * abc/index.jux -> /abc
211
- */
212
- _generateRoutePath(filePath) {
213
- // Normalize separators
214
- const normalized = filePath.replace(/\\/g, '/');
215
-
216
- // Remove extension
217
- const withoutExt = normalized.replace(/\.[^/.]+$/, '');
218
-
219
- // Handle index files
220
- if (withoutExt === 'index') {
221
- return '/';
222
- }
223
-
224
- // Remove trailing /index
225
- const cleaned = withoutExt.replace(/\/index$/, '');
226
-
227
- // Ensure leading slash
228
- return '/' + cleaned.toLowerCase();
229
- }
230
-
231
- /**
232
- * ✅ Generate PascalCase function name from sanitized name
233
- * Examples:
234
- * abc_juxabc -> AbcJuxabc
235
- * index -> Index
236
- */
237
- _generateFunctionName(name) {
238
- return name
239
- .split('_')
240
- .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
241
- .join('');
242
- }
243
-
244
- /**
245
- * ✅ Generate entry point with nested folder support
246
- */
247
- generateEntryPoint(views, dataModules, sharedModules) {
248
- let entry = `// Auto-generated JUX entry point\n\n`;
249
- const allIssues = [];
250
- const sourceSnapshot = {};
251
-
252
- const juxImports = new Set();
253
- const layoutImports = new Set(); // ✅ Track layout imports separately
254
-
255
- // Scan for imports
256
- [...views, ...dataModules, ...sharedModules].forEach(m => {
257
- // Regular juxscript imports
258
- for (const match of m.content.matchAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]juxscript['"]/g)) {
259
- match[1].split(',').map(s => s.trim()).forEach(imp => {
260
- if (imp) {
261
- // ✅ Separate layout imports
262
- if (imp === 'VStack' || imp === 'HStack' || imp === 'ZStack') {
263
- layoutImports.add(imp);
264
- } else {
265
- juxImports.add(imp);
266
- }
267
- }
268
- });
269
- }
270
- });
271
-
272
- // ✅ Import layouts separately
273
- if (layoutImports.size > 0) {
274
- entry += `import { ${[...layoutImports].sort().join(', ')} } from 'juxscript';\n`;
275
- }
276
-
277
- // ✅ Import regular components
278
- if (juxImports.size > 0) {
279
- entry += `import { ${[...juxImports].sort().join(', ')} } from 'juxscript';\n\n`;
280
- }
281
-
282
- // Data and shared modules
283
- dataModules.forEach(m => {
284
- entry += `import * as ${this.sanitizeName(m.name)}Data from './jux/${m.file}';\n`;
285
- });
286
- sharedModules.forEach(m => {
287
- entry += `import * as ${this.sanitizeName(m.name)}Shared from './jux/${m.file}';\n`;
288
- });
289
-
290
- entry += `\n// Expose to window\n`;
291
- dataModules.forEach(m => entry += `Object.assign(window, ${this.sanitizeName(m.name)}Data);\n`);
292
- sharedModules.forEach(m => entry += `Object.assign(window, ${this.sanitizeName(m.name)}Shared);\n`);
293
-
294
- // ✅ Expose layouts to window
295
- if (layoutImports.size > 0) {
296
- entry += `\n// Expose layout components\n`;
297
- layoutImports.forEach(layout => {
298
- entry += `window.${layout} = ${layout};\n`;
299
- });
300
- }
301
-
302
- // ✅ Expose regular components
303
- if (juxImports.size > 0) {
304
- entry += `\n// Expose components\n`;
305
- entry += `Object.assign(window, { ${[...juxImports].join(', ')} });\n`;
306
- }
307
-
308
- entry += `\n// --- VIEW FUNCTIONS ---\n`;
309
-
310
- views.forEach(v => {
311
- const functionName = this._generateFunctionName(v.name);
312
- allIssues.push(...this.validateViewCode(v.name, v.content));
313
-
314
- sourceSnapshot[v.file] = {
315
- name: v.name,
316
- file: v.file,
317
- content: v.content,
318
- lines: v.content.split('\n')
319
- };
320
-
321
- let viewCode = this.removeImports(v.content).replace(/^\s*export\s+default\s+.*$/gm, '');
322
- const asyncPrefix = viewCode.includes('await ') ? 'async ' : '';
323
-
324
- entry += `\n${asyncPrefix}function render${functionName}() {\n${viewCode}\n}\n`;
325
- });
326
-
327
- dataModules.forEach(m => {
328
- sourceSnapshot[m.file] = { name: m.name, file: m.file, content: m.content, lines: m.content.split('\n') };
329
- });
330
- sharedModules.forEach(m => {
331
- sourceSnapshot[m.file] = { name: m.name, file: m.file, content: m.content, lines: m.content.split('\n') };
332
- });
333
-
334
- this._sourceSnapshot = sourceSnapshot;
335
- this._validationIssues = allIssues;
336
- entry += this._generateRouter(views);
337
- return entry;
338
- }
339
-
340
- reportValidationIssues() {
341
- if (!this._validationIssues || this._validationIssues.length === 0) return;
342
-
343
- console.log('\n⚠️ Validation Issues:\n');
344
- this._validationIssues.forEach(issue => {
345
- const icon = issue.type === 'error' ? '❌' : '⚠️';
346
- console.log(` ${icon} ${issue.view}:${issue.line} - ${issue.message}`);
347
- });
348
- console.log('');
349
- }
350
-
351
- /**
352
- * ✅ Generate routes based on folder structure (SINGLE DEFINITION)
206
+ * ✅ Generate routes based on folder structure
353
207
  */
354
208
  _generateRouter(views) {
355
209
  let routeMap = '';
356
210
 
357
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
+
358
217
  const routePath = this._generateRoutePath(v.file);
359
218
  const functionName = this._generateFunctionName(v.name);
219
+
360
220
  routeMap += ` '${routePath}': render${functionName},\n`;
361
221
  });
362
222
 
@@ -488,8 +348,7 @@ window.addEventListener('error', function(e) { __juxErrorOverlay.show(e.error ||
488
348
  window.addEventListener('unhandledrejection', function(e) { __juxErrorOverlay.show(e.reason || new Error('Promise rejected'), 'Unhandled Promise Rejection'); }, true);
489
349
 
490
350
  // --- JUX ROUTER ---
491
- const routes = {
492
- ${routeMap}};
351
+ const routes = {\n${routeMap}};
493
352
 
494
353
  async function navigate(path) {
495
354
  const view = routes[path];
@@ -518,218 +377,29 @@ navigate(location.pathname);
518
377
  }
519
378
 
520
379
  /**
521
- * ✅ Copy source files to dist/jux for esbuild to resolve
522
- */
523
- _copySourceToDist() {
524
- const distJuxDir = path.join(this.distDir, 'jux');
525
-
526
- // Ensure dist/jux directory exists
527
- if (!fs.existsSync(distJuxDir)) {
528
- fs.mkdirSync(distJuxDir, { recursive: true });
529
- }
530
-
531
- // Copy all .jux and .js files from source to dist
532
- this._copySourceFilesRecursive(this.srcDir, this.srcDir, distJuxDir);
533
- }
534
-
535
- /**
536
- * Recursively copy .jux and .js files preserving folder structure
537
- */
538
- _copySourceFilesRecursive(src, baseDir, distBase) {
539
- const entries = fs.readdirSync(src, { withFileTypes: true });
540
-
541
- entries.forEach(entry => {
542
- if (entry.name.startsWith('.')) return; // Skip hidden
543
-
544
- const srcPath = path.join(src, entry.name);
545
- const relativePath = path.relative(baseDir, srcPath);
546
- const destPath = path.join(distBase, relativePath);
547
-
548
- if (entry.isDirectory()) {
549
- // Create directory if it doesn't exist
550
- if (!fs.existsSync(destPath)) {
551
- fs.mkdirSync(destPath, { recursive: true });
552
- }
553
- // Recurse into subdirectory
554
- this._copySourceFilesRecursive(srcPath, baseDir, distBase);
555
- } else if (entry.name.endsWith('.jux') || entry.name.endsWith('.js')) {
556
- // Copy .jux and .js files (skip assets)
557
- if (!this.isAssetFile(entry.name)) {
558
- // Ensure parent directory exists
559
- const destDir = path.dirname(destPath);
560
- if (!fs.existsSync(destDir)) {
561
- fs.mkdirSync(destDir, { recursive: true });
562
- }
563
- fs.copyFileSync(srcPath, destPath);
564
- console.log(` 📋 Copied: ${relativePath}`);
565
- }
566
- }
567
- });
568
- }
569
-
570
- /**
571
- * Copy ONLY public assets (CSS, images, fonts) to dist
572
- * ❌ Does NOT copy .jux files (they are compiled separately)
573
- */
574
- async copyPublicAssets() {
575
- const { publicDir, distDir, paths } = this.config;
576
-
577
- // Resolve public folder path
578
- const publicPath = paths?.public || path.resolve(this.config.srcDir, '..', publicDir);
579
-
580
- if (!fs.existsSync(publicPath)) {
581
- console.log(`ℹ️ No public folder found at ${publicPath}, skipping asset copy`);
582
- return;
583
- }
584
-
585
- console.log(`📦 Copying public assets from ${publicPath}...`);
586
-
587
- // ✅ ONLY copy known asset file types
588
- const assetTypes = [
589
- '.css', '.scss', '.sass', '.less', // Stylesheets
590
- '.jpg', '.jpeg', '.png', '.gif', '.svg', // Images
591
- '.ico', '.webp', '.avif', // Icons/modern images
592
- '.woff', '.woff2', '.ttf', '.eot', '.otf', // Fonts
593
- '.mp4', '.webm', '.ogg', // Video
594
- '.mp3', '.wav', '.m4a', // Audio
595
- '.pdf', '.txt', '.json', '.xml' // Documents
596
- ];
597
-
598
- const copyRecursive = (src, dest) => {
599
- if (!fs.existsSync(src)) return;
600
-
601
- const entries = fs.readdirSync(src, { withFileTypes: true });
602
-
603
- for (const entry of entries) {
604
- const srcPath = path.join(src, entry.name);
605
- const destPath = path.join(dest, entry.name);
606
-
607
- if (entry.isDirectory()) {
608
- fs.mkdirSync(destPath, { recursive: true });
609
- copyRecursive(srcPath, destPath);
610
- } else {
611
- const ext = path.extname(entry.name).toLowerCase();
612
-
613
- // ✅ ONLY copy known asset types
614
- // ❌ SKIP .jux files (they're bundled into entry.js)
615
- if (assetTypes.includes(ext)) {
616
- fs.copyFileSync(srcPath, destPath);
617
- console.log(` ✓ ${entry.name}`);
618
- } else if (ext === '.jux') {
619
- // Silently skip .jux files
620
- continue;
621
- } else {
622
- // Warn about unknown file types
623
- console.warn(` ⚠️ Skipped: ${entry.name} (unknown type: ${ext})`);
624
- }
625
- }
626
- }
627
- };
628
-
629
- copyRecursive(publicPath, distDir);
630
- }
631
-
632
- /**
633
- * Main build pipeline
380
+ * ✅ Generate route path from file path
381
+ * Examples:
382
+ * index.jux -> /
383
+ * abc/juxabc.jux -> /abc/juxabc
384
+ * abc/index.jux -> /abc
634
385
  */
635
- async build() {
636
- console.log('🔨 Building JUX project...\n');
637
-
638
- try {
639
- // 1. Clean dist
640
- if (fs.existsSync(this.config.distDir)) {
641
- fs.rmSync(this.config.distDir, { recursive: true, force: true });
642
- }
643
- fs.mkdirSync(this.config.distDir, { recursive: true });
644
-
645
- // 2. Scan .jux files
646
- const juxFiles = this.scanJuxFiles(this.config.srcDir);
647
- console.log(`📂 Found ${juxFiles.length} .jux files\n`);
648
-
649
- // 3. ✅ Bundle all .jux files → entry.js
650
- await this.bundleJuxFiles(juxFiles);
651
-
652
- // 4. ✅ Bundle vendor libraries → bundle.js
653
- await this.bundleVendorFiles();
654
-
655
- // 5. Copy public assets (CSS, images, etc.)
656
- await this.copyPublicAssets();
657
-
658
- // 6. Generate index.html (with entry.js + bundle.js)
659
- await this.generateIndexHtml();
660
-
661
- console.log('\n✅ Build completed successfully!\n');
662
- console.log(`📁 Output: ${this.config.distDir}`);
663
- console.log(` ✓ entry.js (${juxFiles.length} .jux files bundled)`);
664
- console.log(` ✓ bundle.js (vendor libraries)`);
665
- console.log(` ✓ index.html`);
666
- console.log(` ✓ Public assets copied\n`);
386
+ _generateRoutePath(filePath) {
387
+ // Normalize separators
388
+ const normalized = filePath.replace(/\\/g, '/');
667
389
 
668
- return { success: true };
390
+ // Remove extension
391
+ const withoutExt = normalized.replace(/\.[^/.]+$/, '');
669
392
 
670
- } catch (error) {
671
- console.error('\n❌ Build failed:', error.message);
672
- console.error(error.stack);
673
- return { success: false, error };
393
+ // Handle index files
394
+ if (withoutExt === 'index') {
395
+ return '/';
674
396
  }
675
- }
676
-
677
- /**
678
- * Scan for .jux files ONLY in source directory
679
- */
680
- scanJuxFiles(dir) {
681
- const juxFiles = [];
682
-
683
- const scan = (currentDir) => {
684
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
685
-
686
- for (const entry of entries) {
687
- const fullPath = path.join(currentDir, entry.name);
688
-
689
- if (entry.isDirectory()) {
690
- scan(fullPath);
691
- } else if (entry.name.endsWith('.jux')) {
692
- juxFiles.push(fullPath);
693
- }
694
- }
695
- };
696
-
697
- scan(dir);
698
- return juxFiles;
699
- }
700
397
 
701
- /**
702
- * Compile a single .jux file to .js
703
- */
704
- async compileFile(juxFilePath) {
705
- const relativePath = path.relative(this.config.srcDir, juxFilePath);
706
- const outputPath = path.join(this.config.distDir, relativePath.replace(/\.jux$/, '.js'));
707
-
708
- // Ensure output directory exists
709
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
710
-
711
- // Read .jux source
712
- const juxCode = fs.readFileSync(juxFilePath, 'utf8');
713
-
714
- // Compile to JavaScript
715
- const jsCode = this.transformJuxToJs(juxCode);
716
-
717
- // Write compiled .js file
718
- fs.writeFileSync(outputPath, jsCode, 'utf8');
719
-
720
- console.log(` ✓ ${relativePath} → ${path.basename(outputPath)}`);
721
- }
398
+ // Remove trailing /index
399
+ const cleaned = withoutExt.replace(/\/index$/, '');
722
400
 
723
- /**
724
- * Generate name from folder structure
725
- * Example: abc/juxabc.jux -> abc_juxabc
726
- */
727
- _generateNameFromPath(path) {
728
- return path
729
- .replace(/\.[^/.]+$/, '') // Remove extension
730
- .replace(/\\/g, '/') // Normalize Windows paths
731
- .replace(/\//g, '_') // Folder separator -> underscore
732
- .replace(/[^a-zA-Z0-9_]/g, '_'); // Sanitize
401
+ // Ensure leading slash
402
+ return '/' + cleaned.toLowerCase();
733
403
  }
734
404
 
735
405
  /**
@@ -754,36 +424,18 @@ navigate(location.pathname);
754
424
  const sourceSnapshot = {};
755
425
 
756
426
  const juxImports = new Set();
757
- const layoutImports = new Set(); // ✅ Track layout imports separately
758
-
759
- // Scan for imports
760
427
  [...views, ...dataModules, ...sharedModules].forEach(m => {
761
- // Regular juxscript imports
762
428
  for (const match of m.content.matchAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]juxscript['"]/g)) {
763
429
  match[1].split(',').map(s => s.trim()).forEach(imp => {
764
- if (imp) {
765
- // ✅ Separate layout imports
766
- if (imp === 'VStack' || imp === 'HStack' || imp === 'ZStack') {
767
- layoutImports.add(imp);
768
- } else {
769
- juxImports.add(imp);
770
- }
771
- }
430
+ if (imp) juxImports.add(imp);
772
431
  });
773
432
  }
774
433
  });
775
434
 
776
- // ✅ Import layouts separately
777
- if (layoutImports.size > 0) {
778
- entry += `import { ${[...layoutImports].sort().join(', ')} } from 'juxscript';\n`;
779
- }
780
-
781
- // ✅ Import regular components
782
435
  if (juxImports.size > 0) {
783
436
  entry += `import { ${[...juxImports].sort().join(', ')} } from 'juxscript';\n\n`;
784
437
  }
785
438
 
786
- // Data and shared modules
787
439
  dataModules.forEach(m => {
788
440
  entry += `import * as ${this.sanitizeName(m.name)}Data from './jux/${m.file}';\n`;
789
441
  });
@@ -795,18 +447,8 @@ navigate(location.pathname);
795
447
  dataModules.forEach(m => entry += `Object.assign(window, ${this.sanitizeName(m.name)}Data);\n`);
796
448
  sharedModules.forEach(m => entry += `Object.assign(window, ${this.sanitizeName(m.name)}Shared);\n`);
797
449
 
798
- // ✅ Expose layouts to window
799
- if (layoutImports.size > 0) {
800
- entry += `\n// Expose layout components\n`;
801
- layoutImports.forEach(layout => {
802
- entry += `window.${layout} = ${layout};\n`;
803
- });
804
- }
805
-
806
- // ✅ Expose regular components
807
450
  if (juxImports.size > 0) {
808
- entry += `\n// Expose components\n`;
809
- entry += `Object.assign(window, { ${[...juxImports].join(', ')} });\n`;
451
+ entry += `\nObject.assign(window, { ${[...juxImports].join(', ')} });\n`;
810
452
  }
811
453
 
812
454
  entry += `\n// --- VIEW FUNCTIONS ---\n`;
@@ -842,25 +484,34 @@ navigate(location.pathname);
842
484
  }
843
485
 
844
486
  reportValidationIssues() {
845
- if (!this._validationIssues || this._validationIssues.length === 0) return;
487
+ const issues = this._validationIssues || [];
488
+ const errors = issues.filter(i => i.type === 'error');
489
+ const warnings = issues.filter(i => i.type === 'warning');
490
+
491
+ if (issues.length > 0) {
492
+ console.log('\n⚠️ Validation Issues:\n');
493
+ issues.forEach(issue => {
494
+ const icon = issue.type === 'error' ? '❌' : '⚠️';
495
+ console.log(`${icon} [${issue.view}:${issue.line}] ${issue.message}`);
496
+ });
497
+ console.log('');
498
+ }
846
499
 
847
- console.log('\n⚠️ Validation Issues:\n');
848
- this._validationIssues.forEach(issue => {
849
- const icon = issue.type === 'error' ? '❌' : '⚠️';
850
- console.log(` ${icon} ${issue.view}:${issue.line} - ${issue.message}`);
851
- });
852
- console.log('');
500
+ return { isValid: errors.length === 0, errors, warnings };
853
501
  }
854
502
 
855
- /**
856
- * ✅ Generate routes based on folder structure (SINGLE DEFINITION)
857
- */
858
503
  _generateRouter(views) {
859
504
  let routeMap = '';
860
505
 
861
506
  views.forEach(v => {
507
+ // ✅ Generate route from folder structure
508
+ // abc/juxabc.jux -> /abc/juxabc
509
+ // index.jux -> /
510
+ // abc/index.jux -> /abc
511
+
862
512
  const routePath = this._generateRoutePath(v.file);
863
513
  const functionName = this._generateFunctionName(v.name);
514
+
864
515
  routeMap += ` '${routePath}': render${functionName},\n`;
865
516
  });
866
517
 
@@ -992,8 +643,7 @@ window.addEventListener('error', function(e) { __juxErrorOverlay.show(e.error ||
992
643
  window.addEventListener('unhandledrejection', function(e) { __juxErrorOverlay.show(e.reason || new Error('Promise rejected'), 'Unhandled Promise Rejection'); }, true);
993
644
 
994
645
  // --- JUX ROUTER ---
995
- const routes = {
996
- ${routeMap}};
646
+ const routes = {\n${routeMap}};
997
647
 
998
648
  async function navigate(path) {
999
649
  const view = routes[path];
@@ -1022,218 +672,81 @@ navigate(location.pathname);
1022
672
  }
1023
673
 
1024
674
  /**
1025
- * Copy source files to dist/jux for esbuild to resolve
675
+ * Copy public folder contents to dist
1026
676
  */
1027
- _copySourceToDist() {
1028
- const distJuxDir = path.join(this.distDir, 'jux');
1029
-
1030
- // Ensure dist/jux directory exists
1031
- if (!fs.existsSync(distJuxDir)) {
1032
- fs.mkdirSync(distJuxDir, { recursive: true });
677
+ copyPublicFolder() {
678
+ // Use configured public path or resolve from paths object
679
+ const publicSrc = this.paths.public
680
+ ? this.paths.public
681
+ : path.resolve(process.cwd(), this.publicDir);
682
+
683
+ if (!fs.existsSync(publicSrc)) {
684
+ return; // No public folder, skip
1033
685
  }
1034
686
 
1035
- // Copy all .jux and .js files from source to dist
1036
- this._copySourceFilesRecursive(this.srcDir, this.srcDir, distJuxDir);
687
+ console.log('📦 Copying public assets...');
688
+
689
+ try {
690
+ this._copyDirRecursive(publicSrc, this.distDir, 0);
691
+ console.log('✅ Public assets copied');
692
+ } catch (err) {
693
+ console.warn('⚠️ Error copying public folder:', err.message);
694
+ }
1037
695
  }
1038
696
 
1039
697
  /**
1040
- * Recursively copy .jux and .js files preserving folder structure
698
+ * Recursively copy directory contents
1041
699
  */
1042
- _copySourceFilesRecursive(src, baseDir, distBase) {
700
+ _copyDirRecursive(src, dest, depth = 0) {
1043
701
  const entries = fs.readdirSync(src, { withFileTypes: true });
1044
702
 
1045
703
  entries.forEach(entry => {
1046
- if (entry.name.startsWith('.')) return; // Skip hidden
704
+ // Skip hidden files and directories
705
+ if (entry.name.startsWith('.')) return;
1047
706
 
1048
707
  const srcPath = path.join(src, entry.name);
1049
- const relativePath = path.relative(baseDir, srcPath);
1050
- const destPath = path.join(distBase, relativePath);
708
+ const destPath = path.join(dest, entry.name);
1051
709
 
1052
710
  if (entry.isDirectory()) {
1053
- // Create directory if it doesn't exist
711
+ // Create directory and recurse
1054
712
  if (!fs.existsSync(destPath)) {
1055
713
  fs.mkdirSync(destPath, { recursive: true });
1056
714
  }
1057
- // Recurse into subdirectory
1058
- this._copySourceFilesRecursive(srcPath, baseDir, distBase);
1059
- } else if (entry.name.endsWith('.jux') || entry.name.endsWith('.js')) {
1060
- // Copy .jux and .js files (skip assets)
1061
- if (!this.isAssetFile(entry.name)) {
1062
- // Ensure parent directory exists
1063
- const destDir = path.dirname(destPath);
1064
- if (!fs.existsSync(destDir)) {
1065
- fs.mkdirSync(destDir, { recursive: true });
1066
- }
1067
- fs.copyFileSync(srcPath, destPath);
1068
- console.log(` 📋 Copied: ${relativePath}`);
715
+ this._copyDirRecursive(srcPath, destPath, depth + 1);
716
+ } else {
717
+ // Copy file
718
+ fs.copyFileSync(srcPath, destPath);
719
+
720
+ // Log files at root level only
721
+ if (depth === 0) {
722
+ const ext = path.extname(entry.name);
723
+ const icon = this._getFileIcon(ext);
724
+ console.log(` ${icon} ${entry.name}`);
1069
725
  }
1070
726
  }
1071
727
  });
1072
728
  }
1073
729
 
1074
730
  /**
1075
- * Copy ONLY public assets (CSS, images, fonts) to dist
1076
- * ❌ Does NOT copy .jux files (they are compiled separately)
731
+ * Get icon for file type
1077
732
  */
1078
- async copyPublicAssets() {
1079
- const { publicDir, distDir, paths } = this.config;
1080
-
1081
- // Resolve public folder path
1082
- const publicPath = paths?.public || path.resolve(this.config.srcDir, '..', publicDir);
1083
-
1084
- if (!fs.existsSync(publicPath)) {
1085
- console.log(`ℹ️ No public folder found at ${publicPath}, skipping asset copy`);
1086
- return;
1087
- }
1088
-
1089
- console.log(`📦 Copying public assets from ${publicPath}...`);
1090
-
1091
- // ✅ ONLY copy known asset file types
1092
- const assetTypes = [
1093
- '.css', '.scss', '.sass', '.less', // Stylesheets
1094
- '.jpg', '.jpeg', '.png', '.gif', '.svg', // Images
1095
- '.ico', '.webp', '.avif', // Icons/modern images
1096
- '.woff', '.woff2', '.ttf', '.eot', '.otf', // Fonts
1097
- '.mp4', '.webm', '.ogg', // Video
1098
- '.mp3', '.wav', '.m4a', // Audio
1099
- '.pdf', '.txt', '.json', '.xml' // Documents
1100
- ];
1101
-
1102
- const copyRecursive = (src, dest) => {
1103
- if (!fs.existsSync(src)) return;
1104
-
1105
- const entries = fs.readdirSync(src, { withFileTypes: true });
1106
-
1107
- for (const entry of entries) {
1108
- const srcPath = path.join(src, entry.name);
1109
- const destPath = path.join(dest, entry.name);
1110
-
1111
- if (entry.isDirectory()) {
1112
- fs.mkdirSync(destPath, { recursive: true });
1113
- copyRecursive(srcPath, destPath);
1114
- } else {
1115
- const ext = path.extname(entry.name).toLowerCase();
1116
-
1117
- // ✅ ONLY copy known asset types
1118
- // ❌ SKIP .jux files (they're bundled into entry.js)
1119
- if (assetTypes.includes(ext)) {
1120
- fs.copyFileSync(srcPath, destPath);
1121
- console.log(` ✓ ${entry.name}`);
1122
- } else if (ext === '.jux') {
1123
- // Silently skip .jux files
1124
- continue;
1125
- } else {
1126
- // Warn about unknown file types
1127
- console.warn(` ⚠️ Skipped: ${entry.name} (unknown type: ${ext})`);
1128
- }
1129
- }
1130
- }
733
+ _getFileIcon(ext) {
734
+ const icons = {
735
+ '.html': '📄',
736
+ '.css': '🎨',
737
+ '.js': '📜',
738
+ '.json': '📋',
739
+ '.png': '🖼️',
740
+ '.jpg': '🖼️',
741
+ '.jpeg': '🖼️',
742
+ '.gif': '🖼️',
743
+ '.svg': '🎨',
744
+ '.ico': '🔖',
745
+ '.woff': '🔤',
746
+ '.woff2': '🔤',
747
+ '.ttf': '🔤',
748
+ '.eot': '🔤'
1131
749
  };
1132
-
1133
- copyRecursive(publicPath, distDir);
1134
- }
1135
-
1136
- /**
1137
- * Main build pipeline
1138
- */
1139
- async build() {
1140
- console.log('🔨 Building JUX project...\n');
1141
-
1142
- try {
1143
- // 1. Clean dist
1144
- if (fs.existsSync(this.config.distDir)) {
1145
- fs.rmSync(this.config.distDir, { recursive: true, force: true });
1146
- }
1147
- fs.mkdirSync(this.config.distDir, { recursive: true });
1148
-
1149
- // 2. Scan .jux files
1150
- const juxFiles = this.scanJuxFiles(this.config.srcDir);
1151
- console.log(`📂 Found ${juxFiles.length} .jux files\n`);
1152
-
1153
- // 3. ✅ Bundle all .jux files → entry.js
1154
- await this.bundleJuxFiles(juxFiles);
1155
-
1156
- // 4. ✅ Bundle vendor libraries → bundle.js
1157
- await this.bundleVendorFiles();
1158
-
1159
- // 5. Copy public assets (CSS, images, etc.)
1160
- await this.copyPublicAssets();
1161
-
1162
- // 6. Generate index.html (with entry.js + bundle.js)
1163
- await this.generateIndexHtml();
1164
-
1165
- console.log('\n✅ Build completed successfully!\n');
1166
- console.log(`📁 Output: ${this.config.distDir}`);
1167
- console.log(` ✓ entry.js (${juxFiles.length} .jux files bundled)`);
1168
- console.log(` ✓ bundle.js (vendor libraries)`);
1169
- console.log(` ✓ index.html`);
1170
- console.log(` ✓ Public assets copied\n`);
1171
-
1172
- return { success: true };
1173
-
1174
- } catch (error) {
1175
- console.error('\n❌ Build failed:', error.message);
1176
- console.error(error.stack);
1177
- return { success: false, error };
1178
- }
1179
- }
1180
-
1181
- /**
1182
- * Scan for .jux files ONLY in source directory
1183
- */
1184
- scanJuxFiles(dir) {
1185
- const juxFiles = [];
1186
-
1187
- const scan = (currentDir) => {
1188
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
1189
-
1190
- for (const entry of entries) {
1191
- const fullPath = path.join(currentDir, entry.name);
1192
-
1193
- if (entry.isDirectory()) {
1194
- scan(fullPath);
1195
- } else if (entry.name.endsWith('.jux')) {
1196
- juxFiles.push(fullPath);
1197
- }
1198
- }
1199
- };
1200
-
1201
- scan(dir);
1202
- return juxFiles;
1203
- }
1204
-
1205
- /**
1206
- * Compile a single .jux file to .js
1207
- */
1208
- async compileFile(juxFilePath) {
1209
- const relativePath = path.relative(this.config.srcDir, juxFilePath);
1210
- const outputPath = path.join(this.config.distDir, relativePath.replace(/\.jux$/, '.js'));
1211
-
1212
- // Ensure output directory exists
1213
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
1214
-
1215
- // Read .jux source
1216
- const juxCode = fs.readFileSync(juxFilePath, 'utf8');
1217
-
1218
- // Compile to JavaScript
1219
- const jsCode = this.transformJuxToJs(juxCode);
1220
-
1221
- // Write compiled .js file
1222
- fs.writeFileSync(outputPath, jsCode, 'utf8');
1223
-
1224
- console.log(` ✓ ${relativePath} → ${path.basename(outputPath)}`);
1225
- }
1226
-
1227
- /**
1228
- * ✅ Generate name from folder structure
1229
- * Example: abc/juxabc.jux -> abc_juxabc
1230
- */
1231
- _generateNameFromPath(filepath) {
1232
- // Convert file path to module name
1233
- // e.g., "pages/about.jux" → "pages_about"
1234
- return filepath
1235
- .replace(/\.jux$/, '')
1236
- .replace(/[\/\\]/g, '_')
1237
- .replace(/[^a-zA-Z0-9_]/g, '');
750
+ return icons[ext.toLowerCase()] || '📦';
1238
751
  }
1239
752
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.117",
3
+ "version": "1.1.118",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",