@zhin.js/adapter-github 0.1.64 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/lib/adapter.d.ts +10 -9
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +45 -44
- package/lib/adapter.js.map +1 -1
- package/lib/{bot.d.ts → endpoint.d.ts} +7 -7
- package/lib/endpoint.d.ts.map +1 -0
- package/lib/{bot.js → endpoint.js} +8 -8
- package/lib/endpoint.js.map +1 -0
- package/lib/gh-client.d.ts.map +1 -1
- package/lib/gh-client.js +10 -6
- package/lib/gh-client.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +44 -44
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +3 -3
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +1 -1
- package/lib/types.js.map +1 -1
- package/package.json +5 -4
- package/skills/github/SKILL.md +4 -4
- package/src/adapter.ts +53 -51
- package/src/{bot.ts → endpoint.ts} +10 -10
- package/src/gh-client.ts +16 -12
- package/src/index.ts +47 -47
- package/src/types.ts +4 -4
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js.map +0 -1
package/src/adapter.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { formatCompact, Adapter, Message, Plugin } from 'zhin.js';
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
|
-
import {
|
|
6
|
+
import { GitHubEndpoint } from './endpoint.js';
|
|
7
7
|
import type { Router } from '@zhin.js/host-router';
|
|
8
|
-
import type {
|
|
8
|
+
import type { GitHubEndpointConfig, EventType, GenericWebhookPayload, Subscription } from './types.js';
|
|
9
9
|
import type { GhClient } from './gh-client.js';
|
|
10
10
|
import type { IssueCommentPayload, PRReviewCommentPayload, PRReviewPayload } from './types.js';
|
|
11
11
|
|
|
@@ -69,7 +69,9 @@ function formatNotification(event: string, p: GenericWebhookPayload): string {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export class GitHubAdapter extends Adapter<
|
|
72
|
+
export class GitHubAdapter extends Adapter<GitHubEndpoint> {
|
|
73
|
+
static override readonly capabilities = ['inbound', 'outbound'] as const;
|
|
74
|
+
|
|
73
75
|
/** 轮询定时器 */
|
|
74
76
|
private _pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
75
77
|
/** 每个 repo 的 ETag 缓存 */
|
|
@@ -83,8 +85,8 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
83
85
|
super(plugin, 'github', []);
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
return new
|
|
88
|
+
createEndpoint(config: GitHubEndpointConfig): GitHubEndpoint {
|
|
89
|
+
return new GitHubEndpoint(this, config);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
async start(): Promise<void> {
|
|
@@ -96,15 +98,15 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
96
98
|
await super.stop();
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
/** 获取第一个可用
|
|
101
|
+
/** 获取第一个可用 Endpoint 的 GhClient (工具用) */
|
|
100
102
|
getAPI(): GhClient | null {
|
|
101
|
-
const
|
|
102
|
-
return
|
|
103
|
+
const endpoint = this.endpoints.values().next().value as GitHubEndpoint | undefined;
|
|
104
|
+
return endpoint?.gh || null;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
/** 获取指定用户绑定的 GhClient;未绑定则返回 null */
|
|
106
108
|
async getUserAPI(platform: string, platformUid: string): Promise<GhClient | null> {
|
|
107
|
-
const db = this.plugin.root?.inject('database')
|
|
109
|
+
const db = this.plugin.root?.inject('database');
|
|
108
110
|
const model = db?.models?.get('github_oauth_users');
|
|
109
111
|
if (!model) return null;
|
|
110
112
|
const [record] = await model.select().where({ platform, platform_uid: platformUid });
|
|
@@ -114,7 +116,7 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
114
116
|
return base.withToken(record.access_token);
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
/** 获取用户 API,若未绑定则降级为
|
|
119
|
+
/** 获取用户 API,若未绑定则降级为 Endpoint 默认的 API */
|
|
118
120
|
async getUserOrDefaultAPI(platform?: string, platformUid?: string): Promise<GhClient | null> {
|
|
119
121
|
if (platform && platformUid) {
|
|
120
122
|
const userApi = await this.getUserAPI(platform, platformUid);
|
|
@@ -123,34 +125,34 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
123
125
|
return this.getAPI();
|
|
124
126
|
}
|
|
125
127
|
|
|
126
|
-
/** 获取第一个
|
|
128
|
+
/** 获取第一个 Endpoint 的 client_id(App 认证时从 /app 自动获取) */
|
|
127
129
|
getClientId(): string | null {
|
|
128
|
-
const
|
|
129
|
-
return
|
|
130
|
+
const endpoint = this.endpoints.values().next().value as GitHubEndpoint | undefined;
|
|
131
|
+
return endpoint?.gh.clientId || null;
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
/** 获取第一个
|
|
134
|
+
/** 获取第一个 Endpoint 的 host 配置 */
|
|
133
135
|
getHost(): string | undefined {
|
|
134
|
-
const
|
|
135
|
-
return
|
|
136
|
+
const endpoint = this.endpoints.values().next().value as GitHubEndpoint | undefined;
|
|
137
|
+
return endpoint?.$config.host;
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
/** 获取 App slug(用于生成安装链接) */
|
|
139
141
|
getAppSlug(): string | null {
|
|
140
|
-
const
|
|
141
|
-
return
|
|
142
|
+
const endpoint = this.endpoints.values().next().value as GitHubEndpoint | undefined;
|
|
143
|
+
return endpoint?.gh.appSlug || null;
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
/** 获取所有已发现的安装信息 */
|
|
145
147
|
getInstallations() {
|
|
146
|
-
const
|
|
147
|
-
return
|
|
148
|
+
const endpoint = this.endpoints.values().next().value as GitHubEndpoint | undefined;
|
|
149
|
+
return endpoint?.gh.installations || [];
|
|
148
150
|
}
|
|
149
151
|
|
|
150
|
-
/** 第一个
|
|
152
|
+
/** 第一个 Endpoint 是否配置了 Webhook */
|
|
151
153
|
get hasWebhookConfig(): boolean {
|
|
152
|
-
const
|
|
153
|
-
return !!
|
|
154
|
+
const endpoint = this.endpoints.values().next().value as GitHubEndpoint | undefined;
|
|
155
|
+
return !!endpoint?.$config.webhook_secret;
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
/** Webhook 是否已激活 */
|
|
@@ -162,13 +164,13 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
162
164
|
|
|
163
165
|
/** 在 router 上挂载 Webhook 路由(生产环境推荐) */
|
|
164
166
|
setupWebhook(router: Router): void {
|
|
165
|
-
const
|
|
166
|
-
if (!
|
|
167
|
+
const endpoint = this.endpoints.values().next().value as GitHubEndpoint | undefined;
|
|
168
|
+
if (!endpoint?.$config.webhook_secret) {
|
|
167
169
|
this.plugin.logger.warn(formatCompact( { op: 'webhook', ok: false, error: 'missing webhook_secret' }));
|
|
168
170
|
return;
|
|
169
171
|
}
|
|
170
|
-
const secret =
|
|
171
|
-
const path =
|
|
172
|
+
const secret = endpoint.$config.webhook_secret;
|
|
173
|
+
const path = endpoint.$config.webhook_path || '/github/webhook';
|
|
172
174
|
|
|
173
175
|
router.post(path, async (ctx) => {
|
|
174
176
|
const signature = ctx.get('x-hub-signature-256') as string;
|
|
@@ -207,14 +209,14 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
207
209
|
|
|
208
210
|
/** 处理 Webhook 推送的事件 */
|
|
209
211
|
async handleWebhookPayload(event: string, payload: any): Promise<void> {
|
|
210
|
-
const
|
|
212
|
+
const endpoint = this.endpoints.values().next().value as GitHubEndpoint | undefined;
|
|
211
213
|
const repo = payload.repository?.full_name;
|
|
212
214
|
|
|
213
215
|
this.plugin.logger.debug(`Webhook: ${event}${payload.action ? `.${payload.action}` : ''} ${repo || ''}`);
|
|
214
216
|
|
|
215
217
|
// 记录事件到数据库
|
|
216
218
|
if (repo) {
|
|
217
|
-
const db = this.plugin.root?.inject('database')
|
|
219
|
+
const db = this.plugin.root?.inject('database');
|
|
218
220
|
const eventsModel = db?.models?.get('github_events');
|
|
219
221
|
if (eventsModel) {
|
|
220
222
|
await eventsModel.insert({ id: Date.now(), repo, event_type: event, payload }).catch(() => {});
|
|
@@ -222,26 +224,26 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
222
224
|
}
|
|
223
225
|
|
|
224
226
|
// 处理消息类事件(Issue/PR 评论)
|
|
225
|
-
if (
|
|
226
|
-
const message =
|
|
227
|
-
const botUser =
|
|
227
|
+
if (endpoint && event === 'issue_comment' && payload.action === 'created' && payload.comment) {
|
|
228
|
+
const message = endpoint.$formatMessage(payload as IssueCommentPayload);
|
|
229
|
+
const botUser = endpoint.gh.authenticatedUser;
|
|
228
230
|
if (!(botUser && message.$sender.id === botUser)) {
|
|
229
231
|
this.emit('message.receive', message);
|
|
230
232
|
}
|
|
231
233
|
}
|
|
232
234
|
|
|
233
|
-
if (
|
|
234
|
-
const message =
|
|
235
|
-
const botUser =
|
|
235
|
+
if (endpoint && event === 'pull_request_review_comment' && payload.action === 'created' && payload.comment) {
|
|
236
|
+
const message = endpoint.formatPRReviewComment(payload as PRReviewCommentPayload);
|
|
237
|
+
const botUser = endpoint.gh.authenticatedUser;
|
|
236
238
|
if (!(botUser && message.$sender.id === botUser)) {
|
|
237
239
|
this.emit('message.receive', message);
|
|
238
240
|
}
|
|
239
241
|
}
|
|
240
242
|
|
|
241
|
-
if (
|
|
242
|
-
const message =
|
|
243
|
+
if (endpoint && event === 'pull_request_review' && payload.action === 'submitted') {
|
|
244
|
+
const message = endpoint.formatPRReview(payload as PRReviewPayload);
|
|
243
245
|
if (message) {
|
|
244
|
-
const botUser =
|
|
246
|
+
const botUser = endpoint.gh.authenticatedUser;
|
|
245
247
|
if (!(botUser && message.$sender.id === botUser)) {
|
|
246
248
|
this.emit('message.receive', message);
|
|
247
249
|
}
|
|
@@ -269,8 +271,8 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
269
271
|
/** 启动事件轮询 */
|
|
270
272
|
startPolling(): void {
|
|
271
273
|
if (this._pollTimer) return;
|
|
272
|
-
const
|
|
273
|
-
const interval = (
|
|
274
|
+
const endpoint = this.endpoints.values().next().value as GitHubEndpoint | undefined;
|
|
275
|
+
const interval = (endpoint?.$config.poll_interval || 60) * 1000;
|
|
274
276
|
this.plugin.logger.debug(formatCompact( { op: 'poll', interval_s: interval / 1000 }));
|
|
275
277
|
|
|
276
278
|
// 立即执行一次
|
|
@@ -292,7 +294,7 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
292
294
|
|
|
293
295
|
/** 轮询所有已订阅仓库的事件 */
|
|
294
296
|
private async pollAllRepos(): Promise<void> {
|
|
295
|
-
const db = this.plugin.root?.inject('database')
|
|
297
|
+
const db = this.plugin.root?.inject('database');
|
|
296
298
|
const model = db?.models?.get('github_subscriptions');
|
|
297
299
|
if (!model) return;
|
|
298
300
|
|
|
@@ -339,7 +341,7 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
339
341
|
|
|
340
342
|
this.plugin.logger.debug(`${repo}: ${newEvents.length} 条新事件`);
|
|
341
343
|
|
|
342
|
-
const
|
|
344
|
+
const endpoint = this.endpoints.values().next().value as GitHubEndpoint | undefined;
|
|
343
345
|
|
|
344
346
|
// 按时间正序处理(API 返回倒序)
|
|
345
347
|
for (const ev of newEvents.reverse()) {
|
|
@@ -359,16 +361,16 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
359
361
|
};
|
|
360
362
|
|
|
361
363
|
// 记录事件到数据库
|
|
362
|
-
const db = this.plugin.root?.inject('database')
|
|
364
|
+
const db = this.plugin.root?.inject('database');
|
|
363
365
|
const eventsModel = db?.models?.get('github_events');
|
|
364
366
|
if (eventsModel) {
|
|
365
367
|
await eventsModel.insert({ id: Date.now(), repo, event_type: eventName, payload: ev.payload }).catch(() => {});
|
|
366
368
|
}
|
|
367
369
|
|
|
368
370
|
// 处理消息类事件(Issue/PR 评论)
|
|
369
|
-
if (
|
|
370
|
-
const message =
|
|
371
|
-
const botUser =
|
|
371
|
+
if (endpoint && eventName === 'issue_comment' && ev.payload?.action === 'created' && ev.payload?.comment) {
|
|
372
|
+
const message = endpoint.$formatMessage(ev.payload as IssueCommentPayload);
|
|
373
|
+
const botUser = endpoint.gh.authenticatedUser;
|
|
372
374
|
if (!(botUser && message.$sender.id === botUser)) {
|
|
373
375
|
this.emit('message.receive', message);
|
|
374
376
|
}
|
|
@@ -411,7 +413,7 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
411
413
|
}
|
|
412
414
|
|
|
413
415
|
const repo = payload.repository.full_name;
|
|
414
|
-
const db = this.plugin.root?.inject('database')
|
|
416
|
+
const db = this.plugin.root?.inject('database');
|
|
415
417
|
const model = db?.models?.get('github_subscriptions');
|
|
416
418
|
if (!model) {
|
|
417
419
|
this.plugin.logger.warn(formatCompact( { op: 'notify', ok: false, error: 'subscriptions model not ready' }));
|
|
@@ -431,13 +433,13 @@ export class GitHubAdapter extends Adapter<GitHubBot> {
|
|
|
431
433
|
continue;
|
|
432
434
|
}
|
|
433
435
|
try {
|
|
434
|
-
const
|
|
435
|
-
if (!
|
|
436
|
+
const targetAdapter = this.plugin.root?.inject(s.adapter as keyof Plugin.Contexts);
|
|
437
|
+
if (!(targetAdapter instanceof Adapter)) {
|
|
436
438
|
this.plugin.logger.warn(formatCompact( { op: 'notify', ok: false, adapter: s.adapter, error: 'no sendMessage' }));
|
|
437
439
|
continue;
|
|
438
440
|
}
|
|
439
|
-
this.plugin.logger.debug(formatCompact( { op: 'notify', event: eventType, adapter: s.adapter,
|
|
440
|
-
await
|
|
441
|
+
this.plugin.logger.debug(formatCompact( { op: 'notify', event: eventType, adapter: s.adapter, endpoint: s.endpoint, target: s.target_id }));
|
|
442
|
+
await targetAdapter.sendMessage({ context: s.adapter, endpoint: s.endpoint, id: s.target_id, type: s.target_type, content: text });
|
|
441
443
|
} catch (e) {
|
|
442
444
|
this.plugin.logger.error(`通知推送失败 → ${s.adapter}:${s.target_id}`, e);
|
|
443
445
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GitHub
|
|
2
|
+
* GitHub Endpoint 实现(基于 gh CLI)
|
|
3
3
|
*/
|
|
4
|
-
import { formatCompact,
|
|
4
|
+
import { formatCompact, Endpoint, Message, segment, SendContent, SendOptions, type MessageSegment } from 'zhin.js';
|
|
5
5
|
import type {
|
|
6
|
-
|
|
6
|
+
GitHubEndpointConfig,
|
|
7
7
|
IssueCommentPayload,
|
|
8
8
|
PRReviewCommentPayload,
|
|
9
9
|
PRReviewPayload,
|
|
@@ -40,7 +40,7 @@ export function toMarkdown(content: SendContent): string {
|
|
|
40
40
|
}).join('');
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export class
|
|
43
|
+
export class GitHubEndpoint implements Endpoint<GitHubEndpointConfig, IssueCommentPayload> {
|
|
44
44
|
$connected = false;
|
|
45
45
|
gh: GhClient;
|
|
46
46
|
|
|
@@ -50,7 +50,7 @@ export class GitHubBot implements Bot<GitHubBotConfig, IssueCommentPayload> {
|
|
|
50
50
|
return this.adapter.plugin.logger;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
constructor(public adapter: GitHubAdapter, public $config:
|
|
53
|
+
constructor(public adapter: GitHubAdapter, public $config: GitHubEndpointConfig) {
|
|
54
54
|
const { host, app_id, private_key } = $config;
|
|
55
55
|
const appAuth = app_id && private_key
|
|
56
56
|
? { appId: app_id, privateKey: private_key }
|
|
@@ -62,12 +62,12 @@ export class GitHubBot implements Bot<GitHubBotConfig, IssueCommentPayload> {
|
|
|
62
62
|
const result = await this.gh.verifyAuth();
|
|
63
63
|
if (!result.ok) throw new Error(`GitHub 认证失败: ${result.message}`);
|
|
64
64
|
this.$connected = true;
|
|
65
|
-
this.logger.info(formatCompact({
|
|
65
|
+
this.logger.info(formatCompact({ endpoint: this.$id }));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
async $disconnect(): Promise<void> {
|
|
69
69
|
this.$connected = false;
|
|
70
|
-
this.logger.debug(formatCompact({
|
|
70
|
+
this.logger.debug(formatCompact({ endpoint: this.$id, disconnect: true }));
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
$formatMessage(payload: IssueCommentPayload): Message<IssueCommentPayload> {
|
|
@@ -80,7 +80,7 @@ export class GitHubBot implements Bot<GitHubBotConfig, IssueCommentPayload> {
|
|
|
80
80
|
const result = Message.from(payload, {
|
|
81
81
|
$id: payload.comment.id.toString(),
|
|
82
82
|
$adapter: 'github',
|
|
83
|
-
$
|
|
83
|
+
$endpoint: this.$config.name,
|
|
84
84
|
$sender: { id: payload.sender.login, name: payload.sender.login },
|
|
85
85
|
$channel: { id: channelId, type: 'group' },
|
|
86
86
|
$content: parseMarkdown(payload.comment.body),
|
|
@@ -110,7 +110,7 @@ export class GitHubBot implements Bot<GitHubBotConfig, IssueCommentPayload> {
|
|
|
110
110
|
return Message.from(payload, {
|
|
111
111
|
$id: payload.comment.id.toString(),
|
|
112
112
|
$adapter: 'github',
|
|
113
|
-
$
|
|
113
|
+
$endpoint: this.$config.name,
|
|
114
114
|
$sender: { id: payload.sender.login, name: payload.sender.login },
|
|
115
115
|
$channel: { id: channelId, type: 'group' },
|
|
116
116
|
$content: parseMarkdown(body),
|
|
@@ -140,7 +140,7 @@ export class GitHubBot implements Bot<GitHubBotConfig, IssueCommentPayload> {
|
|
|
140
140
|
return Message.from(payload, {
|
|
141
141
|
$id: payload.review.id.toString(),
|
|
142
142
|
$adapter: 'github',
|
|
143
|
-
$
|
|
143
|
+
$endpoint: this.$config.name,
|
|
144
144
|
$sender: { id: payload.sender.login, name: payload.sender.login },
|
|
145
145
|
$channel: { id: channelId, type: 'group' },
|
|
146
146
|
$content: parseMarkdown(body),
|
package/src/gh-client.ts
CHANGED
|
@@ -162,12 +162,13 @@ export class GhClient {
|
|
|
162
162
|
let data: any;
|
|
163
163
|
try { data = raw ? JSON.parse(raw) : null; } catch { data = raw; }
|
|
164
164
|
return { ok: true, status: 200, data };
|
|
165
|
-
} catch (err:
|
|
165
|
+
} catch (err: unknown) {
|
|
166
166
|
let data: any;
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
const errObj = err instanceof Error ? err : new Error(String(err));
|
|
168
|
+
try { data = (err as any).stdout ? JSON.parse((err as any).stdout) : { message: errObj.message }; } catch {
|
|
169
|
+
data = { message: (err as any).stderr?.trim() || errObj.message };
|
|
169
170
|
}
|
|
170
|
-
return { ok: false, status: err.exitCode || 0, data };
|
|
171
|
+
return { ok: false, status: (err as any).exitCode || 0, data };
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
@@ -205,8 +206,8 @@ export class GhClient {
|
|
|
205
206
|
}
|
|
206
207
|
const errBody = await appRes.text();
|
|
207
208
|
return { ok: false, user: '', message: `App Token 验证失败 (${appRes.status}): ${errBody}` };
|
|
208
|
-
} catch (e:
|
|
209
|
-
return { ok: false, user: '', message: e.message || 'App 认证失败' };
|
|
209
|
+
} catch (e: unknown) {
|
|
210
|
+
return { ok: false, user: '', message: (e instanceof Error ? e.message : String(e)) || 'App 认证失败' };
|
|
210
211
|
}
|
|
211
212
|
}
|
|
212
213
|
// Token 模式(用户绑定)或 gh CLI 默认模式
|
|
@@ -225,8 +226,8 @@ export class GhClient {
|
|
|
225
226
|
const login = raw.trim();
|
|
226
227
|
this._user = login;
|
|
227
228
|
return { ok: true, user: login, message: `gh CLI: ${login}` };
|
|
228
|
-
} catch (e:
|
|
229
|
-
return { ok: false, user: '', message: e.message || 'gh 认证检查失败' };
|
|
229
|
+
} catch (e: unknown) {
|
|
230
|
+
return { ok: false, user: '', message: (e instanceof Error ? e.message : String(e)) || 'gh 认证检查失败' };
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
233
|
|
|
@@ -469,9 +470,10 @@ export class GhClient {
|
|
|
469
470
|
let events: any[];
|
|
470
471
|
try { events = JSON.parse(bodyPart); } catch { events = []; }
|
|
471
472
|
return { events: Array.isArray(events) ? events : [], etag: newEtag };
|
|
472
|
-
} catch (err:
|
|
473
|
+
} catch (err: unknown) {
|
|
473
474
|
// 304 Not Modified — gh 会以非 0 退出码返回
|
|
474
|
-
|
|
475
|
+
const ghErr = err instanceof Error ? (err as Error & { stderr?: string; exitCode?: number }) : null;
|
|
476
|
+
if (ghErr?.stderr?.includes('304') || (ghErr as { exitCode?: number } | null)?.exitCode === 1) {
|
|
475
477
|
return { events: [], etag: etag || null };
|
|
476
478
|
}
|
|
477
479
|
throw err;
|
|
@@ -506,9 +508,11 @@ export class GhClient {
|
|
|
506
508
|
body: JSON.stringify({ client_id: clientId }),
|
|
507
509
|
signal: AbortSignal.timeout(15_000),
|
|
508
510
|
});
|
|
509
|
-
} catch (e:
|
|
511
|
+
} catch (e: unknown) {
|
|
512
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
513
|
+
const errCause = e instanceof Error ? (e as any).cause?.code : undefined;
|
|
510
514
|
throw new Error(
|
|
511
|
-
`Device Flow 请求失败: 无法连接 ${baseUrl} (${
|
|
515
|
+
`Device Flow 请求失败: 无法连接 ${baseUrl} (${errCause || errMsg})。` +
|
|
512
516
|
`Device Flow 需要访问 github.com(非 api.github.com),请检查网络/代理设置。`,
|
|
513
517
|
);
|
|
514
518
|
}
|