slicejs-cli 2.9.0 → 2.9.2

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.
@@ -467,7 +467,7 @@ export default class BundleGenerator {
467
467
  'critical',
468
468
  criticalFile.file
469
469
  );
470
- this.bundles.critical.integrity = criticalIntegrity;
470
+ this.bundles.critical.integrity = `sha256:${criticalFile.hash}`;
471
471
  this.bundles.critical.hash = criticalFile.hash;
472
472
  files.push(criticalFile);
473
473
  }
@@ -483,13 +483,7 @@ export default class BundleGenerator {
483
483
  'route',
484
484
  routeIdentifier
485
485
  );
486
- const routeIntegrity = this.computeBundleIntegrity(
487
- bundle.components,
488
- 'route',
489
- routeIdentifier,
490
- this.routeToFileName(routeIdentifier),
491
- routeFile.file
492
- );
486
+ const routeIntegrity = `sha256:${routeFile.hash}`;
493
487
  const matchingBundle = Object.values(this.bundles.routes)
494
488
  .find((entry) => entry.file === routeFile.file);
495
489
  if (matchingBundle) {
@@ -540,7 +534,11 @@ export default class BundleGenerator {
540
534
  return bundleContent;
541
535
  }
542
536
 
543
- const result = await terserMinify(bundleContent, {
537
+ const options = {
538
+ parse: {
539
+ ecma: 2022
540
+ },
541
+ ecma: 2022,
544
542
  compress: this.options.minify ? {
545
543
  drop_console: false,
546
544
  drop_debugger: true,
@@ -552,12 +550,39 @@ export default class BundleGenerator {
552
550
  keep_fnames: true,
553
551
  keep_classnames: true,
554
552
  format: {
555
- comments: false
553
+ comments: false,
554
+ ecma: 2022
556
555
  }
557
- });
556
+ };
557
+
558
+ let result;
559
+ try {
560
+ result = await terserMinify(bundleContent, options);
561
+ } catch (error) {
562
+ const tmpDir = path.resolve(process.cwd(), '.tmp');
563
+ const safeName = fileName.replace(/[^a-zA-Z0-9_.-]/g, '_');
564
+ const tmpPath = path.join(tmpDir, `terser-fail-${safeName}`);
565
+ try {
566
+ await fs.ensureDir(tmpDir);
567
+ await fs.writeFile(tmpPath, bundleContent, 'utf-8');
568
+ } catch (writeError) {
569
+ console.warn(`Warning: Failed to write ${tmpPath}:`, writeError.message);
570
+ }
571
+ const message = error?.message ? `${error.message}.` : 'Unknown Terser error.';
572
+ throw new Error(`Terser failed for ${fileName}: ${message} Saved bundle to ${tmpPath}`);
573
+ }
558
574
 
559
575
  if (result.error) {
560
- throw new Error(`Terser failed for ${fileName}: ${result.error.message}`);
576
+ const tmpDir = path.resolve(process.cwd(), '.tmp');
577
+ const safeName = fileName.replace(/[^a-zA-Z0-9_.-]/g, '_');
578
+ const tmpPath = path.join(tmpDir, `terser-fail-${safeName}`);
579
+ try {
580
+ await fs.ensureDir(tmpDir);
581
+ await fs.writeFile(tmpPath, bundleContent, 'utf-8');
582
+ } catch (writeError) {
583
+ console.warn(`Warning: Failed to write ${tmpPath}:`, writeError.message);
584
+ }
585
+ throw new Error(`Terser failed for ${fileName}: ${result.error.message}. Saved bundle to ${tmpPath}`);
561
586
  }
562
587
 
563
588
  return result.code || bundleContent;
@@ -737,11 +762,30 @@ export default class BundleGenerator {
737
762
  // Remove imports (components will already be available)
738
763
  code = code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, '');
739
764
 
765
+ // Guard customElements.define to avoid duplicate registrations
766
+ code = code.replace(
767
+ /customElements\.define\(([^)]+)\);?/g,
768
+ (match, args) => {
769
+ const firstArg = args.split(',')[0]?.trim() || '';
770
+ if (!/^['"][^'"]+['"]$/.test(firstArg)) {
771
+ return match;
772
+ }
773
+ return `if (!customElements.get(${firstArg})) { customElements.define(${args}); }`;
774
+ }
775
+ );
776
+
740
777
  // Make sure the class is available globally for bundle evaluation
741
778
  // Preserve original customElements.define if it exists
742
779
  if (code.includes('customElements.define')) {
743
- // Add global assignment before customElements.define
744
- code = code.replace(/customElements\.define\([^;]+\);?\s*$/, `window.${componentName} = ${componentName};\n$&`);
780
+ // Add global assignment before guarded or direct customElements.define
781
+ const globalAssignment = `window.${componentName} = ${componentName};\n`;
782
+ const guardedDefineRegex = /if\s*\(\s*!\s*customElements\.get\([^)]*\)\s*\)\s*\{\s*customElements\.define\([^;]+\);?\s*\}\s*$/;
783
+ const directDefineRegex = /customElements\.define\([^;]+\);?\s*$/;
784
+ if (guardedDefineRegex.test(code)) {
785
+ code = code.replace(guardedDefineRegex, `${globalAssignment}$&`);
786
+ } else {
787
+ code = code.replace(directDefineRegex, `${globalAssignment}$&`);
788
+ }
745
789
  } else {
746
790
  // If no customElements.define found, just assign to global
747
791
  code += `\nwindow.${componentName} = ${componentName};`;
@@ -1072,7 +1116,7 @@ if (window.slice && window.slice.controller) {
1072
1116
  await fs.writeFile(filePath, finalContent, 'utf-8');
1073
1117
 
1074
1118
  const hash = crypto.createHash('sha256').update(finalContent).digest('hex');
1075
- const integrity = this.computeBundleIntegrity(components, 'framework', null, 'framework', fileName);
1119
+ const integrity = `sha256:${hash}`;
1076
1120
 
1077
1121
  return {
1078
1122
  name: 'framework',
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
3
3
  import path from 'path';
4
4
  import { parse } from '@babel/parser';
5
5
  import traverse from '@babel/traverse';
6
- import { getSrcPath, getComponentsJsPath, getProjectRoot } from '../PathHelper.js';
6
+ import { getSrcPath, getComponentsJsPath, getProjectRoot, getConfigPath } from '../PathHelper.js';
7
7
 
8
8
  export default class DependencyAnalyzer {
9
9
  constructor(moduleUrl) {
@@ -64,11 +64,21 @@ export default class DependencyAnalyzer {
64
64
  */
65
65
  async loadComponentsConfig() {
66
66
  const componentsConfigPath = path.join(this.componentsPath, 'components.js');
67
+ const configPath = getConfigPath(this.moduleUrl);
68
+ let sliceConfig = {};
67
69
 
68
70
  if (!await fs.pathExists(componentsConfigPath)) {
69
71
  throw new Error('components.js not found');
70
72
  }
71
73
 
74
+ if (await fs.pathExists(configPath)) {
75
+ try {
76
+ sliceConfig = await fs.readJson(configPath);
77
+ } catch (error) {
78
+ console.warn('Warning: Could not read sliceConfig.json for component paths:', error.message);
79
+ }
80
+ }
81
+
72
82
  // Read and parse components.js
73
83
  const content = await fs.readFile(componentsConfigPath, 'utf-8');
74
84
 
@@ -87,13 +97,18 @@ export default class DependencyAnalyzer {
87
97
 
88
98
  // Process each category
89
99
  for (const [categoryName, componentList] of categoryMap) {
90
- // Determine category type based on category name
91
- let categoryType = 'Visual'; // default
100
+ const configCategory = sliceConfig?.paths?.components?.[categoryName];
101
+
102
+ // Determine category type based on config or category name
103
+ let categoryType = configCategory?.type || 'Visual';
92
104
  if (categoryName === 'Service') categoryType = 'Service';
93
105
  if (categoryName === 'AppComponents') categoryType = 'Visual'; // AppComponents are visual
94
106
 
95
- // Find category path
96
- const categoryPath = path.join(this.componentsPath, categoryName);
107
+ // Resolve category path from config if available
108
+ let categoryPath = path.join(this.componentsPath, categoryName);
109
+ if (configCategory?.path) {
110
+ categoryPath = getSrcPath(this.moduleUrl, configCategory.path);
111
+ }
97
112
 
98
113
  if (await fs.pathExists(categoryPath)) {
99
114
  const files = await fs.readdir(categoryPath);
@@ -330,6 +345,17 @@ export default class DependencyAnalyzer {
330
345
  return node;
331
346
  }
332
347
 
348
+ if (node.type === 'CallExpression') {
349
+ const calleeName = node.callee?.name || null;
350
+ if (calleeName && node.arguments?.length) {
351
+ const firstArg = node.arguments[0];
352
+ const resolvedObject = resolveObjectExpression(firstArg, scope);
353
+ if (resolvedObject) {
354
+ return resolvedObject;
355
+ }
356
+ }
357
+ }
358
+
333
359
  if (node.type === 'ObjectExpression') {
334
360
  const routesProp = node.properties.find(p => p.key?.name === 'routes');
335
361
  if (routesProp?.value) {
@@ -348,6 +374,10 @@ export default class DependencyAnalyzer {
348
374
  return init;
349
375
  }
350
376
 
377
+ if (init?.type === 'CallExpression') {
378
+ return resolveRoutesArray(init, binding.path.scope);
379
+ }
380
+
351
381
  if (init?.type === 'Identifier') {
352
382
  return resolveRoutesArray(init, binding.path.scope);
353
383
  }
@@ -562,8 +592,46 @@ export default class DependencyAnalyzer {
562
592
  return null;
563
593
  };
564
594
 
595
+ const addRouteConfigDependencies = (routesConfigNode, scope) => {
596
+ if (!routesConfigNode || routesConfigNode.type !== 'ObjectExpression') return;
597
+
598
+ const processObject = (node) => {
599
+ if (!node || node.type !== 'ObjectExpression') return;
600
+
601
+ const componentProp = node.properties.find(p => p.key?.name === 'component');
602
+ if (componentProp?.value) {
603
+ const componentName = resolveStringValue(componentProp.value, scope);
604
+ if (componentName) {
605
+ dependencies.add(componentName);
606
+ }
607
+ }
608
+
609
+ const itemsProp = node.properties.find(p => p.key?.name === 'items');
610
+ if (itemsProp?.value) {
611
+ const itemsNode = resolveRoutesArray(itemsProp.value, scope);
612
+ addMultiRouteDependencies(itemsNode, scope);
613
+ }
614
+
615
+ node.properties.forEach((prop) => {
616
+ const valueNode = prop.value;
617
+ if (valueNode?.type === 'ObjectExpression') {
618
+ processObject(valueNode);
619
+ }
620
+ });
621
+ };
622
+
623
+ processObject(routesConfigNode);
624
+ };
625
+
565
626
  const addMultiRouteDependencies = (routesArrayNode, scope) => {
566
- if (!routesArrayNode || routesArrayNode.type !== 'ArrayExpression') return;
627
+ if (!routesArrayNode) return;
628
+
629
+ if (routesArrayNode.type === 'ObjectExpression') {
630
+ addRouteConfigDependencies(routesArrayNode, scope);
631
+ return;
632
+ }
633
+
634
+ if (routesArrayNode.type !== 'ArrayExpression') return;
567
635
 
568
636
  routesArrayNode.elements.forEach(routeElement => {
569
637
  if (!routeElement) return;
@@ -583,6 +651,12 @@ export default class DependencyAnalyzer {
583
651
  dependencies.add(componentName);
584
652
  }
585
653
  }
654
+
655
+ const itemsProp = routeObject.properties.find(p => p.key?.name === 'items');
656
+ if (itemsProp?.value) {
657
+ const itemsNode = resolveRoutesArray(itemsProp.value, scope);
658
+ addMultiRouteDependencies(itemsNode, scope);
659
+ }
586
660
  }
587
661
  });
588
662
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-cli",
3
- "version": "2.9.0",
3
+ "version": "2.9.2",
4
4
  "description": "Command client for developing web applications with Slice.js framework",
5
5
  "main": "client.js",
6
6
  "bin": {