slicejs-cli 2.8.0 → 2.8.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.
@@ -48,29 +48,29 @@ export default async function initializeProject(projectType) {
48
48
  const srcDir = path.join(sliceBaseDir, 'src');
49
49
 
50
50
  try {
51
- if (fs.existsSync(destinationApi)) throw new Error(`The "api" directory already exists: ${destinationApi}`);
52
- if (fs.existsSync(destinationSrc)) throw new Error(`The "src" directory already exists: ${destinationSrc}`);
53
- } catch (error) {
54
- Print.error('Validating destination directories:', error.message);
55
- return;
56
- }
51
+ if (fs.existsSync(destinationApi)) throw new Error(`The "api" directory already exists: ${destinationApi}`);
52
+ if (fs.existsSync(destinationSrc)) throw new Error(`The "src" directory already exists: ${destinationSrc}`);
53
+ } catch (error) {
54
+ Print.error('Validating destination directories:', error.message);
55
+ return;
56
+ }
57
57
 
58
58
  // 1. COPIAR LA CARPETA API (mantener lógica original)
59
- const apiSpinner = ora('Copying API structure...').start();
60
- try {
61
- if (!fs.existsSync(apiDir)) throw new Error(`API folder not found: ${apiDir}`);
62
- await fs.copy(apiDir, destinationApi, { recursive: true });
63
- apiSpinner.succeed('API structure created successfully');
64
- } catch (error) {
65
- apiSpinner.fail('Error copying API structure');
66
- Print.error(error.message);
67
- return;
68
- }
59
+ const apiSpinner = ora('Copying API structure...').start();
60
+ try {
61
+ if (!fs.existsSync(apiDir)) throw new Error(`API folder not found: ${apiDir}`);
62
+ await fs.copy(apiDir, destinationApi, { recursive: true });
63
+ apiSpinner.succeed('API structure created successfully');
64
+ } catch (error) {
65
+ apiSpinner.fail('Error copying API structure');
66
+ Print.error(error.message);
67
+ return;
68
+ }
69
69
 
70
70
  // 2. CREAR ESTRUCTURA SRC BÁSICA (sin copiar componentes Visual)
71
- const srcSpinner = ora('Creating src structure...').start();
72
- try {
73
- if (!fs.existsSync(srcDir)) throw new Error(`src folder not found: ${srcDir}`);
71
+ const srcSpinner = ora('Creating src structure...').start();
72
+ try {
73
+ if (!fs.existsSync(srcDir)) throw new Error(`src folder not found: ${srcDir}`);
74
74
 
75
75
  // Copiar solo los archivos base de src, excluyendo Components/Visual
76
76
  await fs.ensureDir(destinationSrc);
@@ -94,22 +94,22 @@ export default async function initializeProject(projectType) {
94
94
  const destComponentItemPath = path.join(destItemPath, componentItem);
95
95
 
96
96
  if (componentItem !== 'Visual') {
97
- // Copy Service and other component types
98
- await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
99
- } else {
100
- // Only create empty Visual directory
101
- await fs.ensureDir(destComponentItemPath);
102
- }
103
- }
104
- } else {
105
- // Copy other folders normally
106
- await fs.copy(srcItemPath, destItemPath, { recursive: true });
107
- }
108
- } else {
109
- // Copy files normally
110
- await fs.copy(srcItemPath, destItemPath);
111
- }
112
- }
97
+ // Copy Service and other component types
98
+ await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
99
+ } else {
100
+ // Only create empty Visual directory
101
+ await fs.ensureDir(destComponentItemPath);
102
+ }
103
+ }
104
+ } else {
105
+ // Copy other folders normally
106
+ await fs.copy(srcItemPath, destItemPath, { recursive: true });
107
+ }
108
+ } else {
109
+ // Copy files normally
110
+ await fs.copy(srcItemPath, destItemPath);
111
+ }
112
+ }
113
113
 
114
114
  srcSpinner.succeed('Source structure created successfully');
115
115
  } catch (error) {
@@ -142,15 +142,15 @@ export default async function initializeProject(projectType) {
142
142
  if (successful > 0 && failed === 0) {
143
143
  componentsSpinner.succeed(`All ${successful} Visual components installed successfully`);
144
144
  } else if (successful > 0) {
145
- componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
146
- Print.info('You can install failed components later using "slice get <component-name>"');
147
- } else {
148
- componentsSpinner.fail('Failed to install Visual components');
149
- }
150
- } else {
151
- componentsSpinner.warn('No Visual components found in registry');
152
- Print.info('You can add components later using "slice get <component-name>"');
153
- }
145
+ componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
146
+ Print.info('You can install failed components later using "slice get <component-name>"');
147
+ } else {
148
+ componentsSpinner.fail('Failed to install Visual components');
149
+ }
150
+ } else {
151
+ componentsSpinner.warn('No Visual components found in registry');
152
+ Print.info('You can add components later using "slice get <component-name>"');
153
+ }
154
154
 
155
155
  } catch (error) {
156
156
  componentsSpinner.fail('Could not download Visual components from official repository');
@@ -222,21 +222,21 @@ export default async function initializeProject(projectType) {
222
222
  console.log(' npm run get - Install components');
223
223
  console.log(' npm run browse - Browse components');
224
224
  } catch (error) {
225
- pkgSpinner.fail('Failed to configure npm scripts');
226
- Print.error(error.message);
227
- }
225
+ pkgSpinner.fail('Failed to configure npm scripts');
226
+ Print.error(error.message);
227
+ }
228
228
 
