slicejs-cli 2.7.4 → 2.7.6

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.
@@ -0,0 +1,340 @@
1
+ // cli/utils/bundling/DependencyAnalyzer.js
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { parse } from '@babel/parser';
5
+ import traverse from '@babel/traverse';
6
+ import { getSrcPath, getComponentsJsPath, getProjectRoot } from '../PathHelper.js';
7
+
8
+ export default class DependencyAnalyzer {
9
+ constructor(moduleUrl) {
10
+ this.moduleUrl = moduleUrl;
11
+ this.projectRoot = getProjectRoot(moduleUrl);
12
+ this.componentsPath = path.dirname(getComponentsJsPath(moduleUrl));
13
+ this.routesPath = getSrcPath(moduleUrl, 'routes.js');
14
+
15
+ // Analysis storage
16
+ this.components = new Map();
17
+ this.routes = new Map();
18
+ this.dependencyGraph = new Map();
19
+ }
20
+
21
+ /**
22
+ * Executes complete project analysis
23
+ */
24
+ async analyze() {
25
+ console.log('🔍 Analyzing project...');
26
+
27
+ // 1. Load component configuration
28
+ await this.loadComponentsConfig();
29
+
30
+ // 2. Analyze component files
31
+ await this.analyzeComponents();
32
+
33
+ // 3. Load and analyze routes
34
+ await this.analyzeRoutes();
35
+
36
+ // 4. Build dependency graph
37
+ this.buildDependencyGraph();
38
+
39
+ // 5. Calculate metrics
40
+ const metrics = this.calculateMetrics();
41
+
42
+ console.log('✅ Analysis completed');
43
+
44
+ return {
45
+ components: Array.from(this.components.values()),
46
+ routes: Array.from(this.routes.values()),
47
+ dependencyGraph: this.dependencyGraph,
48
+ metrics
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Loads component configuration from components.js
54
+ */
55
+ async loadComponentsConfig() {
56
+ const componentsConfigPath = path.join(this.componentsPath, 'components.js');
57
+
58
+ if (!await fs.pathExists(componentsConfigPath)) {
59
+ throw new Error('components.js not found');
60
+ }
61
+
62
+ // Read and parse components.js
63
+ const content = await fs.readFile(componentsConfigPath, 'utf-8');
64
+
65
+ // Extract configuration using simple regex
66
+ const configMatch = content.match(/export default\s*({[\s\S]*})/);
67
+ if (!configMatch) {
68
+ throw new Error('Could not parse components.js');
69
+ }
70
+
71
+ // Evaluate safely (in production use a more robust parser)
72
+ const config = eval(`(${configMatch[1]})`);
73
+
74
+ // Save category metadata
75
+ for (const [category, info] of Object.entries(config)) {
76
+ const categoryPath = path.join(this.componentsPath, info.path.replace('../', ''));
77
+
78
+ if (await fs.pathExists(categoryPath)) {
79
+ const files = await fs.readdir(categoryPath);
80
+
81
+ for (const file of files) {
82
+ const componentPath = path.join(categoryPath, file);
83
+ const stat = await fs.stat(componentPath);
84
+
85
+ if (stat.isDirectory()) {
86
+ this.components.set(file, {
87
+ name: file,
88
+ category,
89
+ categoryType: info.type,
90
+ path: componentPath,
91
+ dependencies: new Set(),
92
+ usedBy: new Set(),
93
+ routes: new Set(),
94
+ size: 0
95
+ });
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Analyzes each component's files
104
+ */
105
+ async analyzeComponents() {
106
+ for (const [name, component] of this.components) {
107
+ const jsFile = path.join(component.path, `${name}.js`);
108
+
109
+ if (!await fs.pathExists(jsFile)) continue;
110
+
111
+ // Read JavaScript file
112
+ const content = await fs.readFile(jsFile, 'utf-8');
113
+
114
+ // Calculate size
115
+ component.size = await this.calculateComponentSize(component.path);
116
+
117
+ // Parse and extract dependencies
118
+ component.dependencies = await this.extractDependencies(content);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Extracts dependencies from a component file
124
+ */
125
+ async extractDependencies(code) {
126
+ const dependencies = new Set();
127
+
128
+ try {
129
+ const ast = parse(code, {
130
+ sourceType: 'module',
131
+ plugins: ['jsx']
132
+ });
133
+
134
+ traverse.default(ast, {
135
+ // Detect slice.build() calls
136
+ CallExpression(path) {
137
+ const { callee, arguments: args } = path.node;
138
+
139
+ // slice.build('ComponentName', ...)
140
+ if (
141
+ callee.type === 'MemberExpression' &&
142
+ callee.object.name === 'slice' &&
143
+ callee.property.name === 'build' &&
144
+ args[0]?.type === 'StringLiteral'
145
+ ) {
146
+ dependencies.add(args[0].value);
147
+ }
148
+ },
149
+
150
+ // Detect direct imports (less common but possible)
151
+ ImportDeclaration(path) {
152
+ const importPath = path.node.source.value;
153
+ if (importPath.includes('/Components/')) {
154
+ const componentName = importPath.split('/').pop();
155
+ dependencies.add(componentName);
156
+ }
157
+ }
158
+ });
159
+ } catch (error) {
160
+ console.warn(`⚠️ Error parsing component: ${error.message}`);
161
+ }
162
+
163
+ return dependencies;
164
+ }
165
+
166
+ /**
167
+ * Analyzes the routes file
168
+ */
169
+ async analyzeRoutes() {
170
+ if (!await fs.pathExists(this.routesPath)) {
171
+ throw new Error('routes.js no encontrado');
172
+ }
173
+
174
+ const content = await fs.readFile(this.routesPath, 'utf-8');
175
+
176
+ try {
177
+ const ast = parse(content, {
178
+ sourceType: 'module',
179
+ plugins: ['jsx']
180
+ });
181
+
182
+ let currentRoute = null;
183
+ const self = this; // Guardar referencia a la instancia
184
+
185
+ traverse.default(ast, {
186
+ ObjectExpression(path) {
187
+ // Buscar objetos de ruta: { path: '/', component: 'HomePage' }
188
+ const properties = path.node.properties;
189
+ const pathProp = properties.find(p => p.key?.name === 'path');
190
+ const componentProp = properties.find(p => p.key?.name === 'component');
191
+
192
+ if (pathProp && componentProp) {
193
+ const routePath = pathProp.value.value;
194
+ const componentName = componentProp.value.value;
195
+
196
+ currentRoute = {
197
+ path: routePath,
198
+ component: componentName,
199
+ dependencies: new Set([componentName])
200
+ };
201
+
202
+ self.routes.set(routePath, currentRoute);
203
+
204
+ // Marcar el componente como usado por esta ruta
205
+ if (self.components.has(componentName)) {
206
+ self.components.get(componentName).routes.add(routePath);
207
+ }
208
+ }
209
+ }
210
+ });
211
+ } catch (error) {
212
+ console.warn(`⚠️ Error parseando rutas: ${error.message}`);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Builds the complete dependency graph
218
+ */
219
+ buildDependencyGraph() {
220
+ // Propagate transitive dependencies
221
+ for (const [name, component] of this.components) {
222
+ this.dependencyGraph.set(name, this.getAllDependencies(name, new Set()));
223
+ }
224
+
225
+ // Calculate usedBy (inverse dependencies)
226
+ for (const [name, deps] of this.dependencyGraph) {
227
+ for (const dep of deps) {
228
+ if (this.components.has(dep)) {
229
+ this.components.get(dep).usedBy.add(name);
230
+ }
231
+ }
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Gets all dependencies of a component (recursive)
237
+ */
238
+ getAllDependencies(componentName, visited = new Set()) {
239
+ if (visited.has(componentName)) return new Set();
240
+ visited.add(componentName);
241
+
242
+ const component = this.components.get(componentName);
243
+ if (!component) return new Set();
244
+
245
+ const allDeps = new Set(component.dependencies);
246
+
247
+ for (const dep of component.dependencies) {
248
+ const transitiveDeps = this.getAllDependencies(dep, visited);
249
+ for (const transDep of transitiveDeps) {
250
+ allDeps.add(transDep);
251
+ }
252
+ }
253
+
254
+ return allDeps;
255
+ }
256
+
257
+ /**
258
+ * Calculates the total size of a component (JS + HTML + CSS)
259
+ */
260
+ async calculateComponentSize(componentPath) {
261
+ let totalSize = 0;
262
+ const files = await fs.readdir(componentPath);
263
+
264
+ for (const file of files) {
265
+ const filePath = path.join(componentPath, file);
266
+ const stat = await fs.stat(filePath);
267
+
268
+ if (stat.isFile()) {
269
+ totalSize += stat.size;
270
+ }
271
+ }
272
+
273
+ return totalSize;
274
+ }
275
+
276
+ /**
277
+ * Calculates project metrics
278
+ */
279
+ calculateMetrics() {
280
+ const totalComponents = this.components.size;
281
+ const totalRoutes = this.routes.size;
282
+
283
+ // Shared components (used in multiple routes)
284
+ const sharedComponents = Array.from(this.components.values())
285
+ .filter(c => c.routes.size >= 2);
286
+
287
+ // Total size
288
+ const totalSize = Array.from(this.components.values())
289
+ .reduce((sum, c) => sum + c.size, 0);
290
+
291
+ // Components by category
292
+ const byCategory = {};
293
+ for (const comp of this.components.values()) {
294
+ byCategory[comp.category] = (byCategory[comp.category] || 0) + 1;
295
+ }
296
+
297
+ // Top components by usage
298
+ const topByUsage = Array.from(this.components.values())
299
+ .sort((a, b) => b.routes.size - a.routes.size)
300
+ .slice(0, 10)
301
+ .map(c => ({
302
+ name: c.name,
303
+ routes: c.routes.size,
304
+ size: c.size
305
+ }));
306
+
307
+ return {
308
+ totalComponents,
309
+ totalRoutes,
310
+ sharedComponentsCount: sharedComponents.length,
311
+ sharedPercentage: (sharedComponents.length / totalComponents * 100).toFixed(1),
312
+ totalSize,
313
+ averageSize: Math.round(totalSize / totalComponents),
314
+ byCategory,
315
+ topByUsage
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Generates a visual report of the analysis
321
+ */
322
+ generateReport(metrics) {
323
+ console.log('\n📊 PROJECT ANALYSIS\n');
324
+ console.log(`Total components: ${metrics.totalComponents}`);
325
+ console.log(`Total routes: ${metrics.totalRoutes}`);
326
+ console.log(`Shared components: ${metrics.sharedComponentsCount} (${metrics.sharedPercentage}%)`);
327
+ console.log(`Total size: ${(metrics.totalSize / 1024).toFixed(1)} KB`);
328
+ console.log(`Average size: ${(metrics.averageSize / 1024).toFixed(1)} KB per component`);
329
+
330
+ console.log('\n📦 By category:');
331
+ for (const [category, count] of Object.entries(metrics.byCategory)) {
332
+ console.log(` ${category}: ${count} components`);
333
+ }
334
+
335
+ console.log('\n🔥 Top 10 most used components:');
336
+ metrics.topByUsage.forEach((comp, i) => {
337
+ console.log(` ${i + 1}. ${comp.name} - ${comp.routes} routes - ${(comp.size / 1024).toFixed(1)} KB`);
338
+ });
339
+ }
340
+ }
@@ -95,7 +95,7 @@ class UpdateManager {
95
95
  }
96
96
 
97
97
  console.log('');
98
- Print.warning('📦 Actualizaciones Disponibles:');
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: '¿Qué paquetes deseas actualizar?',
150
- choices,
151
- validate: (answer) => {
152
- if (answer.length === 0) {
153
- return 'Debes seleccionar al menos un paquete';
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(`Actualizando ${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} actualizado exitosamente`);
239
- results.push({ packageName, success: true });
240
- } else {
241
- spinner.fail(`Error actualizando ${packageName}`);
242
- Print.error(`Detalles: ${result.error}`);
243
- results.push({ packageName, success: false, error: result.error });
244
- }
245
- } catch (error) {
246
- spinner.fail(`Error actualizando ${packageName}`);
247
- Print.error(`Detalles: ${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('Verificando actualizaciones...').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('No se pudo verificar actualizaciones. Verifica tu conexión a internet.');
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('✅ Todos los componentes están actualizados!');
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('✅ Todos los componentes están actualizados!');
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 se seleccionaron paquetes para actualizar.');
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('🧭 Plan de actualización:');
295
- plan.forEach(item => {
296
- const where = item.target === 'global' ? 'GLOBAL' : 'PROYECTO';
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: 'Se detectó CLI global. ¿Agregar la actualización global del CLI al 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('🧭 Plan actualizado:');
318
- plan.forEach(item => {
319
- const where = item.target === 'global' ? 'GLOBAL' : 'PROYECTO';
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('CLI global detectado. Se recomienda actualizar slicejs-cli global para mantener alineado con el framework.');
327
- console.log(' Sugerencia: 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: '¿Deseas continuar con la actualización según el plan mostrado?',
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('Actualización cancelada.');
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('📥 Instalando actualizaciones...');
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} paquete(s) actualizado(s) exitosamente!`);
362
- } else {
363
- Print.warning(`⚠️ ${successCount} exitoso(s), ${failCount} fallido(s)`);
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('💡 Se recomienda reiniciar el servidor de desarrollo si está ejecutándose.');
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 durante la actualización: ${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.4",
3
+ "version": "2.7.6",
4
4
  "description": "Command client for developing web applications with Slice.js framework",
5
5
  "main": "client.js",
6
6
  "bin": {