slicejs-cli 2.7.9 → 2.8.1

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.
package/README.md CHANGED
@@ -17,13 +17,37 @@
17
17
 
18
18
  ## Installation
19
19
 
20
- - Local (recommended):
20
+ ### Local (Recommended)
21
+
22
+ 1. Install as a development dependency:
21
23
 
22
24
  ```bash
23
25
  npm install slicejs-cli --save-dev
24
26
  ```
25
27
 
26
- - Global:
28
+ 2. Add to your `package.json` scripts:
29
+
30
+ ```json
31
+ {
32
+ "scripts": {
33
+ "dev": "slice dev",
34
+ "build": "slice build",
35
+ "slice": "slice"
36
+ }
37
+ }
38
+ ```
39
+
40
+ 3. usage:
41
+
42
+ ```bash
43
+ npm run dev
44
+ # or pass arguments
45
+ npm run slice -- get Button
46
+ ```
47
+
48
+ ### Global (Not Recommended)
49
+
50
+ Global installations can lead to version mismatches and "works on my machine" issues.
27
51
 
28
52
  ```bash
29
53
  npm install -g slicejs-cli
package/client.js CHANGED
@@ -132,6 +132,33 @@ sliceClient
132
132
  await versionChecker.showVersionInfo();
133
133
  });
134
134
 
135
+ // BUNDLE COMMAND
136
+ const bundleCommand = sliceClient.command("bundle")
137
+ .description("Build component bundles for production")
138
+ .action(async (options) => {
139
+ await runWithVersionCheck(async () => {
140
+ await bundle(options);
141
+ });
142
+ });
143
+
144
+ bundleCommand
145
+ .command("clean")
146
+ .description("Remove all generated bundles")
147
+ .action(async () => {
148
+ await cleanBundles();
149
+ });
150
+
151
+ bundleCommand
152
+ .command("info")
153
+ .description("Show information about generated bundles")
154
+ .action(async () => {
155
+ await bundleInfo();
156
+ });
157
+
158
+ bundleCommand
159
+ .option("-a, --analyze", "Analyze project dependencies without bundling")
160
+ .option("-v, --verbose", "Show detailed output");
161
+
135
162
  // DEV COMMAND (DEVELOPMENT) - COMANDO PRINCIPAL
136
163
  sliceClient
137
164
  .command("dev")
@@ -453,25 +480,6 @@ sliceClient
453
480
  subcommandTerm: (cmd) => cmd.name() + ' ' + cmd.usage()
454
481
  });
455
482
 
456
- sliceClient
457
- .command('bundle')
458
- .description('Generate production bundles for optimal loading')
459
- .option('-a, --analyze', 'Only analyze without generating bundles')
460
- .option('-v, --verbose', 'Show detailed information')
461
- .action(bundle);
462
-
463
- // Subcomando: limpiar bundles
464
- sliceClient
465
- .command('bundle:clean')
466
- .description('Remove all generated bundles')
467
- .action(cleanBundles);
468
-
469
- // Subcomando: información
470
- sliceClient
471
- .command('bundle:info')
472
- .description('Show information about generated bundles')
473
- .action(bundleInfo);
474
-
475
483
 
476
484
  // Custom help - SIMPLIFICADO para development only
477
485
  sliceClient.addHelpText('after', `
@@ -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,5 +1,6 @@
1
1
  // commands/startServer/startServer.js - MEJORADO CON VALIDACIÓN Y FEEDBACK
2
2
 
3
+ import bundle from '../bundle/bundle.js';
3
4
  import fs from 'fs-extra';
4
5
  import path from 'path';
5
6
  import { fileURLToPath } from 'url';
@@ -184,19 +185,17 @@ export default async function startServer(options = {}) {
184
185
  throw new Error('Project structure not found. Run "slice init" first.');
185
186
  }
186
187
 