229
- Print.success('Project initialized successfully.');
230
- Print.newLine();
231
- Print.info('Next steps:');
232
- console.log(' slice browse - View available components');
233
- console.log(' slice get Button - Install specific components');
234
- console.log(' slice sync - Update all components to latest versions');
229
+ Print.success('Project initialized successfully.');
230
+ Print.newLine();
231
+ Print.info('Next steps:');
232
+ console.log(' slice browse - View available components');
233
+ console.log(' slice get Button - Install specific components');
234
+ console.log(' slice sync - Update all components to latest versions');
235
235
 
236
236
  } catch (error) {
237
- Print.error('Unexpected error initializing project:', error.message);
238
- }
239
- }
237
+ Print.error('Unexpected error initializing project:', error.message);
238
+ }
239
+ }
240
240
 
241
241
  /**
242
242
  * Obtiene TODOS los componentes Visual disponibles en el registry
@@ -1,68 +1,68 @@
1
- import path from 'path'
2
- import fs from 'fs-extra'
3
- import { fileURLToPath } from 'url'
4
-
5
- const sanitize = (s) => (s || '').replace(/^[/\\]+/, '')
6
- const dirOf = (url) => path.dirname(fileURLToPath(url))
7
-
8
- function candidates(moduleUrl) {
9
- const dir = dirOf(moduleUrl)
10
- return [
11
- path.join(dir, '../../'),
12
- path.join(dir, '../../../../')
13
- ]
14
- }
15
-
16
- function resolveProjectRoot(moduleUrl) {
17
- const initCwd = process.env.INIT_CWD
18
- if (initCwd && fs.pathExistsSync(initCwd)) {
19
- return initCwd
20
- }
21
-
22
- const cwd = process.cwd()
23
- if (cwd && fs.pathExistsSync(cwd)) {
24
- return cwd
25
- }
26
-
27
- const dirs = candidates(moduleUrl)
28
- for (const root of dirs) {
29
- const hasSrc = fs.pathExistsSync(path.join(root, 'src'))
30
- const hasApi = fs.pathExistsSync(path.join(root, 'api'))
31
- if (hasSrc || hasApi) return root
32
- }
33
- return dirs[1]
34
- }
35
-
36
- function joinProject(moduleUrl, ...segments) {
37
- const root = resolveProjectRoot(moduleUrl)
38
- const clean = segments.map(sanitize)
39
- return path.join(root, ...clean)
40
- }
41
-
42
- export function getProjectRoot(moduleUrl) {
43
- return resolveProjectRoot(moduleUrl)
44
- }
45
-
46
- export function getPath(moduleUrl, folder, ...segments) {
47
- return joinProject(moduleUrl, folder, ...segments)
48
- }
49
-
50
- export function getSrcPath(moduleUrl, ...segments) {
51
- return joinProject(moduleUrl, 'src', ...segments)
52
- }
53
-
54
- export function getApiPath(moduleUrl, ...segments) {
55
- return joinProject(moduleUrl, 'api', ...segments)
56
- }
57
-
58
- export function getDistPath(moduleUrl, ...segments) {
59
- return joinProject(moduleUrl, 'dist', ...segments)
60
- }
61
-
62
- export function getConfigPath(moduleUrl) {
63
- return joinProject(moduleUrl, 'src', 'sliceConfig.json')
64
- }
65
-
66
- export function getComponentsJsPath(moduleUrl) {
67
- return joinProject(moduleUrl, 'src', 'Components', 'components.js')
68
- }
1
+ import path from 'path'
2
+ import fs from 'fs-extra'
3
+ import { fileURLToPath } from 'url'
4
+
5
+ const sanitize = (s) => (s || '').replace(/^[/\\]+/, '')
6
+ const dirOf = (url) => path.dirname(fileURLToPath(url))
7
+
8
+ function candidates(moduleUrl) {
9
+ const dir = dirOf(moduleUrl)
10
+ return [
11
+ path.join(dir, '../../'),
12
+ path.join(dir, '../../../../')
13
+ ]
14
+ }
15
+
16
+ function resolveProjectRoot(moduleUrl) {
17
+ const initCwd = process.env.INIT_CWD
18
+ if (initCwd && fs.pathExistsSync(initCwd)) {
19
+ return initCwd
20
+ }
21
+
22
+ const cwd = process.cwd()
23
+ if (cwd && fs.pathExistsSync(cwd)) {
24
+ return cwd
25
+ }
26
+
27
+ const dirs = candidates(moduleUrl)
28
+ for (const root of dirs) {
29
+ const hasSrc = fs.pathExistsSync(path.join(root, 'src'))
30
+ const hasApi = fs.pathExistsSync(path.join(root, 'api'))
31
+ if (hasSrc || hasApi) return root
32
+ }
33
+ return dirs[1]
34
+ }
35
+
36
+ function joinProject(moduleUrl, ...segments) {
37
+ const root = resolveProjectRoot(moduleUrl)
38
+ const clean = segments.map(sanitize)
39
+ return path.join(root, ...clean)
40
+ }
41
+
42
+ export function getProjectRoot(moduleUrl) {
43
+ return resolveProjectRoot(moduleUrl)
44
+ }
45
+
46
+ export function getPath(moduleUrl, folder, ...segments) {
47
+ return joinProject(moduleUrl, folder, ...segments)
48
+ }
49
+
50
+ export function getSrcPath(moduleUrl, ...segments) {
51
+ return joinProject(moduleUrl, 'src', ...segments)
52
+ }
53
+
54
+ export function getApiPath(moduleUrl, ...segments) {
55
+ return joinProject(moduleUrl, 'api', ...segments)
56
+ }
57
+
58
+ export function getDistPath(moduleUrl, ...segments) {
59
+ return joinProject(moduleUrl, 'dist', ...segments)
60
+ }
61
+
62
+ export function getConfigPath(moduleUrl) {
63
+ return joinProject(moduleUrl, 'src', 'sliceConfig.json')
64
+ }
65
+
66
+ export function getComponentsJsPath(moduleUrl) {
67
+ return joinProject(moduleUrl, 'src', 'Components', 'components.js')
68
+ }
@@ -1,10 +1,10 @@
1
1
  // commands/utils/VersionChecker.js
2
2
 
3
- import fs from "fs-extra";
4
- import path from "path";
5
- import { fileURLToPath } from "url";
6
- import Print from "../Print.js";
7
- import { getProjectRoot } from "../utils/PathHelper.js";
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+ import Print from "../Print.js";
7
+ import { getProjectRoot } from "../utils/PathHelper.js";
8
8
 
9
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
 
@@ -23,9 +23,9 @@ class VersionChecker {
23
23
  const cliPackage = await fs.readJson(cliPackagePath);
24
24
  this.currentCliVersion = cliPackage.version;
25
25
 
26
- // Get Framework version from project node_modules
27
- const projectRoot = getProjectRoot(import.meta.url);
28
- const frameworkPackagePath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'package.json');
26
+ // Get Framework version from project node_modules
27
+ const projectRoot = getProjectRoot(import.meta.url);
28
+ const frameworkPackagePath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'package.json');
29
29
  if (await fs.pathExists(frameworkPackagePath)) {
30
30
  const frameworkPackage = await fs.readJson(frameworkPackagePath);
31
31
  this.currentFrameworkVersion = frameworkPackage.version;
@@ -111,7 +111,7 @@ class VersionChecker {
111
111
 
112
112
  if (!silent && (cliStatus === 'outdated' || frameworkStatus === 'outdated')) {
113
113
  console.log(''); // Line break
114
- Print.warning('📦 Available Updates:');
114
+ Print.warning('📦 Available Updates:');
115
115
 
116
116
  if (cliStatus === 'outdated') {
117
117
  console.log(` 🔧 CLI: ${current.cli} → ${latest.cli}`);
@@ -142,7 +142,7 @@ class VersionChecker {
142
142
  const current = await this.getCurrentVersions();
143
143
  const latest = await this.getLatestVersions();
144
144
 
145
- console.log('\n📋 Version Information:');
145
+ console.log('\n📋 Version Information:');
146
146
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
147
147
 
148
148
  if (current?.cli) {
@@ -164,4 +164,4 @@ class VersionChecker {
164
164
  // Singleton instance
165
165
  const versionChecker = new VersionChecker();
166
166
 
167
- export default versionChecker;
167
+ export default versionChecker;
@@ -256,7 +256,7 @@ export default class BundleGenerator {
256
256
  paths: groupData.routes,
257
257
  components: uniqueComponents,
258
258
  size: totalSize,
259
- file: `slice-bundle.${groupKey}.js`
259
+ file: `slice-bundle.${this.routeToFileName(groupKey)}.js`
260
260
  };
261
261
 
262
262
  console.log(`✓ Bundle ${groupKey}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB (${groupData.routes.length} routes)`);
@@ -313,7 +313,7 @@ export default class BundleGenerator {
313
313
  paths: routePaths,
314
314
  components: uniqueComponents,
315
315
  size: totalSize,
316
- file: `slice-bundle.${category}.js`
316
+ file: `slice-bundle.${this.routeToFileName(category)}.js`
317
317
  };
318
318
 
319
319
  console.log(`✓ Bundle ${category}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB (${routes.length} routes)`);
@@ -398,10 +398,14 @@ export default class BundleGenerator {
398
398
 
399
399
  // 2. Route bundles
400
400
  for (const [routeKey, bundle] of Object.entries(this.bundles.routes)) {
401
+ const routeIdentifier = Array.isArray(bundle.path || bundle.paths)
402
+ ? routeKey
403
+ : (bundle.path || bundle.paths || routeKey);
404
+
401
405
  const routeFile = await this.createBundleFile(
402
406
  bundle.components,
403
407
  'route',
404
- bundle.path || routeKey // Use routeKey as fallback for hybrid bundles
408
+ routeIdentifier
405
409
  );
406
410
  files.push(routeFile);
407
411
  }
@@ -524,7 +528,7 @@ export default class BundleGenerator {
524
528
  name: comp.name,
525
529
  category: comp.category,
526
530
  categoryType: comp.categoryType,
527
- js: this.cleanJavaScript(jsContent, comp.name, comp.path),
531
+ js: this.cleanJavaScript(jsContent, comp.name),
528
532
  externalDependencies: dependencyContents, // Files imported with import statements
529
533
  componentDependencies: Array.from(comp.dependencies), // Other components this one depends on
530
534
  html: htmlContent,
@@ -547,134 +551,29 @@ export default class BundleGenerator {
547
551
  }
548
552
 
549
553
  /**
550
- * Cleans JavaScript code by processing imports and ensuring class is available globally
554
+ * Cleans JavaScript code by removing imports/exports and ensuring class is available globally
551
555
  */
