git-push-time 1.0.3 → 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/.github/workflows/npm-publish.yml +33 -0
- package/README.md +215 -134
- package/bin/index.js +337 -1004
- package/lib/commands/edit-time.js +141 -0
- package/lib/commands/fix.js +113 -0
- package/lib/commands/github.js +439 -0
- package/lib/commands/pull.js +89 -0
- package/lib/commands/push.js +673 -0
- package/lib/config.js +220 -0
- package/lib/fix.js +286 -260
- package/lib/git.js +492 -452
- package/lib/github.js +322 -0
- package/lib/prompt-helpers.js +62 -0
- package/lib/secrets.js +92 -87
- package/lib/ui.js +804 -728
- package/lib/utils.js +120 -106
- package/package.json +35 -32
package/lib/config.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = path.join(os.homedir(), '.git-push-time');
|
|
7
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
8
|
+
|
|
9
|
+
function ensureConfigDir() {
|
|
10
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
11
|
+
try {
|
|
12
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
13
|
+
} catch {
|
|
14
|
+
// Ignore directory creation errors
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Đọc toàn bộ file cấu hình người dùng
|
|
21
|
+
* @returns {Record<string, any>}
|
|
22
|
+
*/
|
|
23
|
+
export function getConfig() {
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
26
|
+
const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
27
|
+
return JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// Ignore JSON parse errors
|
|
31
|
+
}
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Lưu cấu hình người dùng
|
|
37
|
+
* @param {Record<string, any>} data
|
|
38
|
+
*/
|
|
39
|
+
export function saveConfig(data) {
|
|
40
|
+
try {
|
|
41
|
+
ensureConfigDir();
|
|
42
|
+
const current = getConfig();
|
|
43
|
+
const merged = { ...current, ...data };
|
|
44
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), 'utf-8');
|
|
45
|
+
try {
|
|
46
|
+
// Token nhạy cảm: giới hạn quyền đọc chỉ owner (chmod 600)
|
|
47
|
+
fs.chmodSync(CONFIG_FILE, 0o600);
|
|
48
|
+
} catch {
|
|
49
|
+
// Bỏ qua trên Windows hoặc FS không hỗ trợ chmod
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Lấy danh sách tất cả các tài khoản GitHub đã lưu
|
|
59
|
+
* @returns {Array<{ login: string, name: string, token: string, active: boolean, avatar_url?: string }>}
|
|
60
|
+
*/
|
|
61
|
+
export function getAllGitHubAccounts() {
|
|
62
|
+
const cfg = getConfig();
|
|
63
|
+
const accountsMap = cfg.githubAccounts || {};
|
|
64
|
+
const activeLogin = cfg.activeGitHubLogin || (cfg.github?.user?.login) || null;
|
|
65
|
+
|
|
66
|
+
const result = [];
|
|
67
|
+
for (const [login, acc] of Object.entries(accountsMap)) {
|
|
68
|
+
if (acc && typeof acc.token === 'string' && acc.token.trim()) {
|
|
69
|
+
result.push({
|
|
70
|
+
login,
|
|
71
|
+
name: acc.user?.name || login,
|
|
72
|
+
avatar_url: acc.user?.avatar_url || '',
|
|
73
|
+
token: acc.token,
|
|
74
|
+
active: login === activeLogin
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Tương thích ngược với trường `github` cũ nếu chưa migrate
|
|
80
|
+
if (result.length === 0 && cfg.github && cfg.github.token) {
|
|
81
|
+
const login = cfg.github.user?.login || 'default';
|
|
82
|
+
result.push({
|
|
83
|
+
login,
|
|
84
|
+
name: cfg.github.user?.name || login,
|
|
85
|
+
avatar_url: cfg.github.user?.avatar_url || '',
|
|
86
|
+
token: cfg.github.token,
|
|
87
|
+
active: true
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Đặt tài khoản GitHub đang kích hoạt
|
|
96
|
+
* @param {string} login
|
|
97
|
+
*/
|
|
98
|
+
export function setActiveGitHubAccount(login) {
|
|
99
|
+
const cfg = getConfig();
|
|
100
|
+
cfg.activeGitHubLogin = login;
|
|
101
|
+
if (cfg.githubAccounts && cfg.githubAccounts[login]) {
|
|
102
|
+
cfg.github = {
|
|
103
|
+
token: cfg.githubAccounts[login].token,
|
|
104
|
+
user: cfg.githubAccounts[login].user,
|
|
105
|
+
savedAt: new Date().toISOString()
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return saveConfig(cfg);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Lấy GitHub token từ nhiều nguồn theo thứ tự ưu tiên:
|
|
113
|
+
* 1. Biến môi trường GITHUB_TOKEN hoặc GH_TOKEN
|
|
114
|
+
* 2. Tài khoản GitHub active trong file config
|
|
115
|
+
* 3. Token từ GitHub CLI (`gh auth token`) nếu có
|
|
116
|
+
* @returns {{ token: string | null, source: 'env' | 'config' | 'gh-cli' | null, user?: any }}
|
|
117
|
+
*/
|
|
118
|
+
export function getGitHubToken() {
|
|
119
|
+
// 1. Kiểm tra Environment variables
|
|
120
|
+
if (process.env.GITHUB_TOKEN && process.env.GITHUB_TOKEN.trim()) {
|
|
121
|
+
return { token: process.env.GITHUB_TOKEN.trim(), source: 'env' };
|
|
122
|
+
}
|
|
123
|
+
if (process.env.GH_TOKEN && process.env.GH_TOKEN.trim()) {
|
|
124
|
+
return { token: process.env.GH_TOKEN.trim(), source: 'env' };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 2. Kiểm tra File config đã lưu
|
|
128
|
+
const cfg = getConfig();
|
|
129
|
+
const activeLogin = cfg.activeGitHubLogin;
|
|
130
|
+
if (activeLogin && cfg.githubAccounts && cfg.githubAccounts[activeLogin]) {
|
|
131
|
+
const acc = cfg.githubAccounts[activeLogin];
|
|
132
|
+
if (acc && typeof acc.token === 'string' && acc.token.trim()) {
|
|
133
|
+
return { token: acc.token.trim(), source: 'config', user: acc.user };
|
|
134
|
+
}
|
|
135
|
+
return { token: null, source: null };
|
|
136
|
+
}
|
|
137
|
+
if (cfg.github && cfg.github.token && typeof cfg.github.token === 'string') {
|
|
138
|
+
return { token: cfg.github.token.trim(), source: 'config', user: cfg.github.user };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 3. Kiểm tra GitHub CLI gh
|
|
142
|
+
try {
|
|
143
|
+
const r = spawnSync('gh', ['auth', 'token'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
144
|
+
if (r.status === 0 && r.stdout && r.stdout.trim()) {
|
|
145
|
+
return { token: r.stdout.trim(), source: 'gh-cli' };
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
// gh not installed or error
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { token: null, source: null };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Lưu tài khoản GitHub vào file config (hỗ trợ đa tài khoản)
|
|
156
|
+
* @param {string} token
|
|
157
|
+
* @param {object} userInfo
|
|
158
|
+
*/
|
|
159
|
+
export function saveGitHubToken(token, userInfo = null) {
|
|
160
|
+
if (!token || typeof token !== 'string' || !token.trim()) return false;
|
|
161
|
+
const current = getConfig();
|
|
162
|
+
if (!current.githubAccounts) {
|
|
163
|
+
current.githubAccounts = {};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const login = userInfo?.login || current.github?.user?.login || 'default';
|
|
167
|
+
current.githubAccounts[login] = {
|
|
168
|
+
token: token.trim(),
|
|
169
|
+
savedAt: new Date().toISOString(),
|
|
170
|
+
user: userInfo ? {
|
|
171
|
+
login: userInfo.login,
|
|
172
|
+
name: userInfo.name,
|
|
173
|
+
avatar_url: userInfo.avatar_url,
|
|
174
|
+
email: userInfo.email,
|
|
175
|
+
bio: userInfo.bio,
|
|
176
|
+
public_repos: userInfo.public_repos,
|
|
177
|
+
total_private_repos: userInfo.total_private_repos,
|
|
178
|
+
html_url: userInfo.html_url
|
|
179
|
+
} : (current.githubAccounts[login]?.user || null)
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
current.activeGitHubLogin = login;
|
|
183
|
+
current.github = {
|
|
184
|
+
token: token.trim(),
|
|
185
|
+
user: current.githubAccounts[login].user,
|
|
186
|
+
savedAt: new Date().toISOString()
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return saveConfig(current);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Xóa tài khoản GitHub khỏi file config
|
|
194
|
+
* @param {string} [login] nếu không truyền sẽ xóa tài khoản active
|
|
195
|
+
*/
|
|
196
|
+
export function removeGitHubToken(login = null) {
|
|
197
|
+
const current = getConfig();
|
|
198
|
+
const targetLogin = login || current.activeGitHubLogin || current.github?.user?.login;
|
|
199
|
+
|
|
200
|
+
if (targetLogin && current.githubAccounts && current.githubAccounts[targetLogin]) {
|
|
201
|
+
delete current.githubAccounts[targetLogin];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (current.activeGitHubLogin === targetLogin) {
|
|
205
|
+
const remaining = Object.keys(current.githubAccounts || {});
|
|
206
|
+
current.activeGitHubLogin = remaining.length > 0 ? remaining[0] : null;
|
|
207
|
+
if (current.activeGitHubLogin && current.githubAccounts[current.activeGitHubLogin]) {
|
|
208
|
+
current.github = {
|
|
209
|
+
token: current.githubAccounts[current.activeGitHubLogin].token,
|
|
210
|
+
user: current.githubAccounts[current.activeGitHubLogin].user
|
|
211
|
+
};
|
|
212
|
+
} else {
|
|
213
|
+
delete current.github;
|
|
214
|
+
}
|
|
215
|
+
} else if (!targetLogin && current.github) {
|
|
216
|
+
delete current.github;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return saveConfig(current);
|
|
220
|
+
}
|