git-push-time 1.0.0 → 1.0.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/lib/ui.js ADDED
@@ -0,0 +1,409 @@
1
+ import chalk from 'chalk';
2
+ import stringWidth from 'string-width';
3
+
4
+ const W = 66; // Chiều rộng chuẩn của card
5
+
6
+ // Chuyển mã hex thành [r, g, b]
7
+ function hexToRgb(hex) {
8
+ const n = parseInt(hex.replace('#', ''), 16);
9
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
10
+ }
11
+
12
+ /**
13
+ * Tạo gradient màu mượt mà qua một mảng các màu hex
14
+ */
15
+ export function multiGradient(text, colors = ['#8B5CF6', '#3B82F6', '#06B6D4']) {
16
+ const chars = [...text];
17
+ const len = chars.length || 1;
18
+ const rgbColors = colors.map(hexToRgb);
19
+
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('');
31
+ }
32
+
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) {
37
+ const vLen = stringWidth(str);
38
+ if (vLen >= len) return str;
39
+ return str + ' '.repeat(len - vLen);
40
+ }
41
+
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) {
46
+ if (stringWidth(str) <= maxLen) return str;
47
+ let res = '';
48
+ for (const ch of str) {
49
+ if (stringWidth(res + ch + '…') > maxLen) {
50
+ return res + '…';
51
+ }
52
+ res += ch;
53
+ }
54
+ return res + '…';
55
+ }
56
+
57
+ function boxTop(title = '', borderColor = chalk.hex('#3B82F6')) {
58
+ if (!title) {
59
+ return borderColor('╭' + '─'.repeat(W - 2) + '╮');
60
+ }
61
+ const titleWidth = stringWidth(title);
62
+ const leftDash = '─'.repeat(3);
63
+ const rightDashLen = Math.max(0, W - 2 - 3 - titleWidth);
64
+ return borderColor('╭' + leftDash) + title + borderColor('─'.repeat(rightDashLen) + '╮');
65
+ }
66
+
67
+ function boxBottom(borderColor = chalk.hex('#3B82F6')) {
68
+ return borderColor('╰' + '─'.repeat(W - 2) + '╯');
69
+ }
70
+
71
+ function boxDivider(borderColor = chalk.hex('#3B82F6')) {
72
+ return borderColor('├' + '─'.repeat(W - 2) + '┤');
73
+ }
74
+
75
+ function boxRow(content = '', borderColor = chalk.hex('#3B82F6')) {
76
+ return borderColor('│ ') + pad(content, W - 4) + borderColor(' │');
77
+ }
78
+
79
+ /**
80
+ * Banner thương hiệu cao cấp dạng ASCII Art + Gradient
81
+ */
82
+ export function printBanner(version = '1.0.0') {
83
+ const ascii = [
84
+ ' ____ _ _ ____ _ _____ _ ',
85
+ ' / ___(_) |_ | _ \\ _ _ ___| |__ |_ _(_)_ __ ___ ___ ',
86
+ ' | | _| | __| | |_) | | | / __| \'_ \\ | | | | \'_ ` _ \\ / _ \\ ',
87
+ ' | |_| | | |_ | __/| |_| \\__ \\ | | | | | | | | | | | | __/ ',
88
+ ' \\____|_|\\__| |_| \\__,_|___/_| |_| |_| |_|_| |_| |_|\\___| '
89
+ ];
90
+
91
+ console.log('');
92
+ const colors = ['#A855F7', '#6366F1', '#38BDF8', '#34D399'];
93
+ ascii.forEach((l) => console.log(multiGradient(l, colors)));
94
+
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 ');
99
+
100
+ console.log(`\n ${badgeVer} ${badgeSpeed} ${badgeShield} ${badgeDate}\n`);
101
+ }
102
+
103
+ /**
104
+ * Tiêu đề phân đoạn với styling thanh lịch
105
+ */
106
+ 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;
110
+ const remaining = Math.max(0, W - stringWidth(prefix));
111
+ console.log('');
112
+ console.log(prefix + chalk.hex('#475569')('─'.repeat(remaining)));
113
+ }
114
+
115
+ /**
116
+ * Nhãn bước tiến trình dạng pill badge: [ 1/6 ] Khởi tạo Git repo...
117
+ */
118
+ export function stepLabel(current, total, text) {
119
+ const pill = chalk.bgHex('#3B82F6').hex('#FFFFFF').bold(` ${current}/${total} `);
120
+ return `${pill} ` + chalk.hex('#E2E8F0')(text);
121
+ }
122
+
123
+ /**
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)
125
+ */
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('');
133
+ 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));
138
+
139
+ 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));
143
+ console.log(boxBottom(border));
144
+ }
145
+
146
+ /**
147
+ * Card cảnh báo file / nội dung secret trước khi commit (Theme Amber / Gold)
148
+ */
149
+ 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 ');
152
+
153
+ console.log('');
154
+ 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));
157
+
158
+ 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));
164
+ });
165
+ if (riskyFiles.length > 5) {
166
+ console.log(boxRow(chalk.hex('#94A3B8')(` └─ …và ${riskyFiles.length - 5} file khác`), border));
167
+ }
168
+ }
169
+
170
+ 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));
178
+ if (f.preview) {
179
+ const indent = chalk.hex('#64748B')(isLast ? ' ' : ' │ ');
180
+ console.log(boxRow(indent + chalk.hex('#64748B')(`"${truncateVisual(f.preview, 44)}"`), border));
181
+ }
182
+ });
183
+ if (findings.length > 5) {
184
+ console.log(boxRow(chalk.hex('#94A3B8')(` └─ …và ${findings.length - 5} vị trí khác`), border));
185
+ }
186
+ }
187
+
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));
190
+ console.log(boxBottom(border));
191
+ }
192
+
193
+ /**
194
+ * Card cảnh báo khi push bị GitHub Push Protection chặn (Theme Crimson / Ruby)
195
+ */
196
+ export function printSecretBlockedBox(rawMessage, details = {}) {
197
+ const border = chalk.hex('#EF4444');
198
+ const files = details.files || [];
199
+ const types = details.secretTypes || [];
200
+ const title = chalk.bold.hex('#FEE2E2')(' 🚫 PUSH BỊ CHẶN — PHÁT HIỆN SECRET (GH013) ');
201
+
202
+ console.log('');
203
+ 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));
206
+
207
+ if (types.length > 0) {
208
+ console.log(boxRow(chalk.hex('#94A3B8')('🔑 Loại secret : ') + chalk.hex('#F8FAFC').bold(types.slice(0, 2).join(', ')), border));
209
+ }
210
+ 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));
213
+ });
214
+ }
215
+
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));
222
+
223
+ console.log(boxBottom(border));
224
+
225
+ 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)}`));
227
+ }
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`));
229
+ }
230
+
231
+ /**
232
+ * Card thông báo thành công rực rỡ (Theme Emerald / Mint Green)
233
+ */
234
+ 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! ');
237
+
238
+ console.log('');
239
+ 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));
246
+ console.log(boxBottom(border));
247
+ console.log('');
248
+ }
249
+
250
+ /**
251
+ * Thông báo hủy thao tác
252
+ */
253
+ export function printCancelled() {
254
+ const border = chalk.hex('#F59E0B');
255
+ const title = chalk.bold.hex('#FEF3C7')(' ✋ ĐÃ HỦY THAO TÁC ');
256
+ console.log('');
257
+ 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));
259
+ console.log(boxBottom(border));
260
+ console.log('');
261
+ }
262
+
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 || '')];
270
+
271
+ console.log('');
272
+ console.log(boxTop(titleStyled, border));
273
+ rows.forEach((l) => {
274
+ if (!l) {
275
+ console.log(boxRow('', border));
276
+ } else {
277
+ console.log(boxRow(chalk.hex('#FCA5A5')(truncateVisual(String(l), W - 4)), border));
278
+ }
279
+ });
280
+ if (hint) {
281
+ console.log(boxRow('', border));
282
+ console.log(boxRow(chalk.hex('#38BDF8')(`👉 ${truncateVisual(hint, W - 8)}`), border));
283
+ }
284
+ console.log(boxBottom(border));
285
+ console.log('');
286
+ }
287
+
288
+ /**
289
+ * Lỗi chưa cài Git
290
+ */
291
+ 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');
295
+ }
296
+
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);
304
+
305
+ console.log('');
306
+ 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));
314
+ console.log(boxBottom(border));
315
+ console.log('');
316
+ }
317
+
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);
325
+
326
+ console.log('');
327
+ 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));
333
+ console.log(boxBottom(border));
334
+ console.log('');
335
+ }
336
+
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 = [] } = {}) {
341
+ if (fixed.length > 0) {
342
+ console.log(chalk.hex('#34D399')(` ✓ Đã gỡ ${fixed.length} file khỏi commit: ${truncateVisual(fixed.join(', '), 46)}`));
343
+ }
344
+ if (gitignoreAdded.length > 0) {
345
+ console.log(chalk.cyan(` + .gitignore: ${truncateVisual(gitignoreAdded.join(', '), 48)}`));
346
+ }
347
+ 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).'));
351
+ }
352
+ if (failed.length > 0) {
353
+ failed.forEach((f) => console.log(chalk.red(` ✗ ${f.file}: ${f.reason}`)));
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;
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
+ 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'));
386
+ }
387
+
388
+ /**
389
+ * Style cho --help của commander cho đồng bộ theme
390
+ */
391
+ export function styleCliHelp(program) {
392
+ 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),
398
+ });
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));
409
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-push-time",
3
- "version": "1.0.0",
3
+ "version": "1.0.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
  "chalk": "^5.4.1",
27
27
  "commander": "^13.1.0",
28
28
  "dayjs": "^1.11.13",
29
- "ora": "^8.2.0"
29
+ "ora": "^8.2.0",
30
+ "string-width": "^7.2.0"
30
31
  }
31
32
  }