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.
package/client.js CHANGED
@@ -18,6 +18,7 @@ import { exec } from "child_process";
18
18
  import { promisify } from "util";
19
19
  import validations from "./commands/Validations.js";
20
20
  import Print from "./commands/Print.js";
21
+ import bundle, { cleanBundles, bundleInfo } from './commands/bundle/bundle.js';
21
22
 
22
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
24
 
@@ -137,12 +138,21 @@ sliceClient
137
138
  .description("Start development server")
138
139
  .option("-p, --port <port>", "Port for development server", 3000)
139
140
  .option("-w, --watch", "Enable watch mode for file changes")
141
+ .option("-b, --bundled", "Generate bundles before starting server")
140
142
  .action(async (options) => {
141
143
  await runWithVersionCheck(async () => {
144
+ // Si se solicita bundles, generarlos primero
145
+ if (options.bundled) {
146
+ Print.info("Generating bundles before starting server...");
147
+ await bundle({ verbose: false });
148
+ Print.newLine();
149
+ }
150
+
142
151
  await startServer({
143
- mode: 'development',
152
+ mode: options.bundled ? 'bundled' : 'development',
144
153
  port: parseInt(options.port),
145
- watch: options.watch
154
+ watch: options.watch,
155
+ bundled: options.bundled
146
156
  });
147
157
  });
148
158
  });
@@ -153,12 +163,21 @@ sliceClient
153
163
  .description("Start development server (alias for dev)")
154
164
  .option("-p, --port <port>", "Port for server", 3000)
155
165
  .option("-w, --watch", "Enable watch mode for file changes")
166
+ .option("-b, --bundled", "Generate bundles before starting server")
156
167
  .action(async (options) => {
157
168
  await runWithVersionCheck(async () => {
169
+ // Si se solicita bundles, generarlos primero
170
+ if (options.bundled) {
171
+ Print.info("Generating bundles before starting server...");
172
+ await bundle({ verbose: false });
173
+ Print.newLine();
174
+ }
175
+
158
176
  await startServer({
159
- mode: 'development',
177
+ mode: options.bundled ? 'bundled' : 'development',
160
178
  port: parseInt(options.port),
161
- watch: options.watch
179
+ watch: options.watch,
180
+ bundled: options.bundled
162
181
  });
163
182
  });
164
183
  });
@@ -434,13 +453,35 @@ sliceClient
434
453
  subcommandTerm: (cmd) => cmd.name() + ' ' + cmd.usage()
435
454
  });
436
455
 
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
+
437
476
  // Custom help - SIMPLIFICADO para development only
