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.
- package/AGENTS.md +247 -0
- package/client.js +63 -64
- package/commands/Print.js +11 -15
- package/commands/Validations.js +12 -23
- package/commands/buildProduction/buildProduction.js +23 -26
- package/commands/bundle/bundle.js +10 -11
- package/commands/createComponent/createComponent.js +14 -16
- package/commands/deleteComponent/deleteComponent.js +6 -6
- package/commands/doctor/doctor.js +11 -14
- package/commands/getComponent/getComponent.js +99 -162
- package/commands/init/init.js +77 -26
- package/commands/listComponents/listComponents.js +18 -21
- package/commands/startServer/startServer.js +21 -24
- package/commands/startServer/watchServer.js +7 -7
- package/commands/types/types.js +53 -18
- package/commands/utils/PathHelper.js +9 -2
- package/commands/utils/VersionChecker.js +3 -3
- package/commands/utils/bundling/DependencyAnalyzer.js +8 -16
- package/commands/utils/loadConfig.js +31 -0
- package/commands/utils/updateManager.js +3 -4
- package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +105 -105
- package/package.json +14 -2
- package/post.js +2 -2
- package/tests/bundle-generator.test.js +3 -20
- package/tests/component-registry-parse.test.js +34 -0
- package/tests/fixtures/components.js +8 -0
- package/tests/fixtures/sliceConfig.json +74 -0
- package/tests/getcomponent.test.js +407 -0
- package/tests/helpers/setup.js +97 -0
- package/tests/init-command-contract.test.js +46 -0
- package/tests/local-cli-delegation.test.js +7 -5
- package/tests/path-helper.test.js +206 -0
- package/tests/types-breakage.test.js +491 -0
- package/tests/types-generator-errors.test.js +361 -0
- 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 {
|
|
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
|
-
|
|
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
|
-
*
|
|
20
|
-
* @returns {object} -
|
|
17
|
+
* Loads configuration from sliceConfig.json
|
|
18
|
+
* @returns {object} - Configuration object
|
|
21
19
|
*/
|
|
22
|
-
const loadConfig = () =>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 =
|
|
51
|
+
this.config = null;
|
|
52
|
+
this._configPromise = null;
|
|
40
53
|
}
|
|
41
54
|
|
|
42
|
-
async
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* @param {object} allComponents -
|
|
78
|
-
* @returns {object} -
|
|
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
|
-
//
|
|
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
|
|
136
|
+
return JSON.parse(match[1]);
|
|
116
137
|
} catch (error) {
|
|
117
|
-
Print.warning(
|
|
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
|
-
//
|
|
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
|
-
//
|
|
178
|
+
// Special components that don't need all files
|
|
157
179
|
let files;
|
|
158
180
|
if (componentCategory === 'Visual') {
|
|
159
|
-
//
|
|
160
|
-
if (['Route', 'MultiRoute', '
|
|
181
|
+
// Logical routing components only need JS
|
|
182
|
+
if (['Route', 'MultiRoute', 'Link'].includes(name)) {
|
|
161
183
|
files = [`${name}.js`];
|
|
162
184
|
} else {
|
|
163
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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(
|
|
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(
|
|
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 };
|