gitpadi 2.0.1 → 2.0.2
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/dist/cli.js +278 -144
- package/dist/commands/contribute.js +291 -0
- package/dist/commands/issues.js +72 -8
- package/dist/commands/repos.js +31 -21
- package/dist/core/github.js +47 -2
- package/package.json +1 -1
- package/src/cli.ts +311 -172
- package/src/commands/contribute.ts +331 -0
- package/src/commands/issues.ts +75 -8
- package/src/commands/repos.ts +41 -26
- package/src/core/github.ts +54 -2
package/src/cli.ts
CHANGED
|
@@ -9,6 +9,9 @@ import chalk from 'chalk';
|
|
|
9
9
|
import inquirer from 'inquirer';
|
|
10
10
|
import gradient from 'gradient-string';
|
|
11
11
|
import figlet from 'figlet';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
|
|
12
15
|
import boxen from 'boxen';
|
|
13
16
|
import { createSpinner } from 'nanospinner';
|
|
14
17
|
import { Octokit } from '@octokit/rest';
|
|
@@ -19,6 +22,7 @@ import * as prs from './commands/prs.js';
|
|
|
19
22
|
import * as repos from './commands/repos.js';
|
|
20
23
|
import * as contributors from './commands/contributors.js';
|
|
21
24
|
import * as releases from './commands/releases.js';
|
|
25
|
+
import * as contribute from './commands/contribute.js';
|
|
22
26
|
|
|
23
27
|
const VERSION = '2.0.0';
|
|
24
28
|
|
|
@@ -124,62 +128,73 @@ async function bootSequence() {
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
// ── Onboarding ─────────────────────────────────────────────────────────
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Ensures we have a valid GitHub token
|
|
133
|
+
*/
|
|
134
|
+
async function ensureAuthenticated() {
|
|
129
135
|
initGitHub();
|
|
130
|
-
|
|
131
136
|
let token = getToken();
|
|
132
|
-
let owner = getOwner();
|
|
133
|
-
let repo = getRepo();
|
|
134
137
|
|
|
135
|
-
|
|
136
|
-
if (token && owner && repo) {
|
|
137
|
-
const spinner = createSpinner(dim('Authenticating...')).start();
|
|
138
|
+
if (token) {
|
|
138
139
|
try {
|
|
139
140
|
const octokit = getOctokit();
|
|
140
141
|
await octokit.users.getAuthenticated();
|
|
141
|
-
|
|
142
|
-
console.log('');
|
|
142
|
+
// Silent success if token is valid
|
|
143
143
|
return;
|
|
144
144
|
} catch {
|
|
145
|
-
|
|
146
|
-
// Clear and continue to prompt
|
|
145
|
+
console.log(red(' ❌ Saved session invalid. Re-authenticating...'));
|
|
147
146
|
token = '';
|
|
148
147
|
}
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
console.log(neon(' ⚡
|
|
150
|
+
console.log(neon(' ⚡ Authentication — let\'s connect you to GitHub.\n'));
|
|
152
151
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
when: !token,
|
|
152
|
+
const { t } = await inquirer.prompt([{
|
|
153
|
+
type: 'password',
|
|
154
|
+
name: 't',
|
|
155
|
+
message: magenta('🔑 GitHub Token') + dim(' (ghp_xxx):'),
|
|
156
|
+
mask: '•',
|
|
157
|
+
validate: async (v: string) => {
|
|
158
|
+
if (!v.startsWith('ghp_') && !v.startsWith('github_pat_')) {
|
|
159
|
+
return 'Token should start with ghp_ or github_pat_';
|
|
160
|
+
}
|
|
161
|
+
const spinner = createSpinner(dim('Validating token...')).start();
|
|
162
|
+
try {
|
|
163
|
+
const tempOctokit = new Octokit({ auth: v });
|
|
164
|
+
await tempOctokit.users.getAuthenticated();
|
|
165
|
+
spinner.success({ text: green('Token valid!') });
|
|
166
|
+
return true;
|
|
167
|
+
} catch {
|
|
168
|
+
spinner.error({ text: red('Invalid token - GitHub rejected it.') });
|
|
169
|
+
return 'Invalid token';
|
|
170
|
+
}
|
|
175
171
|
},
|
|
172
|
+
}]);
|
|
173
|
+
|
|
174
|
+
initGitHub(t);
|
|
175
|
+
saveConfig({ token: t, owner: getOwner() || '', repo: getRepo() || '' });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Ensures we have a target repository (Owner/Repo)
|
|
180
|
+
*/
|
|
181
|
+
async function ensureTargetRepo() {
|
|
182
|
+
let owner = getOwner();
|
|
183
|
+
let repo = getRepo();
|
|
184
|
+
|
|
185
|
+
if (owner && repo) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log(neon('\n 📦 Project Targeting — which repo are we working on?\n'));
|
|
190
|
+
|
|
191
|
+
const answers = await inquirer.prompt([
|
|
176
192
|
{
|
|
177
193
|
type: 'input',
|
|
178
194
|
name: 'owner',
|
|
179
195
|
message: cyan('👤 GitHub Owner/Org:'),
|
|
180
196
|
default: owner || '',
|
|
181
197
|
validate: (v: string) => v.length > 0 || 'Required',
|
|
182
|
-
when: !owner,
|
|
183
198
|
},
|
|
184
199
|
{
|
|
185
200
|
type: 'input',
|
|
@@ -187,79 +202,133 @@ async function onboarding() {
|
|
|
187
202
|
message: cyan('📦 Repository name:'),
|
|
188
203
|
default: repo || '',
|
|
189
204
|
validate: (v: string) => v.length > 0 || 'Required',
|
|
190
|
-
when: !repo,
|
|
191
205
|
},
|
|
192
206
|
]);
|
|
193
207
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const finalRepo = answers.repo || repo;
|
|
197
|
-
|
|
198
|
-
initGitHub(finalToken, finalOwner, finalRepo);
|
|
199
|
-
saveConfig({ token: finalToken, owner: finalOwner, repo: finalRepo });
|
|
208
|
+
setRepo(answers.owner, answers.repo);
|
|
209
|
+
saveConfig({ token: getToken(), owner: answers.owner, repo: answers.repo });
|
|
200
210
|
|
|
201
|
-
const spinner = createSpinner(dim('Connecting
|
|
211
|
+
const spinner = createSpinner(dim('Connecting...')).start();
|
|
202
212
|
await sleep(400);
|
|
203
|
-
spinner.success({ text: green(`Locked in → ${cyan(`${
|
|
204
|
-
|
|
205
|
-
console.log('');
|
|
206
|
-
console.log(boxen(
|
|
207
|
-
dim('💡 Session saved to ~/.gitpadi/config.json\n\n') +
|
|
208
|
-
dim('Set environment variables to override:\n') +
|
|
209
|
-
yellow(' export GITHUB_TOKEN=ghp_xxx\n') +
|
|
210
|
-
yellow(' export GITHUB_OWNER=' + finalOwner + '\n') +
|
|
211
|
-
yellow(' export GITHUB_REPO=' + finalRepo),
|
|
212
|
-
{ padding: 1, borderColor: 'yellow', dimBorder: true, borderStyle: 'round' }
|
|
213
|
-
));
|
|
213
|
+
spinner.success({ text: green(`Locked in → ${cyan(`${answers.owner}/${answers.repo}`)}`) });
|
|
214
214
|
console.log('');
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
// ──
|
|
217
|
+
// ── Mode Selector ──────────────────────────────────────────────────────
|
|
218
218
|
async function mainMenu() {
|
|
219
219
|
while (true) {
|
|
220
220
|
line('═');
|
|
221
|
-
console.log(cyber(' ⟨ GITPADI
|
|
222
|
-
console.log(dim(' Select
|
|
221
|
+
console.log(cyber(' ⟨ GITPADI MODE SELECTOR ⟩'));
|
|
222
|
+
console.log(dim(' Select your workflow persona to continue'));
|
|
223
223
|
line('═');
|
|
224
224
|
console.log('');
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
226
|
+
const { mode } = await inquirer.prompt([{
|
|
227
|
+
type: 'list',
|
|
228
|
+
name: 'mode',
|
|
229
|
+
message: bold('Choose your path:'),
|
|
230
|
+
choices: [
|
|
231
|
+
{ name: `${cyan('✨')} ${bold('Contributor Mode')} ${dim('— fork, clone, sync, submit PRs')}`, value: 'contributor' },
|
|
232
|
+
{ name: `${magenta('🛠️')} ${bold('Maintainer Mode')} ${dim('— manage issues, PRs, contributors')}`, value: 'maintainer' },
|
|
233
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
234
|
+
{ name: `${dim('👋')} ${dim('Exit')}`, value: 'exit' },
|
|
235
|
+
],
|
|
236
|
+
loop: false,
|
|
237
|
+
}]);
|
|
238
|
+
|
|
239
|
+
if (mode === 'contributor') await safeMenu(contributorMenu);
|
|
240
|
+
else if (mode === 'maintainer') {
|
|
241
|
+
await ensureTargetRepo();
|
|
242
|
+
await safeMenu(maintainerMenu);
|
|
243
|
+
}
|
|
244
|
+
else break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── Contributor Menu ───────────────────────────────────────────────────
|
|
249
|
+
async function contributorMenu() {
|
|
250
|
+
while (true) {
|
|
251
|
+
line('═');
|
|
252
|
+
console.log(cyan(' ✨ GITPADI CONTRIBUTOR WORKSPACE'));
|
|
253
|
+
console.log(dim(' Automating forking, syncing, and PR delivery'));
|
|
254
|
+
line('═');
|
|
255
|
+
|
|
256
|
+
// Auto-check for updates if in a repo
|
|
257
|
+
if (getOwner() && getRepo()) {
|
|
258
|
+
await contribute.syncBranch();
|
|
253
259
|
}
|
|
254
260
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
const { action } = await inquirer.prompt([{
|
|
262
|
+
type: 'list',
|
|
263
|
+
name: 'action',
|
|
264
|
+
message: bold('Contributor Action:'),
|
|
265
|
+
choices: [
|
|
266
|
+
{ name: `${cyan('🚀')} ${bold('Start Contribution')} ${dim('— fork, clone & branch')}`, value: 'start' },
|
|
267
|
+
{ name: `${green('🔄')} ${bold('Sync with Upstream')} ${dim('— pull latest changes')}`, value: 'sync' },
|
|
268
|
+
{ name: `${yellow('📋')} ${bold('View Action Logs')} ${dim('— check PR/commit status')}`, value: 'logs' },
|
|
269
|
+
{ name: `${magenta('🚀')} ${bold('Submit PR')} ${dim('— add, commit, push & PR')}`, value: 'submit' },
|
|
270
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
271
|
+
{ name: `${dim('⬅')} ${dim('Back to Mode Selector')}`, value: 'back' },
|
|
272
|
+
],
|
|
273
|
+
loop: false,
|
|
274
|
+
}]);
|
|
275
|
+
|
|
276
|
+
if (action === 'back') break;
|
|
277
|
+
|
|
278
|
+
if (action === 'start') {
|
|
279
|
+
const { url } = await ask([{ type: 'input', name: 'url', message: 'Enter Repo or Issue URL:' }]);
|
|
280
|
+
await contribute.forkAndClone(url);
|
|
281
|
+
} else {
|
|
282
|
+
// These actions require a target repo
|
|
283
|
+
await ensureTargetRepo();
|
|
284
|
+
|
|
285
|
+
if (action === 'sync') {
|
|
286
|
+
await contribute.syncBranch();
|
|
287
|
+
} else if (action === 'logs') {
|
|
288
|
+
await contribute.viewLogs();
|
|
289
|
+
} else if (action === 'submit') {
|
|
290
|
+
const { title, message, issue } = await ask([
|
|
291
|
+
{ type: 'input', name: 'title', message: 'PR Title:' },
|
|
292
|
+
{ type: 'input', name: 'message', message: 'Commit message (optional):' },
|
|
293
|
+
{ type: 'input', name: 'issue', message: 'Related Issue # (optional):' }
|
|
294
|
+
]);
|
|
295
|
+
await contribute.submitPR({
|
|
296
|
+
title,
|
|
297
|
+
message: message || title,
|
|
298
|
+
issue: issue ? parseInt(issue) : undefined
|
|
299
|
+
});
|
|
300
|
+
}
|
|
262
301
|
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── Maintainer Menu ────────────────────────────────────────────────────
|
|
306
|
+
async function maintainerMenu() {
|
|
307
|
+
while (true) {
|
|
308
|
+
line('═');
|
|
309
|
+
console.log(magenta(' 🛠️ GITPADI MAINTAINER PANEL'));
|
|
310
|
+
console.log(dim(' Managing repository health and contributor intake'));
|
|
311
|
+
line('═');
|
|
312
|
+
console.log('');
|
|
313
|
+
|
|
314
|
+
const { category } = await inquirer.prompt([{
|
|
315
|
+
type: 'list',
|
|
316
|
+
name: 'category',
|
|
317
|
+
message: bold('Select operation:'),
|
|
318
|
+
choices: [
|
|
319
|
+
{ name: `${magenta('📋')} ${bold('Issues')} ${dim('— create, close, delete, assign, search')}`, value: 'issues' },
|
|
320
|
+
{ name: `${cyan('🔀')} ${bold('Pull Requests')} ${dim('— merge, review, approve, diff')}`, value: 'prs' },
|
|
321
|
+
{ name: `${green('📦')} ${bold('Repositories')} ${dim('— create, delete, clone, info')}`, value: 'repos' },
|
|
322
|
+
{ name: `${yellow('🏆')} ${bold('Contributors')} ${dim('— score, rank, auto-assign best')}`, value: 'contributors' },
|
|
323
|
+
{ name: `${red('🚀')} ${bold('Releases')} ${dim('— create, list, manage')}`, value: 'releases' },
|
|
324
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
325
|
+
{ name: `${dim('⚙️')} ${dim('Switch Repo')}`, value: 'switch' },
|
|
326
|
+
{ name: `${dim('⬅')} ${dim('Back to Mode Selector')}`, value: 'back' },
|
|
327
|
+
],
|
|
328
|
+
loop: false,
|
|
329
|
+
}]);
|
|
330
|
+
|
|
331
|
+
if (category === 'back') break;
|
|
263
332
|
|
|
264
333
|
if (category === 'switch') {
|
|
265
334
|
await safeMenu(async () => {
|
|
@@ -276,10 +345,10 @@ async function mainMenu() {
|
|
|
276
345
|
}
|
|
277
346
|
|
|
278
347
|
if (category === 'issues') await safeMenu(issueMenu);
|
|
279
|
-
if (category === 'prs') await safeMenu(prMenu);
|
|
280
|
-
if (category === 'repos') await safeMenu(repoMenu);
|
|
281
|
-
if (category === 'contributors') await safeMenu(
|
|
282
|
-
if (category === 'releases') await safeMenu(releaseMenu);
|
|
348
|
+
else if (category === 'prs') await safeMenu(prMenu);
|
|
349
|
+
else if (category === 'repos') await safeMenu(repoMenu);
|
|
350
|
+
else if (category === 'contributors') await safeMenu(contributorScoringMenu);
|
|
351
|
+
else if (category === 'releases') await safeMenu(releaseMenu);
|
|
283
352
|
|
|
284
353
|
console.log('');
|
|
285
354
|
}
|
|
@@ -290,11 +359,11 @@ async function issueMenu() {
|
|
|
290
359
|
const { action } = await inquirer.prompt([{
|
|
291
360
|
type: 'list', name: 'action', message: magenta('📋 Issue Operation:'),
|
|
292
361
|
choices: [
|
|
293
|
-
{ name: ` ${dim('⬅ Back
|
|
362
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
294
363
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
295
364
|
{ name: ` ${green('▸')} List open issues`, value: 'list' },
|
|
296
365
|
{ name: ` ${green('▸')} Create single issue`, value: 'create' },
|
|
297
|
-
{ name: ` ${magenta('▸')} Bulk create from JSON
|
|
366
|
+
{ name: ` ${magenta('▸')} Bulk create from file (JSON/MD)`, value: 'bulk' },
|
|
298
367
|
{ name: ` ${red('▸')} Close issue`, value: 'close' },
|
|
299
368
|
{ name: ` ${green('▸')} Reopen issue`, value: 'reopen' },
|
|
300
369
|
{ name: ` ${red('▸')} Delete (close & lock)`, value: 'delete' },
|
|
@@ -323,19 +392,13 @@ async function issueMenu() {
|
|
|
323
392
|
await issues.createIssue(a);
|
|
324
393
|
} else if (action === 'bulk') {
|
|
325
394
|
const a = await ask([
|
|
326
|
-
{ type: 'input', name: 'file', message: yellow('📁 Path to issues JSON
|
|
395
|
+
{ type: 'input', name: 'file', message: yellow('📁 Path to issues file (JSON or MD)') + dim(' (q=back):') },
|
|
327
396
|
{ type: 'confirm', name: 'dryRun', message: 'Dry run first?', default: true },
|
|
328
397
|
{ type: 'number', name: 'start', message: dim('Start index:'), default: 1 },
|
|
329
398
|
{ type: 'number', name: 'end', message: dim('End index:'), default: 999 },
|
|
330
399
|
]);
|
|
331
400
|
await issues.createIssuesFromFile(a.file, { dryRun: a.dryRun, start: a.start, end: a.end });
|
|
332
401
|
} else if (action === 'assign-best') {
|
|
333
|
-
// ── Smart Auto-Assign Flow ──
|
|
334
|
-
// 1. Fetch all open issues with comments
|
|
335
|
-
// 2. Filter to ones with applicant comments
|
|
336
|
-
// 3. Let user pick from a list
|
|
337
|
-
// 4. Show applicants + scores, then assign best
|
|
338
|
-
|
|
339
402
|
const spinner = createSpinner(dim('Finding issues with applicants...')).start();
|
|
340
403
|
const octokit = getOctokit();
|
|
341
404
|
|
|
@@ -343,10 +406,7 @@ async function issueMenu() {
|
|
|
343
406
|
owner: getOwner(), repo: getRepo(), state: 'open', per_page: 50,
|
|
344
407
|
});
|
|
345
408
|
|
|
346
|
-
|
|
347
|
-
const realIssues = allIssues.filter((i) => !i.pull_request && i.comments > 0);
|
|
348
|
-
|
|
349
|
-
// Check each issue for applicant comments
|
|
409
|
+
const realIssues = allIssues.filter((i: any) => !i.pull_request && i.comments > 0);
|
|
350
410
|
const issuesWithApplicants: Array<{ number: number; title: string; applicants: string[]; labels: string[] }> = [];
|
|
351
411
|
|
|
352
412
|
for (const issue of realIssues) {
|
|
@@ -355,7 +415,7 @@ async function issueMenu() {
|
|
|
355
415
|
});
|
|
356
416
|
|
|
357
417
|
const applicantUsers = new Set<string>();
|
|
358
|
-
comments.forEach((c) => {
|
|
418
|
+
comments.forEach((c: any) => {
|
|
359
419
|
if (c.user?.login && c.user.login !== 'github-actions[bot]') {
|
|
360
420
|
applicantUsers.add(c.user.login);
|
|
361
421
|
}
|
|
@@ -366,7 +426,7 @@ async function issueMenu() {
|
|
|
366
426
|
number: issue.number,
|
|
367
427
|
title: issue.title.substring(0, 45),
|
|
368
428
|
applicants: Array.from(applicantUsers),
|
|
369
|
-
labels: issue.labels.map((l) => typeof l === 'string' ? l : l.name || '').filter(Boolean),
|
|
429
|
+
labels: issue.labels.map((l: any) => typeof l === 'string' ? l : l.name || '').filter(Boolean),
|
|
370
430
|
});
|
|
371
431
|
}
|
|
372
432
|
}
|
|
@@ -380,7 +440,6 @@ async function issueMenu() {
|
|
|
380
440
|
|
|
381
441
|
console.log(`\n${bold(`🏆 Issues with Applicants`)} (${issuesWithApplicants.length})\n`);
|
|
382
442
|
|
|
383
|
-
// Let user pick an issue
|
|
384
443
|
const { picked } = await inquirer.prompt([{
|
|
385
444
|
type: 'list', name: 'picked', message: yellow('Select an issue:'),
|
|
386
445
|
choices: [
|
|
@@ -393,8 +452,6 @@ async function issueMenu() {
|
|
|
393
452
|
}]);
|
|
394
453
|
|
|
395
454
|
if (picked === -1) return;
|
|
396
|
-
|
|
397
|
-
// Run the scoring
|
|
398
455
|
await issues.assignBest(picked);
|
|
399
456
|
|
|
400
457
|
} else if (action === 'close' || action === 'reopen' || action === 'delete') {
|
|
@@ -429,7 +486,7 @@ async function prMenu() {
|
|
|
429
486
|
const { action } = await inquirer.prompt([{
|
|
430
487
|
type: 'list', name: 'action', message: cyan('🔀 PR Operation:'),
|
|
431
488
|
choices: [
|
|
432
|
-
{ name: ` ${dim('⬅ Back
|
|
489
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
433
490
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
434
491
|
{ name: ` ${green('▸')} List pull requests`, value: 'list' },
|
|
435
492
|
{ name: ` ${green('▸')} Merge PR ${dim('(checks CI first)')}`, value: 'merge' },
|
|
@@ -476,7 +533,7 @@ async function repoMenu() {
|
|
|
476
533
|
const { action } = await inquirer.prompt([{
|
|
477
534
|
type: 'list', name: 'action', message: green('📦 Repo Operation:'),
|
|
478
535
|
choices: [
|
|
479
|
-
{ name: ` ${dim('⬅ Back
|
|
536
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
480
537
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
481
538
|
{ name: ` ${green('▸')} List repositories`, value: 'list' },
|
|
482
539
|
{ name: ` ${green('▸')} Create repo`, value: 'create' },
|
|
@@ -504,26 +561,88 @@ async function repoMenu() {
|
|
|
504
561
|
]);
|
|
505
562
|
await repos.createRepo(a.name, { org: a.org || undefined, description: a.description, private: a.isPrivate });
|
|
506
563
|
} else if (action === 'delete') {
|
|
507
|
-
const
|
|
508
|
-
{ type: 'input', name: '
|
|
509
|
-
{ type: 'input', name: 'org', message: 'Org:', default: getOwner() },
|
|
564
|
+
const { org } = await ask([
|
|
565
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
510
566
|
]);
|
|
567
|
+
|
|
568
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
569
|
+
let repoToDelete: string;
|
|
570
|
+
|
|
571
|
+
if (fetchedRepos.length > 0) {
|
|
572
|
+
const { selection } = await inquirer.prompt([{
|
|
573
|
+
type: 'list',
|
|
574
|
+
name: 'selection',
|
|
575
|
+
message: red('📦 Select Repo to DELETE:'),
|
|
576
|
+
choices: [
|
|
577
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
578
|
+
...fetchedRepos.map((r: any) => ({ name: r.name, value: r.name }))
|
|
579
|
+
]
|
|
580
|
+
}]);
|
|
581
|
+
if (selection === 'back') return;
|
|
582
|
+
repoToDelete = selection;
|
|
583
|
+
} else {
|
|
584
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: red('📦 Repo name to DELETE:') }]);
|
|
585
|
+
repoToDelete = name;
|
|
586
|
+
}
|
|
587
|
+
|
|
511
588
|
const { confirm } = await ask([
|
|
512
|
-
{ type: 'input', name: 'confirm', message: red(`⚠️ Type "${
|
|
589
|
+
{ type: 'input', name: 'confirm', message: red(`⚠️ Type "${repoToDelete}" to confirm deletion:`), validate: (v: string) => v === repoToDelete || 'Name doesn\'t match' },
|
|
513
590
|
]);
|
|
514
|
-
if (confirm ===
|
|
591
|
+
if (confirm === repoToDelete) await repos.deleteRepo(repoToDelete, { org });
|
|
515
592
|
} else if (action === 'clone') {
|
|
516
|
-
const
|
|
517
|
-
{ type: 'input', name: '
|
|
518
|
-
{ type: 'input', name: 'org', message: dim('Org:'), default: getOwner() },
|
|
593
|
+
const { org } = await ask([
|
|
594
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
519
595
|
]);
|
|
520
|
-
|
|
596
|
+
|
|
597
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
598
|
+
let repoToClone: string;
|
|
599
|
+
|
|
600
|
+
if (fetchedRepos.length > 0) {
|
|
601
|
+
const { selection } = await inquirer.prompt([{
|
|
602
|
+
type: 'list',
|
|
603
|
+
name: 'selection',
|
|
604
|
+
message: cyan('📦 Select Repo to Clone:'),
|
|
605
|
+
choices: [
|
|
606
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
607
|
+
...fetchedRepos.map((r: any) => ({ name: r.name, value: r.name }))
|
|
608
|
+
]
|
|
609
|
+
}]);
|
|
610
|
+
if (selection === 'back') return;
|
|
611
|
+
repoToClone = selection;
|
|
612
|
+
} else {
|
|
613
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: cyan('📦 Repo name to Clone:') }]);
|
|
614
|
+
repoToClone = name;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const { dir } = await ask([
|
|
618
|
+
{ type: 'input', name: 'dir', message: dim('Destination path (leave blank for default):'), default: '' },
|
|
619
|
+
]);
|
|
620
|
+
await repos.cloneRepo(repoToClone, { org, dir: dir || undefined });
|
|
521
621
|
} else if (action === 'info') {
|
|
522
|
-
const
|
|
523
|
-
{ type: 'input', name: '
|
|
524
|
-
{ type: 'input', name: 'org', message: dim('Org:'), default: getOwner() },
|
|
622
|
+
const { org } = await ask([
|
|
623
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
525
624
|
]);
|
|
526
|
-
|
|
625
|
+
|
|
626
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
627
|
+
let repoToInfo: string;
|
|
628
|
+
|
|
629
|
+
if (fetchedRepos.length > 0) {
|
|
630
|
+
const { selection } = await inquirer.prompt([{
|
|
631
|
+
type: 'list',
|
|
632
|
+
name: 'selection',
|
|
633
|
+
message: cyan('📦 Select Repo for Info:'),
|
|
634
|
+
choices: [
|
|
635
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
636
|
+
...fetchedRepos.map((r: any) => ({ name: r.name, value: r.name }))
|
|
637
|
+
]
|
|
638
|
+
}]);
|
|
639
|
+
if (selection === 'back') return;
|
|
640
|
+
repoToInfo = selection;
|
|
641
|
+
} else {
|
|
642
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: cyan('📦 Repo name:') }]);
|
|
643
|
+
repoToInfo = name;
|
|
644
|
+
}
|
|
645
|
+
await repos.repoInfo(repoToInfo, { org });
|
|
527
646
|
} else if (action === 'topics') {
|
|
528
647
|
const a = await ask([
|
|
529
648
|
{ type: 'input', name: 'name', message: cyan('Repo name:'), default: getRepo() },
|
|
@@ -533,12 +652,12 @@ async function repoMenu() {
|
|
|
533
652
|
}
|
|
534
653
|
}
|
|
535
654
|
|
|
536
|
-
// ── Contributor Menu
|
|
537
|
-
async function
|
|
655
|
+
// ── Contributor Scoring Menu (Maintainer Tool) ─────────────────────────
|
|
656
|
+
async function contributorScoringMenu() {
|
|
538
657
|
const { action } = await inquirer.prompt([{
|
|
539
658
|
type: 'list', name: 'action', message: yellow('🏆 Contributor Operation:'),
|
|
540
659
|
choices: [
|
|
541
|
-
{ name: ` ${dim('⬅ Back
|
|
660
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
542
661
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
543
662
|
{ name: ` ${yellow('▸')} Score a user`, value: 'score' },
|
|
544
663
|
{ name: ` ${yellow('▸')} Rank applicants for issue`, value: 'rank' },
|
|
@@ -554,7 +673,7 @@ async function contributorMenu() {
|
|
|
554
673
|
} else if (action === 'rank') {
|
|
555
674
|
const { n } = await ask([{ type: 'input', name: 'n', message: yellow('Issue #') + dim(' (q=back):') }]);
|
|
556
675
|
await contributors.rankApplicants(parseInt(n));
|
|
557
|
-
} else {
|
|
676
|
+
} else if (action === 'list') {
|
|
558
677
|
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results (q=back):'), default: '50' }]);
|
|
559
678
|
await contributors.listContributors({ limit: parseInt(a.limit) });
|
|
560
679
|
}
|
|
@@ -565,26 +684,27 @@ async function releaseMenu() {
|
|
|
565
684
|
const { action } = await inquirer.prompt([{
|
|
566
685
|
type: 'list', name: 'action', message: red('🚀 Release Operation:'),
|
|
567
686
|
choices: [
|
|
568
|
-
{ name: ` ${dim('⬅ Back
|
|
687
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
569
688
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
570
|
-
{ name: ` ${
|
|
571
|
-
{ name: ` ${
|
|
689
|
+
{ name: ` ${green('▸')} List releases`, value: 'list' },
|
|
690
|
+
{ name: ` ${green('▸')} Create release`, value: 'create' },
|
|
572
691
|
],
|
|
573
692
|
}]);
|
|
574
693
|
|
|
575
694
|
if (action === 'back') return;
|
|
576
695
|
|
|
577
|
-
if (action === '
|
|
696
|
+
if (action === 'list') {
|
|
697
|
+
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results:'), default: '50' }]);
|
|
698
|
+
await releases.listReleases({ limit: parseInt(a.limit) });
|
|
699
|
+
} else if (action === 'create') {
|
|
578
700
|
const a = await ask([
|
|
579
|
-
{ type: 'input', name: 'tag', message: yellow('Tag (e.g
|
|
580
|
-
{ type: 'input', name: 'name', message: dim('Release name:'), default: '' },
|
|
701
|
+
{ type: 'input', name: 'tag', message: yellow('Tag (e.g. v1.0.0):') },
|
|
702
|
+
{ type: 'input', name: 'name', message: dim('Release name (optional):'), default: '' },
|
|
703
|
+
{ type: 'input', name: 'body', message: dim('Release body (optional):'), default: '' },
|
|
581
704
|
{ type: 'confirm', name: 'draft', message: 'Draft?', default: false },
|
|
582
|
-
{ type: 'confirm', name: 'prerelease', message: '
|
|
705
|
+
{ type: 'confirm', name: 'prerelease', message: 'Prerelease?', default: false },
|
|
583
706
|
]);
|
|
584
|
-
await releases.createRelease(a.tag, { name: a.name
|
|
585
|
-
} else {
|
|
586
|
-
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results (q=back):'), default: '50' }]);
|
|
587
|
-
await releases.listReleases({ limit: parseInt(a.limit) });
|
|
707
|
+
await releases.createRelease(a.tag, { name: a.name, body: a.body, draft: a.draft, prerelease: a.prerelease });
|
|
588
708
|
}
|
|
589
709
|
}
|
|
590
710
|
|
|
@@ -606,52 +726,71 @@ function setupCommander(): Command {
|
|
|
606
726
|
// Issues
|
|
607
727
|
const i = program.command('issues').description('📋 Manage issues');
|
|
608
728
|
i.command('list').option('-s, --state <s>', '', 'open').option('-l, --labels <l>').option('-n, --limit <n>', '', '50')
|
|
609
|
-
.action((o) => issues.listIssues({ state: o.state, labels: o.labels, limit: parseInt(o.limit) }));
|
|
729
|
+
.action(async (o) => { await issues.listIssues({ state: o.state, labels: o.labels, limit: parseInt(o.limit) }); });
|
|
610
730
|
i.command('create').requiredOption('-t, --title <t>').option('-b, --body <b>').option('-l, --labels <l>')
|
|
611
|
-
.action((o) => issues.createIssue(o));
|
|
731
|
+
.action(async (o) => { await issues.createIssue(o); });
|
|
612
732
|
i.command('bulk').requiredOption('-f, --file <f>').option('-d, --dry-run').option('--start <n>', '', '1').option('--end <n>', '', '999')
|
|
613
|
-
.action((o) => issues.createIssuesFromFile(o.file, { dryRun: o.dryRun, start: parseInt(o.start), end: parseInt(o.end) }));
|
|
614
|
-
i.command('close <n>').action((n) => issues.closeIssue(parseInt(n)));
|
|
615
|
-
i.command('reopen <n>').action((n) => issues.reopenIssue(parseInt(n)));
|
|
616
|
-
i.command('delete <n>').action((n) => issues.deleteIssue(parseInt(n)));
|
|
617
|
-
i.command('assign <n> <users...>').action((n, u) => issues.assignIssue(parseInt(n), u));
|
|
618
|
-
i.command('assign-best <n>').action((n) => issues.assignBest(parseInt(n)));
|
|
619
|
-
i.command('search <q>').action((q) => issues.searchIssues(q));
|
|
620
|
-
i.command('label <n> <labels...>').action((n, l) => issues.labelIssue(parseInt(n), l));
|
|
733
|
+
.action(async (o) => { await issues.createIssuesFromFile(o.file, { dryRun: o.dryRun, start: parseInt(o.start), end: parseInt(o.end) }); });
|
|
734
|
+
i.command('close <n>').action(async (n) => { await issues.closeIssue(parseInt(n)); });
|
|
735
|
+
i.command('reopen <n>').action(async (n) => { await issues.reopenIssue(parseInt(n)); });
|
|
736
|
+
i.command('delete <n>').action(async (n) => { await issues.deleteIssue(parseInt(n)); });
|
|
737
|
+
i.command('assign <n> <users...>').action(async (n, u) => { await issues.assignIssue(parseInt(n), u); });
|
|
738
|
+
i.command('assign-best <n>').action(async (n) => { await issues.assignBest(parseInt(n)); });
|
|
739
|
+
i.command('search <q>').action(async (q) => { await issues.searchIssues(q); });
|
|
740
|
+
i.command('label <n> <labels...>').action(async (n, l) => { await issues.labelIssue(parseInt(n), l); });
|
|
621
741
|
|
|
622
742
|
// PRs
|
|
623
743
|
const p = program.command('prs').description('🔀 Manage pull requests');
|
|
624
744
|
p.command('list').option('-s, --state <s>', '', 'open').option('-n, --limit <n>', '', '50')
|
|
625
|
-
.action((o) => prs.listPRs({ state: o.state, limit: parseInt(o.limit) }));
|
|
745
|
+
.action(async (o) => { await prs.listPRs({ state: o.state, limit: parseInt(o.limit) }); });
|
|
626
746
|
p.command('merge <n>').option('-m, --method <m>', '', 'squash').option('--message <msg>').option('-f, --force', 'Skip CI checks')
|
|
627
|
-
.action((n, o) => prs.mergePR(parseInt(n), o));
|
|
628
|
-
p.command('close <n>').action((n) => prs.closePR(parseInt(n)));
|
|
629
|
-
p.command('review <n>').action((n) => prs.reviewPR(parseInt(n)));
|
|
630
|
-
p.command('approve <n>').action((n) => prs.approvePR(parseInt(n)));
|
|
631
|
-
p.command('diff <n>').action((n) => prs.diffPR(parseInt(n)));
|
|
747
|
+
.action(async (n, o) => { await prs.mergePR(parseInt(n), o); });
|
|
748
|
+
p.command('close <n>').action(async (n) => { await prs.closePR(parseInt(n)); });
|
|
749
|
+
p.command('review <n>').action(async (n) => { await prs.reviewPR(parseInt(n)); });
|
|
750
|
+
p.command('approve <n>').action(async (n) => { await prs.approvePR(parseInt(n)); });
|
|
751
|
+
p.command('diff <n>').action(async (n) => { await prs.diffPR(parseInt(n)); });
|
|
632
752
|
|
|
633
753
|
// Repos
|
|
634
754
|
const r = program.command('repo').description('📦 Manage repositories');
|
|
635
755
|
r.command('list').option('-o, --org <o>').option('-n, --limit <n>', '', '50')
|
|
636
|
-
.action((o) => repos.listRepos({ org: o.org, limit: parseInt(o.limit) }));
|
|
756
|
+
.action(async (o) => { await repos.listRepos({ org: o.org, limit: parseInt(o.limit) }); });
|
|
637
757
|
r.command('create <name>').option('-o, --org <o>').option('-p, --private').option('-d, --description <d>')
|
|
638
|
-
.action((n, o) => repos.createRepo(n, o));
|
|
639
|
-
r.command('delete <name>').option('-o, --org <o>').action((n, o) => repos.deleteRepo(n, o));
|
|
640
|
-
r.command('clone <name>').option('-o, --org <o>').option('-d, --dir <d>').action((n, o) => repos.cloneRepo(n, o));
|
|
641
|
-
r.command('info <name>').option('-o, --org <o>').action((n, o) => repos.repoInfo(n, o));
|
|
642
|
-
r.command('topics <name> <topics...>').option('-o, --org <o>').action((n, t, o) => repos.setTopics(n, t, o));
|
|
758
|
+
.action(async (n, o) => { await repos.createRepo(n, o); });
|
|
759
|
+
r.command('delete <name>').option('-o, --org <o>').action(async (n, o) => { await repos.deleteRepo(n, o); });
|
|
760
|
+
r.command('clone <name>').option('-o, --org <o>').option('-d, --dir <d>').action(async (n, o) => { await repos.cloneRepo(n, o); });
|
|
761
|
+
r.command('info <name>').option('-o, --org <o>').action(async (n, o) => { await repos.repoInfo(n, o); });
|
|
762
|
+
r.command('topics <name> <topics...>').option('-o, --org <o>').action(async (n, t, o) => { await repos.setTopics(n, t, o); });
|
|
643
763
|
|
|
644
764
|
// Contributors
|
|
645
765
|
const c = program.command('contributors').description('🏆 Manage contributors');
|
|
646
|
-
c.command('score <user>').action((u) => contributors.scoreUser(u));
|
|
647
|
-
c.command('rank <issue>').action((n) => contributors.rankApplicants(parseInt(n)));
|
|
648
|
-
c.command('list').option('-n, --limit <n>', '', '50').action((o) => contributors.listContributors({ limit: parseInt(o.limit) }));
|
|
766
|
+
c.command('score <user>').action(async (u) => { await contributors.scoreUser(u); });
|
|
767
|
+
c.command('rank <issue>').action(async (n) => { await contributors.rankApplicants(parseInt(n)); });
|
|
768
|
+
c.command('list').option('-n, --limit <n>', '', '50').action(async (o) => { await contributors.listContributors({ limit: parseInt(o.limit) }); });
|
|
649
769
|
|
|
650
770
|
// Releases
|
|
651
771
|
const rel = program.command('release').description('🚀 Manage releases');
|
|
652
772
|
rel.command('create <tag>').option('-n, --name <n>').option('-b, --body <b>').option('-d, --draft').option('-p, --prerelease').option('--no-generate')
|
|
653
|
-
.action((t, o) => releases.createRelease(t, o));
|
|
654
|
-
rel.command('list').option('-n, --limit <n>', '', '50').action((o) => releases.listReleases({ limit: parseInt(o.limit) }));
|
|
773
|
+
.action(async (t, o) => { await releases.createRelease(t, o); });
|
|
774
|
+
rel.command('list').option('-n, --limit <n>', '', '50').action(async (o) => { await releases.listReleases({ limit: parseInt(o.limit) }); });
|
|
775
|
+
|
|
776
|
+
// Contributor Commands
|
|
777
|
+
// ILLUSTRATION: How to start?
|
|
778
|
+
// Option A: `gitpadi start https://github.com/owner/repo`
|
|
779
|
+
// Option B: Interactive Menu -> Contributor Mode -> Start Contribution
|
|
780
|
+
program.command('start <url>').description('🚀 Start contribution (fork, clone & branch)').action(async (u) => { await contribute.forkAndClone(u); });
|
|
781
|
+
program.command('sync').description('🔄 Sync with upstream').action(async () => { await contribute.syncBranch(); });
|
|
782
|
+
program.command('submit').description('🚀 Submit PR (add, commit, push & PR)')
|
|
783
|
+
.option('-t, --title <t>', 'PR title')
|
|
784
|
+
.option('-m, --message <m>', 'Commit message')
|
|
785
|
+
.option('-i, --issue <n>', 'Issue number')
|
|
786
|
+
.action(async (o) => {
|
|
787
|
+
await contribute.submitPR({
|
|
788
|
+
title: o.title || 'Automated PR',
|
|
789
|
+
message: o.message || o.title,
|
|
790
|
+
issue: o.issue ? parseInt(o.issue) : undefined
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
program.command('logs').description('📋 View Action logs').action(async () => { await contribute.viewLogs(); });
|
|
655
794
|
|
|
656
795
|
return program;
|
|
657
796
|
}
|
|
@@ -660,7 +799,7 @@ function setupCommander(): Command {
|
|
|
660
799
|
async function main() {
|
|
661
800
|
if (process.argv.length <= 2) {
|
|
662
801
|
await bootSequence();
|
|
663
|
-
await
|
|
802
|
+
await ensureAuthenticated();
|
|
664
803
|
await mainMenu();
|
|
665
804
|
} else {
|
|
666
805
|
const program = setupCommander();
|