slicejs-cli 3.6.2 → 3.6.4
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/LICENSE +21 -21
- package/README.md +226 -226
- package/client.js +744 -744
- package/commands/Print.js +163 -163
- package/commands/Validations.js +92 -92
- package/commands/build/build.js +40 -40
- package/commands/buildProduction/buildProduction.js +577 -579
- package/commands/bundle/bundle.js +234 -234
- package/commands/createComponent/VisualComponentTemplate.js +55 -55
- package/commands/createComponent/createComponent.js +128 -128
- package/commands/deleteComponent/deleteComponent.js +81 -81
- package/commands/doctor/doctor.js +440 -440
- package/commands/getComponent/getComponent.js +701 -692
- package/commands/init/init.js +467 -463
- package/commands/listComponents/listComponents.js +172 -172
- package/commands/startServer/startServer.js +261 -261
- package/commands/startServer/watchServer.js +66 -79
- package/commands/types/types.js +580 -580
- package/commands/utils/LocalCliDelegation.js +53 -53
- package/commands/utils/PackageManager.js +148 -148
- package/commands/utils/PathHelper.js +75 -75
- package/commands/utils/VersionChecker.js +169 -169
- package/commands/utils/bundling/BundleGenerator.js +2525 -2525
- package/commands/utils/bundling/DependencyAnalyzer.js +925 -925
- package/commands/utils/loadConfig.js +31 -31
- package/commands/utils/sliceScripts.js +23 -23
- package/commands/utils/updateManager.js +471 -471
- package/package.json +73 -73
- package/post.js +60 -60
|
@@ -1,692 +1,701 @@
|
|
|
1
|
-
// commands/getComponent/getComponent.js
|
|
2
|
-
|
|
3
|
-
import fs from "fs-extra";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import inquirer from "inquirer";
|
|
6
|
-
import validations from "../Validations.js";
|
|
7
|
-
import Print from "../Print.js";
|
|
8
|
-
import { getComponentsJsPath, getPath } from "../utils/PathHelper.js";
|
|
9
|
-
import { loadConfig as sharedLoadConfig } from "../utils/loadConfig.js";
|
|
10
|
-
import ora from "ora";
|
|
11
|
-
|
|
12
|
-
// Base URL of the Slice.js documentation repository
|
|
13
|
-
const DOCS_REPO_BASE_URL = 'https://raw.githubusercontent.com/VKneider/slice.js_visual_library/master/src/Components';
|
|
14
|
-
const COMPONENTS_REGISTRY_URL = 'https://raw.githubusercontent.com/VKneider/slice.js_visual_library/master/src/Components/components.js';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Loads configuration from sliceConfig.json
|
|
18
|
-
* @returns {object} - Configuration object
|
|
19
|
-
*/
|
|
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));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
};
|
|
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
|
-
|
|
48
|
-
class ComponentRegistry {
|
|
49
|
-
constructor() {
|
|
50
|
-
this.componentsRegistry = null;
|
|
51
|
-
this.config = null;
|
|
52
|
-
this._configPromise = null;
|
|
53
|
-
// Serializes read-modify-write cycles on components.js: installs run
|
|
54
|
-
// concurrently (runConcurrent), and parallel writers corrupt the file
|
|
55
|
-
// (partial reads) or silently drop registrations (lost updates).
|
|
56
|
-
this._registryLock = Promise.resolve();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async _ensureConfig() {
|
|
60
|
-
if (!this.config && !this._configPromise) {
|
|
61
|
-
this._configPromise = loadConfig();
|
|
62
|
-
}
|
|
63
|
-
if (this._configPromise) {
|
|
64
|
-
this.config = await this._configPromise;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async loadRegistry() {
|
|
69
|
-
await this._ensureConfig();
|
|
70
|
-
Print.info('Loading component registry from official repository...');
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const response = await fetch(COMPONENTS_REGISTRY_URL);
|
|
74
|
-
|
|
75
|
-
if (!response.ok) {
|
|
76
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const content = await response.text();
|
|
80
|
-
|
|
81
|
-
// Parse the components.js file content
|
|
82
|
-
const match = content.match(/const components = ({[\s\S]*?});/);
|
|
83
|
-
if (!match) {
|
|
84
|
-
throw new Error('Invalid components.js format from repository');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const allComponents = JSON.parse(match[1]);
|
|
88
|
-
|
|
89
|
-
// Filter only Visual and Service components
|
|
90
|
-
this.componentsRegistry = this.filterOfficialComponents(allComponents);
|
|
91
|
-
|
|
92
|
-
Print.success('Component registry loaded successfully');
|
|
93
|
-
|
|
94
|
-
} catch (error) {
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Filters the registry to include ONLY Visual and Service category components
|
|
101
|
-
* Excludes AppComponents and any other category
|
|
102
|
-
* @param {object} allComponents - Object with all registry components
|
|
103
|
-
* @returns {object} - Filtered object with only Visual and Service
|
|
104
|
-
*/
|
|
105
|
-
filterOfficialComponents(allComponents) {
|
|
106
|
-
const filtered = {};
|
|
107
|
-
let excludedCount = 0;
|
|
108
|
-
|
|
109
|
-
Object.entries(allComponents).forEach(([name, category]) => {
|
|
110
|
-
// Only include Visual or Service category components
|
|
111
|
-
if (category === 'Visual' || category === 'Service') {
|
|
112
|
-
filtered[name] = category;
|
|
113
|
-
} else {
|
|
114
|
-
excludedCount++;
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
if (excludedCount > 0) {
|
|
119
|
-
Print.info(`Filtered out ${excludedCount} non-Visual/Service components from registry`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return filtered;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async getLocalComponents() {
|
|
126
|
-
try {
|
|
127
|
-
const componentsPath = getComponentsJsPath(import.meta.url);
|
|
128
|
-
|
|
129
|
-
if (!await fs.pathExists(componentsPath)) {
|
|
130
|
-
return {};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const content = await fs.readFile(componentsPath, 'utf8');
|
|
134
|
-
const match = content.match(/const components = ({[\s\S]*?});/);
|
|
135
|
-
|
|
136
|
-
if (!match) {
|
|
137
|
-
return {};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return JSON.parse(match[1]);
|
|
141
|
-
} catch (error) {
|
|
142
|
-
Print.warning(`⚠️ Could not read the local component registry at: ${componentsPath}`);
|
|
143
|
-
return {};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async findUpdatableComponents() {
|
|
148
|
-
await this._ensureConfig();
|
|
149
|
-
const localComponents = await this.getLocalComponents();
|
|
150
|
-
const updatableComponents = [];
|
|
151
|
-
|
|
152
|
-
Object.entries(localComponents).forEach(([name, category]) => {
|
|
153
|
-
// Check if component exists in remote registry
|
|
154
|
-
if (this.componentsRegistry[name] && this.componentsRegistry[name] === category) {
|
|
155
|
-
// Check if local component directory exists using dynamic paths
|
|
156
|
-
const categoryPath = validations.getCategoryPath(category);
|
|
157
|
-
|
|
158
|
-
// Use 4 levels for node_modules compatibility
|
|
159
|
-
const isProduction = this.config?.production?.enabled === true;
|
|
160
|
-
const folderSuffix = isProduction ? 'dist' : 'src';
|
|
161
|
-
const componentPath = getPath(import.meta.url, folderSuffix, categoryPath, name);
|
|
162
|
-
|
|
163
|
-
if (fs.pathExistsSync(componentPath)) {
|
|
164
|
-
updatableComponents.push({
|
|
165
|
-
name,
|
|
166
|
-
category,
|
|
167
|
-
path: componentPath
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
return updatableComponents;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
getAvailableComponents(category = null) {
|
|
177
|
-
if (!this.componentsRegistry) return {};
|
|
178
|
-
|
|
179
|
-
const components = {};
|
|
180
|
-
Object.entries(this.componentsRegistry).forEach(([name, componentCategory]) => {
|
|
181
|
-
if (!category || componentCategory === category) {
|
|
182
|
-
// Special components that don't need all files
|
|
183
|
-
let files;
|
|
184
|
-
if (componentCategory === 'Visual') {
|
|
185
|
-
// Logical routing components only need JS
|
|
186
|
-
if (['Route', 'MultiRoute', 'Link'].includes(name)) {
|
|
187
|
-
files = [`${name}.js`];
|
|
188
|
-
} else {
|
|
189
|
-
//
|
|
190
|
-
files = [`${name}.js`, `${name}.html`, `${name}.css
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
//
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
Print.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
Print.
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
Print.
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
name: '
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
await
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
await
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
await registry.
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
//
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
1
|
+
// commands/getComponent/getComponent.js
|
|
2
|
+
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import inquirer from "inquirer";
|
|
6
|
+
import validations from "../Validations.js";
|
|
7
|
+
import Print from "../Print.js";
|
|
8
|
+
import { getComponentsJsPath, getPath } from "../utils/PathHelper.js";
|
|
9
|
+
import { loadConfig as sharedLoadConfig } from "../utils/loadConfig.js";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
|
|
12
|
+
// Base URL of the Slice.js documentation repository
|
|
13
|
+
const DOCS_REPO_BASE_URL = 'https://raw.githubusercontent.com/VKneider/slice.js_visual_library/master/src/Components';
|
|
14
|
+
const COMPONENTS_REGISTRY_URL = 'https://raw.githubusercontent.com/VKneider/slice.js_visual_library/master/src/Components/components.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Loads configuration from sliceConfig.json
|
|
18
|
+
* @returns {object} - Configuration object
|
|
19
|
+
*/
|
|
20
|
+
const loadConfig = () => sharedLoadConfig(import.meta.url);
|
|
21
|
+
|
|
22
|
+
const fetchWithRetry = async (url, retries = 3, baseDelay = 500, binary = false) => {
|
|
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 binary ? await response.arrayBuffer() : 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));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
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
|
+
|
|
48
|
+
class ComponentRegistry {
|
|
49
|
+
constructor() {
|
|
50
|
+
this.componentsRegistry = null;
|
|
51
|
+
this.config = null;
|
|
52
|
+
this._configPromise = null;
|
|
53
|
+
// Serializes read-modify-write cycles on components.js: installs run
|
|
54
|
+
// concurrently (runConcurrent), and parallel writers corrupt the file
|
|
55
|
+
// (partial reads) or silently drop registrations (lost updates).
|
|
56
|
+
this._registryLock = Promise.resolve();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async _ensureConfig() {
|
|
60
|
+
if (!this.config && !this._configPromise) {
|
|
61
|
+
this._configPromise = loadConfig();
|
|
62
|
+
}
|
|
63
|
+
if (this._configPromise) {
|
|
64
|
+
this.config = await this._configPromise;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async loadRegistry() {
|
|
69
|
+
await this._ensureConfig();
|
|
70
|
+
Print.info('Loading component registry from official repository...');
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(COMPONENTS_REGISTRY_URL);
|
|
74
|
+
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const content = await response.text();
|
|
80
|
+
|
|
81
|
+
// Parse the components.js file content
|
|
82
|
+
const match = content.match(/const components = ({[\s\S]*?});/);
|
|
83
|
+
if (!match) {
|
|
84
|
+
throw new Error('Invalid components.js format from repository');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const allComponents = JSON.parse(match[1]);
|
|
88
|
+
|
|
89
|
+
// Filter only Visual and Service components
|
|
90
|
+
this.componentsRegistry = this.filterOfficialComponents(allComponents);
|
|
91
|
+
|
|
92
|
+
Print.success('Component registry loaded successfully');
|
|
93
|
+
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Filters the registry to include ONLY Visual and Service category components
|
|
101
|
+
* Excludes AppComponents and any other category
|
|
102
|
+
* @param {object} allComponents - Object with all registry components
|
|
103
|
+
* @returns {object} - Filtered object with only Visual and Service
|
|
104
|
+
*/
|
|
105
|
+
filterOfficialComponents(allComponents) {
|
|
106
|
+
const filtered = {};
|
|
107
|
+
let excludedCount = 0;
|
|
108
|
+
|
|
109
|
+
Object.entries(allComponents).forEach(([name, category]) => {
|
|
110
|
+
// Only include Visual or Service category components
|
|
111
|
+
if (category === 'Visual' || category === 'Service') {
|
|
112
|
+
filtered[name] = category;
|
|
113
|
+
} else {
|
|
114
|
+
excludedCount++;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (excludedCount > 0) {
|
|
119
|
+
Print.info(`Filtered out ${excludedCount} non-Visual/Service components from registry`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return filtered;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async getLocalComponents() {
|
|
126
|
+
try {
|
|
127
|
+
const componentsPath = getComponentsJsPath(import.meta.url);
|
|
128
|
+
|
|
129
|
+
if (!await fs.pathExists(componentsPath)) {
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const content = await fs.readFile(componentsPath, 'utf8');
|
|
134
|
+
const match = content.match(/const components = ({[\s\S]*?});/);
|
|
135
|
+
|
|
136
|
+
if (!match) {
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return JSON.parse(match[1]);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
Print.warning(`⚠️ Could not read the local component registry at: ${componentsPath}`);
|
|
143
|
+
return {};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async findUpdatableComponents() {
|
|
148
|
+
await this._ensureConfig();
|
|
149
|
+
const localComponents = await this.getLocalComponents();
|
|
150
|
+
const updatableComponents = [];
|
|
151
|
+
|
|
152
|
+
Object.entries(localComponents).forEach(([name, category]) => {
|
|
153
|
+
// Check if component exists in remote registry
|
|
154
|
+
if (this.componentsRegistry[name] && this.componentsRegistry[name] === category) {
|
|
155
|
+
// Check if local component directory exists using dynamic paths
|
|
156
|
+
const categoryPath = validations.getCategoryPath(category);
|
|
157
|
+
|
|
158
|
+
// Use 4 levels for node_modules compatibility
|
|
159
|
+
const isProduction = this.config?.production?.enabled === true;
|
|
160
|
+
const folderSuffix = isProduction ? 'dist' : 'src';
|
|
161
|
+
const componentPath = getPath(import.meta.url, folderSuffix, categoryPath, name);
|
|
162
|
+
|
|
163
|
+
if (fs.pathExistsSync(componentPath)) {
|
|
164
|
+
updatableComponents.push({
|
|
165
|
+
name,
|
|
166
|
+
category,
|
|
167
|
+
path: componentPath
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return updatableComponents;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
getAvailableComponents(category = null) {
|
|
177
|
+
if (!this.componentsRegistry) return {};
|
|
178
|
+
|
|
179
|
+
const components = {};
|
|
180
|
+
Object.entries(this.componentsRegistry).forEach(([name, componentCategory]) => {
|
|
181
|
+
if (!category || componentCategory === category) {
|
|
182
|
+
// Special components that don't need all files
|
|
183
|
+
let files;
|
|
184
|
+
if (componentCategory === 'Visual') {
|
|
185
|
+
// Logical routing components only need JS
|
|
186
|
+
if (['Route', 'MultiRoute', 'Link'].includes(name)) {
|
|
187
|
+
files = [`${name}.js`];
|
|
188
|
+
} else if (name === 'Icon') {
|
|
189
|
+
// Icon component needs font files to render glyphs
|
|
190
|
+
files = [`${name}.js`, `${name}.html`, `${name}.css`, 'slc.eot', 'slc.woff2', 'slc.woff', 'slc.ttf', 'slc.svg'];
|
|
191
|
+
} else {
|
|
192
|
+
// Normal visual components need JS, HTML, CSS
|
|
193
|
+
files = [`${name}.js`, `${name}.html`, `${name}.css`];
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
// Service components only need JS
|
|
197
|
+
files = [`${name}.js`];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
components[name] = {
|
|
201
|
+
name,
|
|
202
|
+
category: componentCategory,
|
|
203
|
+
files: files
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return components;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
async downloadComponentFiles(componentName, category, targetPath) {
|
|
213
|
+
const component = this.getAvailableComponents(category)[componentName];
|
|
214
|
+
|
|
215
|
+
if (!component) {
|
|
216
|
+
throw new Error(`Component ${componentName} not found in ${category} category`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const downloadedFiles = [];
|
|
220
|
+
const failedFiles = [];
|
|
221
|
+
const total = component.files.length;
|
|
222
|
+
let done = 0;
|
|
223
|
+
const spinner = ora(`Downloading ${componentName} 0/${total}`).start();
|
|
224
|
+
const BINARY_EXTENSIONS = ['.eot', '.woff2', '.woff', '.ttf'];
|
|
225
|
+
const worker = async (fileName) => {
|
|
226
|
+
const url = `${DOCS_REPO_BASE_URL}/${category}/${componentName}/${fileName}`;
|
|
227
|
+
const localPath = path.join(targetPath, fileName);
|
|
228
|
+
const isBinary = BINARY_EXTENSIONS.some(ext => fileName.endsWith(ext));
|
|
229
|
+
try {
|
|
230
|
+
const content = await fetchWithRetry(url, 3, 500, isBinary);
|
|
231
|
+
if (isBinary) {
|
|
232
|
+
await fs.writeFile(localPath, Buffer.from(content));
|
|
233
|
+
} else {
|
|
234
|
+
await fs.writeFile(localPath, content, 'utf8');
|
|
235
|
+
}
|
|
236
|
+
downloadedFiles.push(fileName);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
Print.downloadError(fileName);
|
|
239
|
+
failedFiles.push(fileName);
|
|
240
|
+
} finally {
|
|
241
|
+
done += 1;
|
|
242
|
+
spinner.text = `Downloading ${componentName} ${done}/${total}`;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
await runConcurrent(component.files, worker, 3);
|
|
246
|
+
spinner.stop();
|
|
247
|
+
|
|
248
|
+
// Only throw error if main file (.js) was not downloaded
|
|
249
|
+
const mainFileDownloaded = downloadedFiles.some(file => file.endsWith('.js'));
|
|
250
|
+
|
|
251
|
+
if (!mainFileDownloaded) {
|
|
252
|
+
throw new Error(`Failed to download main component file (${componentName}.js)`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Report files that failed (but don't stop the process)
|
|
256
|
+
if (failedFiles.length > 0) {
|
|
257
|
+
Print.warning(`Some files couldn't be downloaded: ${failedFiles.join(', ')}`);
|
|
258
|
+
Print.info('Component installed with available files');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return downloadedFiles;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async updateLocalRegistrySafe(componentName, category) {
|
|
265
|
+
// Queue behind any in-flight registry update; keep the chain alive even
|
|
266
|
+
// when an update throws so later updates still run.
|
|
267
|
+
const run = this._registryLock.then(() => this._updateLocalRegistry(componentName, category));
|
|
268
|
+
this._registryLock = run.catch(() => {});
|
|
269
|
+
return run;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async _updateLocalRegistry(componentName, category) {
|
|
273
|
+
const componentsPath = getComponentsJsPath(import.meta.url);
|
|
274
|
+
if (!await fs.pathExists(componentsPath)) {
|
|
275
|
+
const dir = path.dirname(componentsPath);
|
|
276
|
+
await fs.ensureDir(dir);
|
|
277
|
+
const initial = `const components = {};\n\nexport default components;\n`;
|
|
278
|
+
await fs.writeFile(componentsPath, initial, 'utf8');
|
|
279
|
+
}
|
|
280
|
+
const content = await fs.readFile(componentsPath, 'utf8');
|
|
281
|
+
const match = content.match(/const components = ({[\s\S]*?});/);
|
|
282
|
+
if (!match) throw new Error('Invalid components.js format in local project');
|
|
283
|
+
const componentsObj = JSON.parse(match[1]);
|
|
284
|
+
if (!componentsObj[componentName]) {
|
|
285
|
+
componentsObj[componentName] = category;
|
|
286
|
+
const sorted = Object.keys(componentsObj)
|
|
287
|
+
.sort()
|
|
288
|
+
.reduce((obj, key) => { obj[key] = componentsObj[key]; return obj; }, {});
|
|
289
|
+
const newContent = `const components = ${JSON.stringify(sorted, null, 2)};\n\nexport default components;\n`;
|
|
290
|
+
await fs.writeFile(componentsPath, newContent, 'utf8');
|
|
291
|
+
Print.registryUpdate(`Registered ${componentName} in local components.js`);
|
|
292
|
+
} else {
|
|
293
|
+
Print.info(`${componentName} already exists in local registry`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async installComponent(componentName, category, force = false) {
|
|
298
|
+
await this._ensureConfig();
|
|
299
|
+
const availableComponents = this.getAvailableComponents(category);
|
|
300
|
+
|
|
301
|
+
if (!availableComponents[componentName]) {
|
|
302
|
+
throw new Error(`Component '${componentName}' not found in category '${category}' in the official repository`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Detect if validations has access to configuration
|
|
306
|
+
let categoryPath;
|
|
307
|
+
const hasValidConfig = validations.config &&
|
|
308
|
+
validations.config.paths &&
|
|
309
|
+
validations.config.paths.components &&
|
|
310
|
+
validations.config.paths.components[category];
|
|
311
|
+
|
|
312
|
+
if (hasValidConfig) {
|
|
313
|
+
categoryPath = validations.getCategoryPath(category);
|
|
314
|
+
} else {
|
|
315
|
+
if (category === 'Visual') {
|
|
316
|
+
categoryPath = 'Components/Visual';
|
|
317
|
+
} else if (category === 'Service') {
|
|
318
|
+
categoryPath = 'Components/Service';
|
|
319
|
+
} else {
|
|
320
|
+
throw new Error(`Unknown category: ${category}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const isProduction = this.config?.production?.enabled === true;
|
|
325
|
+
const folderSuffix = isProduction ? 'dist' : 'src';
|
|
326
|
+
|
|
327
|
+
const cleanCategoryPath = categoryPath ? categoryPath.replace(/^[/\\]+/, '') : '';
|
|
328
|
+
const targetPath = getPath(import.meta.url, folderSuffix, cleanCategoryPath, componentName);
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
// Check if component already exists
|
|
333
|
+
if (await fs.pathExists(targetPath) && !force) {
|
|
334
|
+
const { overwrite } = await inquirer.prompt([
|
|
335
|
+
{
|
|
336
|
+
type: 'confirm',
|
|
337
|
+
name: 'overwrite',
|
|
338
|
+
message: `The component '${componentName}' already exists locally. Overwrite with the repository version?`,
|
|
339
|
+
default: false
|
|
340
|
+
}
|
|
341
|
+
]);
|
|
342
|
+
|
|
343
|
+
if (!overwrite) {
|
|
344
|
+
Print.info('Installation cancelled by user');
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
// Create component directory
|
|
351
|
+
await fs.ensureDir(targetPath);
|
|
352
|
+
|
|
353
|
+
// Download component files
|
|
354
|
+
const downloadedFiles = await this.downloadComponentFiles(componentName, category, targetPath);
|
|
355
|
+
|
|
356
|
+
await this.updateLocalRegistrySafe(componentName, category);
|
|
357
|
+
|
|
358
|
+
Print.success(`${componentName} installed successfully from official repository!`);
|
|
359
|
+
console.log(`📁 Location: ${[folderSuffix, categoryPath, componentName].join('/').replace(/\/+/g, '/')}/`);
|
|
360
|
+
console.log(`📄 Files: ${downloadedFiles.join(', ')}`);
|
|
361
|
+
|
|
362
|
+
return true;
|
|
363
|
+
|
|
364
|
+
} catch (error) {
|
|
365
|
+
// Only clean up if main file (.js) does not exist
|
|
366
|
+
const mainFilePath = path.join(targetPath, `${componentName}.js`);
|
|
367
|
+
const mainFileExists = await fs.pathExists(mainFilePath);
|
|
368
|
+
|
|
369
|
+
if (!mainFileExists && await fs.pathExists(targetPath)) {
|
|
370
|
+
// Only clean up if main file was not installed
|
|
371
|
+
await fs.remove(targetPath);
|
|
372
|
+
} else if (mainFileExists) {
|
|
373
|
+
Print.warning('Component partially installed - main file exists');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async installMultipleComponents(componentNames, category = 'Visual', force = false) {
|
|
381
|
+
const results = [];
|
|
382
|
+
Print.info(`Getting ${componentNames.length} ${category} components from official repository...`);
|
|
383
|
+
const total = componentNames.length;
|
|
384
|
+
let done = 0;
|
|
385
|
+
const spinner = ora(`Installing 0/${total}`).start();
|
|
386
|
+
const worker = async (componentName) => {
|
|
387
|
+
try {
|
|
388
|
+
const result = await this.installComponent(componentName, category, force);
|
|
389
|
+
results.push({ name: componentName, success: result });
|
|
390
|
+
} catch (error) {
|
|
391
|
+
Print.componentError(componentName, 'getting', error.message);
|
|
392
|
+
results.push({ name: componentName, success: false, error: error.message });
|
|
393
|
+
} finally {
|
|
394
|
+
done += 1;
|
|
395
|
+
spinner.text = `Installing ${done}/${total}`;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
await runConcurrent(componentNames, worker, 3);
|
|
399
|
+
spinner.stop();
|
|
400
|
+
|
|
401
|
+
// Summary
|
|
402
|
+
const successful = results.filter(r => r.success).length;
|
|
403
|
+
const failed = results.filter(r => !r.success).length;
|
|
404
|
+
|
|
405
|
+
Print.newLine();
|
|
406
|
+
Print.summary(successful, failed, componentNames.length);
|
|
407
|
+
|
|
408
|
+
return results;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async updateAllComponents(force = false) {
|
|
412
|
+
Print.info('Looking for updatable Visual components...');
|
|
413
|
+
|
|
414
|
+
const allUpdatableComponents = await this.findUpdatableComponents();
|
|
415
|
+
|
|
416
|
+
// Filter only Visual components
|
|
417
|
+
const updatableComponents = allUpdatableComponents.filter(comp => comp.category === 'Visual');
|
|
418
|
+
|
|
419
|
+
if (updatableComponents.length === 0) {
|
|
420
|
+
Print.info('No local Visual components found that match the official repository');
|
|
421
|
+
Print.info('Use "slice browse" to see available components');
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Show statistics if there are Service components that won't be synced
|
|
426
|
+
const serviceComponents = allUpdatableComponents.filter(comp => comp.category === 'Service');
|
|
427
|
+
if (serviceComponents.length > 0) {
|
|
428
|
+
Print.info(`Found ${serviceComponents.length} Service components (skipped - sync only affects Visual components)`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
Print.newLine();
|
|
432
|
+
Print.subtitle(`Found ${updatableComponents.length} updatable Visual components:`);
|
|
433
|
+
Print.newLine();
|
|
434
|
+
updatableComponents.forEach(comp => {
|
|
435
|
+
console.log(`🎨 ${comp.name} (${comp.category})`);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
if (!force) {
|
|
439
|
+
const { confirmUpdate } = await inquirer.prompt([
|
|
440
|
+
{
|
|
441
|
+
type: 'confirm',
|
|
442
|
+
name: 'confirmUpdate',
|
|
443
|
+
message: `Do you want to update these Visual components to the repository versions?`,
|
|
444
|
+
default: true
|
|
445
|
+
}
|
|
446
|
+
]);
|
|
447
|
+
|
|
448
|
+
if (!confirmUpdate) {
|
|
449
|
+
Print.info('Update cancelled by user');
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Only update Visual components
|
|
455
|
+
const visualComponentNames = updatableComponents.map(c => c.name);
|
|
456
|
+
|
|
457
|
+
Print.info(`Updating ${visualComponentNames.length} Visual components...`);
|
|
458
|
+
const results = await this.installMultipleComponents(visualComponentNames, 'Visual', true);
|
|
459
|
+
|
|
460
|
+
// Final summary
|
|
461
|
+
const totalSuccessful = results.filter(r => r.success).length;
|
|
462
|
+
const totalFailed = results.filter(r => !r.success).length;
|
|
463
|
+
|
|
464
|
+
Print.newLine();
|
|
465
|
+
Print.title('Visual Components Sync Summary');
|
|
466
|
+
Print.success(`Visual components updated: ${totalSuccessful}`);
|
|
467
|
+
|
|
468
|
+
if (totalFailed > 0) {
|
|
469
|
+
Print.error(`Visual components failed: ${totalFailed}`);
|
|
470
|
+
} else {
|
|
471
|
+
Print.success('All your Visual components are now updated to the latest official versions!');
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Additional information about Service components
|
|
475
|
+
if (serviceComponents.length > 0) {
|
|
476
|
+
Print.newLine();
|
|
477
|
+
Print.info(`Note: ${serviceComponents.length} Service components were found but not updated`);
|
|
478
|
+
Print.info('Service components maintain manual versioning - update them individually if needed');
|
|
479
|
+
Print.commandExample('Update Service component manually', 'slice get FetchManager --service --force');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return totalFailed === 0;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
displayAvailableComponents() {
|
|
486
|
+
if (!this.componentsRegistry) {
|
|
487
|
+
Print.error('❌ Could not load component registry');
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
console.log('\n📚 Available components in the official Slice.js repository:\n');
|
|
492
|
+
|
|
493
|
+
const visualComponents = this.getAvailableComponents('Visual');
|
|
494
|
+
const serviceComponents = this.getAvailableComponents('Service');
|
|
495
|
+
|
|
496
|
+
// Only show names without descriptions
|
|
497
|
+
Print.info('🎨 Visual Components (UI):');
|
|
498
|
+
Object.keys(visualComponents).forEach(name => {
|
|
499
|
+
const files = visualComponents[name].files;
|
|
500
|
+
const fileIcons = files.map(file => {
|
|
501
|
+
if (file.endsWith('.js')) return '📜';
|
|
502
|
+
if (file.endsWith('.html')) return '🌐';
|
|
503
|
+
if (file.endsWith('.css')) return '🎨';
|
|
504
|
+
return '📄';
|
|
505
|
+
}).join(' ');
|
|
506
|
+
console.log(` • ${name} ${fileIcons}`);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
Print.info('\n⚙️ Service Components (Logic):');
|
|
510
|
+
Object.keys(serviceComponents).forEach(name => {
|
|
511
|
+
console.log(` • ${name} 📜`);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
Print.newLine();
|
|
515
|
+
Print.info(`Total: ${Object.keys(visualComponents).length} Visual + ${Object.keys(serviceComponents).length} Service components`);
|
|
516
|
+
|
|
517
|
+
console.log(`\n💡 Usage examples:`);
|
|
518
|
+
console.log(`slice get Button Card Input # Install Visual components`);
|
|
519
|
+
console.log(`slice get FetchManager --service # Install Service component`);
|
|
520
|
+
console.log(`slice sync # Sync Visual components`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async interactiveInstall() {
|
|
524
|
+
const { componentType } = await inquirer.prompt([
|
|
525
|
+
{
|
|
526
|
+
type: 'list',
|
|
527
|
+
name: 'componentType',
|
|
528
|
+
message: 'Select the type of component to install from the repository:',
|
|
529
|
+
choices: [
|
|
530
|
+
{ name: '🎨 Visual Components (UI)', value: 'Visual' },
|
|
531
|
+
{ name: '⚙️ Service Components (Logic)', value: 'Service' }
|
|
532
|
+
]
|
|
533
|
+
}
|
|
534
|
+
]);
|
|
535
|
+
|
|
536
|
+
const availableComponents = this.getAvailableComponents(componentType);
|
|
537
|
+
const componentChoices = Object.keys(availableComponents).map(name => ({
|
|
538
|
+
name: name,
|
|
539
|
+
value: name
|
|
540
|
+
}));
|
|
541
|
+
|
|
542
|
+
if (componentType === 'Visual') {
|
|
543
|
+
const { installMode } = await inquirer.prompt([
|
|
544
|
+
{
|
|
545
|
+
type: 'list',
|
|
546
|
+
name: 'installMode',
|
|
547
|
+
message: 'How do you want to install Visual components?',
|
|
548
|
+
choices: [
|
|
549
|
+
{ name: 'Get one', value: 'single' },
|
|
550
|
+
{ name: 'Get multiple', value: 'multiple' }
|
|
551
|
+
]
|
|
552
|
+
}
|
|
553
|
+
]);
|
|
554
|
+
|
|
555
|
+
if (installMode === 'multiple') {
|
|
556
|
+
const { selectedComponents } = await inquirer.prompt([
|
|
557
|
+
{
|
|
558
|
+
type: 'checkbox',
|
|
559
|
+
name: 'selectedComponents',
|
|
560
|
+
message: 'Select Visual components to install from the repository:',
|
|
561
|
+
choices: componentChoices,
|
|
562
|
+
validate: (input) => {
|
|
563
|
+
if (input.length === 0) {
|
|
564
|
+
return 'You must select at least one component';
|
|
565
|
+
}
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
]);
|
|
570
|
+
|
|
571
|
+
await this.installMultipleComponents(selectedComponents, componentType);
|
|
572
|
+
} else {
|
|
573
|
+
const { selectedComponent } = await inquirer.prompt([
|
|
574
|
+
{
|
|
575
|
+
type: 'list',
|
|
576
|
+
name: 'selectedComponent',
|
|
577
|
+
message: 'Select a Visual component:',
|
|
578
|
+
choices: componentChoices
|
|
579
|
+
}
|
|
580
|
+
]);
|
|
581
|
+
|
|
582
|
+
await this.installComponent(selectedComponent, componentType);
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
const { selectedComponent } = await inquirer.prompt([
|
|
586
|
+
{
|
|
587
|
+
type: 'list',
|
|
588
|
+
name: 'selectedComponent',
|
|
589
|
+
message: 'Select a Service component:',
|
|
590
|
+
choices: componentChoices
|
|
591
|
+
}
|
|
592
|
+
]);
|
|
593
|
+
|
|
594
|
+
await this.installComponent(selectedComponent, componentType);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
findComponentInRegistry(componentName) {
|
|
599
|
+
if (!this.componentsRegistry) return null;
|
|
600
|
+
|
|
601
|
+
const normalizedName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
|
|
602
|
+
|
|
603
|
+
if (this.componentsRegistry[normalizedName]) {
|
|
604
|
+
return {
|
|
605
|
+
name: normalizedName,
|
|
606
|
+
category: this.componentsRegistry[normalizedName]
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Main get function
|
|
615
|
+
async function getComponents(componentNames = [], options = {}) {
|
|
616
|
+
const registry = new ComponentRegistry();
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
await registry.loadRegistry();
|
|
620
|
+
} catch (error) {
|
|
621
|
+
Print.error('Could not load component registry from official repository');
|
|
622
|
+
Print.info('Check your internet connection and try again');
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Interactive mode if no components specified
|
|
627
|
+
if (!componentNames || componentNames.length === 0) {
|
|
628
|
+
await registry.interactiveInstall();
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Determine category
|
|
633
|
+
const category = options.service ? 'Service' : 'Visual';
|
|
634
|
+
|
|
635
|
+
if (componentNames.length === 1) {
|
|
636
|
+
// Single component install
|
|
637
|
+
const componentInfo = registry.findComponentInRegistry(componentNames[0]);
|
|
638
|
+
|
|
639
|
+
if (!componentInfo) {
|
|
640
|
+
Print.error(`Component '${componentNames[0]}' not found in official repository`);
|
|
641
|
+
Print.commandExample('View available components', 'slice browse');
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Use the category from registry unless Service is explicitly requested
|
|
646
|
+
const actualCategory = options.service ? 'Service' : componentInfo.category;
|
|
647
|
+
|
|
648
|
+
try {
|
|
649
|
+
await registry.installComponent(componentInfo.name, actualCategory, options.force);
|
|
650
|
+
return true;
|
|
651
|
+
} catch (error) {
|
|
652
|
+
Print.error(`Error installing component: ${error.message}`);
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
// Multiple components install
|
|
657
|
+
const normalizedComponents = componentNames.map(name =>
|
|
658
|
+
name.charAt(0).toUpperCase() + name.slice(1)
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
try {
|
|
662
|
+
await registry.installMultipleComponents(normalizedComponents, category, options.force);
|
|
663
|
+
return true;
|
|
664
|
+
} catch (error) {
|
|
665
|
+
Print.error(`Error installing components: ${error.message}`);
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// List components function
|
|
672
|
+
async function listComponents() {
|
|
673
|
+
const registry = new ComponentRegistry();
|
|
674
|
+
|
|
675
|
+
try {
|
|
676
|
+
await registry.loadRegistry();
|
|
677
|
+
registry.displayAvailableComponents();
|
|
678
|
+
return true;
|
|
679
|
+
} catch (error) {
|
|
680
|
+
Print.error('Could not load component registry from official repository');
|
|
681
|
+
Print.info('Check your internet connection and try again');
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Sync components function
|
|
687
|
+
async function syncComponents(options = {}) {
|
|
688
|
+
const registry = new ComponentRegistry();
|
|
689
|
+
|
|
690
|
+
try {
|
|
691
|
+
await registry.loadRegistry();
|
|
692
|
+
return await registry.updateAllComponents(options.force);
|
|
693
|
+
} catch (error) {
|
|
694
|
+
Print.error('Could not load component registry from official repository');
|
|
695
|
+
Print.info('Check your internet connection and try again');
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export default getComponents;
|
|
701
|
+
export { listComponents, syncComponents, ComponentRegistry, loadConfig, runConcurrent, fetchWithRetry };
|