git-push-time 1.1.1 → 1.1.2
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 +1 -2
- package/README.md +219 -215
- package/bin/index.js +25 -23
- package/lib/commands/github.js +16 -10
- package/lib/commands/pull.js +3 -2
- package/lib/commands/push.js +84 -37
- package/lib/git.js +24 -0
- package/lib/prompt-helpers.js +194 -62
- package/lib/ui.js +359 -100
- package/package.json +2 -1
package/lib/ui.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import boxen from "boxen";
|
|
2
3
|
import stringWidth from "string-width";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* Chiều rộng khung thống nhất cho toàn bộ UI.
|
|
7
|
+
* - Luôn bằng nhau giữa dashboard / banner / prompt / box chi tiết
|
|
8
|
+
* nên các khung xếp chồng không bị lệch (vỡ giao diện).
|
|
9
|
+
* - Terminal hẹp thì co theo `cols - 2`, terminal rộng thì cap 76.
|
|
6
10
|
*/
|
|
7
11
|
export function getW() {
|
|
8
|
-
const cols = process.stdout.columns
|
|
9
|
-
|
|
12
|
+
const cols = process.stdout.columns;
|
|
13
|
+
if (!cols || cols < 10) return 72;
|
|
14
|
+
const avail = cols - 2;
|
|
15
|
+
// Terminal quá hẹp (<34): dùng đúng phần hiển thị được, tránh tràn dòng.
|
|
16
|
+
if (avail < 34) return Math.max(24, avail);
|
|
17
|
+
return Math.min(76, avail);
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
function fitStyled(text, maxWidth) {
|
|
13
|
-
const value = String(text ?? '');
|
|
21
|
+
const value = String(text ?? '').replace(/[\r\n\t]+/g, ' ');
|
|
14
22
|
if (maxWidth <= 0) return '';
|
|
15
23
|
if (stringWidth(value) <= maxWidth) return value;
|
|
16
24
|
|
|
@@ -73,58 +81,93 @@ export function truncateVisual(str, maxLen) {
|
|
|
73
81
|
return fitStyled(str, maxLen);
|
|
74
82
|
}
|
|
75
83
|
|
|
84
|
+
/** Khung tiêu đề nhất quán cho từng bước tương tác trong terminal. */
|
|
85
|
+
export function framePrompt(message) {
|
|
86
|
+
// Dùng cùng getW() với mọi box khác để các khung luôn thẳng hàng,
|
|
87
|
+
// không còn tình trạng prompt hẹp (56) còn dashboard rộng (76) gây vỡ.
|
|
88
|
+
return boxen(String(message ?? ""), {
|
|
89
|
+
borderStyle: "round",
|
|
90
|
+
borderColor: "#818CF8",
|
|
91
|
+
padding: { left: 1, right: 1, top: 0, bottom: 0 },
|
|
92
|
+
width: getW(),
|
|
93
|
+
textAlignment: "center",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
76
97
|
/* ==========================================================================
|
|
77
98
|
BOX ENGINE CHUẨN (Bo góc mềm & Căn lề pixel-perfect)
|
|
78
99
|
========================================================================== */
|
|
79
100
|
|
|
80
101
|
export function boxTop(title = "", borderColor = chalk.hex("#334155")) {
|
|
81
102
|
const W = getW();
|
|
82
|
-
if (!title) return borderColor("╭" + "─".repeat(W - 2) + "╮");
|
|
103
|
+
if (!title) return borderColor("╭" + "─".repeat(Math.max(2, W - 2)) + "╮");
|
|
83
104
|
|
|
84
|
-
|
|
105
|
+
// Căn giữa tiêu đề để 2 bên đều nhau: ╭──── Title ────╮
|
|
106
|
+
// Chừa 1 space mỗi bên title + tối thiểu 2 gạch mỗi bên.
|
|
107
|
+
const maxTitle = Math.max(4, W - 8);
|
|
108
|
+
const fittedTitle = fitStyled(title, maxTitle);
|
|
85
109
|
const titleWidth = stringWidth(fittedTitle);
|
|
86
|
-
const
|
|
87
|
-
const
|
|
110
|
+
const remaining = Math.max(4, W - 2 - titleWidth - 2);
|
|
111
|
+
const leftLen = Math.ceil(remaining / 2);
|
|
112
|
+
const rightLen = Math.floor(remaining / 2);
|
|
88
113
|
return (
|
|
89
|
-
borderColor("╭" +
|
|
114
|
+
borderColor("╭" + "─".repeat(leftLen) + " ") +
|
|
90
115
|
fittedTitle +
|
|
91
|
-
borderColor("─".repeat(
|
|
116
|
+
borderColor(" " + "─".repeat(rightLen) + "╮")
|
|
92
117
|
);
|
|
93
118
|
}
|
|
94
119
|
|
|
120
|
+
/** Khung tiêu đề căn trái theo lề nội dung của boxRow. */
|
|
121
|
+
export function boxTopLeft(title = "", borderColor = chalk.hex("#334155")) {
|
|
122
|
+
const W = getW();
|
|
123
|
+
if (!title) return borderColor("╭" + "─".repeat(Math.max(2, W - 2)) + "╮");
|
|
124
|
+
const fittedTitle = truncateVisual(title, W - 6);
|
|
125
|
+
const titleWidth = stringWidth(fittedTitle);
|
|
126
|
+
const rightDash = Math.max(0, W - titleWidth - 4);
|
|
127
|
+
return borderColor("╭ ") + fittedTitle + borderColor(" " + "─".repeat(rightDash) + "╮");
|
|
128
|
+
}
|
|
129
|
+
|
|
95
130
|
export function boxBottom(borderColor = chalk.hex("#334155")) {
|
|
96
131
|
const W = getW();
|
|
97
|
-
return borderColor("╰" + "─".repeat(W - 2) + "╯");
|
|
132
|
+
return borderColor("╰" + "─".repeat(Math.max(2, W - 2)) + "╯");
|
|
98
133
|
}
|
|
99
134
|
|
|
100
135
|
export function boxRow(content = "", borderColor = chalk.hex("#334155")) {
|
|
101
136
|
const W = getW();
|
|
102
|
-
|
|
137
|
+
const inner = Math.max(4, W - 4);
|
|
138
|
+
return borderColor("│ ") + pad(fitStyled(content, inner), inner) + borderColor(" │");
|
|
103
139
|
}
|
|
104
140
|
|
|
105
141
|
export function boxDivider(borderColor = chalk.hex("#334155")) {
|
|
106
142
|
const W = getW();
|
|
107
|
-
return borderColor("├" + "─".repeat(W - 2) + "┤");
|
|
143
|
+
return borderColor("├" + "─".repeat(Math.max(2, W - 2)) + "┤");
|
|
108
144
|
}
|
|
109
145
|
|
|
110
146
|
/**
|
|
111
|
-
* Hàng Key-Value có đường nối bằng dấu chấm
|
|
147
|
+
* Hàng Key-Value có đường nối bằng dấu chấm · · ·
|
|
148
|
+
* Dùng "·" (U+00B7) thay vì "․" (U+2024) để không bị vỡ font trên
|
|
149
|
+
* Windows PowerShell / Consolas. Label không còn bị cắt oan khi
|
|
150
|
+
* terminal hẹp nhờ công thức fitted mới.
|
|
151
|
+
*/
|
|
152
|
+
/**
|
|
153
|
+
* Hàng Key-Value có đường nối bằng dấu chấm · · ·
|
|
154
|
+
* Căn lề chuẩn xác, khoảng cách đều và không bị vỡ font trên mọi terminal.
|
|
112
155
|
*/
|
|
113
156
|
export function boxKvDot(
|
|
114
157
|
label,
|
|
115
158
|
value,
|
|
116
|
-
labelWidth =
|
|
159
|
+
labelWidth = 14,
|
|
117
160
|
borderColor = chalk.hex("#334155"),
|
|
118
161
|
) {
|
|
119
162
|
const W = getW();
|
|
120
|
-
const innerWidth = W - 4;
|
|
121
|
-
const fittedLabelWidth = Math.min(labelWidth, Math.max(
|
|
122
|
-
const maxValWidth = Math.max(4, innerWidth - fittedLabelWidth -
|
|
163
|
+
const innerWidth = Math.max(8, W - 4);
|
|
164
|
+
const fittedLabelWidth = Math.min(labelWidth, Math.max(4, innerWidth - 10));
|
|
165
|
+
const maxValWidth = Math.max(4, innerWidth - fittedLabelWidth - 4);
|
|
123
166
|
const truncatedVal = truncateVisual(value, maxValWidth);
|
|
124
167
|
const valWidth = stringWidth(truncatedVal);
|
|
125
168
|
|
|
126
|
-
const dotsLen = Math.max(
|
|
127
|
-
const dots = chalk.hex("#
|
|
169
|
+
const dotsLen = Math.max(2, innerWidth - fittedLabelWidth - valWidth - 2);
|
|
170
|
+
const dots = " " + chalk.hex("#334155")("·".repeat(dotsLen)) + " ";
|
|
128
171
|
const formattedLabel = chalk.hex("#94A3B8")(
|
|
129
172
|
pad(fitStyled(label, fittedLabelWidth), fittedLabelWidth),
|
|
130
173
|
);
|
|
@@ -136,66 +179,204 @@ export function boxKvDot(
|
|
|
136
179
|
PUBLIC INTERFACE & BANNER
|
|
137
180
|
========================================================================== */
|
|
138
181
|
|
|
182
|
+
/* ==========================================================================
|
|
183
|
+
CYBER CAT MASCOT — 20x12 pixel art grid, two pixel rows per terminal cell
|
|
184
|
+
========================================================================== */
|
|
185
|
+
|
|
186
|
+
const MASCOT_COLORS = {
|
|
187
|
+
P: "#818CF8", // Indigo ears
|
|
188
|
+
W: "#F8FAFC", // White face
|
|
189
|
+
C: "#38BDF8", // Cyan visor
|
|
190
|
+
E: "#06B6D4", // Visor eye glow
|
|
191
|
+
K: "#0F172A", // Dark visor frame
|
|
192
|
+
M: "#F43F5E", // Rose nose/mouth
|
|
193
|
+
G: "#10B981", // Emerald badge
|
|
194
|
+
B: "#334155", // Slate base
|
|
195
|
+
".": "transparent",
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const MASCOT_GRID = [
|
|
199
|
+
"..PP............PP......",
|
|
200
|
+
".PPPP..........PPPP.....",
|
|
201
|
+
".PPWW..........WWPP.....",
|
|
202
|
+
"WWWWWWWWWWWWWWWWWWWW....",
|
|
203
|
+
"WWKKKKKKKKKKKKKKWWWW....",
|
|
204
|
+
"WWKCCCCCCCCCCCCKWWWW....",
|
|
205
|
+
"WWKCEKEEEEEEKECKWWWW....",
|
|
206
|
+
"WWKKKKKKKKKKKKKKWWWW....",
|
|
207
|
+
"WWWWWWWWMMWWWWWWWWWW....",
|
|
208
|
+
".WWWWWWWWWWWWWWWWWW..P..",
|
|
209
|
+
"..WWWW..GGGG..WWWWWWWW..",
|
|
210
|
+
"..BBBB..BBBB..BBBBWWWW..",
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
export function mascotLines() {
|
|
214
|
+
const lines = [];
|
|
215
|
+
for (let y = 0; y < MASCOT_GRID.length; y += 2) {
|
|
216
|
+
let line = "";
|
|
217
|
+
for (let x = 0; x < MASCOT_GRID[y].length; x++) {
|
|
218
|
+
const top = MASCOT_GRID[y][x];
|
|
219
|
+
const bot = MASCOT_GRID[y + 1][x];
|
|
220
|
+
if (top === "." && bot === ".") line += " ";
|
|
221
|
+
else if (top === "." || bot === ".") {
|
|
222
|
+
const pixel = top === "." ? bot : top;
|
|
223
|
+
line += chalk.hex(MASCOT_COLORS[pixel])(top === "." ? "▄" : "▀");
|
|
224
|
+
} else if (top === bot) {
|
|
225
|
+
line += chalk.hex(MASCOT_COLORS[top])("█");
|
|
226
|
+
} else {
|
|
227
|
+
line += chalk.hex(MASCOT_COLORS[top]).bgHex(MASCOT_COLORS[bot])("▀");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
lines.push(line);
|
|
231
|
+
}
|
|
232
|
+
return lines;
|
|
233
|
+
}
|
|
234
|
+
|
|
139
235
|
/**
|
|
140
|
-
* Banner
|
|
236
|
+
* Banner: Cyber Cat Mascot + Wordmark + Badges
|
|
141
237
|
*/
|
|
142
|
-
export function printBanner(version = "1.1.
|
|
143
|
-
const
|
|
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));
|
|
238
|
+
export function printBanner(version = "1.1.1") {
|
|
239
|
+
const W = getW();
|
|
240
|
+
const inner = Math.max(20, W - 2 - 4);
|
|
155
241
|
const badges = [
|
|
156
242
|
chalk.bgHex("#1E1B4B").hex("#818CF8").bold(` v${version} `),
|
|
157
243
|
chalk.bgHex("#064E3B").hex("#34D399").bold(" SMART GIT "),
|
|
158
|
-
chalk.bgHex("#
|
|
159
|
-
chalk.bgHex("#
|
|
160
|
-
chalk.bgHex("#
|
|
244
|
+
chalk.bgHex("#0C4A6E").hex("#38BDF8").bold(" GITHUB SYNC "),
|
|
245
|
+
chalk.bgHex("#4C0519").hex("#FB7185").bold(" SECRET SHIELD "),
|
|
246
|
+
chalk.bgHex("#451A03").hex("#FBBF24").bold(" BACKDATE "),
|
|
161
247
|
];
|
|
162
|
-
|
|
248
|
+
const rows = [];
|
|
249
|
+
let row = "";
|
|
163
250
|
for (const badge of badges) {
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
row =
|
|
168
|
-
} else row
|
|
251
|
+
const next = row ? `${row} ${badge}` : badge;
|
|
252
|
+
if (row && stringWidth(next) > inner) {
|
|
253
|
+
rows.push(row);
|
|
254
|
+
row = badge;
|
|
255
|
+
} else row = next;
|
|
169
256
|
}
|
|
170
|
-
if (row
|
|
171
|
-
|
|
257
|
+
if (row) rows.push(row);
|
|
258
|
+
|
|
259
|
+
const title = multiGradient("G I T P U S H T I M E", [
|
|
260
|
+
"#818CF8",
|
|
261
|
+
"#38BDF8",
|
|
262
|
+
"#2DD4BF",
|
|
263
|
+
"#F472B6",
|
|
264
|
+
]);
|
|
265
|
+
const subtitle = chalk.hex("#94A3B8")(
|
|
266
|
+
"Git & GitHub CLI · Đẩy code siêu tốc · An toàn · Đổi giờ commit",
|
|
267
|
+
);
|
|
268
|
+
const mascot = mascotLines();
|
|
269
|
+
const body = [...mascot, "", title, subtitle, "", ...rows].join("\n");
|
|
270
|
+
const frame = boxen(body, {
|
|
271
|
+
borderStyle: "round",
|
|
272
|
+
borderColor: "#6366F1",
|
|
273
|
+
padding: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
274
|
+
width: W,
|
|
275
|
+
textAlignment: "center",
|
|
276
|
+
});
|
|
277
|
+
console.log(`\n${frame}\n`);
|
|
172
278
|
}
|
|
173
279
|
|
|
174
280
|
/**
|
|
175
|
-
* Hiển thị Dashboard trạng thái ngắn gọn ở đầu menu
|
|
281
|
+
* Hiển thị Dashboard trạng thái ngắn gọn & trực quan ở đầu menu
|
|
176
282
|
*/
|
|
177
|
-
export function printDashboardStatus({
|
|
178
|
-
|
|
283
|
+
export function printDashboardStatus({
|
|
284
|
+
targetDir = process.cwd(),
|
|
285
|
+
currentBranch = "main",
|
|
286
|
+
githubUser = null,
|
|
287
|
+
remoteUrl = null,
|
|
288
|
+
changes = 0,
|
|
289
|
+
lastCommit = null,
|
|
290
|
+
version = "1.1.1",
|
|
291
|
+
}) {
|
|
292
|
+
const border = chalk.hex("#6366F1");
|
|
293
|
+
const W = getW();
|
|
294
|
+
const remote = remoteUrl ? formatRemoteLabel(remoteUrl) : chalk.hex("#94A3B8")("Chưa liên kết");
|
|
295
|
+
|
|
179
296
|
const ghStatus = githubUser
|
|
180
|
-
? chalk.hex("#
|
|
181
|
-
: chalk.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
297
|
+
? `${chalk.hex("#38BDF8").bold(`@${githubUser.login}`)} ${chalk.bgHex("#064E3B").hex("#34D399").bold(" [✓ ĐÃ KẾT NỐI] ")}`
|
|
298
|
+
: chalk.bgHex("#1E1B4B").hex("#A5B4FC").bold(" [○ CHƯA ĐĂNG NHẬP] ");
|
|
299
|
+
|
|
300
|
+
const branchPill = changes === 0
|
|
301
|
+
? chalk.bgHex("#064E3B").hex("#34D399").bold(" [✓ SẠCH] ")
|
|
302
|
+
: chalk.bgHex("#78350F").hex("#FDE047").bold(` [● ${changes} THAY ĐỔI] `);
|
|
303
|
+
|
|
304
|
+
const branchDisplay = `${chalk.hex("#38BDF8").bold(currentBranch)} ${branchPill}`;
|
|
305
|
+
|
|
306
|
+
console.log("");
|
|
307
|
+
console.log(
|
|
308
|
+
boxTop(
|
|
309
|
+
`${chalk.bold.hex("#F8FAFC")(" BẢNG ĐIỀU KHIỂN GIT ")} ${chalk.hex("#94A3B8")(`v${version}`)}`,
|
|
310
|
+
border,
|
|
311
|
+
),
|
|
312
|
+
);
|
|
313
|
+
console.log(boxRow("", border));
|
|
314
|
+
console.log(boxKvDot("Thư mục", chalk.hex("#F1F5F9").bold(targetDir), 14, border));
|
|
315
|
+
console.log(boxKvDot("Nhánh Git", branchDisplay, 14, border));
|
|
316
|
+
console.log(boxKvDot("GitHub", ghStatus, 14, border));
|
|
317
|
+
console.log(boxKvDot("Remote", remote, 14, border));
|
|
318
|
+
|
|
319
|
+
if (lastCommit && lastCommit.hash) {
|
|
320
|
+
const commitText = `${chalk.hex("#A78BFA").bold(lastCommit.hash)} ${chalk.hex("#CBD5E1")(truncateVisual(lastCommit.message || "", 26))} ${chalk.hex("#64748B")(`(${lastCommit.relativeTime || ""})`)}`;
|
|
321
|
+
console.log(boxKvDot("Commit gần", commitText, 14, border));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.log(boxRow("", border));
|
|
186
325
|
console.log(boxBottom(border));
|
|
187
326
|
}
|
|
188
327
|
|
|
328
|
+
function formatRemoteLabel(url) {
|
|
329
|
+
const value = String(url || "").trim().replace(/\.git$/i, "");
|
|
330
|
+
const match = value.match(/(?:github\.com[:/])([^/]+\/[^/]+)$/i);
|
|
331
|
+
return match ? chalk.hex("#38BDF8")(match[1]) : chalk.hex("#CBD5E1")(value);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function printPushProgress({ current, total = 6, title, completed = [] }) {
|
|
335
|
+
if (process.stdout.isTTY) process.stdout.write("\u001b[2J\u001b[H");
|
|
336
|
+
const nodes = Array.from({ length: total }, (_, index) => {
|
|
337
|
+
const n = index + 1;
|
|
338
|
+
if (n < current) return chalk.hex("#34D399")("●");
|
|
339
|
+
if (n === current) return chalk.hex("#38BDF8").bold("●");
|
|
340
|
+
return chalk.hex("#475569")("○");
|
|
341
|
+
});
|
|
342
|
+
const rail = nodes.join(chalk.hex("#334155")("──"));
|
|
343
|
+
const lead = `${chalk.hex("#38BDF8").bold("PUSH PROGRESS")} ${rail}`;
|
|
344
|
+
const detail = chalk.hex("#94A3B8")(`[Bước ${current}/${total} · ${title}]`);
|
|
345
|
+
console.log("");
|
|
346
|
+
if (stringWidth(`${lead} ${detail}`) <= getW() - 2) console.log(`${lead} ${detail}`);
|
|
347
|
+
else {
|
|
348
|
+
console.log(lead);
|
|
349
|
+
console.log(` ${detail}`);
|
|
350
|
+
}
|
|
351
|
+
if (completed.length) {
|
|
352
|
+
console.log("");
|
|
353
|
+
for (const item of completed) {
|
|
354
|
+
console.log(
|
|
355
|
+
` ${chalk.hex("#34D399")("✓")} ${chalk.hex("#94A3B8")(item.label.padEnd(16))} ${chalk.hex("#334155")("·····")} ${chalk.hex("#F1F5F9")(truncateVisual(item.value, getW() - 28))}`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
189
361
|
/**
|
|
190
362
|
* Tiêu đề phân đoạn với điểm nhấn màu sắc
|
|
191
363
|
*/
|
|
192
364
|
export function printSection(title) {
|
|
193
365
|
const W = getW();
|
|
194
|
-
const marker = chalk.hex("#
|
|
195
|
-
const
|
|
196
|
-
const
|
|
366
|
+
const marker = chalk.hex("#A78BFA").bold("■");
|
|
367
|
+
const maxTitle = Math.max(4, W - 12);
|
|
368
|
+
const fittedTitle = fitStyled(String(title || "").trim(), maxTitle);
|
|
369
|
+
const coreWidth = stringWidth(fittedTitle) + 4;
|
|
370
|
+
const remaining = Math.max(4, W - coreWidth);
|
|
371
|
+
const leftLen = Math.ceil(remaining / 2);
|
|
372
|
+
const rightLen = Math.floor(remaining / 2);
|
|
197
373
|
console.log("");
|
|
198
|
-
console.log(
|
|
374
|
+
console.log(
|
|
375
|
+
chalk.hex("#334155")("─".repeat(leftLen) + " ") +
|
|
376
|
+
marker +
|
|
377
|
+
chalk.bold.hex("#E2E8F0")(` ${fittedTitle} `) +
|
|
378
|
+
chalk.hex("#334155")("─".repeat(rightLen)),
|
|
379
|
+
);
|
|
199
380
|
}
|
|
200
381
|
|
|
201
382
|
/**
|
|
@@ -210,17 +391,22 @@ export function stepLabel(current, total, text) {
|
|
|
210
391
|
* Bảng tóm tắt thông tin trước khi Push
|
|
211
392
|
*/
|
|
212
393
|
export function printSummary({ repoUrl, commitMsg, dateDisplay, branch, force, sourceDir }) {
|
|
213
|
-
const border = chalk.hex("#
|
|
214
|
-
const title = chalk.bold.
|
|
394
|
+
const border = chalk.hex("#38BDF8");
|
|
395
|
+
const title = chalk.bold.hex("#F8FAFC")(" XÁC NHẬN THÔNG TIN PUSH ");
|
|
215
396
|
console.log("");
|
|
216
397
|
console.log(boxTop(title, border));
|
|
217
|
-
|
|
218
|
-
console.log(boxKvDot("
|
|
219
|
-
console.log(boxKvDot("
|
|
220
|
-
console.log(boxKvDot("
|
|
221
|
-
console.log(boxKvDot("
|
|
222
|
-
|
|
223
|
-
console.log(boxKvDot("
|
|
398
|
+
console.log(boxRow("", border));
|
|
399
|
+
if (sourceDir) console.log(boxKvDot("Thư mục nguồn", chalk.hex("#F1F5F9")(sourceDir), 16, border));
|
|
400
|
+
console.log(boxKvDot("Kho lưu trữ", formatRemoteLabel(repoUrl), 16, border));
|
|
401
|
+
console.log(boxKvDot("Nhánh đích", chalk.hex("#38BDF8").bold(branch), 16, border));
|
|
402
|
+
console.log(boxKvDot("Thông điệp", chalk.hex("#FDE047")(`"${commitMsg}"`), 16, border));
|
|
403
|
+
console.log(boxKvDot("Thời điểm", chalk.hex("#A78BFA")(dateDisplay), 16, border));
|
|
404
|
+
console.log(boxKvDot("Quét Secret", chalk.hex("#34D399")("✓ Tự động kiểm tra trước khi đẩy"), 16, border));
|
|
405
|
+
if (force) {
|
|
406
|
+
console.log(boxRow("", border));
|
|
407
|
+
console.log(boxRow(chalk.hex("#F43F5E").bold(" [!] CẢNH BÁO: Chế độ ghi đè (--force) đang kích hoạt!"), border));
|
|
408
|
+
}
|
|
409
|
+
console.log(boxRow("", border));
|
|
224
410
|
console.log(boxBottom(border));
|
|
225
411
|
}
|
|
226
412
|
|
|
@@ -394,6 +580,11 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
|
|
|
394
580
|
);
|
|
395
581
|
console.log(boxRow("", border));
|
|
396
582
|
|
|
583
|
+
const W2 = getW();
|
|
584
|
+
const maxFileW = Math.max(10, W2 - 14);
|
|
585
|
+
const maxLocW = Math.max(10, W2 - 24);
|
|
586
|
+
const maxPrevW = Math.max(10, W2 - 18);
|
|
587
|
+
|
|
397
588
|
if (riskyFiles.length > 0) {
|
|
398
589
|
console.log(
|
|
399
590
|
boxRow(chalk.bold.hex("#F8FAFC")("File cấu hình nguy cơ:"), border),
|
|
@@ -402,7 +593,7 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
|
|
|
402
593
|
showFiles.forEach((f) => {
|
|
403
594
|
const tree = chalk.hex("#64748B")(" +- ");
|
|
404
595
|
console.log(
|
|
405
|
-
boxRow(tree + chalk.hex("#FCD34D")(truncateVisual(f,
|
|
596
|
+
boxRow(tree + chalk.hex("#FCD34D")(truncateVisual(f, maxFileW)), border),
|
|
406
597
|
);
|
|
407
598
|
});
|
|
408
599
|
if (riskyFiles.length > 4) {
|
|
@@ -427,7 +618,7 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
|
|
|
427
618
|
showFindings.forEach((f, idx) => {
|
|
428
619
|
const isLast = idx === showFindings.length - 1;
|
|
429
620
|
const tree = chalk.hex("#64748B")(" +- ");
|
|
430
|
-
const loc = truncateVisual(`${f.file}:${f.line}`,
|
|
621
|
+
const loc = truncateVisual(`${f.file}:${f.line}`, maxLocW);
|
|
431
622
|
console.log(
|
|
432
623
|
boxRow(
|
|
433
624
|
tree +
|
|
@@ -440,7 +631,7 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
|
|
|
440
631
|
const indent = chalk.hex("#64748B")(isLast ? " " : " | ");
|
|
441
632
|
console.log(
|
|
442
633
|
boxRow(
|
|
443
|
-
indent + chalk.hex("#64748B")(`"${truncateVisual(f.preview,
|
|
634
|
+
indent + chalk.hex("#64748B")(`"${truncateVisual(f.preview, maxPrevW)}"`),
|
|
444
635
|
border,
|
|
445
636
|
),
|
|
446
637
|
);
|
|
@@ -555,37 +746,72 @@ export function printSecretBlockedBox(rawMessage, details = {}) {
|
|
|
555
746
|
/**
|
|
556
747
|
* Card Thông báo Thành công sau khi Push
|
|
557
748
|
*/
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
749
|
+
/**
|
|
750
|
+
* Card Thông báo Thành công sau khi Push
|
|
751
|
+
*/
|
|
752
|
+
export function printSuccessBox({
|
|
753
|
+
branch,
|
|
754
|
+
repoUrl,
|
|
755
|
+
durationSeconds = null,
|
|
756
|
+
secretStatus = "Đã quét (0 rủi ro)",
|
|
757
|
+
}) {
|
|
758
|
+
const border = chalk.hex("#10B981");
|
|
759
|
+
const title = chalk.bold.hex("#F8FAFC")(" PUSH THÀNH CÔNG ");
|
|
561
760
|
console.log("");
|
|
562
761
|
console.log(boxTop(title, border));
|
|
563
762
|
console.log(boxRow("", border));
|
|
564
|
-
|
|
565
|
-
console.log(boxKvDot("
|
|
566
|
-
|
|
567
|
-
|
|
763
|
+
console.log(boxKvDot("Nhánh đích", chalk.hex("#34D399").bold(branch), 18, border));
|
|
764
|
+
console.log(boxKvDot("Trạng thái Secret", chalk.hex("#38BDF8")(secretStatus), 18, border));
|
|
765
|
+
if (durationSeconds !== null) {
|
|
766
|
+
console.log(
|
|
767
|
+
boxKvDot(
|
|
768
|
+
"Thời gian xử lý",
|
|
769
|
+
chalk.hex("#FBBF24").bold(`${durationSeconds.toFixed(1)}s`),
|
|
770
|
+
18,
|
|
771
|
+
border,
|
|
772
|
+
),
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
const webUrl = toGitHubWebUrl(repoUrl);
|
|
776
|
+
if (webUrl) {
|
|
777
|
+
console.log(boxRow("", border));
|
|
778
|
+
console.log(boxDivider(border));
|
|
779
|
+
console.log(
|
|
780
|
+
boxRow(
|
|
781
|
+
`${chalk.hex("#34D399")("» Xem trên GitHub:")} ${chalk.hex("#38BDF8").underline(webUrl)}`,
|
|
782
|
+
border,
|
|
783
|
+
),
|
|
784
|
+
);
|
|
785
|
+
} else {
|
|
786
|
+
console.log(boxKvDot("Remote", chalk.hex("#CBD5E1")(truncateVisual(repoUrl, getW() - 14)), 18, border));
|
|
787
|
+
}
|
|
568
788
|
console.log(boxRow("", border));
|
|
569
|
-
console.log(boxRow(chalk.hex("#64748B")("» Xem lịch sử commit: git log -1 --stat"), border));
|
|
570
789
|
console.log(boxBottom(border));
|
|
571
790
|
console.log("");
|
|
572
791
|
}
|
|
573
792
|
|
|
793
|
+
function toGitHubWebUrl(url) {
|
|
794
|
+
const match = String(url || "").trim().match(/(?:https?:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([^/]+\/[^/]+?)(?:\.git)?$/i);
|
|
795
|
+
return match ? `https://github.com/${match[1]}` : null;
|
|
796
|
+
}
|
|
797
|
+
|
|
574
798
|
export function printCancelled() {
|
|
575
|
-
const border = chalk.hex("#
|
|
576
|
-
const title = chalk.bold.
|
|
799
|
+
const border = chalk.hex("#64748B");
|
|
800
|
+
const title = chalk.bold.hex("#F8FAFC")(
|
|
577
801
|
" ĐÃ HỦY THAO TÁC ",
|
|
578
802
|
);
|
|
579
803
|
console.log("");
|
|
580
804
|
console.log(boxTop(title, border));
|
|
805
|
+
console.log(boxRow("", border));
|
|
581
806
|
console.log(
|
|
582
807
|
boxRow(
|
|
583
|
-
chalk.hex("#
|
|
584
|
-
"Không có
|
|
808
|
+
chalk.hex("#94A3B8")(
|
|
809
|
+
"Thao tác đã dừng lại an toàn. Không có thay đổi nào được thực hiện.",
|
|
585
810
|
),
|
|
586
811
|
border,
|
|
587
812
|
),
|
|
588
813
|
);
|
|
814
|
+
console.log(boxRow("", border));
|
|
589
815
|
console.log(boxBottom(border));
|
|
590
816
|
console.log("");
|
|
591
817
|
}
|
|
@@ -735,51 +961,84 @@ export function printFixResult({
|
|
|
735
961
|
failed = [],
|
|
736
962
|
} = {}) {
|
|
737
963
|
const W = getW();
|
|
964
|
+
const border = chalk.hex("#10B981");
|
|
965
|
+
const title = chalk.bold.hex("#F8FAFC")(" KẾT QUẢ BẢO VỆ SECRET ");
|
|
966
|
+
|
|
738
967
|
console.log("");
|
|
968
|
+
console.log(boxTop(title, border));
|
|
969
|
+
console.log(boxRow("", border));
|
|
970
|
+
|
|
739
971
|
if (fixed.length > 0) {
|
|
740
972
|
console.log(
|
|
741
|
-
|
|
742
|
-
|
|
973
|
+
boxRow(
|
|
974
|
+
`${chalk.hex("#34D399").bold("[OK] Đã gỡ khỏi commit:")} ${chalk.hex("#F1F5F9")(truncateVisual(fixed.join(", "), W - 28))}`,
|
|
975
|
+
border,
|
|
743
976
|
),
|
|
744
977
|
);
|
|
745
978
|
}
|
|
746
979
|
if (gitignoreAdded.length > 0) {
|
|
747
980
|
console.log(
|
|
748
|
-
|
|
749
|
-
|
|
981
|
+
boxRow(
|
|
982
|
+
`${chalk.hex("#38BDF8").bold("[+] Thêm vào .gitignore:")} ${chalk.hex("#CBD5E1")(truncateVisual(gitignoreAdded.join(", "), W - 30))}`,
|
|
983
|
+
border,
|
|
750
984
|
),
|
|
751
985
|
);
|
|
752
986
|
}
|
|
753
987
|
if (codeFilesWithSecrets && codeFilesWithSecrets.length > 0) {
|
|
988
|
+
console.log(boxRow("", border));
|
|
754
989
|
console.log(
|
|
755
|
-
|
|
756
|
-
"
|
|
990
|
+
boxRow(
|
|
991
|
+
chalk.hex("#FBBF24").bold("[!] File code chứa secret (đã gỡ stage, KHÔNG ignore):"),
|
|
992
|
+
border,
|
|
757
993
|
),
|
|
758
994
|
);
|
|
759
995
|
codeFilesWithSecrets.forEach((f) =>
|
|
760
|
-
console.log(
|
|
996
|
+
console.log(boxRow(` - ${chalk.hex("#CBD5E1")(truncateVisual(f, W - 10))}`, border)),
|
|
761
997
|
);
|
|
762
998
|
console.log(
|
|
763
|
-
chalk.
|
|
999
|
+
boxRow(chalk.hex("#94A3B8")(" » Hãy xóa key trong file và thay bằng process.env."), border),
|
|
764
1000
|
);
|
|
765
1001
|
}
|
|
766
1002
|
if (failed.length > 0) {
|
|
767
|
-
|
|
1003
|
+
console.log(boxRow("", border));
|
|
1004
|
+
failed.forEach((f) =>
|
|
1005
|
+
console.log(boxRow(chalk.hex("#F43F5E")(` [x] ${f.file}: ${f.reason}`), border)),
|
|
1006
|
+
);
|
|
768
1007
|
}
|
|
1008
|
+
|
|
1009
|
+
console.log(boxRow("", border));
|
|
1010
|
+
console.log(boxBottom(border));
|
|
1011
|
+
console.log("");
|
|
769
1012
|
}
|
|
770
1013
|
|
|
771
1014
|
export function printFooter() {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
);
|
|
1015
|
+
const W = getW();
|
|
1016
|
+
// Dải phân cách căn đều 2 bên, full width như mọi khung.
|
|
1017
|
+
const core = "»";
|
|
1018
|
+
const remaining = Math.max(4, W - 3); // 1 core + 2 space
|
|
1019
|
+
const leftLen = Math.ceil(remaining / 2);
|
|
1020
|
+
const rightLen = Math.floor(remaining / 2);
|
|
778
1021
|
console.log(
|
|
779
|
-
chalk.hex("#
|
|
780
|
-
|
|
781
|
-
)
|
|
1022
|
+
chalk.hex("#334155")("─".repeat(leftLen) + " ") +
|
|
1023
|
+
chalk.hex("#64748B")(core) +
|
|
1024
|
+
chalk.hex("#334155")(" " + "─".repeat(rightLen))
|
|
782
1025
|
);
|
|
1026
|
+
const tip1 =
|
|
1027
|
+
chalk.hex("#64748B")("» Mẹo: Chạy ") +
|
|
1028
|
+
chalk.hex("#38BDF8").bold("git-push-time --help") +
|
|
1029
|
+
chalk.hex("#64748B")(" để xem toàn bộ lệnh.");
|
|
1030
|
+
const tip2 = chalk.hex("#475569")("» GitHub CLI & Push Time — Chúc hiệu quả!");
|
|
1031
|
+
for (const tip of [tip1, tip2]) {
|
|
1032
|
+
const w = stringWidth(tip);
|
|
1033
|
+
if (w >= W) {
|
|
1034
|
+
console.log(` ${tip}`);
|
|
1035
|
+
} else {
|
|
1036
|
+
const padTotal = W - w;
|
|
1037
|
+
const left = Math.floor(padTotal / 2);
|
|
1038
|
+
console.log(" ".repeat(left) + tip);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
console.log("");
|
|
783
1042
|
}
|
|
784
1043
|
|
|
785
1044
|
export function styleCliHelp(program) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-push-time",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "CLI tool to push project to Git with custom repo URL and custom commit timestamp",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@inquirer/prompts": "^7.3.2",
|
|
29
|
+
"boxen": "^9.0.0",
|
|
29
30
|
"chalk": "^5.4.1",
|
|
30
31
|
"commander": "^13.1.0",
|
|
31
32
|
"dayjs": "^1.11.13",
|