scss-variable-extractor 1.6.4 ā 1.6.5
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/.prettierignore +7 -0
- package/.prettierrc.json +18 -0
- package/.scssextractrc.example.json +31 -35
- package/README.md +1015 -778
- package/THEME-GUIDE.md +289 -0
- package/bin/cli.js +807 -652
- package/jest.config.js +7 -12
- package/package.json +49 -45
- package/src/analyzer.js +285 -285
- package/src/angular-parser.js +383 -381
- package/src/bootstrap-migrator.js +694 -661
- package/src/config.js +87 -91
- package/src/generator.js +220 -219
- package/src/index.js +37 -29
- package/src/ng-refactorer.js +654 -578
- package/src/parser.js +424 -421
- package/src/refactorer.js +329 -322
- package/src/scanner.js +63 -55
- package/src/style-organizer.js +500 -504
- package/src/theme-utils.js +432 -0
- package/test/analyzer.test.js +107 -107
- package/test/angular-parser.test.js +230 -230
- package/test/bootstrap-migrator.test.js +230 -213
- package/test/generator.test.js +139 -149
- package/test/ng-refactorer-global.test.js +140 -0
- package/test/ng-refactorer.test.js +191 -184
- package/test/parser.test.js +131 -131
- package/test/refactorer-edge-cases.test.js +385 -353
- package/test/refactorer.test.js +277 -257
- package/test/scanner.test.js +34 -32
- package/test/style-organizer.test.js +106 -106
- package/test/theme-utils.test.js +140 -0
package/bin/cli.js
CHANGED
|
@@ -1,652 +1,807 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { Command } = require('commander');
|
|
4
|
-
const chalk = require('chalk');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const { loadConfig } = require('../src/config');
|
|
7
|
-
const { scanScssFiles, scanTemplateFiles } = require('../src/scanner');
|
|
8
|
-
const { parseScss } = require('../src/parser');
|
|
9
|
-
const { analyzeValues } = require('../src/analyzer');
|
|
10
|
-
const { generateVariablesFile, generateReport } = require('../src/generator');
|
|
11
|
-
const { refactorScssFiles } = require('../src/refactorer');
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.
|
|
29
|
-
.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
console.log(chalk.
|
|
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
|
-
console.log(chalk.
|
|
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
|
-
console.log(chalk.
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
console.log(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
console.log(
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
console.log(chalk.
|
|
617
|
-
console.log(chalk.gray(
|
|
618
|
-
console.log(
|
|
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
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { loadConfig } = require('../src/config');
|
|
7
|
+
const { scanScssFiles, scanTemplateFiles } = require('../src/scanner');
|
|
8
|
+
const { parseScss } = require('../src/parser');
|
|
9
|
+
const { analyzeValues } = require('../src/analyzer');
|
|
10
|
+
const { generateVariablesFile, generateReport } = require('../src/generator');
|
|
11
|
+
const { refactorScssFiles } = require('../src/refactorer');
|
|
12
|
+
const {
|
|
13
|
+
analyzeAngularPatterns,
|
|
14
|
+
refactorAngularPatterns,
|
|
15
|
+
generateAngularPatternReport,
|
|
16
|
+
} = require('../src/ng-refactorer');
|
|
17
|
+
const {
|
|
18
|
+
detectBootstrap,
|
|
19
|
+
migrateBootstrapToMaterial,
|
|
20
|
+
generateBootstrapReport,
|
|
21
|
+
} = require('../src/bootstrap-migrator');
|
|
22
|
+
const { analyzeStyleOrganization, generateOrganizationReport } = require('../src/style-organizer');
|
|
23
|
+
const { generateThemeStructure, analyzeThemeReadiness } = require('../src/theme-utils');
|
|
24
|
+
|
|
25
|
+
const program = new Command();
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.name('scss-extract')
|
|
29
|
+
.description(
|
|
30
|
+
'Analyzes Angular SCSS files and extracts repeated hardcoded values into reusable variables'
|
|
31
|
+
)
|
|
32
|
+
.version('1.0.0');
|
|
33
|
+
|
|
34
|
+
// Analyze command
|
|
35
|
+
program
|
|
36
|
+
.command('analyze')
|
|
37
|
+
.description('Dry-run analysis - identifies repeated values without modifying files')
|
|
38
|
+
.argument('[src]', 'Source directory to scan (optional if using angular.json)')
|
|
39
|
+
.option('--threshold <number>', 'Minimum repeat count threshold', parseInt)
|
|
40
|
+
.option('--format <format>', 'Report format (table, json, markdown)', 'table')
|
|
41
|
+
.option('--config <path>', 'Path to config file')
|
|
42
|
+
.option('--angular-json <path>', 'Path to angular.json file')
|
|
43
|
+
.option('--project <name>', 'Angular project name (uses default if not specified)')
|
|
44
|
+
.option('--no-angular', 'Disable angular.json integration')
|
|
45
|
+
.action(async (src, options) => {
|
|
46
|
+
try {
|
|
47
|
+
console.log(chalk.cyan.bold('\nš SCSS Variable Extraction Analysis\n'));
|
|
48
|
+
|
|
49
|
+
const config = loadConfig(options.config, {
|
|
50
|
+
useAngularJson: options.angular !== false,
|
|
51
|
+
angularJsonPath: options.angularJson,
|
|
52
|
+
projectName: options.project,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Override config with command-line options
|
|
56
|
+
if (src) config.src = src;
|
|
57
|
+
if (options.threshold) config.threshold = options.threshold;
|
|
58
|
+
if (options.format) config.reportFormat = options.format;
|
|
59
|
+
|
|
60
|
+
// Show Angular project info if available
|
|
61
|
+
if (config.angular) {
|
|
62
|
+
console.log(chalk.cyan(`Angular Project: ${config.angular.project}`));
|
|
63
|
+
console.log(chalk.gray(`Prefix: ${config.angular.prefix}`));
|
|
64
|
+
console.log(chalk.gray(`Style: ${config.angular.stylePreprocessor}\n`));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(chalk.gray(`Scanning: ${config.src}`));
|
|
68
|
+
console.log(chalk.gray(`Threshold: ${config.threshold} occurrences\n`));
|
|
69
|
+
|
|
70
|
+
// Scan files
|
|
71
|
+
const files = await scanScssFiles(config.src, config.ignore);
|
|
72
|
+
console.log(chalk.green(`ā Found ${files.length} SCSS files\n`));
|
|
73
|
+
|
|
74
|
+
// Parse all files
|
|
75
|
+
const allExtracted = {
|
|
76
|
+
colors: [],
|
|
77
|
+
spacing: [],
|
|
78
|
+
fontSizes: [],
|
|
79
|
+
fontWeights: [],
|
|
80
|
+
fontFamilies: [],
|
|
81
|
+
borderRadius: [],
|
|
82
|
+
shadows: [],
|
|
83
|
+
zIndex: [],
|
|
84
|
+
sizing: [],
|
|
85
|
+
lineHeight: [],
|
|
86
|
+
opacity: [],
|
|
87
|
+
transitions: [],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
files.forEach(file => {
|
|
91
|
+
const content = require('fs').readFileSync(file, 'utf8');
|
|
92
|
+
const extracted = parseScss(content, file);
|
|
93
|
+
|
|
94
|
+
Object.keys(extracted).forEach(category => {
|
|
95
|
+
allExtracted[category].push(...extracted[category]);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Analyze
|
|
100
|
+
const analysis = analyzeValues(allExtracted, config);
|
|
101
|
+
|
|
102
|
+
// Generate report
|
|
103
|
+
const report = generateReport(analysis, config.reportFormat, config);
|
|
104
|
+
console.log(report);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Generate command
|
|
112
|
+
program
|
|
113
|
+
.command('generate')
|
|
114
|
+
.description('Generate variables file only (does not modify existing SCSS files)')
|
|
115
|
+
.argument('[src]', 'Source directory to scan (optional if using angular.json)')
|
|
116
|
+
.option('--output <path>', 'Output path for variables file')
|
|
117
|
+
.option('--threshold <number>', 'Minimum repeat count threshold', parseInt)
|
|
118
|
+
.option('--config <path>', 'Path to config file')
|
|
119
|
+
.option('--angular-json <path>', 'Path to angular.json file')
|
|
120
|
+
.option('--project <name>', 'Angular project name (uses default if not specified)')
|
|
121
|
+
.option('--no-angular', 'Disable angular.json integration')
|
|
122
|
+
.action(async (src, options) => {
|
|
123
|
+
try {
|
|
124
|
+
console.log(chalk.cyan.bold('\nš Generating SCSS Variables File\n'));
|
|
125
|
+
|
|
126
|
+
const config = loadConfig(options.config, {
|
|
127
|
+
useAngularJson: options.angular !== false,
|
|
128
|
+
angularJsonPath: options.angularJson,
|
|
129
|
+
projectName: options.project,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Override config with command-line options
|
|
133
|
+
if (src) config.src = src;
|
|
134
|
+
if (options.output) config.output = options.output;
|
|
135
|
+
if (options.threshold) config.threshold = options.threshold;
|
|
136
|
+
|
|
137
|
+
// Show Angular project info if available
|
|
138
|
+
if (config.angular) {
|
|
139
|
+
console.log(chalk.cyan(`Angular Project: ${config.angular.project}`));
|
|
140
|
+
console.log(chalk.gray(`Prefix: ${config.angular.prefix}`));
|
|
141
|
+
console.log(chalk.gray(`Style: ${config.angular.stylePreprocessor}\n`));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(chalk.gray(`Scanning: ${config.src}`));
|
|
145
|
+
console.log(chalk.gray(`Output: ${config.output}\n`));
|
|
146
|
+
|
|
147
|
+
// Scan files
|
|
148
|
+
const files = await scanScssFiles(config.src, config.ignore);
|
|
149
|
+
console.log(chalk.green(`ā Found ${files.length} SCSS files`));
|
|
150
|
+
|
|
151
|
+
// Parse all files
|
|
152
|
+
const allExtracted = {
|
|
153
|
+
colors: [],
|
|
154
|
+
spacing: [],
|
|
155
|
+
fontSizes: [],
|
|
156
|
+
fontWeights: [],
|
|
157
|
+
fontFamilies: [],
|
|
158
|
+
borderRadius: [],
|
|
159
|
+
shadows: [],
|
|
160
|
+
zIndex: [],
|
|
161
|
+
sizing: [],
|
|
162
|
+
lineHeight: [],
|
|
163
|
+
opacity: [],
|
|
164
|
+
transitions: [],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
files.forEach(file => {
|
|
168
|
+
const content = require('fs').readFileSync(file, 'utf8');
|
|
169
|
+
const extracted = parseScss(content, file);
|
|
170
|
+
|
|
171
|
+
Object.keys(extracted).forEach(category => {
|
|
172
|
+
allExtracted[category].push(...extracted[category]);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Analyze
|
|
177
|
+
const analysis = analyzeValues(allExtracted, config);
|
|
178
|
+
|
|
179
|
+
// Generate variables file
|
|
180
|
+
generateVariablesFile(analysis, config.output, config);
|
|
181
|
+
|
|
182
|
+
console.log(chalk.green(`\nā Generated variables file: ${config.output}`));
|
|
183
|
+
|
|
184
|
+
// Show summary
|
|
185
|
+
const totalVars = Object.values(analysis).reduce((sum, arr) => sum + arr.length, 0);
|
|
186
|
+
console.log(chalk.bold(`\nTotal variables extracted: ${totalVars}\n`));
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Refactor command
|
|
194
|
+
program
|
|
195
|
+
.command('refactor')
|
|
196
|
+
.description('Full extraction + replacement (generates variables file and refactors SCSS files)')
|
|
197
|
+
.argument('[src]', 'Source directory to scan (optional if using angular.json)')
|
|
198
|
+
.option('--output <path>', 'Output path for variables file')
|
|
199
|
+
.option('--threshold <number>', 'Minimum repeat count threshold', parseInt)
|
|
200
|
+
.option('--config <path>', 'Path to config file')
|
|
201
|
+
.option('--angular-json <path>', 'Path to angular.json file')
|
|
202
|
+
.option('--project <name>', 'Angular project name (uses default if not specified)')
|
|
203
|
+
.option('--no-angular', 'Disable angular.json integration')
|
|
204
|
+
.action(async (src, options) => {
|
|
205
|
+
try {
|
|
206
|
+
console.log(chalk.cyan.bold('\nš§ SCSS Refactoring - Full Extraction\n'));
|
|
207
|
+
|
|
208
|
+
const config = loadConfig(options.config, {
|
|
209
|
+
useAngularJson: options.angular !== false,
|
|
210
|
+
angularJsonPath: options.angularJson,
|
|
211
|
+
projectName: options.project,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Override config with command-line options
|
|
215
|
+
if (src) config.src = src;
|
|
216
|
+
if (options.output) config.output = options.output;
|
|
217
|
+
if (options.threshold) config.threshold = options.threshold;
|
|
218
|
+
|
|
219
|
+
// Show Angular project info if available
|
|
220
|
+
if (config.angular) {
|
|
221
|
+
console.log(chalk.cyan(`Angular Project: ${config.angular.project}`));
|
|
222
|
+
console.log(chalk.gray(`Prefix: ${config.angular.prefix}`));
|
|
223
|
+
console.log(chalk.gray(`Style: ${config.angular.stylePreprocessor}\n`));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(chalk.gray(`Scanning: ${config.src}`));
|
|
227
|
+
console.log(chalk.gray(`Output: ${config.output}\n`));
|
|
228
|
+
|
|
229
|
+
// Scan files
|
|
230
|
+
const files = await scanScssFiles(config.src, config.ignore);
|
|
231
|
+
console.log(chalk.green(`ā Found ${files.length} SCSS files`));
|
|
232
|
+
|
|
233
|
+
// Parse all files
|
|
234
|
+
const allExtracted = {
|
|
235
|
+
colors: [],
|
|
236
|
+
spacing: [],
|
|
237
|
+
fontSizes: [],
|
|
238
|
+
fontWeights: [],
|
|
239
|
+
fontFamilies: [],
|
|
240
|
+
borderRadius: [],
|
|
241
|
+
shadows: [],
|
|
242
|
+
zIndex: [],
|
|
243
|
+
sizing: [],
|
|
244
|
+
lineHeight: [],
|
|
245
|
+
opacity: [],
|
|
246
|
+
transitions: [],
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
files.forEach(file => {
|
|
250
|
+
const content = require('fs').readFileSync(file, 'utf8');
|
|
251
|
+
const extracted = parseScss(content, file);
|
|
252
|
+
|
|
253
|
+
Object.keys(extracted).forEach(category => {
|
|
254
|
+
allExtracted[category].push(...extracted[category]);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Analyze
|
|
259
|
+
const analysis = analyzeValues(allExtracted, config);
|
|
260
|
+
|
|
261
|
+
// Generate variables file
|
|
262
|
+
generateVariablesFile(analysis, config.output, config);
|
|
263
|
+
console.log(chalk.green(`ā Generated variables file: ${config.output}`));
|
|
264
|
+
|
|
265
|
+
// Refactor files
|
|
266
|
+
const refactoredFiles = refactorScssFiles(files, analysis, config.output, config);
|
|
267
|
+
console.log(chalk.green(`ā Refactored ${refactoredFiles.length} SCSS files`));
|
|
268
|
+
|
|
269
|
+
// Show summary
|
|
270
|
+
const totalVars = Object.values(analysis).reduce((sum, arr) => sum + arr.length, 0);
|
|
271
|
+
console.log(chalk.bold(`\nTotal variables extracted: ${totalVars}`));
|
|
272
|
+
console.log(chalk.bold(`Files modified: ${refactoredFiles.length}\n`));
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Analyze Angular patterns command (ng-deep, !important)
|
|
280
|
+
program
|
|
281
|
+
.command('analyze-patterns')
|
|
282
|
+
.description('Analyze SCSS files for Angular Material v15+ anti-patterns (::ng-deep, !important)')
|
|
283
|
+
.argument('[src]', 'Source directory to scan (optional if using angular.json)')
|
|
284
|
+
.option('--format <format>', 'Report format (table, json, markdown)', 'table')
|
|
285
|
+
.option('--config <path>', 'Path to config file')
|
|
286
|
+
.option('--angular-json <path>', 'Path to angular.json file')
|
|
287
|
+
.option('--project <name>', 'Angular project name (uses default if not specified)')
|
|
288
|
+
.option('--no-angular', 'Disable angular.json integration')
|
|
289
|
+
.action(async (src, options) => {
|
|
290
|
+
try {
|
|
291
|
+
console.log(chalk.cyan.bold('\nš Angular Anti-Pattern Analysis\n'));
|
|
292
|
+
|
|
293
|
+
const config = loadConfig(options.config, {
|
|
294
|
+
useAngularJson: options.angular !== false,
|
|
295
|
+
angularJsonPath: options.angularJson,
|
|
296
|
+
projectName: options.project,
|
|
297
|
+
});
|
|
298
|
+
if (src) config.src = src;
|
|
299
|
+
|
|
300
|
+
// Show Angular project info if available
|
|
301
|
+
if (config.angular) {
|
|
302
|
+
console.log(chalk.cyan(`Angular Project: ${config.angular.project}`));
|
|
303
|
+
console.log(chalk.gray(`Prefix: ${config.angular.prefix}`));
|
|
304
|
+
console.log(chalk.gray(`Style: ${config.angular.stylePreprocessor}\n`));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
console.log(chalk.gray(`Scanning: ${config.src}\n`));
|
|
308
|
+
|
|
309
|
+
// Scan files
|
|
310
|
+
const files = await scanScssFiles(config.src, config.ignore);
|
|
311
|
+
console.log(chalk.green(`ā Found ${files.length} SCSS files\n`));
|
|
312
|
+
|
|
313
|
+
// Analyze patterns
|
|
314
|
+
const analysis = analyzeAngularPatterns(files);
|
|
315
|
+
|
|
316
|
+
// Generate report
|
|
317
|
+
const report = generateAngularPatternReport(analysis, options.format);
|
|
318
|
+
console.log(report);
|
|
319
|
+
|
|
320
|
+
// Exit with error code if issues found
|
|
321
|
+
if (analysis.summary.ngDeepCount > 0 || analysis.summary.importantCount > 0) {
|
|
322
|
+
console.log(
|
|
323
|
+
chalk.yellow('ā Anti-patterns detected. Run "modernize" command to fix them.\n')
|
|
324
|
+
);
|
|
325
|
+
} else {
|
|
326
|
+
console.log(chalk.green('ā No anti-patterns detected!\n'));
|
|
327
|
+
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Modernize command (refactor ng-deep and !important)
|
|
335
|
+
program
|
|
336
|
+
.command('modernize')
|
|
337
|
+
.description('Refactor SCSS files following Angular Material v15+ best practices')
|
|
338
|
+
.argument('[src]', 'Source directory to scan (optional if using angular.json)')
|
|
339
|
+
.option('--global-styles <path>', 'Path to global styles file', './src/styles.scss')
|
|
340
|
+
.option('--no-ng-deep', 'Skip ::ng-deep refactoring')
|
|
341
|
+
.option('--no-important', 'Skip !important refactoring')
|
|
342
|
+
.option('--dry-run', 'Preview changes without modifying files')
|
|
343
|
+
.option('--config <path>', 'Path to config file')
|
|
344
|
+
.option('--angular-json <path>', 'Path to angular.json file')
|
|
345
|
+
.option('--project <name>', 'Angular project name (uses default if not specified)')
|
|
346
|
+
.option('--no-angular', 'Disable angular.json integration')
|
|
347
|
+
.action(async (src, options) => {
|
|
348
|
+
try {
|
|
349
|
+
console.log(
|
|
350
|
+
chalk.cyan.bold('\nš§ Modernizing SCSS Files (Angular Material v15+ Best Practices)\n')
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const config = loadConfig(options.config, {
|
|
354
|
+
useAngularJson: options.angular !== false,
|
|
355
|
+
angularJsonPath: options.angularJson,
|
|
356
|
+
projectName: options.project,
|
|
357
|
+
});
|
|
358
|
+
if (src) config.src = src;
|
|
359
|
+
|
|
360
|
+
if (options.dryRun) {
|
|
361
|
+
console.log(chalk.yellow('DRY RUN MODE - No files will be modified\n'));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Show Angular project info if available
|
|
365
|
+
if (config.angular) {
|
|
366
|
+
console.log(chalk.cyan(`Angular Project: ${config.angular.project}`));
|
|
367
|
+
console.log(chalk.gray(`Prefix: ${config.angular.prefix}`));
|
|
368
|
+
console.log(chalk.gray(`Style: ${config.angular.stylePreprocessor}\n`));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
console.log(chalk.gray(`Scanning: ${config.src}`));
|
|
372
|
+
console.log(chalk.gray(`Global styles: ${options.globalStyles}\n`));
|
|
373
|
+
|
|
374
|
+
// Scan files
|
|
375
|
+
const files = await scanScssFiles(config.src, config.ignore);
|
|
376
|
+
console.log(chalk.green(`ā Found ${files.length} SCSS files\n`));
|
|
377
|
+
|
|
378
|
+
// Refactor patterns
|
|
379
|
+
const results = refactorAngularPatterns(files, {
|
|
380
|
+
removeNgDeep: options.ngDeep !== false,
|
|
381
|
+
removeImportant: options.important !== false,
|
|
382
|
+
createGlobalStyles: true,
|
|
383
|
+
globalStylesPath: options.globalStyles,
|
|
384
|
+
dryRun: options.dryRun,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Show results
|
|
388
|
+
if (results.modified.length > 0) {
|
|
389
|
+
console.log(chalk.green(`ā Modified ${results.modified.length} file(s)\n`));
|
|
390
|
+
|
|
391
|
+
results.modified.forEach(file => {
|
|
392
|
+
console.log(chalk.white(` ${path.basename(file.file)}:`));
|
|
393
|
+
file.changes.forEach(change => {
|
|
394
|
+
if (change.type === 'ng-deep-removed') {
|
|
395
|
+
console.log(chalk.yellow(` - Removed ${change.count} ::ng-deep occurrence(s)`));
|
|
396
|
+
} else if (change.type === 'important-removed') {
|
|
397
|
+
console.log(chalk.yellow(` - Removed ${change.count} !important declaration(s)`));
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
console.log();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (results.globalStyles.length > 0) {
|
|
405
|
+
console.log(
|
|
406
|
+
chalk.cyan(
|
|
407
|
+
`ā Extracted ${results.globalStyles.length} style(s) to ${options.globalStyles}\n`
|
|
408
|
+
)
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (results.warnings.length > 0) {
|
|
413
|
+
console.log(chalk.yellow.bold('ā Warnings:\n'));
|
|
414
|
+
results.warnings.forEach(warning => {
|
|
415
|
+
console.log(chalk.yellow(` ${warning.message}`));
|
|
416
|
+
});
|
|
417
|
+
console.log();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (results.modified.length === 0) {
|
|
421
|
+
console.log(chalk.green('ā No changes needed - files already follow best practices!\n'));
|
|
422
|
+
} else if (!options.dryRun) {
|
|
423
|
+
console.log(chalk.bold.green('ā Modernization complete!\n'));
|
|
424
|
+
console.log(chalk.gray('Next steps:'));
|
|
425
|
+
console.log(chalk.gray(' 1. Review the modified files'));
|
|
426
|
+
console.log(chalk.gray(' 2. Test your application'));
|
|
427
|
+
console.log(
|
|
428
|
+
chalk.gray(' 3. Consider using Angular Material theme mixins for component theming')
|
|
429
|
+
);
|
|
430
|
+
console.log(chalk.gray(' 4. Review extracted global styles and organize as needed\n'));
|
|
431
|
+
}
|
|
432
|
+
} catch (error) {
|
|
433
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Detect Bootstrap command
|
|
439
|
+
program
|
|
440
|
+
.command('detect-bootstrap')
|
|
441
|
+
.description('Detect Bootstrap classes and analyze migration to Angular Material')
|
|
442
|
+
.argument('[src]', 'Source directory to scan (optional if using angular.json)')
|
|
443
|
+
.option('--format <format>', 'Report format (table, json, markdown)', 'table')
|
|
444
|
+
.option('--config <path>', 'Path to config file')
|
|
445
|
+
.option('--angular-json <path>', 'Path to angular.json file')
|
|
446
|
+
.option('--project <name>', 'Angular project name (uses default if not specified)')
|
|
447
|
+
.option('--no-angular', 'Disable angular.json integration')
|
|
448
|
+
.action(async (src, options) => {
|
|
449
|
+
try {
|
|
450
|
+
console.log(chalk.cyan.bold('\nš¦ Bootstrap to Material Migration Analysis\n'));
|
|
451
|
+
|
|
452
|
+
const config = loadConfig(options.config, {
|
|
453
|
+
useAngularJson: options.angular !== false,
|
|
454
|
+
angularJsonPath: options.angularJson,
|
|
455
|
+
projectName: options.project,
|
|
456
|
+
});
|
|
457
|
+
if (src) config.src = src;
|
|
458
|
+
|
|
459
|
+
// Show Angular project info if available
|
|
460
|
+
if (config.angular) {
|
|
461
|
+
console.log(chalk.cyan(`Angular Project: ${config.angular.project}`));
|
|
462
|
+
console.log(chalk.gray(`Prefix: ${config.angular.prefix}`));
|
|
463
|
+
console.log(chalk.gray(`Style: ${config.angular.stylePreprocessor}\n`));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
console.log(chalk.gray(`Scanning: ${config.src}\n`));
|
|
467
|
+
|
|
468
|
+
// Scan files - look for SCSS, HTML, and TS files
|
|
469
|
+
const allFiles = await scanTemplateFiles(config.src, config.ignore);
|
|
470
|
+
console.log(chalk.green(`ā Found ${allFiles.length} files (SCSS, HTML, TS)\n`));
|
|
471
|
+
|
|
472
|
+
// Detect Bootstrap
|
|
473
|
+
const detection = detectBootstrap(allFiles);
|
|
474
|
+
|
|
475
|
+
// Generate report
|
|
476
|
+
const report = generateBootstrapReport(detection, options.format);
|
|
477
|
+
console.log(report);
|
|
478
|
+
|
|
479
|
+
// Provide migration guidance
|
|
480
|
+
if (detection.summary.totalBootstrapClasses > 0) {
|
|
481
|
+
console.log(
|
|
482
|
+
chalk.yellow(
|
|
483
|
+
'ā Bootstrap classes detected. Use "migrate-bootstrap" command to convert.\n'
|
|
484
|
+
)
|
|
485
|
+
);
|
|
486
|
+
console.log(chalk.gray('Migration options:'));
|
|
487
|
+
console.log(chalk.gray(' ⢠Components (btn, card, etc.) ā Angular Material components'));
|
|
488
|
+
console.log(chalk.gray(' ⢠Utilities (d-flex, spacing, etc.) ā Custom utility classes'));
|
|
489
|
+
console.log(chalk.gray(' ⢠Grid system ā CSS Grid or Flexbox\n'));
|
|
490
|
+
} else {
|
|
491
|
+
console.log(chalk.green('ā No Bootstrap classes detected!\n'));
|
|
492
|
+
}
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// Migrate Bootstrap command
|
|
500
|
+
program
|
|
501
|
+
.command('migrate-bootstrap')
|
|
502
|
+
.description('Migrate Bootstrap classes to Angular Material MDC-based styles')
|
|
503
|
+
.argument('[src]', 'Source directory to scan (optional if using angular.json)')
|
|
504
|
+
.option(
|
|
505
|
+
'--custom-utilities <path>',
|
|
506
|
+
'Path to custom utilities file',
|
|
507
|
+
'./src/styles/utilities.scss'
|
|
508
|
+
)
|
|
509
|
+
.option('--no-custom-utilities', 'Skip creating custom utility classes')
|
|
510
|
+
.option('--no-remove-imports', 'Keep Bootstrap imports')
|
|
511
|
+
.option('--dry-run', 'Preview changes without modifying files')
|
|
512
|
+
.option('--config <path>', 'Path to config file')
|
|
513
|
+
.option('--angular-json <path>', 'Path to angular.json file')
|
|
514
|
+
.option('--project <name>', 'Angular project name (uses default if not specified)')
|
|
515
|
+
.option('--no-angular', 'Disable angular.json integration')
|
|
516
|
+
.action(async (src, options) => {
|
|
517
|
+
try {
|
|
518
|
+
console.log(chalk.cyan.bold('\nš Migrating Bootstrap to Angular Material\n'));
|
|
519
|
+
|
|
520
|
+
const config = loadConfig(options.config, {
|
|
521
|
+
useAngularJson: options.angular !== false,
|
|
522
|
+
angularJsonPath: options.angularJson,
|
|
523
|
+
projectName: options.project,
|
|
524
|
+
});
|
|
525
|
+
if (src) config.src = src;
|
|
526
|
+
|
|
527
|
+
if (options.dryRun) {
|
|
528
|
+
console.log(chalk.yellow('DRY RUN MODE - No files will be modified\n'));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Show Angular project info if available
|
|
532
|
+
if (config.angular) {
|
|
533
|
+
console.log(chalk.cyan(`Angular Project: ${config.angular.project}`));
|
|
534
|
+
console.log(chalk.gray(`Prefix: ${config.angular.prefix}`));
|
|
535
|
+
console.log(chalk.gray(`Style: ${config.angular.stylePreprocessor}\n`));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
console.log(chalk.gray(`Scanning: ${config.src}`));
|
|
539
|
+
console.log(
|
|
540
|
+
chalk.gray(
|
|
541
|
+
`Utilities output: ${options.customUtilities || './src/styles/utilities.scss'}\n`
|
|
542
|
+
)
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
// Scan files - look for SCSS, HTML, and TS files
|
|
546
|
+
const allFiles = await scanTemplateFiles(config.src, config.ignore);
|
|
547
|
+
console.log(chalk.green(`ā Found ${allFiles.length} files (SCSS, HTML, TS)\n`));
|
|
548
|
+
|
|
549
|
+
// Migrate Bootstrap
|
|
550
|
+
const results = migrateBootstrapToMaterial(allFiles, {
|
|
551
|
+
createCustomUtilities: options.customUtilities !== false,
|
|
552
|
+
customUtilitiesPath: options.customUtilities || './src/styles/utilities.scss',
|
|
553
|
+
removeBootstrapImports: options.removeImports !== false,
|
|
554
|
+
dryRun: options.dryRun,
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// Show results
|
|
558
|
+
if (results.modified.length > 0) {
|
|
559
|
+
console.log(chalk.green(`ā Modified ${results.modified.length} file(s)\n`));
|
|
560
|
+
|
|
561
|
+
results.modified.slice(0, 10).forEach(file => {
|
|
562
|
+
console.log(chalk.white(` ${path.basename(file.file)}:`));
|
|
563
|
+
file.changes.forEach(change => {
|
|
564
|
+
if (change.type === 'import-removed') {
|
|
565
|
+
console.log(chalk.yellow(` - ${change.description}`));
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
if (results.modified.length > 10) {
|
|
570
|
+
console.log(chalk.gray(` ... and ${results.modified.length - 10} more`));
|
|
571
|
+
}
|
|
572
|
+
console.log();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (results.customUtilities.length > 0) {
|
|
576
|
+
console.log(
|
|
577
|
+
chalk.cyan(
|
|
578
|
+
`ā Generated ${results.customUtilities.length} custom utility class(es) in ${options.customUtilities || './src/styles/utilities.scss'}\n`
|
|
579
|
+
)
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (results.componentMigrations.length > 0) {
|
|
584
|
+
console.log(
|
|
585
|
+
chalk.yellow(
|
|
586
|
+
`ā ${results.componentMigrations.length} component(s) require manual migration\n`
|
|
587
|
+
)
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (results.warnings.length > 0) {
|
|
592
|
+
console.log(chalk.yellow.bold('ā Warnings:\n'));
|
|
593
|
+
results.warnings.slice(0, 5).forEach(warning => {
|
|
594
|
+
console.log(chalk.yellow(` ${warning.message}`));
|
|
595
|
+
if (warning.variables) {
|
|
596
|
+
console.log(chalk.gray(` Variables: ${warning.variables.join(', ')}`));
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
if (results.warnings.length > 5) {
|
|
600
|
+
console.log(chalk.gray(` ... and ${results.warnings.length - 5} more warnings`));
|
|
601
|
+
}
|
|
602
|
+
console.log();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (results.modified.length === 0 && results.warnings.length === 0) {
|
|
606
|
+
console.log(chalk.green('ā No Bootstrap code detected!\n'));
|
|
607
|
+
} else if (!options.dryRun) {
|
|
608
|
+
console.log(chalk.bold.green('ā Migration complete!\n'));
|
|
609
|
+
console.log(chalk.gray('Next steps:'));
|
|
610
|
+
console.log(chalk.gray(' 1. Review the modified SCSS files'));
|
|
611
|
+
console.log(chalk.gray(' 2. Update HTML templates to use Angular Material components'));
|
|
612
|
+
console.log(chalk.gray(' 3. Import custom utilities in your styles.scss'));
|
|
613
|
+
console.log(chalk.gray(' 4. Install @angular/material if not already installed'));
|
|
614
|
+
console.log(chalk.gray(' 5. Test your application thoroughly\n'));
|
|
615
|
+
console.log(chalk.cyan('Component Migration Guide:'));
|
|
616
|
+
console.log(chalk.gray(' ⢠.btn ā <button mat-button>'));
|
|
617
|
+
console.log(chalk.gray(' ⢠.card ā <mat-card>'));
|
|
618
|
+
console.log(
|
|
619
|
+
chalk.gray(' ⢠.form-control ā <mat-form-field><input matInput></mat-form-field>')
|
|
620
|
+
);
|
|
621
|
+
console.log(chalk.gray(' ⢠See: https://material.angular.io/components\n'));
|
|
622
|
+
}
|
|
623
|
+
} catch (error) {
|
|
624
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
// Analyze style organization command
|
|
630
|
+
program
|
|
631
|
+
.command('analyze-organization')
|
|
632
|
+
.description('Analyze style organization and get recommendations for best practices')
|
|
633
|
+
.argument('[src]', 'Source directory to scan (optional if using angular.json)')
|
|
634
|
+
.option('--format <format>', 'Report format (table, json, markdown)', 'table')
|
|
635
|
+
.option('--config <path>', 'Path to config file')
|
|
636
|
+
.option('--angular-json <path>', 'Path to angular.json file')
|
|
637
|
+
.option('--project <name>', 'Angular project name (uses default if not specified)')
|
|
638
|
+
.option('--no-angular', 'Disable angular.json integration')
|
|
639
|
+
.action(async (src, options) => {
|
|
640
|
+
try {
|
|
641
|
+
console.log(chalk.cyan.bold('\nš Style Organization Analysis\n'));
|
|
642
|
+
|
|
643
|
+
const config = loadConfig(options.config, {
|
|
644
|
+
useAngularJson: options.angular !== false,
|
|
645
|
+
angularJsonPath: options.angularJson,
|
|
646
|
+
projectName: options.project,
|
|
647
|
+
});
|
|
648
|
+
if (src) config.src = src;
|
|
649
|
+
|
|
650
|
+
// Show Angular project info if available
|
|
651
|
+
if (config.angular) {
|
|
652
|
+
console.log(chalk.cyan(`Angular Project: ${config.angular.project}`));
|
|
653
|
+
console.log(chalk.gray(`Prefix: ${config.angular.prefix}`));
|
|
654
|
+
console.log(chalk.gray(`Style: ${config.angular.stylePreprocessor}\n`));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
console.log(chalk.gray(`Scanning: ${config.src}\n`));
|
|
658
|
+
|
|
659
|
+
// Scan files
|
|
660
|
+
const files = await scanScssFiles(config.src, config.ignore);
|
|
661
|
+
console.log(chalk.green(`ā Found ${files.length} SCSS files\n`));
|
|
662
|
+
|
|
663
|
+
// Analyze organization
|
|
664
|
+
const analysis = analyzeStyleOrganization(files, config);
|
|
665
|
+
|
|
666
|
+
// Generate report
|
|
667
|
+
const report = generateOrganizationReport(analysis, options.format);
|
|
668
|
+
console.log(report);
|
|
669
|
+
|
|
670
|
+
// Provide guidance
|
|
671
|
+
if (analysis.recommendations.length > 0) {
|
|
672
|
+
console.log(chalk.yellow.bold('š” Next Steps:\n'));
|
|
673
|
+
console.log(chalk.gray('1. Review recommendations above'));
|
|
674
|
+
console.log(chalk.gray('2. Run "refactor" command to extract variables'));
|
|
675
|
+
console.log(chalk.gray('3. Run "modernize" to remove anti-patterns'));
|
|
676
|
+
console.log(chalk.gray('4. Consider restructuring based on suggested organization'));
|
|
677
|
+
console.log(chalk.gray('5. Use @use instead of @import for better modularity\n'));
|
|
678
|
+
} else {
|
|
679
|
+
console.log(chalk.green('ā Your styles are well organized!\n'));
|
|
680
|
+
}
|
|
681
|
+
} catch (error) {
|
|
682
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// Generate theme structure command
|
|
688
|
+
program
|
|
689
|
+
.command('generate-themes')
|
|
690
|
+
.description('Generate theme structure for dark/light mode support with Angular Material')
|
|
691
|
+
.argument('[src]', 'Source directory to scan for analysis (optional)')
|
|
692
|
+
.option('--output <dir>', 'Output directory for theme files', './src/styles')
|
|
693
|
+
.option('--analyze', 'Analyze existing styles for theme readiness')
|
|
694
|
+
.option('--format <format>', 'Report format (table, json, markdown)', 'table')
|
|
695
|
+
.action(async (src, options) => {
|
|
696
|
+
try {
|
|
697
|
+
console.log(chalk.cyan.bold('\nšØ Theme Structure Generator\n'));
|
|
698
|
+
|
|
699
|
+
const fs = require('fs');
|
|
700
|
+
const outputDir = path.resolve(options.output);
|
|
701
|
+
|
|
702
|
+
// Analyze existing styles if requested
|
|
703
|
+
if (options.analyze && src) {
|
|
704
|
+
console.log(chalk.gray(`Analyzing: ${src}\n`));
|
|
705
|
+
const config = loadConfig();
|
|
706
|
+
const files = await scanScssFiles(src, config.ignore);
|
|
707
|
+
const analysis = analyzeThemeReadiness(files);
|
|
708
|
+
|
|
709
|
+
console.log(chalk.blue.bold('Theme Readiness Analysis:\n'));
|
|
710
|
+
console.log(chalk.gray(`Total hardcoded colors: ${analysis.hardcodedColors.length}`));
|
|
711
|
+
console.log(chalk.gray(`Unique colors: ${analysis.colorUsage.size}`));
|
|
712
|
+
console.log(
|
|
713
|
+
chalk.gray(`Components needing theme mixins: ${analysis.themeableComponents.length}\n`)
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
if (analysis.recommendations.length > 0) {
|
|
717
|
+
console.log(chalk.yellow.bold('š Recommendations:\n'));
|
|
718
|
+
analysis.recommendations.forEach(rec => {
|
|
719
|
+
const icon = rec.priority === 'high' ? 'š“' : 'š”';
|
|
720
|
+
console.log(chalk.gray(`${icon} ${rec.message}`));
|
|
721
|
+
});
|
|
722
|
+
console.log();
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Generate theme files
|
|
727
|
+
console.log(chalk.gray(`Generating theme files in: ${outputDir}\n`));
|
|
728
|
+
|
|
729
|
+
const themeStructure = generateThemeStructure({
|
|
730
|
+
outputDir,
|
|
731
|
+
includeComponents: true,
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// Create output directory if it doesn't exist
|
|
735
|
+
if (!fs.existsSync(outputDir)) {
|
|
736
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Write theme files
|
|
740
|
+
const filesToWrite = {
|
|
741
|
+
'_theme-base.scss': themeStructure.files.base,
|
|
742
|
+
'_theme-light.scss': themeStructure.files.lightTheme,
|
|
743
|
+
'_theme-dark.scss': themeStructure.files.darkTheme,
|
|
744
|
+
'themes.scss': themeStructure.files.themeLoader,
|
|
745
|
+
'_css-variables.scss': themeStructure.files.cssVariables,
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
if (themeStructure.files.componentThemes) {
|
|
749
|
+
filesToWrite['_component-theme-mixin.scss'] = themeStructure.files.componentThemes;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
Object.entries(filesToWrite).forEach(([filename, content]) => {
|
|
753
|
+
const filepath = path.join(outputDir, filename);
|
|
754
|
+
fs.writeFileSync(filepath, content, 'utf8');
|
|
755
|
+
console.log(chalk.green(`ā Created ${filename}`));
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
console.log();
|
|
759
|
+
console.log(chalk.green.bold('ā Theme structure generated!\n'));
|
|
760
|
+
|
|
761
|
+
// Show recommendations
|
|
762
|
+
console.log(chalk.blue.bold('š Usage Guide:\n'));
|
|
763
|
+
console.log(chalk.gray('1. Import themes.scss in your styles.scss:'));
|
|
764
|
+
console.log(chalk.cyan(" @use './styles/themes';"));
|
|
765
|
+
console.log();
|
|
766
|
+
console.log(chalk.gray('2. Add theme class to your app component:'));
|
|
767
|
+
console.log(chalk.cyan(' <div [class.dark-theme]="isDarkMode">'));
|
|
768
|
+
console.log(chalk.cyan(' <router-outlet></router-outlet>'));
|
|
769
|
+
console.log(chalk.cyan(' </div>'));
|
|
770
|
+
console.log();
|
|
771
|
+
console.log(chalk.gray('3. Toggle theme in your component:'));
|
|
772
|
+
console.log(chalk.cyan(' isDarkMode = false;'));
|
|
773
|
+
console.log(chalk.cyan(' toggleTheme() {'));
|
|
774
|
+
console.log(chalk.cyan(' this.isDarkMode = !this.isDarkMode;'));
|
|
775
|
+
console.log(chalk.cyan(' }'));
|
|
776
|
+
console.log();
|
|
777
|
+
console.log(chalk.gray('4. Use theme variables in components:'));
|
|
778
|
+
console.log(chalk.cyan(" @use '../../styles/theme-base' as base;"));
|
|
779
|
+
console.log(chalk.cyan(' .my-element {'));
|
|
780
|
+
console.log(chalk.cyan(' padding: base.$spacing-md;'));
|
|
781
|
+
console.log(chalk.cyan(' }'));
|
|
782
|
+
console.log();
|
|
783
|
+
|
|
784
|
+
// Show recommendations
|
|
785
|
+
console.log(chalk.yellow.bold('š” Best Practices:\n'));
|
|
786
|
+
themeStructure.recommendations.forEach(rec => {
|
|
787
|
+
const icon = rec.priority === 'high' ? 'š“' : rec.priority === 'medium' ? 'š”' : 'š¢';
|
|
788
|
+
console.log(chalk.gray(`${icon} [${rec.category}] ${rec.recommendation}`));
|
|
789
|
+
});
|
|
790
|
+
console.log();
|
|
791
|
+
|
|
792
|
+
// Show structure
|
|
793
|
+
console.log(chalk.blue.bold('š Recommended Directory Structure:\n'));
|
|
794
|
+
Object.entries(themeStructure.structure).forEach(([dir, files]) => {
|
|
795
|
+
console.log(chalk.cyan(dir));
|
|
796
|
+
Object.entries(files).forEach(([file, desc]) => {
|
|
797
|
+
console.log(chalk.gray(` āā ${file} - ${desc}`));
|
|
798
|
+
});
|
|
799
|
+
});
|
|
800
|
+
console.log();
|
|
801
|
+
} catch (error) {
|
|
802
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
803
|
+
process.exit(1);
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
program.parse();
|