552
- cleanJavaScript(code, componentName, componentPath) {
553
- let newCode = code;
554
-
555
- // 1. In-line local imports to support data files and helpers
556
- // Matches: import [clause] from '[path]'
557
- const importRegex = /import\s+([\w\s{},*]+)\s+from\s+['"`]([\.\/][^'"`]+)['"`];?/g;
558
-
559
- newCode = newCode.replace(importRegex, (match, importClause, importPath) => {
560
- try {
561
- const absolutePath = path.resolve(componentPath, importPath);
562
-
563
- // Try different extensions if file doesn't exist
564
- let finalPath = absolutePath;
565
- if (!fs.existsSync(finalPath)) {
566
- if (fs.existsSync(finalPath + '.js')) finalPath += '.js';
567
- else if (fs.existsSync(finalPath + '.json')) finalPath += '.json';
568
- else {
569
- console.warn(`BundleGenerator: Could not resolve ${importPath} in ${componentName}`);
570
- return `/* Fail: File not found ${importPath} */`; // return comment so regular import stripper deletes it
571
- }
572
- }
573
-
574
- let fileContent = fs.readFileSync(finalPath, 'utf-8');
575
-
576
- // Parse Import Clause
577
- let defaultImport = null;
578
- let namedImports = [];
579
-
580
- const cleanClause = importClause.trim();
581
-
582
- if (cleanClause.includes('{')) {
583
- // Has named imports
584
- const parts = cleanClause.split('{');
585
- const preBrace = parts[0].trim(); // "Default, " or ""
586
- const braceContent = parts[1].replace('}', '').trim(); // "A, B as C"
587
-
588
- if (preBrace) {
589
- defaultImport = preBrace.replace(',', '').trim();
590
- }
591
-
592
- if (braceContent) {
593
- namedImports = braceContent.split(',').map(i => {
594
- const [name, alias] = i.split(/\s+as\s+/).map(s => s.trim());
595
- return { name, alias: alias || name };
596
- });
597
- }
598
- } else {
599
- if (cleanClause.includes('*')) return match; // Skip namespace
600
- defaultImport = cleanClause;
601
- }
602
-
603
- // Transform exported content to local variables
604
-
605
- // STRATEGY:
606
- // 1. Convert all 'const/let' to 'var' to allow redeclaration (resolves naming collisions)
607
- // 2. Wrap exports
608
-
609
- // Replace const/let with var globally to prevent "Identifier already declared" errors
610
- // Use a sophisticated regex that handles:
611
- // - Start of line or indentation
612
- // - Previous statement termination (; or })
613
- // - Optional export keyword
614
- fileContent = fileContent.replace(/(^|[\s;}])(export\s+)?(const|let)(?=\s+)/gm, '$1$2var');
615
-
616
- const defaultExportName = `__default_${path.basename(finalPath, path.extname(finalPath)).replace(/\W/g, '_')}_${Math.random().toString(36).substr(2, 5)}`;
617
-
618
- // Handle export default
619
- if (fileContent.includes('export default')) {
620
- fileContent = fileContent.replace(/export\s+default\s+/g, `var ${defaultExportName} = `);
621
- } else {
622
- // If no export default, but we are importing default, try to find a variable with the same name
623
- // This happens in legacy data files like: const data = ...; (no export default)
624
- if (defaultImport) {
625
- // Check if there is a variable matching the import name in the file content
626
- const varRegex = new RegExp(`var\\s+${defaultImport}\\s*=`);
627
- if (varRegex.test(fileContent)) {
628
- // Found it, map it to our default export name for consistency
629
- fileContent += `\nvar ${defaultExportName} = ${defaultImport};\n`;
630
- }
631
- }
632
- }
633
-
634
- // Handle named exports: remove 'export' keyword
635
- fileContent = fileContent.replace(/^\s*export\s+/gm, '');
636
- // Also clean up export { ... } statements
637
- fileContent = fileContent.replace(/^\s*export\s*\{[^}]+\};?/gm, '');
638
-
639
- let aliasCode = '';
640
-
641
- // Map Default Import
642
- if (defaultImport) {
643
- // Use var for alias to be safe against existing variable
644
- aliasCode += `if (typeof ${defaultImport} === 'undefined') { var ${defaultImport} = (typeof ${defaultExportName} !== 'undefined') ? ${defaultExportName} : undefined; }\n`;
645
- }
646
-
647
- // Map Named Imports with Aliases
648
- namedImports.forEach(({ name, alias }) => {
649
- if (name !== alias) {
650
- aliasCode += `var ${alias} = ${name};\n`;
651
- }
652
- });
653
-
654
- return `/* Inlined: ${importPath} */\n${fileContent}\n${aliasCode}`;
655
- } catch (e) {
656
- console.warn(`Failed to inline ${importPath} in ${componentName}: ${e.message}`);
657
- return `/* Error inlining ${importPath}: ${e.message} */`;
658
- }
659
- });
660
-
661
- // 2. Remove any remaining import statements (non-local or failed ones)
662
- newCode = newCode.replace(/^.*import\s+.*from\s+['"`].*['"`];?.*$/gm, '');
663
-
664
- // 3. Remove export default from the component itself
665
- newCode = newCode.replace(/export\s+default\s+/g, '');
666
-
667
- // 4. Make sure the class is available globally for bundle evaluation
668
- if (newCode.includes('customElements.define')) {
669
- newCode = newCode.replace(/customElements\.define\([^;]+\);?\s*$/, `window.${componentName} = ${componentName};\n$&`);
556
+ cleanJavaScript(code, componentName) {
557
+ // Remove export default
558
+ code = code.replace(/export\s+default\s+/g, '');
559
+
560
+ // Remove imports (components will already be available)
561
+ code = code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, '');
562
+
563
+ // Make sure the class is available globally for bundle evaluation
564
+ // Preserve original customElements.define if it exists
565
+ if (code.includes('customElements.define')) {
566
+ // Add global assignment before customElements.define
567
+ code = code.replace(/customElements\.define\([^;]+\);?\s*$/, `window.${componentName} = ${componentName};\n$&`);
670
568
  } else {
671
- newCode += `\nwindow.${componentName} = ${componentName};`;
569
+ // If no customElements.define found, just assign to global
570
+ code += `\nwindow.${componentName} = ${componentName};`;
672
571
  }
673
572
 
674
- // 5. Add return statement for bundle evaluation compatibility
675
- newCode += `\nreturn ${componentName};`;
573
+ // Add return statement for bundle evaluation compatibility
574
+ code += `\nreturn ${componentName};`;
676
575
 
677
- return newCode;
576
+ return code;
678
577
  }
679
578
 
680
579
  /**
@@ -732,9 +631,13 @@ if (window.slice && window.slice.controller) {
732
631
  };
733
632
 
734
633
  for (const [key, bundle] of Object.entries(this.bundles.routes)) {
634
+ const routeIdentifier = Array.isArray(bundle.path || bundle.paths)
635
+ ? key
636
+ : (bundle.path || bundle.paths || key);
637
+
735
638
  config.bundles.routes[key] = {
736
639
  path: bundle.path || bundle.paths || key, // Support both single path and array of paths, fallback to key
737
- file: bundle.file,
640
+ file: `slice-bundle.${this.routeToFileName(routeIdentifier)}.js`,
738
641
  size: bundle.size,
739
642
  components: bundle.components.map(c => c.name),
740
643
  dependencies: ['critical']
@@ -832,4 +735,4 @@ if (typeof window !== 'undefined' && window.slice && window.slice.controller) {
832
735
  }
833
736
  `;
834
737
  }
835
- }
738
+ }
@@ -133,16 +133,280 @@ export default class DependencyAnalyzer {
133
133
  component.size = await this.calculateComponentSize(component.path);
134
134
 
135
135
  // Parse and extract dependencies
136
- component.dependencies = await this.extractDependencies(content);
136
+ component.dependencies = await this.extractDependencies(content, jsFile);
137
137
  }
138
138
  }
139
139
 
140
140
  /**
141
141
  * Extracts dependencies from a component file
142
142
  */
143
- async extractDependencies(code) {
143
+ async extractDependencies(code, componentFilePath = null) {
144
144
  const dependencies = new Set();
145
145
 
146
+ const resolveRoutesArray = (node, scope) => {
147
+ if (!node) return null;
148
+
149
+ if (node.type === 'ArrayExpression') {
150
+ return node;
151
+ }
152
+
153
+ if (node.type === 'ObjectExpression') {
154
+ const routesProp = node.properties.find(p => p.key?.name === 'routes');
155
+ if (routesProp?.value) {
156
+ return resolveRoutesArray(routesProp.value, scope);
157
+ }
158
+ }
159
+
160
+ if (node.type === 'Identifier' && scope) {
161
+ const binding = scope.getBinding(node.name);
162
+ if (!binding) return null;
163
+ const bindingNode = binding.path?.node;
164
+
165
+ if (bindingNode?.type === 'VariableDeclarator') {
166
+ const init = bindingNode.init;
167
+ if (init?.type === 'ArrayExpression') {
168
+ return init;
169
+ }
170
+
171
+ if (init?.type === 'Identifier') {
172
+ return resolveRoutesArray(init, binding.path.scope);
173
+ }
174
+
175
+ if (init?.type === 'ObjectExpression') {
176
+ return resolveRoutesArray(init, binding.path.scope);
177
+ }
178
+
179
+ if (init?.type === 'MemberExpression') {
180
+ return resolveRoutesArray(init, binding.path.scope);
181
+ }
182
+ }
183
+
184
+ if (bindingNode?.type === 'ImportSpecifier' || bindingNode?.type === 'ImportDefaultSpecifier') {
185
+ const parent = binding.path.parentPath?.node;
186
+ if (parent?.type === 'ImportDeclaration') {
187
+ const importedName = bindingNode.type === 'ImportDefaultSpecifier'
188
+ ? 'default'
189
+ : bindingNode.imported?.name;
190
+ const importedNode = resolveImportedValue(parent.source.value, importedName, componentFilePath);
191
+ return resolveRoutesArray(importedNode, null);
192
+ }
193
+ }
194
+ }
195
+
196
+ if (node.type === 'MemberExpression' && scope) {
197
+ const objectNode = resolveObjectExpression(node.object, scope);
198
+ if (objectNode) {
199
+ const propName = node.property?.name || node.property?.value;
200
+ if (propName) {
201
+ const prop = objectNode.properties.find(p => p.key?.name === propName || p.key?.value === propName);
202
+ if (prop?.value) {
203
+ return resolveRoutesArray(prop.value, scope);
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ return null;
210
+ };
211
+
212
+ const resolveObjectExpression = (node, scope) => {
213
+ if (!node) return null;
214
+ if (node.type === 'ObjectExpression') return node;
215
+
216
+ if (node.type === 'Identifier' && scope) {
217
+ const binding = scope.getBinding(node.name);
218
+ const bindingNode = binding?.path?.node;
219
+ if (bindingNode?.type === 'VariableDeclarator') {
220
+ const init = bindingNode.init;
221
+ if (init?.type === 'ObjectExpression') {
222
+ return init;
223
+ }
224
+ if (init?.type === 'Identifier') {
225
+ return resolveObjectExpression(init, binding.path.scope);
226
+ }
227
+ }
228
+
229
+ if (bindingNode?.type === 'ImportSpecifier' || bindingNode?.type === 'ImportDefaultSpecifier') {
230
+ const parent = binding.path.parentPath?.node;
231
+ if (parent?.type === 'ImportDeclaration') {
232
+ const importedName = bindingNode.type === 'ImportDefaultSpecifier'
233
+ ? 'default'
234
+ : bindingNode.imported?.name;
235
+ const importedNode = resolveImportedValue(parent.source.value, importedName, componentFilePath);
236
+ return resolveObjectExpression(importedNode, null);
237
+ }
238
+ }
239
+ }
240
+
241
+ return null;
242
+ };
243
+
244
+ const resolveStringValue = (node, scope) => {
245
+ if (!node) return null;
246
+ if (node.type === 'StringLiteral') return node.value;
247
+ if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
248
+ return node.quasis.map(q => q.value.cooked).join('');
249
+ }
250
+
251
+ if (node.type === 'Identifier' && scope) {
252
+ const binding = scope.getBinding(node.name);
253
+ const bindingNode = binding?.path?.node;
254
+ if (bindingNode?.type === 'VariableDeclarator') {
255
+ return resolveStringValue(bindingNode.init, binding.path.scope);
256
+ }
257
+
258
+ if (bindingNode?.type === 'ImportSpecifier' || bindingNode?.type === 'ImportDefaultSpecifier') {
259
+ const parent = binding.path.parentPath?.node;
260
+ if (parent?.type === 'ImportDeclaration') {
261
+ const importedName = bindingNode.type === 'ImportDefaultSpecifier'
262
+ ? 'default'
263
+ : bindingNode.imported?.name;
264
+ const importedNode = resolveImportedValue(parent.source.value, importedName, componentFilePath);
265
+ return resolveStringValue(importedNode, null);
266
+ }
267
+ }
268
+ }
269
+
270
+ if (node.type === 'MemberExpression' && scope) {
271
+ const objectNode = resolveObjectExpression(node.object, scope);
272
+ if (objectNode) {
273
+ const propName = node.property?.name || node.property?.value;
274
+ if (propName) {
275
+ const prop = objectNode.properties.find(p => p.key?.name === propName || p.key?.value === propName);
276
+ if (prop?.value) {
277
+ return resolveStringValue(prop.value, scope);
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ return null;
284
+ };
285
+
286
+ const resolveImportedValue = (importPath, importedName, fromFilePath) => {
287
+ if (!fromFilePath) return null;
288
+
289
+ const baseDir = path.dirname(fromFilePath);
290
+ const resolvedPath = resolveImportPath(importPath, baseDir);
291
+ if (!resolvedPath) {
292
+ console.warn(`⚠️ Cannot resolve import for MultiRoute routes: ${importPath}`);
293
+ return null;
294
+ }
295
+
296
+ const cacheKey = `${resolvedPath}:${importedName || 'default'}`;
297
+ if (!resolveImportedValue.cache) {
298
+ resolveImportedValue.cache = new Map();
299
+ }
300
+ if (resolveImportedValue.cache.has(cacheKey)) {
301
+ return resolveImportedValue.cache.get(cacheKey);
302
+ }
303
+
304
+ try {
305
+ const source = fs.readFileSync(resolvedPath, 'utf-8');
306
+ const importAst = parse(source, {
307
+ sourceType: 'module',
308
+ plugins: ['jsx']
309
+ });
310
+
311
+ const topLevelBindings = new Map();
312
+ for (const node of importAst.program.body) {
313
+ if (node.type === 'VariableDeclaration') {
314
+ node.declarations.forEach(decl => {
315
+ if (decl.id?.type === 'Identifier') {
316
+ topLevelBindings.set(decl.id.name, decl.init);
317
+ }
318
+ });
319
+ }
320
+ }
321
+
322
+ let exportNode = null;
323
+ for (const node of importAst.program.body) {
324
+ if (node.type === 'ExportDefaultDeclaration' && importedName === 'default') {
325
+ exportNode = node.declaration;
326
+ break;
327
+ }
328
+
329
+ if (node.type === 'ExportNamedDeclaration') {
330
+ if (node.declaration?.type === 'VariableDeclaration') {
331
+ for (const decl of node.declaration.declarations) {
332
+ if (decl.id?.name === importedName) {
333
+ exportNode = decl.init;
334
+ break;
335
+ }
336
+ }
337
+ }
338
+
339
+ if (!exportNode && node.specifiers?.length) {
340
+ const specifier = node.specifiers.find(s => s.exported?.name === importedName);
341
+ if (specifier && specifier.local?.name) {
342
+ exportNode = topLevelBindings.get(specifier.local.name) || null;
343
+ }
344
+ }
345
+ }
346
+
347
+ if (exportNode) break;
348
+ }
349
+
350
+ if (exportNode?.type === 'Identifier') {
351
+ exportNode = topLevelBindings.get(exportNode.name) || exportNode;
352
+ }
353
+
354
+ resolveImportedValue.cache.set(cacheKey, exportNode || null);
355
+ return exportNode || null;
356
+ } catch (error) {
357
+ console.warn(`⚠️ Error resolving import ${importPath}: ${error.message}`);
358
+ resolveImportedValue.cache.set(cacheKey, null);
359
+ return null;
360
+ }
361
+ };
362
+
363
+ const resolveImportPath = (importPath, baseDir) => {
364
+ if (!importPath.startsWith('.')) return null;
365
+
366
+ const resolvedBase = path.resolve(baseDir, importPath);
367
+ const extensions = ['.js', '.mjs', '.cjs', '.json'];
368
+
369
+ if (fs.existsSync(resolvedBase) && fs.statSync(resolvedBase).isFile()) {
370
+ return resolvedBase;
371
+ }
372
+
373
+ if (!path.extname(resolvedBase)) {
374
+ for (const ext of extensions) {
375
+ const candidate = resolvedBase + ext;
376
+ if (fs.existsSync(candidate)) {
377
+ return candidate;
378
+ }
379
+ }
380
+ }
381
+
382
+ return null;
383
+ };
384
+
385
+ const addMultiRouteDependencies = (routesArrayNode, scope) => {
386
+ if (!routesArrayNode || routesArrayNode.type !== 'ArrayExpression') return;
387
+
388
+ routesArrayNode.elements.forEach(routeElement => {
389
+ if (!routeElement) return;
390
+
391
+ if (routeElement.type === 'SpreadElement') {
392
+ const spreadArray = resolveRoutesArray(routeElement.argument, scope);
393
+ addMultiRouteDependencies(spreadArray, scope);
394
+ return;
395
+ }
396
+
397
+ const routeObject = resolveObjectExpression(routeElement, scope) || routeElement;
398
+ if (routeObject?.type === 'ObjectExpression') {
399
+ const componentProp = routeObject.properties.find(p => p.key?.name === 'component');
400
+ if (componentProp?.value) {
401
+ const componentName = resolveStringValue(componentProp.value, scope);
402
+ if (componentName) {
403
+ dependencies.add(componentName);
404
+ }
405
+ }
406
+ }
407
+ });
408
+ };
409
+
146
410
  try {
147
411
  const ast = parse(code, {
148
412
  sourceType: 'module',
@@ -168,15 +432,9 @@ export default class DependencyAnalyzer {
168
432
 
169
433
  // Extract routes from MultiRoute props
170
434
  const routesProp = args[1].properties.find(p => p.key?.name === 'routes');
171
- if (routesProp?.value?.type === 'ArrayExpression') {
172
- routesProp.value.elements.forEach(routeElement => {
173
- if (routeElement.type === 'ObjectExpression') {
174
- const componentProp = routeElement.properties.find(p => p.key?.name === 'component');
175
- if (componentProp?.value?.type === 'StringLiteral') {
176
- dependencies.add(componentProp.value.value);
177
- }
178
- }
179
- });
435
+ if (routesProp) {
436
+ const routesArrayNode = resolveRoutesArray(routesProp.value, path.scope);
437
+ addMultiRouteDependencies(routesArrayNode);
180
438
  }
181
439
  }
182
440
  // Regular slice.build() calls
@@ -418,4 +676,4 @@ export default class DependencyAnalyzer {
418
676
  console.log(` ${i + 1}. ${comp.name} - ${comp.routes} routes - ${(comp.size / 1024).toFixed(1)} KB`);
419
677
  });
420
678
  }
421
- }
679
+ }
@@ -95,7 +95,7 @@ class UpdateManager {
95
95
  }
96
96
 
97
97
  console.log('');
98
- Print.warning('📦 Available Updates:');
98
+ Print.warning('📦 Available Updates:');
99
99
  console.log('');
100
100
 
101
101
  updateInfo.updates.forEach(pkg => {
@@ -145,17 +145,17 @@ class UpdateManager {
145
145
  const answers = await inquirer.prompt([
146
146
  {
147
147
  type: 'checkbox',
148
- name: 'packages',
149
- message: 'Which packages do you want to update?',
150
- choices,
151
- validate: (answer) => {
152
- if (answer.length === 0) {
153
- return 'You must select at least one package';
154
- }
155
- return true;
156
- }
157
- }
158
- ]);
148
+ name: 'packages',
149
+ message: 'Which packages do you want to update?',
150
+ choices,
151
+ validate: (answer) => {
152
+ if (answer.length === 0) {
153
+ return 'You must select at least one package';
154
+ }
155
+ return true;
156
+ }
157
+ }
158
+ ]);
159
159
 
160
160
  return answers.packages;
161
161
  }
@@ -229,25 +229,25 @@ class UpdateManager {
229
229
  const results = [];
230
230
 
231
231
  for (const packageName of packages) {
232
- const spinner = ora(`Updating ${packageName}...`).start();
232
+ const spinner = ora(`Updating ${packageName}...`).start();
233
233
 
234
234
  try {
235
235
  const result = await this.updatePackage(packageName);
236
236
 
237
- if (result.success) {
238
- spinner.succeed(`${packageName} updated successfully`);
239
- results.push({ packageName, success: true });
240
- } else {
241
- spinner.fail(`Error updating ${packageName}`);
242
- Print.error(`Details: ${result.error}`);
243
- results.push({ packageName, success: false, error: result.error });
244
- }
245
- } catch (error) {
246
- spinner.fail(`Error updating ${packageName}`);
247
- Print.error(`Details: ${error.message}`);
248
- results.push({ packageName, success: false, error: error.message });
249
- }
250
- }
237
+ if (result.success) {
238
+ spinner.succeed(`${packageName} updated successfully`);
239
+ results.push({ packageName, success: true });
240
+ } else {
241
+ spinner.fail(`Error updating ${packageName}`);
242
+ Print.error(`Details: ${result.error}`);
243
+ results.push({ packageName, success: false, error: result.error });
244
+ }
245
+ } catch (error) {
246
+ spinner.fail(`Error updating ${packageName}`);
247
+ Print.error(`Details: ${error.message}`);
248
+ results.push({ packageName, success: false, error: error.message });
249
+ }
250
+ }
251
251
 
252
252
  return results;
253
253
  }
@@ -256,26 +256,26 @@ class UpdateManager {
256
256
  * Main method to check and prompt for updates
257
257
  */
258
258
  async checkAndPromptUpdates(options = {}) {
259
- const spinner = ora('Checking for updates...').start();
259
+ const spinner = ora('Checking for updates...').start();
260
260
 
261
261
  try {
262
262
  const updateInfo = await this.checkForUpdates();
263
263
  spinner.stop();
264
264
 
265
- if (!updateInfo) {
266
- Print.error('Could not check for updates. Verify your internet connection.');
267
- return false;
268
- }
265
+ if (!updateInfo) {
266
+ Print.error('Could not check for updates. Verify your internet connection.');
267
+ return false;
268
+ }
269
269
 
270
- if (updateInfo.allCurrent) {
271
- Print.success('✅ All components are up to date!');
272
- return true;
273
- }
270
+ if (updateInfo.allCurrent) {
271
+ Print.success('✅ All components are up to date!');
272
+ return true;
273
+ }
274
274
 
275
- if (!updateInfo.hasUpdates) {
276
- Print.success('✅ All components are up to date!');
277
- return true;
278
- }
275
+ if (!updateInfo.hasUpdates) {
276
+ Print.success('✅ All components are up to date!');
277
+ return true;
278
+ }
279
279
 
280
280
  // Display available updates
281
281
  this.displayUpdates(updateInfo);
@@ -283,20 +283,20 @@ class UpdateManager {
283
283
  // Get packages to update
284
284
  const packagesToUpdate = await this.promptForUpdates(updateInfo, options);
285
285
 
286
- if (!packagesToUpdate || packagesToUpdate.length === 0) {
287
- Print.info('No packages selected for update.');
288
- return false;
289
- }
286
+ if (!packagesToUpdate || packagesToUpdate.length === 0) {
287
+ Print.info('No packages selected for update.');
288
+ return false;
289
+ }
290
290
 
291
291
  // Show plan and confirm installation if not auto-confirmed
292
292
  let plan = await this.buildUpdatePlan(packagesToUpdate);
293
293
  console.log('');
294
- Print.info('🧭 Update plan:');
295
- plan.forEach(item => {
296
- const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
297
- console.log(` • ${item.package} → ${where}`);
298
- console.log(` ${item.command}`);
299
- });
294
+ Print.info('🧭 Update plan:');
295
+ plan.forEach(item => {
296
+ const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
297
+ console.log(` • ${item.package} → ${where}`);
298
+ console.log(` ${item.command}`);
299
+ });
300
300
  console.log('');
301
301
 
302
302
  const cliInfo = await this.detectCliInstall();
@@ -306,47 +306,47 @@ class UpdateManager {
306
306
  {
307
307
  type: 'confirm',
308
308
  name: 'addCli',
309
- message: 'Global CLI detected. Add the global CLI update to the plan?',
310
- default: true
311
- }
312
- ]);
309
+ message: 'Global CLI detected. Add the global CLI update to the plan?',
310
+ default: true
311
+ }
312
+ ]);
313
313
  if (addCli) {
314
314
  packagesToUpdate.push('slicejs-cli');
315
315
  plan = await this.buildUpdatePlan(packagesToUpdate);
316
316
  console.log('');
317
- Print.info('🧭 Updated plan:');
318
- plan.forEach(item => {
319
- const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
320
- console.log(` • ${item.package} → ${where}`);
321
- console.log(` ${item.command}`);
322
- });
317
+ Print.info('🧭 Updated plan:');
318
+ plan.forEach(item => {
319
+ const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
320
+ console.log(` • ${item.package} → ${where}`);
321
+ console.log(` ${item.command}`);
322
+ });
323
323
  console.log('');
324
324
  }
325
325
  } else {
326
- Print.warning('Global CLI detected. It is recommended to update slicejs-cli globally to keep aligned with the framework.');
327
- console.log(' Suggestion: npm install -g slicejs-cli@latest');
328
- console.log('');
329
- }
330
- }
326
+ Print.warning('Global CLI detected. It is recommended to update slicejs-cli globally to keep aligned with the framework.');
327
+ console.log(' Suggestion: npm install -g slicejs-cli@latest');
328
+ console.log('');
329
+ }
330
+ }
331
331
 
332
332
  if (!options.yes && !options.cli && !options.framework) {
333
333
  const { confirm } = await inquirer.prompt([
334
- {
335
- type: 'confirm',
336
- name: 'confirm',
337
- message: 'Do you want to proceed with the update according to the plan shown?',
338
- default: true
339
- }
340
- ]);
334
+ {
335
+ type: 'confirm',
336
+ name: 'confirm',
337
+ message: 'Do you want to proceed with the update according to the plan shown?',
338
+ default: true
339
+ }
340
+ ]);
341
341
 
342
342
  if (!confirm) {
343
- Print.info('Update cancelled.');
344
- return false;
345
- }
346
- }
343
+ Print.info('Update cancelled.');
344
+ return false;
345
+ }
346
+ }
347
347
 
