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/lib/config.js CHANGED
@@ -1,210 +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
- return true;
46
- } catch {
47
- return false;
48
- }
49
- }
50
-
51
- /**
52
- * Lấy danh sách tất cả các tài khoản GitHub đã lưu
53
- * @returns {Array<{ login: string, name: string, token: string, active: boolean, avatar_url?: string }>}
54
- */
55
- export function getAllGitHubAccounts() {
56
- const cfg = getConfig();
57
- const accountsMap = cfg.githubAccounts || {};
58
- const activeLogin = cfg.activeGitHubLogin || (cfg.github?.user?.login) || null;
59
-
60
- const result = [];
61
- for (const [login, acc] of Object.entries(accountsMap)) {
62
- if (acc && acc.token) {
63
- result.push({
64
- login,
65
- name: acc.user?.name || login,
66
- avatar_url: acc.user?.avatar_url || '',
67
- token: acc.token,
68
- active: login === activeLogin
69
- });
70
- }
71
- }
72
-
73
- // Tương thích ngược với trường `github` cũ nếu chưa migrate
74
- if (result.length === 0 && cfg.github && cfg.github.token) {
75
- const login = cfg.github.user?.login || 'default';
76
- result.push({
77
- login,
78
- name: cfg.github.user?.name || login,
79
- avatar_url: cfg.github.user?.avatar_url || '',
80
- token: cfg.github.token,
81
- active: true
82
- });
83
- }
84
-
85
- return result;
86
- }
87
-
88
- /**
89
- * Đặt tài khoản GitHub đang kích hoạt
90
- * @param {string} login
91
- */
92
- export function setActiveGitHubAccount(login) {
93
- const cfg = getConfig();
94
- cfg.activeGitHubLogin = login;
95
- if (cfg.githubAccounts && cfg.githubAccounts[login]) {
96
- cfg.github = {
97
- token: cfg.githubAccounts[login].token,
98
- user: cfg.githubAccounts[login].user,
99
- savedAt: new Date().toISOString()
100
- };
101
- }
102
- return saveConfig(cfg);
103
- }
104
-
105
- /**
106
- * Lấy GitHub token từ nhiều nguồn theo thứ tự ưu tiên:
107
- * 1. Biến môi trường GITHUB_TOKEN hoặc GH_TOKEN
108
- * 2. Tài khoản GitHub active trong file config
109
- * 3. Token từ GitHub CLI (`gh auth token`) nếu có
110
- * @returns {{ token: string | null, source: 'env' | 'config' | 'gh-cli' | null, user?: any }}
111
- */
112
- export function getGitHubToken() {
113
- // 1. Kiểm tra Environment variables
114
- if (process.env.GITHUB_TOKEN && process.env.GITHUB_TOKEN.trim()) {
115
- return { token: process.env.GITHUB_TOKEN.trim(), source: 'env' };
116
- }
117
- if (process.env.GH_TOKEN && process.env.GH_TOKEN.trim()) {
118
- return { token: process.env.GH_TOKEN.trim(), source: 'env' };
119
- }
120
-
121
- // 2. Kiểm tra File config đã lưu
122
- const cfg = getConfig();
123
- const activeLogin = cfg.activeGitHubLogin;
124
- if (activeLogin && cfg.githubAccounts && cfg.githubAccounts[activeLogin]) {
125
- const acc = cfg.githubAccounts[activeLogin];
126
- return { token: acc.token.trim(), source: 'config', user: acc.user };
127
- }
128
- if (cfg.github && cfg.github.token && typeof cfg.github.token === 'string') {
129
- return { token: cfg.github.token.trim(), source: 'config', user: cfg.github.user };
130
- }
131
-
132
- // 3. Kiểm tra GitHub CLI gh
133
- try {
134
- const r = spawnSync('gh', ['auth', 'token'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
135
- if (r.status === 0 && r.stdout && r.stdout.trim()) {
136
- return { token: r.stdout.trim(), source: 'gh-cli' };
137
- }
138
- } catch {
139
- // gh not installed or error
140
- }
141
-
142
- return { token: null, source: null };
143
- }
144
-
145
- /**
146
- * Lưu tài khoản GitHub vào file config (hỗ trợ đa tài khoản)
147
- * @param {string} token
148
- * @param {object} userInfo
149
- */
150
- export function saveGitHubToken(token, userInfo = null) {
151
- const current = getConfig();
152
- if (!current.githubAccounts) {
153
- current.githubAccounts = {};
154
- }
155
-
156
- const login = userInfo?.login || current.github?.user?.login || 'default';
157
- current.githubAccounts[login] = {
158
- token: token.trim(),
159
- savedAt: new Date().toISOString(),
160
- user: userInfo ? {
161
- login: userInfo.login,
162
- name: userInfo.name,
163
- avatar_url: userInfo.avatar_url,
164
- email: userInfo.email,
165
- bio: userInfo.bio,
166
- public_repos: userInfo.public_repos,
167
- total_private_repos: userInfo.total_private_repos,
168
- html_url: userInfo.html_url
169
- } : (current.githubAccounts[login]?.user || null)
170
- };
171
-
172
- current.activeGitHubLogin = login;
173
- current.github = {
174
- token: token.trim(),
175
- user: current.githubAccounts[login].user,
176
- savedAt: new Date().toISOString()
177
- };
178
-
179
- return saveConfig(current);
180
- }
181
-
182
- /**
183
- * Xóa tài khoản GitHub khỏi file config
184
- * @param {string} [login] nếu không truyền sẽ xóa tài khoản active
185
- */
186
- export function removeGitHubToken(login = null) {
187
- const current = getConfig();
188
- const targetLogin = login || current.activeGitHubLogin || current.github?.user?.login;
189
-
190
- if (targetLogin && current.githubAccounts && current.githubAccounts[targetLogin]) {
191
- delete current.githubAccounts[targetLogin];
192
- }
193
-
194
- if (current.activeGitHubLogin === targetLogin) {
195
- const remaining = Object.keys(current.githubAccounts || {});
196
- current.activeGitHubLogin = remaining.length > 0 ? remaining[0] : null;
197
- if (current.activeGitHubLogin && current.githubAccounts[current.activeGitHubLogin]) {
198
- current.github = {
199
- token: current.githubAccounts[current.activeGitHubLogin].token,
200
- user: current.githubAccounts[current.activeGitHubLogin].user
201
- };
202
- } else {
203
- delete current.github;
204
- }
205
- } else if (!targetLogin && current.github) {
206
- delete current.github;
207
- }
208
-
209
- return saveConfig(current);
210
- }
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
+ }