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