438
477
  sliceClient.addHelpText('after', `
439
478
  Common Usage Examples:
440
479
  slice init - Initialize new Slice.js project
441
480
  slice dev - Start development server
442
481
  slice start - Start development server (same as dev)
443
- slice get Button Card Input - Install Visual components from registry
482
+ slice dev --bundled - Generate bundles then start server
483
+ slice start --bundled - Same as above (bundle -> start)
484
+ slice get Button Card Input - Install Visual components from registry
444
485
  slice get FetchManager -s - Install Service component from registry
445
486
  slice browse - Browse all available components
446
487
  slice sync - Update local components to latest versions
@@ -456,9 +497,10 @@ Command Categories:
456
497
  • version, update, doctor - Maintenance commands
457
498
 
458
499
  Development Workflow:
459
- • slice init - Initialize project
460
- • slice dev - Start development server (serves from /src)
461
- • slice start - Alternative to dev command
500
+ • slice init - Initialize project
501
+ • slice dev - Start development server (serves from /src)
502
+ • slice start - Alternative to dev command
503
+ • slice dev --bundled - Start with bundles (bundle -> start)
462
504
 
463
505
  Note: Production builds are disabled. Use development mode for all workflows.
464
506
 
@@ -0,0 +1,226 @@
1
+ // cli/commands/bundle/bundle.js
2
+ import path from 'path';
3
+ import fs from 'fs-extra';
4
+ import DependencyAnalyzer from '../utils/bundling/DependencyAnalyzer.js';
5
+ import BundleGenerator from '../utils/bundling/BundleGenerator.js';
6
+ import Print from '..//Print.js';
7
+
8
+ /**
9
+ * Main bundling command
10
+ */
11
+ export default async function bundle(options = {}) {
12
+ const startTime = Date.now();
13
+ const projectRoot = process.cwd();
14
+
15
+ try {
16
+ Print.title('📦 Slice.js Bundle Generator');
17
+ Print.newLine();
18
+
19
+ // Validate that it's a Slice.js project
20
+ await validateProject(projectRoot);
21
+
22
+ // Phase 1: Analysis
23
+ Print.buildProgress('Analyzing project...');
24
+ const analyzer = new DependencyAnalyzer(import.meta.url);
25
+ const analysisData = await analyzer.analyze();
26
+
27
+ // Show report if in verbose mode
28
+ if (options.verbose || options.analyze) {
29
+ Print.newLine();
30
+ analyzer.generateReport(analysisData.metrics);
31
+ Print.newLine();
32
+ }
33
+
34
+ // If only analysis is requested, finish here
35
+ if (options.analyze) {
36
+ Print.success('Analysis completed');
37
+ return;
38
+ }
39
+
40
+ // Phase 2: Bundle generation
41
+ Print.buildProgress('Generating bundles...');
42
+ const generator = new BundleGenerator(import.meta.url, analysisData);
43
+ const result = await generator.generate();
44
+
45
+ // Phase 3: Save configuration
46
+ Print.buildProgress('Saving configuration...');
47
+ await generator.saveBundleConfig(result.config);
48
+
49
+ // Phase 4: Summary
50
+ Print.newLine();
51
+ printSummary(result, startTime);
52
+
53
+ // Show next step
54
+ Print.newLine();
55
+ Print.info('💡 Next step:');
56
+ console.log(' Update your Slice.js to use the generated bundles');
57
+ console.log(' Bundles will load automatically in production\n');
58
+
59
+ } catch (error) {
60
+ Print.error('Error generating bundles:', error.message);
61
+ console.error('\n📍 Complete stack trace:');
62
+ console.error(error.stack);
63
+
64
+ if (error.code) {
65
+ console.error('\n📍 Error code:', error.code);
66
+ }
67
+
68
+ process.exit(1);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Validates that the project has the correct structure
74
+ */
75
+ async function validateProject(projectRoot) {
76
+ const requiredPaths = [
77
+ 'src/Components/components.js',
78
+ 'src/routes.js'
79
+ ];
80
+
81
+ for (const reqPath of requiredPaths) {
82
+ const fullPath = path.join(projectRoot, reqPath);
83
+ if (!await fs.pathExists(fullPath)) {
84
+ throw new Error(`Required file not found: ${reqPath}`);
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Prints summary of generated bundles
91
+ */
92
+ function printSummary(result, startTime) {
93
+ const { bundles, config, files } = result;
94
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
95
+
96
+ Print.success(`Bundles generated in ${duration}s\n`);
97
+
98
+ // Critical bundle
99
+ if (bundles.critical.components.length > 0) {
100
+ Print.info('📦 Critical Bundle:');
101
+ console.log(` Components: ${bundles.critical.components.length}`);
102
+ console.log(` Size: ${(bundles.critical.size / 1024).toFixed(1)} KB`);
103
+ console.log(` File: ${bundles.critical.file}\n`);
104
+ }
105
+
106
+ // Route bundles
107
+ const routeCount = Object.keys(bundles.routes).length;
108
+ if (routeCount > 0) {
109
+ Print.info(`📦 Route Bundles (${routeCount}):`);
110
+
111
+ for (const [key, bundle] of Object.entries(bundles.routes)) {
112
+ console.log(` ${key}:`);
113
+ console.log(` Route: ${bundle.path}`);
114
+ console.log(` Components: ${bundle.components.length}`);
115
+ console.log(` Size: ${(bundle.size / 1024).toFixed(1)} KB`);
116
+ console.log(` File: ${bundle.file}`);
117
+ }
118
+ Print.newLine();
119
+ }
120
+
121
+ // Global statistics
122
+ Print.info('📊 Statistics:');
123
+ console.log(` Strategy: ${config.strategy}`);
124
+ console.log(` Total components: ${config.stats.totalComponents}`);
125
+ console.log(` Shared components: ${config.stats.sharedComponents}`);
126
+ console.log(` Generated files: ${files.length}`);
127
+
128
+ // Calculate estimated improvement
129
+ const beforeRequests = config.stats.totalComponents;
130
+ const afterRequests = files.length;
131
+ const improvement = Math.round((1 - afterRequests / beforeRequests) * 100);
132
+
133
+ console.log(` Request reduction: ${improvement}% (${beforeRequests} → ${afterRequests})`);
134
+ }
135
+
136
+ /**
137
+ * Subcommand: Clean bundles
138
+ */
139
+ export async function cleanBundles() {
140
+ const projectRoot = process.cwd();
141
+ const srcPath = path.join(projectRoot, 'src');
142
+
143
+ try {
144
+ Print.title('🧹 Cleaning bundles...');
145
+
146
+ const files = await fs.readdir(srcPath);
147
+ const bundleFiles = files.filter(f => f.startsWith('slice-bundle.'));
148
+
149
+ if (bundleFiles.length === 0) {
150
+ Print.warning('No bundles found to clean');
151
+ return;
152
+ }
153
+
154
+ for (const file of bundleFiles) {
155
+ await fs.remove(path.join(srcPath, file));
156
+ console.log(` ✓ Deleted: ${file}`);
157
+ }
158
+
159
+ // Remove config
160
+ const configPath = path.join(srcPath, 'bundle.config.json');
161
+ if (await fs.pathExists(configPath)) {
162
+ await fs.remove(configPath);
163
+ console.log(` ✓ Deleted: bundle.config.json`);
164
+ }
165
+
166
+ Print.newLine();
167
+ Print.success(`${bundleFiles.length} files deleted`);
168
+
169
+ } catch (error) {
170
+ Print.error('Error cleaning bundles:', error.message);
171
+ process.exit(1);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Subcommand: Bundle information
177
+ */
178
+ export async function bundleInfo() {
179
+ const projectRoot = process.cwd();
180
+ const configPath = path.join(projectRoot, 'src/bundle.config.json');
181
+
182
+ try {
183
+ if (!await fs.pathExists(configPath)) {
184
+ Print.warning('Bundle configuration not found');
185
+ Print.info('Run "slice bundle" to generate bundles');
186
+ return;
187
+ }
188
+
189
+ const config = await fs.readJson(configPath);
190
+
191
+ Print.title('📦 Bundle Information');
192
+ Print.newLine();
193
+
194
+ Print.info('Configuration:');
195
+ console.log(` Version: ${config.version}`);
196
+ console.log(` Strategy: ${config.strategy}`);
197
+ console.log(` Generated: ${new Date(config.generated).toLocaleString()}`);
198
+ Print.newLine();
199
+
200
+ Print.info('Statistics:');
201
+ console.log(` Total components: ${config.stats.totalComponents}`);
202
+ console.log(` Total routes: ${config.stats.totalRoutes}`);
203
+ console.log(` Shared components: ${config.stats.sharedComponents} (${config.stats.sharedPercentage}%)`);
204
+ console.log(` Total size: ${(config.stats.totalSize / 1024).toFixed(1)} KB`);
205
+ Print.newLine();
206
+
207
+ Print.info('Bundles:');
208
+
209
+ // Critical
210
+ if (config.bundles.critical) {
211
+ console.log(` Critical: ${config.bundles.critical.components.length} components, ${(config.bundles.critical.size / 1024).toFixed(1)} KB`);
212
+ }
213
+
214
+ // Routes
215
+ const routeCount = Object.keys(config.bundles.routes).length;
216
+ console.log(` Routes: ${routeCount} bundles`);
217
+
218
+ for (const [key, bundle] of Object.entries(config.bundles.routes)) {
219
+ console.log(` ${key}: ${bundle.components.length} components, ${(bundle.size / 1024).toFixed(1)} KB`);
220
+ }
221
+
222
+ } catch (error) {
223
+ Print.error('Error reading information:', error.message);
224
+ process.exit(1);
225
+ }
226
+ }
@@ -9,6 +9,7 @@ import inquirer from 'inquirer';
9
9
  import { exec } from 'child_process';
10
10
  import { promisify } from 'util';
11
11
  import { getProjectRoot, getSrcPath, getApiPath, getConfigPath, getPath } from '../utils/PathHelper.js';
12
+ import updateManager from '../utils/updateManager.js';
12
13
 
13
14
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
15
 
@@ -148,7 +149,8 @@ async function checkPort() {
148
149
  * Verifica dependencias en package.json
149
150
  */
150
151
  async function checkDependencies() {
151
- const packagePath = getPath(import.meta.url, '', 'package.json');
152
+ const projectRoot = getProjectRoot(import.meta.url);
153
+ const packagePath = path.join(projectRoot, 'package.json');
152
154
 
153
155
  if (!await fs.pathExists(packagePath)) {
154
156
  return {
@@ -160,18 +162,18 @@ async function checkDependencies() {
160
162
 
161
163
  try {
162
164
  const pkg = await fs.readJson(packagePath);
163
- const hasCli = pkg.dependencies?.['slicejs-cli'] || pkg.devDependencies?.['slicejs-cli'];
164
- const hasFramework = pkg.dependencies?.['slicejs-web-framework'];
165
+ const hasFrameworkDep = pkg.dependencies?.['slicejs-web-framework'] || pkg.devDependencies?.['slicejs-web-framework'];
166
+ const frameworkNodePath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'package.json');
167
+ const hasFrameworkNode = await fs.pathExists(frameworkNodePath);
168
+ const hasFramework = !!(hasFrameworkDep || hasFrameworkNode);
165
169
 
166
- if (hasCli && hasFramework) {
170
+ if (hasFramework) {
167
171
  return {
168
172
  pass: true,
169
- message: 'All required dependencies are installed'
173
+ message: 'Required framework dependency is installed'
170
174
  };
171
175
  } else {
172
- const missing = [];
173
- if (!hasCli) missing.push('slicejs-cli');
174
- if (!hasFramework) missing.push('slicejs-web-framework');
176
+ const missing = ['slicejs-web-framework'];
175
177
 
176
178
  return {
177
179
  warn: true,
@@ -334,9 +336,7 @@ export default async function runDiagnostics() {
334
336
  if (confirmInstall) {
335
337
  for (const pkg of depsResult.missing) {
336
338
  try {
337
- const cmd = pkg === 'slicejs-cli'
338
- ? 'npm install -D slicejs-cli@latest'
339
- : 'npm install slicejs-web-framework@latest';
339
+ const cmd = 'npm install slicejs-web-framework@latest';
340
340
  Print.info(`Installing ${pkg}...`);
341
341
  await execAsync(cmd, { cwd: projectRoot });
342
342
  Print.success(`${pkg} installed`);
@@ -7,6 +7,7 @@ import inquirer from "inquirer";
7
7
  import validations from "../Validations.js";
8
8
  import Print from "../Print.js";
9
9
  import { getConfigPath, getComponentsJsPath, getPath } from "../utils/PathHelper.js";
10
+ import ora from "ora";
10
11
 
11
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
13
 
@@ -188,32 +189,51 @@ filterOfficialComponents(allComponents) {
188
189
 
189
190
  const downloadedFiles = [];
190
191
  const failedFiles = [];
191
- Print.info(`Downloading ${componentName} from official repository...`);
192
-
193
- for (const fileName of component.files) {
194
- const githubUrl = `${DOCS_REPO_BASE_URL}/${category}/${componentName}/${fileName}`;
192
+ const total = component.files.length;
193
+ let done = 0;
194
+ const spinner = ora(`Downloading ${componentName} 0/${total}`).start();
195
+ const fetchWithRetry = async (url, retries = 3, baseDelay = 500) => {
196
+ for (let attempt = 0; attempt <= retries; attempt++) {
197
+ try {
198
+ const response = await fetch(url);
199
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
200
+ return await response.text();
201
+ } catch (e) {
202
+ if (attempt === retries) throw e;
203
+ const delay = baseDelay * Math.pow(2, attempt);
204
+ await new Promise(r => setTimeout(r, delay));
205
+ }
206
+ }
207
+ };
208
+ const worker = async (fileName) => {
209
+ const url = `${DOCS_REPO_BASE_URL}/${category}/${componentName}/${fileName}`;
195
210
  const localPath = path.join(targetPath, fileName);
196
-
197
211
  try {
198
- const response = await fetch(githubUrl);
199
-
200
- if (!response.ok) {
201
- Print.downloadError(fileName, `HTTP ${response.status}: ${response.statusText}`);
202
- failedFiles.push(fileName);
203
- continue; // ✅ CONTINUAR en lugar de lanzar error
204
- }
205
-
206
- const content = await response.text();
212
+ const content = await fetchWithRetry(url);
207
213
  await fs.writeFile(localPath, content, 'utf8');
208
214
  downloadedFiles.push(fileName);
209
-
210
215
  Print.downloadSuccess(fileName);
211
216
  } catch (error) {
212
217
  Print.downloadError(fileName, error.message);
213
218
  failedFiles.push(fileName);
214
- continue; // ✅ CONTINUAR en lugar de lanzar error
219
+ } finally {
220
+ done += 1;
221
+ spinner.text = `Downloading ${componentName} ${done}/${total}`;
215
222
  }
216
- }
223
+ };
224
+ const runConcurrent = async (items, concurrency = 3) => {
225
+ let index = 0;
226
+ const runners = Array(Math.min(concurrency, items.length)).fill(0).map(async () => {
227
+ while (true) {
228
+ const i = index++;
229
+ if (i >= items.length) break;
230
+ await worker(items[i]);
231
+ }
232
+ });
233
+ await Promise.all(runners);
234
+ };
235
+ await runConcurrent(component.files, 3);
236
+ spinner.stop();
217
237
 
218
238
  // ✅ NUEVO: Solo lanzar error si NO se descargó el archivo principal (.js)
219
239
  const mainFileDownloaded = downloadedFiles.some(file => file.endsWith('.js'));
@@ -311,7 +331,7 @@ filterOfficialComponents(allComponents) {
311
331
  const availableComponents = this.getAvailableComponents(category);
312
332
 
313
333
  if (!availableComponents[componentName]) {
314
- throw new Error(`Componente '${componentName}' no encontrado en la categoría '${category}' del repositorio oficial`);
334
+ throw new Error(`Component '${componentName}' not found in category '${category}' in the official repository`);
315
335
  }
316
336
 
317
337
  // ✅ MEJORADO: Detectar si validations tiene acceso a la configuración
@@ -347,7 +367,7 @@ filterOfficialComponents(allComponents) {
347
367
  {
348
368
  type: 'confirm',
349
369
  name: 'overwrite',
350
- message: `El componente '${componentName}' ya existe localmente. ¿Deseas sobrescribirlo con la versión del repositorio?`,
370
+ message: `The component '${componentName}' already exists locally. Overwrite with the repository version?`,
351
371
  default: false
352
372
  }
353
373
  ]);
@@ -395,16 +415,34 @@ filterOfficialComponents(allComponents) {
395
415
  async installMultipleComponents(componentNames, category = 'Visual', force = false) {
396
416
  const results = [];
397
417
  Print.info(`Getting ${componentNames.length} ${category} components from official repository...`);
398
-
399
- for (const componentName of componentNames) {
418
+ const total = componentNames.length;
419
+ let done = 0;
420
+ const spinner = ora(`Installing 0/${total}`).start();
421
+ const worker = async (componentName) => {
400
422
  try {
401
423
  const result = await this.installComponent(componentName, category, force);
402
424
  results.push({ name: componentName, success: result });
403
425
  } catch (error) {
404
426
  Print.componentError(componentName, 'getting', error.message);
405
427
  results.push({ name: componentName, success: false, error: error.message });
428
+ } finally {
429
+ done += 1;
430
+ spinner.text = `Installing ${done}/${total}`;
406
431
  }
407
- }
432
+ };
433
+ const runConcurrent = async (items, concurrency = 3) => {
434
+ let index = 0;
435
+ const runners = Array(Math.min(concurrency, items.length)).fill(0).map(async () => {
436
+ while (true) {
437
+ const i = index++;
438
+ if (i >= items.length) break;
439
+ await worker(items[i]);
440
+ }
441
+ });
442
+ await Promise.all(runners);
443
+ };
444
+ await runConcurrent(componentNames, 3);
445
+ spinner.stop();
408
446
 
409
447
  // Summary
410
448
  const successful = results.filter(r => r.success).length;
@@ -492,11 +530,11 @@ filterOfficialComponents(allComponents) {
492
530
 
493
531
  displayAvailableComponents() {
494
532
  if (!this.componentsRegistry) {
495
- Print.error('❌ No se pudo cargar el registro de componentes');
533
+ Print.error('❌ Could not load component registry');
496
534
  return;
497
535
  }
498
536
 
499
- console.log('\n📚 Componentes disponibles en el repositorio oficial de Slice.js:\n');
537
+ console.log('\n📚 Available components in the official Slice.js repository:\n');
500
538
 
501
539
  const visualComponents = this.getAvailableComponents('Visual');
502
540
  const serviceComponents = this.getAvailableComponents('Service');
@@ -522,10 +560,10 @@ filterOfficialComponents(allComponents) {
522
560
  Print.newLine();
523
561
  Print.info(`Total: ${Object.keys(visualComponents).length} Visual + ${Object.keys(serviceComponents).length} Service components`);
524
562
 
525
- console.log(`\n💡 Ejemplos de uso:`);
526
- console.log(`slice get Button Card Input # Obtener componentes Visual`);
527
- console.log(`slice get FetchManager --service # Obtener componente Service`);
528
- console.log(`slice sync # Sincronizar componentes Visual`);
563
+ console.log(`\n💡 Usage examples:`);
564
+ console.log(`slice get Button Card Input # Install Visual components`);
565
+ console.log(`slice get FetchManager --service # Install Service component`);
566
+ console.log(`slice sync # Sync Visual components`);
529
567
  }
