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/dist/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import * as prs from './commands/prs.js';
|
|
|
17
17
|
import * as repos from './commands/repos.js';
|
|
18
18
|
import * as contributors from './commands/contributors.js';
|
|
19
19
|
import * as releases from './commands/releases.js';
|
|
20
|
+
import * as contribute from './commands/contribute.js';
|
|
20
21
|
const VERSION = '2.0.0';
|
|
21
22
|
// ── Styling ────────────────────────────────────────────────────────────
|
|
22
23
|
const cyber = gradient(['#ff00ff', '#00ffff', '#ff00ff']);
|
|
@@ -108,33 +109,28 @@ async function bootSequence() {
|
|
|
108
109
|
console.log('');
|
|
109
110
|
}
|
|
110
111
|
// ── Onboarding ─────────────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Ensures we have a valid GitHub token
|
|
114
|
+
*/
|
|
115
|
+
async function ensureAuthenticated() {
|
|
113
116
|
initGitHub();
|
|
114
117
|
let token = getToken();
|
|
115
|
-
|
|
116
|
-
let repo = getRepo();
|
|
117
|
-
// If everything is already set (likely via env or previously saved config), skip
|
|
118
|
-
if (token && owner && repo) {
|
|
119
|
-
const spinner = createSpinner(dim('Authenticating...')).start();
|
|
118
|
+
if (token) {
|
|
120
119
|
try {
|
|
121
120
|
const octokit = getOctokit();
|
|
122
121
|
await octokit.users.getAuthenticated();
|
|
123
|
-
|
|
124
|
-
console.log('');
|
|
122
|
+
// Silent success if token is valid
|
|
125
123
|
return;
|
|
126
124
|
}
|
|
127
125
|
catch {
|
|
128
|
-
|
|
129
|
-
// Clear and continue to prompt
|
|
126
|
+
console.log(red(' ❌ Saved session invalid. Re-authenticating...'));
|
|
130
127
|
token = '';
|
|
131
128
|
}
|
|
132
129
|
}
|
|
133
|
-
console.log(neon(' ⚡
|
|
134
|
-
const
|
|
135
|
-
{
|
|
130
|
+
console.log(neon(' ⚡ Authentication — let\'s connect you to GitHub.\n'));
|
|
131
|
+
const { t } = await inquirer.prompt([{
|
|
136
132
|
type: 'password',
|
|
137
|
-
name: '
|
|
133
|
+
name: 't',
|
|
138
134
|
message: magenta('🔑 GitHub Token') + dim(' (ghp_xxx):'),
|
|
139
135
|
mask: '•',
|
|
140
136
|
validate: async (v) => {
|
|
@@ -153,15 +149,27 @@ async function onboarding() {
|
|
|
153
149
|
return 'Invalid token';
|
|
154
150
|
}
|
|
155
151
|
},
|
|
156
|
-
|
|
157
|
-
|
|
152
|
+
}]);
|
|
153
|
+
initGitHub(t);
|
|
154
|
+
saveConfig({ token: t, owner: getOwner() || '', repo: getRepo() || '' });
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Ensures we have a target repository (Owner/Repo)
|
|
158
|
+
*/
|
|
159
|
+
async function ensureTargetRepo() {
|
|
160
|
+
let owner = getOwner();
|
|
161
|
+
let repo = getRepo();
|
|
162
|
+
if (owner && repo) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
console.log(neon('\n 📦 Project Targeting — which repo are we working on?\n'));
|
|
166
|
+
const answers = await inquirer.prompt([
|
|
158
167
|
{
|
|
159
168
|
type: 'input',
|
|
160
169
|
name: 'owner',
|
|
161
170
|
message: cyan('👤 GitHub Owner/Org:'),
|
|
162
171
|
default: owner || '',
|
|
163
172
|
validate: (v) => v.length > 0 || 'Required',
|
|
164
|
-
when: !owner,
|
|
165
173
|
},
|
|
166
174
|
{
|
|
167
175
|
type: 'input',
|
|
@@ -169,70 +177,126 @@ async function onboarding() {
|
|
|
169
177
|
message: cyan('📦 Repository name:'),
|
|
170
178
|
default: repo || '',
|
|
171
179
|
validate: (v) => v.length > 0 || 'Required',
|
|
172
|
-
when: !repo,
|
|
173
180
|
},
|
|
174
181
|
]);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
initGitHub(finalToken, finalOwner, finalRepo);
|
|
179
|
-
saveConfig({ token: finalToken, owner: finalOwner, repo: finalRepo });
|
|
180
|
-
const spinner = createSpinner(dim('Connecting to GitHub...')).start();
|
|
182
|
+
setRepo(answers.owner, answers.repo);
|
|
183
|
+
saveConfig({ token: getToken(), owner: answers.owner, repo: answers.repo });
|
|
184
|
+
const spinner = createSpinner(dim('Connecting...')).start();
|
|
181
185
|
await sleep(400);
|
|
182
|
-
spinner.success({ text: green(`Locked in → ${cyan(`${
|
|
183
|
-
console.log('');
|
|
184
|
-
console.log(boxen(dim('💡 Session saved to ~/.gitpadi/config.json\n\n') +
|
|
185
|
-
dim('Set environment variables to override:\n') +
|
|
186
|
-
yellow(' export GITHUB_TOKEN=ghp_xxx\n') +
|
|
187
|
-
yellow(' export GITHUB_OWNER=' + finalOwner + '\n') +
|
|
188
|
-
yellow(' export GITHUB_REPO=' + finalRepo), { padding: 1, borderColor: 'yellow', dimBorder: true, borderStyle: 'round' }));
|
|
186
|
+
spinner.success({ text: green(`Locked in → ${cyan(`${answers.owner}/${answers.repo}`)}`) });
|
|
189
187
|
console.log('');
|
|
190
188
|
}
|
|
191
|
-
// ──
|
|
189
|
+
// ── Mode Selector ──────────────────────────────────────────────────────
|
|
192
190
|
async function mainMenu() {
|
|
193
191
|
while (true) {
|
|
194
192
|
line('═');
|
|
195
|
-
console.log(cyber(' ⟨ GITPADI
|
|
196
|
-
console.log(dim(' Select
|
|
193
|
+
console.log(cyber(' ⟨ GITPADI MODE SELECTOR ⟩'));
|
|
194
|
+
console.log(dim(' Select your workflow persona to continue'));
|
|
197
195
|
line('═');
|
|
198
196
|
console.log('');
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}]);
|
|
217
|
-
category = ans.category;
|
|
197
|
+
const { mode } = await inquirer.prompt([{
|
|
198
|
+
type: 'list',
|
|
199
|
+
name: 'mode',
|
|
200
|
+
message: bold('Choose your path:'),
|
|
201
|
+
choices: [
|
|
202
|
+
{ name: `${cyan('✨')} ${bold('Contributor Mode')} ${dim('— fork, clone, sync, submit PRs')}`, value: 'contributor' },
|
|
203
|
+
{ name: `${magenta('🛠️')} ${bold('Maintainer Mode')} ${dim('— manage issues, PRs, contributors')}`, value: 'maintainer' },
|
|
204
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
205
|
+
{ name: `${dim('👋')} ${dim('Exit')}`, value: 'exit' },
|
|
206
|
+
],
|
|
207
|
+
loop: false,
|
|
208
|
+
}]);
|
|
209
|
+
if (mode === 'contributor')
|
|
210
|
+
await safeMenu(contributorMenu);
|
|
211
|
+
else if (mode === 'maintainer') {
|
|
212
|
+
await ensureTargetRepo();
|
|
213
|
+
await safeMenu(maintainerMenu);
|
|
218
214
|
}
|
|
219
|
-
|
|
220
|
-
// Ctrl+C on main menu = exit
|
|
221
|
-
console.log('');
|
|
222
|
-
console.log(dim(' ▸ Saving session...'));
|
|
223
|
-
console.log(dim(' ▸ Disconnecting from GitHub...'));
|
|
224
|
-
console.log('');
|
|
225
|
-
console.log(fire(' 🤖 GitPadi is shutting down now. See you soon, pal :)\n'));
|
|
215
|
+
else
|
|
226
216
|
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// ── Contributor Menu ───────────────────────────────────────────────────
|
|
220
|
+
async function contributorMenu() {
|
|
221
|
+
while (true) {
|
|
222
|
+
line('═');
|
|
223
|
+
console.log(cyan(' ✨ GITPADI CONTRIBUTOR WORKSPACE'));
|
|
224
|
+
console.log(dim(' Automating forking, syncing, and PR delivery'));
|
|
225
|
+
line('═');
|
|
226
|
+
// Auto-check for updates if in a repo
|
|
227
|
+
if (getOwner() && getRepo()) {
|
|
228
|
+
await contribute.syncBranch();
|
|
227
229
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
const { action } = await inquirer.prompt([{
|
|
231
|
+
type: 'list',
|
|
232
|
+
name: 'action',
|
|
233
|
+
message: bold('Contributor Action:'),
|
|
234
|
+
choices: [
|
|
235
|
+
{ name: `${cyan('🚀')} ${bold('Start Contribution')} ${dim('— fork, clone & branch')}`, value: 'start' },
|
|
236
|
+
{ name: `${green('🔄')} ${bold('Sync with Upstream')} ${dim('— pull latest changes')}`, value: 'sync' },
|
|
237
|
+
{ name: `${yellow('📋')} ${bold('View Action Logs')} ${dim('— check PR/commit status')}`, value: 'logs' },
|
|
238
|
+
{ name: `${magenta('🚀')} ${bold('Submit PR')} ${dim('— add, commit, push & PR')}`, value: 'submit' },
|
|
239
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
240
|
+
{ name: `${dim('⬅')} ${dim('Back to Mode Selector')}`, value: 'back' },
|
|
241
|
+
],
|
|
242
|
+
loop: false,
|
|
243
|
+
}]);
|
|
244
|
+
if (action === 'back')
|
|
234
245
|
break;
|
|
246
|
+
if (action === 'start') {
|
|
247
|
+
const { url } = await ask([{ type: 'input', name: 'url', message: 'Enter Repo or Issue URL:' }]);
|
|
248
|
+
await contribute.forkAndClone(url);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
// These actions require a target repo
|
|
252
|
+
await ensureTargetRepo();
|
|
253
|
+
if (action === 'sync') {
|
|
254
|
+
await contribute.syncBranch();
|
|
255
|
+
}
|
|
256
|
+
else if (action === 'logs') {
|
|
257
|
+
await contribute.viewLogs();
|
|
258
|
+
}
|
|
259
|
+
else if (action === 'submit') {
|
|
260
|
+
const { title, message, issue } = await ask([
|
|
261
|
+
{ type: 'input', name: 'title', message: 'PR Title:' },
|
|
262
|
+
{ type: 'input', name: 'message', message: 'Commit message (optional):' },
|
|
263
|
+
{ type: 'input', name: 'issue', message: 'Related Issue # (optional):' }
|
|
264
|
+
]);
|
|
265
|
+
await contribute.submitPR({
|
|
266
|
+
title,
|
|
267
|
+
message: message || title,
|
|
268
|
+
issue: issue ? parseInt(issue) : undefined
|
|
269
|
+
});
|
|
270
|
+
}
|
|
235
271
|
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// ── Maintainer Menu ────────────────────────────────────────────────────
|
|
275
|
+
async function maintainerMenu() {
|
|
276
|
+
while (true) {
|
|
277
|
+
line('═');
|
|
278
|
+
console.log(magenta(' 🛠️ GITPADI MAINTAINER PANEL'));
|
|
279
|
+
console.log(dim(' Managing repository health and contributor intake'));
|
|
280
|
+
line('═');
|
|
281
|
+
console.log('');
|
|
282
|
+
const { category } = await inquirer.prompt([{
|
|
283
|
+
type: 'list',
|
|
284
|
+
name: 'category',
|
|
285
|
+
message: bold('Select operation:'),
|
|
286
|
+
choices: [
|
|
287
|
+
{ name: `${magenta('📋')} ${bold('Issues')} ${dim('— create, close, delete, assign, search')}`, value: 'issues' },
|
|
288
|
+
{ name: `${cyan('🔀')} ${bold('Pull Requests')} ${dim('— merge, review, approve, diff')}`, value: 'prs' },
|
|
289
|
+
{ name: `${green('📦')} ${bold('Repositories')} ${dim('— create, delete, clone, info')}`, value: 'repos' },
|
|
290
|
+
{ name: `${yellow('🏆')} ${bold('Contributors')} ${dim('— score, rank, auto-assign best')}`, value: 'contributors' },
|
|
291
|
+
{ name: `${red('🚀')} ${bold('Releases')} ${dim('— create, list, manage')}`, value: 'releases' },
|
|
292
|
+
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
293
|
+
{ name: `${dim('⚙️')} ${dim('Switch Repo')}`, value: 'switch' },
|
|
294
|
+
{ name: `${dim('⬅')} ${dim('Back to Mode Selector')}`, value: 'back' },
|
|
295
|
+
],
|
|
296
|
+
loop: false,
|
|
297
|
+
}]);
|
|
298
|
+
if (category === 'back')
|
|
299
|
+
break;
|
|
236
300
|
if (category === 'switch') {
|
|
237
301
|
await safeMenu(async () => {
|
|
238
302
|
const a = await inquirer.prompt([
|
|
@@ -248,13 +312,13 @@ async function mainMenu() {
|
|
|
248
312
|
}
|
|
249
313
|
if (category === 'issues')
|
|
250
314
|
await safeMenu(issueMenu);
|
|
251
|
-
if (category === 'prs')
|
|
315
|
+
else if (category === 'prs')
|
|
252
316
|
await safeMenu(prMenu);
|
|
253
|
-
if (category === 'repos')
|
|
317
|
+
else if (category === 'repos')
|
|
254
318
|
await safeMenu(repoMenu);
|
|
255
|
-
if (category === 'contributors')
|
|
256
|
-
await safeMenu(
|
|
257
|
-
if (category === 'releases')
|
|
319
|
+
else if (category === 'contributors')
|
|
320
|
+
await safeMenu(contributorScoringMenu);
|
|
321
|
+
else if (category === 'releases')
|
|
258
322
|
await safeMenu(releaseMenu);
|
|
259
323
|
console.log('');
|
|
260
324
|
}
|
|
@@ -264,11 +328,11 @@ async function issueMenu() {
|
|
|
264
328
|
const { action } = await inquirer.prompt([{
|
|
265
329
|
type: 'list', name: 'action', message: magenta('📋 Issue Operation:'),
|
|
266
330
|
choices: [
|
|
267
|
-
{ name: ` ${dim('⬅ Back
|
|
331
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
268
332
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
269
333
|
{ name: ` ${green('▸')} List open issues`, value: 'list' },
|
|
270
334
|
{ name: ` ${green('▸')} Create single issue`, value: 'create' },
|
|
271
|
-
{ name: ` ${magenta('▸')} Bulk create from JSON
|
|
335
|
+
{ name: ` ${magenta('▸')} Bulk create from file (JSON/MD)`, value: 'bulk' },
|
|
272
336
|
{ name: ` ${red('▸')} Close issue`, value: 'close' },
|
|
273
337
|
{ name: ` ${green('▸')} Reopen issue`, value: 'reopen' },
|
|
274
338
|
{ name: ` ${red('▸')} Delete (close & lock)`, value: 'delete' },
|
|
@@ -298,7 +362,7 @@ async function issueMenu() {
|
|
|
298
362
|
}
|
|
299
363
|
else if (action === 'bulk') {
|
|
300
364
|
const a = await ask([
|
|
301
|
-
{ type: 'input', name: 'file', message: yellow('📁 Path to issues JSON
|
|
365
|
+
{ type: 'input', name: 'file', message: yellow('📁 Path to issues file (JSON or MD)') + dim(' (q=back):') },
|
|
302
366
|
{ type: 'confirm', name: 'dryRun', message: 'Dry run first?', default: true },
|
|
303
367
|
{ type: 'number', name: 'start', message: dim('Start index:'), default: 1 },
|
|
304
368
|
{ type: 'number', name: 'end', message: dim('End index:'), default: 999 },
|
|
@@ -306,19 +370,12 @@ async function issueMenu() {
|
|
|
306
370
|
await issues.createIssuesFromFile(a.file, { dryRun: a.dryRun, start: a.start, end: a.end });
|
|
307
371
|
}
|
|
308
372
|
else if (action === 'assign-best') {
|
|
309
|
-
// ── Smart Auto-Assign Flow ──
|
|
310
|
-
// 1. Fetch all open issues with comments
|
|
311
|
-
// 2. Filter to ones with applicant comments
|
|
312
|
-
// 3. Let user pick from a list
|
|
313
|
-
// 4. Show applicants + scores, then assign best
|
|
314
373
|
const spinner = createSpinner(dim('Finding issues with applicants...')).start();
|
|
315
374
|
const octokit = getOctokit();
|
|
316
375
|
const { data: allIssues } = await octokit.issues.listForRepo({
|
|
317
376
|
owner: getOwner(), repo: getRepo(), state: 'open', per_page: 50,
|
|
318
377
|
});
|
|
319
|
-
// Filter to real issues (not PRs) with comments
|
|
320
378
|
const realIssues = allIssues.filter((i) => !i.pull_request && i.comments > 0);
|
|
321
|
-
// Check each issue for applicant comments
|
|
322
379
|
const issuesWithApplicants = [];
|
|
323
380
|
for (const issue of realIssues) {
|
|
324
381
|
const { data: comments } = await octokit.issues.listComments({
|
|
@@ -345,7 +402,6 @@ async function issueMenu() {
|
|
|
345
402
|
return;
|
|
346
403
|
}
|
|
347
404
|
console.log(`\n${bold(`🏆 Issues with Applicants`)} (${issuesWithApplicants.length})\n`);
|
|
348
|
-
// Let user pick an issue
|
|
349
405
|
const { picked } = await inquirer.prompt([{
|
|
350
406
|
type: 'list', name: 'picked', message: yellow('Select an issue:'),
|
|
351
407
|
choices: [
|
|
@@ -358,7 +414,6 @@ async function issueMenu() {
|
|
|
358
414
|
}]);
|
|
359
415
|
if (picked === -1)
|
|
360
416
|
return;
|
|
361
|
-
// Run the scoring
|
|
362
417
|
await issues.assignBest(picked);
|
|
363
418
|
}
|
|
364
419
|
else if (action === 'close' || action === 'reopen' || action === 'delete') {
|
|
@@ -399,7 +454,7 @@ async function prMenu() {
|
|
|
399
454
|
const { action } = await inquirer.prompt([{
|
|
400
455
|
type: 'list', name: 'action', message: cyan('🔀 PR Operation:'),
|
|
401
456
|
choices: [
|
|
402
|
-
{ name: ` ${dim('⬅ Back
|
|
457
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
403
458
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
404
459
|
{ name: ` ${green('▸')} List pull requests`, value: 'list' },
|
|
405
460
|
{ name: ` ${green('▸')} Merge PR ${dim('(checks CI first)')}`, value: 'merge' },
|
|
@@ -451,7 +506,7 @@ async function repoMenu() {
|
|
|
451
506
|
const { action } = await inquirer.prompt([{
|
|
452
507
|
type: 'list', name: 'action', message: green('📦 Repo Operation:'),
|
|
453
508
|
choices: [
|
|
454
|
-
{ name: ` ${dim('⬅ Back
|
|
509
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
455
510
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
456
511
|
{ name: ` ${green('▸')} List repositories`, value: 'list' },
|
|
457
512
|
{ name: ` ${green('▸')} Create repo`, value: 'create' },
|
|
@@ -480,29 +535,89 @@ async function repoMenu() {
|
|
|
480
535
|
await repos.createRepo(a.name, { org: a.org || undefined, description: a.description, private: a.isPrivate });
|
|
481
536
|
}
|
|
482
537
|
else if (action === 'delete') {
|
|
483
|
-
const
|
|
484
|
-
{ type: 'input', name: '
|
|
485
|
-
{ type: 'input', name: 'org', message: 'Org:', default: getOwner() },
|
|
538
|
+
const { org } = await ask([
|
|
539
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
486
540
|
]);
|
|
541
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
542
|
+
let repoToDelete;
|
|
543
|
+
if (fetchedRepos.length > 0) {
|
|
544
|
+
const { selection } = await inquirer.prompt([{
|
|
545
|
+
type: 'list',
|
|
546
|
+
name: 'selection',
|
|
547
|
+
message: red('📦 Select Repo to DELETE:'),
|
|
548
|
+
choices: [
|
|
549
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
550
|
+
...fetchedRepos.map((r) => ({ name: r.name, value: r.name }))
|
|
551
|
+
]
|
|
552
|
+
}]);
|
|
553
|
+
if (selection === 'back')
|
|
554
|
+
return;
|
|
555
|
+
repoToDelete = selection;
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: red('📦 Repo name to DELETE:') }]);
|
|
559
|
+
repoToDelete = name;
|
|
560
|
+
}
|
|
487
561
|
const { confirm } = await ask([
|
|
488
|
-
{ type: 'input', name: 'confirm', message: red(`⚠️ Type "${
|
|
562
|
+
{ type: 'input', name: 'confirm', message: red(`⚠️ Type "${repoToDelete}" to confirm deletion:`), validate: (v) => v === repoToDelete || 'Name doesn\'t match' },
|
|
489
563
|
]);
|
|
490
|
-
if (confirm ===
|
|
491
|
-
await repos.deleteRepo(
|
|
564
|
+
if (confirm === repoToDelete)
|
|
565
|
+
await repos.deleteRepo(repoToDelete, { org });
|
|
492
566
|
}
|
|
493
567
|
else if (action === 'clone') {
|
|
494
|
-
const
|
|
495
|
-
{ type: 'input', name: '
|
|
496
|
-
|
|
568
|
+
const { org } = await ask([
|
|
569
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
570
|
+
]);
|
|
571
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
572
|
+
let repoToClone;
|
|
573
|
+
if (fetchedRepos.length > 0) {
|
|
574
|
+
const { selection } = await inquirer.prompt([{
|
|
575
|
+
type: 'list',
|
|
576
|
+
name: 'selection',
|
|
577
|
+
message: cyan('📦 Select Repo to Clone:'),
|
|
578
|
+
choices: [
|
|
579
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
580
|
+
...fetchedRepos.map((r) => ({ name: r.name, value: r.name }))
|
|
581
|
+
]
|
|
582
|
+
}]);
|
|
583
|
+
if (selection === 'back')
|
|
584
|
+
return;
|
|
585
|
+
repoToClone = selection;
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: cyan('📦 Repo name to Clone:') }]);
|
|
589
|
+
repoToClone = name;
|
|
590
|
+
}
|
|
591
|
+
const { dir } = await ask([
|
|
592
|
+
{ type: 'input', name: 'dir', message: dim('Destination path (leave blank for default):'), default: '' },
|
|
497
593
|
]);
|
|
498
|
-
await repos.cloneRepo(
|
|
594
|
+
await repos.cloneRepo(repoToClone, { org, dir: dir || undefined });
|
|
499
595
|
}
|
|
500
596
|
else if (action === 'info') {
|
|
501
|
-
const
|
|
502
|
-
{ type: 'input', name: '
|
|
503
|
-
{ type: 'input', name: 'org', message: dim('Org:'), default: getOwner() },
|
|
597
|
+
const { org } = await ask([
|
|
598
|
+
{ type: 'input', name: 'org', message: cyan('👤 Org/Owner name') + dim(' (q=back):'), default: getOwner() },
|
|
504
599
|
]);
|
|
505
|
-
await repos.
|
|
600
|
+
const fetchedRepos = await repos.listRepos({ org, silent: true });
|
|
601
|
+
let repoToInfo;
|
|
602
|
+
if (fetchedRepos.length > 0) {
|
|
603
|
+
const { selection } = await inquirer.prompt([{
|
|
604
|
+
type: 'list',
|
|
605
|
+
name: 'selection',
|
|
606
|
+
message: cyan('📦 Select Repo for Info:'),
|
|
607
|
+
choices: [
|
|
608
|
+
{ name: dim('⬅ Back'), value: 'back' },
|
|
609
|
+
...fetchedRepos.map((r) => ({ name: r.name, value: r.name }))
|
|
610
|
+
]
|
|
611
|
+
}]);
|
|
612
|
+
if (selection === 'back')
|
|
613
|
+
return;
|
|
614
|
+
repoToInfo = selection;
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
const { name } = await ask([{ type: 'input', name: 'name', message: cyan('📦 Repo name:') }]);
|
|
618
|
+
repoToInfo = name;
|
|
619
|
+
}
|
|
620
|
+
await repos.repoInfo(repoToInfo, { org });
|
|
506
621
|
}
|
|
507
622
|
else if (action === 'topics') {
|
|
508
623
|
const a = await ask([
|
|
@@ -512,12 +627,12 @@ async function repoMenu() {
|
|
|
512
627
|
await repos.setTopics(a.name, a.topics.split(/\s+/), { org: getOwner() });
|
|
513
628
|
}
|
|
514
629
|
}
|
|
515
|
-
// ── Contributor Menu
|
|
516
|
-
async function
|
|
630
|
+
// ── Contributor Scoring Menu (Maintainer Tool) ─────────────────────────
|
|
631
|
+
async function contributorScoringMenu() {
|
|
517
632
|
const { action } = await inquirer.prompt([{
|
|
518
633
|
type: 'list', name: 'action', message: yellow('🏆 Contributor Operation:'),
|
|
519
634
|
choices: [
|
|
520
|
-
{ name: ` ${dim('⬅ Back
|
|
635
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
521
636
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
522
637
|
{ name: ` ${yellow('▸')} Score a user`, value: 'score' },
|
|
523
638
|
{ name: ` ${yellow('▸')} Rank applicants for issue`, value: 'rank' },
|
|
@@ -534,7 +649,7 @@ async function contributorMenu() {
|
|
|
534
649
|
const { n } = await ask([{ type: 'input', name: 'n', message: yellow('Issue #') + dim(' (q=back):') }]);
|
|
535
650
|
await contributors.rankApplicants(parseInt(n));
|
|
536
651
|
}
|
|
537
|
-
else {
|
|
652
|
+
else if (action === 'list') {
|
|
538
653
|
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results (q=back):'), default: '50' }]);
|
|
539
654
|
await contributors.listContributors({ limit: parseInt(a.limit) });
|
|
540
655
|
}
|
|
@@ -544,26 +659,27 @@ async function releaseMenu() {
|
|
|
544
659
|
const { action } = await inquirer.prompt([{
|
|
545
660
|
type: 'list', name: 'action', message: red('🚀 Release Operation:'),
|
|
546
661
|
choices: [
|
|
547
|
-
{ name: ` ${dim('⬅ Back
|
|
662
|
+
{ name: ` ${dim('⬅ Back')}`, value: 'back' },
|
|
548
663
|
new inquirer.Separator(dim(' ─────────────────────────────')),
|
|
549
|
-
{ name: ` ${
|
|
550
|
-
{ name: ` ${
|
|
664
|
+
{ name: ` ${green('▸')} List releases`, value: 'list' },
|
|
665
|
+
{ name: ` ${green('▸')} Create release`, value: 'create' },
|
|
551
666
|
],
|
|
552
667
|
}]);
|
|
553
668
|
if (action === 'back')
|
|
554
669
|
return;
|
|
555
|
-
if (action === '
|
|
670
|
+
if (action === 'list') {
|
|
671
|
+
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results:'), default: '50' }]);
|
|
672
|
+
await releases.listReleases({ limit: parseInt(a.limit) });
|
|
673
|
+
}
|
|
674
|
+
else if (action === 'create') {
|
|
556
675
|
const a = await ask([
|
|
557
|
-
{ type: 'input', name: 'tag', message: yellow('Tag (e.g
|
|
558
|
-
{ type: 'input', name: 'name', message: dim('Release name:'), default: '' },
|
|
676
|
+
{ type: 'input', name: 'tag', message: yellow('Tag (e.g. v1.0.0):') },
|
|
677
|
+
{ type: 'input', name: 'name', message: dim('Release name (optional):'), default: '' },
|
|
678
|
+
{ type: 'input', name: 'body', message: dim('Release body (optional):'), default: '' },
|
|
559
679
|
{ type: 'confirm', name: 'draft', message: 'Draft?', default: false },
|
|
560
|
-
{ type: 'confirm', name: 'prerelease', message: '
|
|
680
|
+
{ type: 'confirm', name: 'prerelease', message: 'Prerelease?', default: false },
|
|
561
681
|
]);
|
|
562
|
-
await releases.createRelease(a.tag, { name: a.name
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
const a = await ask([{ type: 'input', name: 'limit', message: dim('Max results (q=back):'), default: '50' }]);
|
|
566
|
-
await releases.listReleases({ limit: parseInt(a.limit) });
|
|
682
|
+
await releases.createRelease(a.tag, { name: a.name, body: a.body, draft: a.draft, prerelease: a.prerelease });
|
|
567
683
|
}
|
|
568
684
|
}
|
|
569
685
|
// ── Commander (direct commands) ────────────────────────────────────────
|
|
@@ -583,55 +699,73 @@ function setupCommander() {
|
|
|
583
699
|
// Issues
|
|
584
700
|
const i = program.command('issues').description('📋 Manage issues');
|
|
585
701
|
i.command('list').option('-s, --state <s>', '', 'open').option('-l, --labels <l>').option('-n, --limit <n>', '', '50')
|
|
586
|
-
.action((o) => issues.listIssues({ state: o.state, labels: o.labels, limit: parseInt(o.limit) }));
|
|
702
|
+
.action(async (o) => { await issues.listIssues({ state: o.state, labels: o.labels, limit: parseInt(o.limit) }); });
|
|
587
703
|
i.command('create').requiredOption('-t, --title <t>').option('-b, --body <b>').option('-l, --labels <l>')
|
|
588
|
-
.action((o) => issues.createIssue(o));
|
|
704
|
+
.action(async (o) => { await issues.createIssue(o); });
|
|
589
705
|
i.command('bulk').requiredOption('-f, --file <f>').option('-d, --dry-run').option('--start <n>', '', '1').option('--end <n>', '', '999')
|
|
590
|
-
.action((o) => issues.createIssuesFromFile(o.file, { dryRun: o.dryRun, start: parseInt(o.start), end: parseInt(o.end) }));
|
|
591
|
-
i.command('close <n>').action((n) => issues.closeIssue(parseInt(n)));
|
|
592
|
-
i.command('reopen <n>').action((n) => issues.reopenIssue(parseInt(n)));
|
|
593
|
-
i.command('delete <n>').action((n) => issues.deleteIssue(parseInt(n)));
|
|
594
|
-
i.command('assign <n> <users...>').action((n, u) => issues.assignIssue(parseInt(n), u));
|
|
595
|
-
i.command('assign-best <n>').action((n) => issues.assignBest(parseInt(n)));
|
|
596
|
-
i.command('search <q>').action((q) => issues.searchIssues(q));
|
|
597
|
-
i.command('label <n> <labels...>').action((n, l) => issues.labelIssue(parseInt(n), l));
|
|
706
|
+
.action(async (o) => { await issues.createIssuesFromFile(o.file, { dryRun: o.dryRun, start: parseInt(o.start), end: parseInt(o.end) }); });
|
|
707
|
+
i.command('close <n>').action(async (n) => { await issues.closeIssue(parseInt(n)); });
|
|
708
|
+
i.command('reopen <n>').action(async (n) => { await issues.reopenIssue(parseInt(n)); });
|
|
709
|
+
i.command('delete <n>').action(async (n) => { await issues.deleteIssue(parseInt(n)); });
|
|
710
|
+
i.command('assign <n> <users...>').action(async (n, u) => { await issues.assignIssue(parseInt(n), u); });
|
|
711
|
+
i.command('assign-best <n>').action(async (n) => { await issues.assignBest(parseInt(n)); });
|
|
712
|
+
i.command('search <q>').action(async (q) => { await issues.searchIssues(q); });
|
|
713
|
+
i.command('label <n> <labels...>').action(async (n, l) => { await issues.labelIssue(parseInt(n), l); });
|
|
598
714
|
// PRs
|
|
599
715
|
const p = program.command('prs').description('🔀 Manage pull requests');
|
|
600
716
|
p.command('list').option('-s, --state <s>', '', 'open').option('-n, --limit <n>', '', '50')
|
|
601
|
-
.action((o) => prs.listPRs({ state: o.state, limit: parseInt(o.limit) }));
|
|
717
|
+
.action(async (o) => { await prs.listPRs({ state: o.state, limit: parseInt(o.limit) }); });
|
|
602
718
|
p.command('merge <n>').option('-m, --method <m>', '', 'squash').option('--message <msg>').option('-f, --force', 'Skip CI checks')
|
|
603
|
-
.action((n, o) => prs.mergePR(parseInt(n), o));
|
|
604
|
-
p.command('close <n>').action((n) => prs.closePR(parseInt(n)));
|
|
605
|
-
p.command('review <n>').action((n) => prs.reviewPR(parseInt(n)));
|
|
606
|
-
p.command('approve <n>').action((n) => prs.approvePR(parseInt(n)));
|
|
607
|
-
p.command('diff <n>').action((n) => prs.diffPR(parseInt(n)));
|
|
719
|
+
.action(async (n, o) => { await prs.mergePR(parseInt(n), o); });
|
|
720
|
+
p.command('close <n>').action(async (n) => { await prs.closePR(parseInt(n)); });
|
|
721
|
+
p.command('review <n>').action(async (n) => { await prs.reviewPR(parseInt(n)); });
|
|
722
|
+
p.command('approve <n>').action(async (n) => { await prs.approvePR(parseInt(n)); });
|
|
723
|
+
p.command('diff <n>').action(async (n) => { await prs.diffPR(parseInt(n)); });
|
|
608
724
|
// Repos
|
|
609
725
|
const r = program.command('repo').description('📦 Manage repositories');
|
|
610
726
|
r.command('list').option('-o, --org <o>').option('-n, --limit <n>', '', '50')
|
|
611
|
-
.action((o) => repos.listRepos({ org: o.org, limit: parseInt(o.limit) }));
|
|
727
|
+
.action(async (o) => { await repos.listRepos({ org: o.org, limit: parseInt(o.limit) }); });
|
|
612
728
|
r.command('create <name>').option('-o, --org <o>').option('-p, --private').option('-d, --description <d>')
|
|
613
|
-
.action((n, o) => repos.createRepo(n, o));
|
|
614
|
-
r.command('delete <name>').option('-o, --org <o>').action((n, o) => repos.deleteRepo(n, o));
|
|
615
|
-
r.command('clone <name>').option('-o, --org <o>').option('-d, --dir <d>').action((n, o) => repos.cloneRepo(n, o));
|
|
616
|
-
r.command('info <name>').option('-o, --org <o>').action((n, o) => repos.repoInfo(n, o));
|
|
617
|
-
r.command('topics <name> <topics...>').option('-o, --org <o>').action((n, t, o) => repos.setTopics(n, t, o));
|
|
729
|
+
.action(async (n, o) => { await repos.createRepo(n, o); });
|
|
730
|
+
r.command('delete <name>').option('-o, --org <o>').action(async (n, o) => { await repos.deleteRepo(n, o); });
|
|
731
|
+
r.command('clone <name>').option('-o, --org <o>').option('-d, --dir <d>').action(async (n, o) => { await repos.cloneRepo(n, o); });
|
|
732
|
+
r.command('info <name>').option('-o, --org <o>').action(async (n, o) => { await repos.repoInfo(n, o); });
|
|
733
|
+
r.command('topics <name> <topics...>').option('-o, --org <o>').action(async (n, t, o) => { await repos.setTopics(n, t, o); });
|
|
618
734
|
// Contributors
|
|
619
735
|
const c = program.command('contributors').description('🏆 Manage contributors');
|
|
620
|
-
c.command('score <user>').action((u) => contributors.scoreUser(u));
|
|
621
|
-
c.command('rank <issue>').action((n) => contributors.rankApplicants(parseInt(n)));
|
|
622
|
-
c.command('list').option('-n, --limit <n>', '', '50').action((o) => contributors.listContributors({ limit: parseInt(o.limit) }));
|
|
736
|
+
c.command('score <user>').action(async (u) => { await contributors.scoreUser(u); });
|
|
737
|
+
c.command('rank <issue>').action(async (n) => { await contributors.rankApplicants(parseInt(n)); });
|
|
738
|
+
c.command('list').option('-n, --limit <n>', '', '50').action(async (o) => { await contributors.listContributors({ limit: parseInt(o.limit) }); });
|
|
623
739
|
// Releases
|
|
624
740
|
const rel = program.command('release').description('🚀 Manage releases');
|
|
625
741
|
rel.command('create <tag>').option('-n, --name <n>').option('-b, --body <b>').option('-d, --draft').option('-p, --prerelease').option('--no-generate')
|
|
626
|
-
.action((t, o) => releases.createRelease(t, o));
|
|
627
|
-
rel.command('list').option('-n, --limit <n>', '', '50').action((o) => releases.listReleases({ limit: parseInt(o.limit) }));
|
|
742
|
+
.action(async (t, o) => { await releases.createRelease(t, o); });
|
|
743
|
+
rel.command('list').option('-n, --limit <n>', '', '50').action(async (o) => { await releases.listReleases({ limit: parseInt(o.limit) }); });
|
|
744
|
+
// Contributor Commands
|
|
745
|
+
// ILLUSTRATION: How to start?
|
|
746
|
+
// Option A: `gitpadi start https://github.com/owner/repo`
|
|
747
|
+
// Option B: Interactive Menu -> Contributor Mode -> Start Contribution
|
|
748
|
+
program.command('start <url>').description('🚀 Start contribution (fork, clone & branch)').action(async (u) => { await contribute.forkAndClone(u); });
|
|
749
|
+
program.command('sync').description('🔄 Sync with upstream').action(async () => { await contribute.syncBranch(); });
|
|
750
|
+
program.command('submit').description('🚀 Submit PR (add, commit, push & PR)')
|
|
751
|
+
.option('-t, --title <t>', 'PR title')
|
|
752
|
+
.option('-m, --message <m>', 'Commit message')
|
|
753
|
+
.option('-i, --issue <n>', 'Issue number')
|
|
754
|
+
.action(async (o) => {
|
|
755
|
+
await contribute.submitPR({
|
|
756
|
+
title: o.title || 'Automated PR',
|
|
757
|
+
message: o.message || o.title,
|
|
758
|
+
issue: o.issue ? parseInt(o.issue) : undefined
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
program.command('logs').description('📋 View Action logs').action(async () => { await contribute.viewLogs(); });
|
|
628
762
|
return program;
|
|
629
763
|
}
|
|
630
764
|
// ── Entry Point ────────────────────────────────────────────────────────
|
|
631
765
|
async function main() {
|
|
632
766
|
if (process.argv.length <= 2) {
|
|
633
767
|
await bootSequence();
|
|
634
|
-
await
|
|
768
|
+
await ensureAuthenticated();
|
|
635
769
|
await mainMenu();
|
|
636
770
|
}
|
|
637
771
|
else {
|