187
- // Verificar disponibilidad del puerto
188
- Print.checkingPort(port);
189
- const portAvailable = await isPortAvailable(port);
190
-
191
- if (!portAvailable) {
192
- throw new Error(
193
- `Port ${port} is already in use. Please:\n` +
194
- ` 1. Stop the process using port ${port}, or\n` +
195
- ` 2. Use a different port: slice ${mode === 'development' ? 'dev' : mode === 'bundled' ? 'start --bundled' : 'start'} -p <port>`
196
- );
188
+ let actualPort = await isPortAvailable(port) ? port : port + 1; // Try one more port
189
+ if(actualPort !== port) {
190
+ // Check if the fallback is available
191
+ const fallbackAvailable = await isPortAvailable(actualPort);
192
+ if(!fallbackAvailable) {
193
+ throw new Error(`Ports ${port} and ${actualPort} are in use.`);
194
+ }
195
+ Print.info(`ℹ️ Port ${port} in use, using ${actualPort} instead.`);
197
196
  }
198
197
 
199
- Print.serverStatus('checking', 'Port available ✓');
198
+ Print.serverStatus('checking', `Port ${actualPort} available ✓`);
200
199
  Print.newLine();
201
200
 
202
201
  if (mode === 'production') {
@@ -214,12 +213,40 @@ export default async function startServer(options = {}) {
214
213
  Print.newLine();
215
214
 
216
215
  // Iniciar el servidor con argumentos
217
- const serverProcess = await startNodeServer(port, mode);
216
+ let serverProcess = await startNodeServer(actualPort, mode);
218
217
 
219
218
  // Configurar watch mode si está habilitado
220
219
  if (watch) {
221
220
  Print.newLine();
222
- const watcher = setupWatcher(serverProcess);
221
+ const watcher = setupWatcher(serverProcess, async (changedPath) => {
222
+ if (serverProcess) {
223
+ serverProcess.kill();
224
+ }
225
+
226
+ // Short delay to ensure port is freed
227
+ await new Promise(r => setTimeout(r, 500));
228
+
229
+ try {
230
+ // If we are in bundled mode, regenerate bundles before restarting
231
+ if (mode === 'bundled') {
232
+ Print.info('🔄 File changed. Regenerating bundles...');
233
+ try {
234
+ await bundle({ verbose: false });
235
+ } catch (err) {
236
+ Print.error('Bundle generation failed during watch restart');
237
+ console.error(err);
238
+ // We continue restarting anyway to show error in browser if possible,
239
+ // or maybe just to keep process alive.
240
+ }
241
+ } else {
242
+ Print.info('🔄 File changed. Restarting server...');
243
+ }
244
+
245
+ serverProcess = await startNodeServer(actualPort, mode);
246
+ } catch (e) {
247
+ Print.error(`Failed to restart server: ${e.message}`);
248
+ }
249
+ });
223
250
 
224
251
  // Cleanup en exit
225
252
  const cleanup = () => {
@@ -7,7 +7,7 @@ import Print from '../Print.js';
7
7
  * @param {ChildProcess} serverProcess - Proceso del servidor
8
8
  * @returns {FSWatcher} - Watcher de chokidar
9
9
  */
10
- export default function setupWatcher(serverProcess) {
10
+ export default function setupWatcher(serverProcess, onRestart) {
11
11
  Print.info('Watch mode enabled - monitoring file changes...');
12
12
  Print.newLine();
13
13
 
@@ -16,6 +16,7 @@ export default function setupWatcher(serverProcess) {
16
16
  /(^|[\/\\])\../, // archivos ocultos
17
17
  '**/node_modules/**',
18
18
  '**/dist/**',
19
+ '**/bundles/**',
19
20
  '**/*.log'
20
21
  ],
21
22
  persistent: true,
@@ -30,19 +31,30 @@ export default function setupWatcher(serverProcess) {
30
31
 
31
32
  watcher
32
33
  .on('change', (path) => {
33
- console.log(chalk.cyan(`📝 File changed: ${path}`));
34
-
35
34
  // Debounce para evitar múltiples reloads
36
35
  clearTimeout(reloadTimeout);
37
36
  reloadTimeout = setTimeout(() => {
38
- console.log(chalk.yellow('🔄 Changes detected, server will reload automatically...'));
39
- }, 200);
37
+ if(onRestart) {
38
+ console.log(chalk.yellow('🔄 Changes detected, restarting server...'));
39
+ onRestart(path);
40
+ } else {
41
+ console.log(chalk.yellow('🔄 Changes detected, server will reload automatically... (No handler)'));
42
+ }
43
+ }, 500);
40
44
  })
41
45
  .on('add', (path) => {
42
- console.log(chalk.green(`➕ New file added: ${path}`));
46
+ // console.log(chalk.green(`➕ New file added: ${path}`));
47
+ clearTimeout(reloadTimeout);
48
+ reloadTimeout = setTimeout(() => {
49
+ if (onRestart) onRestart(path);
50
+ }, 500);
43
51
  })
44
52
  .on('unlink', (path) => {
45
- console.log(chalk.red(`➖ File removed: ${path}`));
53
+ // console.log(chalk.red(`➖ File removed: ${path}`));
54
+ clearTimeout(reloadTimeout);
55
+ reloadTimeout = setTimeout(() => {
56
+ if (onRestart) onRestart(path);
57
+ }, 500);
46
58
  })
47
59
  .on('error', (error) => {
48
60
  Print.error(`Watcher error: ${error.message}`);
@@ -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)`);
@@ -401,7 +401,7 @@ export default class BundleGenerator {
401
401
  const routeFile = await this.createBundleFile(
402
402
  bundle.components,
403
403
  'route',
404
- bundle.path || routeKey // Use routeKey as fallback for hybrid bundles
404
+ bundle.path || bundle.paths || routeKey // Use routeKey as fallback for hybrid bundles
405
405
  );
406
406
  files.push(routeFile);
407
407
  }
@@ -629,7 +629,7 @@ if (window.slice && window.slice.controller) {
629
629
  for (const [key, bundle] of Object.entries(this.bundles.routes)) {
630
630
  config.bundles.routes[key] = {
631
631
  path: bundle.path || bundle.paths || key, // Support both single path and array of paths, fallback to key
632
- file: bundle.file,
632
+ file: `slice-bundle.${this.routeToFileName(bundle.path || bundle.paths || key)}.js`,
633
633
  size: bundle.size,
634
634
  components: bundle.components.map(c => c.name),
635
635
  dependencies: ['critical']
@@ -727,4 +727,4 @@ if (typeof window !== 'undefined' && window.slice && window.slice.controller) {
727
727
  }
728
728
  `;
729
729
  }
730
- }
730
+ }
@@ -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.7.9",
3
+ "version": "2.8.1",
4
4
  "description": "Command client for developing web applications with Slice.js framework",
5
5
  "main": "client.js",
6
6
  "bin": {
@@ -26,6 +26,7 @@
26
26
  ],
27
27
  "author": "vkneider",
28
28
  "type": "module",
29
+ "preferGlobal": false,
29
30
  "license": "ISC",
30
31
  "dependencies": {
31
32
  "chalk": "^5.6.2",
@@ -38,9 +39,7 @@
38
39
  "inquirer": "^12.4.2",
39
40
  "ora": "^8.2.0",
40
41
  "slicejs-web-framework": "latest",
41
- "terser": "^5.43.1"
42
- },
43
- "devDependencies": {
42
+ "terser": "^5.43.1",
44
43
  "@babel/parser": "^7.28.5",
45
44
  "@babel/traverse": "^7.28.5"
46
45
  }
package/post.js CHANGED
@@ -12,14 +12,14 @@ const targetRoot = initCwd || path.resolve(__dirname, '../../');
12
12
  const projectPackageJsonPath = path.join(targetRoot, 'package.json');
13
13
 
14
14
  if (isGlobal) {
15
- console.log('ℹ️ Global installation of slicejs-cli detected.');
16
- console.log(' Skipping scripts setup. Use the binary directly:');
17
- console.log(' slice dev');
18
- console.log(' slice get Button');
15
+ console.log('⚠️ Global installation of slicejs-cli detected.');
16
+ console.log(' We strongly recommend using a local installation to avoid version mismatches.');
17
+ console.log(' Uninstall global: npm uninstall -g slicejs-cli');
19
18
  process.exit(0);
20
19
  }
21
20
 
22
- console.log('ℹ️ Local installation of slicejs-cli detected.');
23
- console.log(' Skipping automatic scripts setup in postinstall.');
24
- console.log(' Use "slice init" to configure project scripts.');
21
+ console.log(' slicejs-cli installed successfully.');
22
+ console.log(' Add the CLI to your package.json scripts:');
23
+ console.log(' "dev": "slice dev"');
24
+ console.log(' Then run: npm run dev');
25
25
  process.exit(0);