git-push-time 1.0.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/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # 🚀 GitHub CLI (`githubcli`)
2
+
3
+ Một công cụ Dòng lệnh (CLI) mạnh mẽ và tiện lợi giúp bạn **đẩy dự án lên Git (GitHub, GitLab, Bitbucket,...) cực nhanh** chỉ bằng cách dán URL repo, đồng thời hỗ trợ **tùy chỉnh hoặc lùi thời gian commit/push** (Author Date & Committer Date).
4
+
5
+ ---
6
+
7
+ ## ✨ Tính năng nổi bật
8
+
9
+ - 🔗 **Đẩy dự án siêu tốc**: Chỉ cần dán URL Git repository, công cụ tự động làm mọi thao tác `git init`, `git remote add/set-url`, `git add .`, `git commit`, `git push`.
10
+ - ⏰ **Tùy chỉnh thời gian commit (Git Backdate)**:
11
+ - Lùi ngày/giờ bằng chuỗi ngắn gọn: `-2d` (2 ngày trước), `-5h` (5 giờ trước), `-1w` (1 tuần trước), `-30m` (30 phút trước).
12
+ - Hoặc nhập ngày giờ chuẩn chính xác: `2024-01-15 14:30:00`.
13
+ - Cập nhật cả `GIT_AUTHOR_DATE` và `GIT_COMMITTER_DATE` chuẩn mực.
14
+ - 💬 **Chế độ Tương tác (Interactive Prompt)**: Giao diện trực quan đẹp mắt với câu hỏi từng bước.
15
+ - ⚡ **Chế độ Lệnh nhanh (Command-line flags)**: Truyền tham số để đẩy code không cần bấm enter chọn lựa.
16
+
17
+ ---
18
+
19
+ ## 📦 Cài đặt
20
+
21
+ ### Cách 1: Cài đặt toàn cục (Recommended)
22
+ ```bash
23
+ npm install -g githubcli
24
+ ```
25
+ Sau khi cài đặt, bạn có thể gọi lệnh `githubcli`, `git-push-time` hoặc `git-custom-push` ở **bất kỳ thư mục dự án nào** trên máy tính!
26
+
27
+ ### Cách 2: Chạy trực tiếp không cần cài đặt
28
+ ```bash
29
+ npx githubcli
30
+ ```
31
+
32
+ ---
33
+
34
+ ## 🛠️ Hướng dẫn sử dụng
35
+
36
+ ### 1. Chế độ tương tác (Interactive Mode)
37
+ Chỉ cần mở Terminal tại thư mục dự án của bạn và gõ:
38
+ ```bash
39
+ git-push-time
40
+ ```
41
+ Công cụ sẽ hiển thị từng câu hỏi giao diện để bạn dán link repo, nhập commit message và chọn thời gian:
42
+ 1. **Dán Link Repo**: `https://github.com/username/repository.git`
43
+ 2. **Commit Message**: `Initial commit`
44
+ 3. **Chọn thời gian**:
45
+ - ⏰ *Ngay bây giờ*
46
+ - ⏳ *Lùi thời gian* (VD: `-2d` là 2 ngày trước)
47
+ - 📅 *Nhập ngày giờ cụ thể* (VD: `2024-05-20 09:15:00`)
48
+ 4. **Tên Branch**: `main`
49
+ 5. **Xác nhận**: Có / Không.
50
+
51
+ ---
52
+
53
+ ### 2. Chế độ dòng lệnh nhanh (Command Line Flags)
54
+ Đẩy dự án với thông số truyền trực tiếp:
55
+
56
+ #### Ví dụ 1: Lùi thời gian commit 2 ngày trước
57
+ ```bash
58
+ git-push-time -r https://github.com/username/my-repo.git -d "-2d" -m "Initial commit"
59
+ ```
60
+
61
+ #### Ví dụ 2: Đặt ngày giờ commit cụ thể trong quá khứ
62
+ ```bash
63
+ git-push-time -r https://github.com/username/my-repo.git -d "2024-01-15 14:30:00" -m "Add core features"
64
+ ```
65
+
66
+ #### Ví dụ 3: Đẩy tự động không hỏi lại (-y) và force push (-f)
67
+ ```bash
68
+ git-push-time -r https://github.com/username/my-repo.git -d "-5h" -m "Update docs" -b main -f -y
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 📊 Bảng định dạng thời gian hỗ trợ
74
+
75
+ | Định dạng ví dụ | Ý nghĩa |
76
+ | :--- | :--- |
77
+ | `now` hoặc để trống | Thời gian hiện tại |
78
+ | `-2d` hoặc `2d` | Lùi lại 2 ngày |
79
+ | `-5h` hoặc `5h` | Lùi lại 5 giờ |
80
+ | `-30m` hoặc `30m` | Lùi lại 30 phút |
81
+ | `-1w` hoặc `1w` | Lùi lại 1 tuần |
82
+ | `-3m` | Lùi lại 3 tháng |
83
+ | `2024-01-15 14:30:00` | Ngày 15/01/2024 lúc 14 giờ 30 phút |
84
+ | `2024-01-15` | Ngày 15/01/2024 |
85
+
86
+ ---
87
+
88
+ ## 🚩 Danh sách các Flags (Options)
89
+
90
+ - `-r, --repo <url>`: Link Git Repository (VD: `https://github.com/user/repo.git`)
91
+ - `-d, --date <datetime>`: Thời gian commit (`now`, `-2d`, `2024-01-15 10:30:00`,...)
92
+ - `-m, --message <msg>`: Thông điệp commit (Mặc định: `Initial commit`)
93
+ - `-b, --branch <branch>`: Tên branch (Mặc định: `main`)
94
+ - `-f, --force`: Đẩy đè lên branch remote (`--force`)
95
+ - `-y, --yes`: Tự động xác nhận, không hiện prompt chọn
96
+ - `-h, --help`: Hướng dẫn trợ giúp
97
+
98
+ ---
99
+
100
+ ## 📜 Giấy phép
101
+ MIT License.
package/bin/index.js ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { input, select, confirm } from '@inquirer/prompts';
7
+ import { isValidGitUrl, parseCustomDate } from '../lib/utils.js';
8
+ import {
9
+ checkGitInstalled,
10
+ isGitRepo,
11
+ initGitRepo,
12
+ setGitRemote,
13
+ stageAllFiles,
14
+ hasChangesToCommit,
15
+ commitWithCustomDate,
16
+ setBranchName,
17
+ pushToRemote,
18
+ getCurrentBranch
19
+ } from '../lib/git.js';
20
+
21
+ const program = new Command();
22
+
23
+ program
24
+ .name('githubcli')
25
+ .description('Công cụ CLI giúp đẩy dự án lên Git và sửa thời gian commit (Backdate Commit)')
26
+ .version('1.0.0')
27
+ .option('-r, --repo <url>', 'Đường dẫn Git repository (URL)')
28
+ .option('-d, --date <datetime>', 'Thời gian commit (Ví dụ: "2024-01-15 14:30:00", "-2d", "-5h", "now")')
29
+ .option('-m, --message <msg>', 'Thông điệp commit (Commit message)')
30
+ .option('-b, --branch <branch>', 'Tên branch trên remote (Mặc định: main)')
31
+ .option('-f, --force', 'Thực hiện push đè (--force)', false)
32
+ .option('-y, --yes', 'Bỏ qua prompt tương tác, tự động dùng tham số hoặc giá trị mặc định', false);
33
+
34
+ program.parse(process.argv);
35
+ const options = program.opts();
36
+
37
+ async function run() {
38
+ console.log(chalk.bold.cyan('\n🚀 GIT PUSH & COMMIT TIME SPOOFER 🚀\n'));
39
+
40
+ // 1. Kiểm tra Git đã cài đặt chưa
41
+ if (!checkGitInstalled()) {
42
+ console.error(chalk.red('❌ Hệ thống chưa cài đặt Git. Vui lòng cài đặt Git trước khi dùng công cụ này.'));
43
+ process.exit(1);
44
+ }
45
+
46
+ let repoUrl = options.repo;
47
+ let commitMsg = options.message;
48
+ let dateInput = options.date;
49
+ let branchName = options.branch || 'main';
50
+
51
+ const isInteractive = !options.yes;
52
+
53
+ // 2. Chế độ Tương tác (Interactive Prompts) nếu thiếu tham số
54
+ if (isInteractive) {
55
+ // Nhập URL Repo nếu chưa truyền qua CLI
56
+ if (!repoUrl) {
57
+ repoUrl = await input({
58
+ message: 'Dán link Git Repository (Ví dụ: https://github.com/user/repo.git):',
59
+ validate: (value) => {
60
+ if (!value || !value.trim()) return 'Vui lòng nhập đường dẫn repository!';
61
+ if (!isValidGitUrl(value)) return 'Đường dẫn Git URL không đúng định dạng (https/ssh/.git)';
62
+ return true;
63
+ }
64
+ });
65
+ }
66
+
67
+ // Nhập Commit Message nếu chưa truyền
68
+ if (!commitMsg) {
69
+ commitMsg = await input({
70
+ message: 'Nhập thông điệp Commit (Commit message):',
71
+ default: 'Initial commit'
72
+ });
73
+ }
74
+
75
+ // Chọn cách nhập thời gian Commit nếu chưa truyền
76
+ if (!dateInput) {
77
+ const dateOption = await select({
78
+ message: 'Chọn thời gian cho Commit:',
79
+ choices: [
80
+ { name: '⏰ Ngay bây giờ (Current Time)', value: 'now' },
81
+ { name: '⏳ Lùi thời gian (Ví dụ: -2d là 2 ngày trước, -5h là 5 giờ trước, -1w là 1 tuần trước)', value: 'relative' },
82
+ { name: '📅 Nhập ngày giờ cụ thể (Ví dụ: 2024-01-15 14:30:00)', value: 'custom' }
83
+ ]
84
+ });
85
+
86
+ if (dateOption === 'now') {
87
+ dateInput = 'now';
88
+ } else if (dateOption === 'relative') {
89
+ dateInput = await input({
90
+ message: 'Nhập thời gian lùi (VD: -2d [2 ngày trước], -5h [5 giờ trước], -1w [1 tuần trước]):',
91
+ default: '-1d',
92
+ validate: (val) => {
93
+ const parsed = parseCustomDate(val);
94
+ return parsed.valid || parsed.error || 'Thời gian không hợp lệ';
95
+ }
96
+ });
97
+ } else if (dateOption === 'custom') {
98
+ dateInput = await input({
99
+ message: 'Nhập ngày giờ cụ thể (Định dạng: YYYY-MM-DD HH:mm:ss):',
100
+ default: '2024-01-15 10:00:00',
101
+ validate: (val) => {
102
+ const parsed = parseCustomDate(val);
103
+ return parsed.valid || parsed.error || 'Ngày giờ không hợp lệ';
104
+ }
105
+ });
106
+ }
107
+ }
108
+
109
+ // Nhập Branch Name nếu chưa truyền và chưa đổi
110
+ if (!options.branch) {
111
+ const currentB = getCurrentBranch();
112
+ branchName = await input({
113
+ message: 'Tên branch trên remote:',
114
+ default: currentB || 'main'
115
+ });
116
+ }
117
+ }
118
+
119
+ // Set default fallback values
120
+ if (!commitMsg) commitMsg = 'Initial commit';
121
+ if (!dateInput) dateInput = 'now';
122
+ if (!branchName) branchName = 'main';
123
+
124
+ // Validate Git URL
125
+ if (!repoUrl || !isValidGitUrl(repoUrl)) {
126
+ console.error(chalk.red(`❌ Lỗi: URL repository '${repoUrl}' không hợp lệ!`));
127
+ console.log(chalk.yellow('Ví dụ URL đúng: https://github.com/username/repository.git'));
128
+ process.exit(1);
129
+ }
130
+
131
+ // Parse Date/Time
132
+ const parsedDate = parseCustomDate(dateInput);
133
+ if (!parsedDate.valid) {
134
+ console.error(chalk.red(`❌ Lỗi: ${parsedDate.error}`));
135
+ process.exit(1);
136
+ }
137
+
138
+ // Hiển thị tóm tắt thông tin trước khi thực thi
139
+ console.log('\n' + chalk.bgBlue.white.bold(' 📋 THÔNG TIN ĐẨY DỰ ÁN '));
140
+ console.log(chalk.gray('----------------------------------------'));
141
+ console.log(`🔗 Repo Remote : ${chalk.green(repoUrl)}`);
142
+ console.log(`💬 Commit Msg : ${chalk.yellow(commitMsg)}`);
143
+ console.log(`📅 Commit Time : ${chalk.magenta(parsedDate.display)}`);
144
+ console.log(`🌿 Branch : ${chalk.cyan(branchName)}`);
145
+ console.log(`⚡ Force Push : ${options.force ? chalk.red('CÓ (--force)') : chalk.gray('Không')}`);
146
+ console.log(chalk.gray('----------------------------------------\n'));
147
+
148
+ if (isInteractive) {
149
+ const ok = await confirm({
150
+ message: 'Xác nhận thực hiện tạo commit và đẩy lên Git?',
151
+ default: true
152
+ });
153
+ if (!ok) {
154
+ console.log(chalk.yellow('Đã hủy thao tác.'));
155
+ process.exit(0);
156
+ }
157
+ }
158
+
159
+ // 3. Thực thi các bước đẩy dự án
160
+ const spinner = ora();
161
+
162
+ try {
163
+ // Bước 1: Git Init
164
+ if (!isGitRepo()) {
165
+ spinner.start('Đang khởi tạo kho chứa Git (git init)...');
166
+ initGitRepo();
167
+ spinner.succeed('Đã khởi tạo kho chứa Git cục bộ.');
168
+ } else {
169
+ spinner.info('Đã phát hiện kho chứa Git có sẵn.');
170
+ }
171
+
172
+ // Bước 2: Thiết lập Remote URL
173
+ spinner.start(`Đang cấu hình Remote Origin: ${repoUrl}`);
174
+ setGitRemote(repoUrl);
175
+ spinner.succeed(`Đã cấu hình Remote Origin thành công.`);
176
+
177
+ // Bước 3: Stage files (git add .)
178
+ spinner.start('Đang thêm tất cả các file (git add .)...');
179
+ stageAllFiles();
180
+ spinner.succeed('Đã stage tất cả các file.');
181
+
182
+ // Bước 4: Commit với thời gian giả lập (Backdate Commit)
183
+ spinner.start(`Đang tạo Commit với thời gian [${parsedDate.display}]...`);
184
+
185
+ if (!hasChangesToCommit()) {
186
+ spinner.info('Không có thay đổi nào mới để commit. Sẽ tiến hành đẩy commit hiện có.');
187
+ } else {
188
+ commitWithCustomDate(commitMsg, parsedDate.formatted);
189
+ spinner.succeed(`Đã tạo commit thành công với thời gian: ${chalk.bold(parsedDate.display)}`);
190
+ }
191
+
192
+ // Bước 5: Đặt tên branch
193
+ spinner.start(`Đang thiết lập branch chính '${branchName}'...`);
194
+ setBranchName(branchName);
195
+ spinner.succeed(`Đã đặt tên branch: ${branchName}`);
196
+
197
+ // Bước 6: Push lên Remote
198
+ spinner.start(`Đang đẩy code lên remote '${repoUrl}' (branch: ${branchName})...`);
199
+ pushToRemote(branchName, options.force);
200
+ spinner.succeed(chalk.bold.green(`🎉 ĐÃ ĐẨY DỰ ÁN LÊN GIT THÀNH CÔNG!`));
201
+
202
+ console.log('\n' + chalk.bold.green('✨ Hoàn tất! Bạn có thể kiểm tra commit log trên GitHub/GitLab của mình.'));
203
+ } catch (error) {
204
+ spinner.fail(chalk.red('Đã xảy ra lỗi trong quá trình thực thi:'));
205
+ console.error(chalk.red(`\nLỗi: ${error.message}\n`));
206
+ process.exit(1);
207
+ }
208
+ }
209
+
210
+ run();
package/lib/git.js ADDED
@@ -0,0 +1,153 @@
1
+ import { execSync, spawnSync } from 'child_process';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Kiểm tra xem máy tính đã cài đặt Git hay chưa
6
+ * @returns {boolean}
7
+ */
8
+ export function checkGitInstalled() {
9
+ try {
10
+ execSync('git --version', { stdio: 'ignore' });
11
+ return true;
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Kiểm tra thư mục hiện tại đã là kho chứa Git (Git repo) chưa
19
+ * @param {string} cwd
20
+ * @returns {boolean}
21
+ */
22
+ export function isGitRepo(cwd = process.cwd()) {
23
+ try {
24
+ const output = execSync('git rev-parse --is-inside-work-tree', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
25
+ return output.trim() === 'true';
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Khởi tạo Git repo trong thư mục hiện tại
33
+ * @param {string} cwd
34
+ */
35
+ export function initGitRepo(cwd = process.cwd()) {
36
+ execSync('git init', { cwd, stdio: 'pipe' });
37
+ }
38
+
39
+ /**
40
+ * Lấy tên branch hiện tại (hoặc mặc định là main)
41
+ * @param {string} cwd
42
+ * @returns {string}
43
+ */
44
+ export function getCurrentBranch(cwd = process.cwd()) {
45
+ try {
46
+ const branch = execSync('git branch --show-current', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
47
+ return branch || 'main';
48
+ } catch {
49
+ return 'main';
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Thiết lập tên branch chính (ví dụ: git branch -M main)
55
+ * @param {string} branchName
56
+ * @param {string} cwd
57
+ */
58
+ export function setBranchName(branchName = 'main', cwd = process.cwd()) {
59
+ try {
60
+ execSync(`git branch -M "${branchName}"`, { cwd, stdio: 'pipe' });
61
+ } catch (err) {
62
+ // Nếu chưa có commit nào, command này có thể báo lỗi nhưng không sao
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Cấu hình remote `origin` cho Git repo
68
+ * @param {string} repoUrl
69
+ * @param {string} cwd
70
+ */
71
+ export function setGitRemote(repoUrl, cwd = process.cwd()) {
72
+ try {
73
+ const remotes = execSync('git remote', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
74
+ if (remotes.includes('origin')) {
75
+ execSync(`git remote set-url origin "${repoUrl}"`, { cwd, stdio: 'pipe' });
76
+ } else {
77
+ execSync(`git remote add origin "${repoUrl}"`, { cwd, stdio: 'pipe' });
78
+ }
79
+ } catch (error) {
80
+ throw new Error(`Không thể thiết lập Git Remote URL: ${error.message}`);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Stage toàn bộ file trong thư mục
86
+ * @param {string} cwd
87
+ */
88
+ export function stageAllFiles(cwd = process.cwd()) {
89
+ execSync('git add .', { cwd, stdio: 'pipe' });
90
+ }
91
+
92
+ /**
93
+ * Kiểm tra xem có thay đổi (staged hoặc unstaged) chưa commit hay không
94
+ * @param {string} cwd
95
+ * @returns {boolean}
96
+ */
97
+ export function hasChangesToCommit(cwd = process.cwd()) {
98
+ try {
99
+ const status = execSync('git status --porcelain', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
100
+ return status.trim().length > 0;
101
+ } catch {
102
+ return true;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Thực hiện Git commit với thời gian tùy chỉnh (sử dụng GIT_AUTHOR_DATE & GIT_COMMITTER_DATE)
108
+ * @param {string} message
109
+ * @param {string} dateFormatted ISO hoặc Git date format
110
+ * @param {string} cwd
111
+ */
112
+ export function commitWithCustomDate(message, dateFormatted, cwd = process.cwd()) {
113
+ const env = {
114
+ ...process.env,
115
+ GIT_AUTHOR_DATE: dateFormatted,
116
+ GIT_COMMITTER_DATE: dateFormatted
117
+ };
118
+
119
+ const result = spawnSync('git', ['commit', '-m', message], {
120
+ cwd,
121
+ env,
122
+ encoding: 'utf-8'
123
+ });
124
+
125
+ if (result.status !== 0) {
126
+ const stderr = result.stderr || result.stdout || 'Lỗi không xác định khi commit';
127
+ throw new Error(`Git commit thất bại: ${stderr.trim()}`);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Đẩy code lên Git remote repo
133
+ * @param {string} branch Tên branch (VD: main)
134
+ * @param {boolean} force Có sử dụng --force hay không
135
+ * @param {string} cwd
136
+ */
137
+ export function pushToRemote(branch = 'main', force = false, cwd = process.cwd()) {
138
+ const args = ['push', '-u', 'origin', branch];
139
+ if (force) {
140
+ args.push('--force');
141
+ }
142
+
143
+ const result = spawnSync('git', args, {
144
+ cwd,
145
+ encoding: 'utf-8',
146
+ stdio: 'pipe'
147
+ });
148
+
149
+ if (result.status !== 0) {
150
+ const stderr = result.stderr || result.stdout || 'Lỗi không xác định khi git push';
151
+ throw new Error(stderr.trim());
152
+ }
153
+ }
package/lib/utils.js ADDED
@@ -0,0 +1,106 @@
1
+ import dayjs from 'dayjs';
2
+ import customParseFormat from 'dayjs/plugin/customParseFormat.js';
3
+
4
+ dayjs.extend(customParseFormat);
5
+
6
+ /**
7
+ * Kiểm tra xem URL git có hợp lệ hay không (HTTP/HTTPS, SSH, hoặc `.git`)
8
+ * @param {string} url
9
+ * @returns {boolean}
10
+ */
11
+ export function isValidGitUrl(url) {
12
+ if (!url || typeof url !== 'string') return false;
13
+ const trimmed = url.trim();
14
+ // Standard git remote URL patterns: https://, http://, git@, ssh://, or file path
15
+ const gitRegex = /^(https?:\/\/|git@|ssh:\/\/|ftps?:\/\/).+$/i;
16
+ return gitRegex.test(trimmed) || trimmed.endsWith('.git');
17
+ }
18
+
19
+ /**
20
+ * Parse chuỗi thời gian thành định dạng ISO/Git hợp lệ.
21
+ * Hỗ trợ các định dạng:
22
+ * - "now" hoặc để trống -> Thời gian hiện tại
23
+ * - Relative: "-2d", "-5h", "-30m", "-1w", "-3m" (month), "-1y"
24
+ * - Absolute: "YYYY-MM-DD HH:mm:ss", "YYYY-MM-DD HH:mm", "YYYY-MM-DD"
25
+ *
26
+ * @param {string} dateStr
27
+ * @returns {{ valid: boolean, formatted: string, display: string, error?: string }}
28
+ */
29
+ export function parseCustomDate(dateStr) {
30
+ if (!dateStr || dateStr.trim().toLowerCase() === 'now') {
31
+ const now = dayjs();
32
+ return {
33
+ valid: true,
34
+ formatted: now.toISOString(),
35
+ display: now.format('YYYY-MM-DD HH:mm:ss Z')
36
+ };
37
+ }
38
+
39
+ const str = dateStr.trim();
40
+
41
+ // Kiểm tra relative time: ví dụ -2d, -5h, -30m, -1w, -3M, -1y hoặc 2d, 5h
42
+ const relativeMatch = str.match(/^([+-]?\d+)\s*([smhdwMy]|sec|min|hour|day|week|month|year)s?$/i);
43
+ if (relativeMatch) {
44
+ let amount = parseInt(relativeMatch[1], 10);
45
+ const unitRaw = relativeMatch[2].toLowerCase();
46
+
47
+ // Map unit
48
+ let unit = 'day';
49
+ if (unitRaw === 's' || unitRaw === 'sec') unit = 'second';
50
+ else if (unitRaw === 'm' || unitRaw === 'min') unit = 'minute';
51
+ else if (unitRaw === 'h' || unitRaw === 'hour') unit = 'hour';
52
+ else if (unitRaw === 'd' || unitRaw === 'day') unit = 'day';
53
+ else if (unitRaw === 'w' || unitRaw === 'week') unit = 'week';
54
+ else if (unitRaw === 'm' || unitRaw === 'month') unit = 'month';
55
+ else if (unitRaw === 'y' || unitRaw === 'year') unit = 'year';
56
+
57
+ // Nếu người dùng nhập "2d" mà không có dấu "-", mặc định hiểu là "lùi 2d" (-2d)
58
+ if (amount > 0 && !str.startsWith('+')) {
59
+ amount = -amount;
60
+ }
61
+
62
+ const calculated = dayjs().add(amount, unit);
63
+ if (!calculated.isValid()) {
64
+ return { valid: false, formatted: '', display: '', error: `Không thể tính toán thời gian từ '${dateStr}'` };
65
+ }
66
+
67
+ return {
68
+ valid: true,
69
+ formatted: calculated.toISOString(),
70
+ display: calculated.format('YYYY-MM-DD HH:mm:ss Z')
71
+ };
72
+ }
73
+
74
+ // Thủ định dạng ngày giờ tuyệt đối
75
+ const formats = [
76
+ 'YYYY-MM-DD HH:mm:ss',
77
+ 'YYYY-MM-DD HH:mm',
78
+ 'YYYY-MM-DD',
79
+ 'YYYY/MM/DD HH:mm:ss',
80
+ 'YYYY/MM/DD HH:mm',
81
+ 'YYYY/MM/DD',
82
+ 'DD/MM/YYYY HH:mm:ss',
83
+ 'DD/MM/YYYY HH:mm',
84
+ 'DD/MM/YYYY'
85
+ ];
86
+
87
+ let parsed = dayjs(str, formats, true);
88
+ if (!parsed.isValid()) {
89
+ parsed = dayjs(str); // Thử parse chuẩn ISO
90
+ }
91
+
92
+ if (parsed.isValid()) {
93
+ return {
94
+ valid: true,
95
+ formatted: parsed.toISOString(),
96
+ display: parsed.format('YYYY-MM-DD HH:mm:ss Z')
97
+ };
98
+ }
99
+
100
+ return {
101
+ valid: false,
102
+ formatted: '',
103
+ display: '',
104
+ error: `Định dạng ngày giờ không hợp lệ: '${dateStr}'. Ví dụ đúng: "2024-01-15 14:30:00", "-2d", "-5h", "now"`
105
+ };
106
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "git-push-time",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool to push project to Git with custom repo URL and custom commit timestamp",
5
+ "main": "bin/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "githubcli": "./bin/index.js",
9
+ "git-push-time": "./bin/index.js",
10
+ "git-custom-push": "./bin/index.js"
11
+ },
12
+ "scripts": {
13
+ "start": "node bin/index.js"
14
+ },
15
+ "keywords": [
16
+ "git",
17
+ "push",
18
+ "commit-date",
19
+ "backdate",
20
+ "cli"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@inquirer/prompts": "^7.3.2",
26
+ "chalk": "^5.4.1",
27
+ "commander": "^13.1.0",
28
+ "dayjs": "^1.11.13",
29
+ "ora": "^8.2.0"
30
+ }
31
+ }