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/.github/workflows/npm-publish.yml +59 -32
- package/README.md +110 -112
- package/bin/index.js +321 -339
- package/lib/commands/edit-time.js +141 -141
- package/lib/commands/fix.js +113 -113
- package/lib/commands/github.js +429 -445
- package/lib/commands/pull.js +90 -90
- package/lib/commands/push.js +720 -720
- package/lib/config.js +220 -220
- package/lib/fix.js +286 -286
- package/lib/git.js +525 -516
- package/lib/github.js +322 -322
- package/lib/prompt-helpers.js +19 -20
- package/lib/secrets.js +92 -92
- package/lib/ui.js +1057 -1063
- package/lib/utils.js +120 -120
- package/package.json +36 -36
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
47
|
-
fs.chmodSync(CONFIG_FILE, 0o600);
|
|
48
|
-
} catch {
|
|
49
|
-
//
|
|
50
|
-
}
|
|
51
|
-
return true;
|
|
52
|
-
} catch {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
*
|
|
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
|
-
//
|
|
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
|
-
*
|
|
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
|
-
*
|
|
113
|
-
* 1.
|
|
114
|
-
* 2.
|
|
115
|
-
* 3.
|
|
116
|
-
* @returns {{ token: string | null, source: 'env' | 'config' | 'gh-cli' | null, user?: any }}
|
|
117
|
-
*/
|
|
118
|
-
export function getGitHubToken() {
|
|
119
|
-
// 1.
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
194
|
-
* @param {string} [login]
|
|
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
|
+
}
|