@toneflix/grithub 0.1.1 → 0.1.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/bin/cli.js +43 -1902
- package/package.json +1 -1
- package/bin/cli.cjs +0 -2002
package/bin/cli.js
CHANGED
|
@@ -1,1077 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { Logger } from "@h3ravel/shared";
|
|
4
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import path, { dirname, join } from "node:path";
|
|
7
|
-
import readline from "node:readline/promises";
|
|
8
|
-
import Database from "better-sqlite3";
|
|
9
|
-
import os, { homedir, type } from "os";
|
|
10
|
-
import fs, { mkdirSync as mkdirSync$1 } from "fs";
|
|
11
|
-
import path$1, { join as join$1 } from "path";
|
|
12
|
-
import { Octokit } from "@octokit/rest";
|
|
13
|
-
import { Command, Kernel } from "@h3ravel/musket";
|
|
14
|
-
import diff from "fast-diff";
|
|
15
|
-
import { installPackage } from "@antfu/install-pkg";
|
|
16
|
-
import Table from "cli-table3";
|
|
17
|
-
import { createRequire as createRequire$1 } from "module";
|
|
18
|
-
import dns from "dns/promises";
|
|
19
|
-
import { createDeviceCode, exchangeDeviceCode } from "@octokit/oauth-methods";
|
|
20
|
-
import open, { apps } from "open";
|
|
21
|
-
import "dotenv/config";
|
|
22
|
-
import axios from "axios";
|
|
23
|
-
|
|
24
|
-
//#region src/utils/global.ts
|
|
25
|
-
String.prototype.toKebabCase = function() {
|
|
26
|
-
return this.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
27
|
-
};
|
|
28
|
-
String.prototype.toCamelCase = function() {
|
|
29
|
-
return this.replace(/[-_ ]+([a-zA-Z0-9])/g, (_, c) => c.toUpperCase()).replace(/^[A-Z]/, (c) => c.toLowerCase());
|
|
30
|
-
};
|
|
31
|
-
String.prototype.toPascalCase = function() {
|
|
32
|
-
return this.replace(/(^\w|[-_ ]+\w)/g, (match) => match.replace(/[-_ ]+/, "").toUpperCase());
|
|
33
|
-
};
|
|
34
|
-
String.prototype.toSnakeCase = function() {
|
|
35
|
-
return this.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
|
|
36
|
-
};
|
|
37
|
-
String.prototype.toTitleCase = function() {
|
|
38
|
-
return this.toLowerCase().replace(/(^|\s)\w/g, (match) => match.toUpperCase());
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region src/db.ts
|
|
43
|
-
let db;
|
|
44
|
-
let dbPath = path$1.join(homedir(), ".grithub");
|
|
45
|
-
mkdirSync$1(dbPath, { recursive: true });
|
|
46
|
-
const useDbPath = () => [dbPath, (path$2) => {
|
|
47
|
-
dbPath = path$2;
|
|
48
|
-
}];
|
|
49
|
-
/**
|
|
50
|
-
* Hook to get or set the database instance.
|
|
51
|
-
*
|
|
52
|
-
* @returns
|
|
53
|
-
*/
|
|
54
|
-
const useDb = () => {
|
|
55
|
-
return [() => db, (filename) => {
|
|
56
|
-
db = new Database(path$1.join(dbPath, filename));
|
|
57
|
-
const [{ journal_mode }] = db.pragma("journal_mode");
|
|
58
|
-
if (journal_mode !== "wal") db.pragma("journal_mode = WAL");
|
|
59
|
-
}];
|
|
60
|
-
};
|
|
61
|
-
const [getDatabase, setDatabase] = useDb();
|
|
62
|
-
setDatabase("app.db");
|
|
63
|
-
/**
|
|
64
|
-
* Initialize the database
|
|
65
|
-
*
|
|
66
|
-
* @param table
|
|
67
|
-
* @returns
|
|
68
|
-
*/
|
|
69
|
-
function init() {
|
|
70
|
-
return getDatabase().exec(`
|
|
2
|
+
import{createRequire as e}from"node:module";import{Logger as t}from"@h3ravel/shared";import{existsSync as n,mkdirSync as r,writeFileSync as i}from"node:fs";import{fileURLToPath as a}from"node:url";import o,{dirname as s,join as c}from"node:path";import l from"node:readline/promises";import u from"better-sqlite3";import d,{homedir as f,type as p}from"os";import m,{mkdirSync as h}from"fs";import g,{join as _}from"path";import{Octokit as v}from"@octokit/rest";import{Command as y,Kernel as b}from"@h3ravel/musket";import ee from"fast-diff";import{installPackage as te}from"@antfu/install-pkg";import ne from"cli-table3";import{createRequire as re}from"module";import ie from"dns/promises";import{createDeviceCode as ae,exchangeDeviceCode as x}from"@octokit/oauth-methods";import S,{apps as C}from"open";import"dotenv/config";import w from"axios";String.prototype.toKebabCase=function(){return this.replace(/([a-z])([A-Z])/g,`$1-$2`).replace(/[\s_]+/g,`-`).toLowerCase()},String.prototype.toCamelCase=function(){return this.replace(/[-_ ]+([a-zA-Z0-9])/g,(e,t)=>t.toUpperCase()).replace(/^[A-Z]/,e=>e.toLowerCase())},String.prototype.toPascalCase=function(){return this.replace(/(^\w|[-_ ]+\w)/g,e=>e.replace(/[-_ ]+/,``).toUpperCase())},String.prototype.toSnakeCase=function(){return this.replace(/([a-z])([A-Z])/g,`$1_$2`).replace(/[\s-]+/g,`_`).toLowerCase()},String.prototype.toTitleCase=function(){return this.toLowerCase().replace(/(^|\s)\w/g,e=>e.toUpperCase())};let T,E=g.join(f(),`.grithub`);h(E,{recursive:!0});const D=()=>[E,e=>{E=e}],[O,oe]=(()=>[()=>T,e=>{T=new u(g.join(E,e));let[{journal_mode:t}]=T.pragma(`journal_mode`);t!==`wal`&&T.pragma(`journal_mode = WAL`)}])();oe(`app.db`);function k(){return O().exec(`
|
|
71
3
|
CREATE TABLE IF NOT EXISTS json_store (
|
|
72
4
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
73
5
|
key TEXT UNIQUE,
|
|
74
6
|
value TEXT
|
|
75
7
|
)
|
|
76
|
-
`);
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Save a value to the database
|
|
80
|
-
*
|
|
81
|
-
* @param key
|
|
82
|
-
* @param value
|
|
83
|
-
* @returns
|
|
84
|
-
*/
|
|
85
|
-
function write(key, value) {
|
|
86
|
-
const db$1 = getDatabase();
|
|
87
|
-
if (typeof value === "boolean") value = value ? "1" : "0";
|
|
88
|
-
if (value instanceof Object) value = JSON.stringify(value);
|
|
89
|
-
return db$1.prepare(`INSERT INTO json_store (key, value)
|
|
8
|
+
`)}function A(e,t){let n=O();return typeof t==`boolean`&&(t=t?`1`:`0`),t instanceof Object&&(t=JSON.stringify(t)),n.prepare(`INSERT INTO json_store (key, value)
|
|
90
9
|
VALUES (?, ?)
|
|
91
10
|
ON CONFLICT(key) DO UPDATE SET value=excluded.value
|
|
92
|
-
`).run(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
|
|
110
|
-
function read(key) {
|
|
111
|
-
const db$1 = getDatabase();
|
|
112
|
-
try {
|
|
113
|
-
const row = db$1.prepare("SELECT * FROM json_store WHERE key = ?").get(key);
|
|
114
|
-
if (row) try {
|
|
115
|
-
return JSON.parse(row.value);
|
|
116
|
-
} catch {
|
|
117
|
-
return row.value;
|
|
118
|
-
}
|
|
119
|
-
} catch {}
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
//#endregion
|
|
124
|
-
//#region src/hooks.ts
|
|
125
|
-
let commandInstance;
|
|
126
|
-
/**
|
|
127
|
-
* Hook to get or set the current Command instance.
|
|
128
|
-
*/
|
|
129
|
-
function useCommand() {
|
|
130
|
-
return [() => {
|
|
131
|
-
if (!commandInstance) throw new Error("Commander instance has not been initialized");
|
|
132
|
-
return commandInstance;
|
|
133
|
-
}, (newCommand) => {
|
|
134
|
-
commandInstance = newCommand;
|
|
135
|
-
}];
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Hook to get or set the application configuration.
|
|
139
|
-
*
|
|
140
|
-
* @returns
|
|
141
|
-
*/
|
|
142
|
-
function useConfig() {
|
|
143
|
-
return [() => {
|
|
144
|
-
return read("config") || {
|
|
145
|
-
debug: false,
|
|
146
|
-
apiBaseURL: "https://api.github.com",
|
|
147
|
-
timeoutDuration: 3e3,
|
|
148
|
-
skipLongCommandGeneration: true
|
|
149
|
-
};
|
|
150
|
-
}, (config$1) => {
|
|
151
|
-
write("config", config$1);
|
|
152
|
-
return read("config");
|
|
153
|
-
}];
|
|
154
|
-
}
|
|
155
|
-
const shortcutUsed = /* @__PURE__ */ new Set();
|
|
156
|
-
/**
|
|
157
|
-
* Hook to make command shortcuts unique across the application.
|
|
158
|
-
*
|
|
159
|
-
* @returns
|
|
160
|
-
*/
|
|
161
|
-
function useShortcuts() {
|
|
162
|
-
return [() => Array.from(shortcutUsed).filter((s) => !!s), (shortcut) => {
|
|
163
|
-
if (!shortcut) {
|
|
164
|
-
shortcutUsed.clear();
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
if (shortcutUsed.has(shortcut)) return false;
|
|
168
|
-
shortcutUsed.add(shortcut);
|
|
169
|
-
return true;
|
|
170
|
-
}];
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Hook to get an authenticated Octokit instance.
|
|
174
|
-
*
|
|
175
|
-
* @returns
|
|
176
|
-
*/
|
|
177
|
-
const useOctokit = () => {
|
|
178
|
-
const token = read("token");
|
|
179
|
-
if (!token) throw new Error("No authentication token found. Please log in first.");
|
|
180
|
-
return new Octokit({ auth: token });
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
//#endregion
|
|
184
|
-
//#region src/helpers.ts
|
|
185
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
186
|
-
const __dirname = path.dirname(__filename);
|
|
187
|
-
/**
|
|
188
|
-
* Wrap a promise to return a tuple of error and result
|
|
189
|
-
*
|
|
190
|
-
* @param promise
|
|
191
|
-
* @returns
|
|
192
|
-
*/
|
|
193
|
-
const promiseWrapper = (promise) => promise.then((data) => [null, data]).catch((error) => [typeof error === "string" ? error : error.message, null]);
|
|
194
|
-
/**
|
|
195
|
-
* Execute a schema
|
|
196
|
-
*
|
|
197
|
-
* @param schema
|
|
198
|
-
* @param options
|
|
199
|
-
* @returns
|
|
200
|
-
*/
|
|
201
|
-
async function executeSchema(root, schema, args) {
|
|
202
|
-
const octokit = useOctokit();
|
|
203
|
-
const { data, message } = await Reflect.apply(octokit[root][schema.api], octokit[root], [args]);
|
|
204
|
-
if (!data || Array.isArray(data) && data.length < 1 || data instanceof Object && Object.keys(data).length < 1) return {
|
|
205
|
-
data: null,
|
|
206
|
-
message: message ?? "Request was successful but returned no data.",
|
|
207
|
-
status: false
|
|
208
|
-
};
|
|
209
|
-
return {
|
|
210
|
-
data,
|
|
211
|
-
message: message ?? "Request Completed",
|
|
212
|
-
status: true
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Wait for a specified number of milliseconds
|
|
217
|
-
*
|
|
218
|
-
* @param ms
|
|
219
|
-
* @param callback
|
|
220
|
-
* @returns
|
|
221
|
-
*/
|
|
222
|
-
const wait = (ms, callback) => {
|
|
223
|
-
return new Promise((resolve) => {
|
|
224
|
-
setTimeout(() => {
|
|
225
|
-
if (callback) resolve(callback());
|
|
226
|
-
resolve();
|
|
227
|
-
}, ms);
|
|
228
|
-
});
|
|
229
|
-
};
|
|
230
|
-
/**
|
|
231
|
-
* Logger helper
|
|
232
|
-
*
|
|
233
|
-
* @param str
|
|
234
|
-
* @param config
|
|
235
|
-
* @returns
|
|
236
|
-
*/
|
|
237
|
-
const logger = (str, config$1 = ["green", "italic"], log) => {
|
|
238
|
-
return Logger.log(str, config$1, log ?? false);
|
|
239
|
-
};
|
|
240
|
-
const viewIssue = (issue) => {
|
|
241
|
-
Logger.log([
|
|
242
|
-
["Title:", ["white", "bold"]],
|
|
243
|
-
[issue.title, ["blue"]],
|
|
244
|
-
["\nType:", ["white", "bold"]],
|
|
245
|
-
[typeof issue.type === "string" ? issue.type : issue.type?.name ?? "N/A", ["blue"]],
|
|
246
|
-
["\nNumber:", ["white", "bold"]],
|
|
247
|
-
[String(issue.number), ["blue"]],
|
|
248
|
-
["\nState:", ["white", "bold"]],
|
|
249
|
-
[issue.state, ["blue"]],
|
|
250
|
-
["\nLabels:", ["white", "bold"]],
|
|
251
|
-
[issue.labels.map((l) => l.name ?? l).join(", "), ["blue"]],
|
|
252
|
-
["\nAssignees:", ["white", "bold"]],
|
|
253
|
-
[issue.assignees?.map((a) => a.login ?? a).join(", ") || "N/A", ["blue"]],
|
|
254
|
-
["\nCreated at:", ["white", "bold"]],
|
|
255
|
-
[new Date(issue.created_at).toLocaleString(), ["blue"]],
|
|
256
|
-
["\nUpdated at:", ["white", "bold"]],
|
|
257
|
-
[new Date(issue.updated_at).toLocaleString(), ["blue"]]
|
|
258
|
-
], " ");
|
|
259
|
-
};
|
|
260
|
-
/**
|
|
261
|
-
* Find the nearest package.json file
|
|
262
|
-
*
|
|
263
|
-
* @param startDir
|
|
264
|
-
* @returns
|
|
265
|
-
*/
|
|
266
|
-
const findCLIPackageJson = (startDir = __dirname) => {
|
|
267
|
-
let dir = startDir;
|
|
268
|
-
while (true) {
|
|
269
|
-
const pkgPath = path.join(dir, "package.json");
|
|
270
|
-
if (existsSync(pkgPath)) return pkgPath;
|
|
271
|
-
const parent = path.dirname(dir);
|
|
272
|
-
if (parent === dir) break;
|
|
273
|
-
dir = parent;
|
|
274
|
-
}
|
|
275
|
-
return null;
|
|
276
|
-
};
|
|
277
|
-
/**
|
|
278
|
-
* Wait for the user to press Enter
|
|
279
|
-
*
|
|
280
|
-
* @param onEnter
|
|
281
|
-
*/
|
|
282
|
-
const waitForEnter = async (onEnter) => {
|
|
283
|
-
const rl = readline.createInterface({
|
|
284
|
-
input: process.stdin,
|
|
285
|
-
output: process.stdout
|
|
286
|
-
});
|
|
287
|
-
await rl.question("");
|
|
288
|
-
onEnter();
|
|
289
|
-
rl.close();
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
//#endregion
|
|
293
|
-
//#region src/github/apis.ts
|
|
294
|
-
const APIs = {
|
|
295
|
-
issues: [
|
|
296
|
-
{
|
|
297
|
-
api: "create",
|
|
298
|
-
alias: void 0,
|
|
299
|
-
endpoint: "/repos/{owner}/{repo}/issues",
|
|
300
|
-
description: "Create an issue",
|
|
301
|
-
params: [
|
|
302
|
-
{
|
|
303
|
-
parameter: "title",
|
|
304
|
-
required: true,
|
|
305
|
-
type: "String",
|
|
306
|
-
description: "The title of the issue",
|
|
307
|
-
paramType: "body",
|
|
308
|
-
flag: true
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
parameter: "body",
|
|
312
|
-
required: false,
|
|
313
|
-
type: "String",
|
|
314
|
-
description: "The contents of the issue",
|
|
315
|
-
paramType: "body",
|
|
316
|
-
flag: true
|
|
317
|
-
},
|
|
318
|
-
{
|
|
319
|
-
parameter: "owner",
|
|
320
|
-
required: false,
|
|
321
|
-
type: "String",
|
|
322
|
-
description: "The account owner of the repository",
|
|
323
|
-
paramType: "path",
|
|
324
|
-
arg: true
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
parameter: "repo",
|
|
328
|
-
required: false,
|
|
329
|
-
type: "String",
|
|
330
|
-
description: "The name of the repository",
|
|
331
|
-
paramType: "path",
|
|
332
|
-
arg: true
|
|
333
|
-
}
|
|
334
|
-
]
|
|
335
|
-
},
|
|
336
|
-
{
|
|
337
|
-
api: "listForRepo",
|
|
338
|
-
alias: "list",
|
|
339
|
-
endpoint: "/repos/{owner}/{repo}/issues",
|
|
340
|
-
description: "List repository issues",
|
|
341
|
-
params: [
|
|
342
|
-
{
|
|
343
|
-
parameter: "owner",
|
|
344
|
-
required: false,
|
|
345
|
-
type: "String",
|
|
346
|
-
description: "The account owner of the repository",
|
|
347
|
-
paramType: "path"
|
|
348
|
-
},
|
|
349
|
-
{
|
|
350
|
-
parameter: "repo",
|
|
351
|
-
required: false,
|
|
352
|
-
type: "String",
|
|
353
|
-
description: "The name of the repository",
|
|
354
|
-
paramType: "path"
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
parameter: "state",
|
|
358
|
-
required: false,
|
|
359
|
-
type: "String",
|
|
360
|
-
description: "Indicates the state of the issues to return. [open, closed]",
|
|
361
|
-
paramType: "query"
|
|
362
|
-
}
|
|
363
|
-
]
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
api: "get",
|
|
367
|
-
alias: "get",
|
|
368
|
-
endpoint: "/repos/{owner}/{repo}/issues/{issue_number}",
|
|
369
|
-
description: "Get a single issue",
|
|
370
|
-
params: [
|
|
371
|
-
{
|
|
372
|
-
parameter: "issue_number",
|
|
373
|
-
required: true,
|
|
374
|
-
type: "Number",
|
|
375
|
-
description: "The number of the issue to get",
|
|
376
|
-
paramType: "path"
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
parameter: "owner",
|
|
380
|
-
required: false,
|
|
381
|
-
type: "String",
|
|
382
|
-
description: "The account owner of the repository",
|
|
383
|
-
paramType: "path"
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
parameter: "repo",
|
|
387
|
-
required: false,
|
|
388
|
-
type: "String",
|
|
389
|
-
description: "The name of the repository",
|
|
390
|
-
paramType: "path"
|
|
391
|
-
}
|
|
392
|
-
]
|
|
393
|
-
}
|
|
394
|
-
],
|
|
395
|
-
orgs: [{
|
|
396
|
-
api: "listForAuthenticatedUser",
|
|
397
|
-
alias: "list",
|
|
398
|
-
endpoint: "/user/orgs",
|
|
399
|
-
description: "List organizations for the authenticated user",
|
|
400
|
-
params: [{
|
|
401
|
-
parameter: "page",
|
|
402
|
-
required: false,
|
|
403
|
-
type: "Number",
|
|
404
|
-
description: "Page number of the results to fetch",
|
|
405
|
-
paramType: "query"
|
|
406
|
-
}, {
|
|
407
|
-
parameter: "per_page",
|
|
408
|
-
required: false,
|
|
409
|
-
type: "Number",
|
|
410
|
-
description: "Results per page (max 100)",
|
|
411
|
-
paramType: "query"
|
|
412
|
-
}]
|
|
413
|
-
}]
|
|
414
|
-
};
|
|
415
|
-
var apis_default = APIs;
|
|
416
|
-
|
|
417
|
-
//#endregion
|
|
418
|
-
//#region src/utils/argument.ts
|
|
419
|
-
/**
|
|
420
|
-
* We would build a command signature string from an array of arguments.
|
|
421
|
-
* Musket command signature for arguments follow this format:
|
|
422
|
-
*
|
|
423
|
-
* - Optional arguments: {argumentName?}
|
|
424
|
-
* - Required arguments: {argumentName}
|
|
425
|
-
* - Optional argument with a default value: {argumentName=defaultValue}
|
|
426
|
-
* - Arguments with description: {argumentName : description}
|
|
427
|
-
* - Arguments Expecting multiple values: {argumentName*}
|
|
428
|
-
*
|
|
429
|
-
* - Boolean flags are represented as: {--flag-name}
|
|
430
|
-
* - Flags expecting values are represented as: {--flag-name=}
|
|
431
|
-
* - Flags with description: {--flag-name : description}
|
|
432
|
-
* - Flags expecting multiple values: {--flag-name=*}
|
|
433
|
-
* - Flags with choices: {--flag-name : : choice1,choice2,choice3}
|
|
434
|
-
* - Or {--flag-name : description : choice1,choice2,choice3}
|
|
435
|
-
*
|
|
436
|
-
* For shortcuts: {--F|flag-name}
|
|
437
|
-
* We will extract the first letter before the pipe as the shortcut, but we also
|
|
438
|
-
* need to ensure it is not already used by another option, in which case we check
|
|
439
|
-
* if the string is a multiword (camel, dash, underscore separated) then we try to use the first letter of the second word.
|
|
440
|
-
*
|
|
441
|
-
* XParam properties used:
|
|
442
|
-
* - parameter: The name of the argument or flag.
|
|
443
|
-
* - required: A boolean indicating if the argument is required.
|
|
444
|
-
* - type: The type of the argument (String, Number, Boolean, Array, Object).
|
|
445
|
-
* - description: An optional description for the argument.
|
|
446
|
-
* - default: An optional default value for the argument.
|
|
447
|
-
* - options: An optional array of choices for the argument.
|
|
448
|
-
*
|
|
449
|
-
* We will make required arguments with defaults arguments.
|
|
450
|
-
* Everything else would be flags.
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
* @param args
|
|
454
|
-
*/
|
|
455
|
-
const buildSignature = (param, cmd) => {
|
|
456
|
-
const [_, setShortcut] = useShortcuts();
|
|
457
|
-
let signature = "";
|
|
458
|
-
if ((!param.required || param.default !== void 0 || param.type === "Boolean" || param.options || param.flag === true) && param.paramType !== "path" && param.arg !== true) {
|
|
459
|
-
signature += "{--";
|
|
460
|
-
if (setShortcut(cmd + ":" + param.parameter.charAt(0).toLowerCase())) signature += `${param.parameter.charAt(0).toLowerCase()}|`;
|
|
461
|
-
else {
|
|
462
|
-
const words = param.parameter.split(/[_-\s]/);
|
|
463
|
-
if (words.length > 1) {
|
|
464
|
-
if (setShortcut(cmd + ":" + words[1].charAt(0).toLowerCase())) signature += `${words[1].charAt(0).toLowerCase()}|`;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
signature += `${param.parameter}`;
|
|
468
|
-
if (param.type !== "Boolean") signature += param.default ? `=${param.default}` : "?";
|
|
469
|
-
if (param.description) signature += ` : ${param.description}`;
|
|
470
|
-
if (param.options) {
|
|
471
|
-
const optionsStr = param.options.join(",");
|
|
472
|
-
signature += ` : ${optionsStr}`;
|
|
473
|
-
}
|
|
474
|
-
signature += "}";
|
|
475
|
-
} else {
|
|
476
|
-
signature += `{${param.parameter}`;
|
|
477
|
-
if (param.default) signature += `=${param.default}`;
|
|
478
|
-
if (param.description) signature += ` : ${param.description}`;
|
|
479
|
-
signature += "}";
|
|
480
|
-
}
|
|
481
|
-
return signature;
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
//#endregion
|
|
485
|
-
//#region src/utils/renderer.ts
|
|
486
|
-
/**
|
|
487
|
-
* We will recursively map through the result data and log each key value pair
|
|
488
|
-
* as we apply coloring based on the value type.
|
|
489
|
-
* We also need to handle root or nested objects and arrays while considering
|
|
490
|
-
* indentation for better readability.
|
|
491
|
-
*
|
|
492
|
-
* @param data
|
|
493
|
-
*/
|
|
494
|
-
const dataRenderer = (data) => {
|
|
495
|
-
const render = (obj, indent = 0) => {
|
|
496
|
-
const indentation = " ".repeat(indent);
|
|
497
|
-
for (const key in obj) {
|
|
498
|
-
const value = obj[key];
|
|
499
|
-
if (typeof value === "object" && value !== null) {
|
|
500
|
-
console.log(`${indentation}${stringFormatter(key)}:`);
|
|
501
|
-
render(value, indent + 2);
|
|
502
|
-
} else {
|
|
503
|
-
let coloredValue;
|
|
504
|
-
switch (typeof value) {
|
|
505
|
-
case "string":
|
|
506
|
-
coloredValue = Logger.log(value, "green", false);
|
|
507
|
-
break;
|
|
508
|
-
case "number":
|
|
509
|
-
coloredValue = Logger.log(String(value), "yellow", false);
|
|
510
|
-
break;
|
|
511
|
-
case "boolean":
|
|
512
|
-
coloredValue = Logger.log(String(value), "blue", false);
|
|
513
|
-
break;
|
|
514
|
-
case "object":
|
|
515
|
-
if (value === null) coloredValue = Logger.log("null", "gray", false);
|
|
516
|
-
else coloredValue = Logger.log(JSON.stringify(value), "cyan", false);
|
|
517
|
-
break;
|
|
518
|
-
default: coloredValue = value;
|
|
519
|
-
}
|
|
520
|
-
console.log(`${indentation}${stringFormatter(key)}: ${coloredValue}`);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
render(data);
|
|
525
|
-
};
|
|
526
|
-
/**
|
|
527
|
-
* We will format a string by replacing underscores and hyphens with spaces,
|
|
528
|
-
* capitalizing the first letter of every word,
|
|
529
|
-
* converting camelCase to spaced words,
|
|
530
|
-
* and trimming any leading or trailing spaces.
|
|
531
|
-
* If a sentence is only two letters long we will make it uppercase.
|
|
532
|
-
*
|
|
533
|
-
* @param str
|
|
534
|
-
* @returns
|
|
535
|
-
*/
|
|
536
|
-
const stringFormatter = (str) => {
|
|
537
|
-
return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ").trim().replace(/^(\w{2})$/, (_, p1) => p1.toUpperCase());
|
|
538
|
-
};
|
|
539
|
-
/**
|
|
540
|
-
* Render the difference between two text strings, highlighting additions and deletions.
|
|
541
|
-
*
|
|
542
|
-
* @param oldText
|
|
543
|
-
* @param newText
|
|
544
|
-
* @returns
|
|
545
|
-
*/
|
|
546
|
-
const diffText = (oldText, newText) => {
|
|
547
|
-
return diff(newText, oldText).map((part) => {
|
|
548
|
-
const [type$1, text] = part;
|
|
549
|
-
if (type$1 === 0) return text;
|
|
550
|
-
else if (type$1 === -1) return logger(text, ["red", "strikethrough"], !1);
|
|
551
|
-
else return logger(text, ["green", "underline"], !1);
|
|
552
|
-
}).join("");
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
//#endregion
|
|
556
|
-
//#region src/Commands/Commands.ts
|
|
557
|
-
var Commands_default = () => {
|
|
558
|
-
const require = createRequire(import.meta.url);
|
|
559
|
-
const commands = [];
|
|
560
|
-
let GeneratedAPIs = apis_default;
|
|
561
|
-
if (!process.argv.includes("generate:apis") && existsSync(path.join(process.cwd(), ".grithub/apis.generated.js"))) ({APIs: GeneratedAPIs} = require(path.join(process.cwd(), ".grithub/apis.generated.js")));
|
|
562
|
-
/**
|
|
563
|
-
* We should map through the APIs and reduce all apis to a single key value pair
|
|
564
|
-
* where key is the API key and the schema array entry api propety separated by a
|
|
565
|
-
* semicolon and the value is schema array entry.
|
|
566
|
-
*/
|
|
567
|
-
const entries = Object.entries(GeneratedAPIs).reduce((acc, [key, schemas]) => {
|
|
568
|
-
schemas.forEach((schema) => {
|
|
569
|
-
const commandKey = key === schema.api ? key : `${key}:${(schema.alias ?? schema.api).toKebabCase()}`;
|
|
570
|
-
acc[commandKey] = schema;
|
|
571
|
-
});
|
|
572
|
-
return acc;
|
|
573
|
-
}, {});
|
|
574
|
-
for (const [key, schema] of Object.entries(entries)) {
|
|
575
|
-
const args = schema.params.map((param) => buildSignature(param, key)).join("\n");
|
|
576
|
-
const command = class extends Command {
|
|
577
|
-
signature = `${key} \n${args}`;
|
|
578
|
-
description = schema.description || "No description available.";
|
|
579
|
-
handle = async () => {
|
|
580
|
-
const root = key.split(":").shift();
|
|
581
|
-
const $args = {
|
|
582
|
-
...this.arguments() ?? {},
|
|
583
|
-
...this.options() ?? {}
|
|
584
|
-
};
|
|
585
|
-
const [_, setCommand] = useCommand();
|
|
586
|
-
setCommand(this);
|
|
587
|
-
if (!root) return void this.error("Unknown command entry.").newLine();
|
|
588
|
-
for (const param of schema.params) if (param.required && !this.argument(param.parameter)) return void this.newLine().error(`Missing required argument: ${param.parameter}`).newLine();
|
|
589
|
-
const repo = read("default_repo");
|
|
590
|
-
const token = read("token");
|
|
591
|
-
const repository = ([$args.owner, $args.repo].filter(Boolean).join("/") || repo.full_name).split("/") ?? ["", ""];
|
|
592
|
-
const requiresRepo = schema.params.some((param) => ["repo", "user"].includes(param.parameter));
|
|
593
|
-
if (requiresRepo && (!repository[0] || !repository[1])) return void this.error("ERROR: No repository set. Please set a default repository using the [set-repo] command or provide one using the --repo option.").newLine();
|
|
594
|
-
if (!token) return void this.error("ERROR: You're not signed in, please run the [login] command before you begin").newLine();
|
|
595
|
-
this.newLine();
|
|
596
|
-
const spinner = this.spinner("Loading...\n").start();
|
|
597
|
-
if (requiresRepo) {
|
|
598
|
-
$args["owner"] = repository[0];
|
|
599
|
-
$args["repo"] = repository[1];
|
|
600
|
-
}
|
|
601
|
-
const [err, result] = await promiseWrapper(executeSchema(root, schema, $args));
|
|
602
|
-
if (err || !result) return void spinner.fail((err || "An error occurred") + "\n");
|
|
603
|
-
spinner.succeed(result.message);
|
|
604
|
-
this.newLine();
|
|
605
|
-
dataRenderer(result.data);
|
|
606
|
-
this.newLine();
|
|
607
|
-
};
|
|
608
|
-
};
|
|
609
|
-
commands.push(command);
|
|
610
|
-
}
|
|
611
|
-
return commands;
|
|
612
|
-
};
|
|
613
|
-
|
|
614
|
-
//#endregion
|
|
615
|
-
//#region src/utils/config.ts
|
|
616
|
-
const configChoices = (config$1) => {
|
|
617
|
-
return [
|
|
618
|
-
{
|
|
619
|
-
name: "Debug Mode",
|
|
620
|
-
value: "debug",
|
|
621
|
-
description: `Enable or disable debug mode (${config$1.debug ? "Enabled" : "Disabled"})`
|
|
622
|
-
},
|
|
623
|
-
{
|
|
624
|
-
name: "API Base URL",
|
|
625
|
-
value: "apiBaseURL",
|
|
626
|
-
description: `Set the base URL for the API (${config$1.apiBaseURL})`
|
|
627
|
-
},
|
|
628
|
-
{
|
|
629
|
-
name: "Timeout Duration",
|
|
630
|
-
value: "timeoutDuration",
|
|
631
|
-
description: `Set the timeout duration for API requests (${config$1.timeoutDuration} ms)`
|
|
632
|
-
},
|
|
633
|
-
{
|
|
634
|
-
name: "Skip Long Command Generation",
|
|
635
|
-
value: "skipLongCommandGeneration",
|
|
636
|
-
description: `Enable or disable skipping of long command generation when calling ${logger("generate:apis", ["grey", "italic"])} (${config$1.skipLongCommandGeneration ? "Enabled" : "Disabled"})`
|
|
637
|
-
},
|
|
638
|
-
{
|
|
639
|
-
name: "Ngrok Auth Token",
|
|
640
|
-
value: "ngrokAuthToken",
|
|
641
|
-
description: `Set the Ngrok Auth Token - will default to environment variable if not set (${config$1.ngrokAuthToken ? "************" : "Not Set"})`
|
|
642
|
-
},
|
|
643
|
-
{
|
|
644
|
-
name: "Reset Configuration",
|
|
645
|
-
value: "reset",
|
|
646
|
-
description: "Reset all configurations to default values"
|
|
647
|
-
}
|
|
648
|
-
];
|
|
649
|
-
};
|
|
650
|
-
const saveConfig = async (choice) => {
|
|
651
|
-
const [getConfig, setConfig] = useConfig();
|
|
652
|
-
const [command] = useCommand();
|
|
653
|
-
let config$1 = getConfig();
|
|
654
|
-
if (choice === "debug") {
|
|
655
|
-
const debug = await command().confirm(`${config$1.debug ? "Dis" : "En"}able debug mode?`, config$1.debug === true);
|
|
656
|
-
config$1.debug = config$1.debug !== debug;
|
|
657
|
-
} else if (choice === "apiBaseURL") config$1.apiBaseURL = await command().ask("Enter API Base URL", config$1.apiBaseURL);
|
|
658
|
-
else if (choice === "ngrokAuthToken") config$1.ngrokAuthToken = await command().ask("Enter Ngrok Auth Token", config$1.ngrokAuthToken || "");
|
|
659
|
-
else if (choice === "timeoutDuration") {
|
|
660
|
-
const timeoutDuration = await command().ask("Enter Timeout Duration (in ms)", config$1.timeoutDuration.toString());
|
|
661
|
-
config$1.timeoutDuration = parseInt(timeoutDuration);
|
|
662
|
-
} else if (choice === "skipLongCommandGeneration") config$1.skipLongCommandGeneration = await command().confirm(`${config$1.skipLongCommandGeneration ? "Dis" : "En"}able skipping of long command generation?`, config$1.skipLongCommandGeneration === true);
|
|
663
|
-
else if (choice === "reset") config$1 = {
|
|
664
|
-
debug: false,
|
|
665
|
-
apiBaseURL: "https://api.github.com",
|
|
666
|
-
timeoutDuration: 3e3,
|
|
667
|
-
skipLongCommandGeneration: true
|
|
668
|
-
};
|
|
669
|
-
setConfig(config$1);
|
|
670
|
-
};
|
|
671
|
-
|
|
672
|
-
//#endregion
|
|
673
|
-
//#region src/Commands/ConfigCommand.ts
|
|
674
|
-
var ConfigCommand = class extends Command {
|
|
675
|
-
signature = "config";
|
|
676
|
-
description = "Configure Grithub";
|
|
677
|
-
async handle() {
|
|
678
|
-
const [_, setCommand] = useCommand();
|
|
679
|
-
setCommand(this);
|
|
680
|
-
const [getConfig, setConfig] = useConfig();
|
|
681
|
-
let config$1 = getConfig();
|
|
682
|
-
if (!config$1) {
|
|
683
|
-
config$1 = {
|
|
684
|
-
debug: false,
|
|
685
|
-
apiBaseURL: "https://api.github.com",
|
|
686
|
-
timeoutDuration: 3e3,
|
|
687
|
-
skipLongCommandGeneration: true
|
|
688
|
-
};
|
|
689
|
-
setConfig(config$1);
|
|
690
|
-
}
|
|
691
|
-
await saveConfig(await this.choice("Select configuration to set", configChoices(config$1)));
|
|
692
|
-
this.info("Configuration updated successfully!").newLine();
|
|
693
|
-
}
|
|
694
|
-
};
|
|
695
|
-
|
|
696
|
-
//#endregion
|
|
697
|
-
//#region src/github/apis-generator.ts
|
|
698
|
-
var ApisGenerator = class ApisGenerator {
|
|
699
|
-
spec;
|
|
700
|
-
config;
|
|
701
|
-
openapi;
|
|
702
|
-
skipApis = new Set([
|
|
703
|
-
"issues:list",
|
|
704
|
-
"issues:update",
|
|
705
|
-
"issues:seed",
|
|
706
|
-
"issues:delete"
|
|
707
|
-
]);
|
|
708
|
-
skipParams = new Set(["s"]);
|
|
709
|
-
PARAM_LOCATIONS = new Set([
|
|
710
|
-
"path",
|
|
711
|
-
"query",
|
|
712
|
-
"header"
|
|
713
|
-
]);
|
|
714
|
-
constructor(openapi, schema = "api.github.com.deref") {
|
|
715
|
-
const [getConfig] = useConfig();
|
|
716
|
-
this.openapi = openapi;
|
|
717
|
-
this.spec = this.openapi.schemas[schema];
|
|
718
|
-
this.config = getConfig();
|
|
719
|
-
if (!this.spec || !this.spec.paths) throw new Error(`Could not find ${schema} schema`);
|
|
720
|
-
}
|
|
721
|
-
static async installOctokitOpenapi() {
|
|
722
|
-
const spinner = useCommand()[0]().spinner("Installing @octokit/openapi...").start();
|
|
723
|
-
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
724
|
-
await installPackage("@octokit/openapi", {
|
|
725
|
-
cwd: path.normalize(path.join(__dirname$1, "../..")),
|
|
726
|
-
silent: true,
|
|
727
|
-
dev: true
|
|
728
|
-
});
|
|
729
|
-
spinner.succeed("@octokit/openapi installed successfully.");
|
|
730
|
-
return (await import("@octokit/openapi")).default;
|
|
731
|
-
}
|
|
732
|
-
skipParam(name) {
|
|
733
|
-
return this.skipParams.has(name) || name.length > 20 || name.length <= 2;
|
|
734
|
-
}
|
|
735
|
-
skipApi(api$1, namespace) {
|
|
736
|
-
const cmd = (namespace ? namespace + ":" : "") + api$1.toCamelCase();
|
|
737
|
-
return this.skipApis.has(cmd) || this.skipApis.has(api$1.toCamelCase()) || cmd.length > (this.config.skipLongCommandGeneration ? 23 : Infinity);
|
|
738
|
-
}
|
|
739
|
-
normalizeType(schema) {
|
|
740
|
-
const typeMap = {
|
|
741
|
-
integer: "Number",
|
|
742
|
-
number: "Number",
|
|
743
|
-
string: "String",
|
|
744
|
-
boolean: "Boolean",
|
|
745
|
-
array: "Array",
|
|
746
|
-
object: "Object",
|
|
747
|
-
enum: "String",
|
|
748
|
-
oneOf: "String",
|
|
749
|
-
anyOf: "String",
|
|
750
|
-
allOf: "String"
|
|
751
|
-
};
|
|
752
|
-
let type$1 = typeMap[schema?.type] || "any";
|
|
753
|
-
if (Array.isArray(schema?.type)) type$1 = schema.type.map((t) => typeMap[t] || "any").join("|");
|
|
754
|
-
if (type$1 !== "any") return type$1;
|
|
755
|
-
if (!schema) type$1 = "any";
|
|
756
|
-
if (Array.isArray(schema.type)) return schema.type.join("|");
|
|
757
|
-
if (schema.type) type$1 = schema.type;
|
|
758
|
-
if (schema.enum) type$1 = "enum";
|
|
759
|
-
if (schema.oneOf) type$1 = "oneOf";
|
|
760
|
-
if (schema.anyOf) type$1 = "anyOf";
|
|
761
|
-
if (schema.allOf) type$1 = "allOf";
|
|
762
|
-
return typeMap[type$1] || "any";
|
|
763
|
-
}
|
|
764
|
-
gatherParams(op) {
|
|
765
|
-
const collected = [];
|
|
766
|
-
for (const p of op.parameters ?? []) {
|
|
767
|
-
const loc = this.PARAM_LOCATIONS.has(p.in) ? p.in : "query";
|
|
768
|
-
if (this.skipParam(p.name)) continue;
|
|
769
|
-
collected.push({
|
|
770
|
-
parameter: p.name,
|
|
771
|
-
required: !!p.required,
|
|
772
|
-
type: this.normalizeType(p.schema).toPascalCase(),
|
|
773
|
-
description: p.description,
|
|
774
|
-
paramType: loc
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
const jsonBody = op.requestBody?.content?.["application/json"];
|
|
778
|
-
const bodySchema = jsonBody?.schema;
|
|
779
|
-
const bodyProps = bodySchema?.properties ?? {};
|
|
780
|
-
const requiredProps = bodySchema?.required ?? [];
|
|
781
|
-
for (const [name, schema] of Object.entries(bodyProps)) {
|
|
782
|
-
if (this.skipParam(name)) continue;
|
|
783
|
-
collected.push({
|
|
784
|
-
parameter: name,
|
|
785
|
-
required: requiredProps.includes(name) || !!jsonBody?.required,
|
|
786
|
-
type: this.normalizeType(schema).toPascalCase(),
|
|
787
|
-
description: schema.description,
|
|
788
|
-
paramType: "body"
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
return collected;
|
|
792
|
-
}
|
|
793
|
-
buildTree() {
|
|
794
|
-
const tree = {};
|
|
795
|
-
for (const [route, ops] of Object.entries(this.spec.paths)) for (const [_method, def] of Object.entries(ops ?? {})) {
|
|
796
|
-
const op = def;
|
|
797
|
-
const opId = op?.operationId;
|
|
798
|
-
if (!opId) continue;
|
|
799
|
-
const [namespace, name] = opId.split("/");
|
|
800
|
-
if (!namespace || !name || this.skipApi(name, namespace)) continue;
|
|
801
|
-
const params = this.gatherParams(op);
|
|
802
|
-
if (!tree[namespace.toCamelCase()]) tree[namespace.toCamelCase()] = [];
|
|
803
|
-
tree[namespace.toCamelCase()].push({
|
|
804
|
-
api: name.toCamelCase(),
|
|
805
|
-
endpoint: route,
|
|
806
|
-
description: op.summary ?? op.description ?? void 0,
|
|
807
|
-
alias: op["x-github"]?.alias ?? op["x-octokit"]?.alias ?? void 0,
|
|
808
|
-
params
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
return tree;
|
|
812
|
-
}
|
|
813
|
-
static async run() {
|
|
814
|
-
const [cmd] = useCommand();
|
|
815
|
-
const command = cmd();
|
|
816
|
-
let octokitOpenapi;
|
|
817
|
-
const spinner = command.spinner("Checking if @octokit/openapi Installed...").start();
|
|
818
|
-
try {
|
|
819
|
-
({default: octokitOpenapi} = await import("@octokit/openapi"));
|
|
820
|
-
spinner.succeed("@octokit/openapi is already installed.");
|
|
821
|
-
} catch {
|
|
822
|
-
spinner.fail("@octokit/openapi is not installed.");
|
|
823
|
-
octokitOpenapi = await ApisGenerator.installOctokitOpenapi();
|
|
824
|
-
}
|
|
825
|
-
spinner.start("Generating Extended APIs...");
|
|
826
|
-
const tree = new ApisGenerator(octokitOpenapi, "api.github.com.deref").buildTree();
|
|
827
|
-
const target = path.join(process.cwd(), ".grithub/apis.generated.js");
|
|
828
|
-
const contents = `// Auto-generated from @octokit/openapi. Do not edit directly.
|
|
829
|
-
|
|
830
|
-
export const APIs = ${JSON.stringify(tree, null, 2).replace(/"([A-Za-z_][\w$]*)":/g, "$1:").replace(/:\s*"((?:[^"\\]|\\.)*)"/g, (_, p1) => `: '${p1.replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`)}\n\nexport default APIs\n`;
|
|
831
|
-
mkdirSync(path.dirname(target), { recursive: true });
|
|
832
|
-
writeFileSync(target, contents, "utf8");
|
|
833
|
-
spinner.succeed("Generated Extended APIs to: " + target);
|
|
834
|
-
}
|
|
835
|
-
};
|
|
836
|
-
|
|
837
|
-
//#endregion
|
|
838
|
-
//#region src/Commands/GenerateApisCommand.ts
|
|
839
|
-
var GenerateApisCommand = class extends Command {
|
|
840
|
-
signature = "generate:apis";
|
|
841
|
-
description = "Generate extended API definitions from the GitHub OpenAPI spec";
|
|
842
|
-
async handle() {
|
|
843
|
-
const [_, setCommand] = useCommand();
|
|
844
|
-
setCommand(this);
|
|
845
|
-
ApisGenerator.run();
|
|
846
|
-
}
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
//#endregion
|
|
850
|
-
//#region src/Commands/InfoCommand.ts
|
|
851
|
-
var InfoCommand = class extends Command {
|
|
852
|
-
signature = "info";
|
|
853
|
-
description = "Display application runtime information.";
|
|
854
|
-
async handle() {
|
|
855
|
-
let pkg = {
|
|
856
|
-
version: "unknown",
|
|
857
|
-
dependencies: {}
|
|
858
|
-
};
|
|
859
|
-
const user = read("user");
|
|
860
|
-
const pkgPath = findCLIPackageJson();
|
|
861
|
-
const require = createRequire$1(import.meta.url);
|
|
862
|
-
const [_, setCommand] = useCommand();
|
|
863
|
-
const [dbPath$1] = useDbPath();
|
|
864
|
-
setCommand(this);
|
|
865
|
-
init();
|
|
866
|
-
const spinner = this.spinner("Gathering application information...\n").start();
|
|
867
|
-
if (pkgPath) try {
|
|
868
|
-
pkg = require(pkgPath);
|
|
869
|
-
} catch {}
|
|
870
|
-
wait(500, () => {
|
|
871
|
-
spinner.succeed("Application Information Loaded.\n");
|
|
872
|
-
const out = new Table();
|
|
873
|
-
out.push({ "App Version": pkg.version }, { "Platform": `${os.platform()} ${os.arch()} (${os.release()})` }, { "CPUs": os.cpus().length }, { "Host": `${os.userInfo().username}@${os.hostname()}` }, { "Memory": `${(os.freemem() / 1024 ** 3).toFixed(2)} GB / ${(os.totalmem() / 1024 ** 3).toFixed(2)} GB` }, { "Database Path": path$1.join(dbPath$1, "app.db") }, { "Github User": user ? `${user.login} (ID: ${user.id})` : "Not logged in" }, { "Default Repo": read("default_repo")?.full_name || "Not set" });
|
|
874
|
-
console.log(out.toString());
|
|
875
|
-
Logger.log("\nDependencies:", "yellow");
|
|
876
|
-
Logger.log(Object.keys(pkg.dependencies).map((dep) => `${dep}`).join(", "), "green");
|
|
877
|
-
this.newLine();
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
};
|
|
881
|
-
|
|
882
|
-
//#endregion
|
|
883
|
-
//#region src/Commands/InitCommand.ts
|
|
884
|
-
var InitCommand = class extends Command {
|
|
885
|
-
signature = "init";
|
|
886
|
-
description = "Initialize the application.";
|
|
887
|
-
async handle() {
|
|
888
|
-
const [_, setCommand] = useCommand();
|
|
889
|
-
setCommand(this);
|
|
890
|
-
init();
|
|
891
|
-
this.info("Application initialized successfully.").newLine();
|
|
892
|
-
}
|
|
893
|
-
};
|
|
894
|
-
|
|
895
|
-
//#endregion
|
|
896
|
-
//#region src/github/issues-seeder.ts
|
|
897
|
-
/**
|
|
898
|
-
* GitHub Issues Creator
|
|
899
|
-
*
|
|
900
|
-
* This script reads markdown issue files from the issues directory
|
|
901
|
-
* and creates them as GitHub issues using the GitHub API.
|
|
902
|
-
*/
|
|
903
|
-
var IssuesSeeder = class {
|
|
904
|
-
command;
|
|
905
|
-
constructor() {
|
|
906
|
-
const [command] = useCommand();
|
|
907
|
-
this.command = command();
|
|
908
|
-
}
|
|
909
|
-
/**
|
|
910
|
-
* Set filename in issue content
|
|
911
|
-
*
|
|
912
|
-
* @param content
|
|
913
|
-
* @param fileName
|
|
914
|
-
*/
|
|
915
|
-
setFilePath(content, filePath) {
|
|
916
|
-
if (!filePath) return content;
|
|
917
|
-
if (content.includes("<!-- grithub#filepath:")) content = content.replace(/<!--\s*grithub#filepath:\s*.+?\s*-->/i, `<!-- grithub#filepath: ${filePath} -->`);
|
|
918
|
-
else content = `<!-- grithub#filepath: ${filePath} -->\n\n` + content;
|
|
919
|
-
return content;
|
|
920
|
-
}
|
|
921
|
-
/**
|
|
922
|
-
* Get filename from issue content
|
|
923
|
-
*
|
|
924
|
-
* @param content
|
|
925
|
-
* @returns
|
|
926
|
-
*/
|
|
927
|
-
getFilePath(content) {
|
|
928
|
-
const match = content.match(/<!--\s*grithub#filepath:\s*(.+?)\s*-->/i);
|
|
929
|
-
if (match) return match[1].trim();
|
|
930
|
-
}
|
|
931
|
-
/**
|
|
932
|
-
* Parse frontmatter from markdown file
|
|
933
|
-
*
|
|
934
|
-
* @param content
|
|
935
|
-
* @returns
|
|
936
|
-
*/
|
|
937
|
-
parseFrontmatter(content) {
|
|
938
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
939
|
-
if (!match) return {
|
|
940
|
-
metadata: {},
|
|
941
|
-
body: content
|
|
942
|
-
};
|
|
943
|
-
const [, frontmatter, body] = match;
|
|
944
|
-
const metadata = {};
|
|
945
|
-
const lines = frontmatter.split("\n");
|
|
946
|
-
let currentKey = null;
|
|
947
|
-
for (const line of lines) {
|
|
948
|
-
const keyValueMatch = line.match(/^(\w+):\s*['"]?(.*?)['"]?$/);
|
|
949
|
-
if (keyValueMatch) {
|
|
950
|
-
const [, key, value] = keyValueMatch;
|
|
951
|
-
currentKey = key;
|
|
952
|
-
metadata[key] = value;
|
|
953
|
-
} else if (currentKey && line.trim()) metadata[currentKey] += "\n" + line.trim();
|
|
954
|
-
}
|
|
955
|
-
return {
|
|
956
|
-
metadata,
|
|
957
|
-
body: body.trim()
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Create a GitHub issue
|
|
962
|
-
*
|
|
963
|
-
* @param entry
|
|
964
|
-
* @param owner
|
|
965
|
-
* @param repo
|
|
966
|
-
* @returns
|
|
967
|
-
*/
|
|
968
|
-
async updateIssue(entry, issue, owner, repo) {
|
|
969
|
-
try {
|
|
970
|
-
const { data } = await useOctokit().issues.update({
|
|
971
|
-
repo,
|
|
972
|
-
owner,
|
|
973
|
-
issue_number: issue.number,
|
|
974
|
-
body: this.setFilePath(entry.body, entry.filePath),
|
|
975
|
-
title: entry.title,
|
|
976
|
-
labels: entry.labels || [],
|
|
977
|
-
assignees: entry.assignees || []
|
|
978
|
-
});
|
|
979
|
-
return data;
|
|
980
|
-
} catch (error) {
|
|
981
|
-
throw this.requestError(error, owner, repo);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
/**
|
|
985
|
-
* Create a GitHub issue
|
|
986
|
-
*
|
|
987
|
-
* @param entry
|
|
988
|
-
* @param owner
|
|
989
|
-
* @param repo
|
|
990
|
-
* @returns
|
|
991
|
-
*/
|
|
992
|
-
async createIssue(entry, owner, repo) {
|
|
993
|
-
try {
|
|
994
|
-
const { data } = await useOctokit().issues.create({
|
|
995
|
-
repo,
|
|
996
|
-
owner,
|
|
997
|
-
type: entry.type,
|
|
998
|
-
body: this.setFilePath(entry.body, entry.filePath),
|
|
999
|
-
title: entry.title,
|
|
1000
|
-
labels: entry.labels || [],
|
|
1001
|
-
assignees: entry.assignees || []
|
|
1002
|
-
});
|
|
1003
|
-
return data;
|
|
1004
|
-
} catch (error) {
|
|
1005
|
-
throw this.requestError(error, owner, repo);
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Read all issue files from a directory
|
|
1010
|
-
*/
|
|
1011
|
-
getIssueFiles(dir) {
|
|
1012
|
-
const files = [];
|
|
1013
|
-
const spinner = this.command.spinner("Reading issue files...").start();
|
|
1014
|
-
const traverse = (currentDir) => {
|
|
1015
|
-
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
1016
|
-
for (const entry of entries) {
|
|
1017
|
-
const fullPath = path$1.join(currentDir, entry.name);
|
|
1018
|
-
if (entry.isDirectory()) traverse(fullPath);
|
|
1019
|
-
else if (entry.isFile() && entry.name.endsWith(".md")) files.push(fullPath);
|
|
1020
|
-
}
|
|
1021
|
-
};
|
|
1022
|
-
traverse(dir);
|
|
1023
|
-
const sortedFiles = files.sort();
|
|
1024
|
-
spinner.succeed(`Found ${sortedFiles.length} issue files`);
|
|
1025
|
-
if (sortedFiles.length === 0) {
|
|
1026
|
-
spinner.info("No issue files found. Exiting.");
|
|
1027
|
-
process.exit(0);
|
|
1028
|
-
}
|
|
1029
|
-
return sortedFiles;
|
|
1030
|
-
}
|
|
1031
|
-
/**
|
|
1032
|
-
* Process a single issue file
|
|
1033
|
-
*
|
|
1034
|
-
* @param filePath
|
|
1035
|
-
* @returns
|
|
1036
|
-
*/
|
|
1037
|
-
processIssueFile(filePath) {
|
|
1038
|
-
const directory = join$1(process.cwd(), this.command.argument("directory", "issues"));
|
|
1039
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
1040
|
-
const { metadata, body } = this.parseFrontmatter(content);
|
|
1041
|
-
const relativePath = path$1.relative(directory, filePath);
|
|
1042
|
-
const fileName = path$1.basename(filePath, ".md");
|
|
1043
|
-
let labels = [];
|
|
1044
|
-
if (metadata.labels) labels = metadata.labels.split(",").map((l) => l.trim()).filter((l) => l);
|
|
1045
|
-
let assignees = [];
|
|
1046
|
-
if (metadata.assignees && metadata.assignees.trim()) assignees = metadata.assignees.split(",").map((a) => a.trim()).filter((a) => a);
|
|
1047
|
-
return {
|
|
1048
|
-
filePath: relativePath,
|
|
1049
|
-
title: metadata.title || metadata.name || fileName,
|
|
1050
|
-
type: metadata.type,
|
|
1051
|
-
body,
|
|
1052
|
-
labels,
|
|
1053
|
-
assignees,
|
|
1054
|
-
fileName
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
/**
|
|
1058
|
-
* Validate GitHub token and repository access
|
|
1059
|
-
*
|
|
1060
|
-
* @param owner
|
|
1061
|
-
* @param repo
|
|
1062
|
-
* @returns
|
|
1063
|
-
*/
|
|
1064
|
-
async validateAccess(owner, repo) {
|
|
1065
|
-
const spinner = this.command.spinner("Checking GitHub access...").start();
|
|
1066
|
-
try {
|
|
1067
|
-
return await useOctokit().repos.get({
|
|
1068
|
-
owner,
|
|
1069
|
-
repo
|
|
1070
|
-
});
|
|
1071
|
-
} catch (error) {
|
|
1072
|
-
spinner.stop();
|
|
1073
|
-
let message = "";
|
|
1074
|
-
if (error.status === 404) message = `ERROR: ${error.message}\n\nThis usually means:
|
|
11
|
+
`).run(e,t).lastInsertRowid}function j(e){return O().prepare(`DELETE FROM json_store WHERE key = ?`).run(e).lastInsertRowid}function M(e){let t=O();try{let n=t.prepare(`SELECT * FROM json_store WHERE key = ?`).get(e);if(n)try{return JSON.parse(n.value)}catch{return n.value}}catch{}return null}let N;function P(){return[()=>{if(!N)throw Error(`Commander instance has not been initialized`);return N},e=>{N=e}]}function F(){return[()=>M(`config`)||{debug:!1,apiBaseURL:`https://api.github.com`,timeoutDuration:3e3,skipLongCommandGeneration:!0},e=>(A(`config`,e),M(`config`))]}const I=new Set;function L(){return[()=>Array.from(I).filter(e=>!!e),e=>e?I.has(e)?!1:(I.add(e),!0):(I.clear(),!1)]}const R=()=>{let e=M(`token`);if(!e)throw Error(`No authentication token found. Please log in first.`);return new v({auth:e})},z=a(import.meta.url),B=o.dirname(z),V=e=>e.then(e=>[null,e]).catch(e=>[typeof e==`string`?e:e.message,null]);async function H(e,t,n){let r=R(),{data:i,message:a}=await Reflect.apply(r[e][t.api],r[e],[n]);return!i||Array.isArray(i)&&i.length<1||i instanceof Object&&Object.keys(i).length<1?{data:null,message:a??`Request was successful but returned no data.`,status:!1}:{data:i,message:a??`Request Completed`,status:!0}}const U=(e,t)=>new Promise(n=>{setTimeout(()=>{t&&n(t()),n()},e)}),W=(e,n=[`green`,`italic`],r)=>t.log(e,n,r??!1),G=e=>{t.log([[`Title:`,[`white`,`bold`]],[e.title,[`blue`]],[`
|
|
12
|
+
Type:`,[`white`,`bold`]],[typeof e.type==`string`?e.type:e.type?.name??`N/A`,[`blue`]],[`
|
|
13
|
+
Number:`,[`white`,`bold`]],[String(e.number),[`blue`]],[`
|
|
14
|
+
State:`,[`white`,`bold`]],[e.state,[`blue`]],[`
|
|
15
|
+
Labels:`,[`white`,`bold`]],[e.labels.map(e=>e.name??e).join(`, `),[`blue`]],[`
|
|
16
|
+
Assignees:`,[`white`,`bold`]],[e.assignees?.map(e=>e.login??e).join(`, `)||`N/A`,[`blue`]],[`
|
|
17
|
+
Created at:`,[`white`,`bold`]],[new Date(e.created_at).toLocaleString(),[`blue`]],[`
|
|
18
|
+
Updated at:`,[`white`,`bold`]],[new Date(e.updated_at).toLocaleString(),[`blue`]]],` `)},K=(e=B)=>{let t=e;for(;;){let e=o.join(t,`package.json`);if(n(e))return e;let r=o.dirname(t);if(r===t)break;t=r}return null},q=async e=>{let t=l.createInterface({input:process.stdin,output:process.stdout});await t.question(``),e(),t.close()};var se={issues:[{api:`create`,alias:void 0,endpoint:`/repos/{owner}/{repo}/issues`,description:`Create an issue`,params:[{parameter:`title`,required:!0,type:`String`,description:`The title of the issue`,paramType:`body`,flag:!0},{parameter:`body`,required:!1,type:`String`,description:`The contents of the issue`,paramType:`body`,flag:!0},{parameter:`owner`,required:!1,type:`String`,description:`The account owner of the repository`,paramType:`path`,arg:!0},{parameter:`repo`,required:!1,type:`String`,description:`The name of the repository`,paramType:`path`,arg:!0}]},{api:`listForRepo`,alias:`list`,endpoint:`/repos/{owner}/{repo}/issues`,description:`List repository issues`,params:[{parameter:`owner`,required:!1,type:`String`,description:`The account owner of the repository`,paramType:`path`},{parameter:`repo`,required:!1,type:`String`,description:`The name of the repository`,paramType:`path`},{parameter:`state`,required:!1,type:`String`,description:`Indicates the state of the issues to return. [open, closed]`,paramType:`query`}]},{api:`get`,alias:`get`,endpoint:`/repos/{owner}/{repo}/issues/{issue_number}`,description:`Get a single issue`,params:[{parameter:`issue_number`,required:!0,type:`Number`,description:`The number of the issue to get`,paramType:`path`},{parameter:`owner`,required:!1,type:`String`,description:`The account owner of the repository`,paramType:`path`},{parameter:`repo`,required:!1,type:`String`,description:`The name of the repository`,paramType:`path`}]}],orgs:[{api:`listForAuthenticatedUser`,alias:`list`,endpoint:`/user/orgs`,description:`List organizations for the authenticated user`,params:[{parameter:`page`,required:!1,type:`Number`,description:`Page number of the results to fetch`,paramType:`query`},{parameter:`per_page`,required:!1,type:`Number`,description:`Results per page (max 100)`,paramType:`query`}]}]};const ce=(e,t)=>{let[n,r]=L(),i=``;if((!e.required||e.default!==void 0||e.type===`Boolean`||e.options||e.flag===!0)&&e.paramType!==`path`&&e.arg!==!0){if(i+=`{--`,r(t+`:`+e.parameter.charAt(0).toLowerCase()))i+=`${e.parameter.charAt(0).toLowerCase()}|`;else{let n=e.parameter.split(/[_-\s]/);n.length>1&&r(t+`:`+n[1].charAt(0).toLowerCase())&&(i+=`${n[1].charAt(0).toLowerCase()}|`)}if(i+=`${e.parameter}`,e.type!==`Boolean`&&(i+=e.default?`=${e.default}`:`?`),e.description&&(i+=` : ${e.description}`),e.options){let t=e.options.join(`,`);i+=` : ${t}`}i+=`}`}else i+=`{${e.parameter}`,e.default&&(i+=`=${e.default}`),e.description&&(i+=` : ${e.description}`),i+=`}`;return i},le=e=>{let n=(e,r=0)=>{let i=` `.repeat(r);for(let a in e){let o=e[a];if(typeof o==`object`&&o)console.log(`${i}${J(a)}:`),n(o,r+2);else{let e;switch(typeof o){case`string`:e=t.log(o,`green`,!1);break;case`number`:e=t.log(String(o),`yellow`,!1);break;case`boolean`:e=t.log(String(o),`blue`,!1);break;case`object`:e=o===null?t.log(`null`,`gray`,!1):t.log(JSON.stringify(o),`cyan`,!1);break;default:e=o}console.log(`${i}${J(a)}: ${e}`)}}};n(e)},J=e=>e.replace(/([a-z])([A-Z])/g,`$1 $2`).replace(/[_-]+/g,` `).replace(/\s+/g,` `).split(` `).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `).trim().replace(/^(\w{2})$/,(e,t)=>t.toUpperCase()),ue=(e,t)=>ee(t,e).map(e=>{let[t,n]=e;return t===0?n:t===-1?W(n,[`red`,`strikethrough`],!1):W(n,[`green`,`underline`],!1)}).join(``);var de=()=>{let t=e(import.meta.url),r=[],i=se;!process.argv.includes(`generate:apis`)&&n(o.join(process.cwd(),`.grithub/apis.generated.js`))&&({APIs:i}=t(o.join(process.cwd(),`.grithub/apis.generated.js`)));let a=Object.entries(i).reduce((e,[t,n])=>(n.forEach(n=>{let r=t===n.api?t:`${t}:${(n.alias??n.api).toKebabCase()}`;e[r]=n}),e),{});for(let[e,t]of Object.entries(a)){let n=t.params.map(t=>ce(t,e)).join(`
|
|
19
|
+
`),i=class extends y{signature=`${e} \n${n}`;description=t.description||`No description available.`;handle=async()=>{let n=e.split(`:`).shift(),r={...this.arguments()??{},...this.options()??{}},[i,a]=P();if(a(this),!n)return void this.error(`Unknown command entry.`).newLine();for(let e of t.params)if(e.required&&!this.argument(e.parameter))return void this.newLine().error(`Missing required argument: ${e.parameter}`).newLine();let o=M(`default_repo`),s=M(`token`),c=([r.owner,r.repo].filter(Boolean).join(`/`)||o.full_name).split(`/`)??[``,``],l=t.params.some(e=>[`repo`,`user`].includes(e.parameter));if(l&&(!c[0]||!c[1]))return void this.error(`ERROR: No repository set. Please set a default repository using the [set-repo] command or provide one using the --repo option.`).newLine();if(!s)return void this.error(`ERROR: You're not signed in, please run the [login] command before you begin`).newLine();this.newLine();let u=this.spinner(`Loading...
|
|
20
|
+
`).start();l&&(r.owner=c[0],r.repo=c[1]);let[d,f]=await V(H(n,t,r));if(d||!f)return void u.fail((d||`An error occurred`)+`
|
|
21
|
+
`);u.succeed(f.message),this.newLine(),le(f.data),this.newLine()}};r.push(i)}return r};const fe=e=>[{name:`Debug Mode`,value:`debug`,description:`Enable or disable debug mode (${e.debug?`Enabled`:`Disabled`})`},{name:`API Base URL`,value:`apiBaseURL`,description:`Set the base URL for the API (${e.apiBaseURL})`},{name:`Timeout Duration`,value:`timeoutDuration`,description:`Set the timeout duration for API requests (${e.timeoutDuration} ms)`},{name:`Skip Long Command Generation`,value:`skipLongCommandGeneration`,description:`Enable or disable skipping of long command generation when calling ${W(`generate:apis`,[`grey`,`italic`])} (${e.skipLongCommandGeneration?`Enabled`:`Disabled`})`},{name:`Ngrok Auth Token`,value:`ngrokAuthToken`,description:`Set the Ngrok Auth Token - will default to environment variable if not set (${e.ngrokAuthToken?`************`:`Not Set`})`},{name:`Reset Configuration`,value:`reset`,description:`Reset all configurations to default values`}],pe=async e=>{let[t,n]=F(),[r]=P(),i=t();if(e===`debug`){let e=await r().confirm(`${i.debug?`Dis`:`En`}able debug mode?`,i.debug===!0);i.debug=i.debug!==e}else if(e===`apiBaseURL`)i.apiBaseURL=await r().ask(`Enter API Base URL`,i.apiBaseURL);else if(e===`ngrokAuthToken`)i.ngrokAuthToken=await r().ask(`Enter Ngrok Auth Token`,i.ngrokAuthToken||``);else if(e===`timeoutDuration`){let e=await r().ask(`Enter Timeout Duration (in ms)`,i.timeoutDuration.toString());i.timeoutDuration=parseInt(e)}else e===`skipLongCommandGeneration`?i.skipLongCommandGeneration=await r().confirm(`${i.skipLongCommandGeneration?`Dis`:`En`}able skipping of long command generation?`,i.skipLongCommandGeneration===!0):e===`reset`&&(i={debug:!1,apiBaseURL:`https://api.github.com`,timeoutDuration:3e3,skipLongCommandGeneration:!0});n(i)};var me=class extends y{signature=`config`;description=`Configure Grithub`;async handle(){let[e,t]=P();t(this);let[n,r]=F(),i=n();i||(i={debug:!1,apiBaseURL:`https://api.github.com`,timeoutDuration:3e3,skipLongCommandGeneration:!0},r(i)),await pe(await this.choice(`Select configuration to set`,fe(i))),this.info(`Configuration updated successfully!`).newLine()}},he=class e{spec;config;openapi;skipApis=new Set([`issues:list`,`issues:update`,`issues:seed`,`issues:delete`]);skipParams=new Set([`s`]);PARAM_LOCATIONS=new Set([`path`,`query`,`header`]);constructor(e,t=`api.github.com.deref`){let[n]=F();if(this.openapi=e,this.spec=this.openapi.schemas[t],this.config=n(),!this.spec||!this.spec.paths)throw Error(`Could not find ${t} schema`)}static async installOctokitOpenapi(){let e=P()[0]().spinner(`Installing @octokit/openapi...`).start(),t=s(a(import.meta.url));return await te(`@octokit/openapi`,{cwd:o.normalize(o.join(t,`../..`)),silent:!0,dev:!0}),e.succeed(`@octokit/openapi installed successfully.`),(await import(`@octokit/openapi`)).default}skipParam(e){return this.skipParams.has(e)||e.length>20||e.length<=2}skipApi(e,t){let n=(t?t+`:`:``)+e.toCamelCase();return this.skipApis.has(n)||this.skipApis.has(e.toCamelCase())||n.length>(this.config.skipLongCommandGeneration?23:1/0)}normalizeType(e){let t={integer:`Number`,number:`Number`,string:`String`,boolean:`Boolean`,array:`Array`,object:`Object`,enum:`String`,oneOf:`String`,anyOf:`String`,allOf:`String`},n=t[e?.type]||`any`;return Array.isArray(e?.type)&&(n=e.type.map(e=>t[e]||`any`).join(`|`)),n===`any`?(e||(n=`any`),Array.isArray(e.type)?e.type.join(`|`):(e.type&&(n=e.type),e.enum&&(n=`enum`),e.oneOf&&(n=`oneOf`),e.anyOf&&(n=`anyOf`),e.allOf&&(n=`allOf`),t[n]||`any`)):n}gatherParams(e){let t=[];for(let n of e.parameters??[]){let e=this.PARAM_LOCATIONS.has(n.in)?n.in:`query`;this.skipParam(n.name)||t.push({parameter:n.name,required:!!n.required,type:this.normalizeType(n.schema).toPascalCase(),description:n.description,paramType:e})}let n=e.requestBody?.content?.[`application/json`],r=n?.schema,i=r?.properties??{},a=r?.required??[];for(let[e,r]of Object.entries(i))this.skipParam(e)||t.push({parameter:e,required:a.includes(e)||!!n?.required,type:this.normalizeType(r).toPascalCase(),description:r.description,paramType:`body`});return t}buildTree(){let e={};for(let[t,n]of Object.entries(this.spec.paths))for(let[r,i]of Object.entries(n??{})){let n=i,r=n?.operationId;if(!r)continue;let[a,o]=r.split(`/`);if(!a||!o||this.skipApi(o,a))continue;let s=this.gatherParams(n);e[a.toCamelCase()]||(e[a.toCamelCase()]=[]),e[a.toCamelCase()].push({api:o.toCamelCase(),endpoint:t,description:n.summary??n.description??void 0,alias:n[`x-github`]?.alias??n[`x-octokit`]?.alias??void 0,params:s})}return e}static async run(){let[t]=P(),n=t(),a,s=n.spinner(`Checking if @octokit/openapi Installed...`).start();try{({default:a}=await import(`@octokit/openapi`)),s.succeed(`@octokit/openapi is already installed.`)}catch{s.fail(`@octokit/openapi is not installed.`),a=await e.installOctokitOpenapi()}s.start(`Generating Extended APIs...`);let c=new e(a,`api.github.com.deref`).buildTree(),l=o.join(process.cwd(),`.grithub/apis.generated.js`),u=`// Auto-generated from @octokit/openapi. Do not edit directly.
|
|
22
|
+
|
|
23
|
+
export const APIs = ${JSON.stringify(c,null,2).replace(/"([A-Za-z_][\w$]*)":/g,`$1:`).replace(/:\s*"((?:[^"\\]|\\.)*)"/g,(e,t)=>`: '${t.replace(/\\"/g,`"`).replace(/'/g,`\\'`)}'`)}\n\nexport default APIs\n`;r(o.dirname(l),{recursive:!0}),i(l,u,`utf8`),s.succeed(`Generated Extended APIs to: `+l)}},ge=class extends y{signature=`generate:apis`;description=`Generate extended API definitions from the GitHub OpenAPI spec`;async handle(){let[e,t]=P();t(this),he.run()}},_e=class extends y{signature=`info`;description=`Display application runtime information.`;async handle(){let e={version:`unknown`,dependencies:{}},n=M(`user`),r=K(),i=re(import.meta.url),[a,o]=P(),[s]=D();o(this),k();let c=this.spinner(`Gathering application information...
|
|
24
|
+
`).start();if(r)try{e=i(r)}catch{}U(500,()=>{c.succeed(`Application Information Loaded.
|
|
25
|
+
`);let r=new ne;r.push({"App Version":e.version},{Platform:`${d.platform()} ${d.arch()} (${d.release()})`},{CPUs:d.cpus().length},{Host:`${d.userInfo().username}@${d.hostname()}`},{Memory:`${(d.freemem()/1024**3).toFixed(2)} GB / ${(d.totalmem()/1024**3).toFixed(2)} GB`},{"Database Path":g.join(s,`app.db`)},{"Github User":n?`${n.login} (ID: ${n.id})`:`Not logged in`},{"Default Repo":M(`default_repo`)?.full_name||`Not set`}),console.log(r.toString()),t.log(`
|
|
26
|
+
Dependencies:`,`yellow`),t.log(Object.keys(e.dependencies).map(e=>`${e}`).join(`, `),`green`),this.newLine()})}},ve=class extends y{signature=`init`;description=`Initialize the application.`;async handle(){let[e,t]=P();t(this),k(),this.info(`Application initialized successfully.`).newLine()}},Y=class{command;constructor(){let[e]=P();this.command=e()}setFilePath(e,t){return t&&(e=e.includes(`<!-- grithub#filepath:`)?e.replace(/<!--\s*grithub#filepath:\s*.+?\s*-->/i,`<!-- grithub#filepath: ${t} -->`):`<!-- grithub#filepath: ${t} -->\n\n`+e),e}getFilePath(e){let t=e.match(/<!--\s*grithub#filepath:\s*(.+?)\s*-->/i);if(t)return t[1].trim()}parseFrontmatter(e){let t=e.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!t)return{metadata:{},body:e};let[,n,r]=t,i={},a=n.split(`
|
|
27
|
+
`),o=null;for(let e of a){let t=e.match(/^(\w+):\s*['"]?(.*?)['"]?$/);if(t){let[,e,n]=t;o=e,i[e]=n}else o&&e.trim()&&(i[o]+=`
|
|
28
|
+
`+e.trim())}return{metadata:i,body:r.trim()}}async updateIssue(e,t,n,r){try{let{data:i}=await R().issues.update({repo:r,owner:n,issue_number:t.number,body:this.setFilePath(e.body,e.filePath),title:e.title,labels:e.labels||[],assignees:e.assignees||[]});return i}catch(e){throw this.requestError(e,n,r)}}async createIssue(e,t,n){try{let{data:r}=await R().issues.create({repo:n,owner:t,type:e.type,body:this.setFilePath(e.body,e.filePath),title:e.title,labels:e.labels||[],assignees:e.assignees||[]});return r}catch(e){throw this.requestError(e,t,n)}}getIssueFiles(e){let t=[],n=this.command.spinner(`Reading issue files...`).start(),r=e=>{let n=m.readdirSync(e,{withFileTypes:!0});for(let i of n){let n=g.join(e,i.name);i.isDirectory()?r(n):i.isFile()&&i.name.endsWith(`.md`)&&t.push(n)}};r(e);let i=t.sort();return n.succeed(`Found ${i.length} issue files`),i.length===0&&(n.info(`No issue files found. Exiting.`),process.exit(0)),i}processIssueFile(e){let t=_(process.cwd(),this.command.argument(`directory`,`issues`)),n=m.readFileSync(e,`utf-8`),{metadata:r,body:i}=this.parseFrontmatter(n),a=g.relative(t,e),o=g.basename(e,`.md`),s=[];r.labels&&(s=r.labels.split(`,`).map(e=>e.trim()).filter(e=>e));let c=[];return r.assignees&&r.assignees.trim()&&(c=r.assignees.split(`,`).map(e=>e.trim()).filter(e=>e)),{filePath:a,title:r.title||r.name||o,type:r.type,body:i,labels:s,assignees:c,fileName:o}}async validateAccess(e,t){let n=this.command.spinner(`Checking GitHub access...`).start();try{return await R().repos.get({owner:e,repo:t})}catch(e){n.stop();let t=``;throw t=e.status===404?`ERROR: ${e.message}\n\nThis usually means:
|
|
1075
29
|
1. No internet connection
|
|
1076
30
|
2. DNS server issues
|
|
1077
31
|
3. Firewall/proxy blocking DNS
|
|
@@ -1082,25 +36,7 @@ Troubleshooting:
|
|
|
1082
36
|
- If behind a corporate firewall, check proxy settings
|
|
1083
37
|
- Try using a different DNS (e.g., 8.8.8.8)
|
|
1084
38
|
|
|
1085
|
-
Original error: ${
|
|
1086
|
-
else message = `ERROR: GitHub access validation failed: ${error.message}`;
|
|
1087
|
-
throw new Error(message);
|
|
1088
|
-
} finally {
|
|
1089
|
-
spinner.succeed("GitHub access validated successfully.");
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
/**
|
|
1093
|
-
* Check network connectivity to GitHub
|
|
1094
|
-
*/
|
|
1095
|
-
async checkConnectivity() {
|
|
1096
|
-
const spinner = this.command.spinner("Checking network connectivity...").start();
|
|
1097
|
-
try {
|
|
1098
|
-
const addresses = await dns.resolve("api.github.com");
|
|
1099
|
-
spinner.succeed(`DNS resolution successful: ${Logger.log(addresses[0], "blue", !1)}`);
|
|
1100
|
-
return addresses;
|
|
1101
|
-
} catch (error) {
|
|
1102
|
-
spinner.stop();
|
|
1103
|
-
throw new Error(`ERROR: Cannot resolve api.github.com
|
|
39
|
+
Original error: ${e.message}`:`ERROR: GitHub access validation failed: ${e.message}`,Error(t)}finally{n.succeed(`GitHub access validated successfully.`)}}async checkConnectivity(){let e=this.command.spinner(`Checking network connectivity...`).start();try{let n=await ie.resolve(`api.github.com`);return e.succeed(`DNS resolution successful: ${t.log(n[0],`blue`,!1)}`),n}catch(t){throw e.stop(),Error(`ERROR: Cannot resolve api.github.com
|
|
1104
40
|
|
|
1105
41
|
This usually means:
|
|
1106
42
|
1. No internet connection
|
|
@@ -1113,91 +49,19 @@ Troubleshooting:
|
|
|
1113
49
|
- If behind a corporate firewall, check proxy settings
|
|
1114
50
|
- Try using a different DNS (e.g., 8.8.8.8)
|
|
1115
51
|
|
|
1116
|
-
Original error: ${
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
/**
|
|
1120
|
-
* Fetch all open issues from the repository
|
|
1121
|
-
*
|
|
1122
|
-
* @param owner
|
|
1123
|
-
* @param repo
|
|
1124
|
-
* @param state
|
|
1125
|
-
* @returns
|
|
1126
|
-
*/
|
|
1127
|
-
async fetchExistingIssues(owner, repo, state) {
|
|
1128
|
-
const issues = [];
|
|
1129
|
-
let page = 1;
|
|
1130
|
-
let hasMore = true;
|
|
1131
|
-
const spinner = this.command.spinner("Fetching existing open issues...").start();
|
|
1132
|
-
while (hasMore) try {
|
|
1133
|
-
const { data } = await useOctokit().issues.listForRepo({
|
|
1134
|
-
owner,
|
|
1135
|
-
repo,
|
|
1136
|
-
state: state || "open",
|
|
1137
|
-
per_page: 100,
|
|
1138
|
-
page
|
|
1139
|
-
});
|
|
1140
|
-
issues.push(...data.filter((issue) => !issue.pull_request));
|
|
1141
|
-
spinner.stop();
|
|
1142
|
-
hasMore = issues.length % 100 === 0 && data.length === 100;
|
|
1143
|
-
if (hasMore) page++;
|
|
1144
|
-
else hasMore = false;
|
|
1145
|
-
} catch (error) {
|
|
1146
|
-
hasMore = false;
|
|
1147
|
-
spinner.stop();
|
|
1148
|
-
this.command.warn(`ERROR: Failed to fetch existing issues: ${error.message}`);
|
|
1149
|
-
this.command.warn("INFO: Proceeding without duplicate check...");
|
|
1150
|
-
}
|
|
1151
|
-
spinner.succeed(`Found ${issues.length} existing issues.`);
|
|
1152
|
-
return issues;
|
|
1153
|
-
}
|
|
1154
|
-
/**
|
|
1155
|
-
* Handle GitHub API request errors
|
|
1156
|
-
*
|
|
1157
|
-
* @param error
|
|
1158
|
-
* @param owner
|
|
1159
|
-
* @param repo
|
|
1160
|
-
* @returns
|
|
1161
|
-
*/
|
|
1162
|
-
requestError(error, owner, repo) {
|
|
1163
|
-
let errorMsg = error.message || "GitHub API error";
|
|
1164
|
-
if (error.status === 401) {
|
|
1165
|
-
errorMsg += "\n\nThis is an authentication error. Check that:";
|
|
1166
|
-
errorMsg += `\n 1. You are logged in (make sure to run the ${Logger.log("login", ["grey", "italic"], !1)}`;
|
|
1167
|
-
errorMsg += "command first)";
|
|
1168
|
-
errorMsg += "\n 2. The app token has \"repo\" scope";
|
|
1169
|
-
errorMsg += "\n 3. The app token hasn't expired";
|
|
1170
|
-
} else if (error.status === 404) {
|
|
1171
|
-
errorMsg += "\n\nRepository not found. Check that:";
|
|
1172
|
-
if (owner) errorMsg += `\n 1. ${Logger.log(owner, ["blue", "bold"], !1)} is a valid gitHub username or organization`;
|
|
1173
|
-
if (repo) errorMsg += `\n 2. ${Logger.log(repo, ["blue", "bold"], !1)} is the correct repository name`;
|
|
1174
|
-
errorMsg += "\n 3. You have access to this repository";
|
|
1175
|
-
} else if (error.status === 422) {
|
|
1176
|
-
errorMsg += "\n\nValidation failed. This usually means:";
|
|
1177
|
-
errorMsg += "\n 1. Issue data format is invalid";
|
|
1178
|
-
errorMsg += "\n 2. Labels don't exist in the repository";
|
|
1179
|
-
errorMsg += "\n 3. Assignees don't have access to the repository";
|
|
1180
|
-
}
|
|
1181
|
-
return new Error(errorMsg);
|
|
1182
|
-
}
|
|
1183
|
-
};
|
|
52
|
+
Original error: ${t.message}`)}}async fetchExistingIssues(e,t,n){let r=[],i=1,a=!0,o=this.command.spinner(`Fetching existing open issues...`).start();for(;a;)try{let{data:s}=await R().issues.listForRepo({owner:e,repo:t,state:n||`open`,per_page:100,page:i});r.push(...s.filter(e=>!e.pull_request)),o.stop(),a=r.length%100==0&&s.length===100,a?i++:a=!1}catch(e){a=!1,o.stop(),this.command.warn(`ERROR: Failed to fetch existing issues: ${e.message}`),this.command.warn(`INFO: Proceeding without duplicate check...`)}return o.succeed(`Found ${r.length} existing issues.`),r}requestError(e,n,r){let i=e.message||`GitHub API error`;return e.status===401?(i+=`
|
|
1184
53
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
*/
|
|
1197
|
-
const deleteIssue = async (owner, repo, issue_number, node_id) => {
|
|
1198
|
-
const octokit = useOctokit();
|
|
1199
|
-
let issueId = node_id;
|
|
1200
|
-
if (!issueId) ({repository: {issue: {id: issueId}}} = await octokit.graphql(`
|
|
54
|
+
This is an authentication error. Check that:`,i+=`\n 1. You are logged in (make sure to run the ${t.log(`login`,[`grey`,`italic`],!1)}`,i+=`command first)`,i+=`
|
|
55
|
+
2. The app token has "repo" scope`,i+=`
|
|
56
|
+
3. The app token hasn't expired`):e.status===404?(i+=`
|
|
57
|
+
|
|
58
|
+
Repository not found. Check that:`,n&&(i+=`\n 1. ${t.log(n,[`blue`,`bold`],!1)} is a valid gitHub username or organization`),r&&(i+=`\n 2. ${t.log(r,[`blue`,`bold`],!1)} is the correct repository name`),i+=`
|
|
59
|
+
3. You have access to this repository`):e.status===422&&(i+=`
|
|
60
|
+
|
|
61
|
+
Validation failed. This usually means:`,i+=`
|
|
62
|
+
1. Issue data format is invalid`,i+=`
|
|
63
|
+
2. Labels don't exist in the repository`,i+=`
|
|
64
|
+
3. Assignees don't have access to the repository`),Error(i)}};const X=async(e,t,n,r)=>{let i=R(),a=r;a||({repository:{issue:{id:a}}}=await i.graphql(`
|
|
1201
65
|
query ($owner: String!, $repo: String!, $issue_number: Int!) {
|
|
1202
66
|
repository(owner: $owner, name: $repo) {
|
|
1203
67
|
issue(number: $issue_number) {
|
|
@@ -1205,756 +69,33 @@ const deleteIssue = async (owner, repo, issue_number, node_id) => {
|
|
|
1205
69
|
}
|
|
1206
70
|
}
|
|
1207
71
|
}
|
|
1208
|
-
`,
|
|
1209
|
-
owner,
|
|
1210
|
-
repo,
|
|
1211
|
-
issue_number
|
|
1212
|
-
}));
|
|
1213
|
-
await octokit.graphql(`
|
|
72
|
+
`,{owner:e,repo:t,issue_number:n})),await i.graphql(`
|
|
1214
73
|
mutation ($issueId: ID!) {
|
|
1215
74
|
deleteIssue(input: {issueId: $issueId}) {
|
|
1216
75
|
clientMutationId
|
|
1217
76
|
}
|
|
1218
77
|
}
|
|
1219
|
-
`,
|
|
1220
|
-
};
|
|
1221
|
-
|
|
1222
|
-
//#endregion
|
|
1223
|
-
//#region src/Commands/IssuesCommand.ts
|
|
1224
|
-
var IssuesCommand = class extends Command {
|
|
1225
|
-
signature = `issues
|
|
78
|
+
`,{issueId:a})};var ye=class extends y{signature=`issues
|
|
1226
79
|
{ repo? : The full name of the repository (e.g., username/repo)}
|
|
1227
|
-
`;
|
|
1228
|
-
description = "Manage issues in the default repository.";
|
|
1229
|
-
async handle() {
|
|
1230
|
-
const [_, setCommand] = useCommand();
|
|
1231
|
-
setCommand(this);
|
|
1232
|
-
const repo = read("default_repo");
|
|
1233
|
-
const repository = this.argument("repo", repo.full_name).split("/") ?? ["", ""];
|
|
1234
|
-
const spinner = this.spinner("Fetching issues...").start();
|
|
1235
|
-
try {
|
|
1236
|
-
let page = 1;
|
|
1237
|
-
const issues = [];
|
|
1238
|
-
do {
|
|
1239
|
-
const newIssues = await this.loadIssues(repository, page);
|
|
1240
|
-
issues.push(...newIssues);
|
|
1241
|
-
spinner.succeed(`${issues.length} issues fetched successfully.`);
|
|
1242
|
-
const choice = await this.choice("Select Issue", issues.map((issue) => ({
|
|
1243
|
-
name: `#${issue.number}: ${issue.state === "open" ? "🟢" : "🔴"} ${issue.title}`,
|
|
1244
|
-
value: String(issue.number)
|
|
1245
|
-
})).concat(issues.length === 20 ? [{
|
|
1246
|
-
name: "Load more issues",
|
|
1247
|
-
value: ">>"
|
|
1248
|
-
}] : []), 0);
|
|
1249
|
-
if (choice === ">>") page++;
|
|
1250
|
-
else {
|
|
1251
|
-
const issue = issues.find((issue$1) => String(issue$1.number) === choice);
|
|
1252
|
-
this.info(`#${issue.number}: ${issue.title}`).newLine();
|
|
1253
|
-
const action = await this.choice("Choose Action", [
|
|
1254
|
-
{
|
|
1255
|
-
name: "View Details",
|
|
1256
|
-
value: "view"
|
|
1257
|
-
},
|
|
1258
|
-
!issue.closed_at ? {
|
|
1259
|
-
name: "Close Issue",
|
|
1260
|
-
value: "close"
|
|
1261
|
-
} : null,
|
|
1262
|
-
issue.closed_at ? {
|
|
1263
|
-
name: "Reopen Issue",
|
|
1264
|
-
value: "reopen"
|
|
1265
|
-
} : null,
|
|
1266
|
-
{
|
|
1267
|
-
name: "Edit Issue",
|
|
1268
|
-
value: "edit"
|
|
1269
|
-
},
|
|
1270
|
-
{
|
|
1271
|
-
name: logger("Delete Issue", ["red", "italic"]),
|
|
1272
|
-
value: "delete"
|
|
1273
|
-
},
|
|
1274
|
-
{
|
|
1275
|
-
name: "Exit",
|
|
1276
|
-
value: "exit"
|
|
1277
|
-
}
|
|
1278
|
-
].filter((e) => !!e), 0);
|
|
1279
|
-
if (action === "view") {
|
|
1280
|
-
viewIssue(issue);
|
|
1281
|
-
this.newLine();
|
|
1282
|
-
} else if (action === "close") if (issue.state === "closed") this.warn("Issue is already closed.").newLine();
|
|
1283
|
-
else {
|
|
1284
|
-
spinner.start(`Closing issue #${issue.number}...`);
|
|
1285
|
-
await useOctokit().issues.update({
|
|
1286
|
-
owner: repository[0],
|
|
1287
|
-
repo: repository[1],
|
|
1288
|
-
issue_number: issue.number,
|
|
1289
|
-
state: "closed"
|
|
1290
|
-
});
|
|
1291
|
-
spinner.succeed(`Issue #${issue.number} closed successfully.`);
|
|
1292
|
-
}
|
|
1293
|
-
else if (action === "reopen") if (issue.state === "open") this.warn("Issue is already open.").newLine();
|
|
1294
|
-
else {
|
|
1295
|
-
spinner.start(`Reopening issue #${issue.number}...`);
|
|
1296
|
-
await useOctokit().issues.update({
|
|
1297
|
-
owner: repository[0],
|
|
1298
|
-
repo: repository[1],
|
|
1299
|
-
issue_number: issue.number,
|
|
1300
|
-
state: "open"
|
|
1301
|
-
});
|
|
1302
|
-
spinner.succeed(`Issue #${issue.number} reopened successfully.`);
|
|
1303
|
-
}
|
|
1304
|
-
else if (action === "edit") {
|
|
1305
|
-
const whatToEdit = await this.choice("What do you want to edit?", [{
|
|
1306
|
-
name: "Title",
|
|
1307
|
-
value: "title"
|
|
1308
|
-
}, {
|
|
1309
|
-
name: "Body",
|
|
1310
|
-
value: "body"
|
|
1311
|
-
}], 0);
|
|
1312
|
-
if (whatToEdit === "exit") return;
|
|
1313
|
-
const updates = {};
|
|
1314
|
-
if (whatToEdit === "title") updates.title = await this.ask("Enter new title:", issue.title);
|
|
1315
|
-
else if (whatToEdit === "body") updates.body = await this.editor("Edit issue body:", ".md", issue.body ?? "");
|
|
1316
|
-
if (Object.keys(updates).length > 0) {
|
|
1317
|
-
const seeder = new IssuesSeeder();
|
|
1318
|
-
spinner.start(`Updating issue #${issue.number}...`);
|
|
1319
|
-
await seeder.updateIssue(Object.assign({
|
|
1320
|
-
labels: issue.labels,
|
|
1321
|
-
assignees: issue.assignees
|
|
1322
|
-
}, updates), issue, ...repository);
|
|
1323
|
-
spinner.succeed(`Issue #${issue.number} updated successfully.`);
|
|
1324
|
-
} else this.info("No changes made to the issue.").newLine();
|
|
1325
|
-
} else if (action === "delete") {
|
|
1326
|
-
spinner.start(`Deleting issue #${issue.number}...`);
|
|
1327
|
-
await deleteIssue(repository[0], repository[1], issue.number, issue.node_id);
|
|
1328
|
-
spinner.succeed(`Issue #${issue.number} deleted successfully.`);
|
|
1329
|
-
} else if (action === "exit") return;
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
} while (issues.length === 20);
|
|
1333
|
-
} catch (error) {
|
|
1334
|
-
spinner.stop();
|
|
1335
|
-
this.error(error.message);
|
|
1336
|
-
return;
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
async loadIssues(repository, page = 1) {
|
|
1340
|
-
let issues = [];
|
|
1341
|
-
({data: issues} = await useOctokit().issues.listForRepo({
|
|
1342
|
-
page,
|
|
1343
|
-
repo: repository[1],
|
|
1344
|
-
owner: repository[0],
|
|
1345
|
-
per_page: 20,
|
|
1346
|
-
state: "all"
|
|
1347
|
-
}));
|
|
1348
|
-
return issues.filter((issue) => !issue.pull_request);
|
|
1349
|
-
}
|
|
1350
|
-
};
|
|
1351
|
-
|
|
1352
|
-
//#endregion
|
|
1353
|
-
//#region src/Commands/IssuesDeleteCommand.ts
|
|
1354
|
-
var IssuesDeleteCommand = class extends Command {
|
|
1355
|
-
signature = `issues:delete
|
|
80
|
+
`;description=`Manage issues in the default repository.`;async handle(){let[e,t]=P();t(this);let n=M(`default_repo`),r=this.argument(`repo`,n.full_name).split(`/`)??[``,``],i=this.spinner(`Fetching issues...`).start();try{let e=1,t=[];do{let n=await this.loadIssues(r,e);t.push(...n),i.succeed(`${t.length} issues fetched successfully.`);let a=await this.choice(`Select Issue`,t.map(e=>({name:`#${e.number}: ${e.state===`open`?`🟢`:`🔴`} ${e.title}`,value:String(e.number)})).concat(t.length===20?[{name:`Load more issues`,value:`>>`}]:[]),0);if(a===`>>`)e++;else{let e=t.find(e=>String(e.number)===a);this.info(`#${e.number}: ${e.title}`).newLine();let n=await this.choice(`Choose Action`,[{name:`View Details`,value:`view`},e.closed_at?null:{name:`Close Issue`,value:`close`},e.closed_at?{name:`Reopen Issue`,value:`reopen`}:null,{name:`Edit Issue`,value:`edit`},{name:W(`Delete Issue`,[`red`,`italic`]),value:`delete`},{name:`Exit`,value:`exit`}].filter(e=>!!e),0);if(n===`view`)G(e),this.newLine();else if(n===`close`)e.state===`closed`?this.warn(`Issue is already closed.`).newLine():(i.start(`Closing issue #${e.number}...`),await R().issues.update({owner:r[0],repo:r[1],issue_number:e.number,state:`closed`}),i.succeed(`Issue #${e.number} closed successfully.`));else if(n===`reopen`)e.state===`open`?this.warn(`Issue is already open.`).newLine():(i.start(`Reopening issue #${e.number}...`),await R().issues.update({owner:r[0],repo:r[1],issue_number:e.number,state:`open`}),i.succeed(`Issue #${e.number} reopened successfully.`));else if(n===`edit`){let t=await this.choice(`What do you want to edit?`,[{name:`Title`,value:`title`},{name:`Body`,value:`body`}],0);if(t===`exit`)return;let n={};if(t===`title`?n.title=await this.ask(`Enter new title:`,e.title):t===`body`&&(n.body=await this.editor(`Edit issue body:`,`.md`,e.body??``)),Object.keys(n).length>0){let t=new Y;i.start(`Updating issue #${e.number}...`),await t.updateIssue(Object.assign({labels:e.labels,assignees:e.assignees},n),e,...r),i.succeed(`Issue #${e.number} updated successfully.`)}else this.info(`No changes made to the issue.`).newLine()}else if(n===`delete`)i.start(`Deleting issue #${e.number}...`),await X(r[0],r[1],e.number,e.node_id),i.succeed(`Issue #${e.number} deleted successfully.`);else if(n===`exit`)return;return}}while(t.length===20)}catch(e){i.stop(),this.error(e.message);return}}async loadIssues(e,t=1){let n=[];return{data:n}=await R().issues.listForRepo({page:t,repo:e[1],owner:e[0],per_page:20,state:`all`}),n.filter(e=>!e.pull_request)}},be=class extends y{signature=`issues:delete
|
|
1356
81
|
{ repo? : The full name of the repository (e.g., username/repo)}
|
|
1357
82
|
{--dry-run : Simulate the deletion without actually deleting issues.}
|
|
1358
|
-
`;
|
|
1359
|
-
description = "Delete issues from the specified repository.";
|
|
1360
|
-
async handle() {
|
|
1361
|
-
const [_, setCommand] = useCommand();
|
|
1362
|
-
setCommand(this);
|
|
1363
|
-
const repo = read("default_repo");
|
|
1364
|
-
const repository = this.argument("repo", repo.full_name).split("/") ?? ["", ""];
|
|
1365
|
-
const spinner = this.spinner("Fetching issues...").start();
|
|
1366
|
-
const isDryRun = this.option("dryRun", false);
|
|
1367
|
-
try {
|
|
1368
|
-
const issues = await this.loadIssues(repository);
|
|
1369
|
-
spinner.succeed(`${issues.length} issues fetched successfully.`);
|
|
1370
|
-
const choices = await this.checkbox(`Select Issue${isDryRun ? " (Dry Run)" : ""}`, issues.map((issue) => ({
|
|
1371
|
-
name: `#${issue.number}: ${issue.state === "open" ? "🟢" : "🔴"} ${issue.title}`,
|
|
1372
|
-
value: String(issue.number)
|
|
1373
|
-
})), true, void 0, 20);
|
|
1374
|
-
if (!await this.confirm(`Are you sure you want to delete the selected ${choices.length} issue(s)? ${isDryRun ? "(Dry Run - No changes will be made)" : "This action cannot be undone"}.`)) {
|
|
1375
|
-
this.info("Operation cancelled.");
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1378
|
-
for (const issue of issues.filter((issue$1) => choices.includes(String(issue$1.number)))) {
|
|
1379
|
-
spinner.start(`Deleting issue #${issue.number}...`);
|
|
1380
|
-
if (!isDryRun) {
|
|
1381
|
-
await deleteIssue(repository[0], repository[1], issue.number, issue.node_id);
|
|
1382
|
-
spinner.succeed(`Issue #${issue.number} deleted successfully.`);
|
|
1383
|
-
} else spinner.info(`Dry run: Issue #${issue.number} would be deleted.`);
|
|
1384
|
-
}
|
|
1385
|
-
this.success(`${choices.length} issue(s) deleted successfully.`);
|
|
1386
|
-
} catch (error) {
|
|
1387
|
-
spinner.stop();
|
|
1388
|
-
this.error(error.message);
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
async loadIssues(repository) {
|
|
1393
|
-
let issues = [];
|
|
1394
|
-
({data: issues} = await useOctokit().issues.listForRepo({
|
|
1395
|
-
repo: repository[1],
|
|
1396
|
-
owner: repository[0],
|
|
1397
|
-
per_page: 20,
|
|
1398
|
-
state: "all"
|
|
1399
|
-
}));
|
|
1400
|
-
return issues.filter((issue) => !issue.pull_request);
|
|
1401
|
-
}
|
|
1402
|
-
};
|
|
1403
|
-
|
|
1404
|
-
//#endregion
|
|
1405
|
-
//#region src/Commands/IssuesSeedCommand.ts
|
|
1406
|
-
var IssuesSeedCommand = class extends Command {
|
|
1407
|
-
signature = `issues:seed
|
|
83
|
+
`;description=`Delete issues from the specified repository.`;async handle(){let[e,t]=P();t(this);let n=M(`default_repo`),r=this.argument(`repo`,n.full_name).split(`/`)??[``,``],i=this.spinner(`Fetching issues...`).start(),a=this.option(`dryRun`,!1);try{let e=await this.loadIssues(r);i.succeed(`${e.length} issues fetched successfully.`);let t=await this.checkbox(`Select Issue${a?` (Dry Run)`:``}`,e.map(e=>({name:`#${e.number}: ${e.state===`open`?`🟢`:`🔴`} ${e.title}`,value:String(e.number)})),!0,void 0,20);if(!await this.confirm(`Are you sure you want to delete the selected ${t.length} issue(s)? ${a?`(Dry Run - No changes will be made)`:`This action cannot be undone`}.`)){this.info(`Operation cancelled.`);return}for(let n of e.filter(e=>t.includes(String(e.number))))i.start(`Deleting issue #${n.number}...`),a?i.info(`Dry run: Issue #${n.number} would be deleted.`):(await X(r[0],r[1],n.number,n.node_id),i.succeed(`Issue #${n.number} deleted successfully.`));this.success(`${t.length} issue(s) deleted successfully.`)}catch(e){i.stop(),this.error(e.message);return}}async loadIssues(e){let t=[];return{data:t}=await R().issues.listForRepo({repo:e[1],owner:e[0],per_page:20,state:`all`}),t.filter(e=>!e.pull_request)}},xe=class extends y{signature=`issues:seed
|
|
1408
84
|
{directory=issues : The directory containing issue files to seed from.}
|
|
1409
85
|
{--r|repo? : The repository to seed issues into. If not provided, the default repository will be used.}
|
|
1410
86
|
{--dry-run : Simulate the deletion without actually deleting issues.}
|
|
1411
|
-
`;
|
|
1412
|
-
|
|
1413
|
-
async handle() {
|
|
1414
|
-
const [_, setCommand] = useCommand();
|
|
1415
|
-
setCommand(this);
|
|
1416
|
-
const directory = join(process.cwd(), this.argument("directory", "issues"));
|
|
1417
|
-
const isDryRun = this.option("dryRun", false);
|
|
1418
|
-
const repo = read("default_repo");
|
|
1419
|
-
if (!repo) return void this.error(`ERROR: No default repository set. Please set a default repository using the ${logger("set-repo", ["grey", "italic"])} command.`);
|
|
1420
|
-
const seeder = new IssuesSeeder();
|
|
1421
|
-
try {
|
|
1422
|
-
const usernameRepo = this.option("repo", repo.full_name).split("/") ?? ["", ""];
|
|
1423
|
-
await seeder.checkConnectivity();
|
|
1424
|
-
await seeder.validateAccess(...usernameRepo);
|
|
1425
|
-
if (!existsSync(directory)) {
|
|
1426
|
-
this.error(`ERROR: Issues directory not found: ${logger(directory, ["grey", "italic"])}`);
|
|
1427
|
-
return;
|
|
1428
|
-
}
|
|
1429
|
-
const issueFiles = seeder.getIssueFiles(directory);
|
|
1430
|
-
const existingIssues = await seeder.fetchExistingIssues(...usernameRepo, "all");
|
|
1431
|
-
const existingIssuePaths = new Set(existingIssues.map((i) => seeder.getFilePath(i.body ?? "")));
|
|
1432
|
-
const issues = issueFiles.map(seeder.processIssueFile.bind(seeder)).filter(Boolean);
|
|
1433
|
-
const toCreate = [];
|
|
1434
|
-
const toSkip = [];
|
|
1435
|
-
issues.forEach((issue) => {
|
|
1436
|
-
if (existingIssuePaths.has(issue.filePath)) {
|
|
1437
|
-
const existingIssue = existingIssues.find((ei) => ei.title.toLowerCase() === issue.title.toLowerCase());
|
|
1438
|
-
toSkip.push({
|
|
1439
|
-
issue,
|
|
1440
|
-
existingIssue
|
|
1441
|
-
});
|
|
1442
|
-
} else toCreate.push(issue);
|
|
1443
|
-
});
|
|
1444
|
-
if (toSkip.length > 0) {
|
|
1445
|
-
this.newLine().info("INFO: Issues to SKIP (already exist):");
|
|
1446
|
-
toSkip.forEach(({ issue, existingIssue }) => {
|
|
1447
|
-
logger(` > ${issue.title}`, "white", !0);
|
|
1448
|
-
logger(` Existing: #${existingIssue.number} (${existingIssue.state})`, "white", !0);
|
|
1449
|
-
});
|
|
1450
|
-
}
|
|
1451
|
-
if (toCreate.length > 0) {
|
|
1452
|
-
this.newLine().info("INFO: Issues to CREATE:").newLine();
|
|
1453
|
-
toCreate.forEach((issue, index) => {
|
|
1454
|
-
logger(`${index + 1}. ${issue.title}`, "white", !0);
|
|
1455
|
-
});
|
|
1456
|
-
this.newLine();
|
|
1457
|
-
} else {
|
|
1458
|
-
this.newLine().success("INFO: No new issues to create. All issues already exist").newLine();
|
|
1459
|
-
Logger.log([["☑ Total files:", "white"], [issues.length.toString(), "blue"]], " ");
|
|
1460
|
-
Logger.log([["> Skipped:", "white"], [toSkip.length.toString(), "blue"]], " ");
|
|
1461
|
-
Logger.log([["± To create:", "white"], [toCreate.length.toString(), "blue"]], " ");
|
|
1462
|
-
this.newLine();
|
|
1463
|
-
return;
|
|
1464
|
-
}
|
|
1465
|
-
Logger.log([
|
|
1466
|
-
["⚠️ ", "white"],
|
|
1467
|
-
[" CONFIRM ", "bgYellow"],
|
|
1468
|
-
["This will create", "yellow"],
|
|
1469
|
-
[toCreate.length.toString(), "blue"],
|
|
1470
|
-
["new issues on GitHub.", "yellow"]
|
|
1471
|
-
], " ");
|
|
1472
|
-
if (toSkip.length > 0) this.info(`(Skipping ${toSkip.length} existing issues)`);
|
|
1473
|
-
if (await this.confirm(`Do you want to proceed?${isDryRun ? " (Dry Run - No changes will be made)" : ""}`)) {
|
|
1474
|
-
this.newLine();
|
|
1475
|
-
let created = 0;
|
|
1476
|
-
let failed = 0;
|
|
1477
|
-
const spinner = this.spinner("Creating issues...").start();
|
|
1478
|
-
for (const issue of toCreate) try {
|
|
1479
|
-
spinner.start(`Creating: ${issue.title}...`);
|
|
1480
|
-
if (!isDryRun) {
|
|
1481
|
-
const result = await seeder.createIssue(issue, ...usernameRepo);
|
|
1482
|
-
spinner.succeed(`Created #${result.number}: ${result.title}`);
|
|
1483
|
-
this.info(`URL: ${result.html_url}\n`);
|
|
1484
|
-
} else spinner.info(`Dry run: Issue ${logger(issue.title, ["cyan", "italic"])} would be created.`);
|
|
1485
|
-
created++;
|
|
1486
|
-
await wait(1e3);
|
|
1487
|
-
} catch (error) {
|
|
1488
|
-
this.error(`ERROR: Failed to create Issue: ${logger(issue.title, ["cyan", "italic"])}`);
|
|
1489
|
-
this.error(`ERROR: ${error.message}\n`);
|
|
1490
|
-
failed++;
|
|
1491
|
-
}
|
|
1492
|
-
spinner.succeed(`All ${toCreate.length} issues processed.`);
|
|
1493
|
-
Logger.log([
|
|
1494
|
-
["=========================", "white"],
|
|
1495
|
-
[`✔ Created: ${created}`, "white"],
|
|
1496
|
-
[`x Failed: ${failed}`, "white"],
|
|
1497
|
-
[`> Skipped: ${toSkip.length}`, "white"],
|
|
1498
|
-
[`☑ Total: ${issues.length}`, "white"],
|
|
1499
|
-
["========================", "white"]
|
|
1500
|
-
], "\n");
|
|
1501
|
-
this.newLine();
|
|
1502
|
-
}
|
|
1503
|
-
} catch (error) {
|
|
1504
|
-
this.error(error.message);
|
|
1505
|
-
return;
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
};
|
|
1509
|
-
|
|
1510
|
-
//#endregion
|
|
1511
|
-
//#region src/Commands/IssuesUpdateCommand.ts
|
|
1512
|
-
var IssuesUpdateCommand = class extends Command {
|
|
1513
|
-
signature = `issues:update
|
|
87
|
+
`;description=`Seed the database with issues from a preset directory.`;async handle(){let[e,r]=P();r(this);let i=c(process.cwd(),this.argument(`directory`,`issues`)),a=this.option(`dryRun`,!1),o=M(`default_repo`);if(!o)return void this.error(`ERROR: No default repository set. Please set a default repository using the ${W(`set-repo`,[`grey`,`italic`])} command.`);let s=new Y;try{let e=this.option(`repo`,o.full_name).split(`/`)??[``,``];if(await s.checkConnectivity(),await s.validateAccess(...e),!n(i)){this.error(`ERROR: Issues directory not found: ${W(i,[`grey`,`italic`])}`);return}let r=s.getIssueFiles(i),c=await s.fetchExistingIssues(...e,`all`),l=new Set(c.map(e=>s.getFilePath(e.body??``))),u=r.map(s.processIssueFile.bind(s)).filter(Boolean),d=[],f=[];if(u.forEach(e=>{if(l.has(e.filePath)){let t=c.find(t=>t.title.toLowerCase()===e.title.toLowerCase());f.push({issue:e,existingIssue:t})}else d.push(e)}),f.length>0&&(this.newLine().info(`INFO: Issues to SKIP (already exist):`),f.forEach(({issue:e,existingIssue:t})=>{W(` > ${e.title}`,`white`,!0),W(` Existing: #${t.number} (${t.state})`,`white`,!0)})),d.length>0)this.newLine().info(`INFO: Issues to CREATE:`).newLine(),d.forEach((e,t)=>{W(`${t+1}. ${e.title}`,`white`,!0)}),this.newLine();else{this.newLine().success(`INFO: No new issues to create. All issues already exist`).newLine(),t.log([[`☑ Total files:`,`white`],[u.length.toString(),`blue`]],` `),t.log([[`> Skipped:`,`white`],[f.length.toString(),`blue`]],` `),t.log([[`± To create:`,`white`],[d.length.toString(),`blue`]],` `),this.newLine();return}if(t.log([[`⚠️ `,`white`],[` CONFIRM `,`bgYellow`],[`This will create`,`yellow`],[d.length.toString(),`blue`],[`new issues on GitHub.`,`yellow`]],` `),f.length>0&&this.info(`(Skipping ${f.length} existing issues)`),await this.confirm(`Do you want to proceed?${a?` (Dry Run - No changes will be made)`:``}`)){this.newLine();let n=0,r=0,i=this.spinner(`Creating issues...`).start();for(let t of d)try{if(i.start(`Creating: ${t.title}...`),a)i.info(`Dry run: Issue ${W(t.title,[`cyan`,`italic`])} would be created.`);else{let n=await s.createIssue(t,...e);i.succeed(`Created #${n.number}: ${n.title}`),this.info(`URL: ${n.html_url}\n`)}n++,await U(1e3)}catch(e){this.error(`ERROR: Failed to create Issue: ${W(t.title,[`cyan`,`italic`])}`),this.error(`ERROR: ${e.message}\n`),r++}i.succeed(`All ${d.length} issues processed.`),t.log([[`=========================`,`white`],[`✔ Created: ${n}`,`white`],[`x Failed: ${r}`,`white`],[`> Skipped: ${f.length}`,`white`],[`☑ Total: ${u.length}`,`white`],[`========================`,`white`]],`
|
|
88
|
+
`),this.newLine()}}catch(e){this.error(e.message);return}}},Se=class extends y{signature=`issues:update
|
|
1514
89
|
{directory=issues : The directory containing issue files to seed from.}
|
|
1515
90
|
{--r|repo? : The repository to seed issues into. If not provided, the default repository will be used.}
|
|
1516
91
|
{--dry-run : Simulate the deletion without actually deleting issues.}
|
|
1517
|
-
`;
|
|
1518
|
-
|
|
1519
|
-
async handle() {
|
|
1520
|
-
const [_, setCommand] = useCommand();
|
|
1521
|
-
setCommand(this);
|
|
1522
|
-
const directory = join(process.cwd(), this.argument("directory", "issues"));
|
|
1523
|
-
const isDryRun = this.option("dryRun", false);
|
|
1524
|
-
const repo = read("default_repo");
|
|
1525
|
-
if (!repo) return void this.error(`ERROR: No default repository set. Please set a default repository using the ${logger("set-repo", ["grey", "italic"])} command.`);
|
|
1526
|
-
const seeder = new IssuesSeeder();
|
|
1527
|
-
try {
|
|
1528
|
-
const usernameRepo = this.option("repo", repo.full_name).split("/") ?? ["", ""];
|
|
1529
|
-
await seeder.checkConnectivity();
|
|
1530
|
-
await seeder.validateAccess(...usernameRepo);
|
|
1531
|
-
if (!existsSync(directory)) {
|
|
1532
|
-
this.error(`ERROR: Issues directory not found: ${logger(directory, ["grey", "italic"])}`);
|
|
1533
|
-
return;
|
|
1534
|
-
}
|
|
1535
|
-
const issueFiles = seeder.getIssueFiles(directory);
|
|
1536
|
-
const existingIssues = await seeder.fetchExistingIssues(...usernameRepo, "all");
|
|
1537
|
-
const existingIssuePaths = new Set(existingIssues.map((i) => seeder.getFilePath(i.body ?? "")));
|
|
1538
|
-
const issues = issueFiles.map(seeder.processIssueFile.bind(seeder)).filter(Boolean);
|
|
1539
|
-
const toSkip = [];
|
|
1540
|
-
const toUpdate = [];
|
|
1541
|
-
issues.forEach((issue) => {
|
|
1542
|
-
if (existingIssuePaths.has(issue.filePath)) {
|
|
1543
|
-
const existingIssue = existingIssues.find((ei) => seeder.getFilePath(ei.body ?? "") === issue.filePath);
|
|
1544
|
-
toUpdate.push({
|
|
1545
|
-
issue,
|
|
1546
|
-
existingIssue
|
|
1547
|
-
});
|
|
1548
|
-
} else toSkip.push(issue);
|
|
1549
|
-
});
|
|
1550
|
-
if (toSkip.length > 0) {
|
|
1551
|
-
this.newLine().info("INFO: Issues to SKIP (not created):");
|
|
1552
|
-
toSkip.forEach((issue, index) => {
|
|
1553
|
-
logger(`${index + 1}. ${issue.title}`, "white", !0);
|
|
1554
|
-
logger(` File: ${issue.filePath} (${issue.type})`, "white", !0);
|
|
1555
|
-
});
|
|
1556
|
-
}
|
|
1557
|
-
if (toUpdate.length > 0) {
|
|
1558
|
-
this.newLine().info("INFO: Issues to UPDATE:").newLine();
|
|
1559
|
-
toUpdate.forEach(({ issue, existingIssue }) => {
|
|
1560
|
-
logger(` > ${diffText(issue.title, existingIssue.title)}`, "white", !0);
|
|
1561
|
-
logger(` Existing: #${existingIssue.number} (${existingIssue.state})`, "white", !0);
|
|
1562
|
-
});
|
|
1563
|
-
this.newLine();
|
|
1564
|
-
} else {
|
|
1565
|
-
this.newLine().success("INFO: No issues to update. All issues are up to date").newLine();
|
|
1566
|
-
Logger.log([["☑ Total files:", "white"], [issues.length.toString(), "blue"]], " ");
|
|
1567
|
-
Logger.log([["> Skipped:", "white"], [toSkip.length.toString(), "blue"]], " ");
|
|
1568
|
-
Logger.log([["± To update:", "white"], [toUpdate.length.toString(), "blue"]], " ");
|
|
1569
|
-
this.newLine();
|
|
1570
|
-
return;
|
|
1571
|
-
}
|
|
1572
|
-
Logger.log([
|
|
1573
|
-
["⚠️ ", "white"],
|
|
1574
|
-
[" CONFIRM ", "bgYellow"],
|
|
1575
|
-
["This will update", "yellow"],
|
|
1576
|
-
[toUpdate.length.toString(), "blue"],
|
|
1577
|
-
["existing issues on GitHub.", "yellow"]
|
|
1578
|
-
], " ");
|
|
1579
|
-
if (toSkip.length > 0) this.info(`(Skipping ${toSkip.length} existing issues)`);
|
|
1580
|
-
if (await this.confirm(`Do you want to proceed?${isDryRun ? " (Dry Run - No changes will be made)" : ""}`)) {
|
|
1581
|
-
this.newLine();
|
|
1582
|
-
let updated = 0;
|
|
1583
|
-
let failed = 0;
|
|
1584
|
-
const spinner = this.spinner("Updating issues...").start();
|
|
1585
|
-
for (const { issue, existingIssue } of toUpdate) try {
|
|
1586
|
-
spinner.start(`Updating: ${issue.title}...`);
|
|
1587
|
-
if (!isDryRun) {
|
|
1588
|
-
const result = await seeder.updateIssue(issue, existingIssue, ...usernameRepo);
|
|
1589
|
-
spinner.succeed(`Updated #${result.number}: ${result.title}`);
|
|
1590
|
-
this.info(`URL: ${result.html_url}\n`);
|
|
1591
|
-
} else spinner.info(`Dry run: Issue ${logger(issue.title, ["cyan", "italic"])} would be updated.`);
|
|
1592
|
-
updated++;
|
|
1593
|
-
await wait(1e3);
|
|
1594
|
-
} catch (error) {
|
|
1595
|
-
this.error(`ERROR: Failed to update Issue: ${logger(issue.title, ["cyan", "italic"])}`);
|
|
1596
|
-
this.error(`ERROR: ${error.message}\n`);
|
|
1597
|
-
failed++;
|
|
1598
|
-
}
|
|
1599
|
-
spinner.succeed(`All ${toUpdate.length} issues processed.`);
|
|
1600
|
-
Logger.log([
|
|
1601
|
-
["=========================", "white"],
|
|
1602
|
-
[`✔ Updated: ${updated}`, "white"],
|
|
1603
|
-
[`x Failed: ${failed}`, "white"],
|
|
1604
|
-
[`> Skipped: ${toSkip.length}`, "white"],
|
|
1605
|
-
[`☑ Total: ${issues.length}`, "white"],
|
|
1606
|
-
["========================", "white"]
|
|
1607
|
-
], "\n");
|
|
1608
|
-
this.newLine();
|
|
1609
|
-
}
|
|
1610
|
-
} catch (error) {
|
|
1611
|
-
this.error(error.message);
|
|
1612
|
-
return;
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
};
|
|
1616
|
-
|
|
1617
|
-
//#endregion
|
|
1618
|
-
//#region src/config.ts
|
|
1619
|
-
const config = {
|
|
1620
|
-
CLIENT_ID: process.env.GITHUB_CLIENT_ID,
|
|
1621
|
-
CLIENT_TYPE: "oauth-app",
|
|
1622
|
-
SCOPES: [
|
|
1623
|
-
"repo",
|
|
1624
|
-
"read:user",
|
|
1625
|
-
"user:email"
|
|
1626
|
-
]
|
|
1627
|
-
};
|
|
1628
|
-
|
|
1629
|
-
//#endregion
|
|
1630
|
-
//#region src/Github.ts
|
|
1631
|
-
/**
|
|
1632
|
-
* Sign in user
|
|
1633
|
-
*
|
|
1634
|
-
* @returns
|
|
1635
|
-
*/
|
|
1636
|
-
async function signIn() {
|
|
1637
|
-
const [cmd] = useCommand();
|
|
1638
|
-
const command = cmd();
|
|
1639
|
-
let spinner = command.spinner("Requesting device code...").start();
|
|
1640
|
-
const { data: { device_code, user_code, verification_uri, interval } } = await createDeviceCode({
|
|
1641
|
-
clientType: config.CLIENT_TYPE,
|
|
1642
|
-
clientId: config.CLIENT_ID,
|
|
1643
|
-
scopes: config.SCOPES
|
|
1644
|
-
});
|
|
1645
|
-
spinner.succeed("Device code created");
|
|
1646
|
-
Logger.log([["Your authentication code is", "white"], [`\n\t ${user_code} \n`, ["white", "bgBlue"]]], " ");
|
|
1647
|
-
Logger.log([["Please open the following URL in your browser to authenticate:", "white"], [verification_uri, ["cyan", "underline"]]], " ");
|
|
1648
|
-
Logger.log([
|
|
1649
|
-
["Press Enter to open your browser, or ", "white"],
|
|
1650
|
-
["Ctrl+C", ["grey", "italic"]],
|
|
1651
|
-
[" to cancel", "white"]
|
|
1652
|
-
], " ");
|
|
1653
|
-
await waitForEnter(async () => {
|
|
1654
|
-
try {
|
|
1655
|
-
if (type() === "Windows_NT") await open(verification_uri, {
|
|
1656
|
-
wait: true,
|
|
1657
|
-
app: { name: apps.browser }
|
|
1658
|
-
});
|
|
1659
|
-
else await open(verification_uri, { wait: true });
|
|
1660
|
-
} catch (error) {
|
|
1661
|
-
command.error("Error opening browser:" + error.message);
|
|
1662
|
-
command.info("Please manually open the following URL in your browser:");
|
|
1663
|
-
command.info(verification_uri);
|
|
1664
|
-
await wait(3e3);
|
|
1665
|
-
}
|
|
1666
|
-
});
|
|
1667
|
-
const currentInterval = interval;
|
|
1668
|
-
let remainingAttempts = 150;
|
|
1669
|
-
spinner = command.spinner("Waiting for authorization...").start();
|
|
1670
|
-
while (true) {
|
|
1671
|
-
remainingAttempts -= 1;
|
|
1672
|
-
if (remainingAttempts < 0) throw new Error("User took too long to respond");
|
|
1673
|
-
try {
|
|
1674
|
-
const { authentication } = await exchangeDeviceCode({
|
|
1675
|
-
clientType: "oauth-app",
|
|
1676
|
-
clientId: config.CLIENT_ID,
|
|
1677
|
-
code: device_code,
|
|
1678
|
-
scopes: config.SCOPES
|
|
1679
|
-
});
|
|
1680
|
-
const { data: user } = await new Octokit({ auth: authentication.token }).request("/user");
|
|
1681
|
-
if (typeof spinner !== "undefined") spinner.succeed("Authorization successful");
|
|
1682
|
-
return {
|
|
1683
|
-
authentication,
|
|
1684
|
-
user
|
|
1685
|
-
};
|
|
1686
|
-
} catch (error) {
|
|
1687
|
-
if (error.status === 400) {
|
|
1688
|
-
const errorCode = error.response.data.error;
|
|
1689
|
-
if (["authorization_pending", "slow_down"].includes(errorCode)) await wait(currentInterval * 3e3);
|
|
1690
|
-
else if ([
|
|
1691
|
-
"expired_token",
|
|
1692
|
-
"incorrect_device_code",
|
|
1693
|
-
"access_denied"
|
|
1694
|
-
].includes(errorCode)) throw new Error(errorCode);
|
|
1695
|
-
else throw new Error(`An unexpected error occurred: ${error.message}`);
|
|
1696
|
-
} else throw new Error(`An unexpected error occurred: ${error.message}`);
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
/**
|
|
1701
|
-
* Store login details
|
|
1702
|
-
*
|
|
1703
|
-
* @param payload
|
|
1704
|
-
*/
|
|
1705
|
-
function storeLoginDetails({ authentication: payload, user }) {
|
|
1706
|
-
write("user", user);
|
|
1707
|
-
write("token", payload.token);
|
|
1708
|
-
write("scopes", payload.scopes);
|
|
1709
|
-
write("clientId", payload.clientId);
|
|
1710
|
-
write("clientType", payload.clientType);
|
|
1711
|
-
}
|
|
1712
|
-
/**
|
|
1713
|
-
* Clear authentication details
|
|
1714
|
-
*/
|
|
1715
|
-
function clearAuth() {
|
|
1716
|
-
remove("token");
|
|
1717
|
-
remove("scopes");
|
|
1718
|
-
remove("clientId");
|
|
1719
|
-
remove("clientType");
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
//#endregion
|
|
1723
|
-
//#region src/Commands/LoginCommand.ts
|
|
1724
|
-
var LoginCommand = class extends Command {
|
|
1725
|
-
signature = "login";
|
|
1726
|
-
description = "Log in to Grithub";
|
|
1727
|
-
async handle() {
|
|
1728
|
-
const [_, setCommand] = useCommand();
|
|
1729
|
-
setCommand(this);
|
|
1730
|
-
let token = read("token"), user;
|
|
1731
|
-
if (token) {
|
|
1732
|
-
this.info("INFO: You're already logged in").newLine();
|
|
1733
|
-
return;
|
|
1734
|
-
} else {
|
|
1735
|
-
const [_$1, response] = await promiseWrapper(signIn());
|
|
1736
|
-
if (response) {
|
|
1737
|
-
storeLoginDetails(response);
|
|
1738
|
-
token = read("token");
|
|
1739
|
-
user = read("user");
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
if (token && user) {
|
|
1743
|
-
const repos = await useOctokit().rest.repos.listForAuthenticatedUser();
|
|
1744
|
-
const repoName = await this.choice("Select default repository", repos.data.map((r) => ({
|
|
1745
|
-
name: r.full_name,
|
|
1746
|
-
value: r.full_name
|
|
1747
|
-
})), 0);
|
|
1748
|
-
const repo = repos.data.find((r) => r.full_name === repoName);
|
|
1749
|
-
if (repo) write("default_repo", {
|
|
1750
|
-
id: repo.id,
|
|
1751
|
-
name: repo.name,
|
|
1752
|
-
full_name: repo.full_name,
|
|
1753
|
-
private: repo.private
|
|
1754
|
-
});
|
|
1755
|
-
else write("default_repo", {});
|
|
1756
|
-
this.info(`INFO: You have been logged in as ${Logger.log(user.name, "blue", !1)}!`).newLine();
|
|
1757
|
-
}
|
|
1758
|
-
process.exit(0);
|
|
1759
|
-
}
|
|
1760
|
-
};
|
|
1761
|
-
|
|
1762
|
-
//#endregion
|
|
1763
|
-
//#region src/Commands/LogoutCommand.ts
|
|
1764
|
-
var LogoutCommand = class extends Command {
|
|
1765
|
-
signature = "logout";
|
|
1766
|
-
description = "Log out of Grithub CLI";
|
|
1767
|
-
async handle() {
|
|
1768
|
-
const [_, setCommand] = useCommand();
|
|
1769
|
-
setCommand(this);
|
|
1770
|
-
const spinner = this.spinner("Logging out...").start();
|
|
1771
|
-
try {
|
|
1772
|
-
await wait(1e3, () => clearAuth());
|
|
1773
|
-
spinner.succeed("Logged out successfully");
|
|
1774
|
-
} catch (error) {
|
|
1775
|
-
spinner.fail("Logout failed");
|
|
1776
|
-
this.error("An error occurred during logout: " + error.message);
|
|
1777
|
-
}
|
|
1778
|
-
this.newLine();
|
|
1779
|
-
}
|
|
1780
|
-
};
|
|
1781
|
-
|
|
1782
|
-
//#endregion
|
|
1783
|
-
//#region src/Commands/SetRepoCommand.ts
|
|
1784
|
-
var SetRepoCommand = class extends Command {
|
|
1785
|
-
signature = `set-repo
|
|
92
|
+
`;description=`Seed the database with updated issues from a preset directory.`;async handle(){let[e,r]=P();r(this);let i=c(process.cwd(),this.argument(`directory`,`issues`)),a=this.option(`dryRun`,!1),o=M(`default_repo`);if(!o)return void this.error(`ERROR: No default repository set. Please set a default repository using the ${W(`set-repo`,[`grey`,`italic`])} command.`);let s=new Y;try{let e=this.option(`repo`,o.full_name).split(`/`)??[``,``];if(await s.checkConnectivity(),await s.validateAccess(...e),!n(i)){this.error(`ERROR: Issues directory not found: ${W(i,[`grey`,`italic`])}`);return}let r=s.getIssueFiles(i),c=await s.fetchExistingIssues(...e,`all`),l=new Set(c.map(e=>s.getFilePath(e.body??``))),u=r.map(s.processIssueFile.bind(s)).filter(Boolean),d=[],f=[];if(u.forEach(e=>{if(l.has(e.filePath)){let t=c.find(t=>s.getFilePath(t.body??``)===e.filePath);f.push({issue:e,existingIssue:t})}else d.push(e)}),d.length>0&&(this.newLine().info(`INFO: Issues to SKIP (not created):`),d.forEach((e,t)=>{W(`${t+1}. ${e.title}`,`white`,!0),W(` File: ${e.filePath} (${e.type})`,`white`,!0)})),f.length>0)this.newLine().info(`INFO: Issues to UPDATE:`).newLine(),f.forEach(({issue:e,existingIssue:t})=>{W(` > ${ue(e.title,t.title)}`,`white`,!0),W(` Existing: #${t.number} (${t.state})`,`white`,!0)}),this.newLine();else{this.newLine().success(`INFO: No issues to update. All issues are up to date`).newLine(),t.log([[`☑ Total files:`,`white`],[u.length.toString(),`blue`]],` `),t.log([[`> Skipped:`,`white`],[d.length.toString(),`blue`]],` `),t.log([[`± To update:`,`white`],[f.length.toString(),`blue`]],` `),this.newLine();return}if(t.log([[`⚠️ `,`white`],[` CONFIRM `,`bgYellow`],[`This will update`,`yellow`],[f.length.toString(),`blue`],[`existing issues on GitHub.`,`yellow`]],` `),d.length>0&&this.info(`(Skipping ${d.length} existing issues)`),await this.confirm(`Do you want to proceed?${a?` (Dry Run - No changes will be made)`:``}`)){this.newLine();let n=0,r=0,i=this.spinner(`Updating issues...`).start();for(let{issue:t,existingIssue:o}of f)try{if(i.start(`Updating: ${t.title}...`),a)i.info(`Dry run: Issue ${W(t.title,[`cyan`,`italic`])} would be updated.`);else{let n=await s.updateIssue(t,o,...e);i.succeed(`Updated #${n.number}: ${n.title}`),this.info(`URL: ${n.html_url}\n`)}n++,await U(1e3)}catch(e){this.error(`ERROR: Failed to update Issue: ${W(t.title,[`cyan`,`italic`])}`),this.error(`ERROR: ${e.message}\n`),r++}i.succeed(`All ${f.length} issues processed.`),t.log([[`=========================`,`white`],[`✔ Updated: ${n}`,`white`],[`x Failed: ${r}`,`white`],[`> Skipped: ${d.length}`,`white`],[`☑ Total: ${u.length}`,`white`],[`========================`,`white`]],`
|
|
93
|
+
`),this.newLine()}}catch(e){this.error(e.message);return}}};const Z={CLIENT_ID:`6+63+123+122+37+32+17+56+112+6+14+0+125+7+31+10+122+58+42+39`.split(`+`).map(e=>String.fromCharCode(Number(e)^73)).join(``),CLIENT_TYPE:`oauth-app`,SCOPES:[`repo`,`read:user`,`user:email`]};async function Ce(){let[e]=P(),n=e(),r=n.spinner(`Requesting device code...`).start();try{let{data:{device_code:e,user_code:i,verification_uri:a,interval:o}}=await ae({clientType:Z.CLIENT_TYPE,clientId:Z.CLIENT_ID,scopes:Z.SCOPES});r.succeed(`Device code created`),t.log([[`Your authentication code is`,`white`],[`\n\t ${i} \n`,[`white`,`bgBlue`]]],` `),t.log([[`Please open the following URL in your browser to authenticate:`,`white`],[a,[`cyan`,`underline`]]],` `),t.log([[`Press Enter to open your browser, or `,`white`],[`Ctrl+C`,[`grey`,`italic`]],[` to cancel`,`white`]],` `),await q(async()=>{try{p()===`Windows_NT`?await S(a,{wait:!0,app:{name:C.browser}}):await S(a,{wait:!0})}catch(e){n.error(`Error opening browser:`+e.message),n.info(`Please manually open the following URL in your browser:`),n.info(a),await U(3e3)}});let s=o,c=150;for(r=n.spinner(`Waiting for authorization...`).start();;){if(--c,c<0)throw Error(`User took too long to respond`);try{let{authentication:t}=await x({clientType:`oauth-app`,clientId:Z.CLIENT_ID,code:e,scopes:Z.SCOPES}),{data:n}=await new v({auth:t.token}).request(`/user`);return r!==void 0&&r.succeed(`Authorization successful`),{authentication:t,user:n}}catch(e){if(e.status===400){let t=e.response.data.error;if([`authorization_pending`,`slow_down`].includes(t))await U(s*3e3);else if([`expired_token`,`incorrect_device_code`,`access_denied`].includes(t))throw Error(t);else throw Error(`An unexpected error occurred: ${e.message}`)}else throw Error(`An unexpected error occurred: ${e.message}`)}}}catch{return Z.CLIENT_ID?r.fail(`Failed to authenticate user`):r.fail(`GitHub Client ID not available.`),null}}function we({authentication:e,user:t}){A(`user`,t),A(`token`,e.token),A(`scopes`,e.scopes),A(`clientId`,e.clientId),A(`clientType`,e.clientType)}function Q(){j(`token`),j(`scopes`),j(`clientId`),j(`clientType`)}var Te=class extends y{signature=`login`;description=`Log in to Grithub`;async handle(){let[e,n]=P();n(this);let r=M(`token`),i;if(r){this.info(`INFO: You're already logged in`).newLine();return}else{let[e,t]=await V(Ce());t&&(we(t),r=M(`token`),i=M(`user`))}if(r&&i){let e=await R().rest.repos.listForAuthenticatedUser(),n=await this.choice(`Select default repository`,e.data.map(e=>({name:e.full_name,value:e.full_name})),0),r=e.data.find(e=>e.full_name===n);r?A(`default_repo`,{id:r.id,name:r.name,full_name:r.full_name,private:r.private}):A(`default_repo`,{}),this.info(`INFO: You have been logged in as ${t.log(i.name,`blue`,!1)}!`).newLine()}process.exit(0)}},Ee=class extends y{signature=`logout`;description=`Log out of Grithub CLI`;async handle(){let[e,t]=P();t(this);let n=this.spinner(`Logging out...`).start();try{await U(1e3,()=>Q()),n.succeed(`Logged out successfully`)}catch(e){n.fail(`Logout failed`),this.error(`An error occurred during logout: `+e.message)}this.newLine()}},De=class extends y{signature=`set-repo
|
|
1786
94
|
{ name? : The full name of the repository (e.g., username/repo)}
|
|
1787
95
|
{--O|org : Set repository from an organization}
|
|
1788
|
-
`;
|
|
1789
|
-
description = "Set the default repository.";
|
|
1790
|
-
async handle() {
|
|
1791
|
-
const [_, setCommand] = useCommand();
|
|
1792
|
-
setCommand(this);
|
|
1793
|
-
const token = read("token");
|
|
1794
|
-
let repo = void 0;
|
|
1795
|
-
if (!token) return void this.error("ERROR: You must be logged in to set a default repository.");
|
|
1796
|
-
if (this.argument("name")) ({data: repo} = await useOctokit().rest.repos.get({
|
|
1797
|
-
owner: this.argument("name").split("/")[0],
|
|
1798
|
-
repo: this.argument("name").split("/")[1]
|
|
1799
|
-
}));
|
|
1800
|
-
else if (this.option("org")) {
|
|
1801
|
-
const spinner = this.spinner("Fetching your organizations...").start();
|
|
1802
|
-
const orgs = await useOctokit().rest.orgs.listForAuthenticatedUser();
|
|
1803
|
-
spinner.succeed(`${orgs.data.length} organizations fetched successfully.`);
|
|
1804
|
-
const orgName = await this.choice("Select organization", orgs.data.map((o) => ({
|
|
1805
|
-
name: o.login,
|
|
1806
|
-
value: o.login
|
|
1807
|
-
})), 0);
|
|
1808
|
-
const orgReposSpinner = this.spinner(`Fetching repositories for organization ${orgName}...`).start();
|
|
1809
|
-
const repos = await useOctokit().rest.repos.listForOrg({ org: orgName });
|
|
1810
|
-
orgReposSpinner.succeed(`${repos.data.length} repositories fetched successfully.`);
|
|
1811
|
-
const repoName = await this.choice(`Select default repository (${read("default_repo")?.full_name ?? "none"})`, repos.data.map((r) => ({
|
|
1812
|
-
name: r.full_name,
|
|
1813
|
-
value: r.full_name
|
|
1814
|
-
})), 0);
|
|
1815
|
-
repo = repos.data.find((r) => r.full_name === repoName);
|
|
1816
|
-
} else {
|
|
1817
|
-
const spinner = this.spinner("Fetching your repositories...").start();
|
|
1818
|
-
const repos = await useOctokit().rest.repos.listForAuthenticatedUser();
|
|
1819
|
-
spinner.succeed(`${repos.data.length} repositories fetched successfully.`);
|
|
1820
|
-
const repoName = await this.choice(`Select default repository (${read("default_repo")?.full_name ?? "none"})`, repos.data.map((r) => ({
|
|
1821
|
-
name: r.full_name,
|
|
1822
|
-
value: r.full_name
|
|
1823
|
-
})), 0);
|
|
1824
|
-
repo = repos.data.find((r) => r.full_name === repoName);
|
|
1825
|
-
}
|
|
1826
|
-
if (repo) {
|
|
1827
|
-
write("default_repo", {
|
|
1828
|
-
id: repo.id,
|
|
1829
|
-
name: repo.name,
|
|
1830
|
-
full_name: repo.full_name,
|
|
1831
|
-
private: repo.private
|
|
1832
|
-
});
|
|
1833
|
-
this.info(`INFO: ${Logger.log(repo.full_name, "blue", !1)} has been set as the default repository.`).newLine();
|
|
1834
|
-
} else {
|
|
1835
|
-
write("default_repo", read("default_repo") ?? {});
|
|
1836
|
-
this.warn("INFO: No repository selected. Default repository has been cleared.").newLine();
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
};
|
|
1840
|
-
|
|
1841
|
-
//#endregion
|
|
1842
|
-
//#region src/axios.ts
|
|
1843
|
-
const api = axios.create({
|
|
1844
|
-
baseURL: "https://api.github.com",
|
|
1845
|
-
headers: { "Content-Type": "application/json" }
|
|
1846
|
-
});
|
|
1847
|
-
/**
|
|
1848
|
-
* Initialize Axios with configuration from the application settings.
|
|
1849
|
-
*/
|
|
1850
|
-
const initAxios = () => {
|
|
1851
|
-
const [getConfig] = useConfig();
|
|
1852
|
-
const config$1 = getConfig();
|
|
1853
|
-
api.defaults.baseURL = config$1.apiBaseURL || "https://api.github.com";
|
|
1854
|
-
api.defaults.timeout = config$1.timeoutDuration || 3e3;
|
|
1855
|
-
};
|
|
1856
|
-
/**
|
|
1857
|
-
* Log the full request details if we are not in production
|
|
1858
|
-
* @param config
|
|
1859
|
-
* @returns
|
|
1860
|
-
*/
|
|
1861
|
-
const logInterceptor = (config$1) => {
|
|
1862
|
-
const [getConfig] = useConfig();
|
|
1863
|
-
const [command] = useCommand();
|
|
1864
|
-
const conf = getConfig();
|
|
1865
|
-
const v = command().getVerbosity();
|
|
1866
|
-
if (conf.debug || v > 1) {
|
|
1867
|
-
if (conf.debug || v >= 2) {
|
|
1868
|
-
console.log("Request URL:", config$1.url);
|
|
1869
|
-
console.log("Request Method:", config$1.method);
|
|
1870
|
-
}
|
|
1871
|
-
if (conf.debug || v == 3) {
|
|
1872
|
-
console.log("Request Headers:", config$1.headers);
|
|
1873
|
-
console.log("Request Data:", config$1.data);
|
|
1874
|
-
}
|
|
1875
|
-
console.log("Error Response URL:", axios.getUri(config$1));
|
|
1876
|
-
}
|
|
1877
|
-
return config$1;
|
|
1878
|
-
};
|
|
1879
|
-
/**
|
|
1880
|
-
* Log only the relevant parts of the response if we are in not in production
|
|
1881
|
-
*
|
|
1882
|
-
* @param response
|
|
1883
|
-
* @returns
|
|
1884
|
-
*/
|
|
1885
|
-
const logResponseInterceptor = (response) => {
|
|
1886
|
-
const [getConfig] = useConfig();
|
|
1887
|
-
const [command] = useCommand();
|
|
1888
|
-
const conf = getConfig();
|
|
1889
|
-
const v = command().getVerbosity();
|
|
1890
|
-
if (conf.debug || v > 1) {
|
|
1891
|
-
const { data, status, statusText, headers } = response;
|
|
1892
|
-
if (conf.debug || v >= 2) {
|
|
1893
|
-
console.log("Response Data:", data);
|
|
1894
|
-
console.log("Response Status:", status);
|
|
1895
|
-
}
|
|
1896
|
-
if (conf.debug || v === 3) {
|
|
1897
|
-
console.log("Response Status Text:", statusText);
|
|
1898
|
-
console.log("Response Headers:", headers);
|
|
1899
|
-
}
|
|
1900
|
-
console.log("Error Response URL:", axios.getUri(response.config));
|
|
1901
|
-
}
|
|
1902
|
-
return response;
|
|
1903
|
-
};
|
|
1904
|
-
const logResponseErrorInterceptor = (error) => {
|
|
1905
|
-
const [getConfig] = useConfig();
|
|
1906
|
-
const [command] = useCommand();
|
|
1907
|
-
const conf = getConfig();
|
|
1908
|
-
const v = command().getVerbosity();
|
|
1909
|
-
if (conf.debug || v > 1) if (error.response) {
|
|
1910
|
-
const { data, status, headers } = error.response;
|
|
1911
|
-
if (conf.debug || v >= 2) {
|
|
1912
|
-
console.log("Error Response Data:", data);
|
|
1913
|
-
console.log("Error Response Status:", status);
|
|
1914
|
-
}
|
|
1915
|
-
if (conf.debug || v === 3) console.log("Error Response Headers:", headers);
|
|
1916
|
-
console.log("Error Response URL:", axios.getUri(error.config));
|
|
1917
|
-
} else console.log("Error Message:", error.message);
|
|
1918
|
-
return Promise.reject(error);
|
|
1919
|
-
};
|
|
1920
|
-
api.interceptors.request.use(logInterceptor, (error) => Promise.reject(error));
|
|
1921
|
-
api.interceptors.response.use(logResponseInterceptor, logResponseErrorInterceptor);
|
|
1922
|
-
|
|
1923
|
-
//#endregion
|
|
1924
|
-
//#region src/logo.ts
|
|
1925
|
-
var logo_default = `
|
|
96
|
+
`;description=`Set the default repository.`;async handle(){let[e,n]=P();n(this);let r=M(`token`),i;if(!r)return void this.error(`ERROR: You must be logged in to set a default repository.`);if(this.argument(`name`))({data:i}=await R().rest.repos.get({owner:this.argument(`name`).split(`/`)[0],repo:this.argument(`name`).split(`/`)[1]}));else if(this.option(`org`)){let e=this.spinner(`Fetching your organizations...`).start(),t=await R().rest.orgs.listForAuthenticatedUser();e.succeed(`${t.data.length} organizations fetched successfully.`);let n=await this.choice(`Select organization`,t.data.map(e=>({name:e.login,value:e.login})),0),r=this.spinner(`Fetching repositories for organization ${n}...`).start(),a=await R().rest.repos.listForOrg({org:n});r.succeed(`${a.data.length} repositories fetched successfully.`);let o=await this.choice(`Select default repository (${M(`default_repo`)?.full_name??`none`})`,a.data.map(e=>({name:e.full_name,value:e.full_name})),0);i=a.data.find(e=>e.full_name===o)}else{let e=this.spinner(`Fetching your repositories...`).start(),t=await R().rest.repos.listForAuthenticatedUser();e.succeed(`${t.data.length} repositories fetched successfully.`);let n=await this.choice(`Select default repository (${M(`default_repo`)?.full_name??`none`})`,t.data.map(e=>({name:e.full_name,value:e.full_name})),0);i=t.data.find(e=>e.full_name===n)}i?(A(`default_repo`,{id:i.id,name:i.name,full_name:i.full_name,private:i.private}),this.info(`INFO: ${t.log(i.full_name,`blue`,!1)} has been set as the default repository.`).newLine()):(A(`default_repo`,M(`default_repo`)??{}),this.warn(`INFO: No repository selected. Default repository has been cleared.`).newLine())}};const $=w.create({baseURL:`https://api.github.com`,headers:{"Content-Type":`application/json`}}),Oe=()=>{let[e]=F(),t=e();$.defaults.baseURL=t.apiBaseURL||`https://api.github.com`,$.defaults.timeout=t.timeoutDuration||3e3};$.interceptors.request.use(e=>{let[t]=F(),[n]=P(),r=t(),i=n().getVerbosity();return(r.debug||i>1)&&((r.debug||i>=2)&&(console.log(`Request URL:`,e.url),console.log(`Request Method:`,e.method)),(r.debug||i==3)&&(console.log(`Request Headers:`,e.headers),console.log(`Request Data:`,e.data)),console.log(`Error Response URL:`,w.getUri(e))),e},e=>Promise.reject(e)),$.interceptors.response.use(e=>{let[t]=F(),[n]=P(),r=t(),i=n().getVerbosity();if(r.debug||i>1){let{data:t,status:n,statusText:a,headers:o}=e;(r.debug||i>=2)&&(console.log(`Response Data:`,t),console.log(`Response Status:`,n)),(r.debug||i===3)&&(console.log(`Response Status Text:`,a),console.log(`Response Headers:`,o)),console.log(`Error Response URL:`,w.getUri(e.config))}return e},e=>{let[t]=F(),[n]=P(),r=t(),i=n().getVerbosity();if(r.debug||i>1)if(e.response){let{data:t,status:n,headers:a}=e.response;(r.debug||i>=2)&&(console.log(`Error Response Data:`,t),console.log(`Error Response Status:`,n)),(r.debug||i===3)&&console.log(`Error Response Headers:`,a),console.log(`Error Response URL:`,w.getUri(e.config))}else console.log(`Error Message:`,e.message);return Promise.reject(e)});var ke=`
|
|
1926
97
|
▗▄▄▖▗▄▄▖ ▗▄▄▄▖▗▄▄▄▖▗▖ ▗▖▗▖ ▗▖▗▄▄▖
|
|
1927
98
|
▐▌ ▐▌ ▐▌ █ █ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌
|
|
1928
99
|
▐▌▝▜▌▐▛▀▚▖ █ █ ▐▛▀▜▌▐▌ ▐▌▐▛▀▚▖
|
|
1929
100
|
▝▚▄▞▘▐▌ ▐▌▗▄█▄▖ █ ▐▌ ▐▌▝▚▄▞▘▐▙▄▞▘
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
//#endregion
|
|
1933
|
-
//#region src/cli.ts
|
|
1934
|
-
var Application = class {};
|
|
1935
|
-
initAxios();
|
|
1936
|
-
Kernel.init(new Application(), {
|
|
1937
|
-
logo: logo_default,
|
|
1938
|
-
exceptionHandler(exception) {
|
|
1939
|
-
const [getConfig] = useConfig();
|
|
1940
|
-
const config$1 = getConfig();
|
|
1941
|
-
console.error(config$1.debug ? exception : exception.message);
|
|
1942
|
-
},
|
|
1943
|
-
baseCommands: [
|
|
1944
|
-
InfoCommand,
|
|
1945
|
-
InitCommand,
|
|
1946
|
-
LoginCommand,
|
|
1947
|
-
LogoutCommand,
|
|
1948
|
-
ConfigCommand,
|
|
1949
|
-
IssuesCommand,
|
|
1950
|
-
SetRepoCommand,
|
|
1951
|
-
IssuesSeedCommand,
|
|
1952
|
-
IssuesUpdateCommand,
|
|
1953
|
-
IssuesDeleteCommand,
|
|
1954
|
-
GenerateApisCommand,
|
|
1955
|
-
...Commands_default()
|
|
1956
|
-
]
|
|
1957
|
-
});
|
|
1958
|
-
|
|
1959
|
-
//#endregion
|
|
1960
|
-
export { };
|
|
101
|
+
`,Ae=class{};Oe(),b.init(new Ae,{logo:ke,exceptionHandler(e){let[t]=F(),n=t();console.error(n.debug?e:e.message)},baseCommands:[_e,ve,Te,Ee,me,ye,De,xe,Se,be,ge,...de()]});export{};
|