git-push-time 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/index.js CHANGED
@@ -1,291 +1,337 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import { select } from '@inquirer/prompts';
5
- import { checkGitInstalled, getCurrentBranch } from '../lib/git.js';
6
- import { getCurrentGitHubUser } from '../lib/github.js';
7
- import { runPushWizard } from '../lib/commands/push.js';
8
- import { runPullCommand } from '../lib/commands/pull.js';
9
- import { runEditCommitTimeCommand } from '../lib/commands/edit-time.js';
10
- import { runFixCommand } from '../lib/commands/fix.js';
11
- import {
12
- runGitHubMenu,
13
- showGitHubStatus,
14
- promptCreateRepo,
15
- showUserRepos,
16
- promptGitHubLogin,
17
- logoutGitHub
18
- } from '../lib/commands/github.js';
19
- import { promptContinueOrBack } from '../lib/prompt-helpers.js';
20
- import {
21
- printBanner,
22
- printDashboardStatus,
23
- printSection,
24
- printGitMissing,
25
- printCancelled,
26
- styleCliHelp
27
- } from '../lib/ui.js';
28
-
29
- const VERSION = '1.1.0';
30
-
31
- /**
32
- * Kiểm tra xem lệnh có được gọi "trần" (không flag/subcommand nào) hay không.
33
- */
34
- function isBareInvocation(options = {}) {
35
- return (
36
- !options.repo &&
37
- !options.message &&
38
- !options.date &&
39
- !options.branch &&
40
- !options.dir &&
41
- !options.force &&
42
- !options.fix
43
- );
44
- }
45
-
46
- /**
47
- * Menu chính khi chạy `git-push-time` không truyền flag
48
- */
49
- async function promptMainMenu() {
50
- printSection('BẢNG ĐIỀU KHIỂN CHÍNH');
51
- return await select({
52
- message: 'Bạn muốn thực hiện thao tác nào?',
53
- pageSize: 10,
54
- choices: [
55
- {
56
- name: '» [PUSH] | Đẩy code lên Git (kèm chọn folder, backdate & chống lộ secret)',
57
- value: 'push',
58
- description: 'Đẩy dự án lên remote repository hiện tại hoặc mới'
59
- },
60
- {
61
- name: '» [GITHUB] | Quản lý GitHub & Tạo repository mới',
62
- value: 'github',
63
- description: 'Tạo repo mới trên GitHub, xem profile, chọn repo nhanh'
64
- },
65
- {
66
- name: '» [PULL] | Cập nhật code mới về máy',
67
- value: 'pull',
68
- description: 'git pull --ff-only hoặc --rebase từ remote'
69
- },
70
- {
71
- name: '» [TIME] | Sửa thời gian commit lịch sử',
72
- value: 'edit-time',
73
- description: 'Đổi Author/Committer Date của commit trong quá khứ'
74
- },
75
- {
76
- name: '» [FIX] | Quét & gỡ sạch secret khỏi repo',
77
- value: 'fix',
78
- description: 'Quét file nhạy cảm, gỡ khỏi stage và squash commit'
79
- },
80
- {
81
- name: 'x [THOÁT] | Kết thúc chương trình',
82
- value: 'exit',
83
- description: 'Thoát ứng dụng'
84
- }
85
- ]
86
- });
87
- }
88
-
89
- /**
90
- * Vòng lặp Menu chính
91
- */
92
- async function runMainMenuLoop(initialOptions = {}) {
93
- while (true) {
94
- const cwd = process.cwd();
95
- const branch = getCurrentBranch(cwd);
96
- const ghStatus = await getCurrentGitHubUser();
97
-
98
- printDashboardStatus({
99
- targetDir: cwd,
100
- currentBranch: branch,
101
- githubUser: ghStatus.authenticated ? ghStatus.user : null
102
- });
103
-
104
- const action = await promptMainMenu();
105
-
106
- if (action === 'exit') {
107
- printCancelled();
108
- process.exit(0);
109
- }
110
-
111
- if (action === 'push') {
112
- await runPushWizard(initialOptions, async () => {
113
- // Callback quay lại menu chính
114
- return await runMainMenuLoop(initialOptions);
115
- });
116
- return;
117
- }
118
-
119
- if (action === 'github') {
120
- const ghResult = await runGitHubMenu({ defaultDir: cwd });
121
- if (ghResult && ghResult.autoPush && ghResult.repoUrl) {
122
- // Sau khi tạo repo mới, chuyển tiếp thẳng vào luồng push
123
- await runPushWizard({ ...initialOptions, repo: ghResult.repoUrl });
124
- return;
125
- }
126
- } else if (action === 'pull') {
127
- await runPullCommand('origin', undefined, { fromMenu: true });
128
- } else if (action === 'edit-time') {
129
- await runEditCommitTimeCommand({ fromMenu: true });
130
- } else if (action === 'fix') {
131
- await runFixCommand({ fromMenu: true });
132
- }
133
-
134
- const next = await promptContinueOrBack('Hoàn tất! Bạn muốn tiếp tục hay quay lại bảng điều khiển?');
135
- if (next === 'exit') {
136
- process.exit(0);
137
- }
138
- }
139
- }
140
-
141
- async function main(options = {}) {
142
- printBanner(VERSION);
143
-
144
- if (!checkGitInstalled()) {
145
- printGitMissing();
146
- process.exit(1);
147
- }
148
-
149
- const isInteractive = !options.yes;
150
-
151
- if (isInteractive && isBareInvocation(options)) {
152
- await runMainMenuLoop(options);
153
- return;
154
- }
155
-
156
- // Chạy trực tiếp push wizard với các options được cung cấp
157
- await runPushWizard(options);
158
- }
159
-
160
- /* ==========================================================================
161
- COMMANDER CLI CONFIGURATION
162
- ========================================================================== */
163
-
164
- const program = new Command();
165
- styleCliHelp(program);
166
-
167
- program
168
- .name('git-push-time')
169
- .description('Bộ công cụ Git & GitHub CLI: Đẩy code siêu tốc, kết nối GitHub tạo repo, chỉnh thời gian commit và bảo vệ secret')
170
- .version(VERSION)
171
- .option('-r, --repo <url>', 'Đường dẫn Git repository (URL hoặc để trống để chọn/tạo mới)')
172
- .option('-d, --date <datetime>', 'Thời gian commit (VD: "2024-01-15 14:30:00", "-2d", "-5h", "now")')
173
- .option('-m, --message <msg>', 'Thông điệp commit (Commit message)')
174
- .option('-b, --branch <branch>', 'Tên branch trên remote (Mặc định: main)')
175
- .option('-C, --dir <path>', 'Thư mục nguồn cần push (Mặc định: thư mục hiện tại)')
176
- .option('-f, --force', 'Thực hiện push đè (--force)', false)
177
- .option('-y, --yes', 'Bỏ qua giao diện hỏi, tự động dùng tham số hoặc giá trị mặc định', false)
178
- .option('--fix', 'Tự động gỡ file secret khỏi stage & thêm .gitignore (an toàn, không xóa file thật)', false)
179
- .action(async (opts) => {
180
- await main(opts);
181
- });
182
-
183
- // LỆNH GITHUB
184
- const ghCmd = program
185
- .command('github')
186
- .description('Quản lý tài khoản GitHub và tạo repository mới trực tiếp')
187
- .action(async () => {
188
- printBanner(VERSION);
189
- await runGitHubMenu();
190
- });
191
-
192
- ghCmd
193
- .command('create')
194
- .description('Tạo repository mới trên GitHub (Public hoặc Private)')
195
- .option('-C, --dir <path>', 'Thư mục nguồn cần liên kết')
196
- .action(async (opts) => {
197
- printBanner(VERSION);
198
- const res = await promptCreateRepo({ targetDir: opts.dir || process.cwd() });
199
- if (res.ok && res.repo) {
200
- console.log(`\n » Repo URL: ${res.repo.cloneUrl}\n`);
201
- }
202
- });
203
-
204
- ghCmd
205
- .command('whoami')
206
- .description('Xem thông tin tài khoản GitHub đang kết nối')
207
- .action(async () => {
208
- printBanner(VERSION);
209
- await showGitHubStatus();
210
- });
211
-
212
- ghCmd
213
- .command('repos')
214
- .description('Liệt kê danh sách repository trên GitHub của bạn')
215
- .action(async () => {
216
- printBanner(VERSION);
217
- await showUserRepos();
218
- });
219
-
220
- ghCmd
221
- .command('login')
222
- .description('Đăng nhập tài khoản GitHub bằng OAuth Device Flow hoặc Token')
223
- .action(async () => {
224
- printBanner(VERSION);
225
- await promptGitHubLogin();
226
- });
227
-
228
- ghCmd
229
- .command('logout')
230
- .description('Đăng xuất tài khoản GitHub khỏi máy')
231
- .action(async () => {
232
- printBanner(VERSION);
233
- logoutGitHub();
234
- });
235
-
236
- // LỆNH LOGIN / LOGOUT / WHOAMI TRỰC TIẾP
237
- program
238
- .command('login')
239
- .description('Đăng nhập GitHub qua OAuth Device Flow (mở trình duyệt) hoặc Token')
240
- .action(async () => {
241
- printBanner(VERSION);
242
- await promptGitHubLogin();
243
- });
244
-
245
- program
246
- .command('whoami')
247
- .description('Xem thông tin tài khoản GitHub đang kết nối')
248
- .action(async () => {
249
- printBanner(VERSION);
250
- await showGitHubStatus();
251
- });
252
-
253
- program
254
- .command('logout')
255
- .description('Đăng xuất tài khoản GitHub khỏi máy')
256
- .action(async () => {
257
- printBanner(VERSION);
258
- logoutGitHub();
259
- });
260
-
261
- // LỆNH FIX
262
- program
263
- .command('fix')
264
- .description('Quét & tự động gỡ file secret khỏi stage, cập nhật .gitignore và làm sạch commit unpushed')
265
- .option('-C, --dir <path>', 'Thư mục cần quét fix (Mặc định: thư mục hiện tại)')
266
- .action(async (opts) => {
267
- printBanner(VERSION);
268
- await runFixCommand(opts);
269
- });
270
-
271
- // LỆNH PULL
272
- program
273
- .command('pull [remote] [branch]')
274
- .description('Cập nhật code từ remote bằng fast-forward --ff-only hoặc --rebase')
275
- .option('-C, --dir <path>', 'Thư mục repo cần pull (Mặc định: thư mục hiện tại)')
276
- .action(async (remote, branch, opts) => {
277
- printBanner(VERSION);
278
- await runPullCommand(remote || 'origin', branch, opts || {});
279
- });
280
-
281
- // LỆNH EDIT-TIME
282
- program
283
- .command('edit-time')
284
- .description('Sửa thời gian của commit lịch sử (Backdate commit hash)')
285
- .option('-C, --dir <path>', 'Thư mục repo (Mặc định: thư mục hiện tại)')
286
- .action(async (opts) => {
287
- printBanner(VERSION);
288
- await runEditCommitTimeCommand(opts);
289
- });
290
-
291
- program.parse(process.argv);
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { select } from '@inquirer/prompts';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { checkGitInstalled, getCurrentBranch } from '../lib/git.js';
9
+ import { getCurrentGitHubUser } from '../lib/github.js';
10
+ import { runPushWizard } from '../lib/commands/push.js';
11
+ import { runPullCommand } from '../lib/commands/pull.js';
12
+ import { runEditCommitTimeCommand } from '../lib/commands/edit-time.js';
13
+ import { runFixCommand } from '../lib/commands/fix.js';
14
+ import {
15
+ runGitHubMenu,
16
+ promptCreateRepo,
17
+ showUserRepos,
18
+ } from '../lib/commands/github.js';
19
+ import { promptContinueOrBack } from '../lib/prompt-helpers.js';
20
+ import {
21
+ printBanner,
22
+ printDashboardStatus,
23
+ printSection,
24
+ printGitMissing,
25
+ printCancelled,
26
+ styleCliHelp
27
+ } from '../lib/ui.js';
28
+
29
+ const __filename = fileURLToPath(import.meta.url);
30
+ const __dirname = path.dirname(__filename);
31
+
32
+ function getPackageVersion() {
33
+ try {
34
+ const raw = fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8');
35
+ return JSON.parse(raw).version || '1.1.0';
36
+ } catch {
37
+ return '1.1.0';
38
+ }
39
+ }
40
+
41
+ const VERSION = getPackageVersion();
42
+
43
+ function isExitPromptError(err) {
44
+ const msg = String(err?.message || '');
45
+ return (
46
+ err?.name === 'ExitPromptError' ||
47
+ msg.includes('User force closed the prompt') ||
48
+ msg.includes('SIGINT')
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Kiểm tra xem lệnh có được gọi "trần" (không flag/subcommand nào) hay không.
54
+ */
55
+ function isBareInvocation(options = {}) {
56
+ return (
57
+ !options.repo &&
58
+ !options.message &&
59
+ !options.date &&
60
+ !options.branch &&
61
+ !options.dir &&
62
+ !options.force &&
63
+ !options.fix
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Menu chính khi chạy `git-push-time` không truyền flag
69
+ */
70
+ async function promptMainMenu() {
71
+ printSection('BẢNG ĐIỀU KHIỂN CHÍNH');
72
+ return await select({
73
+ message: 'Bạn muốn thực hiện thao tác nào?',
74
+ pageSize: 10,
75
+ choices: [
76
+ {
77
+ name: 'Đẩy dự án lên Git',
78
+ value: 'push',
79
+ description: 'Chọn repository, thời gian commit và đẩy code'
80
+ },
81
+ {
82
+ name: 'Quản lý GitHub',
83
+ value: 'github',
84
+ description: 'Tạo repository, xem tài khoản và quản lý kết nối'
85
+ },
86
+ {
87
+ name: 'Pull code mới',
88
+ value: 'pull',
89
+ description: 'Cập nhật branch hiện tại từ remote'
90
+ },
91
+ {
92
+ name: 'Sửa thời gian commit',
93
+ value: 'edit-time',
94
+ description: 'Chọn commit trong lịch sử và cập nhật ngày giờ'
95
+ },
96
+ {
97
+ name: 'Quét secret',
98
+ value: 'fix',
99
+ description: 'Tìm file nhạy cảm trước khi đẩy code'
100
+ },
101
+ {
102
+ name: 'Thoát',
103
+ value: 'exit',
104
+ description: 'Thoát ứng dụng'
105
+ }
106
+ ]
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Vòng lặp Menu chính (không đệ quy — callback back chỉ trả sentinel)
112
+ */
113
+ async function runMainMenuLoop(initialOptions = {}) {
114
+ while (true) {
115
+ const cwd = process.cwd();
116
+ let branch = 'main';
117
+ try {
118
+ branch = getCurrentBranch(cwd);
119
+ } catch {}
120
+ let ghStatus = { authenticated: false };
121
+ try {
122
+ ghStatus = await getCurrentGitHubUser();
123
+ } catch {
124
+ ghStatus = { authenticated: false };
125
+ }
126
+
127
+ printDashboardStatus({
128
+ targetDir: cwd,
129
+ currentBranch: branch,
130
+ githubUser: ghStatus.authenticated ? ghStatus.user : null
131
+ });
132
+
133
+ let action;
134
+ try {
135
+ action = await promptMainMenu();
136
+ } catch (err) {
137
+ if (isExitPromptError(err)) {
138
+ printCancelled();
139
+ process.exit(0);
140
+ }
141
+ throw err;
142
+ }
143
+
144
+ if (action === 'exit') {
145
+ printCancelled();
146
+ process.exit(0);
147
+ }
148
+
149
+ try {
150
+ if (action === 'push') {
151
+ const res = await runPushWizard(initialOptions, async () => 'back');
152
+ // 'back' = user muốn về menu → tiếp tục loop; ngược lại push xong thì thoát
153
+ if (res === 'back') continue;
154
+ return;
155
+ }
156
+
157
+ if (action === 'github') {
158
+ const ghResult = await runGitHubMenu({ defaultDir: cwd });
159
+ if (ghResult && ghResult.autoPush && ghResult.repoUrl) {
160
+ // Sau khi tạo repo mới, chuyển tiếp thẳng vào luồng push
161
+ const res = await runPushWizard({ ...initialOptions, repo: ghResult.repoUrl }, async () => 'back');
162
+ if (res === 'back') continue;
163
+ return;
164
+ }
165
+ } else if (action === 'pull') {
166
+ await runPullCommand('origin', undefined, { fromMenu: true });
167
+ } else if (action === 'edit-time') {
168
+ await runEditCommitTimeCommand({ fromMenu: true });
169
+ } else if (action === 'fix') {
170
+ await runFixCommand({ fromMenu: true });
171
+ }
172
+ } catch (err) {
173
+ if (isExitPromptError(err)) {
174
+ printCancelled();
175
+ process.exit(0);
176
+ }
177
+ throw err;
178
+ }
179
+
180
+ let next;
181
+ try {
182
+ next = await promptContinueOrBack('Hoàn tất! Bạn muốn tiếp tục hay quay lại bảng điều khiển?');
183
+ } catch (err) {
184
+ if (isExitPromptError(err)) {
185
+ printCancelled();
186
+ process.exit(0);
187
+ }
188
+ throw err;
189
+ }
190
+ if (next === 'exit') {
191
+ process.exit(0);
192
+ }
193
+ }
194
+ }
195
+
196
+ async function main(options = {}) {
197
+ printBanner(VERSION);
198
+
199
+ if (!checkGitInstalled()) {
200
+ printGitMissing();
201
+ process.exit(1);
202
+ }
203
+
204
+ const isInteractive = !options.yes;
205
+
206
+ if (isInteractive && isBareInvocation(options)) {
207
+ try {
208
+ await runMainMenuLoop(options);
209
+ } catch (err) {
210
+ if (isExitPromptError(err)) {
211
+ printCancelled();
212
+ process.exit(0);
213
+ }
214
+ throw err;
215
+ }
216
+ return;
217
+ }
218
+
219
+ // Chạy trực tiếp push wizard với các options được cung cấp
220
+ try {
221
+ await runPushWizard(options);
222
+ } catch (err) {
223
+ if (isExitPromptError(err)) {
224
+ printCancelled();
225
+ process.exit(0);
226
+ }
227
+ throw err;
228
+ }
229
+ }
230
+
231
+ /* ==========================================================================
232
+ COMMANDER CLI CONFIGURATION
233
+ ========================================================================== */
234
+
235
+ const program = new Command();
236
+ styleCliHelp(program);
237
+
238
+ program
239
+ .name('git-push-time')
240
+ .description('Bộ công cụ Git & GitHub CLI: Đẩy code siêu tốc, kết nối GitHub tạo repo, chỉnh thời gian commit và bảo vệ secret')
241
+ .version(VERSION)
242
+ .option('-r, --repo <url>', 'Đường dẫn Git repository (URL hoặc để trống để chọn/tạo mới)')
243
+ .option('-d, --date <datetime>', 'Thời gian commit (VD: "2024-01-15 14:30:00", "-2d", "-5h", "now")')
244
+ .option('-m, --message <msg>', 'Thông điệp commit (Commit message)')
245
+ .option('-b, --branch <branch>', 'Tên branch trên remote (Mặc định: main)')
246
+ .option('-C, --dir <path>', 'Thư mục nguồn cần push (Mặc định: thư mục hiện tại)')
247
+ .option('-f, --force', 'Thực hiện push đè (--force)', false)
248
+ .option('-y, --yes', 'Bỏ qua giao diện hỏi, tự động dùng tham số hoặc giá trị mặc định', false)
249
+ .option('--fix', 'Tự động gỡ file secret khỏi stage & thêm .gitignore (an toàn, không xóa file thật)', false)
250
+ .action(async (opts) => {
251
+ await main(opts);
252
+ });
253
+
254
+ // LỆNH GITHUB
255
+ const ghCmd = program
256
+ .command('github')
257
+ .description('Quản lý tài khoản GitHub và tạo repository mới trực tiếp')
258
+ .action(async () => {
259
+ printBanner(VERSION);
260
+ await runGitHubMenu();
261
+ });
262
+
263
+ ghCmd
264
+ .command('create')
265
+ .description('Tạo repository mới trên GitHub (Public hoặc Private)')
266
+ .option('-C, --dir <path>', 'Thư mục nguồn cần liên kết')
267
+ .action(async (opts) => {
268
+ printBanner(VERSION);
269
+ const res = await promptCreateRepo({ targetDir: opts.dir || process.cwd() });
270
+ if (res.ok && res.repo) {
271
+ console.log(`\n » Repo URL: ${res.repo.cloneUrl}\n`);
272
+ }
273
+ });
274
+
275
+ ghCmd
276
+ .command('whoami')
277
+ .description('Xem thông tin tài khoản GitHub đang kết nối')
278
+ .action(async () => {
279
+ printBanner(VERSION);
280
+ await showGitHubStatus();
281
+ });
282
+
283
+ ghCmd
284
+ .command('repos')
285
+ .description('Liệt kê danh sách repository trên GitHub của bạn')
286
+ .action(async () => {
287
+ printBanner(VERSION);
288
+ await showUserRepos();
289
+ });
290
+
291
+ ghCmd
292
+ .command('login')
293
+ .description('Đăng nhập tài khoản GitHub bằng OAuth Device Flow hoặc Token')
294
+ .action(async () => {
295
+ printBanner(VERSION);
296
+ await promptGitHubLogin();
297
+ });
298
+
299
+ ghCmd
300
+ .command('logout')
301
+ .description('Đăng xuất tài khoản GitHub khỏi máy')
302
+ .action(async () => {
303
+ printBanner(VERSION);
304
+ logoutGitHub();
305
+ });
306
+
307
+ // LỆNH FIX
308
+ program
309
+ .command('fix')
310
+ .description('Quét & tự động gỡ file secret khỏi stage, cập nhật .gitignore và làm sạch commit unpushed')
311
+ .option('-C, --dir <path>', 'Thư mục cần quét fix (Mặc định: thư mục hiện tại)')
312
+ .action(async (opts) => {
313
+ printBanner(VERSION);
314
+ await runFixCommand(opts);
315
+ });
316
+
317
+ // LỆNH PULL
318
+ program
319
+ .command('pull [remote] [branch]')
320
+ .description('Cập nhật code từ remote bằng fast-forward --ff-only hoặc --rebase')
321
+ .option('-C, --dir <path>', 'Thư mục repo cần pull (Mặc định: thư mục hiện tại)')
322
+ .action(async (remote, branch, opts) => {
323
+ printBanner(VERSION);
324
+ await runPullCommand(remote || 'origin', branch, opts || {});
325
+ });
326
+
327
+ // LỆNH EDIT-TIME
328
+ program
329
+ .command('edit-time')
330
+ .description('Sửa thời gian của commit lịch sử (Backdate commit hash)')
331
+ .option('-C, --dir <path>', 'Thư mục repo (Mặc định: thư mục hiện tại)')
332
+ .action(async (opts) => {
333
+ printBanner(VERSION);
334
+ await runEditCommitTimeCommand(opts);
335
+ });
336
+
337
+ program.parse(process.argv);