git-push-time 1.1.3 → 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/README.md +109 -111
- package/bin/index.js +45 -46
- package/lib/commands/edit-time.js +27 -27
- package/lib/commands/fix.js +19 -19
- package/lib/commands/github.js +98 -98
- package/lib/commands/pull.js +13 -13
- package/lib/commands/push.js +147 -147
- package/lib/config.js +17 -17
- package/lib/fix.js +34 -34
- package/lib/git.js +88 -88
- package/lib/github.js +39 -39
- package/lib/prompt-helpers.js +19 -20
- package/lib/secrets.js +12 -12
- package/lib/ui.js +122 -125
- package/lib/utils.js +15 -15
- package/package.json +1 -1
package/lib/commands/github.js
CHANGED
|
@@ -27,17 +27,17 @@ import {
|
|
|
27
27
|
} from '../ui.js';
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Display the currently connected GitHub account.
|
|
31
31
|
*/
|
|
32
32
|
export async function showGitHubStatus() {
|
|
33
|
-
const spinner = ora({ text: '
|
|
33
|
+
const spinner = ora({ text: 'Checking the GitHub connection…', color: 'cyan' }).start();
|
|
34
34
|
const info = await getCurrentGitHubUser();
|
|
35
35
|
spinner.stop();
|
|
36
36
|
|
|
37
37
|
if (!info.authenticated) {
|
|
38
|
-
printSection('
|
|
39
|
-
console.log(chalk.hex('#F59E0B')(' [!]
|
|
40
|
-
console.log(chalk.hex('#94A3B8')('
|
|
38
|
+
printSection('GITHUB STATUS');
|
|
39
|
+
console.log(chalk.hex('#F59E0B')(' [!] No GitHub account is connected.'));
|
|
40
|
+
console.log(chalk.hex('#94A3B8')(' Run "git-push-time github login" to connect an account in your browser.\n'));
|
|
41
41
|
return false;
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -46,112 +46,112 @@ export async function showGitHubStatus() {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* Sign in to GitHub with OAuth Device Flow.
|
|
50
50
|
*/
|
|
51
51
|
async function loginViaDeviceFlow() {
|
|
52
|
-
const initSpinner = ora({ text: '
|
|
52
|
+
const initSpinner = ora({ text: 'Connecting to GitHub OAuth…', color: 'cyan' }).start();
|
|
53
53
|
const init = await initiateDeviceFlow();
|
|
54
54
|
initSpinner.stop();
|
|
55
55
|
|
|
56
56
|
if (!init.ok || !init.data) {
|
|
57
|
-
printErrorBox('
|
|
57
|
+
printErrorBox('OAUTH CONNECTION FAILED', [init.error || 'GitHub did not return a verification code.']);
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const { device_code, user_code, verification_uri, interval, expires_in } = init.data;
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// Show the verification code to the user.
|
|
64
64
|
printDeviceCodePrompt({ userCode: user_code, verificationUri: verification_uri });
|
|
65
65
|
|
|
66
|
-
//
|
|
66
|
+
// Open the browser automatically.
|
|
67
67
|
openBrowser(verification_uri);
|
|
68
68
|
|
|
69
69
|
const pollSpinner = ora({
|
|
70
|
-
text: chalk.hex('#38BDF8')('
|
|
70
|
+
text: chalk.hex('#38BDF8')('Waiting for you to authorize access in your browser…'),
|
|
71
71
|
color: 'cyan'
|
|
72
72
|
}).start();
|
|
73
73
|
|
|
74
74
|
const pollResult = await pollDeviceToken(device_code, interval, expires_in, (status) => {
|
|
75
75
|
if (status === 'slow_down') {
|
|
76
|
-
pollSpinner.text = chalk.hex('#F59E0B')('GitHub
|
|
76
|
+
pollSpinner.text = chalk.hex('#F59E0B')('GitHub requested a longer polling interval. Still waiting for authorization…');
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
if (!pollResult.ok || !pollResult.token) {
|
|
81
|
-
pollSpinner.fail(chalk.red('
|
|
82
|
-
printErrorBox('
|
|
81
|
+
pollSpinner.fail(chalk.red('Authorization failed.'));
|
|
82
|
+
printErrorBox('SIGN-IN FAILED', [pollResult.error]);
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
pollSpinner.text = '
|
|
86
|
+
pollSpinner.text = 'Loading account information…';
|
|
87
87
|
const userRes = await verifyAndFetchUser(pollResult.token);
|
|
88
88
|
if (!userRes.ok) {
|
|
89
|
-
pollSpinner.fail(chalk.red('
|
|
90
|
-
printErrorBox('
|
|
89
|
+
pollSpinner.fail(chalk.red('Could not load the GitHub profile.'));
|
|
90
|
+
printErrorBox('PROFILE ERROR', [userRes.error]);
|
|
91
91
|
return null;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
saveGitHubToken(pollResult.token, userRes.user);
|
|
95
|
-
pollSpinner.succeed(chalk.hex('#34D399').bold(
|
|
95
|
+
pollSpinner.succeed(chalk.hex('#34D399').bold(`Signed in successfully. Welcome, @${userRes.user.login}.`));
|
|
96
96
|
printGitHubProfile(userRes.user, 'oauth-device');
|
|
97
97
|
return userRes.user;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
|
-
*
|
|
101
|
+
* Sign in to GitHub with a Personal Access Token (PAT).
|
|
102
102
|
*/
|
|
103
103
|
async function loginViaToken() {
|
|
104
|
-
printSection('
|
|
105
|
-
console.log(chalk.hex('#38BDF8')(' »
|
|
106
|
-
console.log(chalk.hex('#94A3B8')(' 1.
|
|
107
|
-
console.log(chalk.hex('#94A3B8')(' 2.
|
|
108
|
-
console.log(chalk.hex('#94A3B8')(' 3.
|
|
109
|
-
console.log(chalk.hex('#94A3B8')(' 4. Copy
|
|
104
|
+
printSection('SIGN IN WITH A PERSONAL ACCESS TOKEN');
|
|
105
|
+
console.log(chalk.hex('#38BDF8')(' » To create a token:'));
|
|
106
|
+
console.log(chalk.hex('#94A3B8')(' 1. Open: https://github.com/settings/tokens'));
|
|
107
|
+
console.log(chalk.hex('#94A3B8')(' 2. Select "Generate new token (classic)"'));
|
|
108
|
+
console.log(chalk.hex('#94A3B8')(' 3. Grant the ') + chalk.hex('#34D399').bold('repo') + chalk.hex('#94A3B8')(' scope (full control of repositories)'));
|
|
109
|
+
console.log(chalk.hex('#94A3B8')(' 4. Copy the token (starts with ghp_ or github_pat_) and paste it below.\n'));
|
|
110
110
|
|
|
111
111
|
const tokenInput = await inputWithBack({
|
|
112
|
-
message: 'GitHub
|
|
113
|
-
hint: ['
|
|
112
|
+
message: 'GitHub token (PAT)',
|
|
113
|
+
hint: ['Starts with ghp_ or github_pat_ · requires the repo scope'],
|
|
114
114
|
validate: (val) => {
|
|
115
|
-
if (!val || !val.trim()) return '
|
|
116
|
-
if (val.trim().length < 15) return '
|
|
115
|
+
if (!val || !val.trim()) return 'Token cannot be empty.';
|
|
116
|
+
if (val.trim().length < 15) return 'This token seems too short. Check it and try again.';
|
|
117
117
|
return true;
|
|
118
118
|
}
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
if (tokenInput === BACK) return null;
|
|
122
122
|
|
|
123
|
-
const spinner = ora({ text: '
|
|
123
|
+
const spinner = ora({ text: 'Verifying token with GitHub…', color: 'cyan' }).start();
|
|
124
124
|
const res = await verifyAndFetchUser(tokenInput.trim());
|
|
125
125
|
if (!res.ok) {
|
|
126
|
-
spinner.fail(chalk.red('
|
|
127
|
-
printErrorBox('TOKEN
|
|
126
|
+
spinner.fail(chalk.red('Verification failed.'));
|
|
127
|
+
printErrorBox('INVALID TOKEN', [res.error], 'Check that the token is active and has the "repo" scope.');
|
|
128
128
|
return null;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
saveGitHubToken(tokenInput.trim(), res.user);
|
|
132
|
-
spinner.succeed(chalk.hex('#34D399')(
|
|
132
|
+
spinner.succeed(chalk.hex('#34D399')(`Signed in successfully. Welcome, @${res.user.login}.`));
|
|
133
133
|
printGitHubProfile(res.user, 'config');
|
|
134
134
|
return res.user;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
|
-
*
|
|
138
|
+
* Flexible GitHub sign-in menu (OAuth Device Flow or token).
|
|
139
139
|
*/
|
|
140
140
|
export async function promptGitHubLogin() {
|
|
141
|
-
printSection('
|
|
141
|
+
printSection('SIGN IN TO GITHUB');
|
|
142
142
|
|
|
143
143
|
const method = await selectWithBack({
|
|
144
|
-
message: '
|
|
144
|
+
message: 'Choose a sign-in method:',
|
|
145
145
|
choices: [
|
|
146
146
|
{
|
|
147
|
-
name: '[OAuth]
|
|
147
|
+
name: '[OAuth] Authorize in your browser (Device Flow) — Recommended',
|
|
148
148
|
value: 'oauth',
|
|
149
|
-
description: '
|
|
149
|
+
description: 'Open the browser and enter a verification code; no token copy is needed'
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
|
-
name: '[Token]
|
|
152
|
+
name: '[Token] Paste a Personal Access Token (PAT)',
|
|
153
153
|
value: 'token',
|
|
154
|
-
description: '
|
|
154
|
+
description: 'Enter a token created at github.com/settings/tokens'
|
|
155
155
|
}
|
|
156
156
|
]
|
|
157
157
|
});
|
|
@@ -163,30 +163,30 @@ export async function promptGitHubLogin() {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
|
-
*
|
|
166
|
+
* GitHub account selection step in the push wizard.
|
|
167
167
|
* @returns {Promise<{ useGitHub: boolean, user?: any, back?: boolean }>}
|
|
168
168
|
*/
|
|
169
169
|
export async function promptSelectGitHubAccount({ clearPromptOnDone = false } = {}) {
|
|
170
|
-
if (!clearPromptOnDone) printSection('
|
|
170
|
+
if (!clearPromptOnDone) printSection('CHOOSE A GITHUB ACCOUNT');
|
|
171
171
|
|
|
172
172
|
const accounts = getAllGitHubAccounts();
|
|
173
173
|
const currentStatus = await getCurrentGitHubUser();
|
|
174
174
|
|
|
175
175
|
if (accounts.length === 0 && !currentStatus.authenticated) {
|
|
176
|
-
//
|
|
176
|
+
// No account is connected.
|
|
177
177
|
const loginChoice = await selectWithBack({
|
|
178
|
-
message: '
|
|
178
|
+
message: 'No GitHub account is connected. What would you like to do?',
|
|
179
179
|
clearPromptOnDone,
|
|
180
180
|
choices: [
|
|
181
181
|
{
|
|
182
|
-
name: '
|
|
182
|
+
name: 'Sign in to GitHub (authorize in your browser)',
|
|
183
183
|
value: 'login',
|
|
184
|
-
description: '
|
|
184
|
+
description: 'Connect an account to choose or create repositories'
|
|
185
185
|
},
|
|
186
186
|
{
|
|
187
|
-
name: '
|
|
187
|
+
name: 'Skip GitHub (enter a repository URL manually)',
|
|
188
188
|
value: 'skip',
|
|
189
|
-
description: '
|
|
189
|
+
description: 'Continue without a GitHub account and enter an HTTPS URL'
|
|
190
190
|
}
|
|
191
191
|
]
|
|
192
192
|
});
|
|
@@ -197,48 +197,48 @@ export async function promptSelectGitHubAccount({ clearPromptOnDone = false } =
|
|
|
197
197
|
if (user) {
|
|
198
198
|
return { useGitHub: true, user };
|
|
199
199
|
}
|
|
200
|
-
//
|
|
200
|
+
// If sign-in is canceled or fails, allow the user to enter a URL manually.
|
|
201
201
|
return { useGitHub: false };
|
|
202
202
|
}
|
|
203
203
|
return { useGitHub: false };
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
//
|
|
206
|
+
// An account is already connected.
|
|
207
207
|
const currentLogin = currentStatus.authenticated ? currentStatus.user.login : accounts[0]?.login;
|
|
208
208
|
const choices = [
|
|
209
209
|
{
|
|
210
|
-
name: `
|
|
210
|
+
name: `Continue as @${currentLogin} (Current account)`,
|
|
211
211
|
value: `use:${currentLogin}`,
|
|
212
|
-
description: `
|
|
212
|
+
description: `Use @${currentLogin} to connect a repository`
|
|
213
213
|
}
|
|
214
214
|
];
|
|
215
215
|
|
|
216
|
-
//
|
|
216
|
+
// List the other accounts.
|
|
217
217
|
for (const acc of accounts) {
|
|
218
218
|
if (acc.login !== currentLogin) {
|
|
219
219
|
choices.push({
|
|
220
|
-
name:
|
|
220
|
+
name: `Switch to @${acc.login}`,
|
|
221
221
|
value: `switch:${acc.login}`,
|
|
222
|
-
description: `
|
|
222
|
+
description: `Use the @${acc.login} account`
|
|
223
223
|
});
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
choices.push(
|
|
228
228
|
{
|
|
229
|
-
name: '+
|
|
229
|
+
name: '+ Sign in to another GitHub account',
|
|
230
230
|
value: 'add_account',
|
|
231
|
-
description: '
|
|
231
|
+
description: 'Add an account using OAuth or a token'
|
|
232
232
|
},
|
|
233
233
|
{
|
|
234
|
-
name: '
|
|
234
|
+
name: 'Skip GitHub (enter a repository URL manually)',
|
|
235
235
|
value: 'skip',
|
|
236
|
-
description: '
|
|
236
|
+
description: 'Enter a repository URL manually'
|
|
237
237
|
}
|
|
238
238
|
);
|
|
239
239
|
|
|
240
240
|
const picked = await selectWithBack({
|
|
241
|
-
message: '
|
|
241
|
+
message: 'Choose the GitHub account to use:',
|
|
242
242
|
clearPromptOnDone,
|
|
243
243
|
choices
|
|
244
244
|
});
|
|
@@ -256,7 +256,7 @@ export async function promptSelectGitHubAccount({ clearPromptOnDone = false } =
|
|
|
256
256
|
const targetLogin = picked.replace('switch:', '');
|
|
257
257
|
setActiveGitHubAccount(targetLogin);
|
|
258
258
|
const updated = await getCurrentGitHubUser();
|
|
259
|
-
console.log(chalk.hex('#34D399')(` [OK]
|
|
259
|
+
console.log(chalk.hex('#34D399')(` [OK] Switched to @${targetLogin}.\n`));
|
|
260
260
|
return { useGitHub: true, user: updated.user };
|
|
261
261
|
}
|
|
262
262
|
|
|
@@ -264,15 +264,15 @@ export async function promptSelectGitHubAccount({ clearPromptOnDone = false } =
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
/**
|
|
267
|
-
*
|
|
267
|
+
* Create a new GitHub repository.
|
|
268
268
|
* @param {object} options
|
|
269
|
-
* @param {string} options.targetDir
|
|
269
|
+
* @param {string} options.targetDir Source folder (used to suggest a default repository name)
|
|
270
270
|
* @returns {Promise<{ ok: boolean, repo?: any, action?: 'push' | 'done', back?: boolean }>}
|
|
271
271
|
*/
|
|
272
272
|
export async function promptCreateRepo({ targetDir = process.cwd() } = {}) {
|
|
273
273
|
let currentUser = await getCurrentGitHubUser();
|
|
274
274
|
if (!currentUser.authenticated) {
|
|
275
|
-
console.log(chalk.hex('#F59E0B')('\n [!]
|
|
275
|
+
console.log(chalk.hex('#F59E0B')('\n [!] Connect a GitHub account before creating a repository.'));
|
|
276
276
|
const loginUser = await promptGitHubLogin();
|
|
277
277
|
if (!loginUser) {
|
|
278
278
|
return { ok: false, back: true };
|
|
@@ -280,22 +280,22 @@ export async function promptCreateRepo({ targetDir = process.cwd() } = {}) {
|
|
|
280
280
|
currentUser = { authenticated: true, user: loginUser };
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
printSection('
|
|
284
|
-
console.log(chalk.hex('#94A3B8')(` »
|
|
283
|
+
printSection('CREATE A GITHUB REPOSITORY');
|
|
284
|
+
console.log(chalk.hex('#94A3B8')(` » Creating under account: ${chalk.hex('#38BDF8').bold('@' + currentUser.user.login)}`));
|
|
285
285
|
|
|
286
286
|
const folderName = path.basename(path.resolve(targetDir))
|
|
287
287
|
.toLowerCase()
|
|
288
288
|
.replace(/[^a-z0-9_.-]/g, '-');
|
|
289
289
|
|
|
290
290
|
const repoName = await inputWithBack({
|
|
291
|
-
message: '
|
|
292
|
-
hint: ['
|
|
291
|
+
message: 'New repository name',
|
|
292
|
+
hint: ['Letters, numbers, -, _, and . only (e.g. my-awesome-repo)'],
|
|
293
293
|
default: folderName || 'my-awesome-repo',
|
|
294
294
|
validate: (val) => {
|
|
295
295
|
const trimmed = (val || '').trim();
|
|
296
|
-
if (!trimmed) return '
|
|
296
|
+
if (!trimmed) return 'Repository name cannot be empty.';
|
|
297
297
|
if (!/^[a-zA-Z0-9_.-]+$/.test(trimmed)) {
|
|
298
|
-
return '
|
|
298
|
+
return 'Use only letters, numbers, hyphens (-), underscores (_), or periods (.).';
|
|
299
299
|
}
|
|
300
300
|
return true;
|
|
301
301
|
}
|
|
@@ -304,24 +304,24 @@ export async function promptCreateRepo({ targetDir = process.cwd() } = {}) {
|
|
|
304
304
|
if (repoName === BACK) return { ok: false, back: true };
|
|
305
305
|
|
|
306
306
|
const description = await inputWithBack({
|
|
307
|
-
message: '
|
|
308
|
-
hint: ['
|
|
307
|
+
message: 'Short project description',
|
|
308
|
+
hint: ['Optional; press Enter to skip'],
|
|
309
309
|
default: ''
|
|
310
310
|
});
|
|
311
311
|
if (description === BACK) return { ok: false, back: true };
|
|
312
312
|
|
|
313
313
|
const visibility = await selectWithBack({
|
|
314
|
-
message: '
|
|
314
|
+
message: 'Repository visibility:',
|
|
315
315
|
choices: [
|
|
316
|
-
{ name: 'Public (
|
|
317
|
-
{ name: 'Private (
|
|
316
|
+
{ name: 'Public (Anyone can view it)', value: 'public', description: 'Public repository' },
|
|
317
|
+
{ name: 'Private (Only you and collaborators can view it)', value: 'private', description: 'Private repository' }
|
|
318
318
|
]
|
|
319
319
|
});
|
|
320
320
|
if (visibility === BACK) return { ok: false, back: true };
|
|
321
321
|
|
|
322
322
|
const isPrivate = visibility === 'private';
|
|
323
323
|
|
|
324
|
-
const spinner = ora({ text:
|
|
324
|
+
const spinner = ora({ text: `Creating '${repoName}' on GitHub…`, color: 'cyan' }).start();
|
|
325
325
|
const createRes = await createGitHubRepo({
|
|
326
326
|
name: repoName,
|
|
327
327
|
description: String(description || ''),
|
|
@@ -330,36 +330,36 @@ export async function promptCreateRepo({ targetDir = process.cwd() } = {}) {
|
|
|
330
330
|
});
|
|
331
331
|
|
|
332
332
|
if (!createRes.ok) {
|
|
333
|
-
spinner.fail(chalk.red('
|
|
334
|
-
printErrorBox('
|
|
333
|
+
spinner.fail(chalk.red('Could not create repository.'));
|
|
334
|
+
printErrorBox('REPOSITORY CREATION FAILED', [createRes.error]);
|
|
335
335
|
return { ok: false, error: createRes.error };
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
spinner.succeed(chalk.hex('#34D399')(
|
|
338
|
+
spinner.succeed(chalk.hex('#34D399')(`Created @${currentUser.user.login}/${repoName} successfully.`));
|
|
339
339
|
printRepoCreatedBox(createRes.repo);
|
|
340
340
|
|
|
341
341
|
return { ok: true, repo: createRes.repo };
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
/**
|
|
345
|
-
*
|
|
345
|
+
* Display the user's most recently updated repositories.
|
|
346
346
|
*/
|
|
347
347
|
export async function showUserRepos({ limit = 15 } = {}) {
|
|
348
|
-
const spinner = ora({ text: '
|
|
348
|
+
const spinner = ora({ text: 'Loading repositories from GitHub…', color: 'cyan' }).start();
|
|
349
349
|
const res = await listUserRepos({ limit });
|
|
350
350
|
spinner.stop();
|
|
351
351
|
|
|
352
352
|
if (!res.ok) {
|
|
353
|
-
printErrorBox('
|
|
353
|
+
printErrorBox('COULD NOT LOAD REPOSITORIES', [res.error]);
|
|
354
354
|
return [];
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
if (res.repos.length === 0) {
|
|
358
|
-
console.log(chalk.hex('#94A3B8')(' »
|
|
358
|
+
console.log(chalk.hex('#94A3B8')(' » This account does not have any repositories yet.\n'));
|
|
359
359
|
return [];
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
printSection(`
|
|
362
|
+
printSection(`REPOSITORIES (${res.repos.length} RECENT)`);
|
|
363
363
|
res.repos.forEach((r, idx) => {
|
|
364
364
|
const lock = r.private ? chalk.hex('#F59E0B')('[Private]') : chalk.hex('#10B981')('[Public ]');
|
|
365
365
|
const num = chalk.hex('#64748B')(String(idx + 1).padStart(2, ' '));
|
|
@@ -373,43 +373,43 @@ export async function showUserRepos({ limit = 15 } = {}) {
|
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
/**
|
|
376
|
-
*
|
|
376
|
+
* Sign out of a GitHub account (defaults to the active account).
|
|
377
377
|
*/
|
|
378
378
|
export function logoutGitHub(login = null) {
|
|
379
379
|
const accounts = getAllGitHubAccounts();
|
|
380
380
|
const target = login || accounts.find((a) => a.active)?.login || accounts[0]?.login || 'GitHub';
|
|
381
381
|
removeGitHubToken(login);
|
|
382
|
-
console.log(chalk.hex('#34D399')(`\n [OK]
|
|
382
|
+
console.log(chalk.hex('#34D399')(`\n [OK] Signed out @${target} and removed the token from this machine.\n`));
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
/**
|
|
386
|
-
*
|
|
386
|
+
* Interactive GitHub management menu.
|
|
387
387
|
*/
|
|
388
388
|
export async function runGitHubMenu() {
|
|
389
389
|
while (true) {
|
|
390
390
|
const userStatus = await getCurrentGitHubUser();
|
|
391
391
|
const isLogged = userStatus.authenticated;
|
|
392
392
|
|
|
393
|
-
printSection('
|
|
393
|
+
printSection('GITHUB & REPOSITORIES');
|
|
394
394
|
if (isLogged) {
|
|
395
|
-
console.log(chalk.hex('#34D399')(` [✓]
|
|
395
|
+
console.log(chalk.hex('#34D399')(` [✓] Signed in as @${userStatus.user.login} (${userStatus.user.name || 'Developer'})`));
|
|
396
396
|
} else {
|
|
397
|
-
console.log(chalk.hex('#F59E0B')(' [!]
|
|
397
|
+
console.log(chalk.hex('#F59E0B')(' [!] GitHub is not connected.'));
|
|
398
398
|
}
|
|
399
399
|
|
|
400
400
|
const choices = [
|
|
401
|
-
{ name: '
|
|
402
|
-
{ name: '
|
|
401
|
+
{ name: 'Account', value: 'status', description: 'View profile and connection status' },
|
|
402
|
+
{ name: 'Repositories', value: 'list', description: 'Browse your repositories and clone URLs' },
|
|
403
403
|
isLogged
|
|
404
|
-
? { name: '
|
|
405
|
-
: { name: '
|
|
406
|
-
...(isLogged ? [{ name: '
|
|
407
|
-
{ name: '
|
|
404
|
+
? { name: 'Sign in to another account', value: 'login', description: 'Add or switch GitHub accounts' }
|
|
405
|
+
: { name: 'Sign in', value: 'login', description: 'Connect using a browser or token' },
|
|
406
|
+
...(isLogged ? [{ name: 'Sign out', value: 'logout', description: 'Disconnect the current account' }] : []),
|
|
407
|
+
{ name: 'Go back', value: BACK, description: 'Return to the dashboard' }
|
|
408
408
|
];
|
|
409
409
|
|
|
410
410
|
const action = await selectBox({
|
|
411
|
-
message: '
|
|
412
|
-
hint: '
|
|
411
|
+
message: 'Choose a GitHub action:',
|
|
412
|
+
hint: 'View accounts · browse repositories · switch accounts',
|
|
413
413
|
choices
|
|
414
414
|
});
|
|
415
415
|
|
package/lib/commands/pull.js
CHANGED
|
@@ -28,7 +28,7 @@ export async function runPullCommand(remote = 'origin', branch, options = {}) {
|
|
|
28
28
|
if (options.dir) {
|
|
29
29
|
const r = resolveTargetDir(options.dir, process.cwd());
|
|
30
30
|
if (!r.ok) {
|
|
31
|
-
printErrorBox('
|
|
31
|
+
printErrorBox('INVALID DIRECTORY', [r.error]);
|
|
32
32
|
if (fromMenu) return;
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
|
@@ -36,7 +36,7 @@ export async function runPullCommand(remote = 'origin', branch, options = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (!isGitRepo(targetDir)) {
|
|
39
|
-
printErrorBox('
|
|
39
|
+
printErrorBox('NOT A GIT REPOSITORY', [`'${targetDir}' is not a Git repository.`]);
|
|
40
40
|
if (fromMenu) return;
|
|
41
41
|
process.exit(1);
|
|
42
42
|
}
|
|
@@ -46,43 +46,43 @@ export async function runPullCommand(remote = 'origin', branch, options = {}) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
if (!branch) {
|
|
49
|
-
printErrorBox('
|
|
49
|
+
printErrorBox('CANNOT DETERMINE BRANCH', ['HEAD is detached, so the branch to pull is unknown.'], 'Check out a branch and try again.');
|
|
50
50
|
if (fromMenu) return;
|
|
51
51
|
process.exit(1);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const isInteractive = !options.yes;
|
|
55
55
|
try {
|
|
56
|
-
printSection(`PULL --
|
|
56
|
+
printSection(`PULL --FF-ONLY ${remote}/${branch}`);
|
|
57
57
|
printTargetDir(targetDir);
|
|
58
58
|
const result = pullFromRemote(remote, branch, targetDir);
|
|
59
|
-
console.log(chalk.hex('#34D399')(` [OK] ${result || 'Repository
|
|
59
|
+
console.log(chalk.hex('#34D399')(` [OK] ${result || 'Repository is up to date (fast-forward).'}\n`));
|
|
60
60
|
if (!fromMenu) printFooter();
|
|
61
61
|
} catch (error) {
|
|
62
|
-
printErrorBox('PULL
|
|
62
|
+
printErrorBox('PULL FAILED', [String(error.message).split('\n')[0]], 'Fast-forward failed because the histories diverged. You can try pull --rebase.');
|
|
63
63
|
if (!isInteractive) {
|
|
64
64
|
if (fromMenu) return;
|
|
65
65
|
process.exit(1);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const choice = await selectBox({
|
|
69
|
-
message: '
|
|
69
|
+
message: 'Fast-forward pull failed. What would you like to do?',
|
|
70
70
|
hint: `git pull --rebase ${remote} ${branch}`,
|
|
71
71
|
choices: [
|
|
72
|
-
{ name: '
|
|
73
|
-
{ name: '
|
|
74
|
-
{ name: '
|
|
72
|
+
{ name: 'Try pulling with rebase', value: 'rebase', description: `git pull --rebase ${remote} ${branch}` },
|
|
73
|
+
{ name: 'Go back', value: 'back', description: 'Return to the previous screen' },
|
|
74
|
+
{ name: 'Exit', value: 'exit', description: 'Stop here' }
|
|
75
75
|
]
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
if (choice === 'rebase') {
|
|
79
79
|
try {
|
|
80
|
-
console.log(chalk.hex('#38BDF8')(`\n »
|
|
80
|
+
console.log(chalk.hex('#38BDF8')(`\n » Running: git pull --rebase ${remote} ${branch}...`));
|
|
81
81
|
const out = pullFromRemote(remote, branch, targetDir, { rebase: true });
|
|
82
|
-
console.log(chalk.hex('#34D399')(` [OK] Pull --rebase
|
|
82
|
+
console.log(chalk.hex('#34D399')(` [OK] Pull --rebase succeeded. ${String(out).split('\n')[0].slice(0, 100)}\n`));
|
|
83
83
|
if (!fromMenu) printFooter();
|
|
84
84
|
} catch (rebaseErr) {
|
|
85
|
-
printErrorBox('PULL --REBASE
|
|
85
|
+
printErrorBox('PULL --REBASE FAILED', [String(rebaseErr.message).split('\n')[0]], 'There may be merge conflicts. Run git status to review them.');
|
|
86
86
|
if (!fromMenu) process.exit(1);
|
|
87
87
|
}
|
|
88
88
|
}
|