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/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
+ }