slicejs-cli 3.4.0 → 3.5.0

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.
Files changed (35) hide show
  1. package/AGENTS.md +247 -0
  2. package/client.js +63 -64
  3. package/commands/Print.js +11 -15
  4. package/commands/Validations.js +12 -23
  5. package/commands/buildProduction/buildProduction.js +23 -26
  6. package/commands/bundle/bundle.js +10 -11
  7. package/commands/createComponent/createComponent.js +14 -16
  8. package/commands/deleteComponent/deleteComponent.js +6 -6
  9. package/commands/doctor/doctor.js +11 -14
  10. package/commands/getComponent/getComponent.js +99 -162
  11. package/commands/init/init.js +77 -26
  12. package/commands/listComponents/listComponents.js +18 -21
  13. package/commands/startServer/startServer.js +21 -24
  14. package/commands/startServer/watchServer.js +7 -7
  15. package/commands/types/types.js +53 -18
  16. package/commands/utils/PathHelper.js +9 -2
  17. package/commands/utils/VersionChecker.js +3 -3
  18. package/commands/utils/bundling/DependencyAnalyzer.js +8 -16
  19. package/commands/utils/loadConfig.js +31 -0
  20. package/commands/utils/updateManager.js +3 -4
  21. package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +105 -105
  22. package/package.json +14 -2
  23. package/post.js +2 -2
  24. package/tests/bundle-generator.test.js +3 -20
  25. package/tests/component-registry-parse.test.js +34 -0
  26. package/tests/fixtures/components.js +8 -0
  27. package/tests/fixtures/sliceConfig.json +74 -0
  28. package/tests/getcomponent.test.js +407 -0
  29. package/tests/helpers/setup.js +97 -0
  30. package/tests/init-command-contract.test.js +46 -0
  31. package/tests/local-cli-delegation.test.js +7 -5
  32. package/tests/path-helper.test.js +206 -0
  33. package/tests/types-breakage.test.js +491 -0
  34. package/tests/types-generator-errors.test.js +361 -0
  35. package/tests/types-generator.test.js +172 -184
@@ -2,87 +2,108 @@
2
2
 
3
3
  import fs from "fs-extra";
4
4
  import path from "path";
5
- import { fileURLToPath } from "url";
6
5
  import inquirer from "inquirer";
7
6
  import validations from "../Validations.js";
8
7
  import Print from "../Print.js";
9
- import { getConfigPath, getComponentsJsPath, getPath } from "../utils/PathHelper.js";
8
+ import { getComponentsJsPath, getPath } from "../utils/PathHelper.js";
9
+ import { loadConfig as sharedLoadConfig } from "../utils/loadConfig.js";
10
10
  import ora from "ora";
11
11
 
12
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
-
14
- // Base URL del repositorio de documentación de Slice.js
12
+ // Base URL of the Slice.js documentation repository
15
13
  const DOCS_REPO_BASE_URL = 'https://raw.githubusercontent.com/VKneider/slice.js_visual_library/master/src/Components';
16
14
  const COMPONENTS_REGISTRY_URL = 'https://raw.githubusercontent.com/VKneider/slice.js_visual_library/master/src/Components/components.js';
17
15
 
18
16
  /**
19
- * Carga la configuración desde sliceConfig.json
20
- * @returns {object} - Objeto de configuración
17
+ * Loads configuration from sliceConfig.json
18
+ * @returns {object} - Configuration object
21
19
  */
