git-push-time 1.1.2 → 1.1.4

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,220 +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
- }
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
+ * Read the user's complete configuration file.
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
+ * Save the user's configuration.
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
+ // Restrict access to sensitive tokens to the owner (chmod 600).
47
+ fs.chmodSync(CONFIG_FILE, 0o600);
48
+ } catch {
49
+ // Ignore on Windows or filesystems that do not support chmod.
50
+ }
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Get all saved GitHub accounts.
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
+ // Support the legacy `github` field if it has not been migrated.
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
+ * Set the active GitHub account.
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
+ * Get a GitHub token from the available sources, in priority order:
113
+ * 1. GITHUB_TOKEN or GH_TOKEN environment variable
114
+ * 2. Active GitHub account in the config file
115
+ * 3. GitHub CLI token (`gh auth token`), if available
116
+ * @returns {{ token: string | null, source: 'env' | 'config' | 'gh-cli' | null, user?: any }}
117
+ */
118
+ export function getGitHubToken() {
119
+ // 1. Check 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. Check the saved config file.
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. Check the 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
+ * Save a GitHub account to the config file (supports multiple accounts).
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
+ * Remove a GitHub account from the config file.
194
+ * @param {string} [login] If omitted, removes the active account.
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
+ }