gitpadi 2.0.1 → 2.0.3
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 +57 -225
- package/dist/cli.js +279 -146
- package/dist/commands/contribute.js +314 -0
- package/dist/commands/issues.js +107 -24
- package/dist/commands/repos.js +31 -21
- package/dist/core/github.js +47 -2
- package/package.json +2 -2
- package/src/cli.ts +312 -174
- package/src/commands/contribute.ts +354 -0
- package/src/commands/issues.ts +118 -26
- 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,12 @@ 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
|
|
327
|
-
{ type: 'confirm', name: 'dryRun', message: 'Dry run first?', default: true },
|
|
395
|
+
{ type: 'input', name: 'file', message: yellow('📁 Path to issues file (JSON or MD)') + dim(' (q=back):') },
|
|
328
396
|
{ type: 'number', name: 'start', message: dim('Start index:'), default: 1 },
|
|
329
397
|
{ type: 'number', name: 'end', message: dim('End index:'), default: 999 },
|
|
330
398
|
]);
|
|
331
|
-
await issues.createIssuesFromFile(a.file, {
|
|
399
|
+
await issues.createIssuesFromFile(a.file, { start: a.start, end: a.end });
|
|
332
400
|
} 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
401
|
const spinner = createSpinner(dim('Finding issues with applicants...')).start();
|
|
340
402
|
const octokit = getOctokit();
|
|
341
403
|
|
|
@@ -343,10 +405,7 @@ async function issueMenu() {
|
|
|
343
405
|
owner: getOwner(), repo: getRepo(), state: 'open', per_page: 50,
|
|
344
406
|
});
|
|
345
407
|
|
|
346
|
-
|
|
347
|
-
const realIssues = allIssues.filter((i) => !i.pull_request && i.comments > 0);
|
|
348
|
-
|
|
349
|
-
// Check each issue for applicant comments
|
|
408
|
+
const realIssues = allIssues.filter((i: any) => !i.pull_request && i.comments > 0);
|
|
350
409
|
const issuesWithApplicants: Array<{ number: number; title: string; applicants: string[]; labels: string[] }> = [];
|
|
351
410
|
|
|
352
411
|
for (const issue of realIssues) {
|
|
@@ -355,7 +414,7 @@ async function issueMenu() {
|
|
|
355
414
|
});
|
|
356
415
|
|
|
357
416
|
const applicantUsers = new Set<string>();
|
|
358
|
-
comments.forEach((c) => {
|
|
417
|
+
comments.forEach((c: any) => {
|
|
359
418
|
if (c.user?.login && c.user.login !== 'github-actions[bot]') {
|
|
360
419
|
applicantUsers.add(c.user.login);
|
|
361
420
|
}
|
|
@@ -366,7 +425,7 @@ async function issueMenu() {
|
|
|
366
425
|
number: issue.number,
|
|
367
426
|
title: issue.title.substring(0, 45),
|
|
368
427
|
applicants: Array.from(applicantUsers),
|
|
369
|
-
labels: issue.labels.map((l) => typeof l === 'string' ? l : l.name || '').filter(Boolean),
|
|
428
|
+
labels: issue.labels.map((l: any) => typeof l === 'string' ? l : l.name || '').filter(Boolean),
|
|
370
429
|
});
|
|
371
430
|
}
|
|
372
431
|
}
|
|
@@ -380,7 +439,6 @@ async function issueMenu() {
|
|
|
380
439
|
|
|
381
440
|
console.log(`\n${bold(`🏆 Issues with Applicants`)} (${issuesWithApplicants.length})\n`);
|
|
382
441
|
|
|
383
|
-
// Let user pick an issue
|
|
384
442
|
const { picked } = await inquirer.prompt([{
|
|
385
443
|
type: 'list', name: 'picked', message: yellow('Select an issue:'),
|
|
386
444
|
choices: [
|
|
@@ -393,8 +451,6 @@ async function issueMenu() {
|
|
|
393
451
|
}]);
|
|
394
452
|
|
|
395
453
|
if (picked === -1) return;
|
|
396
|
-
|
|
397
|
-
// Run the scoring
|
|
398
454
|
await issues.assignBest(picked);
|
|
399
455
|
|
|
400
456
|
} else if (action === 'close' || action === 'reopen' || action === 'delete') {
|
|
@@ -429,7 +485,7 @@ async function prMenu() {
|
|
|
429
485
|
const { action } = await inquirer.prompt([{
|
|
430
486
|
type: 'list', name: 'action', message: cyan('🔀 PR Operation:'),
|
|
431
487
|
choices: [
|
|
432
|
-
{ name: ` ${dim('⬅ Back
|
|
488
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
433
489
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
434
490
|
{ name: ` ${green('▸')} List pull requests`, value: 'list' },
|
|
435
491
|
{ name: ` ${green('▸')} Merge PR ${dim('(checks CI first)')}`, value: 'merge' },
|
|
@@ -476,7 +532,7 @@ async function repoMenu() {
|
|
|
476
532
|
const { action } = await inquirer.prompt([{
|
|
477
533
|
type: 'list', name: 'action', message: green('📦 Repo Operation:'),
|
|
478
534
|
choices: [
|
|
479
|
-
{ name: ` ${dim('⬅ Back
|
|
535
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
480
536
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
481
537
|
{ name: ` ${green('▸')} List repositories`, value: 'list' },
|
|
482
538
|
{ name: ` ${green('▸')} Create repo`, value: 'create' },
|
|
@@ -504,26 +560,88 @@ async function repoMenu() {
|
|
|
504
560
|
]);
|
|
505
561
|
await repos.createRepo(a.name, { org: a.org || undefined, description: a.description, private: a.isPrivate });
|
|
506
562
|
} else if (action === 'delete') {
|
|
507
|
-
const
|
|
508
|
-
{ type: 'input', name: '
|
|
509
|
-
{ type: 'input', name: 'org', message: 'Org:', default: getOwner() },
|
|
563
|
+
const { org } = await ask([
|
|
564
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
510
565
|
]);
|
|
566
|
+
|
|
567
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
568
|
+
let repoToDelete: string;
|
|
569
|
+
|
|
570
|
+
if (fetchedRepos.length > 0) {
|
|
571
|
+
const { selection } = await inquirer.prompt([{
|
|
572
|
+
type: 'list',
|
|
573
|
+
name: 'selection',
|
|
574
|
+
message: red('📦 Select Repo to DELETE:'),
|
|
575
|
+
choices: [
|
|
576
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
577
|
+
...fetchedRepos.map((r: any) => ({ name: r.name, value: r.name }))
|
|
578
|
+
]
|
|
579
|
+
}]);
|
|
580
|
+
if (selection === 'back') return;
|
|
581
|
+
repoToDelete = selection;
|
|
582
|
+
} else {
|
|
583
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: red('📦 Repo name to DELETE:') }]);
|
|
584
|
+
repoToDelete = name;
|
|
585
|
+
}
|
|
586
|
+
|
|
511
587
|
const { confirm } = await ask([
|
|
512
|
-
{ type: 'input', name: 'confirm', message: red(`⚠️ Type "${
|
|
588
|
+
{ type: 'input', name: 'confirm', message: red(`⚠️ Type "${repoToDelete}" to confirm deletion:`), validate: (v: string) => v === repoToDelete || 'Name doesn\'t match' },
|
|
513
589
|
]);
|
|
514
|
-
if (confirm ===
|
|
590
|
+
if (confirm === repoToDelete) await repos.deleteRepo(repoToDelete, { org });
|
|
515
591
|
} else if (action === 'clone') {
|
|
516
|
-
const
|
|
517
|
-
{ type: 'input', name: '
|
|
518
|
-
{ type: 'input', name: 'org', message: dim('Org:'), default: getOwner() },
|
|
592
|
+
const { org } = await ask([
|
|
593
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
519
594
|
]);
|
|
520
|
-
|
|
595
|
+
|
|
596
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
597
|
+
let repoToClone: string;
|
|
598
|
+
|
|
599
|
+
if (fetchedRepos.length > 0) {
|
|
600
|
+
const { selection } = await inquirer.prompt([{
|
|
601
|
+
type: 'list',
|
|
602
|
+
name: 'selection',
|
|
603
|
+
message: cyan('📦 Select Repo to Clone:'),
|
|
604
|
+
choices: [
|
|
605
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
606
|
+
...fetchedRepos.map((r: any) => ({ name: r.name, value: r.name }))
|
|
607
|
+
]
|
|
608
|
+
}]);
|
|
609
|
+
if (selection === 'back') return;
|
|
610
|
+
repoToClone = selection;
|
|
611
|
+
} else {
|
|
612
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: cyan('📦 Repo name to Clone:') }]);
|
|
613
|
+
repoToClone = name;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const { dir } = await ask([
|
|
617
|
+
{ type: 'input', name: 'dir', message: dim('Destination path (leave blank for default):'), default: '' },
|
|
618
|
+
]);
|
|
619
|
+
await repos.cloneRepo(repoToClone, { org, dir: dir || undefined });
|
|
521
620
|
} else if (action === 'info') {
|
|
522
|
-
const
|
|
523
|
-
{ type: 'input', name: '
|
|
524
|
-
{ type: 'input', name: 'org', message: dim('Org:'), default: getOwner() },
|
|
621
|
+
const { org } = await ask([
|
|
622
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
525
623
|
]);
|
|
526
|
-
|
|
624
|
+
|
|
625
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
626
|
+
let repoToInfo: string;
|
|
627
|
+
|
|
628
|
+
if (fetchedRepos.length > 0) {
|
|
629
|
+
const { selection } = await inquirer.prompt([{
|
|
630
|
+
type: 'list',
|
|
631
|
+
name: 'selection',
|
|
632
|
+
message: cyan('📦 Select Repo for Info:'),
|
|
633
|
+
choices: [
|
|
634
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
635
|
+
...fetchedRepos.map((r: any) => ({ name: r.name, value: r.name }))
|
|
636
|
+
]
|
|
637
|
+
}]);
|
|
638
|
+
if (selection === 'back') return;
|
|
639
|
+
repoToInfo = selection;
|
|
640
|
+
} else {
|
|
641
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: cyan('📦 Repo name:') }]);
|
|
642
|
+
repoToInfo = name;
|
|
643
|
+
}
|
|
644
|
+
await repos.repoInfo(repoToInfo, { org });
|
|
527
645
|
} else if (action === 'topics') {
|
|
528
646
|
const a = await ask([
|
|
529
647
|
{ type: 'input', name: 'name', message: cyan('Repo name:'), default: getRepo() },
|
|
@@ -533,12 +651,12 @@ async function repoMenu() {
|
|
|
533
651
|
}
|
|
534
652
|
}
|
|
535
653
|
|
|
536
|
-
// ── Contributor Menu
|
|
537
|
-
async function
|
|
654
|
+
// ── Contributor Scoring Menu (Maintainer Tool) ─────────────────────────
|
|
655
|
+
async function contributorScoringMenu() {
|
|
538
656
|
const { action } = await inquirer.prompt([{
|
|
539
657
|
type: 'list', name: 'action', message: yellow('🏆 Contributor Operation:'),
|
|
540
658
|
choices: [
|
|
541
|
-
{ name: ` ${dim('⬅ Back
|
|
659
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
542
660
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
543
661
|
{ name: ` ${yellow('▸')} Score a user`, value: 'score' },
|
|
544
662
|
{ name: ` ${yellow('▸')} Rank applicants for issue`, value: 'rank' },
|
|
@@ -554,7 +672,7 @@ async function contributorMenu() {
|
|
|
554
672
|
} else if (action === 'rank') {
|
|
555
673
|
const { n } = await ask([{ type: 'input', name: 'n', message: yellow('Issue #') + dim(' (q=back):') }]);
|
|
556
674
|
await contributors.rankApplicants(parseInt(n));
|
|
557
|
-
} else {
|
|
675
|
+
} else if (action === 'list') {
|
|
558
676
|
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results (q=back):'), default: '50' }]);
|
|
559
677
|
await contributors.listContributors({ limit: parseInt(a.limit) });
|
|
560
678
|
}
|
|
@@ -565,26 +683,27 @@ async function releaseMenu() {
|
|
|
565
683
|
const { action } = await inquirer.prompt([{
|
|
566
684
|
type: 'list', name: 'action', message: red('🚀 Release Operation:'),
|
|
567
685
|
choices: [
|
|
568
|
-
{ name: ` ${dim('⬅ Back
|
|
686
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
569
687
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
570
|
-
{ name: ` ${
|
|
571
|
-
{ name: ` ${
|
|
688
|
+
{ name: ` ${green('▸')} List releases`, value: 'list' },
|
|
689
|
+
{ name: ` ${green('▸')} Create release`, value: 'create' },
|
|
572
690
|
],
|
|
573
691
|
}]);
|
|
574
692
|
|
|
575
693
|
if (action === 'back') return;
|
|
576
694
|
|
|
577
|
-
if (action === '
|
|
695
|
+
if (action === 'list') {
|
|
696
|
+
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results:'), default: '50' }]);
|
|
697
|
+
await releases.listReleases({ limit: parseInt(a.limit) });
|
|
698
|
+
} else if (action === 'create') {
|
|
578
699
|
const a = await ask([
|
|
579
|
-
{ type: 'input', name: 'tag', message: yellow('Tag (e.g
|
|
580
|
-
{ type: 'input', name: 'name', message: dim('Release name:'), default: '' },
|
|
700
|
+
{ type: 'input', name: 'tag', message: yellow('Tag (e.g. v1.0.0):') },
|
|
701
|
+
{ type: 'input', name: 'name', message: dim('Release name (optional):'), default: '' },
|
|
702
|
+
{ type: 'input', name: 'body', message: dim('Release body (optional):'), default: '' },
|
|
581
703
|
{ type: 'confirm', name: 'draft', message: 'Draft?', default: false },
|
|
582
|
-
{ type: 'confirm', name: 'prerelease', message: '
|
|
704
|
+
{ type: 'confirm', name: 'prerelease', message: 'Prerelease?', default: false },
|
|
583
705
|
]);
|
|
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) });
|
|
706
|
+
await releases.createRelease(a.tag, { name: a.name, body: a.body, draft: a.draft, prerelease: a.prerelease });
|
|
588
707
|
}
|
|
589
708
|
}
|
|
590
709
|
|
|
@@ -606,52 +725,71 @@ function setupCommander(): Command {
|
|
|
606
725
|
// Issues
|
|
607
726
|
const i = program.command('issues').description('📋 Manage issues');
|
|
608
727
|
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) }));
|
|
728
|
+
.action(async (o) => { await issues.listIssues({ state: o.state, labels: o.labels, limit: parseInt(o.limit) }); });
|
|
610
729
|
i.command('create').requiredOption('-t, --title <t>').option('-b, --body <b>').option('-l, --labels <l>')
|
|
611
|
-
.action((o) => issues.createIssue(o));
|
|
730
|
+
.action(async (o) => { await issues.createIssue(o); });
|
|
612
731
|
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));
|
|
732
|
+
.action(async (o) => { await issues.createIssuesFromFile(o.file, { dryRun: o.dryRun, start: parseInt(o.start), end: parseInt(o.end) }); });
|
|
733
|
+
i.command('close <n>').action(async (n) => { await issues.closeIssue(parseInt(n)); });
|
|
734
|
+
i.command('reopen <n>').action(async (n) => { await issues.reopenIssue(parseInt(n)); });
|
|
735
|
+
i.command('delete <n>').action(async (n) => { await issues.deleteIssue(parseInt(n)); });
|
|
736
|
+
i.command('assign <n> <users...>').action(async (n, u) => { await issues.assignIssue(parseInt(n), u); });
|
|
737
|
+
i.command('assign-best <n>').action(async (n) => { await issues.assignBest(parseInt(n)); });
|
|
738
|
+
i.command('search <q>').action(async (q) => { await issues.searchIssues(q); });
|
|
739
|
+
i.command('label <n> <labels...>').action(async (n, l) => { await issues.labelIssue(parseInt(n), l); });
|
|
621
740
|
|
|
622
741
|
// PRs
|
|
623
742
|
const p = program.command('prs').description('🔀 Manage pull requests');
|
|
624
743
|
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) }));
|
|
744
|
+
.action(async (o) => { await prs.listPRs({ state: o.state, limit: parseInt(o.limit) }); });
|
|
626
745
|
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)));
|
|
746
|
+
.action(async (n, o) => { await prs.mergePR(parseInt(n), o); });
|
|
747
|
+
p.command('close <n>').action(async (n) => { await prs.closePR(parseInt(n)); });
|
|
748
|
+
p.command('review <n>').action(async (n) => { await prs.reviewPR(parseInt(n)); });
|
|
749
|
+
p.command('approve <n>').action(async (n) => { await prs.approvePR(parseInt(n)); });
|
|
750
|
+
p.command('diff <n>').action(async (n) => { await prs.diffPR(parseInt(n)); });
|
|
632
751
|
|
|
633
752
|
// Repos
|
|
634
753
|
const r = program.command('repo').description('📦 Manage repositories');
|
|
635
754
|
r.command('list').option('-o, --org <o>').option('-n, --limit <n>', '', '50')
|
|
636
|
-
.action((o) => repos.listRepos({ org: o.org, limit: parseInt(o.limit) }));
|
|
755
|
+
.action(async (o) => { await repos.listRepos({ org: o.org, limit: parseInt(o.limit) }); });
|
|
637
756
|
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));
|
|
757
|
+
.action(async (n, o) => { await repos.createRepo(n, o); });
|
|
758
|
+
r.command('delete <name>').option('-o, --org <o>').action(async (n, o) => { await repos.deleteRepo(n, o); });
|
|
759
|
+
r.command('clone <name>').option('-o, --org <o>').option('-d, --dir <d>').action(async (n, o) => { await repos.cloneRepo(n, o); });
|
|
760
|
+
r.command('info <name>').option('-o, --org <o>').action(async (n, o) => { await repos.repoInfo(n, o); });
|
|
761
|
+
r.command('topics <name> <topics...>').option('-o, --org <o>').action(async (n, t, o) => { await repos.setTopics(n, t, o); });
|
|
643
762
|
|
|
644
763
|
// Contributors
|
|
645
764
|
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) }));
|
|
765
|
+
c.command('score <user>').action(async (u) => { await contributors.scoreUser(u); });
|
|
766
|
+
c.command('rank <issue>').action(async (n) => { await contributors.rankApplicants(parseInt(n)); });
|
|
767
|
+
c.command('list').option('-n, --limit <n>', '', '50').action(async (o) => { await contributors.listContributors({ limit: parseInt(o.limit) }); });
|
|
649
768
|
|
|
650
769
|
// Releases
|
|
651
770
|
const rel = program.command('release').description('🚀 Manage releases');
|
|
652
771
|
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) }));
|
|
772
|
+
.action(async (t, o) => { await releases.createRelease(t, o); });
|
|
773
|
+
rel.command('list').option('-n, --limit <n>', '', '50').action(async (o) => { await releases.listReleases({ limit: parseInt(o.limit) }); });
|
|
774
|
+
|
|
775
|
+
// Contributor Commands
|
|
776
|
+
// ILLUSTRATION: How to start?
|
|
777
|
+
// Option A: `gitpadi start https://github.com/owner/repo`
|
|
778
|
+
// Option B: Interactive Menu -> Contributor Mode -> Start Contribution
|
|
779
|
+
program.command('start <url>').description('🚀 Start contribution (fork, clone & branch)').action(async (u) => { await contribute.forkAndClone(u); });
|
|
780
|
+
program.command('sync').description('🔄 Sync with upstream').action(async () => { await contribute.syncBranch(); });
|
|
781
|
+
program.command('submit').description('🚀 Submit PR (add, commit, push & PR)')
|
|
782
|
+
.option('-t, --title <t>', 'PR title')
|
|
783
|
+
.option('-m, --message <m>', 'Commit message')
|
|
784
|
+
.option('-i, --issue <n>', 'Issue number')
|
|
785
|
+
.action(async (o) => {
|
|
786
|
+
await contribute.submitPR({
|
|
787
|
+
title: o.title || 'Automated PR',
|
|
788
|
+
message: o.message || o.title,
|
|
789
|
+
issue: o.issue ? parseInt(o.issue) : undefined
|
|
790
|
+
});
|
|
791
|
+
});
|
|
792
|
+
program.command('logs').description('📋 View Action logs').action(async () => { await contribute.viewLogs(); });
|
|
655
793
|
|
|
656
794
|
return program;
|
|
657
795
|
}
|
|
@@ -660,7 +798,7 @@ function setupCommander(): Command {
|
|
|
660
798
|
async function main() {
|
|
661
799
|
if (process.argv.length <= 2) {
|
|
662
800
|
await bootSequence();
|
|
663
|
-
await
|
|
801
|
+
await ensureAuthenticated();
|
|
664
802
|
await mainMenu();
|
|
665
803
|
} else {
|
|
666
804
|
const program = setupCommander();
|