git-push-time 1.0.2 → 1.1.0

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