pkg-scaffold 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.scaffold-ignore +22 -0
- package/bin/cli.js +91 -0
- package/index.js +2254 -2544
- package/package.json +19 -7
- package/src/EngineContext.js +282 -0
- package/src/ast/ASTAnalyzer.js +313 -0
- package/src/ast/BarrelParser.js +177 -0
- package/src/ast/MagicDetector.js +154 -0
- package/src/healing/GitSandbox.js +160 -0
- package/src/healing/SelfHealer.js +150 -0
- package/src/index.js +343 -0
- package/src/performance/GraphCache.js +82 -0
- package/src/performance/SupplyChainGuard.js +106 -0
- package/src/performance/WorkerPool.js +89 -0
- package/src/performance/WorkerTaskRunner.js +64 -0
- package/src/refractor/ImpactAnalyzer.js +92 -0
- package/src/refractor/SourceRewriter.js +86 -0
- package/src/refractor/TransactionManager.js +131 -0
- package/src/refractor/TypeIntegrity.js +75 -0
- package/src/resolution/DepencyResolver.js +120 -0
- package/src/resolution/PathMapper.js +115 -0
- package/src/resolution/WorkSpaceGraph.js +171 -0
- package/tsconfig.json +26 -0
package/index.js
CHANGED
|
@@ -1,2544 +1,2254 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* ============================================================================
|
|
5
|
-
* 📦 pkg-scaffold v2.
|
|
6
|
-
* ============================================================================
|
|
7
|
-
* * Eine hochgradig integrierte Code-Analyse- und Projektbootstrapping-Engine.
|
|
8
|
-
* Kombiniert rekursive Erreichbarkeitsanalysen (Reachability Graphs) auf
|
|
9
|
-
* Knip-Niveau mit proaktiven Code-Qualitäts-Audits, Sicherheitsprüfungen,
|
|
10
|
-
* statischem Schwachstellen-Scanning und interaktivem Scaffolding.
|
|
11
|
-
*
|
|
12
|
-
* Diese Datei ist als vollständig entfalteter, langformbasierter Monolith
|
|
13
|
-
* strukturiert, um maximale Wartbarkeit, lückenlose Fehlerabdeckung und
|
|
14
|
-
* absolute Ausführungssicherheit im Produktivbetrieb zu garantieren.
|
|
15
|
-
* * Eigenschaften:
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
import
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (fs.existsSync(path.join(targetDir, '
|
|
499
|
-
detectedLockfiles.push('
|
|
500
|
-
}
|
|
501
|
-
if (fs.existsSync(path.join(targetDir, '
|
|
502
|
-
detectedLockfiles.push('
|
|
503
|
-
}
|
|
504
|
-
if (fs.existsSync(path.join(targetDir, '
|
|
505
|
-
detectedLockfiles.push('
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
if (detectedLockfiles.
|
|
516
|
-
return '
|
|
517
|
-
}
|
|
518
|
-
if (detectedLockfiles.includes('
|
|
519
|
-
return '
|
|
520
|
-
}
|
|
521
|
-
if (detectedLockfiles.includes('
|
|
522
|
-
return '
|
|
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
|
-
if (spaces ===
|
|
566
|
-
stats.style.
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
if (REGEX_PATTERNS.
|
|
575
|
-
stats.quality.
|
|
576
|
-
}
|
|
577
|
-
if (REGEX_PATTERNS.
|
|
578
|
-
stats.quality.
|
|
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
|
-
if (importString
|
|
615
|
-
return
|
|
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
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (buffer[0] ===
|
|
702
|
-
return buffer.toString('
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
if (
|
|
812
|
-
|
|
813
|
-
}
|
|
814
|
-
if (
|
|
815
|
-
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
const
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
if (
|
|
1027
|
-
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
return
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
/**
|
|
1034
|
-
*
|
|
1035
|
-
*/
|
|
1036
|
-
function
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
const
|
|
1238
|
-
if (
|
|
1239
|
-
const
|
|
1240
|
-
const
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
const
|
|
1267
|
-
if (
|
|
1268
|
-
const
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
if (
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
if (
|
|
1416
|
-
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
stats.
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
stats.
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
const
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
if (
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
}
|
|
2013
|
-
console.log(
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
console.log(
|
|
2039
|
-
console.log(`${'─'.repeat(67)}`);
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
if (
|
|
2191
|
-
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
fs.writeFileSync(path.join(targetDir, '
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
const rawTemplate = await fetchRemoteLicense(cleanedInput);
|
|
2256
|
-
if (rawTemplate) {
|
|
2257
|
-
const parsedText = rawTemplate.replace(/\[year\]|<year>/gi, new Date().getFullYear().toString()).replace(/\[fullname\]|\[name of copyright owner\]|<copyright holders>|<name of author>/gi, gitInfo.name);
|
|
2258
|
-
fs.writeFileSync(licensePath, parsedText);
|
|
2259
|
-
chosenLicenseType = cleanedInput.toUpperCase();
|
|
2260
|
-
console.log(` ⚖️ Provisioned: LICENSE`);
|
|
2261
|
-
} else {
|
|
2262
|
-
chosenLicenseType = cleanedInput;
|
|
2263
|
-
}
|
|
2264
|
-
packageJson.license = chosenLicenseType;
|
|
2265
|
-
}
|
|
2266
|
-
} else {
|
|
2267
|
-
if (preExistingLicense) {
|
|
2268
|
-
chosenLicenseType = preExistingLicense;
|
|
2269
|
-
if (!fs.existsSync(licensePath) && ['mit', 'apache-2.0', 'gpl-3.0'].includes(preExistingLicense.toLowerCase())) {
|
|
2270
|
-
const rawTemplate = await fetchRemoteLicense(preExistingLicense);
|
|
2271
|
-
if (rawTemplate) {
|
|
2272
|
-
const parsedText = rawTemplate.replace(/\[year\]|<year>/gi, new Date().getFullYear().toString()).replace(/\[fullname\]|\[name of copyright owner\]|<copyright holders>|<name of author>/gi, gitInfo.name);
|
|
2273
|
-
fs.writeFileSync(licensePath, parsedText);
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2276
|
-
} else if (fs.existsSync(licensePath)) {
|
|
2277
|
-
try {
|
|
2278
|
-
const currentLicenseContent = fs.readFileSync(licensePath, 'utf8');
|
|
2279
|
-
if (currentLicenseContent.includes('MIT')) {
|
|
2280
|
-
chosenLicenseType = 'MIT';
|
|
2281
|
-
} else if (currentLicenseContent.includes('Apache')) {
|
|
2282
|
-
chosenLicenseType = 'Apache-2.0';
|
|
2283
|
-
} else {
|
|
2284
|
-
chosenLicenseType = 'Custom';
|
|
2285
|
-
}
|
|
2286
|
-
} catch (licenseFileReadSyncError) {}
|
|
2287
|
-
}
|
|
2288
|
-
packageJson.license = chosenLicenseType;
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
if (!stats.hasTests) {
|
|
2292
|
-
const bootstrapTest = await safeQuestion(`\n❓ No test files detected. Scaffold a zero-bloat testing harness via Node native test runner? (y/N): `);
|
|
2293
|
-
if (bootstrapTest.trim().toLowerCase() === 'y' || bootstrapTest.trim().toLowerCase() === 'yes') {
|
|
2294
|
-
const isEsm = packageJson.type === 'module';
|
|
2295
|
-
const testExt = isTypeScript ? '.test.ts' : '.test.js';
|
|
2296
|
-
const testFilePath = path.join(targetDir, `index${testExt}`);
|
|
2297
|
-
const testTemplate = isEsm
|
|
2298
|
-
? `import { test, describe } from 'node:test';\nimport assert from 'node:assert';\n\ndescribe('Core Architecture Testing Suite', () => {\n test('should verify systemic environmental execution health', () => {\n assert.strictEqual(1, 1);\n });\n});\n`
|
|
2299
|
-
: `const { test, describe } = require('node:test');\nconst assert = require('node:assert');\n\ndescribe('Core Architecture Testing Suite', () => {\n test('should verify systemic environmental execution health', () => {\n assert.strictEqual(1, 1);\n });\n});\n`;
|
|
2300
|
-
fs.writeFileSync(testFilePath, testTemplate);
|
|
2301
|
-
packageJson.scripts.test = 'node --test';
|
|
2302
|
-
stats.hasTests = true;
|
|
2303
|
-
console.log(` 🧪 Generated: index${testExt}`);
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
console.log(`\n⚙️ Writing ecosystem configuration artifacts...`);
|
|
2308
|
-
|
|
2309
|
-
if (stats.bootstrapEslintSuite) {
|
|
2310
|
-
packageJson.scripts.lint = 'eslint .';
|
|
2311
|
-
let eslintConfigContent = '';
|
|
2312
|
-
if (isTypeScript) {
|
|
2313
|
-
eslintConfigContent = `import eslint from '@eslint/js';\nimport tseslint from 'typescript-eslint';\n\nexport default tseslint.config(\n eslint.configs.recommended,\n ...tseslint.configs.recommended,\n);\n`;
|
|
2314
|
-
} else {
|
|
2315
|
-
if (packageJson.type === 'module') {
|
|
2316
|
-
eslintConfigContent = `import js from "@eslint/js";\n\nexport default [\n js.configs.recommended,\n {\n rules: {\n "no-unused-vars": "warn",\n "no-undef": "error"\n }\n }\n];\n`;
|
|
2317
|
-
} else {
|
|
2318
|
-
eslintConfigContent = `const js = require("@eslint/js");\n\nmodule.exports = [\n js.configs.recommended,\n {\n rules: {\n "no-unused-vars": "warn",\n "no-undef": "error"\n }\n }\n];\n`;
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
fs.writeFileSync(eslintConfigFile, eslintConfigContent);
|
|
2322
|
-
console.log(` 🎨 Provisioned: eslint.config.js`);
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
|
-
if (fs.existsSync(pkgPath)) {
|
|
2326
|
-
try {
|
|
2327
|
-
const currentPackageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
2328
|
-
currentPackageJson.dependencies = { ...packageJson.dependencies, ...currentPackageJson.dependencies };
|
|
2329
|
-
currentPackageJson.devDependencies = { ...packageJson.devDependencies, ...currentPackageJson.devDependencies };
|
|
2330
|
-
if (packageJson.scripts.lint && !currentPackageJson.scripts?.lint) {
|
|
2331
|
-
currentPackageJson.scripts = currentPackageJson.scripts || {};
|
|
2332
|
-
currentPackageJson.scripts.lint = packageJson.scripts.lint;
|
|
2333
|
-
}
|
|
2334
|
-
fs.writeFileSync(pkgPath, JSON.stringify(currentPackageJson, null, 2));
|
|
2335
|
-
console.log(` 🔄 Safely merged discovered dependencies into existing package.json`);
|
|
2336
|
-
} catch (mergePackageJsonError) {}
|
|
2337
|
-
} else {
|
|
2338
|
-
fs.writeFileSync(pkgPath, JSON.stringify(packageJson, null, 2));
|
|
2339
|
-
console.log(` 📝 Generated: package.json`);
|
|
2340
|
-
}
|
|
2341
|
-
|
|
2342
|
-
const prettierPath = path.join(targetDir, '.prettierrc');
|
|
2343
|
-
if (!fs.existsSync(prettierPath)) {
|
|
2344
|
-
const useTabs = stats.style.tabCount > (stats.style.space2Count + stats.style.space4Count);
|
|
2345
|
-
const useSemi = stats.style.semiCount >= stats.style.noSemiCount;
|
|
2346
|
-
const tabWidth = stats.style.space4Count > stats.style.space2Count ? 4 : 2;
|
|
2347
|
-
fs.writeFileSync(prettierPath, JSON.stringify({ semi: useSemi, useTabs, tabWidth, singleQuote: true, trailingComma: "es5" }, null, 2));
|
|
2348
|
-
console.log(` 🎨 Code formatting mirror locked: .prettierrc`);
|
|
2349
|
-
}
|
|
2350
|
-
|
|
2351
|
-
if (stats.envVars.size > 0) {
|
|
2352
|
-
const envExamplePath = path.join(targetDir, '.env.example');
|
|
2353
|
-
if (!fs.existsSync(envExamplePath)) {
|
|
2354
|
-
fs.writeFileSync(envExamplePath, Array.from(stats.envVars).map(v => { return `${v}=`; }).join('\n') + '\n');
|
|
2355
|
-
console.log(` 🔒 Extracted environmental configurations: .env.example`);
|
|
2356
|
-
}
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
2360
|
-
if (!fs.existsSync(gitignorePath)) {
|
|
2361
|
-
fs.writeFileSync(gitignorePath, `node_modules/\ndist/\nbuild/\n.env\n.env.local\n.DS_Store\n*.log\n`);
|
|
2362
|
-
console.log(` ⚙️ Generated: .gitignore`);
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
|
-
if (isTypeScript) {
|
|
2366
|
-
const tsconfigPath = path.join(targetDir, 'tsconfig.json');
|
|
2367
|
-
if (!fs.existsSync(tsconfigPath)) {
|
|
2368
|
-
fs.writeFileSync(tsconfigPath, JSON.stringify({
|
|
2369
|
-
compilerOptions: { target: "ES2022", module: "NodeNext", moduleResolution: "NodeNext", esModuleInterop: true, strict: true, skipLibCheck: true, outDir: "./dist" },
|
|
2370
|
-
include: ["src/**/*", "**/*.ts"]
|
|
2371
|
-
}, null, 2));
|
|
2372
|
-
console.log(` ⚙️ Generated: tsconfig.json`);
|
|
2373
|
-
}
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
|
-
const readmePath = path.join(targetDir, 'README.md');
|
|
2377
|
-
if (!fs.existsSync(readmePath)) {
|
|
2378
|
-
const pName = packageJson.name;
|
|
2379
|
-
const layoutTree = buildAsciiTree(targetDir).join('\n');
|
|
2380
|
-
const displayDeps = Object.keys(packageJson.dependencies).map(d => { return `* \`${d}\``; }).join('\n') || '* None extracted';
|
|
2381
|
-
const displayDevDeps = Object.keys(packageJson.devDependencies).map(d => { return `* \`${d}\``; }).join('\n') || '* None extracted';
|
|
2382
|
-
const licenseBadgeParam = encodeURIComponent(chosenLicenseType.replace(/-/g, '_'));
|
|
2383
|
-
|
|
2384
|
-
const documentationTemplate = `# ${pName}\n\n\n\n\n\n${packageJson.description}\n\n## Workspace Dependency Landscapes\n\n### Core Infrastructure Runtimes (\`dependencies\`)\n${displayDeps}\n\n### System Tooling Engines (\`devDependencies\`)\n${displayDevDeps}\n\n---\n\n## Project Architecture Layout\n\`\`\`text\n${layoutTree}\n\`\`\`\n\n## Installation\n\n\`\`\`bash\n${activePkgManager} install\n\`\`\`\n`;
|
|
2385
|
-
fs.writeFileSync(readmePath, documentationTemplate);
|
|
2386
|
-
console.log(` 📖 Generated: README.md`);
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
if (stats.phantomInjections.size > 0 || (stats.injectDotenvEngine && stats.filesWithEnvVars.size > 0)) {
|
|
2390
|
-
console.log(`\n💡 Source Code Modification Subsystem:`);
|
|
2391
|
-
const injectChoice = await safeQuestion(`❓ Found phantom modules or unmanaged env components. Mutate file headers cleanly now? (y/N): `);
|
|
2392
|
-
|
|
2393
|
-
if (injectChoice.trim().toLowerCase() === 'y' || injectChoice.trim().toLowerCase() === 'yes') {
|
|
2394
|
-
const allTargets = new Set([...stats.phantomInjections.keys(), ...stats.filesWithEnvVars]);
|
|
2395
|
-
for (const filePath of allTargets) {
|
|
2396
|
-
const originalCode = readFileSyncNormalized(filePath);
|
|
2397
|
-
let declarationBlock = '';
|
|
2398
|
-
|
|
2399
|
-
const missingModules = stats.phantomInjections.get(filePath);
|
|
2400
|
-
if (missingModules) {
|
|
2401
|
-
for (const mod of missingModules) {
|
|
2402
|
-
if (packageJson.type === 'module') {
|
|
2403
|
-
declarationBlock += `import ${mod} from '${mod}';\n`;
|
|
2404
|
-
} else {
|
|
2405
|
-
declarationBlock += `const ${mod} = require('${mod}');\n`;
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
if (stats.injectDotenvEngine && stats.filesWithEnvVars.has(filePath) && !originalCode.includes('dotenv')) {
|
|
2410
|
-
if (packageJson.type === 'module') {
|
|
2411
|
-
declarationBlock += `import 'dotenv/config';\n`;
|
|
2412
|
-
} else {
|
|
2413
|
-
declarationBlock += `require('dotenv').config();\n`;
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
|
-
if (declarationBlock !== '') {
|
|
2417
|
-
fs.writeFileSync(filePath, smartPrepend(originalCode, declarationBlock));
|
|
2418
|
-
console.log(` ⚡ Injected headers: ${path.relative(targetDir, filePath)}`);
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
}
|
|
2423
|
-
|
|
2424
|
-
console.log(`\n🛑 INITIALIZING LIVE ECOSYSTEM DEPRECATION SECURITY SCAN...`);
|
|
2425
|
-
console.log(` Running integrated npm-deprecated-check validation:\n`);
|
|
2426
|
-
try {
|
|
2427
|
-
const localRequire = createRequire(import.meta.url);
|
|
2428
|
-
const dependencyPkgJsonPath = localRequire.resolve('npm-deprecated-check/package.json');
|
|
2429
|
-
const dependencyPkgJson = JSON.parse(fs.readFileSync(dependencyPkgJsonPath, 'utf8'));
|
|
2430
|
-
const binRelativeMapping = typeof dependencyPkgJson.bin === 'string' ? dependencyPkgJson.bin : (dependencyPkgJson.bin['npm-deprecated-check'] || dependencyPkgJson.bin['ndc']);
|
|
2431
|
-
const absoluteExecutablePath = path.join(path.dirname(dependencyPkgJsonPath), binRelativeMapping);
|
|
2432
|
-
execSync(`node "${absoluteExecutablePath}" current`, { stdio: 'inherit', cwd: targetDir });
|
|
2433
|
-
} catch (deprecationBinaryRunError) {}
|
|
2434
|
-
|
|
2435
|
-
if (stats.conflictingLockfiles.length > 1) {
|
|
2436
|
-
console.log(`\n⚠️ CONFLICTING LOCKFILES DETECTED: [${stats.conflictingLockfiles.join(', ')}]`);
|
|
2437
|
-
const cleanLocks = await safeQuestion(`❓ Purge legacy/mismatched lockfiles to protect package integrity? (y/N): `);
|
|
2438
|
-
if (cleanLocks.trim().toLowerCase() === 'y' || cleanLocks.trim().toLowerCase() === 'yes') {
|
|
2439
|
-
const packageEngineLockmap = { npm: 'package-lock.json', pnpm: 'pnpm-lock.yaml', yarn: 'yarn.lock', bun: 'bun.lockb' };
|
|
2440
|
-
const operationalLockfile = packageEngineLockmap[activePkgManager];
|
|
2441
|
-
for (const lockfile of stats.conflictingLockfiles) {
|
|
2442
|
-
if (lockfile !== operationalLockfile) {
|
|
2443
|
-
try {
|
|
2444
|
-
fs.unlinkSync(path.join(targetDir, lockfile));
|
|
2445
|
-
console.log(` 🗑 Cleaned: ${lockfile}`);
|
|
2446
|
-
} catch (lockFileUnlinkError) {}
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
console.log(`\n📦 Auto-scaffolding pipeline complete!`);
|
|
2453
|
-
|
|
2454
|
-
if (stats.frameworkOptimizations.length > 0) {
|
|
2455
|
-
console.log(`\n🧩 FRAMEWORK ARCHITECTURE OPTIMIZATIONS:`);
|
|
2456
|
-
console.log('─'.repeat(67));
|
|
2457
|
-
for (const optimization of stats.frameworkOptimizations) {
|
|
2458
|
-
console.log(` 💡 ${optimization}`);
|
|
2459
|
-
}
|
|
2460
|
-
console.log('─'.repeat(67));
|
|
2461
|
-
}
|
|
2462
|
-
|
|
2463
|
-
console.log(`\n${'═'.repeat(67)}`);
|
|
2464
|
-
console.log(`📊 DEPENDENCY INTELLIGENCE SUMMARY`);
|
|
2465
|
-
console.log(`${'═'.repeat(67)}`);
|
|
2466
|
-
console.log(` 📁 Files scanned: ${stats.scannedFiles}`);
|
|
2467
|
-
console.log(` 📦 Packages imported: ${stats.allImportedPackages.size}`);
|
|
2468
|
-
if (stats.ghostDependencies.size > 0) {
|
|
2469
|
-
console.log(` 🚨 Ghost deps (missing): ${stats.ghostDependencies.size} — \x1b[31mCRITICAL\x1b[0m`);
|
|
2470
|
-
}
|
|
2471
|
-
if (stats.orphanedDependencies.size > 0) {
|
|
2472
|
-
console.log(` 🗑️ Orphaned deps (unused): ${stats.orphanedDependencies.size}`);
|
|
2473
|
-
}
|
|
2474
|
-
if (stats.unusedExportsPerFile.size > 0) {
|
|
2475
|
-
console.log(` 📤 Unused public symbols: ${Array.from(stats.unusedExportsPerFile.values()).reduce((acc, val) => { return acc + val.size; }, 0)} tokens`);
|
|
2476
|
-
}
|
|
2477
|
-
if (stats.unusedFiles.size > 0) {
|
|
2478
|
-
console.log(` 🗑️ Unused dead files: ${stats.unusedFiles.size} files`);
|
|
2479
|
-
}
|
|
2480
|
-
if (stats.deprecatedPackages.size > 0) {
|
|
2481
|
-
console.log(` ` + `📛 Deprecated packages: ${stats.deprecatedPackages.size}`);
|
|
2482
|
-
}
|
|
2483
|
-
if (stats.phantomInjections.size > 0) {
|
|
2484
|
-
console.log(` 👻 Phantom injections: ${stats.phantomInjections.size} file(s)`);
|
|
2485
|
-
}
|
|
2486
|
-
if (stats.discoveredSecrets.length > 0) {
|
|
2487
|
-
console.log(` 🔐 Hardcoded secrets: ${stats.discoveredSecrets.length} — \x1b[31mSECURITY RISK\x1b[0m`);
|
|
2488
|
-
}
|
|
2489
|
-
if (stats.quality.insecureCryptoUsage.length > 0) {
|
|
2490
|
-
console.log(` 🚫 Insecure Crypto: ${stats.quality.insecureCryptoUsage.length} — \x1b[31mSECURITY RISK\x1b[0m`);
|
|
2491
|
-
}
|
|
2492
|
-
if (stats.quality.sqlInjectionVulnerabilities.length > 0) {
|
|
2493
|
-
console.log(` 💉 SQL Injection: ${stats.quality.sqlInjectionVulnerabilities.length} — \x1b[31mSECURITY RISK\x1b[0m`);
|
|
2494
|
-
}
|
|
2495
|
-
if (stats.quality.xssVulnerabilities.length > 0) {
|
|
2496
|
-
console.log(` 🌐 XSS Vulnerabilities: ${stats.quality.xssVulnerabilities.length} — \x1b[31mSECURITY RISK\x1b[0m`);
|
|
2497
|
-
}
|
|
2498
|
-
if (stats.quality.insecurePatterns.length > 0) {
|
|
2499
|
-
console.log(` 🌐 Insecure DOM Patterns: ${stats.quality.insecurePatterns.length} — \x1b[31mSECURITY RISK\x1b[0m`);
|
|
2500
|
-
}
|
|
2501
|
-
if (stats.quality.largeImageImports.length > 0) {
|
|
2502
|
-
console.log(` 🖼️ Large Image Imports: ${stats.quality.largeImageImports.length} — \x1b[33mPERFORMANCE WARNING\x1b[0m`);
|
|
2503
|
-
}
|
|
2504
|
-
if (stats.quality.unoptimizedLoops.length > 0) {
|
|
2505
|
-
console.log(` 🐌 Unoptimized Loops: ${stats.quality.unoptimizedLoops.length} — \x1b[33mPERFORMANCE WARNING\x1b[0m`);
|
|
2506
|
-
}
|
|
2507
|
-
console.log(`${'═'.repeat(67)}`);
|
|
2508
|
-
|
|
2509
|
-
const templateManager = new TemplateManager(targetDir, safeQuestion);
|
|
2510
|
-
const availableTemplates = await templateManager.listAvailableTemplates();
|
|
2511
|
-
|
|
2512
|
-
if (availableTemplates.length > 0) {
|
|
2513
|
-
console.log(`\n🧩 \x1b[1mCustom Templating Engine Detected:\x1b[0m`);
|
|
2514
|
-
console.log(` Available templates: ${availableTemplates.join(", ")}`);
|
|
2515
|
-
const useTemplate = await safeQuestion(`❓ Do you want to generate code from a template? (y/N): `);
|
|
2516
|
-
if (useTemplate.toLowerCase() === 'y') {
|
|
2517
|
-
const chosenTemplate = await safeQuestion(`❓ Enter template name: `);
|
|
2518
|
-
if (availableTemplates.includes(chosenTemplate)) {
|
|
2519
|
-
const templateVars = await templateManager.promptForVariables(chosenTemplate);
|
|
2520
|
-
await templateManager.generate(chosenTemplate, templateVars);
|
|
2521
|
-
} else {
|
|
2522
|
-
console.log(` ⚠️ Template '${chosenTemplate}' not found.`);
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
const userPromptChoice = await safeQuestion(`❓ Detected package manager: "${activePkgManager}". Run "${activePkgManager} install" now? (y/N): `);
|
|
2528
|
-
rl.close();
|
|
2529
|
-
|
|
2530
|
-
const normalizedAnswer = userPromptChoice.trim().toLowerCase();
|
|
2531
|
-
if (normalizedAnswer === 'y' || normalizedAnswer === 'yes') {
|
|
2532
|
-
console.log(`\n⏳ Executing automated asset installations...`);
|
|
2533
|
-
try {
|
|
2534
|
-
execSync(`${activePkgManager} install`, { stdio: 'inherit', cwd: targetDir });
|
|
2535
|
-
console.log(`\n🎉 Project fully mapped, configured, and installed successfully!`);
|
|
2536
|
-
} catch (installProcessRuntimeError) {
|
|
2537
|
-
console.error(`\n❌ Installation returned an issue. Please run "${activePkgManager} install" manually.`);
|
|
2538
|
-
}
|
|
2539
|
-
} else {
|
|
2540
|
-
console.log(`\n▶️ Skipping install. Run "${activePkgManager} install" manually when ready.`);
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
|
|
2544
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* 📦 pkg-scaffold v2.3.0: Enterprise Dependency Intelligence & Scaffolding Engine
|
|
6
|
+
* ============================================================================
|
|
7
|
+
* * Eine hochgradig integrierte Code-Analyse- und Projektbootstrapping-Engine.
|
|
8
|
+
* Kombiniert rekursive Erreichbarkeitsanalysen (Reachability Graphs) auf
|
|
9
|
+
* Knip-Niveau mit proaktiven Code-Qualitäts-Audits, Sicherheitsprüfungen,
|
|
10
|
+
* statischem Schwachstellen-Scanning und interaktivem Scaffolding.
|
|
11
|
+
*
|
|
12
|
+
* Diese Datei ist als vollständig entfalteter, langformbasierter Monolith
|
|
13
|
+
* strukturiert, um maximale Wartbarkeit, lückenlose Fehlerabdeckung und
|
|
14
|
+
* absolute Ausführungssicherheit im Produktivbetrieb zu garantieren.
|
|
15
|
+
* * Eigenschaften:
|
|
16
|
+
* - Semantische Analyse via TypeScript Compiler API (Type-Aware Parsing)
|
|
17
|
+
* - Präzise Modulauflösung via enhanced-resolve (Webpack-Level)
|
|
18
|
+
* - Umfassendes Symbol-Tracking & Erreichbarkeitsmatrix (Knip-Niveau)
|
|
19
|
+
* - Entrypoint-basiertes Scanning zur Erkennung verwaister Dateien
|
|
20
|
+
* - Hardcoded Credentials Extraction & .env-Isolation
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from 'fs';
|
|
24
|
+
import path from 'path';
|
|
25
|
+
import { builtinModules, createRequire } from 'module';
|
|
26
|
+
import { execSync } from 'child_process';
|
|
27
|
+
import readline from 'readline/promises';
|
|
28
|
+
|
|
29
|
+
// --- Bulletproof AST & Resolution Infrastructure Engines ---
|
|
30
|
+
import ts from 'typescript';
|
|
31
|
+
import resolve from 'enhanced-resolve';
|
|
32
|
+
|
|
33
|
+
const localRequire = createRequire(import.meta.url);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Globale Konfigurations-Sets für Verzeichnis- und Dateifilterung.
|
|
37
|
+
* Verhindert das Eindringen von System-, Cache- und Build-Dateien in die Analyse.
|
|
38
|
+
* @type {Set<string>}
|
|
39
|
+
*/
|
|
40
|
+
const IGNORED_DIRS = new Set([
|
|
41
|
+
'node_modules',
|
|
42
|
+
'.git',
|
|
43
|
+
'dist',
|
|
44
|
+
'build',
|
|
45
|
+
'.turbo',
|
|
46
|
+
'coverage',
|
|
47
|
+
'out',
|
|
48
|
+
'.next',
|
|
49
|
+
'.nuxt',
|
|
50
|
+
'.svelte-kit',
|
|
51
|
+
'storybook-static',
|
|
52
|
+
'.cache'
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Unterstützte Dateiendungen für die statische Codeanalyse und das Parsing.
|
|
57
|
+
* @type {Set<string>}
|
|
58
|
+
*/
|
|
59
|
+
const VALID_EXTENSIONS = new Set([
|
|
60
|
+
'.js',
|
|
61
|
+
'.jsx',
|
|
62
|
+
'.ts',
|
|
63
|
+
'.tsx',
|
|
64
|
+
'.mjs',
|
|
65
|
+
'.cjs',
|
|
66
|
+
'.vue',
|
|
67
|
+
'.svelte'
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* --- Refined Target Signature Dictionaries ---
|
|
72
|
+
* Umfassendes Verzeichnis regulärer Ausdrücke für Code-Smells, kryptografische Risiken,
|
|
73
|
+
* Framework-Routing und Umgebungsvariablen.
|
|
74
|
+
* @type {Object}
|
|
75
|
+
*/
|
|
76
|
+
const REGEX_PATTERNS = {
|
|
77
|
+
/**
|
|
78
|
+
* Zugriff auf Umgebungsvariablen im Node- oder Vite-Kontext.
|
|
79
|
+
*/
|
|
80
|
+
env: /(?:process\.env|import\.meta\.env)\.([A-Z_][A-Z0-9_]*)/g,
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Erkennung von Testdateien für diverse Testrunner.
|
|
84
|
+
*/
|
|
85
|
+
testFile: /\.(test|spec)\.(js|ts|jsx|tsx|mjs|cjs)$/i,
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Erkennung gängiger Konfigurationsdateien im JavaScript-Ökosystem.
|
|
89
|
+
*/
|
|
90
|
+
configFile: /^(vite|webpack|rollup|babel|jest|vitest|tailwind|postcss|next|nuxt|svelte|astro)\.config\./i,
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Veraltete Variablendeklarationen (Code-Smell).
|
|
94
|
+
*/
|
|
95
|
+
legacyVar: /\bvar\s+[a-zA-Z_]/g,
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Gefährliche dynamische Code-Ausführung.
|
|
99
|
+
*/
|
|
100
|
+
dangerousEval: /\beval\s*\(/g,
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Blockierende synchrone Dateisystemaufrufe.
|
|
104
|
+
*/
|
|
105
|
+
syncFsCalls: /\.readFileSync|\.writeFileSync|\.mkdirSync|\.existsSync/g,
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Allgemeine Zuweisung harter Anmeldedaten.
|
|
109
|
+
*/
|
|
110
|
+
secretKeys: /\b(secret|passwd|password|token|api_?key|private_?key)\s*=\s*['"`]([a-zA-Z0-9_\-\.]{8,})['"`]/gi,
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Spezifische AWS Zugriffsschlüssel.
|
|
114
|
+
*/
|
|
115
|
+
awsKeys: /AKIA[0-9A-Z]{16}/g,
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Spezifische Google Cloud API Schlüssel.
|
|
119
|
+
*/
|
|
120
|
+
googleCloudKeys: /AIza[0-9A-Za-z\-_]{35}/g,
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Live-Schlüssel für den Stripe-Zahlungsdienstleister.
|
|
124
|
+
*/
|
|
125
|
+
stripeKeys: /sk_live_[0-9a-zA-Z]{24}/g,
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Slack Bot- und Benutzer-Tokens.
|
|
129
|
+
*/
|
|
130
|
+
slackKeys: /xox[baprs]-[0-9a-zA-Z]{10,48}/g,
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* GitHub Personal Access Tokens.
|
|
134
|
+
*/
|
|
135
|
+
githubTokens: /gh[pousr]_[a-zA-Z0-9]{36}/g,
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Private RSA-Schlüsselblöcke.
|
|
139
|
+
*/
|
|
140
|
+
rsaPrivateKeys: /-----BEGIN RSA PRIVATE KEY-----/g,
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Private OpenSSH-Schlüsselblöcke.
|
|
144
|
+
*/
|
|
145
|
+
sshPrivateKeys: /-----BEGIN OPENSSH PRIVATE KEY-----/g,
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Private PGP-Schlüsselblöcke.
|
|
149
|
+
*/
|
|
150
|
+
pgpPrivateKeys: /-----BEGIN PGP PRIVATE KEY BLOCK-----/g,
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Unsicheres Einfügen von unbereinigtem HTML (XSS-Risiko).
|
|
154
|
+
*/
|
|
155
|
+
insecureInnerHTML: /\.innerHTML\s*=/g,
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Direktes Schreiben in das Dokumentenobjekt im Browser.
|
|
159
|
+
*/
|
|
160
|
+
insecureDocumentWrite: /document\.write\s*\(/g,
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* React-spezifisches Äquivalent zu innerHTML.
|
|
164
|
+
*/
|
|
165
|
+
insecureDangerouslySet: /dangerouslySetInnerHTML/g,
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Riskante reguläre Ausdrücke mit Potenzial für Catastrophic Backtracking.
|
|
169
|
+
*/
|
|
170
|
+
insecureRegex: /\/\.\*\//g,
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Veraltete oder unsichere Krypto-Verfahren in Node.js.
|
|
174
|
+
*/
|
|
175
|
+
insecureCrypto: /crypto\.(?:createCipher|createDecipher|pbkdf2Sync)/g,
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Basis-Muster zur Erkennung potenzieller SQL-Injections in String-Literalen.
|
|
179
|
+
*/
|
|
180
|
+
sqlInjection: /(?:SELECT|INSERT|UPDATE|DELETE)\s+.*\s+FROM\s+.*\s+WHERE\s+.*\s*=\s*[""]?.*[""]?/i,
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Erkennung von harten Script-Tags im Quellcode (XSS-Indikator).
|
|
184
|
+
*/
|
|
185
|
+
xssVulnerability: /<script\b[^>]*>[\s\S]*?<\/script>/i,
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Performance-Smell: Direkter Import von großen Bilddateien im Quellcode.
|
|
189
|
+
*/
|
|
190
|
+
largeImageImport: /import\s+.*\s+from\s+[""](?:.*\.(?:png|jpg|jpeg|gif|svg))[""]/g,
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Performance-Smell: Unoptimierte for-Schleife über Array-Längen.
|
|
194
|
+
*/
|
|
195
|
+
unoptimizedLoop: /for\s*\(let\s+i\s*=\s*0;\s*i\s*<\s*\w+\.length;\s*i\s*\+\+\)/g,
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Framework-Muster: Next.js Bildkomponente.
|
|
199
|
+
*/
|
|
200
|
+
nextjsImageComponent: /<Image\s+[^>]*>/g,
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Framework-Muster: Next.js Schriftoptimierung.
|
|
204
|
+
*/
|
|
205
|
+
nextjsFontOptimization: /next\/font/g,
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Framework-Muster: Nuxt 3 Auto-Imports für Datenabrufe.
|
|
209
|
+
*/
|
|
210
|
+
nuxtAutoImport: /use(?:State|Fetch|AsyncData)/g,
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Framework-Muster: SvelteKit Datenladefunktion.
|
|
214
|
+
*/
|
|
215
|
+
sveltekitLoadFunction: /export\s+const\s+load\s*=/g,
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Erkennt unvollständige React useEffect Hooks ohne jegliches Abhängigkeitsarray.
|
|
219
|
+
*/
|
|
220
|
+
reactUseEffectNoDeps: /useEffect\s*\(\s*(?:\([^)]*\)|[^=]+)\s*=>\s*\{[\s\S]*?\}\s*\)/g,
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Framework-Dateipfade für tiefe Routing-Analysen.
|
|
224
|
+
*/
|
|
225
|
+
nextjsPage: /pages\/[^\/]+\.(js|jsx|ts|tsx)$/i,
|
|
226
|
+
nextjsApi: /pages\/api\/[^\/]+\.(js|jsx|ts|tsx)$/i,
|
|
227
|
+
nextjsComponent: /components\/[^\/]+\.(js|jsx|ts|tsx)$/i,
|
|
228
|
+
nuxtPage: /pages\/[^\/]+\.(vue|js|ts)$/i,
|
|
229
|
+
nuxtComponent: /components\/[^\/]+\.(vue|js|ts)$/i,
|
|
230
|
+
sveltekitPage: /src\/routes\/[^\/]+\/\+page\.(svelte|js|ts)$/i,
|
|
231
|
+
sveltekitComponent: /src\/lib\/[^\/]+\.(svelte|js|ts)$/i,
|
|
232
|
+
reactHook: /hooks\/[^\/]+\.(js|jsx|ts|tsx)$/i,
|
|
233
|
+
vueComposable: /composables\/[^\/]+\.(js|ts)$/i
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Maps CLI-Binärnamen auf tatsächliche npm-Paketnamen (Knip-Stil).
|
|
238
|
+
* @type {Object}
|
|
239
|
+
*/
|
|
240
|
+
const BINARY_TO_PACKAGE_MAP = {
|
|
241
|
+
'tsc': 'typescript',
|
|
242
|
+
'ts-node': 'ts-node',
|
|
243
|
+
'tsx': 'tsx',
|
|
244
|
+
'tsup': 'tsup',
|
|
245
|
+
'esbuild': 'esbuild',
|
|
246
|
+
'swc': '@swc/cli',
|
|
247
|
+
'jest': 'jest',
|
|
248
|
+
'vitest': 'vitest',
|
|
249
|
+
'mocha': 'mocha',
|
|
250
|
+
'jasmine': 'jasmine',
|
|
251
|
+
'ava': 'ava',
|
|
252
|
+
'tap': 'tap',
|
|
253
|
+
'c8': 'c8',
|
|
254
|
+
'nyc': 'nyc',
|
|
255
|
+
'eslint': 'eslint',
|
|
256
|
+
'prettier': 'prettier',
|
|
257
|
+
'biome': '@biomejs/biome',
|
|
258
|
+
'oxlint': 'oxlint',
|
|
259
|
+
'tslint': 'tslint',
|
|
260
|
+
'xo': 'xo',
|
|
261
|
+
'standard': 'standard',
|
|
262
|
+
'vite': 'vite',
|
|
263
|
+
'webpack': 'webpack',
|
|
264
|
+
'rollup': 'rollup',
|
|
265
|
+
'parcel': 'parcel',
|
|
266
|
+
'turbo': 'turbo',
|
|
267
|
+
'nx': 'nx',
|
|
268
|
+
'nodemon': 'nodemon',
|
|
269
|
+
'pm2': 'pm2',
|
|
270
|
+
'concurrently': 'concurrently',
|
|
271
|
+
'cross-env': 'cross-env',
|
|
272
|
+
'dotenv-cli': 'dotenv-cli',
|
|
273
|
+
'env-cmd': 'env-cmd',
|
|
274
|
+
'hygen': 'hygen',
|
|
275
|
+
'plop': 'plop',
|
|
276
|
+
'prisma': 'prisma',
|
|
277
|
+
'drizzle-kit': 'drizzle-kit',
|
|
278
|
+
'typeorm': 'typeorm',
|
|
279
|
+
'sequelize': 'sequelize-cli',
|
|
280
|
+
'knex': 'knex',
|
|
281
|
+
'mikro-orm': '@mikro-orm/cli',
|
|
282
|
+
'rimraf': 'rimraf',
|
|
283
|
+
'copyfiles': 'copyfiles',
|
|
284
|
+
'mkdirp': 'mkdirp',
|
|
285
|
+
'shx': 'shx',
|
|
286
|
+
'ncp': 'ncp',
|
|
287
|
+
'cpx': 'cpx',
|
|
288
|
+
'npm-run-all': 'npm-run-all',
|
|
289
|
+
'run-s': 'npm-run-all',
|
|
290
|
+
'run-p': 'npm-run-all',
|
|
291
|
+
'typedoc': 'typedoc',
|
|
292
|
+
'jsdoc': 'jsdoc',
|
|
293
|
+
'storybook': 'storybook',
|
|
294
|
+
'sb': 'storybook',
|
|
295
|
+
'husky': 'husky',
|
|
296
|
+
'lint-staged': 'lint-staged',
|
|
297
|
+
'commitlint': '@commitlint/cli',
|
|
298
|
+
'release-it': 'release-it',
|
|
299
|
+
'semantic-release': 'semantic-release',
|
|
300
|
+
'changeset': '@changesets/cli',
|
|
301
|
+
'changesets': '@changesets/cli',
|
|
302
|
+
'np': 'np',
|
|
303
|
+
'bumpp': 'bumpp'
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Menge von Entwicklungswerkzeugen, die nicht als verwaist eingestuft werden sollen.
|
|
308
|
+
* @type {Set<string>}
|
|
309
|
+
*/
|
|
310
|
+
const DEV_TOOLING_ECOSYSTEM = new Set([
|
|
311
|
+
'eslint',
|
|
312
|
+
'prettier',
|
|
313
|
+
'biome',
|
|
314
|
+
'@biomejs/biome',
|
|
315
|
+
'oxlint',
|
|
316
|
+
'tslint',
|
|
317
|
+
'xo',
|
|
318
|
+
'standard',
|
|
319
|
+
'typescript',
|
|
320
|
+
'typescript-eslint',
|
|
321
|
+
'@eslint/js',
|
|
322
|
+
'ts-node',
|
|
323
|
+
'tsx',
|
|
324
|
+
'tsup',
|
|
325
|
+
'esbuild',
|
|
326
|
+
'@swc/cli',
|
|
327
|
+
'jest',
|
|
328
|
+
'vitest',
|
|
329
|
+
'mocha',
|
|
330
|
+
'jasmine',
|
|
331
|
+
'ava',
|
|
332
|
+
'tap',
|
|
333
|
+
'c8',
|
|
334
|
+
'nyc',
|
|
335
|
+
'vite',
|
|
336
|
+
'webpack',
|
|
337
|
+
'rollup',
|
|
338
|
+
'parcel',
|
|
339
|
+
'turbo',
|
|
340
|
+
'nx',
|
|
341
|
+
'nodemon',
|
|
342
|
+
'pm2',
|
|
343
|
+
'concurrently',
|
|
344
|
+
'cross-env',
|
|
345
|
+
'dotenv-cli',
|
|
346
|
+
'env-cmd',
|
|
347
|
+
'rimraf',
|
|
348
|
+
'copyfiles',
|
|
349
|
+
'mkdirp',
|
|
350
|
+
'shx',
|
|
351
|
+
'ncp',
|
|
352
|
+
'cpx',
|
|
353
|
+
'npm-run-all',
|
|
354
|
+
'typedoc',
|
|
355
|
+
'jsdoc',
|
|
356
|
+
'storybook',
|
|
357
|
+
'husky',
|
|
358
|
+
'lint-staged',
|
|
359
|
+
'@commitlint/cli',
|
|
360
|
+
'release-it',
|
|
361
|
+
'semantic-release',
|
|
362
|
+
'@changesets/cli',
|
|
363
|
+
'np',
|
|
364
|
+
'bumpp',
|
|
365
|
+
'prisma',
|
|
366
|
+
'drizzle-kit',
|
|
367
|
+
'typeorm',
|
|
368
|
+
'sequelize-cli',
|
|
369
|
+
'knex',
|
|
370
|
+
'@mikro-orm/cli',
|
|
371
|
+
'hygen',
|
|
372
|
+
'plop'
|
|
373
|
+
]);
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Bekannte Namensraum-Importbezeichner gängiger Bibliotheken.
|
|
377
|
+
* @type {Object}
|
|
378
|
+
*/
|
|
379
|
+
const PACKAGE_IMPORT_ALIASES = {
|
|
380
|
+
'lodash': ['_', 'lodash'],
|
|
381
|
+
'lodash-es': ['_', 'lodash'],
|
|
382
|
+
'underscore': ['_'],
|
|
383
|
+
'jquery': ['$', 'jQuery'],
|
|
384
|
+
'moment': ['moment'],
|
|
385
|
+
'dayjs': ['dayjs'],
|
|
386
|
+
'date-fns': ['dateFns'],
|
|
387
|
+
'ramda': ['R'],
|
|
388
|
+
'rxjs': ['Rx'],
|
|
389
|
+
'three': ['THREE'],
|
|
390
|
+
'chart.js': ['Chart'],
|
|
391
|
+
'socket.io': ['io', 'Server'],
|
|
392
|
+
'socket.io-client': ['io'],
|
|
393
|
+
'mongoose': ['mongoose'],
|
|
394
|
+
'sequelize': ['Sequelize'],
|
|
395
|
+
'typeorm': ['typeorm'],
|
|
396
|
+
'prisma': ['prisma', 'PrismaClient'],
|
|
397
|
+
'@prisma/client': ['prisma', 'PrismaClient'],
|
|
398
|
+
'knex': ['knex'],
|
|
399
|
+
'redis': ['redis', 'createClient'],
|
|
400
|
+
'ioredis': ['Redis'],
|
|
401
|
+
'pg': ['Pool', 'Client', 'pg'],
|
|
402
|
+
'mysql2': ['mysql', 'createConnection', 'createPool'],
|
|
403
|
+
'sqlite3': ['sqlite3'],
|
|
404
|
+
'express': ['app', 'express', 'router'],
|
|
405
|
+
'fastify': ['fastify'],
|
|
406
|
+
'koa': ['Koa', 'koa'],
|
|
407
|
+
'hapi': ['Hapi'],
|
|
408
|
+
'axios': ['axios'],
|
|
409
|
+
'node-fetch': ['fetch'],
|
|
410
|
+
'got': ['got'],
|
|
411
|
+
'superagent': ['request'],
|
|
412
|
+
'chalk': ['chalk'],
|
|
413
|
+
'ora': ['ora'],
|
|
414
|
+
'inquirer': ['inquirer'],
|
|
415
|
+
'commander': ['program', 'Command'],
|
|
416
|
+
'yargs': ['yargs'],
|
|
417
|
+
'minimist': ['argv'],
|
|
418
|
+
'dotenv': ['dotenv'],
|
|
419
|
+
'winston': ['winston', 'logger'],
|
|
420
|
+
'pino': ['pino', 'logger'],
|
|
421
|
+
'morgan': ['morgan'],
|
|
422
|
+
'helmet': ['helmet'],
|
|
423
|
+
'cors': ['cors'],
|
|
424
|
+
'compression': ['compression'],
|
|
425
|
+
'body-parser': ['bodyParser'],
|
|
426
|
+
'multer': ['multer', 'upload'],
|
|
427
|
+
'passport': ['passport'],
|
|
428
|
+
'jsonwebtoken': ['jwt'],
|
|
429
|
+
'bcrypt': ['bcrypt'],
|
|
430
|
+
'bcryptjs': ['bcrypt'],
|
|
431
|
+
'crypto-js': ['CryptoJS'],
|
|
432
|
+
'uuid': ['uuid', 'v4', 'uuidv4'],
|
|
433
|
+
'nanoid': ['nanoid'],
|
|
434
|
+
'zod': ['z', 'zod'],
|
|
435
|
+
'joi': ['Joi'],
|
|
436
|
+
'yup': ['yup'],
|
|
437
|
+
'valibot': ['v'],
|
|
438
|
+
'class-validator': ['IsEmail', 'IsString', 'IsNumber'],
|
|
439
|
+
'react': ['React'],
|
|
440
|
+
'react-dom': ['ReactDOM'],
|
|
441
|
+
'vue': ['Vue', 'createApp'],
|
|
442
|
+
'svelte': ['svelte'],
|
|
443
|
+
'@angular/core': ['Component', 'NgModule'],
|
|
444
|
+
'next': ['next'],
|
|
445
|
+
'nuxt': ['nuxt']
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// ============================================================
|
|
449
|
+
// BASE HOISTED HELPER INFRASTRUCTURE
|
|
450
|
+
// ============================================================
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Extrahiert die Git-Konfigurationsparameter des lokalen Benutzers.
|
|
454
|
+
* @returns {Object} Git-Identity Datenstruktur.
|
|
455
|
+
*/
|
|
456
|
+
function getGitIdentity() {
|
|
457
|
+
const identity = {
|
|
458
|
+
name: "Developer",
|
|
459
|
+
author: "Developer",
|
|
460
|
+
repository: ""
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const name = execSync('git config user.name', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
465
|
+
const email = execSync('git config user.email', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
466
|
+
|
|
467
|
+
if (name) {
|
|
468
|
+
identity.name = name;
|
|
469
|
+
if (email) {
|
|
470
|
+
identity.author = `${name} <${email}>`;
|
|
471
|
+
} else {
|
|
472
|
+
identity.author = name;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
try {
|
|
477
|
+
const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
478
|
+
identity.repository = remoteUrl.replace(/\.git$/, '');
|
|
479
|
+
} catch (gitRemoteError) {
|
|
480
|
+
// Fehlendes Remote wird abgefangen
|
|
481
|
+
}
|
|
482
|
+
} catch (gitConfigError) {
|
|
483
|
+
// Fehlende Git-Umgebung wird abgefangen
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return identity;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Prüft das Vorhandensein von Lockfiles im Zielverzeichnis zur Bestimmung des Paketmanagers.
|
|
491
|
+
* @param {string} targetDir Das zu scannende Projektverzeichnis.
|
|
492
|
+
* @param {Object} stats Das globale Analyse-Objekt.
|
|
493
|
+
* @returns {string} Der ermittelte Paketmanager.
|
|
494
|
+
*/
|
|
495
|
+
function detectPackageManager(targetDir, stats = null) {
|
|
496
|
+
const detectedLockfiles = [];
|
|
497
|
+
|
|
498
|
+
if (fs.existsSync(path.join(targetDir, 'pnpm-lock.yaml'))) {
|
|
499
|
+
detectedLockfiles.push('pnpm-lock.yaml');
|
|
500
|
+
}
|
|
501
|
+
if (fs.existsSync(path.join(targetDir, 'yarn.lock'))) {
|
|
502
|
+
detectedLockfiles.push('yarn.lock');
|
|
503
|
+
}
|
|
504
|
+
if (fs.existsSync(path.join(targetDir, 'package-lock.json'))) {
|
|
505
|
+
detectedLockfiles.push('package-lock.json');
|
|
506
|
+
}
|
|
507
|
+
if (fs.existsSync(path.join(targetDir, 'bun.lockb')) || fs.existsSync(path.join(targetDir, 'bun.lock'))) {
|
|
508
|
+
detectedLockfiles.push('bun.lock');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (detectedLockfiles.length > 1 && stats !== null) {
|
|
512
|
+
stats.conflictingLockfiles = detectedLockfiles;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (detectedLockfiles.some(l => { return l.startsWith('bun'); })) {
|
|
516
|
+
return 'bun';
|
|
517
|
+
}
|
|
518
|
+
if (detectedLockfiles.includes('pnpm-lock.yaml')) {
|
|
519
|
+
return 'pnpm';
|
|
520
|
+
}
|
|
521
|
+
if (detectedLockfiles.includes('yarn.lock')) {
|
|
522
|
+
return 'yarn';
|
|
523
|
+
}
|
|
524
|
+
if (detectedLockfiles.includes('package-lock.json')) {
|
|
525
|
+
return 'npm';
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
execSync('pnpm --version', { stdio: 'ignore' });
|
|
530
|
+
return 'pnpm';
|
|
531
|
+
} catch (pnpmVersionError) {}
|
|
532
|
+
|
|
533
|
+
try {
|
|
534
|
+
execSync('yarn --version', { stdio: 'ignore' });
|
|
535
|
+
return 'yarn';
|
|
536
|
+
} catch (yarnVersionError) {}
|
|
537
|
+
|
|
538
|
+
return 'npm';
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Führt Code-Style Metriken-Suchen auf dem rohen Text-Inhalt einer Datei aus.
|
|
543
|
+
* @param {string} content Dateiinhalt.
|
|
544
|
+
* @param {Object} stats Globales Statistikobjekt.
|
|
545
|
+
*/
|
|
546
|
+
function analyzeCodeStyle(content, stats) {
|
|
547
|
+
const lines = content.split('\n');
|
|
548
|
+
|
|
549
|
+
for (const line of lines) {
|
|
550
|
+
const trimmed = line.trim();
|
|
551
|
+
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (trimmed.endsWith(';')) {
|
|
556
|
+
stats.style.semiCount++;
|
|
557
|
+
} else if (!/[{}:,\[\]]/.test(trimmed.slice(-1))) {
|
|
558
|
+
stats.style.noSemiCount++;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (line.startsWith('\t')) {
|
|
562
|
+
stats.style.tabCount++;
|
|
563
|
+
} else if (line.startsWith(' ')) {
|
|
564
|
+
const spaces = line.match(/^(\s+)/)?.[1]?.length || 0;
|
|
565
|
+
if (spaces === 2) {
|
|
566
|
+
stats.style.space2Count++;
|
|
567
|
+
}
|
|
568
|
+
if (spaces === 4) {
|
|
569
|
+
stats.style.space4Count++;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (REGEX_PATTERNS.legacyVar.test(content)) {
|
|
575
|
+
stats.quality.varCount += (content.match(REGEX_PATTERNS.legacyVar) || []).length;
|
|
576
|
+
}
|
|
577
|
+
if (REGEX_PATTERNS.dangerousEval.test(content)) {
|
|
578
|
+
stats.quality.hasEval = true;
|
|
579
|
+
}
|
|
580
|
+
if (REGEX_PATTERNS.syncFsCalls.test(content)) {
|
|
581
|
+
stats.quality.syncFsCount += (content.match(REGEX_PATTERNS.syncFsCalls) || []).length;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Durchsucht npm scripts nach ausführbaren Binärbefehlen dritter Anbieter.
|
|
587
|
+
* @param {Object} packageJsonContent Parste package.json Struktur.
|
|
588
|
+
* @returns {Array<string>} Liste gefundener CLI-Befehlsaufrufe.
|
|
589
|
+
*/
|
|
590
|
+
function getBinariesFromPackageJson(packageJsonContent) {
|
|
591
|
+
const binaries = new Set();
|
|
592
|
+
|
|
593
|
+
if (packageJsonContent && packageJsonContent.scripts) {
|
|
594
|
+
for (const script of Object.values(packageJsonContent.scripts)) {
|
|
595
|
+
const commands = String(script).split(/\s*&&\s*|\s*;\s*|\s*\|\|\s*/);
|
|
596
|
+
for (const cmd of commands) {
|
|
597
|
+
const firstWord = cmd.trim().split(/\s+/)[0];
|
|
598
|
+
if (firstWord && !['npm', 'yarn', 'pnpm', 'bun', 'node', 'npx', 'bunx', 'echo', 'exit', 'cd', 'mkdir', 'rm', 'cp', 'mv', 'cat', 'grep', 'sed', 'awk', 'find', 'sh', 'bash', 'zsh'].includes(firstWord)) {
|
|
599
|
+
binaries.add(firstWord);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return Array.from(binaries);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Isoliert den reinen npm Paketnamen unter Ausschluss interner Datei-Imports.
|
|
610
|
+
* @param {string} importString Import-Quell-Identifikator.
|
|
611
|
+
* @returns {string|null} Bereinigter Paketname oder null.
|
|
612
|
+
*/
|
|
613
|
+
function cleanPackageName(importString) {
|
|
614
|
+
if (!importString || /^[./~\\]/.test(importString)) {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
if (importString.startsWith('@')) {
|
|
618
|
+
return importString.split('/').slice(0, 2).join('/');
|
|
619
|
+
}
|
|
620
|
+
return importString.split('/')[0];
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Fügt Header-Komponenten präzise unterhalb von Shebangs oder Systemdirektiven ein.
|
|
625
|
+
*/
|
|
626
|
+
function smartPrepend(originalCode, declarationBlock) {
|
|
627
|
+
const lines = originalCode.split(/\r?\n/);
|
|
628
|
+
let insertIdx = 0;
|
|
629
|
+
|
|
630
|
+
while (insertIdx < lines.length) {
|
|
631
|
+
const line = lines[insertIdx].trim();
|
|
632
|
+
if (line.startsWith('#!') || line === '"use strict";' || line === "'use strict';" || line === '`use strict`;') {
|
|
633
|
+
insertIdx++;
|
|
634
|
+
} else if (line === '') {
|
|
635
|
+
insertIdx++;
|
|
636
|
+
} else {
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
lines.splice(insertIdx, 0, declarationBlock);
|
|
642
|
+
return lines.join('\n');
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Validiert die Existenz eines Pakets auf der offiziellen NPM-Registry.
|
|
647
|
+
*/
|
|
648
|
+
async function inspectNpmPackage(pkgName) {
|
|
649
|
+
try {
|
|
650
|
+
const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
|
|
651
|
+
headers: { 'User-Agent': 'pkg-scaffold-dx-client/2.0' },
|
|
652
|
+
signal: AbortSignal.timeout(4000)
|
|
653
|
+
});
|
|
654
|
+
if (response.status === 200) {
|
|
655
|
+
const data = await response.json();
|
|
656
|
+
return {
|
|
657
|
+
version: data.version,
|
|
658
|
+
deprecated: data.deprecated || null,
|
|
659
|
+
error: null
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (response.status === 404) {
|
|
663
|
+
return {
|
|
664
|
+
version: null,
|
|
665
|
+
deprecated: null,
|
|
666
|
+
error: 'NOT_FOUND'
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
} catch (networkInspectError) {
|
|
670
|
+
return {
|
|
671
|
+
version: 'latest',
|
|
672
|
+
deprecated: null,
|
|
673
|
+
error: 'NETWORK_FAIL'
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Holt Lizenztexte aus der GitHub Legal API.
|
|
681
|
+
*/
|
|
682
|
+
async function fetchRemoteLicense(licenseKey) {
|
|
683
|
+
try {
|
|
684
|
+
const response = await fetch(`https://api.github.com/licenses/${licenseKey.toLowerCase()}`, {
|
|
685
|
+
headers: { 'User-Agent': 'pkg-scaffold-dx-client/2.0' },
|
|
686
|
+
signal: AbortSignal.timeout(5000)
|
|
687
|
+
});
|
|
688
|
+
if (response.status === 200) {
|
|
689
|
+
const data = await response.json();
|
|
690
|
+
return data.body;
|
|
691
|
+
}
|
|
692
|
+
} catch (licenseApiError) {}
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Liest Dateien synchron ein und bereinigt etwaige BOM-Byte-Anordnungen.
|
|
698
|
+
*/
|
|
699
|
+
function readFileSyncNormalized(fullPath) {
|
|
700
|
+
const buffer = fs.readFileSync(fullPath);
|
|
701
|
+
if (buffer[0] === 0xFF && buffer[1] === 0xFE) {
|
|
702
|
+
return buffer.toString('utf16le');
|
|
703
|
+
}
|
|
704
|
+
if (buffer[0] === 0xFE && buffer[1] === 0xFF) {
|
|
705
|
+
return buffer.toString('utf8');
|
|
706
|
+
}
|
|
707
|
+
return buffer.toString('utf8');
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Hochpräziser Modul-Auflösungsalgorithmus via enhanced-resolve.
|
|
712
|
+
*/
|
|
713
|
+
const enhancedResolver = resolve.create.sync({
|
|
714
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.vue', '.svelte'],
|
|
715
|
+
conditionNames: ['import', 'require', 'node', 'default'],
|
|
716
|
+
mainFields: ['module', 'main'],
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
function resolveLocalModulePath(basePath, importSource) {
|
|
720
|
+
try {
|
|
721
|
+
return enhancedResolver(path.dirname(basePath), importSource);
|
|
722
|
+
} catch (e) {
|
|
723
|
+
// Fallback für den Fall, dass enhanced-resolve scheitert
|
|
724
|
+
const extensions = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.vue', '.svelte'];
|
|
725
|
+
let absoluteTarget = path.resolve(path.dirname(basePath), importSource);
|
|
726
|
+
|
|
727
|
+
if (fs.existsSync(absoluteTarget) && fs.statSync(absoluteTarget).isDirectory()) {
|
|
728
|
+
const indexFile = extensions.map(ext => { return path.join(absoluteTarget, `index${ext}`); }).find(fs.existsSync);
|
|
729
|
+
if (indexFile) {
|
|
730
|
+
return indexFile;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const directFile = extensions.map(ext => { return absoluteTarget + ext; }).find(fs.existsSync);
|
|
734
|
+
if (directFile) {
|
|
735
|
+
return directFile;
|
|
736
|
+
}
|
|
737
|
+
if (fs.existsSync(absoluteTarget)) {
|
|
738
|
+
return absoluteTarget;
|
|
739
|
+
}
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// ============================================================
|
|
745
|
+
// 🏗️ FRAMEWORK-SPECIFIC DEEP SCAN LOGIC
|
|
746
|
+
// ============================================================
|
|
747
|
+
class FrameworkAnalyzer {
|
|
748
|
+
static analyzeNextjsFile(filePath, content, stats) {
|
|
749
|
+
if (filePath.includes("pages/") && content.includes("getServerSideProps")) {
|
|
750
|
+
stats.frameworkFiles.nextjs.dataFetching.set(filePath, "getServerSideProps");
|
|
751
|
+
stats.frameworkOptimizations.push(`Next.js: Consider using 'getStaticProps' or client-side fetching for '${path.relative(process.cwd(), filePath)}' if data is not highly dynamic.`);
|
|
752
|
+
}
|
|
753
|
+
if (filePath.includes("pages/") && content.includes("getStaticProps")) {
|
|
754
|
+
stats.frameworkFiles.nextjs.dataFetching.set(filePath, "getStaticProps");
|
|
755
|
+
}
|
|
756
|
+
if (filePath.includes("pages/") && content.includes("getStaticPaths")) {
|
|
757
|
+
stats.frameworkFiles.nextjs.dataFetching.set(filePath, "getStaticPaths");
|
|
758
|
+
}
|
|
759
|
+
if (filePath.includes("app/") && content.includes("export async function GET")) {
|
|
760
|
+
stats.frameworkFiles.nextjs.dataFetching.set(filePath, "Route Handler (GET)");
|
|
761
|
+
}
|
|
762
|
+
if (content.includes("<img") && !content.includes("<Image")) {
|
|
763
|
+
stats.frameworkOptimizations.push(`Next.js: Use next/image for '${path.relative(process.cwd(), filePath)}' to optimize images.`);
|
|
764
|
+
}
|
|
765
|
+
if (content.includes("<link") && content.includes("googlefonts") && !content.includes("next/font")) {
|
|
766
|
+
stats.frameworkOptimizations.push(`Next.js: Use next/font for '${path.relative(process.cwd(), filePath)}' to optimize fonts.`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
static analyzeNuxtFile(filePath, content, stats) {
|
|
771
|
+
if (content.includes("useAsyncData")) {
|
|
772
|
+
stats.frameworkFiles.nuxt.dataFetching.set(filePath, "useAsyncData");
|
|
773
|
+
}
|
|
774
|
+
if (content.includes("useFetch")) {
|
|
775
|
+
stats.frameworkFiles.nuxt.dataFetching.set(filePath, "useFetch");
|
|
776
|
+
}
|
|
777
|
+
if (filePath.includes("components/") && !content.includes("defineComponent")) {
|
|
778
|
+
stats.frameworkOptimizations.push(`Nuxt: Ensure components in '${path.relative(process.cwd(), filePath)}' are properly defined for auto-import or explicitly imported.`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
static analyzeSvelteKitFile(filePath, content, stats) {
|
|
783
|
+
if (content.includes("export async function load")) {
|
|
784
|
+
stats.frameworkFiles.sveltekit.loadFunctions.set(filePath, "load");
|
|
785
|
+
}
|
|
786
|
+
if (filePath.includes("src/routes/") && content.includes("export const actions")) {
|
|
787
|
+
stats.frameworkFiles.sveltekit.endpoints.add(filePath);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
static analyzeReactFile(filePath, content, stats) {
|
|
792
|
+
REGEX_PATTERNS.reactUseEffectNoDeps.lastIndex = 0;
|
|
793
|
+
if (REGEX_PATTERNS.reactUseEffectNoDeps.test(content)) {
|
|
794
|
+
stats.frameworkOptimizations.push(`React Warning: useEffect hook inside '${path.relative(process.cwd(), filePath)}' is missing a trailing dependency array, which can cause severe infinite re-render loops.`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
static analyzeFile(filePath, content, stats, detectedFrameworks) {
|
|
799
|
+
if (!detectedFrameworks || !Array.isArray(detectedFrameworks)) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (detectedFrameworks.includes("next")) {
|
|
803
|
+
FrameworkAnalyzer.analyzeNextjsFile(filePath, content, stats);
|
|
804
|
+
}
|
|
805
|
+
if (detectedFrameworks.includes("nuxt")) {
|
|
806
|
+
FrameworkAnalyzer.analyzeNuxtFile(filePath, content, stats);
|
|
807
|
+
}
|
|
808
|
+
if (detectedFrameworks.includes("svelte")) {
|
|
809
|
+
FrameworkAnalyzer.analyzeSvelteKitFile(filePath, content, stats);
|
|
810
|
+
}
|
|
811
|
+
if (detectedFrameworks.includes("react")) {
|
|
812
|
+
FrameworkAnalyzer.analyzeReactFile(filePath, content, stats);
|
|
813
|
+
}
|
|
814
|
+
if (detectedFrameworks.includes("vue")) {
|
|
815
|
+
FrameworkAnalyzer.analyzeVueFile(filePath, content, stats);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
class FrameworkEngine {
|
|
821
|
+
static detect(targetDir, packageJson) {
|
|
822
|
+
const detected = new Set();
|
|
823
|
+
const allDependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
824
|
+
|
|
825
|
+
if (allDependencies.next) {
|
|
826
|
+
detected.add("next");
|
|
827
|
+
}
|
|
828
|
+
if (allDependencies.nuxt) {
|
|
829
|
+
detected.add("nuxt");
|
|
830
|
+
}
|
|
831
|
+
if (allDependencies.sveltekit) {
|
|
832
|
+
detected.add("svelte");
|
|
833
|
+
}
|
|
834
|
+
if (allDependencies.react) {
|
|
835
|
+
detected.add("react");
|
|
836
|
+
}
|
|
837
|
+
if (allDependencies.vue) {
|
|
838
|
+
detected.add("vue");
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (fs.existsSync(path.join(targetDir, "next.config.js")) || fs.existsSync(path.join(targetDir, "next.config.mjs"))) {
|
|
842
|
+
detected.add("next");
|
|
843
|
+
}
|
|
844
|
+
if (fs.existsSync(path.join(targetDir, "nuxt.config.js")) || fs.existsSync(path.join(targetDir, "nuxt.config.ts"))) {
|
|
845
|
+
detected.add("nuxt");
|
|
846
|
+
}
|
|
847
|
+
if (fs.existsSync(path.join(targetDir, "svelte.config.js"))) {
|
|
848
|
+
detected.add("svelte");
|
|
849
|
+
}
|
|
850
|
+
if (fs.existsSync(path.join(targetDir, "vite.config.js")) || fs.existsSync(path.join(targetDir, "vite.config.ts"))) {
|
|
851
|
+
if (allDependencies["@vitejs/plugin-react"]) {
|
|
852
|
+
detected.add("react");
|
|
853
|
+
}
|
|
854
|
+
if (allDependencies["@vitejs/plugin-vue"]) {
|
|
855
|
+
detected.add("vue");
|
|
856
|
+
}
|
|
857
|
+
if (allDependencies["@sveltejs/vite-plugin-svelte"]) {
|
|
858
|
+
detected.add("svelte");
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return Array.from(detected);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// ============================================================
|
|
866
|
+
// 🧱 CUSTOM STRUCTURAL HOISTED TEMPLATE MANAGER
|
|
867
|
+
// ============================================================
|
|
868
|
+
class TemplateManager {
|
|
869
|
+
constructor(baseDir, safeQuestion) {
|
|
870
|
+
this.baseDir = baseDir;
|
|
871
|
+
this.safeQuestion = safeQuestion;
|
|
872
|
+
this.templateSources = [{ name: 'local', path: path.join(this.baseDir, '.templates') }];
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
async listAvailableTemplates() {
|
|
876
|
+
const allTemplates = new Set();
|
|
877
|
+
for (const source of this.templateSources) {
|
|
878
|
+
if (source.name === 'local') {
|
|
879
|
+
const localTemplatesPath = source.path;
|
|
880
|
+
if (fs.existsSync(localTemplatesPath)) {
|
|
881
|
+
const templates = fs.readdirSync(localTemplatesPath, { withFileTypes: true })
|
|
882
|
+
.filter(dirent => { return dirent.isDirectory(); })
|
|
883
|
+
.map(dirent => { return dirent.name; });
|
|
884
|
+
templates.forEach(t => { return allTemplates.add(t); });
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return Array.from(allTemplates);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
async getTemplatePath(templateName) {
|
|
892
|
+
for (const source of this.templateSources) {
|
|
893
|
+
if (source.name === 'local') {
|
|
894
|
+
const templatePath = path.join(source.path, templateName);
|
|
895
|
+
if (fs.existsSync(templatePath)) {
|
|
896
|
+
return templatePath;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
async promptForVariables(templateName) {
|
|
904
|
+
const templatePath = await this.getTemplatePath(templateName);
|
|
905
|
+
if (!templatePath) {
|
|
906
|
+
console.log(` ⚠️ Template '${templateName}' not found.`);
|
|
907
|
+
return {};
|
|
908
|
+
}
|
|
909
|
+
const configPath = path.join(templatePath, '_config.json');
|
|
910
|
+
if (fs.existsSync(configPath)) {
|
|
911
|
+
try {
|
|
912
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
913
|
+
const variables = {};
|
|
914
|
+
for (const key in config.prompts) {
|
|
915
|
+
const prompt = config.prompts[key];
|
|
916
|
+
let answer = await this.safeQuestion(`❓ ${prompt.message || key}: `);
|
|
917
|
+
if (prompt.type === 'boolean') {
|
|
918
|
+
answer = answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
919
|
+
} else if (prompt.type === 'number') {
|
|
920
|
+
answer = parseFloat(answer);
|
|
921
|
+
}
|
|
922
|
+
variables[key] = answer;
|
|
923
|
+
}
|
|
924
|
+
return variables;
|
|
925
|
+
} catch (jsonConfigReadError) {
|
|
926
|
+
console.error(` ❌ Error reading template config for '${templateName}': ${jsonConfigReadError.message}`);
|
|
927
|
+
return {};
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return {};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
async generate(templateName, variables) {
|
|
934
|
+
const templatePath = await this.getTemplatePath(templateName);
|
|
935
|
+
if (!templatePath) {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
console.log(` 🚀 Generating '${templateName}' template with full route and token mutations...`);
|
|
939
|
+
|
|
940
|
+
const renderFile = async (srcPath, destPath, vars) => {
|
|
941
|
+
const content = fs.readFileSync(srcPath, 'utf8');
|
|
942
|
+
let renderedContent = content;
|
|
943
|
+
for (const key in vars) {
|
|
944
|
+
renderedContent = renderedContent.replace(new RegExp(`{{\\s*${key}\\s*}}`, 'g'), vars[key]);
|
|
945
|
+
}
|
|
946
|
+
fs.writeFileSync(destPath, renderedContent);
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
const processDirectory = async (currentSrcDir, currentDestDir, vars) => {
|
|
950
|
+
fs.mkdirSync(currentDestDir, { recursive: true });
|
|
951
|
+
const items = fs.readdirSync(currentSrcDir, { withFileTypes: true });
|
|
952
|
+
for (const item of items) {
|
|
953
|
+
if (item.name === '_config.json' || item.name === 'config.json') {
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
let mutatedName = item.name;
|
|
958
|
+
for (const key in vars) {
|
|
959
|
+
mutatedName = mutatedName.replace(new RegExp(`{{\\s*${key}\\s*}}`, 'g'), vars[key]);
|
|
960
|
+
}
|
|
961
|
+
mutatedName = mutatedName.replace(/_([a-zA-Z0-9_]+)_/g, (match, p1) => { return vars[p1] || match; });
|
|
962
|
+
|
|
963
|
+
const srcItemPath = path.join(currentSrcDir, item.name);
|
|
964
|
+
const destItemPath = path.join(currentDestDir, mutatedName);
|
|
965
|
+
|
|
966
|
+
if (item.isDirectory()) {
|
|
967
|
+
await processDirectory(srcItemPath, destItemPath, vars);
|
|
968
|
+
} else {
|
|
969
|
+
await renderFile(srcItemPath, destItemPath, vars);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
await processDirectory(templatePath, this.baseDir, variables);
|
|
974
|
+
console.log(` ✅ Template '${templateName}' generated successfully.`);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Generiert einen sauberen ASCII-Layout-Baum für das automatisierte README-Dokument.
|
|
980
|
+
*/
|
|
981
|
+
function buildAsciiTree(dir, prefix = '') {
|
|
982
|
+
const results = [];
|
|
983
|
+
try {
|
|
984
|
+
const files = fs.readdirSync(dir);
|
|
985
|
+
const filtered = files.filter(f => { return !IGNORED_DIRS.has(f) && !f.startsWith('.'); });
|
|
986
|
+
|
|
987
|
+
filtered.forEach((file, index) => {
|
|
988
|
+
const isLast = index === filtered.length - 1;
|
|
989
|
+
const marker = isLast ? '└── ' : '├── ';
|
|
990
|
+
results.push(`${prefix}${marker}${file}`);
|
|
991
|
+
|
|
992
|
+
const fullPath = path.join(dir, file);
|
|
993
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
994
|
+
const newPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
995
|
+
results.push(...buildAsciiTree(fullPath, newPrefix));
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
} catch (asciiTreeTreeError) {}
|
|
999
|
+
return results;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// ============================================================
|
|
1003
|
+
// STRUKTURELLES IMPLEMENTIERUNGSHELFER SYSTEM
|
|
1004
|
+
// ============================================================
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* KORREKTUR: Prüft, ob ein extrahierter Import-Bezeichner im Programmcode verwendet wird.
|
|
1008
|
+
* Führt den regulären Ausdruck nun gegen das tatsächliche executionCode-Skelett aus, anstatt gegen den Alias selbst.
|
|
1009
|
+
*/
|
|
1010
|
+
function analyzeIdentifierUsage(pkg, identifiers, executionCode) {
|
|
1011
|
+
const autoUsedMarkers = new Set(['__SIDE_EFFECT__', '__DYNAMIC__', '__REEXPORT__', '__TYPE_ONLY__']);
|
|
1012
|
+
for (const id of identifiers) {
|
|
1013
|
+
if (autoUsedMarkers.has(id)) {
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
for (const id of identifiers) {
|
|
1018
|
+
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1019
|
+
if (new RegExp(`\\b${escaped}\\b`).test(executionCode)) {
|
|
1020
|
+
return true;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
const aliases = PACKAGE_IMPORT_ALIASES[pkg] || [];
|
|
1024
|
+
for (const alias of aliases) {
|
|
1025
|
+
const escapedAlias = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1026
|
+
if (new RegExp(`\\b${escapedAlias}\\b`).test(executionCode)) { // 👈 Use the properly escaped token here
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* Findet Pakete, die im Programmcode importiert wurden, aber nicht in der package.json deklariert sind.
|
|
1035
|
+
*/
|
|
1036
|
+
function detectGhostDependencies(allImportedPackages, declaredDeps, declaredDevDeps) {
|
|
1037
|
+
const allDeclared = new Set([...declaredDeps, ...declaredDevDeps]);
|
|
1038
|
+
const ghosts = new Set();
|
|
1039
|
+
for (const pkg of allImportedPackages) {
|
|
1040
|
+
if (!allDeclared.has(pkg) && !builtinModules.includes(pkg)) {
|
|
1041
|
+
ghosts.add(pkg);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return ghosts;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Ermittelt ungenutzte verwaiste Abhängigkeiten aus der package.json.
|
|
1049
|
+
*/
|
|
1050
|
+
function detectOrphanedDependencies(declaredDeps, allImportedPackages, binariesUsed, devTooling) {
|
|
1051
|
+
const orphans = new Set();
|
|
1052
|
+
for (const dep of declaredDeps) {
|
|
1053
|
+
if (devTooling.has(dep) || dep.startsWith('@types/')) {
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
const binaryPkg = Object.values(BINARY_TO_PACKAGE_MAP).find(p => { return p === dep; });
|
|
1057
|
+
if (binaryPkg && binariesUsed.has(dep)) {
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (!allImportedPackages.has(dep)) {
|
|
1061
|
+
orphans.add(dep);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return orphans;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Semantische Symbol-Extraktion via TypeScript Compiler API.
|
|
1069
|
+
*/
|
|
1070
|
+
function extractAdvancedSymbols(sourceFile, stats, currentFilePath) {
|
|
1071
|
+
if (!stats.fileSymbolMetadata) {
|
|
1072
|
+
stats.fileSymbolMetadata = new Map();
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const fileMeta = {
|
|
1076
|
+
imports: [],
|
|
1077
|
+
exports: new Map(),
|
|
1078
|
+
reExports: [],
|
|
1079
|
+
namespaceUses: new Map(),
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
function visit(node) {
|
|
1083
|
+
// --- Imports ---
|
|
1084
|
+
if (ts.isImportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
1085
|
+
const importSource = node.moduleSpecifier.text;
|
|
1086
|
+
const specifiers = [];
|
|
1087
|
+
|
|
1088
|
+
if (node.importClause) {
|
|
1089
|
+
if (node.importClause.name) {
|
|
1090
|
+
specifiers.push({ type: 'ImportDefaultSpecifier', local: node.importClause.name.text });
|
|
1091
|
+
}
|
|
1092
|
+
if (node.importClause.namedBindings) {
|
|
1093
|
+
if (ts.isNamespaceImport(node.importClause.namedBindings)) {
|
|
1094
|
+
specifiers.push({ type: 'ImportNamespaceSpecifier', local: node.importClause.namedBindings.name.text });
|
|
1095
|
+
} else if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
1096
|
+
node.importClause.namedBindings.elements.forEach(el => {
|
|
1097
|
+
specifiers.push({
|
|
1098
|
+
type: 'ImportSpecifier',
|
|
1099
|
+
local: el.name.text,
|
|
1100
|
+
imported: el.propertyName ? el.propertyName.text : el.name.text
|
|
1101
|
+
});
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
fileMeta.imports.push({
|
|
1108
|
+
source: importSource,
|
|
1109
|
+
specifiers,
|
|
1110
|
+
isTypeOnly: !!node.importClause?.isTypeOnly
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// --- Exports ---
|
|
1115
|
+
if (ts.isExportDeclaration(node)) {
|
|
1116
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
1117
|
+
const specifiers = [];
|
|
1118
|
+
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
1119
|
+
node.exportClause.elements.forEach(el => {
|
|
1120
|
+
specifiers.push({
|
|
1121
|
+
exported: el.name.text,
|
|
1122
|
+
local: el.propertyName ? el.propertyName.text : el.name.text
|
|
1123
|
+
});
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
fileMeta.reExports.push({ source: node.moduleSpecifier.text, specifiers });
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
if (ts.isVariableStatement(node) && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
1131
|
+
node.declarationList.declarations.forEach(decl => {
|
|
1132
|
+
if (ts.isIdentifier(decl.name)) {
|
|
1133
|
+
fileMeta.exports.set(decl.name.text, { type: 'variable' });
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
if (ts.isFunctionDeclaration(node) && node.name && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
1139
|
+
fileMeta.exports.set(node.name.text, { type: 'function' });
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (ts.isClassDeclaration(node) && node.name && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
1143
|
+
fileMeta.exports.set(node.name.text, { type: 'class' });
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// --- Symbol Usage Tracking ---
|
|
1147
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
|
|
1148
|
+
const namespace = node.expression.text;
|
|
1149
|
+
if (!fileMeta.namespaceUses.has(namespace)) {
|
|
1150
|
+
fileMeta.namespaceUses.set(namespace, new Set());
|
|
1151
|
+
}
|
|
1152
|
+
fileMeta.namespaceUses.get(namespace).add(node.name.text);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
ts.forEachChild(node, visit);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
visit(sourceFile);
|
|
1159
|
+
stats.fileSymbolMetadata.set(currentFilePath, fileMeta);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function extractImportsFromAST(sourceFile, fileRawDeps, importedIdentifiers, importedLocations, exportedSymbols, stats, currentFilePath) {
|
|
1163
|
+
extractAdvancedSymbols(sourceFile, stats, currentFilePath);
|
|
1164
|
+
|
|
1165
|
+
const fileMeta = stats.fileSymbolMetadata.get(currentFilePath);
|
|
1166
|
+
|
|
1167
|
+
fileMeta.imports.forEach(imp => {
|
|
1168
|
+
const pkg = cleanPackageName(imp.source);
|
|
1169
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
1170
|
+
fileRawDeps.add(pkg);
|
|
1171
|
+
if (!importedIdentifiers.has(pkg)) {
|
|
1172
|
+
importedIdentifiers.set(pkg, new Set());
|
|
1173
|
+
}
|
|
1174
|
+
if (!importedLocations.has(pkg)) {
|
|
1175
|
+
importedLocations.set(pkg, []);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
imp.specifiers.forEach(spec => {
|
|
1179
|
+
importedIdentifiers.get(pkg).add(spec.local);
|
|
1180
|
+
if (spec.imported && spec.imported !== spec.local) {
|
|
1181
|
+
importedIdentifiers.get(pkg).add(spec.imported);
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
if (imp.specifiers.length === 0) {
|
|
1186
|
+
importedIdentifiers.get(pkg).add('__SIDE_EFFECT__');
|
|
1187
|
+
}
|
|
1188
|
+
if (imp.isTypeOnly) {
|
|
1189
|
+
importedIdentifiers.get(pkg).add('__TYPE_ONLY__');
|
|
1190
|
+
}
|
|
1191
|
+
} else if (imp.source.startsWith('.') || imp.source.startsWith('/')) {
|
|
1192
|
+
const resolvedPath = resolveLocalModulePath(currentFilePath, imp.source);
|
|
1193
|
+
if (resolvedPath) {
|
|
1194
|
+
if (!stats.localFileImports) {
|
|
1195
|
+
stats.localFileImports = new Map();
|
|
1196
|
+
}
|
|
1197
|
+
if (!stats.localFileImports.has(resolvedPath)) {
|
|
1198
|
+
stats.localFileImports.set(resolvedPath, new Set());
|
|
1199
|
+
}
|
|
1200
|
+
imp.specifiers.forEach(spec => {
|
|
1201
|
+
stats.localFileImports.get(resolvedPath).add(spec.local);
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
// Handle CJS require
|
|
1208
|
+
function visit(node) {
|
|
1209
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'require') {
|
|
1210
|
+
const arg = node.arguments[0];
|
|
1211
|
+
if (arg && ts.isStringLiteral(arg)) {
|
|
1212
|
+
const pkg = cleanPackageName(arg.text);
|
|
1213
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
1214
|
+
fileRawDeps.add(pkg);
|
|
1215
|
+
if (!importedIdentifiers.has(pkg)) {
|
|
1216
|
+
importedIdentifiers.set(pkg, new Set());
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
ts.forEachChild(node, visit);
|
|
1222
|
+
}
|
|
1223
|
+
visit(sourceFile);
|
|
1224
|
+
|
|
1225
|
+
// Sync exported symbols
|
|
1226
|
+
fileMeta.exports.forEach((meta, name) => {
|
|
1227
|
+
exportedSymbols.set(name, meta);
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Textbasierter Regex-Ersatz-Parser, falls der AST-Parser auf Syntaxfehler stößt.
|
|
1233
|
+
*/
|
|
1234
|
+
function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations, stats, currentFilePath) {
|
|
1235
|
+
codeLines.forEach((line, lineIdx) => {
|
|
1236
|
+
const lineNum = lineIdx + 1;
|
|
1237
|
+
const typeImportMatch = line.match(/\bimport\s+type\s+\{[^}]*\}\s+from\s+['"]([^'"]+)['"]/);
|
|
1238
|
+
if (typeImportMatch) {
|
|
1239
|
+
const importSource = typeImportMatch[1];
|
|
1240
|
+
const pkg = cleanPackageName(importSource);
|
|
1241
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
1242
|
+
fileRawDeps.add(pkg);
|
|
1243
|
+
if (!importedIdentifiers.has(pkg)) {
|
|
1244
|
+
importedIdentifiers.set(pkg, new Set());
|
|
1245
|
+
}
|
|
1246
|
+
importedIdentifiers.get(pkg).add('__TYPE_ONLY__');
|
|
1247
|
+
if (!importedLocations.has(pkg)) {
|
|
1248
|
+
importedLocations.set(pkg, []);
|
|
1249
|
+
}
|
|
1250
|
+
importedLocations.get(pkg).push(lineNum);
|
|
1251
|
+
} else if (importSource.startsWith(".") || importSource.startsWith("/")) {
|
|
1252
|
+
const resolvedPath = resolveLocalModulePath(currentFilePath, importSource);
|
|
1253
|
+
if (resolvedPath) {
|
|
1254
|
+
if (!stats.localFileImports) {
|
|
1255
|
+
stats.localFileImports = new Map();
|
|
1256
|
+
}
|
|
1257
|
+
if (!stats.localFileImports.has(resolvedPath)) {
|
|
1258
|
+
stats.localFileImports.set(resolvedPath, new Set());
|
|
1259
|
+
}
|
|
1260
|
+
stats.localFileImports.get(resolvedPath).add('__TYPE_ONLY__');
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const esmDefaultMatch = line.match(/\bimport\s+(?:\*\s+as\s+)?([a-zA-Z0-9_$]+)\s+from\s+['"]([^'"]+)['"]/);
|
|
1267
|
+
if (esmDefaultMatch) {
|
|
1268
|
+
const id = esmDefaultMatch[1];
|
|
1269
|
+
const importSource = esmDefaultMatch[2];
|
|
1270
|
+
const pkg = cleanPackageName(importSource);
|
|
1271
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
1272
|
+
fileRawDeps.add(pkg);
|
|
1273
|
+
if (!importedIdentifiers.has(pkg)) {
|
|
1274
|
+
importedIdentifiers.set(pkg, new Set());
|
|
1275
|
+
}
|
|
1276
|
+
importedIdentifiers.get(pkg).add(id);
|
|
1277
|
+
if (!importedLocations.has(pkg)) {
|
|
1278
|
+
importedLocations.set(pkg, []);
|
|
1279
|
+
}
|
|
1280
|
+
importedLocations.get(pkg).push(lineNum);
|
|
1281
|
+
} else if (importSource.startsWith(".") || importSource.startsWith("/")) {
|
|
1282
|
+
const resolvedPath = resolveLocalModulePath(currentFilePath, importSource);
|
|
1283
|
+
if (resolvedPath) {
|
|
1284
|
+
if (!stats.localFileImports) {
|
|
1285
|
+
stats.localFileImports = new Map();
|
|
1286
|
+
}
|
|
1287
|
+
if (!stats.localFileImports.has(resolvedPath)) {
|
|
1288
|
+
stats.localFileImports.set(resolvedPath, new Set());
|
|
1289
|
+
}
|
|
1290
|
+
stats.localFileImports.get(resolvedPath).add(id);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const esmNamedMatch = line.match(/\bimport\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/);
|
|
1297
|
+
if (esmNamedMatch) {
|
|
1298
|
+
const importSource = esmNamedMatch[2];
|
|
1299
|
+
const pkg = cleanPackageName(importSource);
|
|
1300
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
1301
|
+
if (!importedIdentifiers.has(pkg)) {
|
|
1302
|
+
importedIdentifiers.set(pkg, new Set());
|
|
1303
|
+
}
|
|
1304
|
+
fileRawDeps.add(pkg);
|
|
1305
|
+
esmNamedMatch[1].split(',').forEach(part => {
|
|
1306
|
+
const chunk = part.trim();
|
|
1307
|
+
if (!chunk) {
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
const id = chunk.includes(' as ') ? chunk.split(' as ')[1].trim() : chunk;
|
|
1311
|
+
importedIdentifiers.get(pkg).add(id);
|
|
1312
|
+
});
|
|
1313
|
+
if (!importedLocations.has(pkg)) {
|
|
1314
|
+
importedLocations.set(pkg, []);
|
|
1315
|
+
}
|
|
1316
|
+
importedLocations.get(pkg).push(lineNum);
|
|
1317
|
+
} else if (importSource.startsWith(".") || importSource.startsWith("/")) {
|
|
1318
|
+
const resolvedPath = resolveLocalModulePath(currentFilePath, importSource);
|
|
1319
|
+
if (resolvedPath) {
|
|
1320
|
+
if (!stats.localFileImports) {
|
|
1321
|
+
stats.localFileImports = new Map();
|
|
1322
|
+
}
|
|
1323
|
+
if (!stats.localFileImports.has(resolvedPath)) {
|
|
1324
|
+
stats.localFileImports.set(resolvedPath, new Set());
|
|
1325
|
+
}
|
|
1326
|
+
esmNamedMatch[1].split(',').forEach(part => {
|
|
1327
|
+
const chunk = part.trim();
|
|
1328
|
+
if (!chunk) {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
const id = chunk.includes(' as ') ? chunk.split(' as ')[1].trim() : chunk;
|
|
1332
|
+
stats.localFileImports.get(resolvedPath).add(id);
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
const sideEffectMatch = line.match(/\bimport\s+['"]([^'"]+)['"]/);
|
|
1340
|
+
if (sideEffectMatch) {
|
|
1341
|
+
const importSource = sideEffectMatch[1];
|
|
1342
|
+
const pkg = cleanPackageName(importSource);
|
|
1343
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
1344
|
+
fileRawDeps.add(pkg);
|
|
1345
|
+
if (!importedIdentifiers.has(pkg)) {
|
|
1346
|
+
importedIdentifiers.set(pkg, new Set());
|
|
1347
|
+
}
|
|
1348
|
+
importedIdentifiers.get(pkg).add('__SIDE_EFFECT__');
|
|
1349
|
+
if (!importedLocations.has(pkg)) {
|
|
1350
|
+
importedLocations.set(pkg, []);
|
|
1351
|
+
}
|
|
1352
|
+
importedLocations.get(pkg).push(lineNum);
|
|
1353
|
+
} else if (importSource.startsWith(".") || importSource.startsWith("/")) {
|
|
1354
|
+
const resolvedPath = resolveLocalModulePath(currentFilePath, importSource);
|
|
1355
|
+
if (resolvedPath) {
|
|
1356
|
+
if (!stats.localFileImports) {
|
|
1357
|
+
stats.localFileImports = new Map();
|
|
1358
|
+
}
|
|
1359
|
+
if (!stats.localFileImports.has(resolvedPath)) {
|
|
1360
|
+
stats.localFileImports.set(resolvedPath, new Set());
|
|
1361
|
+
}
|
|
1362
|
+
stats.localFileImports.get(resolvedPath).add('__SIDE_EFFECT__');
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* Rekursive Workspace-Analyse-Engine.
|
|
1372
|
+
*/
|
|
1373
|
+
function scanWorkspace(dir, stats, rootNamespace, detectedFrameworks) {
|
|
1374
|
+
try {
|
|
1375
|
+
const entries = fs.readdirSync(dir);
|
|
1376
|
+
for (const file of entries) {
|
|
1377
|
+
const fullPath = path.join(dir, file);
|
|
1378
|
+
const stat = fs.statSync(fullPath);
|
|
1379
|
+
|
|
1380
|
+
if (stat.isDirectory()) {
|
|
1381
|
+
if (IGNORED_DIRS.has(file) || file.startsWith('.')) {
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
scanWorkspace(fullPath, stats, rootNamespace, detectedFrameworks);
|
|
1385
|
+
} else {
|
|
1386
|
+
const ext = path.extname(file);
|
|
1387
|
+
if (file === 'index.html' || REGEX_PATTERNS.configFile.test(file)) {
|
|
1388
|
+
stats.hasHtml = true;
|
|
1389
|
+
}
|
|
1390
|
+
if (REGEX_PATTERNS.testFile.test(file)) {
|
|
1391
|
+
stats.hasTests = true;
|
|
1392
|
+
}
|
|
1393
|
+
if (ext === '.ts' || ext === '.tsx') {
|
|
1394
|
+
stats.tsFiles++;
|
|
1395
|
+
}
|
|
1396
|
+
if (ext === '.js' || ext === '.jsx' || ext === '.mjs') {
|
|
1397
|
+
stats.jsFiles++;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
if (REGEX_PATTERNS.nextjsPage.test(fullPath)) {
|
|
1401
|
+
stats.frameworkFiles.nextjs.pages.add(fullPath);
|
|
1402
|
+
}
|
|
1403
|
+
if (REGEX_PATTERNS.nextjsApi.test(fullPath)) {
|
|
1404
|
+
stats.frameworkFiles.nextjs.apiRoutes.add(fullPath);
|
|
1405
|
+
}
|
|
1406
|
+
if (REGEX_PATTERNS.nextjsComponent.test(fullPath)) {
|
|
1407
|
+
stats.frameworkFiles.nextjs.components.add(fullPath);
|
|
1408
|
+
}
|
|
1409
|
+
if (REGEX_PATTERNS.nuxtPage.test(fullPath)) {
|
|
1410
|
+
stats.frameworkFiles.nuxt.pages.add(fullPath);
|
|
1411
|
+
}
|
|
1412
|
+
if (REGEX_PATTERNS.nuxtComponent.test(fullPath)) {
|
|
1413
|
+
stats.frameworkFiles.nuxt.components.add(fullPath);
|
|
1414
|
+
}
|
|
1415
|
+
if (REGEX_PATTERNS.sveltekitPage.test(fullPath)) {
|
|
1416
|
+
stats.frameworkFiles.sveltekit.pages.add(fullPath);
|
|
1417
|
+
}
|
|
1418
|
+
if (REGEX_PATTERNS.sveltekitComponent.test(fullPath)) {
|
|
1419
|
+
stats.frameworkFiles.sveltekit.components.add(fullPath);
|
|
1420
|
+
}
|
|
1421
|
+
if (REGEX_PATTERNS.reactHook.test(fullPath)) {
|
|
1422
|
+
stats.frameworkFiles.react.hooks.add(fullPath);
|
|
1423
|
+
}
|
|
1424
|
+
if (REGEX_PATTERNS.vueComposable.test(fullPath)) {
|
|
1425
|
+
stats.frameworkFiles.vue.composables.add(fullPath);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
if (VALID_EXTENSIONS.has(ext)) {
|
|
1429
|
+
stats.scannedFiles++;
|
|
1430
|
+
stats.scannedFilePaths.push(fullPath);
|
|
1431
|
+
const rawContent = readFileSyncNormalized(fullPath);
|
|
1432
|
+
const content = rawContent.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
1433
|
+
|
|
1434
|
+
const codeLines = content.split(/\r?\n/);
|
|
1435
|
+
const importedIdentifiers = new Map();
|
|
1436
|
+
const importedLocations = new Map();
|
|
1437
|
+
const fileRawDeps = new Set();
|
|
1438
|
+
|
|
1439
|
+
analyzeCodeStyle(content, stats);
|
|
1440
|
+
|
|
1441
|
+
for (const [patternName, patternRegex] of Object.entries(REGEX_PATTERNS)) {
|
|
1442
|
+
if (patternName.startsWith("secretKeys") || patternName.endsWith("Keys") || patternName.endsWith("Tokens")) {
|
|
1443
|
+
patternRegex.lastIndex = 0;
|
|
1444
|
+
let match;
|
|
1445
|
+
while ((match = patternRegex.exec(content)) !== null) {
|
|
1446
|
+
const keyName = match[1] || patternName;
|
|
1447
|
+
const secretValue = match[2] || match[0];
|
|
1448
|
+
const envVarName = `${rootNamespace.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_${keyName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}`;
|
|
1449
|
+
stats.discoveredSecrets.push({ filePath: fullPath, keyName, secretValue, envVarName, type: patternName });
|
|
1450
|
+
stats.envVars.add(envVarName);
|
|
1451
|
+
}
|
|
1452
|
+
} else if (patternName.startsWith("insecure")) {
|
|
1453
|
+
patternRegex.lastIndex = 0;
|
|
1454
|
+
let match;
|
|
1455
|
+
while ((match = patternRegex.exec(content)) !== null) {
|
|
1456
|
+
const line = content.substring(0, match.index).split("\n").length;
|
|
1457
|
+
if (patternName === "insecureCrypto") {
|
|
1458
|
+
stats.quality.insecureCryptoUsage.push({ filePath: fullPath, type: patternName, line, code: match[0] });
|
|
1459
|
+
} else if (patternName === "sqlInjection") {
|
|
1460
|
+
stats.quality.sqlInjectionVulnerabilities.push({ filePath: fullPath, type: patternName, line, code: match[0] });
|
|
1461
|
+
} else if (patternName === "xssVulnerability") {
|
|
1462
|
+
stats.quality.xssVulnerabilities.push({ filePath: fullPath, type: patternName, line, code: match[0] });
|
|
1463
|
+
} else {
|
|
1464
|
+
stats.quality.insecurePatterns.push({ filePath: fullPath, type: patternName, line, code: match[0] });
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
} else if (patternName.startsWith("largeImageImport")) {
|
|
1468
|
+
patternRegex.lastIndex = 0;
|
|
1469
|
+
let match;
|
|
1470
|
+
while ((match = patternRegex.exec(content)) !== null) {
|
|
1471
|
+
const line = content.substring(0, match.index).split("\n").length;
|
|
1472
|
+
stats.quality.largeImageImports.push({ filePath: fullPath, type: patternName, line, code: match[0] });
|
|
1473
|
+
}
|
|
1474
|
+
} else if (patternName.startsWith("unoptimizedLoop")) {
|
|
1475
|
+
patternRegex.lastIndex = 0;
|
|
1476
|
+
let match;
|
|
1477
|
+
while ((match = patternRegex.exec(content)) !== null) {
|
|
1478
|
+
const line = content.substring(0, match.index).split("\n").length;
|
|
1479
|
+
stats.quality.unoptimizedLoops.push({ filePath: fullPath, type: patternName, line, code: match[0] });
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
let fileHasEnv = false;
|
|
1485
|
+
REGEX_PATTERNS.env.lastIndex = 0;
|
|
1486
|
+
while ((envMatch = REGEX_PATTERNS.env.exec(content)) !== null) {
|
|
1487
|
+
stats.envVars.add(envMatch[1]);
|
|
1488
|
+
fileHasEnv = true;
|
|
1489
|
+
}
|
|
1490
|
+
if (fileHasEnv) {
|
|
1491
|
+
stats.filesWithEnvVars.add(fullPath);
|
|
1492
|
+
}
|
|
1493
|
+
if (content.includes('import ') || content.includes('export ')) {
|
|
1494
|
+
stats.usesEsm = true;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
FrameworkAnalyzer.analyzeFile(fullPath, content, stats, detectedFrameworks);
|
|
1498
|
+
|
|
1499
|
+
let sourceFile = null;
|
|
1500
|
+
try {
|
|
1501
|
+
sourceFile = ts.createSourceFile(fullPath, content, ts.ScriptTarget.Latest, true);
|
|
1502
|
+
} catch (e) {}
|
|
1503
|
+
|
|
1504
|
+
if (sourceFile) {
|
|
1505
|
+
const currentFileExportedSymbols = new Map();
|
|
1506
|
+
extractImportsFromAST(sourceFile, fileRawDeps, importedIdentifiers, importedLocations, currentFileExportedSymbols, stats, fullPath);
|
|
1507
|
+
if (currentFileExportedSymbols.size > 0) {
|
|
1508
|
+
stats.exportedSymbols.set(fullPath, currentFileExportedSymbols);
|
|
1509
|
+
}
|
|
1510
|
+
} else {
|
|
1511
|
+
extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations, stats, fullPath);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
fileRawDeps.forEach(dep => { return stats.allImportedPackages.add(dep); });
|
|
1515
|
+
fileRawDeps.forEach(dep => { return stats.rawDeps.add(dep); });
|
|
1516
|
+
|
|
1517
|
+
const executionCode = codeLines.filter(l => {
|
|
1518
|
+
const t = l.trim();
|
|
1519
|
+
return !t.startsWith('import ') && !/\brequire\s*\(/.test(t);
|
|
1520
|
+
}).join('\n');
|
|
1521
|
+
|
|
1522
|
+
for (const [pkg, identifiers] of importedIdentifiers.entries()) {
|
|
1523
|
+
const isUsed = analyzeIdentifierUsage(pkg, identifiers, executionCode);
|
|
1524
|
+
if (!isUsed && identifiers.size > 0) {
|
|
1525
|
+
if (!stats.unusedImportsPerFile.has(fullPath)) {
|
|
1526
|
+
stats.unusedImportsPerFile.set(fullPath, new Map());
|
|
1527
|
+
}
|
|
1528
|
+
const lines = importedLocations.get(pkg) || [];
|
|
1529
|
+
stats.unusedImportsPerFile.get(fullPath).set(pkg, lines);
|
|
1530
|
+
if (!isUsed && !['__TYPE_ONLY__'].some(m => identifiers.has(m))) {
|
|
1531
|
+
stats.unusedDepsInCode.add(pkg);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
} catch (scanWorkspaceError) {}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// ============================================================
|
|
1542
|
+
// 🌳 KNIP-LEVEL REACHABILITY TRAVERSAL ENGINE
|
|
1543
|
+
// ============================================================
|
|
1544
|
+
class KnipEcosystemGraph {
|
|
1545
|
+
constructor(stats) {
|
|
1546
|
+
this.stats = stats;
|
|
1547
|
+
this.reachableFiles = new Set();
|
|
1548
|
+
this.usedSymbolsMap = new Map();
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/**
|
|
1552
|
+
* Traversiert den Modulgraphen ausgehend von den Entrypoints, um erreichbare Dateien und Tokens zu mappen.
|
|
1553
|
+
* @param {Array<string>} entrypoints Liste absoluter Dateipfade als Einstiegspunkte.
|
|
1554
|
+
*/
|
|
1555
|
+
traceReachability(entrypoints) {
|
|
1556
|
+
const queue = [...entrypoints];
|
|
1557
|
+
const visited = new Set();
|
|
1558
|
+
|
|
1559
|
+
while (queue.length > 0) {
|
|
1560
|
+
const currentFile = queue.shift();
|
|
1561
|
+
if (visited.has(currentFile)) {
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
visited.add(currentFile);
|
|
1565
|
+
this.reachableFiles.add(currentFile);
|
|
1566
|
+
|
|
1567
|
+
const meta = this.stats.fileSymbolMetadata?.get(currentFile);
|
|
1568
|
+
if (!meta) {
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
meta.imports.forEach(imp => {
|
|
1573
|
+
const targetPath = resolveLocalModulePath(currentFile, imp.source);
|
|
1574
|
+
if (!targetPath) {
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
if (!this.usedSymbolsMap.has(targetPath)) {
|
|
1579
|
+
this.usedSymbolsMap.set(targetPath, new Set());
|
|
1580
|
+
}
|
|
1581
|
+
const targetUses = this.usedSymbolsMap.get(targetPath);
|
|
1582
|
+
|
|
1583
|
+
imp.specifiers.forEach(spec => {
|
|
1584
|
+
if (spec.type === 'ImportNamespaceSpecifier') {
|
|
1585
|
+
const propsAccessed = meta.namespaceUses.get(spec.local);
|
|
1586
|
+
if (propsAccessed) {
|
|
1587
|
+
propsAccessed.forEach(p => { return targetUses.add(p); });
|
|
1588
|
+
} else {
|
|
1589
|
+
targetUses.add('*');
|
|
1590
|
+
}
|
|
1591
|
+
} else {
|
|
1592
|
+
targetUses.add(spec.imported);
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
if (!visited.has(targetPath)) {
|
|
1596
|
+
queue.push(targetPath);
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
meta.reExports.forEach(re => {
|
|
1601
|
+
const targetPath = resolveLocalModulePath(currentFile, re.source);
|
|
1602
|
+
if (!targetPath) {
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
if (!this.usedSymbolsMap.has(targetPath)) {
|
|
1607
|
+
this.usedSymbolsMap.set(targetPath, new Set());
|
|
1608
|
+
}
|
|
1609
|
+
const targetUses = this.usedSymbolsMap.get(targetPath);
|
|
1610
|
+
|
|
1611
|
+
re.specifiers.forEach(spec => {
|
|
1612
|
+
targetUses.add(spec.local || spec.exported);
|
|
1613
|
+
});
|
|
1614
|
+
if (!visited.has(targetPath)) {
|
|
1615
|
+
queue.push(targetPath);
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// ============================================================
|
|
1623
|
+
// 📊 POST-PROCESSING ANALYSIS PASS
|
|
1624
|
+
// ============================================================
|
|
1625
|
+
/**
|
|
1626
|
+
* Führt nach der Traversierung den Abgleich zwischen deklarierten und ungenutzten Exporten/Dateien durch.
|
|
1627
|
+
* @param {Object} stats Globales Analyseobjekt.
|
|
1628
|
+
* @param {Object} graphEngine Instanz der Graphen-Traversierung.
|
|
1629
|
+
*/
|
|
1630
|
+
function postProcessAnalysis(stats, graphEngine) {
|
|
1631
|
+
stats.unusedFiles = new Set();
|
|
1632
|
+
stats.unusedExportsPerFile = new Map();
|
|
1633
|
+
|
|
1634
|
+
stats.scannedFilePaths.forEach(filePath => {
|
|
1635
|
+
if (!graphEngine.reachableFiles.has(filePath)) {
|
|
1636
|
+
stats.unusedFiles.add(filePath);
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
for (const filePath of graphEngine.reachableFiles) {
|
|
1641
|
+
const meta = stats.fileSymbolMetadata?.get(filePath);
|
|
1642
|
+
if (!meta) {
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
const globalUses = graphEngine.usedSymbolsMap.get(filePath) || new Set();
|
|
1647
|
+
if (globalUses.has('*')) {
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
const unusedSet = new Set();
|
|
1652
|
+
for (const [exportName] of meta.exports.entries()) {
|
|
1653
|
+
if (!globalUses.has(exportName)) {
|
|
1654
|
+
unusedSet.add(exportName);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
if (unusedSet.size > 0) {
|
|
1658
|
+
stats.unusedExportsPerFile.set(filePath, unusedSet);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
if (stats.detectedFrameworks && stats.detectedFrameworks.includes('tailwind')) {
|
|
1663
|
+
const tailwindConfigPath = path.join(stats.targetDir, 'tailwind.config.js');
|
|
1664
|
+
if (fs.existsSync(tailwindConfigPath)) {
|
|
1665
|
+
try {
|
|
1666
|
+
const tailwindContent = fs.readFileSync(tailwindConfigPath, 'utf8');
|
|
1667
|
+
const contentArrayMatch = tailwindContent.match(/content:\s*\[([^\]]+)\]/s);
|
|
1668
|
+
if (contentArrayMatch && contentArrayMatch[1]) {
|
|
1669
|
+
const globPatterns = contentArrayMatch[1].split(',').map(s => { return s.trim().replace(/["']/g, ''); });
|
|
1670
|
+
if (globPatterns.length > 0) {
|
|
1671
|
+
console.log(` ℹ️ Tailwind Context Scan: Registered ${globPatterns.length} content matching globs vectors.`);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
} catch (tailwindFileReadException) {}
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// ============================================================
|
|
1680
|
+
// INTERACTIVE ENGINE COMMAND LINE SYSTEM
|
|
1681
|
+
// ============================================================
|
|
1682
|
+
async function main() {
|
|
1683
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
1684
|
+
console.log(`\n📦 pkg-scaffold v2.3.0: Advanced Dependency Intelligence Engine\n`);
|
|
1685
|
+
console.log(`Usage: npx pkg-scaffold [options]\n`);
|
|
1686
|
+
console.log(`Options:`);
|
|
1687
|
+
console.log(` -h, --help Show this comprehensive workspace helper panel`);
|
|
1688
|
+
process.exit(0);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
if (process.env.INIT_CWD && !process.env.NPX_CLI_JS) {
|
|
1692
|
+
console.log("\x1b[31m%s\x1b[0m", "🛑 Wait! Do not install this package locally.");
|
|
1693
|
+
console.log("Please run it directly using: \x1b[36mnpx pkg-scaffold\x1b[0m\n");
|
|
1694
|
+
process.exit(1);
|
|
1695
|
+
}
|
|
1696
|
+
const targetDir = process.cwd();
|
|
1697
|
+
const folderName = path.basename(targetDir);
|
|
1698
|
+
const gitInfo = getGitIdentity();
|
|
1699
|
+
|
|
1700
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1701
|
+
let rlClosed = false;
|
|
1702
|
+
rl.on('close', () => { rlClosed = true; });
|
|
1703
|
+
const safeQuestion = async (prompt) => {
|
|
1704
|
+
if (rlClosed || !process.stdin.readable) {
|
|
1705
|
+
return '';
|
|
1706
|
+
}
|
|
1707
|
+
try {
|
|
1708
|
+
return await rl.question(prompt);
|
|
1709
|
+
} catch (readlineQuestionPromptError) {
|
|
1710
|
+
return '';
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
|
|
1714
|
+
const stats = {
|
|
1715
|
+
tsFiles: 0,
|
|
1716
|
+
jsFiles: 0,
|
|
1717
|
+
usesEsm: false,
|
|
1718
|
+
hasHtml: false,
|
|
1719
|
+
hasTests: false,
|
|
1720
|
+
scannedFiles: 0,
|
|
1721
|
+
scannedFilePaths: [],
|
|
1722
|
+
rawDeps: new Set(),
|
|
1723
|
+
allImportedPackages: new Set(),
|
|
1724
|
+
envVars: new Set(),
|
|
1725
|
+
style: { semiCount: 0, noSemiCount: 0, tabCount: 0, space2Count: 0, space4Count: 0 },
|
|
1726
|
+
quality: {
|
|
1727
|
+
varCount: 0, hasEval: false, syncFsCount: 0, insecurePatterns: [], complexRegexes: [],
|
|
1728
|
+
insecureCryptoUsage: [], sqlInjectionVulnerabilities: [], xssVulnerabilities: [],
|
|
1729
|
+
largeImageImports: [], unoptimizedLoops: [], frameworkSpecificIssues: []
|
|
1730
|
+
},
|
|
1731
|
+
phantomInjections: new Map(),
|
|
1732
|
+
discoveredSecrets: [],
|
|
1733
|
+
insecureCodePatterns: [],
|
|
1734
|
+
subWorkspaces: [],
|
|
1735
|
+
conflictingLockfiles: [],
|
|
1736
|
+
exportedSymbols: new Map(),
|
|
1737
|
+
usedExports: new Map(),
|
|
1738
|
+
unusedFiles: new Set(),
|
|
1739
|
+
unusedExportsPerFile: new Map(),
|
|
1740
|
+
localFileImports: new Map(),
|
|
1741
|
+
unusedDepsInCode: new Set(),
|
|
1742
|
+
unusedImportsPerFile: new Map(),
|
|
1743
|
+
filesWithEnvVars: new Set(),
|
|
1744
|
+
injectDotenvEngine: false,
|
|
1745
|
+
bootstrapEslintSuite: false,
|
|
1746
|
+
ghostDependencies: new Set(),
|
|
1747
|
+
orphanedDependencies: new Set(),
|
|
1748
|
+
deprecatedPackages: new Map(),
|
|
1749
|
+
fileSymbolMetadata: new Map(),
|
|
1750
|
+
frameworkFiles: {
|
|
1751
|
+
nextjs: { pages: new Set(), apiRoutes: new Set(), components: new Set(), dataFetching: new Map(), optimizations: [] },
|
|
1752
|
+
nuxt: { pages: new Set(), components: new Set(), modules: new Set(), dataFetching: new Map(), optimizations: [] },
|
|
1753
|
+
sveltekit: { pages: new Set(), components: new Set(), endpoints: new Set(), loadFunctions: new Map(), optimizations: [] },
|
|
1754
|
+
react: { hooks: new Set(), components: new Set(), optimizations: [] },
|
|
1755
|
+
vue: { composables: new Set(), components: new Set(), optimizations: [] },
|
|
1756
|
+
},
|
|
1757
|
+
frameworkOptimizations: [],
|
|
1758
|
+
packageJson: null,
|
|
1759
|
+
targetDir: targetDir,
|
|
1760
|
+
detectedFrameworks: []
|
|
1761
|
+
};
|
|
1762
|
+
|
|
1763
|
+
const activePkgManager = detectPackageManager(targetDir, stats);
|
|
1764
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
1765
|
+
let preExistingLicense = null, preExistingDeps = [], preExistingDevDeps = [], existingPackageJson = null;
|
|
1766
|
+
let detectedFrameworks = [];
|
|
1767
|
+
|
|
1768
|
+
console.log(`\n${'═'.repeat(67)}`);
|
|
1769
|
+
console.log(`🚀 pkg-scaffold v2.3.0: Enterprise Graph Intelligence Analyzer`);
|
|
1770
|
+
console.log(`${'═'.repeat(67)}\n`);
|
|
1771
|
+
|
|
1772
|
+
const topLevelItems = fs.readdirSync(targetDir);
|
|
1773
|
+
const potentialSubModules = [];
|
|
1774
|
+
for (const item of topLevelItems) {
|
|
1775
|
+
const fullPath = path.join(targetDir, item);
|
|
1776
|
+
if (!IGNORED_DIRS.has(item) && !item.startsWith('.') && fs.statSync(fullPath).isDirectory()) {
|
|
1777
|
+
let containsSourceCode = false;
|
|
1778
|
+
const examineDirectory = (d) => {
|
|
1779
|
+
try {
|
|
1780
|
+
const subEntries = fs.readdirSync(d);
|
|
1781
|
+
for (const entry of subEntries) {
|
|
1782
|
+
const entryPath = path.join(d, entry);
|
|
1783
|
+
if (fs.statSync(entryPath).isDirectory()) {
|
|
1784
|
+
if (!IGNORED_DIRS.has(entry) && !entry.startsWith('.')) {
|
|
1785
|
+
examineDirectory(entryPath);
|
|
1786
|
+
}
|
|
1787
|
+
} else if (VALID_EXTENSIONS.has(path.extname(entry))) {
|
|
1788
|
+
containsSourceCode = true;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
} catch (subDirReadError) {}
|
|
1792
|
+
};
|
|
1793
|
+
examineDirectory(fullPath);
|
|
1794
|
+
if (containsSourceCode) {
|
|
1795
|
+
potentialSubModules.push(item);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
if (potentialSubModules.length > 1) {
|
|
1800
|
+
stats.subWorkspaces = potentialSubModules;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
if (fs.existsSync(pkgPath)) {
|
|
1804
|
+
console.log(`⚠️ An existing package.json was found in this working directory.`);
|
|
1805
|
+
console.log(`📡 Analyzing existing installation arrays for invalid metrics...`);
|
|
1806
|
+
try {
|
|
1807
|
+
existingPackageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
1808
|
+
stats.packageJson = existingPackageJson;
|
|
1809
|
+
if (existingPackageJson.license && typeof existingPackageJson.license === 'string' && existingPackageJson.license.toLowerCase() !== 'none') {
|
|
1810
|
+
preExistingLicense = existingPackageJson.license;
|
|
1811
|
+
}
|
|
1812
|
+
if (existingPackageJson.dependencies) {
|
|
1813
|
+
preExistingDeps = Object.keys(existingPackageJson.dependencies);
|
|
1814
|
+
}
|
|
1815
|
+
if (existingPackageJson.devDependencies) {
|
|
1816
|
+
preExistingDevDeps = Object.keys(existingPackageJson.devDependencies);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
detectedFrameworks = FrameworkEngine.detect(targetDir, existingPackageJson);
|
|
1820
|
+
stats.detectedFrameworks = detectedFrameworks;
|
|
1821
|
+
|
|
1822
|
+
const combinedDeps = [...preExistingDeps, ...preExistingDevDeps];
|
|
1823
|
+
let brokenEcosystem = false;
|
|
1824
|
+
|
|
1825
|
+
if (combinedDeps.length > 0) {
|
|
1826
|
+
console.log(` 🔍 Validating ${combinedDeps.length} declared package(s) against npm registry...`);
|
|
1827
|
+
for (const dep of combinedDeps) {
|
|
1828
|
+
const check = await inspectNpmPackage(dep);
|
|
1829
|
+
|
|
1830
|
+
if (check && check.error === 'NOT_FOUND') {
|
|
1831
|
+
if (dep.startsWith('@')) {
|
|
1832
|
+
console.log(` ℹ️ Scoped or private module bypassed registry assertion: "${dep}"`);
|
|
1833
|
+
} else {
|
|
1834
|
+
brokenEcosystem = true;
|
|
1835
|
+
console.log(` ❌ Non-existent package on registry: "${dep}"`);
|
|
1836
|
+
}
|
|
1837
|
+
} else if (check && check.deprecated) {
|
|
1838
|
+
stats.deprecatedPackages.set(dep, check.deprecated);
|
|
1839
|
+
console.log(` ⚠️ Deprecated package detected: "${dep}" — ${check.deprecated}`);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (brokenEcosystem) {
|
|
1844
|
+
console.log(`\n🛑 CRITICAL COMPLIANCE BREAK: Your current package.json contains non-existent packages.`);
|
|
1845
|
+
console.log(`👉 Action Required: Please remove or backup the existing 'package.json' from this folder.\n`);
|
|
1846
|
+
rl.close();
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
} catch (packageJsonParseRuntimeError) {
|
|
1850
|
+
console.log(`\n🛑 CRITICAL: Existing package.json is malformed or corrupt.\n`);
|
|
1851
|
+
rl.close();
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
console.log(`\n🔬 Scanning workspace source files...`);
|
|
1857
|
+
scanWorkspace(targetDir, stats, folderName, detectedFrameworks);
|
|
1858
|
+
console.log(` Janitor: Found ${stats.scannedFiles} source module assets.`);
|
|
1859
|
+
|
|
1860
|
+
// --- TRIGGER RE-ARCHITECTED KNIP GRAPH INTELLIGENCE ---
|
|
1861
|
+
const graphEngine = new KnipEcosystemGraph(stats);
|
|
1862
|
+
const initialEntries = new Set();
|
|
1863
|
+
const baseEntryFallbacks = ['index.js', 'index.ts', 'src/index.js', 'src/index.ts', 'main.js', 'src/main.js', 'src/main.ts'];
|
|
1864
|
+
baseEntryFallbacks.forEach(f => {
|
|
1865
|
+
const absolutePath = path.join(targetDir, f);
|
|
1866
|
+
if (fs.existsSync(absolutePath)) {
|
|
1867
|
+
initialEntries.add(absolutePath);
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
|
|
1871
|
+
if (existingPackageJson) {
|
|
1872
|
+
if (existingPackageJson.main) {
|
|
1873
|
+
initialEntries.add(path.resolve(targetDir, existingPackageJson.main));
|
|
1874
|
+
}
|
|
1875
|
+
if (existingPackageJson.module) {
|
|
1876
|
+
initialEntries.add(path.resolve(targetDir, existingPackageJson.module));
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
stats.frameworkFiles.nextjs.pages.forEach(file => { return initialEntries.add(file); });
|
|
1881
|
+
stats.frameworkFiles.nextjs.apiRoutes.forEach(file => { return initialEntries.add(file); });
|
|
1882
|
+
stats.frameworkFiles.nuxt.pages.forEach(file => { return initialEntries.add(file); });
|
|
1883
|
+
stats.frameworkFiles.sveltekit.pages.forEach(file => { return initialEntries.add(file); });
|
|
1884
|
+
|
|
1885
|
+
graphEngine.traceReachability(Array.from(initialEntries));
|
|
1886
|
+
postProcessAnalysis(stats, graphEngine);
|
|
1887
|
+
|
|
1888
|
+
const binariesInScripts = existingPackageJson ? getBinariesFromPackageJson(existingPackageJson) : [];
|
|
1889
|
+
const resolvedBinaryPackages = new Set();
|
|
1890
|
+
for (const binary of binariesInScripts) {
|
|
1891
|
+
const pkgName = BINARY_TO_PACKAGE_MAP[binary] || binary;
|
|
1892
|
+
resolvedBinaryPackages.add(pkgName);
|
|
1893
|
+
stats.rawDeps.add(pkgName);
|
|
1894
|
+
stats.allImportedPackages.add(pkgName);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
if (preExistingDeps.length > 0 || preExistingDevDeps.length > 0) {
|
|
1898
|
+
stats.ghostDependencies = detectGhostDependencies(stats.allImportedPackages, preExistingDeps, preExistingDevDeps);
|
|
1899
|
+
for (const dep of stats.ghostDependencies) {
|
|
1900
|
+
if (DEV_TOOLING_ECOSYSTEM.has(dep) || dep.startsWith('@types/')) {
|
|
1901
|
+
stats.ghostDependencies.delete(dep);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
if (preExistingDeps.length > 0) {
|
|
1907
|
+
stats.orphanedDependencies = detectOrphanedDependencies(preExistingDeps, stats.allImportedPackages, resolvedBinaryPackages, DEV_TOOLING_ECOSYSTEM);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
if (stats.ghostDependencies.size > 0) {
|
|
1911
|
+
console.log(`\n${'─'.repeat(67)}`);
|
|
1912
|
+
console.log(`👻 GHOST DEPENDENCIES DETECTED (Used but not in package.json)`);
|
|
1913
|
+
console.log(`${'─'.repeat(67)}`);
|
|
1914
|
+
for (const pkg of stats.ghostDependencies) {
|
|
1915
|
+
console.log(` 🚫 \x1b[31m"${pkg}"\x1b[0m — imported but missing from declarations.`);
|
|
1916
|
+
}
|
|
1917
|
+
console.log(`${'─'.repeat(67)}`);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
if (stats.unusedFiles.size > 0) {
|
|
1921
|
+
console.log(`\n${'─'.repeat(67)}`);
|
|
1922
|
+
console.log(`💀 ORPHANED FILES DETECTED (Never reached from entrypoints)`);
|
|
1923
|
+
console.log(`${'─'.repeat(67)}`);
|
|
1924
|
+
stats.unusedFiles.forEach(f => {
|
|
1925
|
+
console.log(` 💀 \x1b[31m"${path.relative(targetDir, f)}"\x1b[0m — file never mapped or imported.`);
|
|
1926
|
+
});
|
|
1927
|
+
console.log(`${'─'.repeat(67)}`);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
if (stats.unusedExportsPerFile.size > 0) {
|
|
1931
|
+
console.log(`\n${'─'.repeat(67)}`);
|
|
1932
|
+
console.log(`📤 UNUSED EXPORTS DETECTED (Dead Public API Symbols)`);
|
|
1933
|
+
console.log(`${'─'.repeat(67)}`);
|
|
1934
|
+
for (const [file, symbols] of stats.unusedExportsPerFile.entries()) {
|
|
1935
|
+
console.log(` ⚡ \x1b[33m"${path.relative(targetDir, file)}"\x1b[0m -> Dead Token(s): [ ${Array.from(symbols).join(', ')} ]`);
|
|
1936
|
+
}
|
|
1937
|
+
console.log(`${'─'.repeat(67)}`);
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
if (stats.orphanedDependencies.size > 0) {
|
|
1941
|
+
console.log(`\n${'─'.repeat(67)}`);
|
|
1942
|
+
console.log(`📦 ORPHANED DEPENDENCIES DETECTED (in package.json, never imported)`);
|
|
1943
|
+
console.log(`${'─'.repeat(67)}`);
|
|
1944
|
+
for (const pkg of stats.orphanedDependencies) {
|
|
1945
|
+
console.log(` 🗑️ \x1b[33m"${pkg}"\x1b[0m — declared but never imported`);
|
|
1946
|
+
}
|
|
1947
|
+
console.log(`${'─'.repeat(67)}`);
|
|
1948
|
+
const pruneOrphans = await safeQuestion(`❓ Remove these orphaned packages from package.json? (y/N): `);
|
|
1949
|
+
if (pruneOrphans.trim().toLowerCase() === 'y' || pruneOrphans.trim().toLowerCase() === 'yes') {
|
|
1950
|
+
if (existingPackageJson) {
|
|
1951
|
+
for (const pkg of stats.orphanedDependencies) {
|
|
1952
|
+
delete existingPackageJson.dependencies?.[pkg];
|
|
1953
|
+
}
|
|
1954
|
+
fs.writeFileSync(pkgPath, JSON.stringify(existingPackageJson, null, 2));
|
|
1955
|
+
console.log(` 🗑️ Orphaned dependencies removed from package.json.`);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
if (stats.deprecatedPackages.size > 0) {
|
|
1961
|
+
console.log(`\n${'─'.repeat(67)}`);
|
|
1962
|
+
console.log(`⚠️ DEPRECATED PACKAGES DETECTED`);
|
|
1963
|
+
console.log(`${'─'.repeat(67)}`);
|
|
1964
|
+
for (const [pkg, msg] of stats.deprecatedPackages.entries()) {
|
|
1965
|
+
console.log(` 📛 \x1b[33m"${pkg}"\x1b[0m — ${msg}`);
|
|
1966
|
+
}
|
|
1967
|
+
console.log(`${'─'.repeat(67)}`);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
const allDeclaredForPhantom = new Set([...preExistingDeps, ...preExistingDevDeps]);
|
|
1971
|
+
const phantomScanContent = new Map();
|
|
1972
|
+
function collectExecutionContent(dir) {
|
|
1973
|
+
try {
|
|
1974
|
+
for (const file of fs.readdirSync(dir)) {
|
|
1975
|
+
const fullPath = path.join(dir, file);
|
|
1976
|
+
const stat = fs.statSync(fullPath);
|
|
1977
|
+
if (stat.isDirectory() && !IGNORED_DIRS.has(file) && !file.startsWith('.')) {
|
|
1978
|
+
collectExecutionContent(fullPath);
|
|
1979
|
+
} else if (VALID_EXTENSIONS.has(path.extname(file))) {
|
|
1980
|
+
try {
|
|
1981
|
+
const content = readFileSyncNormalized(fullPath);
|
|
1982
|
+
const execCode = content.split(/\r?\n/).filter(l => {
|
|
1983
|
+
const t = l.trim();
|
|
1984
|
+
return !t.startsWith('import ') && !/\brequire\s*\(/.test(t);
|
|
1985
|
+
}).join('\n');
|
|
1986
|
+
phantomScanContent.set(fullPath, execCode);
|
|
1987
|
+
} catch (readExecContentError) {}
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
} catch (fsCollectExecError) {}
|
|
1991
|
+
}
|
|
1992
|
+
collectExecutionContent(targetDir);
|
|
1993
|
+
|
|
1994
|
+
for (const [filePath, execCode] of phantomScanContent.entries()) {
|
|
1995
|
+
for (const token of allDeclaredForPhantom) {
|
|
1996
|
+
const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1997
|
+
if (new RegExp(`\\b${escaped}\\b`).test(execCode) && !stats.allImportedPackages.has(token)) {
|
|
1998
|
+
stats.rawDeps.add(token);
|
|
1999
|
+
if (!stats.phantomInjections.has(filePath)) {
|
|
2000
|
+
stats.phantomInjections.set(filePath, new Set());
|
|
2001
|
+
}
|
|
2002
|
+
stats.phantomInjections.get(filePath).add(token);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
const isTypeScript = stats.tsFiles > stats.jsFiles;
|
|
2008
|
+
const isFrontendWeb = stats.hasHtml || stats.rawDeps.has('react') || stats.rawDeps.has('vue') || stats.rawDeps.has('vite') || stats.rawDeps.has('svelte') || stats.rawDeps.has('next') || stats.rawDeps.has('nuxt');
|
|
2009
|
+
|
|
2010
|
+
if (stats.envVars.size > 0 && !stats.rawDeps.has('dotenv') && !isFrontendWeb) {
|
|
2011
|
+
console.log(`\n📡 CONFIGURATION COMPLIANCE GAP: UNMANAGED ENVIRONMENT VARIABLES`);
|
|
2012
|
+
console.log(`${'─'.repeat(67)}`);
|
|
2013
|
+
console.log(` Workspace utilizes 'process.env' variables but 'dotenv' is missing.`);
|
|
2014
|
+
console.log(`${'─'.repeat(67)}`);
|
|
2015
|
+
const choiceEnv = await safeQuestion(`❓ Add 'dotenv' and automatically wire initialization hooks into your files? (Y/n): `);
|
|
2016
|
+
if (choiceEnv.trim().toLowerCase() !== 'n' && choiceEnv.trim().toLowerCase() !== 'no') {
|
|
2017
|
+
stats.rawDeps.add('dotenv');
|
|
2018
|
+
stats.injectDotenvEngine = true;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
const packageJson = {
|
|
2023
|
+
name: folderName.toLowerCase().replace(/[^a-z0-9-_]/g, '-'),
|
|
2024
|
+
version: '1.0.0',
|
|
2025
|
+
description: `Automated ${isFrontendWeb ? 'frontend layout application' : 'backend infrastructure runtime'}.`,
|
|
2026
|
+
type: (stats.usesEsm || isTypeScript || isFrontendWeb) ? 'module' : 'commonjs',
|
|
2027
|
+
author: gitInfo.author || undefined,
|
|
2028
|
+
repository: gitInfo.repository ? { type: "git", url: `git+${gitInfo.repository}.git` } : undefined,
|
|
2029
|
+
scripts: { test: stats.hasTests ? (isFrontendWeb ? 'vitest' : 'jest') : 'echo "No workspace test vectors specified" && exit 0' },
|
|
2030
|
+
dependencies: {},
|
|
2031
|
+
devDependencies: {}
|
|
2032
|
+
};
|
|
2033
|
+
|
|
2034
|
+
const eslintConfigFile = path.join(targetDir, 'eslint.config.js');
|
|
2035
|
+
const linterPresent = fs.existsSync(eslintConfigFile) || fs.existsSync(path.join(targetDir, '.eslintrc.json')) || fs.existsSync(path.join(targetDir, '.eslintrc.js'));
|
|
2036
|
+
|
|
2037
|
+
if (!linterPresent && (stats.quality.varCount > 0 || stats.quality.hasEval || stats.phantomInjections.size > 0)) {
|
|
2038
|
+
console.log(`\n🎨 QUALITY LAYER AUDITOR: SYNTAX VALIDATION SYSTEM REQUIRED`);
|
|
2039
|
+
console.log(`${'─'.repeat(67)}`);
|
|
2040
|
+
console.log(` Code anomalies (legacy 'var' or 'eval()') require static linter guards.`);
|
|
2041
|
+
console.log(`${'─'.repeat(67)}`);
|
|
2042
|
+
const choiceLintSetup = await safeQuestion(`❓ Bootstrap standard ESLint flat verification rules into workspace? (Y/n): `);
|
|
2043
|
+
if (choiceLintSetup.trim().toLowerCase() !== 'n' && choiceLintSetup.trim().toLowerCase() !== 'no') {
|
|
2044
|
+
stats.bootstrapEslintSuite = true;
|
|
2045
|
+
stats.rawDeps.add('eslint');
|
|
2046
|
+
if (isTypeScript) {
|
|
2047
|
+
stats.rawDeps.add('typescript-eslint');
|
|
2048
|
+
} else {
|
|
2049
|
+
stats.rawDeps.add('@eslint/js');
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
if (isFrontendWeb) {
|
|
2055
|
+
packageJson.scripts.dev = 'vite';
|
|
2056
|
+
packageJson.scripts.build = 'vite build';
|
|
2057
|
+
packageJson.scripts.preview = 'vite preview';
|
|
2058
|
+
stats.rawDeps.add('vite');
|
|
2059
|
+
if (stats.hasTests) {
|
|
2060
|
+
stats.rawDeps.add('vitest');
|
|
2061
|
+
}
|
|
2062
|
+
} else {
|
|
2063
|
+
if (isTypeScript) {
|
|
2064
|
+
packageJson.scripts.build = 'tsc';
|
|
2065
|
+
packageJson.scripts.start = 'node dist/index.js';
|
|
2066
|
+
packageJson.scripts.dev = 'node --watch dist/index.js';
|
|
2067
|
+
} else {
|
|
2068
|
+
packageJson.scripts.start = 'node index.js';
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
if (isTypeScript) {
|
|
2073
|
+
packageJson.devDependencies.typescript = '^5.4.0';
|
|
2074
|
+
if (!isFrontendWeb) {
|
|
2075
|
+
packageJson.devDependencies['@types/node'] = '^20.11.0';
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
if (stats.rawDeps.size > 0) {
|
|
2080
|
+
console.log(`\n📡 Resolving baseline package registry definitions...`);
|
|
2081
|
+
for (const pkg of stats.rawDeps) {
|
|
2082
|
+
const cleaned = cleanPackageName(pkg);
|
|
2083
|
+
if (cleaned && !builtinModules.includes(cleaned)) {
|
|
2084
|
+
const check = await inspectNpmPackage(cleaned);
|
|
2085
|
+
if (check && check.error !== 'NOT_FOUND') {
|
|
2086
|
+
const version = check.version || 'latest';
|
|
2087
|
+
const isDevDep = ['vite', 'vitest', 'typescript', 'eslint', 'typescript-eslint', '@eslint/js', 'prettier', 'jest', 'nodemon', 'ts-node', 'tsup', 'esbuild', '@swc/cli', 'tsx', 'rimraf', 'copyfiles', 'mkdirp', 'husky', 'lint-staged', '@commitlint/cli', 'typedoc', 'c8', 'nyc', 'mocha', 'ava', 'tap', 'jasmine', 'storybook', 'turbo', 'nx', 'biome', '@biomejs/biome', 'oxlint', 'xo', 'standard'].includes(cleaned) || cleaned.startsWith('@types/');
|
|
2088
|
+
if (isDevDep) {
|
|
2089
|
+
packageJson.devDependencies[cleaned] = `^${version}`;
|
|
2090
|
+
} else {
|
|
2091
|
+
packageJson.dependencies[cleaned] = `^${version}`;
|
|
2092
|
+
}
|
|
2093
|
+
console.log(` ✔ Synced: ${cleaned}@^${version}${check.deprecated ? ' \x1b[33m[DEPRECATED]\x1b[0m' : ''}`);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
if (stats.phantomInjections.size > 0) {
|
|
2100
|
+
console.log(`\n${'─'.repeat(67)}`);
|
|
2101
|
+
console.log(`👻 PHANTOM STRUCTURE ALERT: UNIMPORTED EXECUTIONS DETECTED`);
|
|
2102
|
+
console.log(`${'─'.repeat(67)}`);
|
|
2103
|
+
for (const [filePath, missingModules] of stats.phantomInjections.entries()) {
|
|
2104
|
+
console.log(`📂 File: ${path.relative(targetDir, filePath)}`);
|
|
2105
|
+
console.log(` ❌ Used but never imported: ${Array.from(missingModules).map(m => { return `"${m}"`; }).join(', ')}`);
|
|
2106
|
+
}
|
|
2107
|
+
console.log(`${'─'.repeat(67)}`);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
if (stats.quality.varCount > 0 || stats.quality.hasEval || stats.quality.syncFsCount > 0) {
|
|
2111
|
+
console.log(`\n⚠️ CODE ARCHITECTURE & MODERNIZATION COMPLIANCE WARNINGS:`);
|
|
2112
|
+
console.log(`${'─'.repeat(67)}`);
|
|
2113
|
+
if (stats.quality.varCount > 0) {
|
|
2114
|
+
console.log(` ⚡ Found ${stats.quality.varCount} instances of legacy 'var'. Transition to 'let' / 'const'.`);
|
|
2115
|
+
}
|
|
2116
|
+
if (stats.quality.hasEval) {
|
|
2117
|
+
console.log(` 🔥 DANGER: 'eval()' detected! Refactor to mitigate remote code execution vectors.`);
|
|
2118
|
+
}
|
|
2119
|
+
if (stats.quality.syncFsCount > 0) {
|
|
2120
|
+
console.log(` 📉 Performance: Found ${stats.quality.syncFsCount} synchronous fs calls. Transition to 'fs/promises'.`);
|
|
2121
|
+
}
|
|
2122
|
+
console.log(`${'─'.repeat(67)}`);
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
if (stats.discoveredSecrets.length > 0) {
|
|
2126
|
+
console.log(`\n🚨 CRITICAL SECURITY COMPLIANCE ALERT: HARDCODED CREDENTIALS DETECTED`);
|
|
2127
|
+
console.log(`${'─'.repeat(67)}`);
|
|
2128
|
+
for (const secretMeta of stats.discoveredSecrets) {
|
|
2129
|
+
console.log(`📂 File: ${path.relative(targetDir, secretMeta.filePath)}`);
|
|
2130
|
+
console.log(` ⚠️ Hardcoded credential found: [${secretMeta.keyName}]`);
|
|
2131
|
+
}
|
|
2132
|
+
console.log(`${'─'.repeat(67)}`);
|
|
2133
|
+
|
|
2134
|
+
const fixSecrets = await safeQuestion(`❓ Automatically extract credentials into environment mappings safely? (y/N): `);
|
|
2135
|
+
if (fixSecrets.trim().toLowerCase() === 'y' || fixSecrets.trim().toLowerCase() === 'yes') {
|
|
2136
|
+
const envPath = path.join(targetDir, '.env');
|
|
2137
|
+
let envBuffer = fs.existsSync(envPath) ? readFileSyncNormalized(envPath) : '';
|
|
2138
|
+
|
|
2139
|
+
for (const secretMeta of stats.discoveredSecrets) {
|
|
2140
|
+
let currentCodeContent = readFileSyncNormalized(secretMeta.filePath);
|
|
2141
|
+
const envAccessor = isFrontendWeb ? `import.meta.env.${secretMeta.envVarName}` : `process.env.${secretMeta.envVarName}`;
|
|
2142
|
+
const exactLiteralPattern = new RegExp(`\\b${secretMeta.keyName}\\s*=\\s*['"\\ ]${secretMeta.secretValue.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}['"\\ ]`, 'g');
|
|
2143
|
+
currentCodeContent = currentCodeContent.replace(exactLiteralPattern, `${secretMeta.keyName} = ${envAccessor}`);
|
|
2144
|
+
fs.writeFileSync(secretMeta.filePath, currentCodeContent);
|
|
2145
|
+
if (!envBuffer.includes(`${secretMeta.envVarName}=`)) {
|
|
2146
|
+
envBuffer += `${secretMeta.envVarName}=${secretMeta.secretValue}\n`;
|
|
2147
|
+
}
|
|
2148
|
+
console.log(` 🔒 Isolated: ${secretMeta.keyName} → ${envAccessor}`);
|
|
2149
|
+
}
|
|
2150
|
+
fs.writeFileSync(envPath, envBuffer);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
if (stats.subWorkspaces && stats.subWorkspaces.length > 1) {
|
|
2155
|
+
console.log(`\n📂 MULTI-WORKSPACE SEGMENTATION DETECTED`);
|
|
2156
|
+
console.log(` Identified sub-module paths: ${stats.subWorkspaces.map(w => { return `/${w}`; }).join(', ')}`);
|
|
2157
|
+
const setupWorkspace = await safeQuestion(`❓ Setup as a multi-package Monorepo Workspace layout? (y/N): `);
|
|
2158
|
+
if (setupWorkspace.trim().toLowerCase() === 'y' || setupWorkspace.trim().toLowerCase() === 'yes') {
|
|
2159
|
+
if (activePkgManager === 'pnpm') {
|
|
2160
|
+
fs.writeFileSync(path.join(targetDir, 'pnpm-workspace.yaml'), `packages:\n${stats.subWorkspaces.map(w => { return ` - '${w}'`; }).join('\n')}\n`);
|
|
2161
|
+
console.log(` 🏗️ Generated: pnpm-workspace.yaml`);
|
|
2162
|
+
} else {
|
|
2163
|
+
packageJson.workspaces = stats.subWorkspaces;
|
|
2164
|
+
console.log(` 🏗️ Injected 'workspaces' into root package.json.`);
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
const licensePath = path.join(targetDir, 'LICENSE');
|
|
2170
|
+
let chosenLicenseType = preExistingLicense || 'None';
|
|
2171
|
+
|
|
2172
|
+
if (!fs.existsSync(licensePath) && !preExistingLicense) {
|
|
2173
|
+
console.log(`\n⚖️ Legal Compliance Auditor: No LICENSE file located.`);
|
|
2174
|
+
const licInput = await safeQuestion(`❓ Enter Open Source License (e.g. MIT, Apache-2.0, ISC, BSD-3-Clause, skip): `);
|
|
2175
|
+
const cleanedInput = licInput.trim();
|
|
2176
|
+
if (cleanedInput.toLowerCase() !== 'skip' && cleanedInput.toLowerCase() !== 'none' && cleanedInput !== '') {
|
|
2177
|
+
console.log(` 📡 Querying GitHub Legal Databases for "${cleanedInput.toUpperCase()}"...`);
|
|
2178
|
+
const rawTemplate = await fetchRemoteLicense(cleanedInput);
|
|
2179
|
+
if (rawTemplate) {
|
|
2180
|
+
const parsedText = rawTemplate.replace(/\[year\]|<year>/gi, new Date().getFullYear().toString()).replace(/\[fullname\]|\[name of copyright owner\]|<copyright holders>|<name of author>/gi, gitInfo.name);
|
|
2181
|
+
fs.writeFileSync(licensePath, parsedText);
|
|
2182
|
+
chosenLicenseType = cleanedInput.toUpperCase();
|
|
2183
|
+
console.log(` ⚖️ Provisioned: LICENSE`);
|
|
2184
|
+
} else {
|
|
2185
|
+
chosenLicenseType = cleanedInput;
|
|
2186
|
+
}
|
|
2187
|
+
packageJson.license = chosenLicenseType;
|
|
2188
|
+
}
|
|
2189
|
+
} else {
|
|
2190
|
+
if (preExistingLicense) {
|
|
2191
|
+
chosenLicenseType = preExistingLicense;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
if (existingPackageJson) {
|
|
2196
|
+
console.log(`\n${'─'.repeat(67)}`);
|
|
2197
|
+
console.log(`📡 UPDATE MODE: Merging newly discovered intelligence into existing package.json...`);
|
|
2198
|
+
const finalPackageJson = { ...existingPackageJson };
|
|
2199
|
+
finalPackageJson.dependencies = { ...existingPackageJson.dependencies, ...packageJson.dependencies };
|
|
2200
|
+
finalPackageJson.devDependencies = { ...existingPackageJson.devDependencies, ...packageJson.devDependencies };
|
|
2201
|
+
|
|
2202
|
+
if (stats.injectDotenvEngine) {
|
|
2203
|
+
const indexFile = isTypeScript ? 'src/index.ts' : 'index.js';
|
|
2204
|
+
const indexPath = path.join(targetDir, indexFile);
|
|
2205
|
+
if (fs.existsSync(indexPath)) {
|
|
2206
|
+
const currentContent = fs.readFileSync(indexPath, 'utf8');
|
|
2207
|
+
if (!currentContent.includes('dotenv')) {
|
|
2208
|
+
const dotenvBlock = stats.usesEsm ? "import 'dotenv/config';\n" : "require('dotenv').config();\n";
|
|
2209
|
+
fs.writeFileSync(indexPath, smartPrepend(currentContent, dotenvBlock));
|
|
2210
|
+
console.log(` 🔌 Wired: dotenv hooks into ${indexFile}`);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
if (stats.bootstrapEslintSuite) {
|
|
2216
|
+
const eslintConfig = isTypeScript ?
|
|
2217
|
+
`import tseslint from 'typescript-eslint';\n\nexport default tseslint.config(\n ...tseslint.configs.recommended,\n);` :
|
|
2218
|
+
`import js from '@eslint/js';\n\nexport default [\n js.configs.recommended,\n];`;
|
|
2219
|
+
fs.writeFileSync(eslintConfigFile, eslintConfig);
|
|
2220
|
+
console.log(` 🎨 Provisioned: eslint.config.js`);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
fs.writeFileSync(pkgPath, JSON.stringify(finalPackageJson, null, 2));
|
|
2224
|
+
} else {
|
|
2225
|
+
console.log(`\n${'─'.repeat(67)}`);
|
|
2226
|
+
console.log(`📡 INITIALIZATION MODE: Provisioning new workspace infrastructure...`);
|
|
2227
|
+
const confirmInit = await safeQuestion(`❓ Proceed with workspace initialization and dependency sync? (Y/n): `);
|
|
2228
|
+
if (confirmInit.trim().toLowerCase() !== 'n' && confirmInit.trim().toLowerCase() !== 'no') {
|
|
2229
|
+
fs.writeFileSync(pkgPath, JSON.stringify(packageJson, null, 2));
|
|
2230
|
+
console.log(` 📦 Generated: package.json`);
|
|
2231
|
+
|
|
2232
|
+
if (isTypeScript) {
|
|
2233
|
+
const tsconfig = { compilerOptions: { target: "ESNext", module: "NodeNext", outDir: "dist", strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true } };
|
|
2234
|
+
fs.writeFileSync(path.join(targetDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
|
|
2235
|
+
console.log(` ⚙️ Generated: tsconfig.json`);
|
|
2236
|
+
fs.mkdirSync(path.join(targetDir, 'src'), { recursive: true });
|
|
2237
|
+
fs.writeFileSync(path.join(targetDir, 'src/index.ts'), '// Entrypoint\n');
|
|
2238
|
+
} else if (isFrontendWeb) {
|
|
2239
|
+
fs.writeFileSync(path.join(targetDir, 'index.html'), `<!DOCTYPE html><html><head><title>${folderName}</title></head><body><div id="app"></div><script type="module" src="/src/main.js"></script></body></html>`);
|
|
2240
|
+
fs.mkdirSync(path.join(targetDir, 'src'), { recursive: true });
|
|
2241
|
+
fs.writeFileSync(path.join(targetDir, 'src/main.js'), '// Entrypoint\n');
|
|
2242
|
+
} else {
|
|
2243
|
+
fs.writeFileSync(path.join(targetDir, 'index.js'), '// Entrypoint\n');
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
console.log(`\n${'═'.repeat(67)}`);
|
|
2249
|
+
console.log(`✅ ANALYSIS COMPLETE: Workspace reached Enterprise Compliance.`);
|
|
2250
|
+
console.log(`${'═'.repeat(67)}\n`);
|
|
2251
|
+
rl.close();
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
main();
|