530
568
 
531
569
  async interactiveInstall() {
@@ -533,7 +571,7 @@ filterOfficialComponents(allComponents) {
533
571
  {
534
572
  type: 'list',
535
573
  name: 'componentType',
536
- message: 'Selecciona el tipo de componente a obtener del repositorio:',
574
+ message: 'Select the type of component to install from the repository:',
537
575
  choices: [
538
576
  { name: '🎨 Visual Components (UI)', value: 'Visual' },
539
577
  { name: '⚙️ Service Components (Logic)', value: 'Service' }
@@ -552,10 +590,10 @@ filterOfficialComponents(allComponents) {
552
590
  {
553
591
  type: 'list',
554
592
  name: 'installMode',
555
- message: '¿Cómo deseas obtener los componentes Visual?',
593
+ message: 'How do you want to install Visual components?',
556
594
  choices: [
557
- { name: 'Obtener uno solo', value: 'single' },
558
- { name: 'Obtener múltiples', value: 'multiple' }
595
+ { name: 'Get one', value: 'single' },
596
+ { name: 'Get multiple', value: 'multiple' }
559
597
  ]
560
598
  }
561
599
  ]);
@@ -565,11 +603,11 @@ filterOfficialComponents(allComponents) {
565
603
  {
566
604
  type: 'checkbox',
567
605
  name: 'selectedComponents',
568
- message: 'Selecciona los componentes Visual a obtener del repositorio:',
606
+ message: 'Select Visual components to install from the repository:',
569
607
  choices: componentChoices,
570
608
  validate: (input) => {
571
609
  if (input.length === 0) {
572
- return 'Debes seleccionar al menos un componente';
610
+ return 'You must select at least one component';
573
611
  }
574
612
  return true;
575
613
  }
@@ -582,7 +620,7 @@ filterOfficialComponents(allComponents) {
582
620
  {
583
621
  type: 'list',
584
622
  name: 'selectedComponent',
585
- message: 'Selecciona un componente Visual:',
623
+ message: 'Select a Visual component:',
586
624
  choices: componentChoices
587
625
  }
588
626
  ]);
@@ -594,7 +632,7 @@ filterOfficialComponents(allComponents) {
594
632
  {
595
633
  type: 'list',
596
634
  name: 'selectedComponent',
597
- message: 'Selecciona un componente Service:',
635
+ message: 'Select a Service component:',
598
636
  choices: componentChoices
599
637
  }
600
638
  ]);