22
- const loadConfig = () => {
23
- try {
24
- const configPath = getConfigPath(import.meta.url);
25
- if (!fs.existsSync(configPath)) {
26
- throw new Error('sliceConfig.json not found in src folder');
20
+ const loadConfig = () => sharedLoadConfig(import.meta.url);
21
+
22
+ const fetchWithRetry = async (url, retries = 3, baseDelay = 500) => {
23
+ for (let attempt = 0; attempt <= retries; attempt++) {
24
+ try {
25
+ const response = await fetch(url);
26
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
27
+ return await response.text();
28
+ } catch (e) {
29
+ if (attempt === retries) throw e;
30
+ const delay = baseDelay * Math.pow(2, attempt);
31
+ await new Promise(r => setTimeout(r, delay));
27
32
  }
28
- const rawData = fs.readFileSync(configPath, 'utf-8');
29
- return JSON.parse(rawData);
30
- } catch (error) {
31
- console.error(`Error loading configuration: ${error.message}`);
32
- return null;
33
33
  }
34
34
  };
35
35
 
36
+ const runConcurrent = async (items, worker, concurrency = 3) => {
37
+ let index = 0;
38
+ const runners = Array(Math.min(concurrency, items.length)).fill(0).map(async () => {
39
+ while (true) {
40
+ const i = index++;
41
+ if (i >= items.length) break;
42
+ await worker(items[i]);
43
+ }
44
+ });
45
+ await Promise.all(runners);
46
+ };
47
+
36
48
  class ComponentRegistry {
37
49
  constructor() {
38
50
  this.componentsRegistry = null;
39
- this.config = loadConfig();
51
+ this.config = null;
52
+ this._configPromise = null;
40
53
  }
41
54
 
42
- async loadRegistry() {
43
- Print.info('Loading component registry from official repository...');
44
-
45
- try {
46
- const response = await fetch(COMPONENTS_REGISTRY_URL);
47
-
48
- if (!response.ok) {
49
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
55
+ async _ensureConfig() {
56
+ if (!this.config && !this._configPromise) {
57
+ this._configPromise = loadConfig();
50
58
  }
51
-
52
- const content = await response.text();
53
-
54
- // Parse the components.js file content
55
- const match = content.match(/const components = ({[\s\S]*?});/);
56
- if (!match) {
57
- throw new Error('Invalid components.js format from repository');
59
+ if (this._configPromise) {
60
+ this.config = await this._configPromise;
58
61
  }
62
+ }
59
63
 
60
- const allComponents = eval('(' + match[1] + ')');
61
-
62
- // NUEVO: FILTRAR solo componentes Visual y Service
63
- this.componentsRegistry = this.filterOfficialComponents(allComponents);
64
-
65
- Print.success('Component registry loaded successfully');
66
-
67
- } catch (error) {
68
- Print.error(`Loading component registry: ${error.message}`);
69
- Print.info('Check your internet connection and repository accessibility');
70
- throw error;
64
+ async loadRegistry() {
65
+ await this._ensureConfig();
66
+ Print.info('Loading component registry from official repository...');
67
+
68
+ try {
69
+ const response = await fetch(COMPONENTS_REGISTRY_URL);
70
+
71
+ if (!response.ok) {
72
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
73
+ }
74
+
75
+ const content = await response.text();
76
+
77
+ // Parse the components.js file content
78
+ const match = content.match(/const components = ({[\s\S]*?});/);
79
+ if (!match) {
80
+ throw new Error('Invalid components.js format from repository');
81
+ }
82
+
83
+ const allComponents = JSON.parse(match[1]);
84
+
85
+ // Filter only Visual and Service components
86
+ this.componentsRegistry = this.filterOfficialComponents(allComponents);
87
+
88
+ Print.success('Component registry loaded successfully');
89
+
90
+ } catch (error) {
91
+ throw error;
92
+ }
71
93
  }
72
- }
73
94
 
74
95
  /**
75
- * Filtra el registry para incluir SOLO componentes de categorías Visual y Service
76
- * Excluye AppComponents y cualquier otra categoría
77
- * @param {object} allComponents - Objeto con todos los componentes del registry
78
- * @returns {object} - Objeto filtrado solo con Visual y Service
96
+ * Filters the registry to include ONLY Visual and Service category components
97
+ * Excludes AppComponents and any other category
98
+ * @param {object} allComponents - Object with all registry components
99
+ * @returns {object} - Filtered object with only Visual and Service
79
100
  */
80
101
  filterOfficialComponents(allComponents) {
81
102
  const filtered = {};
82
103
  let excludedCount = 0;
83
104
 
84
105
  Object.entries(allComponents).forEach(([name, category]) => {
85
- // Solo incluir componentes de categoría Visual o Service
106
+ // Only include Visual or Service category components
86
107
  if (category === 'Visual' || category === 'Service') {
87
108
  filtered[name] = category;
88
109
  } else {
@@ -112,14 +133,15 @@ filterOfficialComponents(allComponents) {
112
133
  return {};
113
134
  }
114
135
 
115
- return eval('(' + match[1] + ')');
136
+ return JSON.parse(match[1]);
116
137
  } catch (error) {
117
- Print.warning('⚠️ No se pudo leer el registro local de componentes');
138
+ Print.warning(`⚠️ Could not read the local component registry at: ${componentsPath}`);
118
139
  return {};
119
140
  }
120
141
  }
121
142
 
122
143
  async findUpdatableComponents() {
144
+ await this._ensureConfig();
123
145
  const localComponents = await this.getLocalComponents();
124
146
  const updatableComponents = [];
125
147
 
@@ -129,7 +151,7 @@ filterOfficialComponents(allComponents) {
129
151
  // Check if local component directory exists using dynamic paths
130
152
  const categoryPath = validations.getCategoryPath(category);
131
153
 
132
- // CORREGIDO: Usar 4 niveles para compatibilidad con node_modules
154
+ // Use 4 levels for node_modules compatibility
133
155
  const isProduction = this.config?.production?.enabled === true;
134
156
  const folderSuffix = isProduction ? 'dist' : 'src';
135
157
  const componentPath = getPath(import.meta.url, folderSuffix, categoryPath, name);
@@ -153,18 +175,18 @@ filterOfficialComponents(allComponents) {
153
175
  const components = {};
154
176
  Object.entries(this.componentsRegistry).forEach(([name, componentCategory]) => {
155
177
  if (!category || componentCategory === category) {
156
- // CORREGIDO: Componentes especiales que no necesitan todos los archivos
178
+ // Special components that don't need all files
157
179
  let files;
158
180
  if (componentCategory === 'Visual') {
159
- // Componentes de routing lógico solo necesitan JS
160
- if (['Route', 'MultiRoute', 'NotFound'].includes(name)) {
181
+ // Logical routing components only need JS
182
+ if (['Route', 'MultiRoute', 'Link'].includes(name)) {
161
183
  files = [`${name}.js`];
162
184
  } else {
163
- // Componentes visuales normales necesitan JS, HTML, CSS
185
+ // Normal visual components need JS, HTML, CSS
164
186
  files = [`${name}.js`, `${name}.html`, `${name}.css`];
165
187
  }
166
188
  } else {
167
- // Service components solo necesitan JS
189
+ // Service components only need JS
168
190
  files = [`${name}.js`];
169
191
  }
170
192
 
@@ -192,19 +214,6 @@ filterOfficialComponents(allComponents) {
192
214
  const total = component.files.length;
193
215
  let done = 0;
194
216
  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
217
  const worker = async (fileName) => {
209
218
  const url = `${DOCS_REPO_BASE_URL}/${category}/${componentName}/${fileName}`;
210
219
  const localPath = path.join(targetPath, fileName);
@@ -212,37 +221,25 @@ filterOfficialComponents(allComponents) {
212
221
  const content = await fetchWithRetry(url);
213
222
  await fs.writeFile(localPath, content, 'utf8');
214
223
  downloadedFiles.push(fileName);
215
- Print.downloadSuccess(fileName);
216
224
  } catch (error) {
217
- Print.downloadError(fileName, error.message);
225
+ Print.downloadError(fileName);
218
226
  failedFiles.push(fileName);
219
227
  } finally {
220
228
  done += 1;
221
229
  spinner.text = `Downloading ${componentName} ${done}/${total}`;
222
230
  }
223
231
  };
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);
232
+ await runConcurrent(component.files, worker, 3);
236
233
  spinner.stop();
237
234
 
238
- // NUEVO: Solo lanzar error si NO se descargó el archivo principal (.js)
235
+ // Only throw error if main file (.js) was not downloaded
239
236
  const mainFileDownloaded = downloadedFiles.some(file => file.endsWith('.js'));
240
237
 
241
238
  if (!mainFileDownloaded) {
242
239
  throw new Error(`Failed to download main component file (${componentName}.js)`);
243
240
  }
244
241
 
245
- // ADVERTENCIA: Informar sobre archivos que fallaron (pero no detener el proceso)
242
+ // Report files that failed (but don't stop the process)
246
243
  if (failedFiles.length > 0) {
247
244
  Print.warning(`Some files couldn't be downloaded: ${failedFiles.join(', ')}`);
248
245
  Print.info('Component installed with available files');
@@ -252,7 +249,7 @@ filterOfficialComponents(allComponents) {
252
249
  }
253
250
 
254
251
  async updateLocalRegistrySafe(componentName, category) {
255
- const componentsPath = path.join(__dirname, '../../../../src/Components/components.js');
252
+ const componentsPath = getComponentsJsPath(import.meta.url);
256
253
  try {
257
254
  if (!await fs.pathExists(componentsPath)) {
258
255
  const dir = path.dirname(componentsPath);
@@ -263,7 +260,7 @@ filterOfficialComponents(allComponents) {
263
260
  const content = await fs.readFile(componentsPath, 'utf8');
264
261
  const match = content.match(/const components = ({[\s\S]*?});/);
265
262
  if (!match) throw new Error('Invalid components.js format in local project');
266
- const componentsObj = eval('(' + match[1] + ')');
263
+ const componentsObj = JSON.parse(match[1]);
267
264
  if (!componentsObj[componentName]) {
268
265
  componentsObj[componentName] = category;
269
266
  const sorted = Object.keys(componentsObj)
@@ -276,65 +273,19 @@ filterOfficialComponents(allComponents) {
276
273
  Print.info(`${componentName} already exists in local registry`);
277
274
  }
278
275
  } catch (error) {
279
- Print.error(`Updating local components.js: ${error.message}`);
280
- throw error;
281
- }
282
- }
283
-
284
- async updateLocalRegistry(componentName, category) {
285
- // ✅ CORREGIDO: Usar 4 niveles para compatibilidad con node_modules
286
- const componentsPath = path.join(__dirname, '../../../../src/Components/components.js');
287
-
288
- try {
289
- let content = await fs.readFile(componentsPath, 'utf8');
290
-
291
- // Parse existing components
292
- const componentsMatch = content.match(/const components = ({[\s\S]*?});/);
293
- if (!componentsMatch) {
294
- throw new Error('Invalid components.js format in local project');
295
- }
296
-
297
- const componentsObj = eval('(' + componentsMatch[1] + ')');
298
-
299
- // Add new component if it doesn't exist
300
- if (!componentsObj[componentName]) {
301
- componentsObj[componentName] = category;
302
-
303
- // Generate new content
304
- const sortedComponents = Object.keys(componentsObj)
305
- .sort()
306
- .reduce((obj, key) => {
307
- obj[key] = componentsObj[key];
308
- return obj;
309
- }, {});
310
-
311
- const newComponentsString = JSON.stringify(sortedComponents, null, 2)
312
- .replace(/"/g, '"')
313
- .replace(/: "/g, ': "')
314
- .replace(/",\n/g, '",\n');
315
-
316
- const newContent = `const components = ${newComponentsString}; export default components;`;
317
-
318
- await fs.writeFile(componentsPath, newContent, 'utf8');
319
- Print.registryUpdate(`Registered ${componentName} in local components.js`);
320
- } else {
321
- Print.info(`${componentName} already exists in local registry`);
322
- }
323
-
324
- } catch (error) {
325
- Print.error(`Updating local components.js: ${error.message}`);
326
276
  throw error;
327
277
  }
328
278
  }
329
279
 
330
280
  async installComponent(componentName, category, force = false) {
281
+ await this._ensureConfig();
331
282
  const availableComponents = this.getAvailableComponents(category);
332
283
 
333
284
  if (!availableComponents[componentName]) {
334
285
  throw new Error(`Component '${componentName}' not found in category '${category}' in the official repository`);
335
286
  }
336
287
 
337
- // MEJORADO: Detectar si validations tiene acceso a la configuración
288
+ // Detect if validations has access to configuration
338
289
  let categoryPath;
339
290
  const hasValidConfig = validations.config &&
340
291
  validations.config.paths &&
@@ -394,16 +345,13 @@ filterOfficialComponents(allComponents) {
394
345
  return true;
395
346
 
396
347
  } catch (error) {
397
- Print.error(`Error installing ${componentName}: ${error.message}`);
398
-
399
- // ✅ MEJORADO: Solo borrar si el archivo principal (.js) no existe
348
+ // Only clean up if main file (.js) does not exist
400
349
  const mainFilePath = path.join(targetPath, `${componentName}.js`);
401
350
  const mainFileExists = await fs.pathExists(mainFilePath);
402
351
 
403
352
  if (!mainFileExists && await fs.pathExists(targetPath)) {
404
- // Solo limpiar si no se instaló el archivo principal
353
+ // Only clean up if main file was not installed
405
354
  await fs.remove(targetPath);
406
- Print.info('Cleaned up failed installation');
407
355
  } else if (mainFileExists) {
408
356
  Print.warning('Component partially installed - main file exists');
409
357
  }
@@ -430,18 +378,7 @@ filterOfficialComponents(allComponents) {
430
378
  spinner.text = `Installing ${done}/${total}`;
431
379
  }
432
380
  };
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);
381
+ await runConcurrent(componentNames, worker, 3);
445
382
  spinner.stop();
446
383
 
447
384
  // Summary
@@ -459,7 +396,7 @@ filterOfficialComponents(allComponents) {
459
396
 
460
397
  const allUpdatableComponents = await this.findUpdatableComponents();
461
398
 
462
- // NUEVO: Filtrar solo componentes Visual
399
+ // Filter only Visual components
463
400
  const updatableComponents = allUpdatableComponents.filter(comp => comp.category === 'Visual');
464
401
 
465
402
  if (updatableComponents.length === 0) {
@@ -468,7 +405,7 @@ filterOfficialComponents(allComponents) {
468
405
  return true;
469
406
  }
470
407
 
471
- // Mostrar estadísticas si hay componentes Service que no se sincronizarán
408
+ // Show statistics if there are Service components that won't be synced
472
409
  const serviceComponents = allUpdatableComponents.filter(comp => comp.category === 'Service');
473
410
  if (serviceComponents.length > 0) {
474
411
  Print.info(`Found ${serviceComponents.length} Service components (skipped - sync only affects Visual components)`);
@@ -497,7 +434,7 @@ filterOfficialComponents(allComponents) {
497
434
  }
498
435
  }
499
436
 
500
- // SIMPLIFICADO: Solo actualizar componentes Visual
437
+ // Only update Visual components
501
438
  const visualComponentNames = updatableComponents.map(c => c.name);
502
439
 
503
440
  Print.info(`Updating ${visualComponentNames.length} Visual components...`);
@@ -517,7 +454,7 @@ filterOfficialComponents(allComponents) {
517
454
  Print.success('All your Visual components are now updated to the latest official versions!');
518
455
  }
519
456
 
520
- // Información adicional sobre Service components
457
+ // Additional information about Service components
521
458
  if (serviceComponents.length > 0) {
522
459
  Print.newLine();
523
460
  Print.info(`Note: ${serviceComponents.length} Service components were found but not updated`);
@@ -539,7 +476,7 @@ filterOfficialComponents(allComponents) {
539
476
  const visualComponents = this.getAvailableComponents('Visual');
540
477
  const serviceComponents = this.getAvailableComponents('Service');
541
478
 
542
- // SIMPLIFICADO: Solo mostrar nombres sin descripciones
479
+ // Only show names without descriptions
543
480
  Print.info('🎨 Visual Components (UI):');
544
481
  Object.keys(visualComponents).forEach(name => {
545
482
  const files = visualComponents[name].files;
@@ -695,7 +632,7 @@ async function getComponents(componentNames = [], options = {}) {
695
632
  await registry.installComponent(componentInfo.name, actualCategory, options.force);
696
633
  return true;
697
634
  } catch (error) {
698
- Print.error(`${error.message}`);
635
+ Print.error(`Error installing component: ${error.message}`);
699
636
  return false;
700
637
  }
701
638
  } else {
@@ -708,7 +645,7 @@ async function getComponents(componentNames = [], options = {}) {
708
645
  await registry.installMultipleComponents(normalizedComponents, category, options.force);
709
646
  return true;
710
647
  } catch (error) {
711
- Print.error(`${error.message}`);
648
+ Print.error(`Error installing components: ${error.message}`);
712
649
  return false;
713
650
  }
714
651
  }
@@ -744,4 +681,4 @@ async function syncComponents(options = {}) {
744
681
  }
745
682
 
746
683
  export default getComponents;
747
- export { listComponents, syncComponents, ComponentRegistry };
684
+ export { listComponents, syncComponents, ComponentRegistry, loadConfig, runConcurrent, fetchWithRetry };