gh-manager-cli 1.16.1 → 1.17.0
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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.17.0](https://github.com/wiiiimm/gh-manager-cli/compare/v1.16.1...v1.17.0) (2025-09-02)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* implement comprehensive logging system ([#17](https://github.com/wiiiimm/gh-manager-cli/issues/17)) ([a447ce1](https://github.com/wiiiimm/gh-manager-cli/commit/a447ce1287be86ce20830ddd96fa9d20ae22743e))
|
|
7
|
+
|
|
1
8
|
## [1.16.1](https://github.com/wiiiimm/gh-manager-cli/compare/v1.16.0...v1.16.1) (2025-09-02)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -29,9 +29,177 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
import { graphql as makeGraphQL } from "@octokit/graphql";
|
|
30
30
|
import { ApolloClient, InMemoryCache, HttpLink, gql } from "@apollo/client/core/index.js";
|
|
31
31
|
import { persistCache } from "apollo3-cache-persist";
|
|
32
|
+
import fs2 from "fs";
|
|
33
|
+
import path2 from "path";
|
|
34
|
+
import envPaths2 from "env-paths";
|
|
35
|
+
|
|
36
|
+
// src/logger.ts
|
|
32
37
|
import fs from "fs";
|
|
33
38
|
import path from "path";
|
|
34
39
|
import envPaths from "env-paths";
|
|
40
|
+
var LOG_COLOURS = {
|
|
41
|
+
[0 /* DEBUG */]: "\x1B[36m",
|
|
42
|
+
// Cyan
|
|
43
|
+
[1 /* INFO */]: "\x1B[37m",
|
|
44
|
+
// White
|
|
45
|
+
[2 /* WARN */]: "\x1B[33m",
|
|
46
|
+
// Yellow
|
|
47
|
+
[3 /* ERROR */]: "\x1B[31m",
|
|
48
|
+
// Red
|
|
49
|
+
[4 /* FATAL */]: "\x1B[35m"
|
|
50
|
+
// Magenta
|
|
51
|
+
};
|
|
52
|
+
var RESET_COLOUR = "\x1B[0m";
|
|
53
|
+
var Logger = class _Logger {
|
|
54
|
+
constructor(config = {}) {
|
|
55
|
+
this.logLevel = config.logLevel ?? (process.env.GH_MANAGER_DEBUG === "1" ? 0 /* DEBUG */ : 1 /* INFO */);
|
|
56
|
+
this.logToFile = config.logToFile ?? true;
|
|
57
|
+
this.logToConsole = config.logToConsole ?? process.env.GH_MANAGER_DEBUG === "1";
|
|
58
|
+
this.maxFileSize = config.maxFileSize ?? 5 * 1024 * 1024;
|
|
59
|
+
this.maxFiles = config.maxFiles ?? 5;
|
|
60
|
+
const paths = envPaths("gh-manager-cli", { suffix: "" });
|
|
61
|
+
this.logDir = config.logDir ?? paths.log;
|
|
62
|
+
if (this.logToFile) {
|
|
63
|
+
fs.mkdirSync(this.logDir, { recursive: true });
|
|
64
|
+
this.logFile = path.join(this.logDir, "gh-manager-cli.log");
|
|
65
|
+
this.initLogFile();
|
|
66
|
+
} else {
|
|
67
|
+
this.logFile = "";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
static getInstance(config) {
|
|
71
|
+
if (!_Logger.instance) {
|
|
72
|
+
_Logger.instance = new _Logger(config);
|
|
73
|
+
}
|
|
74
|
+
return _Logger.instance;
|
|
75
|
+
}
|
|
76
|
+
initLogFile() {
|
|
77
|
+
try {
|
|
78
|
+
if (fs.existsSync(this.logFile)) {
|
|
79
|
+
const stats = fs.statSync(this.logFile);
|
|
80
|
+
if (stats.size >= this.maxFileSize) {
|
|
81
|
+
this.rotateLogFiles();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
this.writeStream = fs.createWriteStream(this.logFile, { flags: "a" });
|
|
85
|
+
} catch (error) {
|
|
86
|
+
this.logToFile = false;
|
|
87
|
+
console.error("Failed to initialise log file:", error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
rotateLogFiles() {
|
|
91
|
+
try {
|
|
92
|
+
if (this.writeStream) {
|
|
93
|
+
this.writeStream.end();
|
|
94
|
+
}
|
|
95
|
+
for (let i = this.maxFiles - 1; i > 0; i--) {
|
|
96
|
+
const oldFile = i === 1 ? this.logFile : `${this.logFile}.${i - 1}`;
|
|
97
|
+
const newFile = `${this.logFile}.${i}`;
|
|
98
|
+
if (fs.existsSync(oldFile)) {
|
|
99
|
+
if (fs.existsSync(newFile)) {
|
|
100
|
+
fs.unlinkSync(newFile);
|
|
101
|
+
}
|
|
102
|
+
fs.renameSync(oldFile, newFile);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error("Failed to rotate log files:", error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
formatTimestamp() {
|
|
110
|
+
const now = /* @__PURE__ */ new Date();
|
|
111
|
+
const year = now.getFullYear();
|
|
112
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
113
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
114
|
+
const hours = String(now.getHours()).padStart(2, "0");
|
|
115
|
+
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
116
|
+
const seconds = String(now.getSeconds()).padStart(2, "0");
|
|
117
|
+
const ms = String(now.getMilliseconds()).padStart(3, "0");
|
|
118
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
|
|
119
|
+
}
|
|
120
|
+
getLevelName(level) {
|
|
121
|
+
const names = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"];
|
|
122
|
+
return names[level] || "UNKNOWN";
|
|
123
|
+
}
|
|
124
|
+
formatMessage(level, message, context) {
|
|
125
|
+
const timestamp = this.formatTimestamp();
|
|
126
|
+
const levelName = this.getLevelName(level);
|
|
127
|
+
const paddedLevel = levelName.padEnd(5);
|
|
128
|
+
let formattedMessage = `[${timestamp}] [${paddedLevel}] ${message}`;
|
|
129
|
+
if (context !== void 0) {
|
|
130
|
+
try {
|
|
131
|
+
const contextStr = typeof context === "object" ? JSON.stringify(context, null, 2) : String(context);
|
|
132
|
+
formattedMessage += `
|
|
133
|
+
${contextStr}`;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
formattedMessage += "\n[Unable to serialise context]";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return formattedMessage;
|
|
139
|
+
}
|
|
140
|
+
log(level, message, context) {
|
|
141
|
+
if (level < this.logLevel) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const formattedMessage = this.formatMessage(level, message, context);
|
|
145
|
+
if (this.logToConsole) {
|
|
146
|
+
const colour = LOG_COLOURS[level];
|
|
147
|
+
console.log(`${colour}${formattedMessage}${RESET_COLOUR}`);
|
|
148
|
+
}
|
|
149
|
+
if (this.logToFile && this.writeStream) {
|
|
150
|
+
this.writeStream.write(formattedMessage + "\n");
|
|
151
|
+
const stats = fs.statSync(this.logFile);
|
|
152
|
+
if (stats.size >= this.maxFileSize) {
|
|
153
|
+
this.rotateLogFiles();
|
|
154
|
+
this.initLogFile();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
debug(message, context) {
|
|
159
|
+
this.log(0 /* DEBUG */, message, context);
|
|
160
|
+
}
|
|
161
|
+
info(message, context) {
|
|
162
|
+
this.log(1 /* INFO */, message, context);
|
|
163
|
+
}
|
|
164
|
+
warn(message, context) {
|
|
165
|
+
this.log(2 /* WARN */, message, context);
|
|
166
|
+
}
|
|
167
|
+
error(message, context) {
|
|
168
|
+
this.log(3 /* ERROR */, message, context);
|
|
169
|
+
}
|
|
170
|
+
fatal(message, context) {
|
|
171
|
+
this.log(4 /* FATAL */, message, context);
|
|
172
|
+
}
|
|
173
|
+
// Method to get log file path
|
|
174
|
+
getLogFilePath() {
|
|
175
|
+
return this.logFile;
|
|
176
|
+
}
|
|
177
|
+
// Method to get all log files
|
|
178
|
+
getLogFiles() {
|
|
179
|
+
if (!this.logToFile) return [];
|
|
180
|
+
const files = [];
|
|
181
|
+
if (fs.existsSync(this.logFile)) {
|
|
182
|
+
files.push(this.logFile);
|
|
183
|
+
}
|
|
184
|
+
for (let i = 1; i < this.maxFiles; i++) {
|
|
185
|
+
const rotatedFile = `${this.logFile}.${i}`;
|
|
186
|
+
if (fs.existsSync(rotatedFile)) {
|
|
187
|
+
files.push(rotatedFile);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return files;
|
|
191
|
+
}
|
|
192
|
+
// Clean up method
|
|
193
|
+
close() {
|
|
194
|
+
if (this.writeStream) {
|
|
195
|
+
this.writeStream.end();
|
|
196
|
+
this.writeStream = void 0;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
var logger = Logger.getInstance();
|
|
201
|
+
|
|
202
|
+
// src/github.ts
|
|
35
203
|
function makeClient(token) {
|
|
36
204
|
return makeGraphQL.defaults({
|
|
37
205
|
headers: { authorization: `token ${token}` }
|
|
@@ -50,22 +218,22 @@ async function makeApolloClient(token) {
|
|
|
50
218
|
const storage = {
|
|
51
219
|
async getItem(key) {
|
|
52
220
|
try {
|
|
53
|
-
const p =
|
|
54
|
-
const file =
|
|
55
|
-
return
|
|
221
|
+
const p = envPaths2("gh-manager-cli").data;
|
|
222
|
+
const file = path2.join(p, "apollo-cache.json");
|
|
223
|
+
return fs2.readFileSync(file, "utf8");
|
|
56
224
|
} catch {
|
|
57
225
|
return null;
|
|
58
226
|
}
|
|
59
227
|
},
|
|
60
228
|
async setItem(key, value) {
|
|
61
229
|
try {
|
|
62
|
-
const p =
|
|
63
|
-
|
|
64
|
-
const file =
|
|
65
|
-
|
|
230
|
+
const p = envPaths2("gh-manager-cli").data;
|
|
231
|
+
fs2.mkdirSync(p, { recursive: true });
|
|
232
|
+
const file = path2.join(p, "apollo-cache.json");
|
|
233
|
+
fs2.writeFileSync(file, value, "utf8");
|
|
66
234
|
if (process.platform !== "win32") {
|
|
67
235
|
try {
|
|
68
|
-
|
|
236
|
+
fs2.chmodSync(file, 384);
|
|
69
237
|
} catch {
|
|
70
238
|
}
|
|
71
239
|
}
|
|
@@ -74,9 +242,9 @@ async function makeApolloClient(token) {
|
|
|
74
242
|
},
|
|
75
243
|
async removeItem(key) {
|
|
76
244
|
try {
|
|
77
|
-
const p =
|
|
78
|
-
const file =
|
|
79
|
-
|
|
245
|
+
const p = envPaths2("gh-manager-cli").data;
|
|
246
|
+
const file = path2.join(p, "apollo-cache.json");
|
|
247
|
+
fs2.unlinkSync(file);
|
|
80
248
|
} catch {
|
|
81
249
|
}
|
|
82
250
|
}
|
|
@@ -91,6 +259,10 @@ async function makeApolloClient(token) {
|
|
|
91
259
|
apolloClientInstance = { client, gql };
|
|
92
260
|
return apolloClientInstance;
|
|
93
261
|
} catch (error) {
|
|
262
|
+
logger.error("Failed to initialize Apollo Client", {
|
|
263
|
+
error: error.message,
|
|
264
|
+
stack: error.stack
|
|
265
|
+
});
|
|
94
266
|
const debug = process.env.GH_MANAGER_DEBUG === "1";
|
|
95
267
|
if (debug) {
|
|
96
268
|
process.stderr.write(`
|
|
@@ -115,8 +287,15 @@ async function getViewerLogin(client) {
|
|
|
115
287
|
}
|
|
116
288
|
`
|
|
117
289
|
);
|
|
118
|
-
|
|
119
|
-
|
|
290
|
+
try {
|
|
291
|
+
logger.debug("Fetching viewer login");
|
|
292
|
+
const res = await client(query);
|
|
293
|
+
logger.info(`Successfully fetched viewer login: ${res.viewer.login}`);
|
|
294
|
+
return res.viewer.login;
|
|
295
|
+
} catch (error) {
|
|
296
|
+
logger.error("Failed to fetch viewer login", { error: error.message, stack: error.stack });
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
120
299
|
}
|
|
121
300
|
async function fetchViewerOrganizations(client) {
|
|
122
301
|
const query = (
|
|
@@ -140,6 +319,9 @@ async function fetchViewerOrganizations(client) {
|
|
|
140
319
|
return res.viewer.organizations.nodes;
|
|
141
320
|
}
|
|
142
321
|
async function checkOrganizationIsEnterprise(client, orgLogin) {
|
|
322
|
+
logger.info("Checking if organization is enterprise", {
|
|
323
|
+
orgLogin
|
|
324
|
+
});
|
|
143
325
|
try {
|
|
144
326
|
const query = (
|
|
145
327
|
/* GraphQL */
|
|
@@ -154,12 +336,23 @@ async function checkOrganizationIsEnterprise(client, orgLogin) {
|
|
|
154
336
|
`
|
|
155
337
|
);
|
|
156
338
|
const res = await client(query, { orgLogin });
|
|
157
|
-
|
|
339
|
+
const isEnterprise = res.organization?.enterpriseOwners?.totalCount > 0;
|
|
340
|
+
logger.info("Organization enterprise status checked", {
|
|
341
|
+
orgLogin,
|
|
342
|
+
isEnterprise
|
|
343
|
+
});
|
|
344
|
+
return isEnterprise;
|
|
158
345
|
} catch (error) {
|
|
159
346
|
return false;
|
|
160
347
|
}
|
|
161
348
|
}
|
|
162
349
|
async function fetchViewerReposPage(client, first, after, orderBy, includeForkTracking = true, ownerAffiliations = ["OWNER"], organizationLogin, privacy) {
|
|
350
|
+
logger.debug("Using Octokit client for fetching repos", {
|
|
351
|
+
first,
|
|
352
|
+
after,
|
|
353
|
+
organizationLogin,
|
|
354
|
+
privacy
|
|
355
|
+
});
|
|
163
356
|
const sortField = orderBy?.field || "UPDATED_AT";
|
|
164
357
|
const sortDirection = orderBy?.direction || "DESC";
|
|
165
358
|
const isOrgContext = !!organizationLogin;
|
|
@@ -245,7 +438,7 @@ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTr
|
|
|
245
438
|
}
|
|
246
439
|
`
|
|
247
440
|
);
|
|
248
|
-
const
|
|
441
|
+
const res = await client(query2, {
|
|
249
442
|
first,
|
|
250
443
|
after: after ?? null,
|
|
251
444
|
sortField,
|
|
@@ -253,13 +446,13 @@ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTr
|
|
|
253
446
|
orgLogin: organizationLogin,
|
|
254
447
|
privacy: privacy ?? null
|
|
255
448
|
});
|
|
256
|
-
const
|
|
449
|
+
const data = res.organization.repositories;
|
|
257
450
|
return {
|
|
258
|
-
nodes:
|
|
259
|
-
endCursor:
|
|
260
|
-
hasNextPage:
|
|
261
|
-
totalCount:
|
|
262
|
-
rateLimit:
|
|
451
|
+
nodes: data.nodes,
|
|
452
|
+
endCursor: data.pageInfo.endCursor,
|
|
453
|
+
hasNextPage: data.pageInfo.hasNextPage,
|
|
454
|
+
totalCount: data.totalCount,
|
|
455
|
+
rateLimit: res.rateLimit
|
|
263
456
|
};
|
|
264
457
|
}
|
|
265
458
|
const query = (
|
|
@@ -344,33 +537,54 @@ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTr
|
|
|
344
537
|
}
|
|
345
538
|
`
|
|
346
539
|
);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
540
|
+
try {
|
|
541
|
+
const res = await client(query, {
|
|
542
|
+
first,
|
|
543
|
+
after: after ?? null,
|
|
544
|
+
sortField,
|
|
545
|
+
sortDirection,
|
|
546
|
+
affiliations: ownerAffiliations,
|
|
547
|
+
privacy: privacy ?? null
|
|
548
|
+
});
|
|
549
|
+
const data = res.viewer.repositories;
|
|
550
|
+
logger.info(`Octokit successfully fetched ${data.nodes.length} repositories`);
|
|
551
|
+
return {
|
|
552
|
+
nodes: data.nodes,
|
|
553
|
+
endCursor: data.pageInfo.endCursor,
|
|
554
|
+
hasNextPage: data.pageInfo.hasNextPage,
|
|
555
|
+
totalCount: data.totalCount,
|
|
556
|
+
rateLimit: res.rateLimit
|
|
557
|
+
};
|
|
558
|
+
} catch (error) {
|
|
559
|
+
logger.error("Octokit query failed", {
|
|
560
|
+
error: error.message,
|
|
561
|
+
stack: error.stack,
|
|
562
|
+
status: error.status,
|
|
563
|
+
response: error.response
|
|
564
|
+
});
|
|
565
|
+
throw error;
|
|
566
|
+
}
|
|
363
567
|
}
|
|
364
568
|
async function fetchViewerReposPageUnified(token, first, after, orderBy, includeForkTracking = true, fetchPolicy = "cache-first", ownerAffiliations = ["OWNER"], organizationLogin, privacy) {
|
|
365
569
|
const isApolloEnabled = true;
|
|
366
570
|
const debug = process.env.GH_MANAGER_DEBUG === "1";
|
|
367
571
|
const isOrgContext = !!organizationLogin;
|
|
572
|
+
logger.info("Fetching repositories", {
|
|
573
|
+
fetchPolicy,
|
|
574
|
+
isOrgContext,
|
|
575
|
+
organizationLogin,
|
|
576
|
+
first,
|
|
577
|
+
after,
|
|
578
|
+
privacy,
|
|
579
|
+
ownerAffiliations
|
|
580
|
+
});
|
|
368
581
|
if (debug) {
|
|
369
582
|
console.log(`\u{1F50D} Apollo enabled: ${isApolloEnabled}, Policy: ${fetchPolicy}, After: ${after || "null"}, Context: ${isOrgContext ? "Organization" : "Personal"}`);
|
|
370
583
|
}
|
|
371
584
|
try {
|
|
372
585
|
if (isApolloEnabled) {
|
|
373
586
|
if (debug) console.log("\u{1F680} Attempting Apollo Client...");
|
|
587
|
+
logger.debug("Attempting to use Apollo Client");
|
|
374
588
|
const ap = await makeApolloClient(token);
|
|
375
589
|
const sortField = orderBy?.field || "UPDATED_AT";
|
|
376
590
|
const sortDirection = orderBy?.direction || "DESC";
|
|
@@ -446,18 +660,28 @@ async function fetchViewerReposPageUnified(token, first, after, orderBy, include
|
|
|
446
660
|
`;
|
|
447
661
|
}
|
|
448
662
|
const startTime = Date.now();
|
|
663
|
+
logger.debug("Executing Apollo query", { variables });
|
|
449
664
|
const res = await ap.client.query({
|
|
450
665
|
query: q,
|
|
451
666
|
variables,
|
|
452
667
|
fetchPolicy
|
|
453
668
|
});
|
|
454
669
|
const duration = Date.now() - startTime;
|
|
670
|
+
logger.info(`Apollo query completed in ${duration}ms`, {
|
|
671
|
+
duration,
|
|
672
|
+
fromCache: res.loading === false && duration < 50,
|
|
673
|
+
networkStatus: res.networkStatus
|
|
674
|
+
});
|
|
455
675
|
if (debug) {
|
|
456
676
|
console.log(`\u26A1 Apollo query completed in ${duration}ms`);
|
|
457
677
|
console.log(`\u{1F4CA} From cache: ${res.loading === false && duration < 50 ? "YES" : "NO"}`);
|
|
458
678
|
console.log(`\u{1F504} Network status: ${res.networkStatus}`);
|
|
459
679
|
}
|
|
460
680
|
const data = isOrgContext ? res.data.organization.repositories : res.data.viewer.repositories;
|
|
681
|
+
logger.info(`Successfully fetched ${data.nodes.length} repositories`, {
|
|
682
|
+
totalCount: data.totalCount,
|
|
683
|
+
hasNextPage: data.pageInfo.hasNextPage
|
|
684
|
+
});
|
|
461
685
|
return {
|
|
462
686
|
nodes: data.nodes,
|
|
463
687
|
endCursor: data.pageInfo.endCursor,
|
|
@@ -467,8 +691,15 @@ async function fetchViewerReposPageUnified(token, first, after, orderBy, include
|
|
|
467
691
|
};
|
|
468
692
|
}
|
|
469
693
|
} catch (e) {
|
|
694
|
+
logger.error("Apollo query failed", {
|
|
695
|
+
error: e.message,
|
|
696
|
+
stack: e.stack,
|
|
697
|
+
graphQLErrors: e.graphQLErrors,
|
|
698
|
+
networkError: e.networkError
|
|
699
|
+
});
|
|
470
700
|
if (debug) console.log(`\u274C Apollo failed, falling back to Octokit:`, e.message);
|
|
471
701
|
}
|
|
702
|
+
logger.warn("Falling back to Octokit client");
|
|
472
703
|
if (debug) console.log("\u{1F4E1} Using Octokit fallback...");
|
|
473
704
|
const octo = makeClient(token);
|
|
474
705
|
return fetchViewerReposPage(octo, first, after, orderBy, includeForkTracking, ownerAffiliations, organizationLogin, privacy);
|
|
@@ -543,6 +774,11 @@ async function searchRepositoriesUnified(token, viewer, text, first, after, sort
|
|
|
543
774
|
}
|
|
544
775
|
async function deleteRepositoryRest(token, owner, repo) {
|
|
545
776
|
const url = `https://api.github.com/repos/${owner}/${repo}`;
|
|
777
|
+
logger.info("Deleting repository", {
|
|
778
|
+
owner,
|
|
779
|
+
repo,
|
|
780
|
+
url
|
|
781
|
+
});
|
|
546
782
|
const res = await fetch(url, {
|
|
547
783
|
method: "DELETE",
|
|
548
784
|
headers: {
|
|
@@ -551,16 +787,32 @@ async function deleteRepositoryRest(token, owner, repo) {
|
|
|
551
787
|
"User-Agent": "gh-manager-cli"
|
|
552
788
|
}
|
|
553
789
|
});
|
|
554
|
-
if (res.status === 204)
|
|
790
|
+
if (res.status === 204) {
|
|
791
|
+
logger.info("Successfully deleted repository", {
|
|
792
|
+
owner,
|
|
793
|
+
repo,
|
|
794
|
+
status: res.status
|
|
795
|
+
});
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
555
798
|
let msg = `GitHub REST delete failed (status ${res.status})`;
|
|
556
799
|
try {
|
|
557
800
|
const body = await res.json();
|
|
558
801
|
if (body && body.message) msg += `: ${body.message}`;
|
|
559
802
|
} catch {
|
|
560
803
|
}
|
|
804
|
+
logger.error("Failed to delete repository", {
|
|
805
|
+
status: res.status,
|
|
806
|
+
error: msg,
|
|
807
|
+
owner,
|
|
808
|
+
repo
|
|
809
|
+
});
|
|
561
810
|
throw new Error(msg);
|
|
562
811
|
}
|
|
563
812
|
async function archiveRepositoryById(client, repositoryId) {
|
|
813
|
+
logger.info("Archiving repository", {
|
|
814
|
+
repositoryId
|
|
815
|
+
});
|
|
564
816
|
const mutation = (
|
|
565
817
|
/* GraphQL */
|
|
566
818
|
`
|
|
@@ -571,9 +823,24 @@ async function archiveRepositoryById(client, repositoryId) {
|
|
|
571
823
|
}
|
|
572
824
|
`
|
|
573
825
|
);
|
|
574
|
-
|
|
826
|
+
try {
|
|
827
|
+
await client(mutation, { repositoryId });
|
|
828
|
+
logger.info("Successfully archived repository", {
|
|
829
|
+
repositoryId
|
|
830
|
+
});
|
|
831
|
+
} catch (error) {
|
|
832
|
+
logger.error("Failed to archive repository", {
|
|
833
|
+
repositoryId,
|
|
834
|
+
error: error.message,
|
|
835
|
+
stack: error.stack
|
|
836
|
+
});
|
|
837
|
+
throw error;
|
|
838
|
+
}
|
|
575
839
|
}
|
|
576
840
|
async function unarchiveRepositoryById(client, repositoryId) {
|
|
841
|
+
logger.info("Unarchiving repository", {
|
|
842
|
+
repositoryId
|
|
843
|
+
});
|
|
577
844
|
const mutation = (
|
|
578
845
|
/* GraphQL */
|
|
579
846
|
`
|
|
@@ -584,7 +851,19 @@ async function unarchiveRepositoryById(client, repositoryId) {
|
|
|
584
851
|
}
|
|
585
852
|
`
|
|
586
853
|
);
|
|
587
|
-
|
|
854
|
+
try {
|
|
855
|
+
await client(mutation, { repositoryId });
|
|
856
|
+
logger.info("Successfully unarchived repository", {
|
|
857
|
+
repositoryId
|
|
858
|
+
});
|
|
859
|
+
} catch (error) {
|
|
860
|
+
logger.error("Failed to unarchive repository", {
|
|
861
|
+
repositoryId,
|
|
862
|
+
error: error.message,
|
|
863
|
+
stack: error.stack
|
|
864
|
+
});
|
|
865
|
+
throw error;
|
|
866
|
+
}
|
|
588
867
|
}
|
|
589
868
|
async function changeRepositoryVisibility(client, repositoryId, visibility, token) {
|
|
590
869
|
const query = (
|
|
@@ -624,8 +903,22 @@ async function changeRepositoryVisibility(client, repositoryId, visibility, toke
|
|
|
624
903
|
});
|
|
625
904
|
if (!response.ok) {
|
|
626
905
|
const error = await response.text();
|
|
906
|
+
logger.error("Failed to change repository visibility", {
|
|
907
|
+
status: response.status,
|
|
908
|
+
statusText: response.statusText,
|
|
909
|
+
error,
|
|
910
|
+
owner,
|
|
911
|
+
name,
|
|
912
|
+
visibility
|
|
913
|
+
});
|
|
627
914
|
throw new Error(`Failed to change visibility: ${error}`);
|
|
628
915
|
}
|
|
916
|
+
logger.info("Successfully changed repository visibility", {
|
|
917
|
+
owner,
|
|
918
|
+
name,
|
|
919
|
+
newVisibility: visibility,
|
|
920
|
+
nameWithOwner: repo.nameWithOwner
|
|
921
|
+
});
|
|
629
922
|
return { nameWithOwner: repo.nameWithOwner };
|
|
630
923
|
}
|
|
631
924
|
async function getRepositoryFromCache(token, repositoryId) {
|
|
@@ -742,6 +1035,12 @@ async function fetchRepositoryById(client, repositoryId, includeForkTracking = t
|
|
|
742
1035
|
}
|
|
743
1036
|
async function syncForkWithUpstream(token, owner, repo, branch = "main") {
|
|
744
1037
|
const url = `https://api.github.com/repos/${owner}/${repo}/merge-upstream`;
|
|
1038
|
+
logger.info("Syncing fork with upstream", {
|
|
1039
|
+
owner,
|
|
1040
|
+
repo,
|
|
1041
|
+
branch,
|
|
1042
|
+
url
|
|
1043
|
+
});
|
|
745
1044
|
const res = await fetch(url, {
|
|
746
1045
|
method: "POST",
|
|
747
1046
|
headers: {
|
|
@@ -752,10 +1051,24 @@ async function syncForkWithUpstream(token, owner, repo, branch = "main") {
|
|
|
752
1051
|
body: JSON.stringify({ branch })
|
|
753
1052
|
});
|
|
754
1053
|
if (res.status === 204) {
|
|
1054
|
+
logger.info("Fork already up-to-date with upstream", {
|
|
1055
|
+
owner,
|
|
1056
|
+
repo,
|
|
1057
|
+
branch,
|
|
1058
|
+
status: res.status
|
|
1059
|
+
});
|
|
755
1060
|
return { message: "Already up-to-date", merge_type: "none", base_branch: branch };
|
|
756
1061
|
}
|
|
757
1062
|
if (res.status === 200) {
|
|
758
1063
|
const body = await res.json();
|
|
1064
|
+
logger.info("Successfully synced fork with upstream", {
|
|
1065
|
+
owner,
|
|
1066
|
+
repo,
|
|
1067
|
+
branch,
|
|
1068
|
+
status: res.status,
|
|
1069
|
+
mergeType: body.merge_type,
|
|
1070
|
+
message: body.message
|
|
1071
|
+
});
|
|
759
1072
|
return body;
|
|
760
1073
|
}
|
|
761
1074
|
let msg = `Fork sync failed (status ${res.status})`;
|
|
@@ -772,25 +1085,32 @@ async function syncForkWithUpstream(token, owner, repo, branch = "main") {
|
|
|
772
1085
|
}
|
|
773
1086
|
} catch {
|
|
774
1087
|
}
|
|
1088
|
+
logger.error("Failed to sync fork with upstream", {
|
|
1089
|
+
status: res.status,
|
|
1090
|
+
error: msg,
|
|
1091
|
+
owner,
|
|
1092
|
+
repo,
|
|
1093
|
+
branch
|
|
1094
|
+
});
|
|
775
1095
|
throw new Error(msg);
|
|
776
1096
|
}
|
|
777
1097
|
async function purgeApolloCacheFiles() {
|
|
778
1098
|
try {
|
|
779
|
-
const
|
|
780
|
-
const
|
|
781
|
-
const
|
|
782
|
-
const p =
|
|
783
|
-
const cacheFile =
|
|
784
|
-
const metaFile =
|
|
1099
|
+
const fs3 = await import("fs");
|
|
1100
|
+
const path3 = await import("path");
|
|
1101
|
+
const envPaths3 = (await import("env-paths")).default;
|
|
1102
|
+
const p = envPaths3("gh-manager-cli").data;
|
|
1103
|
+
const cacheFile = path3.join(p, "apollo-cache.json");
|
|
1104
|
+
const metaFile = path3.join(p, "apollo-cache-meta.json");
|
|
785
1105
|
if (process.env.GH_MANAGER_DEBUG === "1") {
|
|
786
1106
|
console.log(`\u{1F5D1}\uFE0F Purging cache files from: ${p}`);
|
|
787
1107
|
}
|
|
788
1108
|
try {
|
|
789
|
-
|
|
1109
|
+
fs3.unlinkSync(cacheFile);
|
|
790
1110
|
} catch {
|
|
791
1111
|
}
|
|
792
1112
|
try {
|
|
793
|
-
|
|
1113
|
+
fs3.unlinkSync(metaFile);
|
|
794
1114
|
} catch {
|
|
795
1115
|
}
|
|
796
1116
|
} catch {
|
|
@@ -819,6 +1139,10 @@ async function updateCacheAfterArchive(token, repositoryId, isArchived) {
|
|
|
819
1139
|
}
|
|
820
1140
|
}
|
|
821
1141
|
async function updateCacheAfterVisibilityChange(token, repositoryId, visibility) {
|
|
1142
|
+
logger.info("Updating cache after repository visibility change", {
|
|
1143
|
+
repositoryId,
|
|
1144
|
+
visibility
|
|
1145
|
+
});
|
|
822
1146
|
try {
|
|
823
1147
|
const ap = await makeApolloClient(token);
|
|
824
1148
|
if (!ap || !ap.client) return;
|
|
@@ -889,17 +1213,17 @@ async function updateCacheWithRepository(token, repository) {
|
|
|
889
1213
|
}
|
|
890
1214
|
async function inspectCacheStatus() {
|
|
891
1215
|
try {
|
|
892
|
-
const
|
|
893
|
-
const
|
|
894
|
-
const
|
|
895
|
-
const p =
|
|
896
|
-
const cacheFile =
|
|
897
|
-
const metaFile =
|
|
1216
|
+
const fs3 = await import("fs");
|
|
1217
|
+
const path3 = await import("path");
|
|
1218
|
+
const envPaths3 = (await import("env-paths")).default;
|
|
1219
|
+
const p = envPaths3("gh-manager-cli").data;
|
|
1220
|
+
const cacheFile = path3.join(p, "apollo-cache.json");
|
|
1221
|
+
const metaFile = path3.join(p, "apollo-cache-meta.json");
|
|
898
1222
|
process.stderr.write(`
|
|
899
1223
|
\u{1F4C2} Cache directory: ${p}
|
|
900
1224
|
`);
|
|
901
1225
|
try {
|
|
902
|
-
const cacheStats =
|
|
1226
|
+
const cacheStats = fs3.statSync(cacheFile);
|
|
903
1227
|
process.stderr.write(`\u{1F4BE} Cache file: ${Math.round(cacheStats.size / 1024)}KB (${cacheStats.mtime.toISOString()})
|
|
904
1228
|
`);
|
|
905
1229
|
} catch {
|
|
@@ -907,8 +1231,8 @@ async function inspectCacheStatus() {
|
|
|
907
1231
|
`);
|
|
908
1232
|
}
|
|
909
1233
|
try {
|
|
910
|
-
const metaStats =
|
|
911
|
-
const metaContent =
|
|
1234
|
+
const metaStats = fs3.statSync(metaFile);
|
|
1235
|
+
const metaContent = fs3.readFileSync(metaFile, "utf8");
|
|
912
1236
|
const meta = JSON.parse(metaContent);
|
|
913
1237
|
process.stderr.write(`\u{1F4CA} Meta file: ${Object.keys(meta.fetched || {}).length} entries (${metaStats.mtime.toISOString()})
|
|
914
1238
|
`);
|
|
@@ -935,6 +1259,7 @@ async function inspectCacheStatus() {
|
|
|
935
1259
|
export {
|
|
936
1260
|
__commonJS,
|
|
937
1261
|
__toESM,
|
|
1262
|
+
logger,
|
|
938
1263
|
makeClient,
|
|
939
1264
|
getViewerLogin,
|
|
940
1265
|
fetchViewerOrganizations,
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getRepositoryFromCache,
|
|
12
12
|
getViewerLogin,
|
|
13
13
|
inspectCacheStatus,
|
|
14
|
+
logger,
|
|
14
15
|
makeClient,
|
|
15
16
|
purgeApolloCacheFiles,
|
|
16
17
|
searchRepositoriesUnified,
|
|
@@ -20,14 +21,14 @@ import {
|
|
|
20
21
|
updateCacheAfterDelete,
|
|
21
22
|
updateCacheAfterVisibilityChange,
|
|
22
23
|
updateCacheWithRepository
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-FPJS7YJW.js";
|
|
24
25
|
|
|
25
26
|
// package.json
|
|
26
27
|
var require_package = __commonJS({
|
|
27
28
|
"package.json"(exports, module) {
|
|
28
29
|
module.exports = {
|
|
29
30
|
name: "gh-manager-cli",
|
|
30
|
-
version: "1.
|
|
31
|
+
version: "1.17.0",
|
|
31
32
|
private: false,
|
|
32
33
|
description: "Interactive CLI to manage your GitHub repos (personal) with Ink",
|
|
33
34
|
license: "MIT",
|
|
@@ -484,7 +485,7 @@ function OrgSwitcher({ token, currentContext, onSelect, onClose }) {
|
|
|
484
485
|
try {
|
|
485
486
|
setLoading(true);
|
|
486
487
|
setError(null);
|
|
487
|
-
const client = await import("./github-
|
|
488
|
+
const client = await import("./github-7RR5WPCN.js").then((m) => m.makeClient(token));
|
|
488
489
|
const orgs = await fetchViewerOrganizations(client);
|
|
489
490
|
setOrganizations(orgs);
|
|
490
491
|
const entOrgs = /* @__PURE__ */ new Set();
|
|
@@ -1234,6 +1235,13 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
|
|
|
1234
1235
|
};
|
|
1235
1236
|
React10.useEffect(() => {
|
|
1236
1237
|
addDebugMessage(`[RepoList] Component mounted`);
|
|
1238
|
+
logger.info("RepoList component mounted", {
|
|
1239
|
+
token: token ? "present" : "missing",
|
|
1240
|
+
tokenLength: token?.length,
|
|
1241
|
+
viewerLogin,
|
|
1242
|
+
ownerContext,
|
|
1243
|
+
prefsLoaded
|
|
1244
|
+
});
|
|
1237
1245
|
}, []);
|
|
1238
1246
|
const terminalWidth = stdout?.columns ?? 80;
|
|
1239
1247
|
const availableHeight = maxVisibleRows ?? 20;
|
|
@@ -1473,6 +1481,15 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
|
|
|
1473
1481
|
"stars": "STARGAZERS"
|
|
1474
1482
|
};
|
|
1475
1483
|
const fetchPage = async (after, reset = false, isSortChange = false, overrideForkTracking, policy) => {
|
|
1484
|
+
logger.info("fetchPage called", {
|
|
1485
|
+
after,
|
|
1486
|
+
reset,
|
|
1487
|
+
isSortChange,
|
|
1488
|
+
policy,
|
|
1489
|
+
token: token ? "present" : "missing",
|
|
1490
|
+
viewerLogin,
|
|
1491
|
+
ownerContext
|
|
1492
|
+
});
|
|
1476
1493
|
if (isSortChange) {
|
|
1477
1494
|
setSortingLoading(true);
|
|
1478
1495
|
} else if (after && !reset) {
|
|
@@ -1534,6 +1551,14 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
|
|
|
1534
1551
|
setRateLimit(page.rateLimit);
|
|
1535
1552
|
setError(null);
|
|
1536
1553
|
} catch (e) {
|
|
1554
|
+
logger.error("Failed to fetch repositories in RepoList", {
|
|
1555
|
+
error: e.message,
|
|
1556
|
+
stack: e.stack,
|
|
1557
|
+
graphQLErrors: e.graphQLErrors,
|
|
1558
|
+
networkError: e.networkError,
|
|
1559
|
+
statusCode: e.statusCode,
|
|
1560
|
+
response: e.response
|
|
1561
|
+
});
|
|
1537
1562
|
setError("Failed to load repositories. Check network or token.");
|
|
1538
1563
|
} finally {
|
|
1539
1564
|
setLoading(false);
|
|
@@ -2928,6 +2953,11 @@ function App() {
|
|
|
2928
2953
|
const login = await getViewerLogin(client);
|
|
2929
2954
|
clearTimeout(timeoutId);
|
|
2930
2955
|
setViewer(login);
|
|
2956
|
+
logger.info("User authenticated successfully", {
|
|
2957
|
+
user: login,
|
|
2958
|
+
tokenSource,
|
|
2959
|
+
tokenStored: !getStoredToken()
|
|
2960
|
+
});
|
|
2931
2961
|
setWasRateLimited(false);
|
|
2932
2962
|
setRateLimitReset(null);
|
|
2933
2963
|
if (!getStoredToken()) {
|
|
@@ -2999,6 +3029,10 @@ function App() {
|
|
|
2999
3029
|
setMode("validating");
|
|
3000
3030
|
};
|
|
3001
3031
|
const handleLogout = () => {
|
|
3032
|
+
logger.info("User logged out", {
|
|
3033
|
+
previousUser: viewer,
|
|
3034
|
+
tokenSource
|
|
3035
|
+
});
|
|
3002
3036
|
try {
|
|
3003
3037
|
clearStoredToken();
|
|
3004
3038
|
} catch {
|
|
@@ -3212,16 +3246,39 @@ Env:
|
|
|
3212
3246
|
}
|
|
3213
3247
|
if (process.env.GH_MANAGER_DEBUG === "1") {
|
|
3214
3248
|
process.stderr.write("\u{1F41B} Debug mode enabled\n");
|
|
3249
|
+
logger.debug("Debug mode enabled via GH_MANAGER_DEBUG");
|
|
3215
3250
|
}
|
|
3251
|
+
logger.info("Starting gh-manager-cli", {
|
|
3252
|
+
version: import_package.default?.version || "0.0.0",
|
|
3253
|
+
node: process.version
|
|
3254
|
+
});
|
|
3255
|
+
var handleShutdown = (signal) => {
|
|
3256
|
+
logger.info("Shutting down gh-manager-cli", {
|
|
3257
|
+
signal,
|
|
3258
|
+
uptime: process.uptime()
|
|
3259
|
+
});
|
|
3260
|
+
process.exit(0);
|
|
3261
|
+
};
|
|
3262
|
+
process.on("SIGINT", () => handleShutdown("SIGINT"));
|
|
3263
|
+
process.on("SIGTERM", () => handleShutdown("SIGTERM"));
|
|
3264
|
+
process.on("exit", (code) => {
|
|
3265
|
+
logger.info("gh-manager-cli exited", {
|
|
3266
|
+
exitCode: code,
|
|
3267
|
+
uptime: process.uptime()
|
|
3268
|
+
});
|
|
3269
|
+
});
|
|
3216
3270
|
process.on("uncaughtException", (err) => {
|
|
3271
|
+
logger.fatal("Uncaught exception", { error: err.message, stack: err.stack });
|
|
3217
3272
|
console.error("Unhandled error:", err.message || err);
|
|
3218
3273
|
process.exit(1);
|
|
3219
3274
|
});
|
|
3220
3275
|
process.on("unhandledRejection", (reason) => {
|
|
3276
|
+
logger.fatal("Unhandled rejection", { error: reason?.message || reason, stack: reason?.stack });
|
|
3221
3277
|
console.error("Unhandled rejection:", reason?.message || reason);
|
|
3222
3278
|
process.exit(1);
|
|
3223
3279
|
});
|
|
3224
|
-
|
|
3280
|
+
logger.debug("Rendering UI");
|
|
3281
|
+
var { unmount } = render(
|
|
3225
3282
|
/* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
|
|
3226
3283
|
/* @__PURE__ */ jsx18(App, {}),
|
|
3227
3284
|
/* @__PURE__ */ jsx18(Text18, { color: "gray" })
|