git-push-time 1.0.3 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +33 -0
- package/README.md +215 -134
- package/bin/index.js +337 -1004
- package/lib/commands/edit-time.js +141 -0
- package/lib/commands/fix.js +113 -0
- package/lib/commands/github.js +439 -0
- package/lib/commands/pull.js +89 -0
- package/lib/commands/push.js +673 -0
- package/lib/config.js +220 -0
- package/lib/fix.js +286 -260
- package/lib/git.js +492 -452
- package/lib/github.js +322 -0
- package/lib/prompt-helpers.js +62 -0
- package/lib/secrets.js +92 -87
- package/lib/ui.js +804 -728
- package/lib/utils.js +120 -106
- package/package.json +35 -32
package/lib/ui.js
CHANGED
|
@@ -1,728 +1,804 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import stringWidth from "string-width";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Tự động tính
|
|
6
|
-
*/
|
|
7
|
-
function getW() {
|
|
8
|
-
const cols = process.stdout.columns ||
|
|
9
|
-
return Math.min(
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.
|
|
30
|
-
const
|
|
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
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
console.log(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
.bgHex("#
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
console.log("");
|
|
183
|
-
console.log(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
);
|
|
198
|
-
console.log(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
console.log(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
console.log(
|
|
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
|
-
console.log(
|
|
249
|
-
boxKvDot("
|
|
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
|
-
console.log(boxRow("", border));
|
|
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
|
-
console.log(
|
|
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
|
-
boxRow(
|
|
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
|
-
console.log(
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
console.log(
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
console.log("");
|
|
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
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
console.log(
|
|
566
|
-
console.log(
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
console.log(
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
);
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
);
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
)
|
|
612
|
-
|
|
613
|
-
.
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
console.log(
|
|
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
|
-
console.log(
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
chalk.hex("#
|
|
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
|
-
console.log(
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
-
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import stringWidth from "string-width";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tự động tính chiều rộng card theo Terminal, có giới hạn cho cửa sổ rộng.
|
|
6
|
+
*/
|
|
7
|
+
export function getW() {
|
|
8
|
+
const cols = process.stdout.columns || 72;
|
|
9
|
+
return Math.min(76, Math.max(24, cols - 4));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function fitStyled(text, maxWidth) {
|
|
13
|
+
const value = String(text ?? '');
|
|
14
|
+
if (maxWidth <= 0) return '';
|
|
15
|
+
if (stringWidth(value) <= maxWidth) return value;
|
|
16
|
+
|
|
17
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
18
|
+
const parts = value.match(/\u001b\[[0-?]*[ -/]*[@-~]|[^\u001b]+|\u001b/g) || [];
|
|
19
|
+
const output = [];
|
|
20
|
+
let visible = 0;
|
|
21
|
+
let clipped = false;
|
|
22
|
+
const target = Math.max(0, maxWidth - 1);
|
|
23
|
+
|
|
24
|
+
for (const part of parts) {
|
|
25
|
+
if (part.startsWith('\u001b[')) {
|
|
26
|
+
output.push(part);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
for (const { segment } of segmenter.segment(part)) {
|
|
30
|
+
const width = stringWidth(segment);
|
|
31
|
+
if (visible + width > target) {
|
|
32
|
+
clipped = true;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
output.push(segment);
|
|
36
|
+
visible += width;
|
|
37
|
+
}
|
|
38
|
+
if (clipped) break;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
output.push('…');
|
|
42
|
+
// A clipped styled value must not leak its color into later terminal output.
|
|
43
|
+
if (value.includes('\u001b[')) output.push('\u001b[0m');
|
|
44
|
+
return output.join('');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
function hexToRgb(hex) {
|
|
49
|
+
const n = parseInt(hex.replace("#", ""), 16);
|
|
50
|
+
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function multiGradient(text, colors = ["#818CF8", "#38BDF8", "#34D399", "#A7F3D0"]) {
|
|
54
|
+
const chars = [...text];
|
|
55
|
+
const rgbColors = colors.map(hexToRgb);
|
|
56
|
+
return chars.map((char, index) => {
|
|
57
|
+
const t = (index / (chars.length - 1 || 1)) * (rgbColors.length - 1);
|
|
58
|
+
const colorIndex = Math.min(Math.floor(t), rgbColors.length - 2);
|
|
59
|
+
const localT = t - colorIndex;
|
|
60
|
+
const [r1, g1, b1] = rgbColors[colorIndex];
|
|
61
|
+
const [r2, g2, b2] = rgbColors[colorIndex + 1];
|
|
62
|
+
return chalk.rgb(Math.round(r1 + (r2-r1)*localT), Math.round(g1 + (g2-g1)*localT), Math.round(b1 + (b2-b1)*localT))(char);
|
|
63
|
+
}).join("");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function pad(str, len) {
|
|
67
|
+
const vLen = stringWidth(str);
|
|
68
|
+
if (vLen >= len) return str;
|
|
69
|
+
return str + " ".repeat(len - vLen);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function truncateVisual(str, maxLen) {
|
|
73
|
+
return fitStyled(str, maxLen);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ==========================================================================
|
|
77
|
+
BOX ENGINE CHUẨN (Bo góc mềm & Căn lề pixel-perfect)
|
|
78
|
+
========================================================================== */
|
|
79
|
+
|
|
80
|
+
export function boxTop(title = "", borderColor = chalk.hex("#334155")) {
|
|
81
|
+
const W = getW();
|
|
82
|
+
if (!title) return borderColor("╭" + "─".repeat(W - 2) + "╮");
|
|
83
|
+
|
|
84
|
+
const fittedTitle = fitStyled(title, W - 8);
|
|
85
|
+
const titleWidth = stringWidth(fittedTitle);
|
|
86
|
+
const leftDash = "─".repeat(3);
|
|
87
|
+
const rightDashLen = Math.max(0, W - 2 - 3 - titleWidth);
|
|
88
|
+
return (
|
|
89
|
+
borderColor("╭" + leftDash) +
|
|
90
|
+
fittedTitle +
|
|
91
|
+
borderColor("─".repeat(rightDashLen) + "╮")
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function boxBottom(borderColor = chalk.hex("#334155")) {
|
|
96
|
+
const W = getW();
|
|
97
|
+
return borderColor("╰" + "─".repeat(W - 2) + "╯");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function boxRow(content = "", borderColor = chalk.hex("#334155")) {
|
|
101
|
+
const W = getW();
|
|
102
|
+
return borderColor("│ ") + pad(fitStyled(content, W - 4), W - 4) + borderColor(" │");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function boxDivider(borderColor = chalk.hex("#334155")) {
|
|
106
|
+
const W = getW();
|
|
107
|
+
return borderColor("├" + "─".repeat(W - 2) + "┤");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Hàng Key-Value có đường nối bằng dấu chấm ․ ․ ․
|
|
112
|
+
*/
|
|
113
|
+
export function boxKvDot(
|
|
114
|
+
label,
|
|
115
|
+
value,
|
|
116
|
+
labelWidth = 16,
|
|
117
|
+
borderColor = chalk.hex("#334155"),
|
|
118
|
+
) {
|
|
119
|
+
const W = getW();
|
|
120
|
+
const innerWidth = W - 4;
|
|
121
|
+
const fittedLabelWidth = Math.min(labelWidth, Math.max(8, Math.floor(innerWidth * 0.4)));
|
|
122
|
+
const maxValWidth = Math.max(4, innerWidth - fittedLabelWidth - 1);
|
|
123
|
+
const truncatedVal = truncateVisual(value, maxValWidth);
|
|
124
|
+
const valWidth = stringWidth(truncatedVal);
|
|
125
|
+
|
|
126
|
+
const dotsLen = Math.max(1, innerWidth - fittedLabelWidth - valWidth);
|
|
127
|
+
const dots = chalk.hex("#1E293B")("․".repeat(dotsLen));
|
|
128
|
+
const formattedLabel = chalk.hex("#94A3B8")(
|
|
129
|
+
pad(fitStyled(label, fittedLabelWidth), fittedLabelWidth),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
return boxRow(`${formattedLabel}${dots}${truncatedVal}`, borderColor);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* ==========================================================================
|
|
136
|
+
PUBLIC INTERFACE & BANNER
|
|
137
|
+
========================================================================== */
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Banner DevTools sắc nét, hiện đại
|
|
141
|
+
*/
|
|
142
|
+
export function printBanner(version = "1.1.0") {
|
|
143
|
+
const ascii = [
|
|
144
|
+
" ____ _ _ ____ _ _____ _ ",
|
|
145
|
+
" / ___(_) |_ | _ \\ _ _ ___| |__ |_ _(_)_ __ ___ ___ ",
|
|
146
|
+
" | | _| | __| | |_) | | | / __| '_ \\ | | | | '_ ` _ \\ / _ \\ ",
|
|
147
|
+
" | |_| | | |_ | __/| |_| \\__ \\ | | | | | | | | | | | | | __/ ",
|
|
148
|
+
" \\____|_|\\__| |_| \\__,_|___/_| |_| |_| |_|_| |_| |_|\\___| ",
|
|
149
|
+
];
|
|
150
|
+
const terminalWidth = process.stdout.columns || 72;
|
|
151
|
+
const colors = ["#818CF8", "#38BDF8", "#34D399", "#A7F3D0"];
|
|
152
|
+
console.log("");
|
|
153
|
+
if (terminalWidth >= 72) ascii.forEach((line) => console.log(multiGradient(line, colors)));
|
|
154
|
+
else console.log(multiGradient(" GIT PUSH TIME", colors));
|
|
155
|
+
const badges = [
|
|
156
|
+
chalk.bgHex("#1E1B4B").hex("#818CF8").bold(` v${version} `),
|
|
157
|
+
chalk.bgHex("#064E3B").hex("#34D399").bold(" SMART GIT "),
|
|
158
|
+
chalk.bgHex("#4338CA").hex("#C7D2FE").bold(" GITHUB SYNC "),
|
|
159
|
+
chalk.bgHex("#0F172A").hex("#38BDF8").bold(" SECRET SHIELD "),
|
|
160
|
+
chalk.bgHex("#78350F").hex("#FDE047").bold(" BACKDATE "),
|
|
161
|
+
];
|
|
162
|
+
let row = " ";
|
|
163
|
+
for (const badge of badges) {
|
|
164
|
+
const gap = row.trim() ? " " : "";
|
|
165
|
+
if (stringWidth(row + gap + badge) > terminalWidth - 2) {
|
|
166
|
+
console.log(row);
|
|
167
|
+
row = " " + badge;
|
|
168
|
+
} else row += gap + badge;
|
|
169
|
+
}
|
|
170
|
+
if (row.trim()) console.log(row);
|
|
171
|
+
console.log("");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Hiển thị Dashboard trạng thái ngắn gọn ở đầu menu
|
|
176
|
+
*/
|
|
177
|
+
export function printDashboardStatus({ targetDir = process.cwd(), currentBranch = "main", githubUser = null }) {
|
|
178
|
+
const border = chalk.hex("#1E293B");
|
|
179
|
+
const ghStatus = githubUser
|
|
180
|
+
? chalk.hex("#34D399").bold(`@${githubUser.login}`) + chalk.hex("#64748B")(" (Đã kết nối)")
|
|
181
|
+
: chalk.hex("#94A3B8")("Chưa kết nối") + chalk.hex("#64748B")(" [chọn mục GITHUB để login]");
|
|
182
|
+
console.log(boxTop(chalk.hex("#475569")(" [ TRẠNG THÁI ] "), border));
|
|
183
|
+
console.log(boxKvDot("Thư mục", chalk.hex("#F8FAFC")(truncateVisual(targetDir, 32)), 14, border));
|
|
184
|
+
console.log(boxKvDot("Nhánh (Branch)", chalk.hex("#38BDF8").bold(currentBranch), 14, border));
|
|
185
|
+
console.log(boxKvDot("GitHub Account", ghStatus, 14, border));
|
|
186
|
+
console.log(boxBottom(border));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Tiêu đề phân đoạn với điểm nhấn màu sắc
|
|
191
|
+
*/
|
|
192
|
+
export function printSection(title) {
|
|
193
|
+
const W = getW();
|
|
194
|
+
const marker = chalk.hex("#38BDF8").bold("»");
|
|
195
|
+
const prefix = chalk.hex("#334155")("── ") + marker + chalk.bold.hex("#F8FAFC")(` ${String(title || "").trim()} `);
|
|
196
|
+
const remaining = Math.max(0, W - stringWidth(prefix));
|
|
197
|
+
console.log("");
|
|
198
|
+
console.log(prefix + chalk.hex("#334155")("─".repeat(remaining)));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Pill Step Badge (Ví dụ: 1/6)
|
|
203
|
+
*/
|
|
204
|
+
export function stepLabel(current, total, text) {
|
|
205
|
+
const pill = chalk.bgHex("#2563EB").hex("#FFFFFF").bold(` ${current}/${total} `);
|
|
206
|
+
return `${pill} ${chalk.hex("#F1F5F9").bold(text)}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Bảng tóm tắt thông tin trước khi Push
|
|
211
|
+
*/
|
|
212
|
+
export function printSummary({ repoUrl, commitMsg, dateDisplay, branch, force, sourceDir }) {
|
|
213
|
+
const border = chalk.hex("#475569");
|
|
214
|
+
const title = chalk.bold.bgHex("#4338CA").hex("#FFFFFF")(" XÁC NHẬN THÔNG TIN ĐẨY DỰ ÁN ");
|
|
215
|
+
console.log("");
|
|
216
|
+
console.log(boxTop(title, border));
|
|
217
|
+
if (sourceDir) console.log(boxKvDot("Thư mục nguồn", chalk.hex("#34D399")(sourceDir), 16, border));
|
|
218
|
+
console.log(boxKvDot("Git Repository", chalk.hex("#38BDF8").underline(repoUrl), 16, border));
|
|
219
|
+
console.log(boxKvDot("Commit Message", chalk.hex("#FBBF24").bold(commitMsg), 16, border));
|
|
220
|
+
console.log(boxKvDot("Thời gian", chalk.hex("#C084FC")(dateDisplay), 16, border));
|
|
221
|
+
console.log(boxKvDot("Nhánh đẩy lên", chalk.bgHex("#065F46").hex("#6EE7B7").bold(` ${branch} `), 16, border));
|
|
222
|
+
const forceText = force ? chalk.bgHex("#991B1B").hex("#FCA5A5").bold(" BẬT (--force) [!] ") : chalk.hex("#64748B")("Tắt (chế độ an toàn)");
|
|
223
|
+
console.log(boxKvDot("Ghi đè (Force)", forceText, 16, border));
|
|
224
|
+
console.log(boxBottom(border));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function printTargetDir(dir = "") {
|
|
228
|
+
const W = getW();
|
|
229
|
+
console.log(
|
|
230
|
+
chalk.hex("#64748B")(
|
|
231
|
+
` Thư mục: ${truncateVisual(String(dir || ""), W - 13)}`,
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Hiển thị thẻ Profile thông tin tài khoản GitHub
|
|
238
|
+
*/
|
|
239
|
+
export function printGitHubProfile(user, source = '') {
|
|
240
|
+
const border = chalk.hex("#4F46E5");
|
|
241
|
+
const title = chalk.bold.bgHex("#4F46E5").hex("#FFFFFF")(
|
|
242
|
+
" TÀI KHOẢN GITHUB KẾT NỐI ",
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
console.log("");
|
|
246
|
+
console.log(boxTop(title, border));
|
|
247
|
+
|
|
248
|
+
console.log(
|
|
249
|
+
boxKvDot("Username", chalk.hex("#38BDF8").bold(`@${user.login}`), 16, border)
|
|
250
|
+
);
|
|
251
|
+
if (user.name) {
|
|
252
|
+
console.log(boxKvDot("Tên hiển thị", chalk.hex("#F8FAFC")(user.name), 16, border));
|
|
253
|
+
}
|
|
254
|
+
if (user.email) {
|
|
255
|
+
console.log(boxKvDot("Email", chalk.hex("#94A3B8")(user.email), 16, border));
|
|
256
|
+
}
|
|
257
|
+
if (user.bio) {
|
|
258
|
+
console.log(boxKvDot("Tiểu sử", chalk.hex("#CBD5E1")(truncateVisual(user.bio, 40)), 16, border));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const repoStats = `${user.public_repos || 0} public • ${user.total_private_repos || 0} private`;
|
|
262
|
+
console.log(boxKvDot("Repositories", chalk.hex("#34D399").bold(repoStats), 16, border));
|
|
263
|
+
|
|
264
|
+
if (source) {
|
|
265
|
+
const srcDesc = source === 'gh-cli' ? 'GitHub CLI (gh)' : source === 'env' ? 'Biến môi trường' : 'Lưu trữ cá nhân';
|
|
266
|
+
console.log(boxKvDot("Nguồn xác thực", chalk.hex("#A78BFA")(srcDesc), 16, border));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(boxDivider(border));
|
|
270
|
+
console.log(boxRow(chalk.hex("#64748B")(`» Trang cá nhân: ${user.html_url}`), border));
|
|
271
|
+
console.log(boxBottom(border));
|
|
272
|
+
console.log("");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Hiển thị mã xác thực GitHub Device Flow cho người dùng
|
|
277
|
+
*/
|
|
278
|
+
export function printDeviceCodePrompt({ userCode, verificationUri }) {
|
|
279
|
+
const border = chalk.hex("#6366F1");
|
|
280
|
+
const title = chalk.bold.bgHex("#4F46E5").hex("#FFFFFF")(
|
|
281
|
+
" GITHUB OAUTH / DEVICE FLOW ",
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
console.log("");
|
|
285
|
+
console.log(boxTop(title, border));
|
|
286
|
+
console.log(boxRow("", border));
|
|
287
|
+
console.log(boxRow(chalk.hex("#F8FAFC").bold("Bước 1: Mở trình duyệt và truy cập liên kết:"), border));
|
|
288
|
+
console.log(boxRow(chalk.hex("#38BDF8").bold.underline(` ${verificationUri}`), border));
|
|
289
|
+
console.log(boxRow("", border));
|
|
290
|
+
console.log(boxRow(chalk.hex("#F8FAFC").bold("Bước 2: Nhập mã xác nhận (User Code):"), border));
|
|
291
|
+
|
|
292
|
+
const codeBox = chalk.bgHex("#1E1B4B").hex("#A5B4FC").bold(` >>> ${userCode} <<< `);
|
|
293
|
+
console.log(boxRow(` ${codeBox}`, border));
|
|
294
|
+
console.log(boxRow("", border));
|
|
295
|
+
console.log(boxRow(chalk.hex("#94A3B8")("» Hệ thống đang tự động mở trình duyệt cho bạn..."), border));
|
|
296
|
+
console.log(boxRow(chalk.hex("#64748B")("» Nhấn Authorize trên trình duyệt để hoàn tất."), border));
|
|
297
|
+
console.log(boxBottom(border));
|
|
298
|
+
console.log("");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Hiển thị thông báo sau khi tạo repository GitHub mới thành công
|
|
303
|
+
*/
|
|
304
|
+
export function printRepoCreatedBox(repo) {
|
|
305
|
+
const border = chalk.hex("#059669");
|
|
306
|
+
const title = chalk.bold.bgHex("#10B981").hex("#064E3B")(
|
|
307
|
+
" TẠO REPOSITORY GITHUB THÀNH CÔNG! ",
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
console.log("");
|
|
311
|
+
console.log(boxTop(title, border));
|
|
312
|
+
console.log(boxRow("", border));
|
|
313
|
+
|
|
314
|
+
const visibilityPill = repo.isPrivate
|
|
315
|
+
? chalk.bgHex("#7F1D1D").hex("#FCA5A5").bold(" PRIVATE ")
|
|
316
|
+
: chalk.bgHex("#065F46").hex("#6EE7B7").bold(" PUBLIC ");
|
|
317
|
+
|
|
318
|
+
console.log(boxKvDot("Tên Repo", chalk.hex("#F8FAFC").bold(repo.fullName || repo.name), 14, border));
|
|
319
|
+
console.log(boxKvDot("Quyền riêng tư", visibilityPill, 14, border));
|
|
320
|
+
if (repo.description) {
|
|
321
|
+
console.log(boxKvDot("Mô tả", chalk.hex("#CBD5E1")(truncateVisual(repo.description, 42)), 14, border));
|
|
322
|
+
}
|
|
323
|
+
console.log(boxKvDot("HTTPS Clone", chalk.hex("#38BDF8").underline(repo.cloneUrl), 14, border));
|
|
324
|
+
console.log(boxKvDot("SSH Clone", chalk.hex("#A78BFA")(repo.sshUrl), 14, border));
|
|
325
|
+
|
|
326
|
+
console.log(boxRow("", border));
|
|
327
|
+
console.log(boxDivider(border));
|
|
328
|
+
console.log(boxRow(chalk.hex("#6EE7B7")(`» Xem trên GitHub: ${repo.htmlUrl}`), border));
|
|
329
|
+
console.log(boxBottom(border));
|
|
330
|
+
console.log("");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Cảnh báo Folder lồng trong Repo cha
|
|
335
|
+
*/
|
|
336
|
+
export function printNestedRepoWarning({ targetDir = "", topLevel = "" } = {}) {
|
|
337
|
+
const border = chalk.hex("#D97706");
|
|
338
|
+
const title = chalk.bold.bgHex("#D97706").hex("#FFFFFF")(
|
|
339
|
+
" FOLDER NẰM TRONG REPO CHA ",
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
console.log("");
|
|
343
|
+
console.log(boxTop(title, border));
|
|
344
|
+
console.log(
|
|
345
|
+
boxRow(
|
|
346
|
+
chalk.hex("#FDE047")(
|
|
347
|
+
"Folder nguồn đang nằm trong Git repo của folder cha:",
|
|
348
|
+
),
|
|
349
|
+
border,
|
|
350
|
+
),
|
|
351
|
+
);
|
|
352
|
+
console.log(boxKvDot(" +- CHA", chalk.hex("#F8FAFC")(topLevel), 10, border));
|
|
353
|
+
console.log(
|
|
354
|
+
boxKvDot(" +- CON", chalk.hex("#F8FAFC")(targetDir), 10, border),
|
|
355
|
+
);
|
|
356
|
+
console.log(boxRow("", border));
|
|
357
|
+
console.log(
|
|
358
|
+
boxRow(
|
|
359
|
+
chalk.hex("#94A3B8")(
|
|
360
|
+
"Nếu push tiếp, lịch sử/file của folder cha có thể đi kèm.",
|
|
361
|
+
),
|
|
362
|
+
border,
|
|
363
|
+
),
|
|
364
|
+
);
|
|
365
|
+
console.log(
|
|
366
|
+
boxRow(
|
|
367
|
+
chalk.hex("#34D399")(
|
|
368
|
+
"Gợi ý: Tạo repo RIÊNG để chỉ push folder này.",
|
|
369
|
+
),
|
|
370
|
+
border,
|
|
371
|
+
),
|
|
372
|
+
);
|
|
373
|
+
console.log(boxBottom(border));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Cảnh báo Secret nhạy cảm
|
|
378
|
+
*/
|
|
379
|
+
export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
|
|
380
|
+
const border = chalk.hex("#D97706");
|
|
381
|
+
const title = chalk.bold.bgHex("#D97706").hex("#FFFFFF")(
|
|
382
|
+
" PHÁT HIỆN DỮ LIỆU NHẠY CẢM ",
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
console.log("");
|
|
386
|
+
console.log(boxTop(title, border));
|
|
387
|
+
console.log(
|
|
388
|
+
boxRow(
|
|
389
|
+
chalk.hex("#FDE047")(
|
|
390
|
+
"GitHub có thể chặn push nếu phát hiện key bí mật (GH013)",
|
|
391
|
+
),
|
|
392
|
+
border,
|
|
393
|
+
),
|
|
394
|
+
);
|
|
395
|
+
console.log(boxRow("", border));
|
|
396
|
+
|
|
397
|
+
if (riskyFiles.length > 0) {
|
|
398
|
+
console.log(
|
|
399
|
+
boxRow(chalk.bold.hex("#F8FAFC")("File cấu hình nguy cơ:"), border),
|
|
400
|
+
);
|
|
401
|
+
const showFiles = riskyFiles.slice(0, 4);
|
|
402
|
+
showFiles.forEach((f) => {
|
|
403
|
+
const tree = chalk.hex("#64748B")(" +- ");
|
|
404
|
+
console.log(
|
|
405
|
+
boxRow(tree + chalk.hex("#FCD34D")(truncateVisual(f, 46)), border),
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
if (riskyFiles.length > 4) {
|
|
409
|
+
console.log(
|
|
410
|
+
boxRow(
|
|
411
|
+
chalk.hex("#64748B")(` +- ...và ${riskyFiles.length - 4} file khác`),
|
|
412
|
+
border,
|
|
413
|
+
),
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (findings.length > 0) {
|
|
419
|
+
if (riskyFiles.length > 0) console.log(boxRow("", border));
|
|
420
|
+
console.log(
|
|
421
|
+
boxRow(
|
|
422
|
+
chalk.bold.hex("#F8FAFC")("Vị trí nghi vấn Secret trong code:"),
|
|
423
|
+
border,
|
|
424
|
+
),
|
|
425
|
+
);
|
|
426
|
+
const showFindings = findings.slice(0, 4);
|
|
427
|
+
showFindings.forEach((f, idx) => {
|
|
428
|
+
const isLast = idx === showFindings.length - 1;
|
|
429
|
+
const tree = chalk.hex("#64748B")(" +- ");
|
|
430
|
+
const loc = truncateVisual(`${f.file}:${f.line}`, 26);
|
|
431
|
+
console.log(
|
|
432
|
+
boxRow(
|
|
433
|
+
tree +
|
|
434
|
+
chalk.hex("#F8FAFC")(loc) +
|
|
435
|
+
chalk.hex("#94A3B8")(` [${f.type}]`),
|
|
436
|
+
border,
|
|
437
|
+
),
|
|
438
|
+
);
|
|
439
|
+
if (f.preview) {
|
|
440
|
+
const indent = chalk.hex("#64748B")(isLast ? " " : " | ");
|
|
441
|
+
console.log(
|
|
442
|
+
boxRow(
|
|
443
|
+
indent + chalk.hex("#64748B")(`"${truncateVisual(f.preview, 40)}"`),
|
|
444
|
+
border,
|
|
445
|
+
),
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
if (findings.length > 4) {
|
|
450
|
+
console.log(
|
|
451
|
+
boxRow(
|
|
452
|
+
chalk.hex("#64748B")(` +- ...và ${findings.length - 4} vị trí khác`),
|
|
453
|
+
border,
|
|
454
|
+
),
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
console.log(boxRow("", border));
|
|
460
|
+
console.log(
|
|
461
|
+
boxRow(
|
|
462
|
+
chalk.hex("#34D399")(
|
|
463
|
+
'Gợi ý: Chọn "Tự động fix" để gỡ file + .gitignore an toàn',
|
|
464
|
+
),
|
|
465
|
+
border,
|
|
466
|
+
),
|
|
467
|
+
);
|
|
468
|
+
console.log(boxBottom(border));
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Cảnh báo Push bị chặn bởi GitHub Push Protection (GH013)
|
|
473
|
+
*/
|
|
474
|
+
export function printSecretBlockedBox(rawMessage, details = {}) {
|
|
475
|
+
const border = chalk.hex("#E11D48");
|
|
476
|
+
const files = details.files || [];
|
|
477
|
+
const types = details.secretTypes || [];
|
|
478
|
+
const title = chalk.bold.bgHex("#E11D48").hex("#FFFFFF")(
|
|
479
|
+
" PUSH BỊ CHẶN — PHÁT HIỆN SECRET (GH013) ",
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
console.log("");
|
|
483
|
+
console.log(boxTop(title, border));
|
|
484
|
+
console.log(
|
|
485
|
+
boxRow(
|
|
486
|
+
chalk.hex("#FDA4AF")(
|
|
487
|
+
"GitHub Push Protection đã chặn push vì phát hiện secret!",
|
|
488
|
+
),
|
|
489
|
+
border,
|
|
490
|
+
),
|
|
491
|
+
);
|
|
492
|
+
console.log(boxRow("", border));
|
|
493
|
+
|
|
494
|
+
if (types.length > 0) {
|
|
495
|
+
console.log(
|
|
496
|
+
boxKvDot(
|
|
497
|
+
"Loại secret",
|
|
498
|
+
chalk.hex("#F8FAFC").bold(types.slice(0, 2).join(", ")),
|
|
499
|
+
14,
|
|
500
|
+
border,
|
|
501
|
+
),
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
if (files.length > 0) {
|
|
505
|
+
files.slice(0, 2).forEach((f) => {
|
|
506
|
+
console.log(
|
|
507
|
+
boxKvDot("File nguồn", chalk.hex("#FCD34D")(f), 14, border),
|
|
508
|
+
);
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
console.log(boxRow("", border));
|
|
513
|
+
console.log(
|
|
514
|
+
boxRow(chalk.bold.hex("#F8FAFC")("» Các giải pháp khắc phục:"), border),
|
|
515
|
+
);
|
|
516
|
+
console.log(
|
|
517
|
+
boxRow(
|
|
518
|
+
chalk.hex("#E2E8F0")(" [1] Thu hồi (revoke) key ngay nếu là token thật"),
|
|
519
|
+
border,
|
|
520
|
+
),
|
|
521
|
+
);
|
|
522
|
+
console.log(
|
|
523
|
+
boxRow(
|
|
524
|
+
chalk.hex("#38BDF8")(
|
|
525
|
+
" [2] Dùng tool tự động gỡ file & squash làm sạch commit",
|
|
526
|
+
),
|
|
527
|
+
border,
|
|
528
|
+
),
|
|
529
|
+
);
|
|
530
|
+
console.log(
|
|
531
|
+
boxRow(
|
|
532
|
+
chalk.hex("#94A3B8")(
|
|
533
|
+
" [3] Xóa secret trong code / dùng biến môi trường (process.env)",
|
|
534
|
+
),
|
|
535
|
+
border,
|
|
536
|
+
),
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
console.log(boxBottom(border));
|
|
540
|
+
|
|
541
|
+
if (details.unblockUrl) {
|
|
542
|
+
console.log(
|
|
543
|
+
chalk.hex("#F59E0B")(
|
|
544
|
+
`\n » URL mở khóa tạm thời (nếu là key mẫu thử nghiệm):\n ${chalk.underline(details.unblockUrl)}`,
|
|
545
|
+
),
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
console.log(
|
|
549
|
+
chalk.hex("#64748B")(
|
|
550
|
+
`\n » Tài liệu GitHub: ${details.resolveUrl || "https://docs.github.com/code-security/secret-scanning/pushing-a-branch-blocked-by-push-protection"}\n`,
|
|
551
|
+
),
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Card Thông báo Thành công sau khi Push
|
|
557
|
+
*/
|
|
558
|
+
export function printSuccessBox({ branch, repoUrl }) {
|
|
559
|
+
const border = chalk.hex("#059669");
|
|
560
|
+
const title = chalk.bold.bgHex("#10B981").hex("#064E3B")(" ĐẨY DỰ ÁN LÊN GIT THÀNH CÔNG! ");
|
|
561
|
+
console.log("");
|
|
562
|
+
console.log(boxTop(title, border));
|
|
563
|
+
console.log(boxRow("", border));
|
|
564
|
+
const branchPill = chalk.bgHex("#065F46").hex("#6EE7B7").bold(` ${branch} `);
|
|
565
|
+
console.log(boxKvDot("Branch", branchPill, 12, border));
|
|
566
|
+
console.log(boxKvDot("Remote", chalk.hex("#38BDF8").underline(repoUrl), 12, border));
|
|
567
|
+
console.log(boxKvDot("Trạng thái", chalk.hex("#A7F3D0")("Đồng bộ hoàn tất 100%"), 12, border));
|
|
568
|
+
console.log(boxRow("", border));
|
|
569
|
+
console.log(boxRow(chalk.hex("#64748B")("» Xem lịch sử commit: git log -1 --stat"), border));
|
|
570
|
+
console.log(boxBottom(border));
|
|
571
|
+
console.log("");
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export function printCancelled() {
|
|
575
|
+
const border = chalk.hex("#D97706");
|
|
576
|
+
const title = chalk.bold.bgHex("#D97706").hex("#FFFFFF")(
|
|
577
|
+
" ĐÃ HỦY THAO TÁC ",
|
|
578
|
+
);
|
|
579
|
+
console.log("");
|
|
580
|
+
console.log(boxTop(title, border));
|
|
581
|
+
console.log(
|
|
582
|
+
boxRow(
|
|
583
|
+
chalk.hex("#FDE68A")(
|
|
584
|
+
"Không có bất kỳ thay đổi nào được thực hiện trên repo.",
|
|
585
|
+
),
|
|
586
|
+
border,
|
|
587
|
+
),
|
|
588
|
+
);
|
|
589
|
+
console.log(boxBottom(border));
|
|
590
|
+
console.log("");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export function printErrorBox(title = "XẢY RA LỖI", lines = [], hint = "") {
|
|
594
|
+
const W = getW();
|
|
595
|
+
const border = chalk.hex("#E11D48");
|
|
596
|
+
const cleanTitle = String(title || "").toUpperCase();
|
|
597
|
+
const titleStyled = chalk.bold.bgHex("#E11D48").hex("#FFFFFF")(` [LỖI] ${cleanTitle} `);
|
|
598
|
+
const rows = Array.isArray(lines) ? lines : [String(lines || "")];
|
|
599
|
+
|
|
600
|
+
console.log("");
|
|
601
|
+
console.log(boxTop(titleStyled, border));
|
|
602
|
+
rows.forEach((l) => {
|
|
603
|
+
if (!l) {
|
|
604
|
+
console.log(boxRow("", border));
|
|
605
|
+
} else {
|
|
606
|
+
console.log(
|
|
607
|
+
boxRow(chalk.hex("#FDA4AF")(truncateVisual(String(l), W - 4)), border),
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
if (hint) {
|
|
612
|
+
console.log(boxRow("", border));
|
|
613
|
+
console.log(
|
|
614
|
+
boxRow(chalk.hex("#38BDF8")(`» ${truncateVisual(hint, W - 6)}`), border),
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
console.log(boxBottom(border));
|
|
618
|
+
console.log("");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export function printGitMissing() {
|
|
622
|
+
printErrorBox(
|
|
623
|
+
"CHƯA CÀI ĐẶT GIT",
|
|
624
|
+
[
|
|
625
|
+
"Không tìm thấy Git trên hệ thống máy tính của bạn.",
|
|
626
|
+
"Vui lòng tải và cài đặt Git để tiếp tục!",
|
|
627
|
+
],
|
|
628
|
+
"Tải ngay tại: https://git-scm.com/downloads",
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export function printAuthFailedBox(message = "") {
|
|
633
|
+
const border = chalk.hex("#E11D48");
|
|
634
|
+
const title = chalk.bold.bgHex("#E11D48").hex("#FFFFFF")(
|
|
635
|
+
" LỖI XÁC THỰC GIT ",
|
|
636
|
+
);
|
|
637
|
+
const firstLine = String(message || "")
|
|
638
|
+
.split("\n")[0]
|
|
639
|
+
.slice(0, 120);
|
|
640
|
+
|
|
641
|
+
console.log("");
|
|
642
|
+
console.log(boxTop(title, border));
|
|
643
|
+
console.log(
|
|
644
|
+
boxRow(
|
|
645
|
+
chalk.hex("#FDA4AF")(
|
|
646
|
+
"Không thể xác thực với remote. Kiểm tra lại quyền truy cập.",
|
|
647
|
+
),
|
|
648
|
+
border,
|
|
649
|
+
),
|
|
650
|
+
);
|
|
651
|
+
if (firstLine)
|
|
652
|
+
console.log(
|
|
653
|
+
boxRow(
|
|
654
|
+
chalk.hex("#64748B")(truncateVisual(firstLine, getW() - 4)),
|
|
655
|
+
border,
|
|
656
|
+
),
|
|
657
|
+
);
|
|
658
|
+
console.log(boxRow("", border));
|
|
659
|
+
console.log(
|
|
660
|
+
boxRow(chalk.bold.hex("#F8FAFC")("» Cách khắc phục nhanh:"), border),
|
|
661
|
+
);
|
|
662
|
+
console.log(
|
|
663
|
+
boxRow(
|
|
664
|
+
chalk.hex("#E2E8F0")(" [1] Chạy tính năng GITHUB trong tool để kết nối tài khoản"),
|
|
665
|
+
border,
|
|
666
|
+
),
|
|
667
|
+
);
|
|
668
|
+
console.log(
|
|
669
|
+
boxRow(
|
|
670
|
+
chalk.hex("#E2E8F0")(" [2] Dùng lệnh gh: gh auth login"),
|
|
671
|
+
border,
|
|
672
|
+
),
|
|
673
|
+
);
|
|
674
|
+
console.log(
|
|
675
|
+
boxRow(
|
|
676
|
+
chalk.hex("#94A3B8")(" [3] Sử dụng SSH URL: git@github.com:user/repo.git"),
|
|
677
|
+
border,
|
|
678
|
+
),
|
|
679
|
+
);
|
|
680
|
+
console.log(boxBottom(border));
|
|
681
|
+
console.log("");
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export function printNonFastForwardBox(message = "", branch = "main") {
|
|
685
|
+
const border = chalk.hex("#D97706");
|
|
686
|
+
const title = chalk.bold.bgHex("#D97706").hex("#FFFFFF")(
|
|
687
|
+
" REMOTE ĐÃ CÓ COMMIT MỚI HƠN ",
|
|
688
|
+
);
|
|
689
|
+
const firstLine = String(message || "")
|
|
690
|
+
.split("\n")[0]
|
|
691
|
+
.slice(0, 120);
|
|
692
|
+
|
|
693
|
+
console.log("");
|
|
694
|
+
console.log(boxTop(title, border));
|
|
695
|
+
console.log(
|
|
696
|
+
boxRow(
|
|
697
|
+
chalk.hex("#FDE68A")(
|
|
698
|
+
"Kho lưu trữ trên Remote đang chứa commit mới hơn Local.",
|
|
699
|
+
),
|
|
700
|
+
border,
|
|
701
|
+
),
|
|
702
|
+
);
|
|
703
|
+
if (firstLine)
|
|
704
|
+
console.log(
|
|
705
|
+
boxRow(
|
|
706
|
+
chalk.hex("#64748B")(truncateVisual(firstLine, getW() - 4)),
|
|
707
|
+
border,
|
|
708
|
+
),
|
|
709
|
+
);
|
|
710
|
+
console.log(boxRow("", border));
|
|
711
|
+
console.log(
|
|
712
|
+
boxRow(
|
|
713
|
+
chalk.hex("#38BDF8")(
|
|
714
|
+
` » Bạn có thể chọn Pull (rebase) rồi push lại ngay trong tool`,
|
|
715
|
+
),
|
|
716
|
+
border,
|
|
717
|
+
),
|
|
718
|
+
);
|
|
719
|
+
console.log(
|
|
720
|
+
boxRow(
|
|
721
|
+
chalk.hex("#94A3B8")(
|
|
722
|
+
" Chỉ dùng --force khi bạn chắc chắn muốn ghi đè Remote.",
|
|
723
|
+
),
|
|
724
|
+
border,
|
|
725
|
+
),
|
|
726
|
+
);
|
|
727
|
+
console.log(boxBottom(border));
|
|
728
|
+
console.log("");
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
export function printFixResult({
|
|
732
|
+
fixed = [],
|
|
733
|
+
gitignoreAdded = [],
|
|
734
|
+
codeFilesWithSecrets = [],
|
|
735
|
+
failed = [],
|
|
736
|
+
} = {}) {
|
|
737
|
+
const W = getW();
|
|
738
|
+
console.log("");
|
|
739
|
+
if (fixed.length > 0) {
|
|
740
|
+
console.log(
|
|
741
|
+
chalk.hex("#34D399")(
|
|
742
|
+
` [OK] Đã gỡ ${fixed.length} file khỏi commit: ${truncateVisual(fixed.join(", "), W - 32)}`,
|
|
743
|
+
),
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
if (gitignoreAdded.length > 0) {
|
|
747
|
+
console.log(
|
|
748
|
+
chalk.cyan(
|
|
749
|
+
` [+] Thêm vào .gitignore: ${truncateVisual(gitignoreAdded.join(", "), W - 28)}`,
|
|
750
|
+
),
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
if (codeFilesWithSecrets && codeFilesWithSecrets.length > 0) {
|
|
754
|
+
console.log(
|
|
755
|
+
chalk.yellowBright(
|
|
756
|
+
" [!] File code chứa secret (đã gỡ khỏi commit, KHÔNG cho vào .gitignore):",
|
|
757
|
+
),
|
|
758
|
+
);
|
|
759
|
+
codeFilesWithSecrets.forEach((f) =>
|
|
760
|
+
console.log(chalk.yellow(` - ${f}`)),
|
|
761
|
+
);
|
|
762
|
+
console.log(
|
|
763
|
+
chalk.gray(" » Hãy xóa key trong file và thay bằng process.env."),
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
if (failed.length > 0) {
|
|
767
|
+
failed.forEach((f) => console.log(chalk.red(` [x] ${f.file}: ${f.reason}`)));
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export function printFooter() {
|
|
772
|
+
console.log(chalk.hex("#334155")(" ─── » ───"));
|
|
773
|
+
console.log(
|
|
774
|
+
chalk.hex("#64748B")(" » Mẹo: Chạy ") +
|
|
775
|
+
chalk.hex("#38BDF8").bold("git-push-time --help") +
|
|
776
|
+
chalk.hex("#64748B")(" để xem toàn bộ danh sách lệnh và phím tắt."),
|
|
777
|
+
);
|
|
778
|
+
console.log(
|
|
779
|
+
chalk.hex("#475569")(
|
|
780
|
+
" » GitHub CLI & Push Time — Chúc bạn làm việc hiệu quả!\n",
|
|
781
|
+
),
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export function styleCliHelp(program) {
|
|
786
|
+
program.configureHelp({
|
|
787
|
+
styleTitle: (t) => chalk.bold.hex("#38BDF8")(t),
|
|
788
|
+
styleCommandText: (t) => chalk.hex("#F8FAFC")(t),
|
|
789
|
+
styleOptionText: (t) => chalk.hex("#34D399")(t),
|
|
790
|
+
styleArgumentText: (t) => chalk.hex("#FBBF24")(t),
|
|
791
|
+
styleDescriptionText: (t) => chalk.hex("#94A3B8")(t),
|
|
792
|
+
});
|
|
793
|
+
program.addHelpText(
|
|
794
|
+
"after",
|
|
795
|
+
`
|
|
796
|
+
${chalk.hex("#334155")("─── » Ví dụ phổ biến ───")}
|
|
797
|
+
${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time')} # Mở menu điều khiển tương tác trực quan
|
|
798
|
+
${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time github create')} # Tạo nhanh repo mới trên GitHub
|
|
799
|
+
${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time -r <repo-url> -d "-2d" -y')} # Push tự động lùi 2 ngày
|
|
800
|
+
${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time fix')} # Quét & gỡ sạch secret
|
|
801
|
+
${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time pull')} # Cập nhật code mới từ remote
|
|
802
|
+
`,
|
|
803
|
+
);
|
|
804
|
+
}
|