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