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,679 +1,859 @@
|
|
|
1
|
-
// cli/utils/bundling/DependencyAnalyzer.js
|
|
2
|
-
import fs from 'fs-extra';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { parse } from '@babel/parser';
|
|
5
|
-
import traverse from '@babel/traverse';
|
|
6
|
-
import { getSrcPath, getComponentsJsPath, getProjectRoot } from '../PathHelper.js';
|
|
7
|
-
|
|
8
|
-
export default class DependencyAnalyzer {
|
|
9
|
-
constructor(moduleUrl) {
|
|
10
|
-
this.moduleUrl = moduleUrl;
|
|
11
|
-
this.projectRoot = getProjectRoot(moduleUrl);
|
|
12
|
-
this.componentsPath = path.dirname(getComponentsJsPath(moduleUrl));
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (!
|
|
69
|
-
throw new Error('
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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
|
-
if (
|
|
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
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
const
|
|
454
|
-
if (
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
-
|
|
1
|
+
// cli/utils/bundling/DependencyAnalyzer.js
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { parse } from '@babel/parser';
|
|
5
|
+
import traverse from '@babel/traverse';
|
|
6
|
+
import { getSrcPath, getComponentsJsPath, getProjectRoot } from '../PathHelper.js';
|
|
7
|
+
|
|
8
|
+
export default class DependencyAnalyzer {
|
|
9
|
+
constructor(moduleUrl) {
|
|
10
|
+
this.moduleUrl = moduleUrl;
|
|
11
|
+
this.projectRoot = getProjectRoot(moduleUrl);
|
|
12
|
+
this.componentsPath = path.dirname(getComponentsJsPath(moduleUrl));
|
|
13
|
+
this.frameworkComponentsPath = path.join(
|
|
14
|
+
this.projectRoot,
|
|
15
|
+
'node_modules',
|
|
16
|
+
'slicejs-web-framework',
|
|
17
|
+
'Slice',
|
|
18
|
+
'Components',
|
|
19
|
+
'Structural'
|
|
20
|
+
);
|
|
21
|
+
this.routesPath = getSrcPath(moduleUrl, 'routes.js');
|
|
22
|
+
|
|
23
|
+
// Analysis storage
|
|
24
|
+
this.components = new Map();
|
|
25
|
+
this.routes = new Map();
|
|
26
|
+
this.dependencyGraph = new Map();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Executes complete project analysis
|
|
31
|
+
*/
|
|
32
|
+
async analyze() {
|
|
33
|
+
console.log('š Analyzing project...');
|
|
34
|
+
|
|
35
|
+
// 1. Load component configuration
|
|
36
|
+
await this.loadComponentsConfig();
|
|
37
|
+
|
|
38
|
+
// 2. Analyze component files
|
|
39
|
+
await this.analyzeComponents();
|
|
40
|
+
await this.analyzeFrameworkComponents();
|
|
41
|
+
|
|
42
|
+
// 3. Load and analyze routes
|
|
43
|
+
await this.analyzeRoutes();
|
|
44
|
+
|
|
45
|
+
// 4. Build dependency graph
|
|
46
|
+
this.buildDependencyGraph();
|
|
47
|
+
|
|
48
|
+
// 5. Calculate metrics
|
|
49
|
+
const metrics = this.calculateMetrics();
|
|
50
|
+
|
|
51
|
+
console.log('ā
Analysis completed');
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
components: Array.from(this.components.values()),
|
|
55
|
+
routes: Array.from(this.routes.values()),
|
|
56
|
+
dependencyGraph: this.dependencyGraph,
|
|
57
|
+
routeGroups: this.routeGroups,
|
|
58
|
+
metrics
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Loads component configuration from components.js
|
|
64
|
+
*/
|
|
65
|
+
async loadComponentsConfig() {
|
|
66
|
+
const componentsConfigPath = path.join(this.componentsPath, 'components.js');
|
|
67
|
+
|
|
68
|
+
if (!await fs.pathExists(componentsConfigPath)) {
|
|
69
|
+
throw new Error('components.js not found');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Read and parse components.js
|
|
73
|
+
const content = await fs.readFile(componentsConfigPath, 'utf-8');
|
|
74
|
+
|
|
75
|
+
const config = this.parseComponentsConfig(content);
|
|
76
|
+
|
|
77
|
+
// Group components by category
|
|
78
|
+
const categoryMap = new Map();
|
|
79
|
+
|
|
80
|
+
// Build category map from component assignments
|
|
81
|
+
for (const [componentName, categoryName] of Object.entries(config)) {
|
|
82
|
+
if (!categoryMap.has(categoryName)) {
|
|
83
|
+
categoryMap.set(categoryName, []);
|
|
84
|
+
}
|
|
85
|
+
categoryMap.get(categoryName).push(componentName);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Process each category
|
|
89
|
+
for (const [categoryName, componentList] of categoryMap) {
|
|
90
|
+
// Determine category type based on category name
|
|
91
|
+
let categoryType = 'Visual'; // default
|
|
92
|
+
if (categoryName === 'Service') categoryType = 'Service';
|
|
93
|
+
if (categoryName === 'AppComponents') categoryType = 'Visual'; // AppComponents are visual
|
|
94
|
+
|
|
95
|
+
// Find category path
|
|
96
|
+
const categoryPath = path.join(this.componentsPath, categoryName);
|
|
97
|
+
|
|
98
|
+
if (await fs.pathExists(categoryPath)) {
|
|
99
|
+
const files = await fs.readdir(categoryPath);
|
|
100
|
+
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
const componentPath = path.join(categoryPath, file);
|
|
103
|
+
const stat = await fs.stat(componentPath);
|
|
104
|
+
|
|
105
|
+
if (stat.isDirectory() && componentList.includes(file)) {
|
|
106
|
+
this.components.set(file, {
|
|
107
|
+
name: file,
|
|
108
|
+
category: categoryName,
|
|
109
|
+
categoryType: categoryType,
|
|
110
|
+
path: componentPath,
|
|
111
|
+
dependencies: new Set(),
|
|
112
|
+
usedBy: new Set(),
|
|
113
|
+
routes: new Set(),
|
|
114
|
+
size: 0
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parses components.js safely using AST (no eval).
|
|
124
|
+
* @param {string} content
|
|
125
|
+
* @returns {Record<string, string>}
|
|
126
|
+
*/
|
|
127
|
+
parseComponentsConfig(content) {
|
|
128
|
+
try {
|
|
129
|
+
const ast = parse(content, {
|
|
130
|
+
sourceType: 'module',
|
|
131
|
+
plugins: ['jsx']
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
let componentsNode = null;
|
|
135
|
+
|
|
136
|
+
traverse.default(ast, {
|
|
137
|
+
VariableDeclarator(path) {
|
|
138
|
+
if (path.node.id?.type === 'Identifier' && path.node.id.name === 'components') {
|
|
139
|
+
componentsNode = path.node.init;
|
|
140
|
+
path.stop();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!componentsNode || componentsNode.type !== 'ObjectExpression') {
|
|
146
|
+
throw new Error('components object not found');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const config = {};
|
|
150
|
+
for (const prop of componentsNode.properties) {
|
|
151
|
+
if (prop.type !== 'ObjectProperty') continue;
|
|
152
|
+
|
|
153
|
+
const key = this.extractStringValue(prop.key);
|
|
154
|
+
const value = this.extractStringValue(prop.value);
|
|
155
|
+
|
|
156
|
+
if (!key || !value) {
|
|
157
|
+
throw new Error('Invalid components entry');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
config[key] = value;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return config;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error(`Could not parse components.js: ${error.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Extracts string values from AST nodes.
|
|
172
|
+
* @param {object} node
|
|
173
|
+
* @returns {string|null}
|
|
174
|
+
*/
|
|
175
|
+
extractStringValue(node) {
|
|
176
|
+
if (!node) return null;
|
|
177
|
+
|
|
178
|
+
if (node.type === 'StringLiteral') {
|
|
179
|
+
return node.value;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (node.type === 'Identifier') {
|
|
183
|
+
return node.name;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
187
|
+
return node.quasis.map((q) => q.value.cooked).join('');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Analyzes each component's files
|
|
195
|
+
*/
|
|
196
|
+
async analyzeComponents() {
|
|
197
|
+
for (const [name, component] of this.components) {
|
|
198
|
+
const jsFile = path.join(component.path, `${name}.js`);
|
|
199
|
+
|
|
200
|
+
if (!await fs.pathExists(jsFile)) continue;
|
|
201
|
+
|
|
202
|
+
// Read JavaScript file
|
|
203
|
+
const content = await fs.readFile(jsFile, 'utf-8');
|
|
204
|
+
|
|
205
|
+
// Calculate size
|
|
206
|
+
component.size = await this.calculateComponentSize(component.path);
|
|
207
|
+
|
|
208
|
+
// Parse and extract dependencies
|
|
209
|
+
component.dependencies = await this.extractDependencies(content, jsFile);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async analyzeFrameworkComponents() {
|
|
214
|
+
if (!await fs.pathExists(this.frameworkComponentsPath)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const frameworkConfigPath = path.join(this.projectRoot, 'src', 'sliceConfig.json');
|
|
218
|
+
let frameworkConfig = {};
|
|
219
|
+
try {
|
|
220
|
+
frameworkConfig = await fs.readJson(frameworkConfigPath);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.warn('Warning: Could not read sliceConfig.json for framework bundling:', error.message);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const enabledStructural = this.getEnabledStructuralComponents(frameworkConfig);
|
|
226
|
+
const componentEntries = await fs.readdir(this.frameworkComponentsPath);
|
|
227
|
+
|
|
228
|
+
for (const componentName of componentEntries) {
|
|
229
|
+
if (!enabledStructural.has(componentName)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const componentPath = path.join(this.frameworkComponentsPath, componentName);
|
|
234
|
+
const componentStat = await fs.stat(componentPath);
|
|
235
|
+
if (!componentStat.isDirectory()) continue;
|
|
236
|
+
|
|
237
|
+
const key = `Framework/Structural/${componentName}`;
|
|
238
|
+
if (this.components.has(key)) continue;
|
|
239
|
+
|
|
240
|
+
this.components.set(key, {
|
|
241
|
+
name: componentName,
|
|
242
|
+
category: 'Framework/Structural',
|
|
243
|
+
categoryType: 'Visual',
|
|
244
|
+
path: componentPath,
|
|
245
|
+
dependencies: new Set(),
|
|
246
|
+
usedBy: new Set(),
|
|
247
|
+
routes: new Set(),
|
|
248
|
+
size: 0,
|
|
249
|
+
isFramework: true
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (enabledStructural.has('EventManagerDebugger')) {
|
|
254
|
+
this.addFrameworkFileComponent('EventManagerDebugger', 'EventManager');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (enabledStructural.has('ContextManagerDebugger')) {
|
|
258
|
+
this.addFrameworkFileComponent('ContextManagerDebugger', 'ContextManager');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (enabledStructural.has('ThemeManager')) {
|
|
262
|
+
this.addFrameworkFileComponent('ThemeManager', path.join('StylesManager', 'ThemeManager'));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
addFrameworkFileComponent(componentName, folderName) {
|
|
267
|
+
const componentPath = path.join(this.frameworkComponentsPath, folderName);
|
|
268
|
+
const key = `Framework/Structural/${componentName}`;
|
|
269
|
+
|
|
270
|
+
if (this.components.has(key)) return;
|
|
271
|
+
|
|
272
|
+
this.components.set(key, {
|
|
273
|
+
name: componentName,
|
|
274
|
+
fileName: componentName,
|
|
275
|
+
category: 'Framework/Structural',
|
|
276
|
+
categoryType: 'Visual',
|
|
277
|
+
path: componentPath,
|
|
278
|
+
dependencies: new Set(),
|
|
279
|
+
usedBy: new Set(),
|
|
280
|
+
routes: new Set(),
|
|
281
|
+
size: 0,
|
|
282
|
+
isFramework: true
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
getEnabledStructuralComponents(config) {
|
|
287
|
+
const enabled = new Set(['Controller', 'Router', 'StylesManager']);
|
|
288
|
+
|
|
289
|
+
if (config?.logger?.enabled) {
|
|
290
|
+
enabled.add('Logger');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (config?.debugger?.enabled) {
|
|
294
|
+
enabled.add('Debugger');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (config?.events?.enabled) {
|
|
298
|
+
enabled.add('EventManager');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (config?.events?.ui?.enabled) {
|
|
302
|
+
enabled.add('EventManagerDebugger');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (config?.context?.enabled) {
|
|
306
|
+
enabled.add('ContextManager');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (config?.context?.ui?.enabled) {
|
|
310
|
+
enabled.add('ContextManagerDebugger');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (config?.themeManager?.enabled) {
|
|
314
|
+
enabled.add('ThemeManager');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return enabled;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Extracts dependencies from a component file
|
|
322
|
+
*/
|
|
323
|
+
async extractDependencies(code, componentFilePath = null) {
|
|
324
|
+
const dependencies = new Set();
|
|
325
|
+
|
|
326
|
+
const resolveRoutesArray = (node, scope) => {
|
|
327
|
+
if (!node) return null;
|
|
328
|
+
|
|
329
|
+
if (node.type === 'ArrayExpression') {
|
|
330
|
+
return node;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (node.type === 'ObjectExpression') {
|
|
334
|
+
const routesProp = node.properties.find(p => p.key?.name === 'routes');
|
|
335
|
+
if (routesProp?.value) {
|
|
336
|
+
return resolveRoutesArray(routesProp.value, scope);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (node.type === 'Identifier' && scope) {
|
|
341
|
+
const binding = scope.getBinding(node.name);
|
|
342
|
+
if (!binding) return null;
|
|
343
|
+
const bindingNode = binding.path?.node;
|
|
344
|
+
|
|
345
|
+
if (bindingNode?.type === 'VariableDeclarator') {
|
|
346
|
+
const init = bindingNode.init;
|
|
347
|
+
if (init?.type === 'ArrayExpression') {
|
|
348
|
+
return init;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (init?.type === 'Identifier') {
|
|
352
|
+
return resolveRoutesArray(init, binding.path.scope);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (init?.type === 'ObjectExpression') {
|
|
356
|
+
return resolveRoutesArray(init, binding.path.scope);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (init?.type === 'MemberExpression') {
|
|
360
|
+
return resolveRoutesArray(init, binding.path.scope);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (bindingNode?.type === 'ImportSpecifier' || bindingNode?.type === 'ImportDefaultSpecifier') {
|
|
365
|
+
const parent = binding.path.parentPath?.node;
|
|
366
|
+
if (parent?.type === 'ImportDeclaration') {
|
|
367
|
+
const importedName = bindingNode.type === 'ImportDefaultSpecifier'
|
|
368
|
+
? 'default'
|
|
369
|
+
: bindingNode.imported?.name;
|
|
370
|
+
const importedNode = resolveImportedValue(parent.source.value, importedName, componentFilePath);
|
|
371
|
+
return resolveRoutesArray(importedNode, null);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (node.type === 'MemberExpression' && scope) {
|
|
377
|
+
const objectNode = resolveObjectExpression(node.object, scope);
|
|
378
|
+
if (objectNode) {
|
|
379
|
+
const propName = node.property?.name || node.property?.value;
|
|
380
|
+
if (propName) {
|
|
381
|
+
const prop = objectNode.properties.find(p => p.key?.name === propName || p.key?.value === propName);
|
|
382
|
+
if (prop?.value) {
|
|
383
|
+
return resolveRoutesArray(prop.value, scope);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return null;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const resolveObjectExpression = (node, scope) => {
|
|
393
|
+
if (!node) return null;
|
|
394
|
+
if (node.type === 'ObjectExpression') return node;
|
|
395
|
+
|
|
396
|
+
if (node.type === 'Identifier' && scope) {
|
|
397
|
+
const binding = scope.getBinding(node.name);
|
|
398
|
+
const bindingNode = binding?.path?.node;
|
|
399
|
+
if (bindingNode?.type === 'VariableDeclarator') {
|
|
400
|
+
const init = bindingNode.init;
|
|
401
|
+
if (init?.type === 'ObjectExpression') {
|
|
402
|
+
return init;
|
|
403
|
+
}
|
|
404
|
+
if (init?.type === 'Identifier') {
|
|
405
|
+
return resolveObjectExpression(init, binding.path.scope);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (bindingNode?.type === 'ImportSpecifier' || bindingNode?.type === 'ImportDefaultSpecifier') {
|
|
410
|
+
const parent = binding.path.parentPath?.node;
|
|
411
|
+
if (parent?.type === 'ImportDeclaration') {
|
|
412
|
+
const importedName = bindingNode.type === 'ImportDefaultSpecifier'
|
|
413
|
+
? 'default'
|
|
414
|
+
: bindingNode.imported?.name;
|
|
415
|
+
const importedNode = resolveImportedValue(parent.source.value, importedName, componentFilePath);
|
|
416
|
+
return resolveObjectExpression(importedNode, null);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return null;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const resolveStringValue = (node, scope) => {
|
|
425
|
+
if (!node) return null;
|
|
426
|
+
if (node.type === 'StringLiteral') return node.value;
|
|
427
|
+
if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
428
|
+
return node.quasis.map(q => q.value.cooked).join('');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (node.type === 'Identifier' && scope) {
|
|
432
|
+
const binding = scope.getBinding(node.name);
|
|
433
|
+
const bindingNode = binding?.path?.node;
|
|
434
|
+
if (bindingNode?.type === 'VariableDeclarator') {
|
|
435
|
+
return resolveStringValue(bindingNode.init, binding.path.scope);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (bindingNode?.type === 'ImportSpecifier' || bindingNode?.type === 'ImportDefaultSpecifier') {
|
|
439
|
+
const parent = binding.path.parentPath?.node;
|
|
440
|
+
if (parent?.type === 'ImportDeclaration') {
|
|
441
|
+
const importedName = bindingNode.type === 'ImportDefaultSpecifier'
|
|
442
|
+
? 'default'
|
|
443
|
+
: bindingNode.imported?.name;
|
|
444
|
+
const importedNode = resolveImportedValue(parent.source.value, importedName, componentFilePath);
|
|
445
|
+
return resolveStringValue(importedNode, null);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (node.type === 'MemberExpression' && scope) {
|
|
451
|
+
const objectNode = resolveObjectExpression(node.object, scope);
|
|
452
|
+
if (objectNode) {
|
|
453
|
+
const propName = node.property?.name || node.property?.value;
|
|
454
|
+
if (propName) {
|
|
455
|
+
const prop = objectNode.properties.find(p => p.key?.name === propName || p.key?.value === propName);
|
|
456
|
+
if (prop?.value) {
|
|
457
|
+
return resolveStringValue(prop.value, scope);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return null;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const resolveImportedValue = (importPath, importedName, fromFilePath) => {
|
|
467
|
+
if (!fromFilePath) return null;
|
|
468
|
+
|
|
469
|
+
const baseDir = path.dirname(fromFilePath);
|
|
470
|
+
const resolvedPath = resolveImportPath(importPath, baseDir);
|
|
471
|
+
if (!resolvedPath) {
|
|
472
|
+
console.warn(`ā ļø Cannot resolve import for MultiRoute routes: ${importPath}`);
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const cacheKey = `${resolvedPath}:${importedName || 'default'}`;
|
|
477
|
+
if (!resolveImportedValue.cache) {
|
|
478
|
+
resolveImportedValue.cache = new Map();
|
|
479
|
+
}
|
|
480
|
+
if (resolveImportedValue.cache.has(cacheKey)) {
|
|
481
|
+
return resolveImportedValue.cache.get(cacheKey);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
const source = fs.readFileSync(resolvedPath, 'utf-8');
|
|
486
|
+
const importAst = parse(source, {
|
|
487
|
+
sourceType: 'module',
|
|
488
|
+
plugins: ['jsx']
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const topLevelBindings = new Map();
|
|
492
|
+
for (const node of importAst.program.body) {
|
|
493
|
+
if (node.type === 'VariableDeclaration') {
|
|
494
|
+
node.declarations.forEach(decl => {
|
|
495
|
+
if (decl.id?.type === 'Identifier') {
|
|
496
|
+
topLevelBindings.set(decl.id.name, decl.init);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
let exportNode = null;
|
|
503
|
+
for (const node of importAst.program.body) {
|
|
504
|
+
if (node.type === 'ExportDefaultDeclaration' && importedName === 'default') {
|
|
505
|
+
exportNode = node.declaration;
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
510
|
+
if (node.declaration?.type === 'VariableDeclaration') {
|
|
511
|
+
for (const decl of node.declaration.declarations) {
|
|
512
|
+
if (decl.id?.name === importedName) {
|
|
513
|
+
exportNode = decl.init;
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (!exportNode && node.specifiers?.length) {
|
|
520
|
+
const specifier = node.specifiers.find(s => s.exported?.name === importedName);
|
|
521
|
+
if (specifier && specifier.local?.name) {
|
|
522
|
+
exportNode = topLevelBindings.get(specifier.local.name) || null;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (exportNode) break;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (exportNode?.type === 'Identifier') {
|
|
531
|
+
exportNode = topLevelBindings.get(exportNode.name) || exportNode;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
resolveImportedValue.cache.set(cacheKey, exportNode || null);
|
|
535
|
+
return exportNode || null;
|
|
536
|
+
} catch (error) {
|
|
537
|
+
console.warn(`ā ļø Error resolving import ${importPath}: ${error.message}`);
|
|
538
|
+
resolveImportedValue.cache.set(cacheKey, null);
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
const resolveImportPath = (importPath, baseDir) => {
|
|
544
|
+
if (!importPath.startsWith('.')) return null;
|
|
545
|
+
|
|
546
|
+
const resolvedBase = path.resolve(baseDir, importPath);
|
|
547
|
+
const extensions = ['.js', '.mjs', '.cjs', '.json'];
|
|
548
|
+
|
|
549
|
+
if (fs.existsSync(resolvedBase) && fs.statSync(resolvedBase).isFile()) {
|
|
550
|
+
return resolvedBase;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (!path.extname(resolvedBase)) {
|
|
554
|
+
for (const ext of extensions) {
|
|
555
|
+
const candidate = resolvedBase + ext;
|
|
556
|
+
if (fs.existsSync(candidate)) {
|
|
557
|
+
return candidate;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return null;
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const addMultiRouteDependencies = (routesArrayNode, scope) => {
|
|
566
|
+
if (!routesArrayNode || routesArrayNode.type !== 'ArrayExpression') return;
|
|
567
|
+
|
|
568
|
+
routesArrayNode.elements.forEach(routeElement => {
|
|
569
|
+
if (!routeElement) return;
|
|
570
|
+
|
|
571
|
+
if (routeElement.type === 'SpreadElement') {
|
|
572
|
+
const spreadArray = resolveRoutesArray(routeElement.argument, scope);
|
|
573
|
+
addMultiRouteDependencies(spreadArray, scope);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const routeObject = resolveObjectExpression(routeElement, scope) || routeElement;
|
|
578
|
+
if (routeObject?.type === 'ObjectExpression') {
|
|
579
|
+
const componentProp = routeObject.properties.find(p => p.key?.name === 'component');
|
|
580
|
+
if (componentProp?.value) {
|
|
581
|
+
const componentName = resolveStringValue(componentProp.value, scope);
|
|
582
|
+
if (componentName) {
|
|
583
|
+
dependencies.add(componentName);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
const ast = parse(code, {
|
|
592
|
+
sourceType: 'module',
|
|
593
|
+
plugins: ['jsx']
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
traverse.default(ast, {
|
|
597
|
+
// Detect slice.build() calls
|
|
598
|
+
CallExpression(path) {
|
|
599
|
+
const { callee, arguments: args } = path.node;
|
|
600
|
+
|
|
601
|
+
// slice.build('MultiRoute', { routes: [...] })
|
|
602
|
+
if (
|
|
603
|
+
callee.type === 'MemberExpression' &&
|
|
604
|
+
callee.object.name === 'slice' &&
|
|
605
|
+
callee.property.name === 'build' &&
|
|
606
|
+
args[0]?.type === 'StringLiteral' &&
|
|
607
|
+
args[0].value === 'MultiRoute' &&
|
|
608
|
+
args[1]?.type === 'ObjectExpression'
|
|
609
|
+
) {
|
|
610
|
+
// Add MultiRoute itself
|
|
611
|
+
dependencies.add('MultiRoute');
|
|
612
|
+
|
|
613
|
+
// Extract routes from MultiRoute props
|
|
614
|
+
const routesProp = args[1].properties.find(p => p.key?.name === 'routes');
|
|
615
|
+
if (routesProp) {
|
|
616
|
+
const routesArrayNode = resolveRoutesArray(routesProp.value, path.scope);
|
|
617
|
+
addMultiRouteDependencies(routesArrayNode);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Regular slice.build() calls
|
|
621
|
+
else if (
|
|
622
|
+
callee.type === 'MemberExpression' &&
|
|
623
|
+
callee.object.name === 'slice' &&
|
|
624
|
+
callee.property.name === 'build' &&
|
|
625
|
+
args[0]?.type === 'StringLiteral'
|
|
626
|
+
) {
|
|
627
|
+
dependencies.add(args[0].value);
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
// Detect direct imports (less common but possible)
|
|
632
|
+
ImportDeclaration(path) {
|
|
633
|
+
const importPath = path.node.source.value;
|
|
634
|
+
if (importPath.includes('/Components/')) {
|
|
635
|
+
const componentName = importPath.split('/').pop();
|
|
636
|
+
dependencies.add(componentName);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
} catch (error) {
|
|
641
|
+
console.warn(`ā ļø Error parsing component: ${error.message}`);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return dependencies;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Analyzes the routes file and detects route groups
|
|
649
|
+
*/
|
|
650
|
+
async analyzeRoutes() {
|
|
651
|
+
if (!await fs.pathExists(this.routesPath)) {
|
|
652
|
+
throw new Error('routes.js no encontrado');
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const content = await fs.readFile(this.routesPath, 'utf-8');
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
const ast = parse(content, {
|
|
659
|
+
sourceType: 'module',
|
|
660
|
+
plugins: ['jsx']
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
let currentRoute = null;
|
|
664
|
+
const self = this; // Guardar referencia a la instancia
|
|
665
|
+
|
|
666
|
+
traverse.default(ast, {
|
|
667
|
+
ObjectExpression(path) {
|
|
668
|
+
// Buscar objetos de ruta: { path: '/', component: 'HomePage' }
|
|
669
|
+
const properties = path.node.properties;
|
|
670
|
+
const pathProp = properties.find(p => p.key?.name === 'path');
|
|
671
|
+
const componentProp = properties.find(p => p.key?.name === 'component');
|
|
672
|
+
|
|
673
|
+
if (pathProp && componentProp) {
|
|
674
|
+
const routePath = pathProp.value.value;
|
|
675
|
+
const componentName = componentProp.value.value;
|
|
676
|
+
|
|
677
|
+
currentRoute = {
|
|
678
|
+
path: routePath,
|
|
679
|
+
component: componentName,
|
|
680
|
+
dependencies: new Set([componentName])
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
self.routes.set(routePath, currentRoute);
|
|
684
|
+
|
|
685
|
+
// Marcar el componente como usado por esta ruta
|
|
686
|
+
if (self.components.has(componentName)) {
|
|
687
|
+
self.components.get(componentName).routes.add(routePath);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// Detect and store route groups based on MultiRoute usage
|
|
694
|
+
this.routeGroups = this.detectRouteGroups();
|
|
695
|
+
|
|
696
|
+
} catch (error) {
|
|
697
|
+
console.warn(`ā ļø Error parseando rutas: ${error.message}`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Builds the complete dependency graph
|
|
703
|
+
*/
|
|
704
|
+
buildDependencyGraph() {
|
|
705
|
+
// Propagate transitive dependencies
|
|
706
|
+
for (const [name, component] of this.components) {
|
|
707
|
+
this.dependencyGraph.set(name, this.getAllDependencies(name, new Set()));
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Calculate usedBy (inverse dependencies)
|
|
711
|
+
for (const [name, deps] of this.dependencyGraph) {
|
|
712
|
+
for (const dep of deps) {
|
|
713
|
+
if (this.components.has(dep)) {
|
|
714
|
+
this.components.get(dep).usedBy.add(name);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Detects route groups based on MultiRoute usage
|
|
722
|
+
*/
|
|
723
|
+
detectRouteGroups() {
|
|
724
|
+
const routeGroups = new Map();
|
|
725
|
+
|
|
726
|
+
for (const [componentName, component] of this.components) {
|
|
727
|
+
// Check if component uses MultiRoute
|
|
728
|
+
const hasMultiRoute = Array.from(component.dependencies).includes('MultiRoute');
|
|
729
|
+
|
|
730
|
+
if (hasMultiRoute) {
|
|
731
|
+
// Find all routes that point to this component
|
|
732
|
+
const relatedRoutes = Array.from(this.routes.values())
|
|
733
|
+
.filter(route => route.component === componentName);
|
|
734
|
+
|
|
735
|
+
if (relatedRoutes.length > 1) {
|
|
736
|
+
// Group these routes together
|
|
737
|
+
const groupKey = `multiroute-${componentName}`;
|
|
738
|
+
routeGroups.set(groupKey, {
|
|
739
|
+
component: componentName,
|
|
740
|
+
routes: relatedRoutes.map(r => r.path),
|
|
741
|
+
type: 'multiroute'
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// Mark component as multiroute handler
|
|
745
|
+
component.isMultiRouteHandler = true;
|
|
746
|
+
component.multiRoutePaths = relatedRoutes.map(r => r.path);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return routeGroups;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Gets all dependencies of a component (recursive)
|
|
756
|
+
*/
|
|
757
|
+
getAllDependencies(componentName, visited = new Set()) {
|
|
758
|
+
if (visited.has(componentName)) return new Set();
|
|
759
|
+
visited.add(componentName);
|
|
760
|
+
|
|
761
|
+
const component = this.components.get(componentName);
|
|
762
|
+
if (!component) return new Set();
|
|
763
|
+
|
|
764
|
+
const allDeps = new Set(component.dependencies);
|
|
765
|
+
|
|
766
|
+
for (const dep of component.dependencies) {
|
|
767
|
+
const transitiveDeps = this.getAllDependencies(dep, visited);
|
|
768
|
+
for (const transDep of transitiveDeps) {
|
|
769
|
+
allDeps.add(transDep);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return allDeps;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Calculates the total size of a component (JS + HTML + CSS)
|
|
778
|
+
*/
|
|
779
|
+
async calculateComponentSize(componentPath) {
|
|
780
|
+
let totalSize = 0;
|
|
781
|
+
const files = await fs.readdir(componentPath);
|
|
782
|
+
|
|
783
|
+
for (const file of files) {
|
|
784
|
+
const filePath = path.join(componentPath, file);
|
|
785
|
+
const stat = await fs.stat(filePath);
|
|
786
|
+
|
|
787
|
+
if (stat.isFile()) {
|
|
788
|
+
totalSize += stat.size;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return totalSize;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Calculates project metrics
|
|
797
|
+
*/
|
|
798
|
+
calculateMetrics() {
|
|
799
|
+
const totalComponents = this.components.size;
|
|
800
|
+
const totalRoutes = this.routes.size;
|
|
801
|
+
|
|
802
|
+
// Shared components (used in multiple routes)
|
|
803
|
+
const sharedComponents = Array.from(this.components.values())
|
|
804
|
+
.filter(c => c.routes.size >= 2);
|
|
805
|
+
|
|
806
|
+
// Total size
|
|
807
|
+
const totalSize = Array.from(this.components.values())
|
|
808
|
+
.reduce((sum, c) => sum + c.size, 0);
|
|
809
|
+
|
|
810
|
+
// Components by category
|
|
811
|
+
const byCategory = {};
|
|
812
|
+
for (const comp of this.components.values()) {
|
|
813
|
+
byCategory[comp.category] = (byCategory[comp.category] || 0) + 1;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Top components by usage
|
|
817
|
+
const topByUsage = Array.from(this.components.values())
|
|
818
|
+
.sort((a, b) => b.routes.size - a.routes.size)
|
|
819
|
+
.slice(0, 10)
|
|
820
|
+
.map(c => ({
|
|
821
|
+
name: c.name,
|
|
822
|
+
routes: c.routes.size,
|
|
823
|
+
size: c.size
|
|
824
|
+
}));
|
|
825
|
+
|
|
826
|
+
return {
|
|
827
|
+
totalComponents,
|
|
828
|
+
totalRoutes,
|
|
829
|
+
sharedComponentsCount: sharedComponents.length,
|
|
830
|
+
sharedPercentage: (sharedComponents.length / totalComponents * 100).toFixed(1),
|
|
831
|
+
totalSize,
|
|
832
|
+
averageSize: Math.round(totalSize / totalComponents),
|
|
833
|
+
byCategory,
|
|
834
|
+
topByUsage
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Generates a visual report of the analysis
|
|
840
|
+
*/
|
|
841
|
+
generateReport(metrics) {
|
|
842
|
+
console.log('\nš PROJECT ANALYSIS\n');
|
|
843
|
+
console.log(`Total components: ${metrics.totalComponents}`);
|
|
844
|
+
console.log(`Total routes: ${metrics.totalRoutes}`);
|
|
845
|
+
console.log(`Shared components: ${metrics.sharedComponentsCount} (${metrics.sharedPercentage}%)`);
|
|
846
|
+
console.log(`Total size: ${(metrics.totalSize / 1024).toFixed(1)} KB`);
|
|
847
|
+
console.log(`Average size: ${(metrics.averageSize / 1024).toFixed(1)} KB per component`);
|
|
848
|
+
|
|
849
|
+
console.log('\nš¦ By category:');
|
|
850
|
+
for (const [category, count] of Object.entries(metrics.byCategory)) {
|
|
851
|
+
console.log(` ${category}: ${count} components`);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
console.log('\nš„ Top 10 most used components:');
|
|
855
|
+
metrics.topByUsage.forEach((comp, i) => {
|
|
856
|
+
console.log(` ${i + 1}. ${comp.name} - ${comp.routes} routes - ${(comp.size / 1024).toFixed(1)} KB`);
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|