slicejs-cli 2.8.6 → 2.9.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 +347 -315
- package/client.js +526 -539
- package/commands/Print.js +167 -167
- package/commands/Validations.js +103 -103
- package/commands/build/build.js +40 -0
- package/commands/buildProduction/buildProduction.js +45 -10
- package/commands/bundle/bundle.js +235 -231
- package/commands/createComponent/VisualComponentTemplate.js +55 -55
- package/commands/createComponent/createComponent.js +126 -126
- package/commands/deleteComponent/deleteComponent.js +77 -77
- package/commands/doctor/doctor.js +369 -369
- package/commands/getComponent/getComponent.js +747 -747
- package/commands/init/init.js +261 -261
- package/commands/listComponents/listComponents.js +175 -175
- package/commands/startServer/startServer.js +260 -270
- package/commands/startServer/watchServer.js +79 -79
- package/commands/utils/PathHelper.js +68 -68
- package/commands/utils/VersionChecker.js +167 -167
- package/commands/utils/bundling/BundleGenerator.js +1331 -783
- package/commands/utils/bundling/DependencyAnalyzer.js +859 -679
- package/commands/utils/updateManager.js +437 -384
- package/package.json +46 -46
- package/post.js +25 -25
- package/refactor.md +271 -271
- package/tests/bundle-generator.test.js +38 -0
- package/tests/dependency-analyzer.test.js +24 -0
|
@@ -1,747 +1,747 @@
|
|
|
1
|
-
// commands/getComponent/getComponent.js
|
|
2
|
-
|
|
3
|
-
import fs from "fs-extra";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
|
-
import inquirer from "inquirer";
|
|
7
|
-
import validations from "../Validations.js";
|
|
8
|
-
import Print from "../Print.js";
|
|
9
|
-
import { getConfigPath, getComponentsJsPath, getPath } from "../utils/PathHelper.js";
|
|
10
|
-
import ora from "ora";
|
|
11
|
-
|
|
12
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
|
|
14
|
-
// Base URL del repositorio de documentación de Slice.js
|
|
15
|
-
const DOCS_REPO_BASE_URL = 'https://raw.githubusercontent.com/VKneider/slicejs_docs/master/src/Components';
|
|
16
|
-
const COMPONENTS_REGISTRY_URL = 'https://raw.githubusercontent.com/VKneider/slicejs_docs/master/src/Components/components.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Carga la configuración desde sliceConfig.json
|
|
20
|
-
* @returns {object} - Objeto de configuración
|
|
21
|
-
*/
|
|
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');
|
|
27
|
-
}
|
|
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
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
class ComponentRegistry {
|
|
37
|
-
constructor() {
|
|
38
|
-
this.componentsRegistry = null;
|
|
39
|
-
this.config = loadConfig();
|
|
40
|
-
}
|
|
41
|
-
|
|
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}`);
|
|
50
|
-
}
|
|
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');
|
|
58
|
-
}
|
|
59
|
-
|
|
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;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
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
|
|
79
|
-
*/
|
|
80
|
-
filterOfficialComponents(allComponents) {
|
|
81
|
-
const filtered = {};
|
|
82
|
-
let excludedCount = 0;
|
|
83
|
-
|
|
84
|
-
Object.entries(allComponents).forEach(([name, category]) => {
|
|
85
|
-
// Solo incluir componentes de categoría Visual o Service
|
|
86
|
-
if (category === 'Visual' || category === 'Service') {
|
|
87
|
-
filtered[name] = category;
|
|
88
|
-
} else {
|
|
89
|
-
excludedCount++;
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
if (excludedCount > 0) {
|
|
94
|
-
Print.info(`Filtered out ${excludedCount} non-Visual/Service components from registry`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return filtered;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async getLocalComponents() {
|
|
101
|
-
try {
|
|
102
|
-
const componentsPath = getComponentsJsPath(import.meta.url);
|
|
103
|
-
|
|
104
|
-
if (!await fs.pathExists(componentsPath)) {
|
|
105
|
-
return {};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const content = await fs.readFile(componentsPath, 'utf8');
|
|
109
|
-
const match = content.match(/const components = ({[\s\S]*?});/);
|
|
110
|
-
|
|
111
|
-
if (!match) {
|
|
112
|
-
return {};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return eval('(' + match[1] + ')');
|
|
116
|
-
} catch (error) {
|
|
117
|
-
Print.warning('⚠️ No se pudo leer el registro local de componentes');
|
|
118
|
-
return {};
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async findUpdatableComponents() {
|
|
123
|
-
const localComponents = await this.getLocalComponents();
|
|
124
|
-
const updatableComponents = [];
|
|
125
|
-
|
|
126
|
-
Object.entries(localComponents).forEach(([name, category]) => {
|
|
127
|
-
// Check if component exists in remote registry
|
|
128
|
-
if (this.componentsRegistry[name] && this.componentsRegistry[name] === category) {
|
|
129
|
-
// Check if local component directory exists using dynamic paths
|
|
130
|
-
const categoryPath = validations.getCategoryPath(category);
|
|
131
|
-
|
|
132
|
-
// ✅ CORREGIDO: Usar 4 niveles para compatibilidad con node_modules
|
|
133
|
-
const isProduction = this.config?.production?.enabled === true;
|
|
134
|
-
const folderSuffix = isProduction ? 'dist' : 'src';
|
|
135
|
-
const componentPath = getPath(import.meta.url, folderSuffix, categoryPath, name);
|
|
136
|
-
|
|
137
|
-
if (fs.pathExistsSync(componentPath)) {
|
|
138
|
-
updatableComponents.push({
|
|
139
|
-
name,
|
|
140
|
-
category,
|
|
141
|
-
path: componentPath
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
return updatableComponents;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
getAvailableComponents(category = null) {
|
|
151
|
-
if (!this.componentsRegistry) return {};
|
|
152
|
-
|
|
153
|
-
const components = {};
|
|
154
|
-
Object.entries(this.componentsRegistry).forEach(([name, componentCategory]) => {
|
|
155
|
-
if (!category || componentCategory === category) {
|
|
156
|
-
// ✅ CORREGIDO: Componentes especiales que no necesitan todos los archivos
|
|
157
|
-
let files;
|
|
158
|
-
if (componentCategory === 'Visual') {
|
|
159
|
-
// Componentes de routing lógico solo necesitan JS
|
|
160
|
-
if (['Route', 'MultiRoute', 'NotFound'].includes(name)) {
|
|
161
|
-
files = [`${name}.js`];
|
|
162
|
-
} else {
|
|
163
|
-
// Componentes visuales normales necesitan JS, HTML, CSS
|
|
164
|
-
files = [`${name}.js`, `${name}.html`, `${name}.css`];
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
// Service components solo necesitan JS
|
|
168
|
-
files = [`${name}.js`];
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
components[name] = {
|
|
172
|
-
name,
|
|
173
|
-
category: componentCategory,
|
|
174
|
-
files: files
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
return components;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
async downloadComponentFiles(componentName, category, targetPath) {
|
|
184
|
-
const component = this.getAvailableComponents(category)[componentName];
|
|
185
|
-
|
|
186
|
-
if (!component) {
|
|
187
|
-
throw new Error(`Component ${componentName} not found in ${category} category`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const downloadedFiles = [];
|
|
191
|
-
const failedFiles = [];
|
|
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}`;
|
|
210
|
-
const localPath = path.join(targetPath, fileName);
|
|
211
|
-
try {
|
|
212
|
-
const content = await fetchWithRetry(url);
|
|
213
|
-
await fs.writeFile(localPath, content, 'utf8');
|
|
214
|
-
downloadedFiles.push(fileName);
|
|
215
|
-
Print.downloadSuccess(fileName);
|
|
216
|
-
} catch (error) {
|
|
217
|
-
Print.downloadError(fileName, error.message);
|
|
218
|
-
failedFiles.push(fileName);
|
|
219
|
-
} finally {
|
|
220
|
-
done += 1;
|
|
221
|
-
spinner.text = `Downloading ${componentName} ${done}/${total}`;
|
|
222
|
-
}
|
|
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();
|
|
237
|
-
|
|
238
|
-
// ✅ NUEVO: Solo lanzar error si NO se descargó el archivo principal (.js)
|
|
239
|
-
const mainFileDownloaded = downloadedFiles.some(file => file.endsWith('.js'));
|
|
240
|
-
|
|
241
|
-
if (!mainFileDownloaded) {
|
|
242
|
-
throw new Error(`Failed to download main component file (${componentName}.js)`);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// ✅ ADVERTENCIA: Informar sobre archivos que fallaron (pero no detener el proceso)
|
|
246
|
-
if (failedFiles.length > 0) {
|
|
247
|
-
Print.warning(`Some files couldn't be downloaded: ${failedFiles.join(', ')}`);
|
|
248
|
-
Print.info('Component installed with available files');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return downloadedFiles;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async updateLocalRegistrySafe(componentName, category) {
|
|
255
|
-
const componentsPath = path.join(__dirname, '../../../../src/Components/components.js');
|
|
256
|
-
try {
|
|
257
|
-
if (!await fs.pathExists(componentsPath)) {
|
|
258
|
-
const dir = path.dirname(componentsPath);
|
|
259
|
-
await fs.ensureDir(dir);
|
|
260
|
-
const initial = `const components = {};\n\nexport default components;\n`;
|
|
261
|
-
await fs.writeFile(componentsPath, initial, 'utf8');
|
|
262
|
-
}
|
|
263
|
-
const content = await fs.readFile(componentsPath, 'utf8');
|
|
264
|
-
const match = content.match(/const components = ({[\s\S]*?});/);
|
|
265
|
-
if (!match) throw new Error('Invalid components.js format in local project');
|
|
266
|
-
const componentsObj = eval('(' + match[1] + ')');
|
|
267
|
-
if (!componentsObj[componentName]) {
|
|
268
|
-
componentsObj[componentName] = category;
|
|
269
|
-
const sorted = Object.keys(componentsObj)
|
|
270
|
-
.sort()
|
|
271
|
-
.reduce((obj, key) => { obj[key] = componentsObj[key]; return obj; }, {});
|
|
272
|
-
const newContent = `const components = ${JSON.stringify(sorted, null, 2)};\n\nexport default components;\n`;
|
|
273
|
-
await fs.writeFile(componentsPath, newContent, 'utf8');
|
|
274
|
-
Print.registryUpdate(`Registered ${componentName} in local components.js`);
|
|
275
|
-
} else {
|
|
276
|
-
Print.info(`${componentName} already exists in local registry`);
|
|
277
|
-
}
|
|
278
|
-
} 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
|
-
throw error;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
async installComponent(componentName, category, force = false) {
|
|
331
|
-
const availableComponents = this.getAvailableComponents(category);
|
|
332
|
-
|
|
333
|
-
if (!availableComponents[componentName]) {
|
|
334
|
-
throw new Error(`Component '${componentName}' not found in category '${category}' in the official repository`);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// ✅ MEJORADO: Detectar si validations tiene acceso a la configuración
|
|
338
|
-
let categoryPath;
|
|
339
|
-
const hasValidConfig = validations.config &&
|
|
340
|
-
validations.config.paths &&
|
|
341
|
-
validations.config.paths.components &&
|
|
342
|
-
validations.config.paths.components[category];
|
|
343
|
-
|
|
344
|
-
if (hasValidConfig) {
|
|
345
|
-
categoryPath = validations.getCategoryPath(category);
|
|
346
|
-
} else {
|
|
347
|
-
if (category === 'Visual') {
|
|
348
|
-
categoryPath = 'Components/Visual';
|
|
349
|
-
} else if (category === 'Service') {
|
|
350
|
-
categoryPath = 'Components/Service';
|
|
351
|
-
} else {
|
|
352
|
-
throw new Error(`Unknown category: ${category}`);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const isProduction = this.config?.production?.enabled === true;
|
|
357
|
-
const folderSuffix = isProduction ? 'dist' : 'src';
|
|
358
|
-
|
|
359
|
-
const cleanCategoryPath = categoryPath ? categoryPath.replace(/^[/\\]+/, '') : '';
|
|
360
|
-
const targetPath = getPath(import.meta.url, folderSuffix, cleanCategoryPath, componentName);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// Check if component already exists
|
|
365
|
-
if (await fs.pathExists(targetPath) && !force) {
|
|
366
|
-
const { overwrite } = await inquirer.prompt([
|
|
367
|
-
{
|
|
368
|
-
type: 'confirm',
|
|
369
|
-
name: 'overwrite',
|
|
370
|
-
message: `The component '${componentName}' already exists locally. Overwrite with the repository version?`,
|
|
371
|
-
default: false
|
|
372
|
-
}
|
|
373
|
-
]);
|
|
374
|
-
|
|
375
|
-
if (!overwrite) {
|
|
376
|
-
Print.info('Installation cancelled by user');
|
|
377
|
-
return false;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
// Create component directory
|
|
383
|
-
await fs.ensureDir(targetPath);
|
|
384
|
-
|
|
385
|
-
// Download component files
|
|
386
|
-
const downloadedFiles = await this.downloadComponentFiles(componentName, category, targetPath);
|
|
387
|
-
|
|
388
|
-
await this.updateLocalRegistrySafe(componentName, category);
|
|
389
|
-
|
|
390
|
-
Print.success(`${componentName} installed successfully from official repository!`);
|
|
391
|
-
console.log(`📁 Location: ${folderSuffix}/${categoryPath}/${componentName}/`);
|
|
392
|
-
console.log(`📄 Files: ${downloadedFiles.join(', ')}`);
|
|
393
|
-
|
|
394
|
-
return true;
|
|
395
|
-
|
|
396
|
-
} catch (error) {
|
|
397
|
-
Print.error(`Error installing ${componentName}: ${error.message}`);
|
|
398
|
-
|
|
399
|
-
// ✅ MEJORADO: Solo borrar si el archivo principal (.js) no existe
|
|
400
|
-
const mainFilePath = path.join(targetPath, `${componentName}.js`);
|
|
401
|
-
const mainFileExists = await fs.pathExists(mainFilePath);
|
|
402
|
-
|
|
403
|
-
if (!mainFileExists && await fs.pathExists(targetPath)) {
|
|
404
|
-
// Solo limpiar si no se instaló el archivo principal
|
|
405
|
-
await fs.remove(targetPath);
|
|
406
|
-
Print.info('Cleaned up failed installation');
|
|
407
|
-
} else if (mainFileExists) {
|
|
408
|
-
Print.warning('Component partially installed - main file exists');
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
throw error;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
async installMultipleComponents(componentNames, category = 'Visual', force = false) {
|
|
416
|
-
const results = [];
|
|
417
|
-
Print.info(`Getting ${componentNames.length} ${category} components from official repository...`);
|
|
418
|
-
const total = componentNames.length;
|
|
419
|
-
let done = 0;
|
|
420
|
-
const spinner = ora(`Installing 0/${total}`).start();
|
|
421
|
-
const worker = async (componentName) => {
|
|
422
|
-
try {
|
|
423
|
-
const result = await this.installComponent(componentName, category, force);
|
|
424
|
-
results.push({ name: componentName, success: result });
|
|
425
|
-
} catch (error) {
|
|
426
|
-
Print.componentError(componentName, 'getting', error.message);
|
|
427
|
-
results.push({ name: componentName, success: false, error: error.message });
|
|
428
|
-
} finally {
|
|
429
|
-
done += 1;
|
|
430
|
-
spinner.text = `Installing ${done}/${total}`;
|
|
431
|
-
}
|
|
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();
|
|
446
|
-
|
|
447
|
-
// Summary
|
|
448
|
-
const successful = results.filter(r => r.success).length;
|
|
449
|
-
const failed = results.filter(r => !r.success).length;
|
|
450
|
-
|
|
451
|
-
Print.newLine();
|
|
452
|
-
Print.summary(successful, failed, componentNames.length);
|
|
453
|
-
|
|
454
|
-
return results;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
async updateAllComponents(force = false) {
|
|
458
|
-
Print.info('Looking for updatable Visual components...');
|
|
459
|
-
|
|
460
|
-
const allUpdatableComponents = await this.findUpdatableComponents();
|
|
461
|
-
|
|
462
|
-
// ✅ NUEVO: Filtrar solo componentes Visual
|
|
463
|
-
const updatableComponents = allUpdatableComponents.filter(comp => comp.category === 'Visual');
|
|
464
|
-
|
|
465
|
-
if (updatableComponents.length === 0) {
|
|
466
|
-
Print.info('No local Visual components found that match the official repository');
|
|
467
|
-
Print.info('Use "slice browse" to see available components');
|
|
468
|
-
return true;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Mostrar estadísticas si hay componentes Service que no se sincronizarán
|
|
472
|
-
const serviceComponents = allUpdatableComponents.filter(comp => comp.category === 'Service');
|
|
473
|
-
if (serviceComponents.length > 0) {
|
|
474
|
-
Print.info(`Found ${serviceComponents.length} Service components (skipped - sync only affects Visual components)`);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
Print.newLine();
|
|
478
|
-
Print.subtitle(`Found ${updatableComponents.length} updatable Visual components:`);
|
|
479
|
-
Print.newLine();
|
|
480
|
-
updatableComponents.forEach(comp => {
|
|
481
|
-
console.log(`🎨 ${comp.name} (${comp.category})`);
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
if (!force) {
|
|
485
|
-
const { confirmUpdate } = await inquirer.prompt([
|
|
486
|
-
{
|
|
487
|
-
type: 'confirm',
|
|
488
|
-
name: 'confirmUpdate',
|
|
489
|
-
message: `Do you want to update these Visual components to the repository versions?`,
|
|
490
|
-
default: true
|
|
491
|
-
}
|
|
492
|
-
]);
|
|
493
|
-
|
|
494
|
-
if (!confirmUpdate) {
|
|
495
|
-
Print.info('Update cancelled by user');
|
|
496
|
-
return false;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// ✅ SIMPLIFICADO: Solo actualizar componentes Visual
|
|
501
|
-
const visualComponentNames = updatableComponents.map(c => c.name);
|
|
502
|
-
|
|
503
|
-
Print.info(`Updating ${visualComponentNames.length} Visual components...`);
|
|
504
|
-
const results = await this.installMultipleComponents(visualComponentNames, 'Visual', true);
|
|
505
|
-
|
|
506
|
-
// Final summary
|
|
507
|
-
const totalSuccessful = results.filter(r => r.success).length;
|
|
508
|
-
const totalFailed = results.filter(r => !r.success).length;
|
|
509
|
-
|
|
510
|
-
Print.newLine();
|
|
511
|
-
Print.title('Visual Components Sync Summary');
|
|
512
|
-
Print.success(`Visual components updated: ${totalSuccessful}`);
|
|
513
|
-
|
|
514
|
-
if (totalFailed > 0) {
|
|
515
|
-
Print.error(`Visual components failed: ${totalFailed}`);
|
|
516
|
-
} else {
|
|
517
|
-
Print.success('All your Visual components are now updated to the latest official versions!');
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Información adicional sobre Service components
|
|
521
|
-
if (serviceComponents.length > 0) {
|
|
522
|
-
Print.newLine();
|
|
523
|
-
Print.info(`Note: ${serviceComponents.length} Service components were found but not updated`);
|
|
524
|
-
Print.info('Service components maintain manual versioning - update them individually if needed');
|
|
525
|
-
Print.commandExample('Update Service component manually', 'slice get FetchManager --service --force');
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return totalFailed === 0;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
displayAvailableComponents() {
|
|
532
|
-
if (!this.componentsRegistry) {
|
|
533
|
-
Print.error('❌ Could not load component registry');
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
console.log('\n📚 Available components in the official Slice.js repository:\n');
|
|
538
|
-
|
|
539
|
-
const visualComponents = this.getAvailableComponents('Visual');
|
|
540
|
-
const serviceComponents = this.getAvailableComponents('Service');
|
|
541
|
-
|
|
542
|
-
// ✅ SIMPLIFICADO: Solo mostrar nombres sin descripciones
|
|
543
|
-
Print.info('🎨 Visual Components (UI):');
|
|
544
|
-
Object.keys(visualComponents).forEach(name => {
|
|
545
|
-
const files = visualComponents[name].files;
|
|
546
|
-
const fileIcons = files.map(file => {
|
|
547
|
-
if (file.endsWith('.js')) return '📜';
|
|
548
|
-
if (file.endsWith('.html')) return '🌐';
|
|
549
|
-
if (file.endsWith('.css')) return '🎨';
|
|
550
|
-
return '📄';
|
|
551
|
-
}).join(' ');
|
|
552
|
-
console.log(` • ${name} ${fileIcons}`);
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
Print.info('\n⚙️ Service Components (Logic):');
|
|
556
|
-
Object.keys(serviceComponents).forEach(name => {
|
|
557
|
-
console.log(` • ${name} 📜`);
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
Print.newLine();
|
|
561
|
-
Print.info(`Total: ${Object.keys(visualComponents).length} Visual + ${Object.keys(serviceComponents).length} Service components`);
|
|
562
|
-
|
|
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`);
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
async interactiveInstall() {
|
|
570
|
-
const { componentType } = await inquirer.prompt([
|
|
571
|
-
{
|
|
572
|
-
type: 'list',
|
|
573
|
-
name: 'componentType',
|
|
574
|
-
message: 'Select the type of component to install from the repository:',
|
|
575
|
-
choices: [
|
|
576
|
-
{ name: '🎨 Visual Components (UI)', value: 'Visual' },
|
|
577
|
-
{ name: '⚙️ Service Components (Logic)', value: 'Service' }
|
|
578
|
-
]
|
|
579
|
-
}
|
|
580
|
-
]);
|
|
581
|
-
|
|
582
|
-
const availableComponents = this.getAvailableComponents(componentType);
|
|
583
|
-
const componentChoices = Object.keys(availableComponents).map(name => ({
|
|
584
|
-
name: name,
|
|
585
|
-
value: name
|
|
586
|
-
}));
|
|
587
|
-
|
|
588
|
-
if (componentType === 'Visual') {
|
|
589
|
-
const { installMode } = await inquirer.prompt([
|
|
590
|
-
{
|
|
591
|
-
type: 'list',
|
|
592
|
-
name: 'installMode',
|
|
593
|
-
message: 'How do you want to install Visual components?',
|
|
594
|
-
choices: [
|
|
595
|
-
{ name: 'Get one', value: 'single' },
|
|
596
|
-
{ name: 'Get multiple', value: 'multiple' }
|
|
597
|
-
]
|
|
598
|
-
}
|
|
599
|
-
]);
|
|
600
|
-
|
|
601
|
-
if (installMode === 'multiple') {
|
|
602
|
-
const { selectedComponents } = await inquirer.prompt([
|
|
603
|
-
{
|
|
604
|
-
type: 'checkbox',
|
|
605
|
-
name: 'selectedComponents',
|
|
606
|
-
message: 'Select Visual components to install from the repository:',
|
|
607
|
-
choices: componentChoices,
|
|
608
|
-
validate: (input) => {
|
|
609
|
-
if (input.length === 0) {
|
|
610
|
-
return 'You must select at least one component';
|
|
611
|
-
}
|
|
612
|
-
return true;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
]);
|
|
616
|
-
|
|
617
|
-
await this.installMultipleComponents(selectedComponents, componentType);
|
|
618
|
-
} else {
|
|
619
|
-
const { selectedComponent } = await inquirer.prompt([
|
|
620
|
-
{
|
|
621
|
-
type: 'list',
|
|
622
|
-
name: 'selectedComponent',
|
|
623
|
-
message: 'Select a Visual component:',
|
|
624
|
-
choices: componentChoices
|
|
625
|
-
}
|
|
626
|
-
]);
|
|
627
|
-
|
|
628
|
-
await this.installComponent(selectedComponent, componentType);
|
|
629
|
-
}
|
|
630
|
-
} else {
|
|
631
|
-
const { selectedComponent } = await inquirer.prompt([
|
|
632
|
-
{
|
|
633
|
-
type: 'list',
|
|
634
|
-
name: 'selectedComponent',
|
|
635
|
-
message: 'Select a Service component:',
|
|
636
|
-
choices: componentChoices
|
|
637
|
-
}
|
|
638
|
-
]);
|
|
639
|
-
|
|
640
|
-
await this.installComponent(selectedComponent, componentType);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
findComponentInRegistry(componentName) {
|
|
645
|
-
if (!this.componentsRegistry) return null;
|
|
646
|
-
|
|
647
|
-
const normalizedName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
|
|
648
|
-
|
|
649
|
-
if (this.componentsRegistry[normalizedName]) {
|
|
650
|
-
return {
|
|
651
|
-
name: normalizedName,
|
|
652
|
-
category: this.componentsRegistry[normalizedName]
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Main get function
|
|
661
|
-
async function getComponents(componentNames = [], options = {}) {
|
|
662
|
-
const registry = new ComponentRegistry();
|
|
663
|
-
|
|
664
|
-
try {
|
|
665
|
-
await registry.loadRegistry();
|
|
666
|
-
} catch (error) {
|
|
667
|
-
Print.error('Could not load component registry from official repository');
|
|
668
|
-
Print.info('Check your internet connection and try again');
|
|
669
|
-
return false;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// Interactive mode if no components specified
|
|
673
|
-
if (!componentNames || componentNames.length === 0) {
|
|
674
|
-
await registry.interactiveInstall();
|
|
675
|
-
return true;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// Determine category
|
|
679
|
-
const category = options.service ? 'Service' : 'Visual';
|
|
680
|
-
|
|
681
|
-
if (componentNames.length === 1) {
|
|
682
|
-
// Single component install
|
|
683
|
-
const componentInfo = registry.findComponentInRegistry(componentNames[0]);
|
|
684
|
-
|
|
685
|
-
if (!componentInfo) {
|
|
686
|
-
Print.error(`Component '${componentNames[0]}' not found in official repository`);
|
|
687
|
-
Print.commandExample('View available components', 'slice browse');
|
|
688
|
-
return false;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Use the category from registry unless Service is explicitly requested
|
|
692
|
-
const actualCategory = options.service ? 'Service' : componentInfo.category;
|
|
693
|
-
|
|
694
|
-
try {
|
|
695
|
-
await registry.installComponent(componentInfo.name, actualCategory, options.force);
|
|
696
|
-
return true;
|
|
697
|
-
} catch (error) {
|
|
698
|
-
Print.error(`${error.message}`);
|
|
699
|
-
return false;
|
|
700
|
-
}
|
|
701
|
-
} else {
|
|
702
|
-
// Multiple components install
|
|
703
|
-
const normalizedComponents = componentNames.map(name =>
|
|
704
|
-
name.charAt(0).toUpperCase() + name.slice(1)
|
|
705
|
-
);
|
|
706
|
-
|
|
707
|
-
try {
|
|
708
|
-
await registry.installMultipleComponents(normalizedComponents, category, options.force);
|
|
709
|
-
return true;
|
|
710
|
-
} catch (error) {
|
|
711
|
-
Print.error(`${error.message}`);
|
|
712
|
-
return false;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// List components function
|
|
718
|
-
async function listComponents() {
|
|
719
|
-
const registry = new ComponentRegistry();
|
|
720
|
-
|
|
721
|
-
try {
|
|
722
|
-
await registry.loadRegistry();
|
|
723
|
-
registry.displayAvailableComponents();
|
|
724
|
-
return true;
|
|
725
|
-
} catch (error) {
|
|
726
|
-
Print.error('Could not load component registry from official repository');
|
|
727
|
-
Print.info('Check your internet connection and try again');
|
|
728
|
-
return false;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// Sync components function
|
|
733
|
-
async function syncComponents(options = {}) {
|
|
734
|
-
const registry = new ComponentRegistry();
|
|
735
|
-
|
|
736
|
-
try {
|
|
737
|
-
await registry.loadRegistry();
|
|
738
|
-
return await registry.updateAllComponents(options.force);
|
|
739
|
-
} catch (error) {
|
|
740
|
-
Print.error('Could not load component registry from official repository');
|
|
741
|
-
Print.info('Check your internet connection and try again');
|
|
742
|
-
return false;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
export default getComponents;
|
|
747
|
-
export { listComponents, syncComponents, ComponentRegistry };
|
|
1
|
+
// commands/getComponent/getComponent.js
|
|
2
|
+
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import inquirer from "inquirer";
|
|
7
|
+
import validations from "../Validations.js";
|
|
8
|
+
import Print from "../Print.js";
|
|
9
|
+
import { getConfigPath, getComponentsJsPath, getPath } from "../utils/PathHelper.js";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
// Base URL del repositorio de documentación de Slice.js
|
|
15
|
+
const DOCS_REPO_BASE_URL = 'https://raw.githubusercontent.com/VKneider/slicejs_docs/master/src/Components';
|
|
16
|
+
const COMPONENTS_REGISTRY_URL = 'https://raw.githubusercontent.com/VKneider/slicejs_docs/master/src/Components/components.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Carga la configuración desde sliceConfig.json
|
|
20
|
+
* @returns {object} - Objeto de configuración
|
|
21
|
+
*/
|
|
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');
|
|
27
|
+
}
|
|
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
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
class ComponentRegistry {
|
|
37
|
+
constructor() {
|
|
38
|
+
this.componentsRegistry = null;
|
|
39
|
+
this.config = loadConfig();
|
|
40
|
+
}
|
|
41
|
+
|
|
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}`);
|
|
50
|
+
}
|
|
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');
|
|
58
|
+
}
|
|
59
|
+
|
|
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;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
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
|
|
79
|
+
*/
|
|
80
|
+
filterOfficialComponents(allComponents) {
|
|
81
|
+
const filtered = {};
|
|
82
|
+
let excludedCount = 0;
|
|
83
|
+
|
|
84
|
+
Object.entries(allComponents).forEach(([name, category]) => {
|
|
85
|
+
// Solo incluir componentes de categoría Visual o Service
|
|
86
|
+
if (category === 'Visual' || category === 'Service') {
|
|
87
|
+
filtered[name] = category;
|
|
88
|
+
} else {
|
|
89
|
+
excludedCount++;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (excludedCount > 0) {
|
|
94
|
+
Print.info(`Filtered out ${excludedCount} non-Visual/Service components from registry`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return filtered;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async getLocalComponents() {
|
|
101
|
+
try {
|
|
102
|
+
const componentsPath = getComponentsJsPath(import.meta.url);
|
|
103
|
+
|
|
104
|
+
if (!await fs.pathExists(componentsPath)) {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const content = await fs.readFile(componentsPath, 'utf8');
|
|
109
|
+
const match = content.match(/const components = ({[\s\S]*?});/);
|
|
110
|
+
|
|
111
|
+
if (!match) {
|
|
112
|
+
return {};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return eval('(' + match[1] + ')');
|
|
116
|
+
} catch (error) {
|
|
117
|
+
Print.warning('⚠️ No se pudo leer el registro local de componentes');
|
|
118
|
+
return {};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async findUpdatableComponents() {
|
|
123
|
+
const localComponents = await this.getLocalComponents();
|
|
124
|
+
const updatableComponents = [];
|
|
125
|
+
|
|
126
|
+
Object.entries(localComponents).forEach(([name, category]) => {
|
|
127
|
+
// Check if component exists in remote registry
|
|
128
|
+
if (this.componentsRegistry[name] && this.componentsRegistry[name] === category) {
|
|
129
|
+
// Check if local component directory exists using dynamic paths
|
|
130
|
+
const categoryPath = validations.getCategoryPath(category);
|
|
131
|
+
|
|
132
|
+
// ✅ CORREGIDO: Usar 4 niveles para compatibilidad con node_modules
|
|
133
|
+
const isProduction = this.config?.production?.enabled === true;
|
|
134
|
+
const folderSuffix = isProduction ? 'dist' : 'src';
|
|
135
|
+
const componentPath = getPath(import.meta.url, folderSuffix, categoryPath, name);
|
|
136
|
+
|
|
137
|
+
if (fs.pathExistsSync(componentPath)) {
|
|
138
|
+
updatableComponents.push({
|
|
139
|
+
name,
|
|
140
|
+
category,
|
|
141
|
+
path: componentPath
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return updatableComponents;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getAvailableComponents(category = null) {
|
|
151
|
+
if (!this.componentsRegistry) return {};
|
|
152
|
+
|
|
153
|
+
const components = {};
|
|
154
|
+
Object.entries(this.componentsRegistry).forEach(([name, componentCategory]) => {
|
|
155
|
+
if (!category || componentCategory === category) {
|
|
156
|
+
// ✅ CORREGIDO: Componentes especiales que no necesitan todos los archivos
|
|
157
|
+
let files;
|
|
158
|
+
if (componentCategory === 'Visual') {
|
|
159
|
+
// Componentes de routing lógico solo necesitan JS
|
|
160
|
+
if (['Route', 'MultiRoute', 'NotFound'].includes(name)) {
|
|
161
|
+
files = [`${name}.js`];
|
|
162
|
+
} else {
|
|
163
|
+
// Componentes visuales normales necesitan JS, HTML, CSS
|
|
164
|
+
files = [`${name}.js`, `${name}.html`, `${name}.css`];
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
// Service components solo necesitan JS
|
|
168
|
+
files = [`${name}.js`];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
components[name] = {
|
|
172
|
+
name,
|
|
173
|
+
category: componentCategory,
|
|
174
|
+
files: files
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return components;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
async downloadComponentFiles(componentName, category, targetPath) {
|
|
184
|
+
const component = this.getAvailableComponents(category)[componentName];
|
|
185
|
+
|
|
186
|
+
if (!component) {
|
|
187
|
+
throw new Error(`Component ${componentName} not found in ${category} category`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const downloadedFiles = [];
|
|
191
|
+
const failedFiles = [];
|
|
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}`;
|
|
210
|
+
const localPath = path.join(targetPath, fileName);
|
|
211
|
+
try {
|
|
212
|
+
const content = await fetchWithRetry(url);
|
|
213
|
+
await fs.writeFile(localPath, content, 'utf8');
|
|
214
|
+
downloadedFiles.push(fileName);
|
|
215
|
+
Print.downloadSuccess(fileName);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
Print.downloadError(fileName, error.message);
|
|
218
|
+
failedFiles.push(fileName);
|
|
219
|
+
} finally {
|
|
220
|
+
done += 1;
|
|
221
|
+
spinner.text = `Downloading ${componentName} ${done}/${total}`;
|
|
222
|
+
}
|
|
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();
|
|
237
|
+
|
|
238
|
+
// ✅ NUEVO: Solo lanzar error si NO se descargó el archivo principal (.js)
|
|
239
|
+
const mainFileDownloaded = downloadedFiles.some(file => file.endsWith('.js'));
|
|
240
|
+
|
|
241
|
+
if (!mainFileDownloaded) {
|
|
242
|
+
throw new Error(`Failed to download main component file (${componentName}.js)`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ✅ ADVERTENCIA: Informar sobre archivos que fallaron (pero no detener el proceso)
|
|
246
|
+
if (failedFiles.length > 0) {
|
|
247
|
+
Print.warning(`Some files couldn't be downloaded: ${failedFiles.join(', ')}`);
|
|
248
|
+
Print.info('Component installed with available files');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return downloadedFiles;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async updateLocalRegistrySafe(componentName, category) {
|
|
255
|
+
const componentsPath = path.join(__dirname, '../../../../src/Components/components.js');
|
|
256
|
+
try {
|
|
257
|
+
if (!await fs.pathExists(componentsPath)) {
|
|
258
|
+
const dir = path.dirname(componentsPath);
|
|
259
|
+
await fs.ensureDir(dir);
|
|
260
|
+
const initial = `const components = {};\n\nexport default components;\n`;
|
|
261
|
+
await fs.writeFile(componentsPath, initial, 'utf8');
|
|
262
|
+
}
|
|
263
|
+
const content = await fs.readFile(componentsPath, 'utf8');
|
|
264
|
+
const match = content.match(/const components = ({[\s\S]*?});/);
|
|
265
|
+
if (!match) throw new Error('Invalid components.js format in local project');
|
|
266
|
+
const componentsObj = eval('(' + match[1] + ')');
|
|
267
|
+
if (!componentsObj[componentName]) {
|
|
268
|
+
componentsObj[componentName] = category;
|
|
269
|
+
const sorted = Object.keys(componentsObj)
|
|
270
|
+
.sort()
|
|
271
|
+
.reduce((obj, key) => { obj[key] = componentsObj[key]; return obj; }, {});
|
|
272
|
+
const newContent = `const components = ${JSON.stringify(sorted, null, 2)};\n\nexport default components;\n`;
|
|
273
|
+
await fs.writeFile(componentsPath, newContent, 'utf8');
|
|
274
|
+
Print.registryUpdate(`Registered ${componentName} in local components.js`);
|
|
275
|
+
} else {
|
|
276
|
+
Print.info(`${componentName} already exists in local registry`);
|
|
277
|
+
}
|
|
278
|
+
} 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
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async installComponent(componentName, category, force = false) {
|
|
331
|
+
const availableComponents = this.getAvailableComponents(category);
|
|
332
|
+
|
|
333
|
+
if (!availableComponents[componentName]) {
|
|
334
|
+
throw new Error(`Component '${componentName}' not found in category '${category}' in the official repository`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ✅ MEJORADO: Detectar si validations tiene acceso a la configuración
|
|
338
|
+
let categoryPath;
|
|
339
|
+
const hasValidConfig = validations.config &&
|
|
340
|
+
validations.config.paths &&
|
|
341
|
+
validations.config.paths.components &&
|
|
342
|
+
validations.config.paths.components[category];
|
|
343
|
+
|
|
344
|
+
if (hasValidConfig) {
|
|
345
|
+
categoryPath = validations.getCategoryPath(category);
|
|
346
|
+
} else {
|
|
347
|
+
if (category === 'Visual') {
|
|
348
|
+
categoryPath = 'Components/Visual';
|
|
349
|
+
} else if (category === 'Service') {
|
|
350
|
+
categoryPath = 'Components/Service';
|
|
351
|
+
} else {
|
|
352
|
+
throw new Error(`Unknown category: ${category}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const isProduction = this.config?.production?.enabled === true;
|
|
357
|
+
const folderSuffix = isProduction ? 'dist' : 'src';
|
|
358
|
+
|
|
359
|
+
const cleanCategoryPath = categoryPath ? categoryPath.replace(/^[/\\]+/, '') : '';
|
|
360
|
+
const targetPath = getPath(import.meta.url, folderSuffix, cleanCategoryPath, componentName);
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
// Check if component already exists
|
|
365
|
+
if (await fs.pathExists(targetPath) && !force) {
|
|
366
|
+
const { overwrite } = await inquirer.prompt([
|
|
367
|
+
{
|
|
368
|
+
type: 'confirm',
|
|
369
|
+
name: 'overwrite',
|
|
370
|
+
message: `The component '${componentName}' already exists locally. Overwrite with the repository version?`,
|
|
371
|
+
default: false
|
|
372
|
+
}
|
|
373
|
+
]);
|
|
374
|
+
|
|
375
|
+
if (!overwrite) {
|
|
376
|
+
Print.info('Installation cancelled by user');
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
// Create component directory
|
|
383
|
+
await fs.ensureDir(targetPath);
|
|
384
|
+
|
|
385
|
+
// Download component files
|
|
386
|
+
const downloadedFiles = await this.downloadComponentFiles(componentName, category, targetPath);
|
|
387
|
+
|
|
388
|
+
await this.updateLocalRegistrySafe(componentName, category);
|
|
389
|
+
|
|
390
|
+
Print.success(`${componentName} installed successfully from official repository!`);
|
|
391
|
+
console.log(`📁 Location: ${folderSuffix}/${categoryPath}/${componentName}/`);
|
|
392
|
+
console.log(`📄 Files: ${downloadedFiles.join(', ')}`);
|
|
393
|
+
|
|
394
|
+
return true;
|
|
395
|
+
|
|
396
|
+
} catch (error) {
|
|
397
|
+
Print.error(`Error installing ${componentName}: ${error.message}`);
|
|
398
|
+
|
|
399
|
+
// ✅ MEJORADO: Solo borrar si el archivo principal (.js) no existe
|
|
400
|
+
const mainFilePath = path.join(targetPath, `${componentName}.js`);
|
|
401
|
+
const mainFileExists = await fs.pathExists(mainFilePath);
|
|
402
|
+
|
|
403
|
+
if (!mainFileExists && await fs.pathExists(targetPath)) {
|
|
404
|
+
// Solo limpiar si no se instaló el archivo principal
|
|
405
|
+
await fs.remove(targetPath);
|
|
406
|
+
Print.info('Cleaned up failed installation');
|
|
407
|
+
} else if (mainFileExists) {
|
|
408
|
+
Print.warning('Component partially installed - main file exists');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async installMultipleComponents(componentNames, category = 'Visual', force = false) {
|
|
416
|
+
const results = [];
|
|
417
|
+
Print.info(`Getting ${componentNames.length} ${category} components from official repository...`);
|
|
418
|
+
const total = componentNames.length;
|
|
419
|
+
let done = 0;
|
|
420
|
+
const spinner = ora(`Installing 0/${total}`).start();
|
|
421
|
+
const worker = async (componentName) => {
|
|
422
|
+
try {
|
|
423
|
+
const result = await this.installComponent(componentName, category, force);
|
|
424
|
+
results.push({ name: componentName, success: result });
|
|
425
|
+
} catch (error) {
|
|
426
|
+
Print.componentError(componentName, 'getting', error.message);
|
|
427
|
+
results.push({ name: componentName, success: false, error: error.message });
|
|
428
|
+
} finally {
|
|
429
|
+
done += 1;
|
|
430
|
+
spinner.text = `Installing ${done}/${total}`;
|
|
431
|
+
}
|
|
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();
|
|
446
|
+
|
|
447
|
+
// Summary
|
|
448
|
+
const successful = results.filter(r => r.success).length;
|
|
449
|
+
const failed = results.filter(r => !r.success).length;
|
|
450
|
+
|
|
451
|
+
Print.newLine();
|
|
452
|
+
Print.summary(successful, failed, componentNames.length);
|
|
453
|
+
|
|
454
|
+
return results;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async updateAllComponents(force = false) {
|
|
458
|
+
Print.info('Looking for updatable Visual components...');
|
|
459
|
+
|
|
460
|
+
const allUpdatableComponents = await this.findUpdatableComponents();
|
|
461
|
+
|
|
462
|
+
// ✅ NUEVO: Filtrar solo componentes Visual
|
|
463
|
+
const updatableComponents = allUpdatableComponents.filter(comp => comp.category === 'Visual');
|
|
464
|
+
|
|
465
|
+
if (updatableComponents.length === 0) {
|
|
466
|
+
Print.info('No local Visual components found that match the official repository');
|
|
467
|
+
Print.info('Use "slice browse" to see available components');
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Mostrar estadísticas si hay componentes Service que no se sincronizarán
|
|
472
|
+
const serviceComponents = allUpdatableComponents.filter(comp => comp.category === 'Service');
|
|
473
|
+
if (serviceComponents.length > 0) {
|
|
474
|
+
Print.info(`Found ${serviceComponents.length} Service components (skipped - sync only affects Visual components)`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
Print.newLine();
|
|
478
|
+
Print.subtitle(`Found ${updatableComponents.length} updatable Visual components:`);
|
|
479
|
+
Print.newLine();
|
|
480
|
+
updatableComponents.forEach(comp => {
|
|
481
|
+
console.log(`🎨 ${comp.name} (${comp.category})`);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
if (!force) {
|
|
485
|
+
const { confirmUpdate } = await inquirer.prompt([
|
|
486
|
+
{
|
|
487
|
+
type: 'confirm',
|
|
488
|
+
name: 'confirmUpdate',
|
|
489
|
+
message: `Do you want to update these Visual components to the repository versions?`,
|
|
490
|
+
default: true
|
|
491
|
+
}
|
|
492
|
+
]);
|
|
493
|
+
|
|
494
|
+
if (!confirmUpdate) {
|
|
495
|
+
Print.info('Update cancelled by user');
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ✅ SIMPLIFICADO: Solo actualizar componentes Visual
|
|
501
|
+
const visualComponentNames = updatableComponents.map(c => c.name);
|
|
502
|
+
|
|
503
|
+
Print.info(`Updating ${visualComponentNames.length} Visual components...`);
|
|
504
|
+
const results = await this.installMultipleComponents(visualComponentNames, 'Visual', true);
|
|
505
|
+
|
|
506
|
+
// Final summary
|
|
507
|
+
const totalSuccessful = results.filter(r => r.success).length;
|
|
508
|
+
const totalFailed = results.filter(r => !r.success).length;
|
|
509
|
+
|
|
510
|
+
Print.newLine();
|
|
511
|
+
Print.title('Visual Components Sync Summary');
|
|
512
|
+
Print.success(`Visual components updated: ${totalSuccessful}`);
|
|
513
|
+
|
|
514
|
+
if (totalFailed > 0) {
|
|
515
|
+
Print.error(`Visual components failed: ${totalFailed}`);
|
|
516
|
+
} else {
|
|
517
|
+
Print.success('All your Visual components are now updated to the latest official versions!');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Información adicional sobre Service components
|
|
521
|
+
if (serviceComponents.length > 0) {
|
|
522
|
+
Print.newLine();
|
|
523
|
+
Print.info(`Note: ${serviceComponents.length} Service components were found but not updated`);
|
|
524
|
+
Print.info('Service components maintain manual versioning - update them individually if needed');
|
|
525
|
+
Print.commandExample('Update Service component manually', 'slice get FetchManager --service --force');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return totalFailed === 0;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
displayAvailableComponents() {
|
|
532
|
+
if (!this.componentsRegistry) {
|
|
533
|
+
Print.error('❌ Could not load component registry');
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
console.log('\n📚 Available components in the official Slice.js repository:\n');
|
|
538
|
+
|
|
539
|
+
const visualComponents = this.getAvailableComponents('Visual');
|
|
540
|
+
const serviceComponents = this.getAvailableComponents('Service');
|
|
541
|
+
|
|
542
|
+
// ✅ SIMPLIFICADO: Solo mostrar nombres sin descripciones
|
|
543
|
+
Print.info('🎨 Visual Components (UI):');
|
|
544
|
+
Object.keys(visualComponents).forEach(name => {
|
|
545
|
+
const files = visualComponents[name].files;
|
|
546
|
+
const fileIcons = files.map(file => {
|
|
547
|
+
if (file.endsWith('.js')) return '📜';
|
|
548
|
+
if (file.endsWith('.html')) return '🌐';
|
|
549
|
+
if (file.endsWith('.css')) return '🎨';
|
|
550
|
+
return '📄';
|
|
551
|
+
}).join(' ');
|
|
552
|
+
console.log(` • ${name} ${fileIcons}`);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
Print.info('\n⚙️ Service Components (Logic):');
|
|
556
|
+
Object.keys(serviceComponents).forEach(name => {
|
|
557
|
+
console.log(` • ${name} 📜`);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
Print.newLine();
|
|
561
|
+
Print.info(`Total: ${Object.keys(visualComponents).length} Visual + ${Object.keys(serviceComponents).length} Service components`);
|
|
562
|
+
|
|
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`);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async interactiveInstall() {
|
|
570
|
+
const { componentType } = await inquirer.prompt([
|
|
571
|
+
{
|
|
572
|
+
type: 'list',
|
|
573
|
+
name: 'componentType',
|
|
574
|
+
message: 'Select the type of component to install from the repository:',
|
|
575
|
+
choices: [
|
|
576
|
+
{ name: '🎨 Visual Components (UI)', value: 'Visual' },
|
|
577
|
+
{ name: '⚙️ Service Components (Logic)', value: 'Service' }
|
|
578
|
+
]
|
|
579
|
+
}
|
|
580
|
+
]);
|
|
581
|
+
|
|
582
|
+
const availableComponents = this.getAvailableComponents(componentType);
|
|
583
|
+
const componentChoices = Object.keys(availableComponents).map(name => ({
|
|
584
|
+
name: name,
|
|
585
|
+
value: name
|
|
586
|
+
}));
|
|
587
|
+
|
|
588
|
+
if (componentType === 'Visual') {
|
|
589
|
+
const { installMode } = await inquirer.prompt([
|
|
590
|
+
{
|
|
591
|
+
type: 'list',
|
|
592
|
+
name: 'installMode',
|
|
593
|
+
message: 'How do you want to install Visual components?',
|
|
594
|
+
choices: [
|
|
595
|
+
{ name: 'Get one', value: 'single' },
|
|
596
|
+
{ name: 'Get multiple', value: 'multiple' }
|
|
597
|
+
]
|
|
598
|
+
}
|
|
599
|
+
]);
|
|
600
|
+
|
|
601
|
+
if (installMode === 'multiple') {
|
|
602
|
+
const { selectedComponents } = await inquirer.prompt([
|
|
603
|
+
{
|
|
604
|
+
type: 'checkbox',
|
|
605
|
+
name: 'selectedComponents',
|
|
606
|
+
message: 'Select Visual components to install from the repository:',
|
|
607
|
+
choices: componentChoices,
|
|
608
|
+
validate: (input) => {
|
|
609
|
+
if (input.length === 0) {
|
|
610
|
+
return 'You must select at least one component';
|
|
611
|
+
}
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
]);
|
|
616
|
+
|
|
617
|
+
await this.installMultipleComponents(selectedComponents, componentType);
|
|
618
|
+
} else {
|
|
619
|
+
const { selectedComponent } = await inquirer.prompt([
|
|
620
|
+
{
|
|
621
|
+
type: 'list',
|
|
622
|
+
name: 'selectedComponent',
|
|
623
|
+
message: 'Select a Visual component:',
|
|
624
|
+
choices: componentChoices
|
|
625
|
+
}
|
|
626
|
+
]);
|
|
627
|
+
|
|
628
|
+
await this.installComponent(selectedComponent, componentType);
|
|
629
|
+
}
|
|
630
|
+
} else {
|
|
631
|
+
const { selectedComponent } = await inquirer.prompt([
|
|
632
|
+
{
|
|
633
|
+
type: 'list',
|
|
634
|
+
name: 'selectedComponent',
|
|
635
|
+
message: 'Select a Service component:',
|
|
636
|
+
choices: componentChoices
|
|
637
|
+
}
|
|
638
|
+
]);
|
|
639
|
+
|
|
640
|
+
await this.installComponent(selectedComponent, componentType);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
findComponentInRegistry(componentName) {
|
|
645
|
+
if (!this.componentsRegistry) return null;
|
|
646
|
+
|
|
647
|
+
const normalizedName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
|
|
648
|
+
|
|
649
|
+
if (this.componentsRegistry[normalizedName]) {
|
|
650
|
+
return {
|
|
651
|
+
name: normalizedName,
|
|
652
|
+
category: this.componentsRegistry[normalizedName]
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Main get function
|
|
661
|
+
async function getComponents(componentNames = [], options = {}) {
|
|
662
|
+
const registry = new ComponentRegistry();
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
await registry.loadRegistry();
|
|
666
|
+
} catch (error) {
|
|
667
|
+
Print.error('Could not load component registry from official repository');
|
|
668
|
+
Print.info('Check your internet connection and try again');
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Interactive mode if no components specified
|
|
673
|
+
if (!componentNames || componentNames.length === 0) {
|
|
674
|
+
await registry.interactiveInstall();
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Determine category
|
|
679
|
+
const category = options.service ? 'Service' : 'Visual';
|
|
680
|
+
|
|
681
|
+
if (componentNames.length === 1) {
|
|
682
|
+
// Single component install
|
|
683
|
+
const componentInfo = registry.findComponentInRegistry(componentNames[0]);
|
|
684
|
+
|
|
685
|
+
if (!componentInfo) {
|
|
686
|
+
Print.error(`Component '${componentNames[0]}' not found in official repository`);
|
|
687
|
+
Print.commandExample('View available components', 'slice browse');
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Use the category from registry unless Service is explicitly requested
|
|
692
|
+
const actualCategory = options.service ? 'Service' : componentInfo.category;
|
|
693
|
+
|
|
694
|
+
try {
|
|
695
|
+
await registry.installComponent(componentInfo.name, actualCategory, options.force);
|
|
696
|
+
return true;
|
|
697
|
+
} catch (error) {
|
|
698
|
+
Print.error(`${error.message}`);
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
} else {
|
|
702
|
+
// Multiple components install
|
|
703
|
+
const normalizedComponents = componentNames.map(name =>
|
|
704
|
+
name.charAt(0).toUpperCase() + name.slice(1)
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
await registry.installMultipleComponents(normalizedComponents, category, options.force);
|
|
709
|
+
return true;
|
|
710
|
+
} catch (error) {
|
|
711
|
+
Print.error(`${error.message}`);
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// List components function
|
|
718
|
+
async function listComponents() {
|
|
719
|
+
const registry = new ComponentRegistry();
|
|
720
|
+
|
|
721
|
+
try {
|
|
722
|
+
await registry.loadRegistry();
|
|
723
|
+
registry.displayAvailableComponents();
|
|
724
|
+
return true;
|
|
725
|
+
} catch (error) {
|
|
726
|
+
Print.error('Could not load component registry from official repository');
|
|
727
|
+
Print.info('Check your internet connection and try again');
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Sync components function
|
|
733
|
+
async function syncComponents(options = {}) {
|
|
734
|
+
const registry = new ComponentRegistry();
|
|
735
|
+
|
|
736
|
+
try {
|
|
737
|
+
await registry.loadRegistry();
|
|
738
|
+
return await registry.updateAllComponents(options.force);
|
|
739
|
+
} catch (error) {
|
|
740
|
+
Print.error('Could not load component registry from official repository');
|
|
741
|
+
Print.info('Check your internet connection and try again');
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export default getComponents;
|
|
747
|
+
export { listComponents, syncComponents, ComponentRegistry };
|