348
348
  console.log(''); // Line break
349
- Print.info('📥 Installing updates...');
349
+ Print.info('📥 Installing updates...');
350
350
  console.log('');
351
351
 
352
352
  // Install updates
@@ -357,24 +357,24 @@ class UpdateManager {
357
357
  const successCount = results.filter(r => r.success).length;
358
358
  const failCount = results.filter(r => !r.success).length;
359
359
 
360
- if (failCount === 0) {
361
- Print.success(`✅ ${successCount} package(s) updated successfully!`);
362
- } else {
363
- Print.warning(`⚠️ ${successCount} successful, ${failCount} failed`);
364
- }
360
+ if (failCount === 0) {
361
+ Print.success(`✅ ${successCount} package(s) updated successfully!`);
362
+ } else {
363
+ Print.warning(`⚠️ ${successCount} successful, ${failCount} failed`);
364
+ }
365
365
 
366
366
  if (successCount > 0) {
367
367
  console.log('');
368
- Print.info('💡 It is recommended to restart the development server if it is running.');
369
- }
368
+ Print.info('💡 It is recommended to restart the development server if it is running.');
369
+ }
370
370
 
371
371
  return failCount === 0;
372
372
 
373
373
  } catch (error) {
374
374
  spinner.stop();
375
- Print.error(`Error during update: ${error.message}`);
376
- return false;
377
- }
375
+ Print.error(`Error during update: ${error.message}`);
376
+ return false;
377
+ }
378
378
  }
379
379
  }
380
380
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-cli",
3
- "version": "2.8.0",
3
+ "version": "2.8.2",
4
4
  "description": "Command client for developing web applications with Slice.js framework",
5
5
  "main": "client.js",
6
6
  "bin": {