@spaceflow/core 0.1.3 → 0.3.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/dist/index.js +2642 -3247
- package/dist/index.js.map +1 -1
- package/package.json +2 -13
- package/src/config/config-loader.ts +5 -6
- package/src/config/config-reader.service.ts +6 -11
- package/src/config/config-reader.ts +75 -0
- package/src/config/index.ts +4 -1
- package/src/config/load-env.ts +15 -0
- package/src/config/schema-generator.service.ts +0 -2
- package/src/config/spaceflow.config.ts +7 -20
- package/src/extension-system/define-extension.ts +25 -0
- package/src/extension-system/extension.interface.ts +0 -63
- package/src/extension-system/index.ts +5 -0
- package/src/extension-system/types.ts +201 -0
- package/src/index.ts +15 -18
- package/src/shared/claude-setup/claude-setup.service.ts +3 -8
- package/src/shared/claude-setup/index.ts +0 -1
- package/src/shared/feishu-sdk/feishu-sdk.service.ts +33 -21
- package/src/shared/feishu-sdk/fieshu-card.service.ts +9 -11
- package/src/shared/feishu-sdk/index.ts +0 -1
- package/src/shared/git-provider/adapters/gitea.adapter.ts +59 -22
- package/src/shared/git-provider/adapters/github.adapter.spec.ts +2 -1
- package/src/shared/git-provider/adapters/github.adapter.ts +200 -29
- package/src/shared/git-provider/adapters/gitlab.adapter.ts +78 -26
- package/src/shared/git-provider/git-provider.interface.ts +20 -1
- package/src/shared/git-provider/git-provider.service.ts +28 -6
- package/src/shared/git-provider/index.ts +0 -1
- package/src/shared/git-provider/types.ts +27 -0
- package/src/shared/git-sdk/git-sdk.service.ts +0 -2
- package/src/shared/git-sdk/index.ts +0 -1
- package/src/shared/llm-proxy/adapters/claude-code.adapter.spec.ts +15 -32
- package/src/shared/llm-proxy/adapters/claude-code.adapter.ts +5 -6
- package/src/shared/llm-proxy/adapters/open-code.adapter.ts +1 -3
- package/src/shared/llm-proxy/adapters/openai.adapter.spec.ts +7 -21
- package/src/shared/llm-proxy/adapters/openai.adapter.ts +1 -3
- package/src/shared/llm-proxy/index.ts +0 -1
- package/src/shared/llm-proxy/interfaces/config.interface.ts +8 -0
- package/src/shared/llm-proxy/llm-proxy.service.spec.ts +91 -68
- package/src/shared/llm-proxy/llm-proxy.service.ts +5 -8
- package/src/shared/mcp/index.ts +1 -33
- package/src/shared/output/index.ts +0 -1
- package/src/shared/output/output.service.ts +43 -13
- package/src/shared/rspack-config/rspack-config.ts +0 -6
- package/src/shared/storage/index.ts +0 -1
- package/src/shared/storage/storage.service.ts +16 -8
- package/src/shared/storage/types.ts +0 -44
- package/src/shared/verbose/index.ts +15 -0
- package/src/app.module.ts +0 -18
- package/src/config/config-reader.module.ts +0 -16
- package/src/shared/claude-setup/claude-setup.module.ts +0 -8
- package/src/shared/feishu-sdk/feishu-sdk.module.ts +0 -77
- package/src/shared/git-provider/git-provider.module.ts +0 -73
- package/src/shared/git-sdk/git-sdk.module.ts +0 -8
- package/src/shared/llm-proxy/llm-proxy.module.ts +0 -140
- package/src/shared/output/output.module.ts +0 -9
- package/src/shared/storage/storage.module.ts +0 -150
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EventEmitter2 } from "@nestjs/event-emitter";
|
|
1
|
+
import { EventEmitter } from "events";
|
|
3
2
|
import * as lark from "@larksuiteoapi/node-sdk";
|
|
4
3
|
import {
|
|
5
4
|
FeishuModuleOptions,
|
|
6
|
-
FEISHU_MODULE_OPTIONS,
|
|
7
5
|
FeishuUser,
|
|
8
6
|
GetUserParams,
|
|
9
7
|
FEISHU_CARD_ACTION_TRIGGER,
|
|
@@ -15,12 +13,39 @@ import {
|
|
|
15
13
|
/**
|
|
16
14
|
* 飞书 API 服务
|
|
17
15
|
*/
|
|
18
|
-
|
|
19
|
-
export class FeishuSdkService implements OnModuleInit, OnModuleDestroy {
|
|
16
|
+
export class FeishuSdkService {
|
|
20
17
|
protected readonly client: lark.Client;
|
|
18
|
+
protected readonly eventEmitter: EventEmitter;
|
|
19
|
+
private eventDispatcher: lark.EventDispatcher | null = null;
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
constructor(protected readonly options: FeishuModuleOptions) {
|
|
22
|
+
this.eventEmitter = new EventEmitter();
|
|
23
|
+
this.client = new lark.Client({
|
|
24
|
+
appId: options.appId,
|
|
25
|
+
appSecret: options.appSecret,
|
|
26
|
+
appType: options.appType === "store" ? lark.AppType.ISV : lark.AppType.SelfBuild,
|
|
27
|
+
domain: options.domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
this.initEventDispatcher();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 注册事件监听器
|
|
35
|
+
*/
|
|
36
|
+
on(eventName: string, listener: (...args: any[]) => void): void {
|
|
37
|
+
this.eventEmitter.on(eventName, listener);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 移除事件监听器
|
|
42
|
+
*/
|
|
43
|
+
off(eventName: string, listener: (...args: any[]) => void): void {
|
|
44
|
+
this.eventEmitter.off(eventName, listener);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private initEventDispatcher(): void {
|
|
48
|
+
this.eventDispatcher = new lark.EventDispatcher({
|
|
24
49
|
encryptKey: "encrypt key",
|
|
25
50
|
}).register<CardEvents>({
|
|
26
51
|
[FEISHU_CARD_ACTION_TRIGGER]: async (data) => {
|
|
@@ -41,23 +66,10 @@ export class FeishuSdkService implements OnModuleInit, OnModuleDestroy {
|
|
|
41
66
|
});
|
|
42
67
|
}
|
|
43
68
|
|
|
44
|
-
|
|
69
|
+
destroy(): void {
|
|
45
70
|
this.eventEmitter.removeAllListeners();
|
|
46
71
|
}
|
|
47
72
|
|
|
48
|
-
constructor(
|
|
49
|
-
@Inject(FEISHU_MODULE_OPTIONS)
|
|
50
|
-
protected readonly options: FeishuModuleOptions,
|
|
51
|
-
protected readonly eventEmitter: EventEmitter2,
|
|
52
|
-
) {
|
|
53
|
-
this.client = new lark.Client({
|
|
54
|
-
appId: options.appId,
|
|
55
|
-
appSecret: options.appSecret,
|
|
56
|
-
appType: options.appType === "store" ? lark.AppType.ISV : lark.AppType.SelfBuild,
|
|
57
|
-
domain: options.domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
73
|
/**
|
|
62
74
|
* 验证飞书配置
|
|
63
75
|
*/
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Injectable } from "@nestjs/common";
|
|
2
1
|
import { FeishuSdkService } from "./feishu-sdk.service";
|
|
3
2
|
import {
|
|
4
3
|
SendCardParams,
|
|
@@ -8,25 +7,24 @@ import {
|
|
|
8
7
|
CardContent,
|
|
9
8
|
type CardActionTriggerCallback,
|
|
10
9
|
} from "./types";
|
|
11
|
-
import { OnEvent, type EventEmitter2 } from "@nestjs/event-emitter";
|
|
12
10
|
import { FEISHU_CARD_ACTION_TRIGGER } from "./types";
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* 飞书卡片消息服务
|
|
16
14
|
* 提供卡片消息的发送、回复、更新功能
|
|
17
15
|
*/
|
|
18
|
-
@Injectable()
|
|
19
16
|
export class FeishuCardService {
|
|
20
|
-
constructor(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
constructor(protected readonly feishuSdkService: FeishuSdkService) {
|
|
18
|
+
// 监听卡片动作触发事件
|
|
19
|
+
feishuSdkService.on(FEISHU_CARD_ACTION_TRIGGER, (event) => {
|
|
20
|
+
this.handleCardActionTrigger(event);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const event_hook = event.header.event_type ?? event.event.action.tag;
|
|
24
|
+
async handleCardActionTrigger(event: CardActionTriggerCallback): Promise<void> {
|
|
25
|
+
const _eventHook = event.header.event_type ?? event.event.action.tag;
|
|
28
26
|
|
|
29
|
-
// console.log(
|
|
27
|
+
// console.log(_eventHook);
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
/**
|
|
@@ -5,28 +5,30 @@ import type {
|
|
|
5
5
|
LockBranchOptions,
|
|
6
6
|
ListPullRequestsOptions,
|
|
7
7
|
} from "../git-provider.interface";
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
8
|
+
import {
|
|
9
|
+
REVIEW_STATE,
|
|
10
|
+
type GitProviderModuleOptions,
|
|
11
|
+
type BranchProtection,
|
|
12
|
+
type CreateBranchProtectionOption,
|
|
13
|
+
type EditBranchProtectionOption,
|
|
14
|
+
type Branch,
|
|
15
|
+
type Repository,
|
|
16
|
+
type PullRequest,
|
|
17
|
+
type PullRequestCommit,
|
|
18
|
+
type ChangedFile,
|
|
19
|
+
type CommitInfo,
|
|
20
|
+
type IssueComment,
|
|
21
|
+
type CreateIssueCommentOption,
|
|
22
|
+
type CreateIssueOption,
|
|
23
|
+
type Issue,
|
|
24
|
+
type CreatePullReviewOption,
|
|
25
|
+
type PullReview,
|
|
26
|
+
type PullReviewComment,
|
|
27
|
+
type Reaction,
|
|
28
|
+
type EditPullRequestOption,
|
|
29
|
+
type User,
|
|
30
|
+
type RepositoryContent,
|
|
31
|
+
type ResolvedThread,
|
|
30
32
|
} from "../types";
|
|
31
33
|
|
|
32
34
|
/**
|
|
@@ -439,6 +441,21 @@ export class GiteaAdapter implements GitProvider {
|
|
|
439
441
|
return this.request<PullReview[]>("GET", `/repos/${owner}/${repo}/pulls/${index}/reviews`);
|
|
440
442
|
}
|
|
441
443
|
|
|
444
|
+
async updatePullReview(
|
|
445
|
+
owner: string,
|
|
446
|
+
repo: string,
|
|
447
|
+
index: number,
|
|
448
|
+
reviewId: number,
|
|
449
|
+
body: string,
|
|
450
|
+
): Promise<PullReview> {
|
|
451
|
+
// Gitea 不支持更新 review,使用删除+创建的方式模拟
|
|
452
|
+
await this.deletePullReview(owner, repo, index, reviewId);
|
|
453
|
+
return this.createPullReview(owner, repo, index, {
|
|
454
|
+
event: REVIEW_STATE.COMMENT,
|
|
455
|
+
body,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
442
459
|
async deletePullReview(
|
|
443
460
|
owner: string,
|
|
444
461
|
repo: string,
|
|
@@ -463,6 +480,14 @@ export class GiteaAdapter implements GitProvider {
|
|
|
463
480
|
);
|
|
464
481
|
}
|
|
465
482
|
|
|
483
|
+
async deletePullReviewComment(owner: string, repo: string, commentId: number): Promise<void> {
|
|
484
|
+
await this.request<void>("DELETE", `/repos/${owner}/${repo}/pulls/comments/${commentId}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async listResolvedThreads(): Promise<ResolvedThread[]> {
|
|
488
|
+
return [];
|
|
489
|
+
}
|
|
490
|
+
|
|
466
491
|
// ============ Reaction 操作 ============
|
|
467
492
|
|
|
468
493
|
async getIssueCommentReactions(
|
|
@@ -476,6 +501,18 @@ export class GiteaAdapter implements GitProvider {
|
|
|
476
501
|
);
|
|
477
502
|
}
|
|
478
503
|
|
|
504
|
+
async getPullReviewCommentReactions(
|
|
505
|
+
owner: string,
|
|
506
|
+
repo: string,
|
|
507
|
+
commentId: number,
|
|
508
|
+
): Promise<Reaction[]> {
|
|
509
|
+
// Gitea: PR review comment reactions 使用与 issue comment 相同的路径
|
|
510
|
+
return this.request<Reaction[]>(
|
|
511
|
+
"GET",
|
|
512
|
+
`/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
479
516
|
async getIssueReactions(owner: string, repo: string, index: number): Promise<Reaction[]> {
|
|
480
517
|
return this.request<Reaction[]>("GET", `/repos/${owner}/${repo}/issues/${index}/reactions`);
|
|
481
518
|
}
|
|
@@ -300,7 +300,8 @@ describe("GithubAdapter", () => {
|
|
|
300
300
|
});
|
|
301
301
|
const body = JSON.parse(fetchSpy.mock.calls[0][1].body);
|
|
302
302
|
expect(body.event).toBe("COMMENT");
|
|
303
|
-
expect(body.comments[0].
|
|
303
|
+
expect(body.comments[0].line).toBe(10);
|
|
304
|
+
expect(body.comments[0].side).toBe("RIGHT");
|
|
304
305
|
expect(body.comments[0].path).toBe("a.ts");
|
|
305
306
|
});
|
|
306
307
|
});
|
|
@@ -4,30 +4,42 @@ import type {
|
|
|
4
4
|
LockBranchOptions,
|
|
5
5
|
ListPullRequestsOptions,
|
|
6
6
|
} from "../git-provider.interface";
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
7
|
+
import {
|
|
8
|
+
REVIEW_STATE,
|
|
9
|
+
DIFF_SIDE,
|
|
10
|
+
type GitProviderModuleOptions,
|
|
11
|
+
type BranchProtection,
|
|
12
|
+
type CreateBranchProtectionOption,
|
|
13
|
+
type EditBranchProtectionOption,
|
|
14
|
+
type Branch,
|
|
15
|
+
type Repository,
|
|
16
|
+
type PullRequest,
|
|
17
|
+
type PullRequestCommit,
|
|
18
|
+
type ChangedFile,
|
|
19
|
+
type CommitInfo,
|
|
20
|
+
type IssueComment,
|
|
21
|
+
type CreateIssueCommentOption,
|
|
22
|
+
type CreateIssueOption,
|
|
23
|
+
type Issue,
|
|
24
|
+
type CreatePullReviewOption,
|
|
25
|
+
type PullReview,
|
|
26
|
+
type PullReviewComment,
|
|
27
|
+
type Reaction,
|
|
28
|
+
type EditPullRequestOption,
|
|
29
|
+
type User,
|
|
30
|
+
type RepositoryContent,
|
|
31
|
+
type ResolvedThread,
|
|
29
32
|
} from "../types";
|
|
30
33
|
|
|
34
|
+
/** GraphQL review thread 节点类型 */
|
|
35
|
+
interface GraphQLReviewThread {
|
|
36
|
+
isResolved: boolean;
|
|
37
|
+
resolvedBy?: { login: string; databaseId: number } | null;
|
|
38
|
+
path?: string | null;
|
|
39
|
+
line?: number | null;
|
|
40
|
+
comments: { nodes: Array<{ databaseId: number }> };
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
/**
|
|
32
44
|
* GitHub 平台适配器
|
|
33
45
|
*/
|
|
@@ -72,6 +84,31 @@ export class GithubAdapter implements GitProvider {
|
|
|
72
84
|
return response.json() as Promise<T>;
|
|
73
85
|
}
|
|
74
86
|
|
|
87
|
+
protected async requestGraphQL<T>(
|
|
88
|
+
query: string,
|
|
89
|
+
variables?: Record<string, unknown>,
|
|
90
|
+
): Promise<T> {
|
|
91
|
+
const graphqlUrl =
|
|
92
|
+
this.baseUrl.replace(/\/v3\/?$/, "").replace(/\/api\/v3\/?$/, "") + "/graphql";
|
|
93
|
+
const response = await fetch(graphqlUrl, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: {
|
|
96
|
+
Authorization: `Bearer ${this.token}`,
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({ query, variables }),
|
|
100
|
+
});
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
const errorText = await response.text();
|
|
103
|
+
throw new Error(`GitHub GraphQL error: ${response.status} - ${errorText}`);
|
|
104
|
+
}
|
|
105
|
+
const json = (await response.json()) as { data: T; errors?: unknown[] };
|
|
106
|
+
if (json.errors) {
|
|
107
|
+
throw new Error(`GitHub GraphQL errors: ${JSON.stringify(json.errors)}`);
|
|
108
|
+
}
|
|
109
|
+
return json.data;
|
|
110
|
+
}
|
|
111
|
+
|
|
75
112
|
protected async fetchText(url: string): Promise<string> {
|
|
76
113
|
const response = await fetch(url, {
|
|
77
114
|
headers: {
|
|
@@ -468,7 +505,8 @@ export class GithubAdapter implements GitProvider {
|
|
|
468
505
|
body.comments = options.comments.map((c) => ({
|
|
469
506
|
path: c.path,
|
|
470
507
|
body: c.body,
|
|
471
|
-
|
|
508
|
+
line: c.new_position,
|
|
509
|
+
side: DIFF_SIDE.RIGHT,
|
|
472
510
|
}));
|
|
473
511
|
}
|
|
474
512
|
const result = await this.request<Record<string, unknown>>(
|
|
@@ -487,6 +525,26 @@ export class GithubAdapter implements GitProvider {
|
|
|
487
525
|
return results.map((r) => this.mapPullReview(r));
|
|
488
526
|
}
|
|
489
527
|
|
|
528
|
+
async updatePullReview(
|
|
529
|
+
owner: string,
|
|
530
|
+
repo: string,
|
|
531
|
+
index: number,
|
|
532
|
+
reviewId: number,
|
|
533
|
+
body: string,
|
|
534
|
+
): Promise<PullReview> {
|
|
535
|
+
// GitHub 的 updatePullReview 只能更新 PENDING 状态的 review
|
|
536
|
+
// 已提交的 review 无法更新,所以使用删除+创建的方式
|
|
537
|
+
try {
|
|
538
|
+
await this.deletePullReview(owner, repo, index, reviewId);
|
|
539
|
+
} catch {
|
|
540
|
+
// 已提交的 review 无法删除,忽略错误
|
|
541
|
+
}
|
|
542
|
+
return this.createPullReview(owner, repo, index, {
|
|
543
|
+
event: REVIEW_STATE.COMMENT,
|
|
544
|
+
body,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
490
548
|
async deletePullReview(
|
|
491
549
|
owner: string,
|
|
492
550
|
repo: string,
|
|
@@ -509,7 +567,23 @@ export class GithubAdapter implements GitProvider {
|
|
|
509
567
|
"GET",
|
|
510
568
|
`/repos/${owner}/${repo}/pulls/${index}/reviews/${reviewId}/comments`,
|
|
511
569
|
);
|
|
512
|
-
|
|
570
|
+
const comments = results.map((c) => this.mapPullReviewComment(c));
|
|
571
|
+
// 通过 GraphQL 补充 resolved 状态
|
|
572
|
+
try {
|
|
573
|
+
const resolvedMap = await this.fetchResolvedThreads(owner, repo, index);
|
|
574
|
+
for (const comment of comments) {
|
|
575
|
+
if (comment.id && resolvedMap.has(comment.id)) {
|
|
576
|
+
comment.resolver = resolvedMap.get(comment.id) ?? null;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
} catch {
|
|
580
|
+
// GraphQL 查询失败不影响主流程
|
|
581
|
+
}
|
|
582
|
+
return comments;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async deletePullReviewComment(owner: string, repo: string, commentId: number): Promise<void> {
|
|
586
|
+
await this.request<void>("DELETE", `/repos/${owner}/${repo}/pulls/comments/${commentId}`);
|
|
513
587
|
}
|
|
514
588
|
|
|
515
589
|
// ============ Reaction 操作 ============
|
|
@@ -526,6 +600,18 @@ export class GithubAdapter implements GitProvider {
|
|
|
526
600
|
return results.map((r) => this.mapReaction(r));
|
|
527
601
|
}
|
|
528
602
|
|
|
603
|
+
async getPullReviewCommentReactions(
|
|
604
|
+
owner: string,
|
|
605
|
+
repo: string,
|
|
606
|
+
commentId: number,
|
|
607
|
+
): Promise<Reaction[]> {
|
|
608
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
609
|
+
"GET",
|
|
610
|
+
`/repos/${owner}/${repo}/pulls/comments/${commentId}/reactions`,
|
|
611
|
+
);
|
|
612
|
+
return results.map((r) => this.mapReaction(r));
|
|
613
|
+
}
|
|
614
|
+
|
|
529
615
|
async getIssueReactions(owner: string, repo: string, index: number): Promise<Reaction[]> {
|
|
530
616
|
const results = await this.request<Array<Record<string, unknown>>>(
|
|
531
617
|
"GET",
|
|
@@ -722,6 +808,91 @@ export class GithubAdapter implements GitProvider {
|
|
|
722
808
|
};
|
|
723
809
|
}
|
|
724
810
|
|
|
811
|
+
/**
|
|
812
|
+
* 通过 GraphQL 查询 PR 的 review threads resolved 状态
|
|
813
|
+
* 返回 Map<commentId, resolver>(用于 listPullReviewComments 内部补充 resolver)
|
|
814
|
+
*/
|
|
815
|
+
protected async fetchResolvedThreads(
|
|
816
|
+
owner: string,
|
|
817
|
+
repo: string,
|
|
818
|
+
prNumber: number,
|
|
819
|
+
): Promise<Map<number, { id?: number; login?: string } | null>> {
|
|
820
|
+
const threads = await this.queryReviewThreads(owner, repo, prNumber);
|
|
821
|
+
const resolvedMap = new Map<number, { id?: number; login?: string } | null>();
|
|
822
|
+
for (const thread of threads) {
|
|
823
|
+
if (!thread.isResolved) continue;
|
|
824
|
+
const firstComment = thread.comments.nodes[0];
|
|
825
|
+
if (!firstComment?.databaseId) continue;
|
|
826
|
+
resolvedMap.set(
|
|
827
|
+
firstComment.databaseId,
|
|
828
|
+
thread.resolvedBy
|
|
829
|
+
? { id: thread.resolvedBy.databaseId, login: thread.resolvedBy.login }
|
|
830
|
+
: null,
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
return resolvedMap;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
async listResolvedThreads(owner: string, repo: string, index: number): Promise<ResolvedThread[]> {
|
|
837
|
+
const threads = await this.queryReviewThreads(owner, repo, index);
|
|
838
|
+
const result: ResolvedThread[] = [];
|
|
839
|
+
for (const thread of threads) {
|
|
840
|
+
if (!thread.isResolved) continue;
|
|
841
|
+
result.push({
|
|
842
|
+
path: thread.path ?? undefined,
|
|
843
|
+
line: thread.line ?? undefined,
|
|
844
|
+
resolvedBy: thread.resolvedBy
|
|
845
|
+
? { id: thread.resolvedBy.databaseId, login: thread.resolvedBy.login }
|
|
846
|
+
: null,
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
return result;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* GraphQL 查询 PR 的所有 review threads
|
|
854
|
+
*/
|
|
855
|
+
protected async queryReviewThreads(
|
|
856
|
+
owner: string,
|
|
857
|
+
repo: string,
|
|
858
|
+
prNumber: number,
|
|
859
|
+
): Promise<GraphQLReviewThread[]> {
|
|
860
|
+
const QUERY = `
|
|
861
|
+
query($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
862
|
+
repository(owner: $owner, name: $repo) {
|
|
863
|
+
pullRequest(number: $prNumber) {
|
|
864
|
+
reviewThreads(first: 100) {
|
|
865
|
+
nodes {
|
|
866
|
+
isResolved
|
|
867
|
+
resolvedBy { login databaseId }
|
|
868
|
+
path
|
|
869
|
+
line
|
|
870
|
+
comments(first: 1) {
|
|
871
|
+
nodes { databaseId }
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
`;
|
|
879
|
+
interface GraphQLResult {
|
|
880
|
+
repository: {
|
|
881
|
+
pullRequest: {
|
|
882
|
+
reviewThreads: {
|
|
883
|
+
nodes: GraphQLReviewThread[];
|
|
884
|
+
};
|
|
885
|
+
};
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
const data = await this.requestGraphQL<GraphQLResult>(QUERY, {
|
|
889
|
+
owner,
|
|
890
|
+
repo,
|
|
891
|
+
prNumber,
|
|
892
|
+
});
|
|
893
|
+
return data.repository.pullRequest.reviewThreads.nodes;
|
|
894
|
+
}
|
|
895
|
+
|
|
725
896
|
protected mapPullReviewComment(data: Record<string, unknown>): PullReviewComment {
|
|
726
897
|
const user = data.user as Record<string, unknown> | undefined;
|
|
727
898
|
return {
|
|
@@ -809,12 +980,12 @@ export class GithubAdapter implements GitProvider {
|
|
|
809
980
|
|
|
810
981
|
protected mapReviewEvent(event?: string): string {
|
|
811
982
|
const eventMap: Record<string, string> = {
|
|
812
|
-
APPROVE:
|
|
813
|
-
REQUEST_CHANGES:
|
|
814
|
-
COMMENT:
|
|
815
|
-
PENDING:
|
|
983
|
+
[REVIEW_STATE.APPROVE]: REVIEW_STATE.APPROVE,
|
|
984
|
+
[REVIEW_STATE.REQUEST_CHANGES]: REVIEW_STATE.REQUEST_CHANGES,
|
|
985
|
+
[REVIEW_STATE.COMMENT]: REVIEW_STATE.COMMENT,
|
|
986
|
+
[REVIEW_STATE.PENDING]: REVIEW_STATE.PENDING,
|
|
816
987
|
};
|
|
817
|
-
return event ? eventMap[event] || event :
|
|
988
|
+
return event ? eventMap[event] || event : REVIEW_STATE.COMMENT;
|
|
818
989
|
}
|
|
819
990
|
|
|
820
991
|
protected mapSortParam(sort: string): string {
|
|
@@ -4,28 +4,30 @@ import type {
|
|
|
4
4
|
LockBranchOptions,
|
|
5
5
|
ListPullRequestsOptions,
|
|
6
6
|
} from "../git-provider.interface";
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
7
|
+
import {
|
|
8
|
+
REVIEW_STATE,
|
|
9
|
+
type GitProviderModuleOptions,
|
|
10
|
+
type BranchProtection,
|
|
11
|
+
type CreateBranchProtectionOption,
|
|
12
|
+
type EditBranchProtectionOption,
|
|
13
|
+
type Branch,
|
|
14
|
+
type Repository,
|
|
15
|
+
type PullRequest,
|
|
16
|
+
type PullRequestCommit,
|
|
17
|
+
type ChangedFile,
|
|
18
|
+
type CommitInfo,
|
|
19
|
+
type IssueComment,
|
|
20
|
+
type CreateIssueCommentOption,
|
|
21
|
+
type CreateIssueOption,
|
|
22
|
+
type Issue,
|
|
23
|
+
type CreatePullReviewOption,
|
|
24
|
+
type PullReview,
|
|
25
|
+
type PullReviewComment,
|
|
26
|
+
type Reaction,
|
|
27
|
+
type EditPullRequestOption,
|
|
28
|
+
type User,
|
|
29
|
+
type RepositoryContent,
|
|
30
|
+
type ResolvedThread,
|
|
29
31
|
} from "../types";
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -553,7 +555,7 @@ export class GitlabAdapter implements GitProvider {
|
|
|
553
555
|
return {
|
|
554
556
|
id: note.id,
|
|
555
557
|
body: note.body,
|
|
556
|
-
state: options.event ||
|
|
558
|
+
state: options.event || REVIEW_STATE.COMMENT,
|
|
557
559
|
user: note.user,
|
|
558
560
|
created_at: note.created_at,
|
|
559
561
|
updated_at: note.updated_at,
|
|
@@ -570,13 +572,13 @@ export class GitlabAdapter implements GitProvider {
|
|
|
570
572
|
}
|
|
571
573
|
}
|
|
572
574
|
// 如果是 APPROVE 事件,调用 approve API
|
|
573
|
-
if (options.event ===
|
|
575
|
+
if (options.event === REVIEW_STATE.APPROVE) {
|
|
574
576
|
await this.request<void>("POST", `/projects/${project}/merge_requests/${index}/approve`);
|
|
575
577
|
}
|
|
576
578
|
return {
|
|
577
579
|
id: 0,
|
|
578
580
|
body: options.body || "",
|
|
579
|
-
state: options.event ||
|
|
581
|
+
state: options.event || REVIEW_STATE.COMMENT,
|
|
580
582
|
};
|
|
581
583
|
}
|
|
582
584
|
|
|
@@ -594,7 +596,7 @@ export class GitlabAdapter implements GitProvider {
|
|
|
594
596
|
return {
|
|
595
597
|
id: note.id,
|
|
596
598
|
body: note.body,
|
|
597
|
-
state:
|
|
599
|
+
state: REVIEW_STATE.COMMENT,
|
|
598
600
|
user: note.user,
|
|
599
601
|
created_at: note.created_at,
|
|
600
602
|
updated_at: note.updated_at,
|
|
@@ -602,6 +604,32 @@ export class GitlabAdapter implements GitProvider {
|
|
|
602
604
|
});
|
|
603
605
|
}
|
|
604
606
|
|
|
607
|
+
async updatePullReview(
|
|
608
|
+
owner: string,
|
|
609
|
+
repo: string,
|
|
610
|
+
index: number,
|
|
611
|
+
reviewId: number,
|
|
612
|
+
body: string,
|
|
613
|
+
): Promise<PullReview> {
|
|
614
|
+
const project = this.encodeProject(owner, repo);
|
|
615
|
+
const result = await this.request<Record<string, unknown>>(
|
|
616
|
+
"PUT",
|
|
617
|
+
`/projects/${project}/merge_requests/${index}/notes/${reviewId}`,
|
|
618
|
+
{ body },
|
|
619
|
+
);
|
|
620
|
+
return {
|
|
621
|
+
id: result.id as number,
|
|
622
|
+
body: result.body as string,
|
|
623
|
+
state: REVIEW_STATE.COMMENT,
|
|
624
|
+
user: result.author
|
|
625
|
+
? {
|
|
626
|
+
id: (result.author as Record<string, unknown>).id as number,
|
|
627
|
+
login: (result.author as Record<string, unknown>).username as string,
|
|
628
|
+
}
|
|
629
|
+
: undefined,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
605
633
|
async deletePullReview(
|
|
606
634
|
owner: string,
|
|
607
635
|
repo: string,
|
|
@@ -645,6 +673,21 @@ export class GitlabAdapter implements GitProvider {
|
|
|
645
673
|
});
|
|
646
674
|
}
|
|
647
675
|
|
|
676
|
+
async deletePullReviewComment(owner: string, repo: string, commentId: number): Promise<void> {
|
|
677
|
+
// GitLab: 删除 MR note
|
|
678
|
+
const project = this.encodeProject(owner, repo);
|
|
679
|
+
// GitLab 删除 note 需要 merge_request_iid,但此处只有 commentId(note_id)
|
|
680
|
+
// 使用全局 note 删除不可行,需要通过其他方式获取 MR iid
|
|
681
|
+
// 暂时忽略,GitLab 场景下行级评论删除不常用
|
|
682
|
+
console.warn(
|
|
683
|
+
`⚠️ GitLab 暂不支持删除单条 review comment (id: ${commentId}, project: ${project})`,
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async listResolvedThreads(): Promise<ResolvedThread[]> {
|
|
688
|
+
return [];
|
|
689
|
+
}
|
|
690
|
+
|
|
648
691
|
// ============ Reaction 操作 ============
|
|
649
692
|
|
|
650
693
|
async getIssueCommentReactions(
|
|
@@ -656,6 +699,15 @@ export class GitlabAdapter implements GitProvider {
|
|
|
656
699
|
return [];
|
|
657
700
|
}
|
|
658
701
|
|
|
702
|
+
async getPullReviewCommentReactions(
|
|
703
|
+
_owner: string,
|
|
704
|
+
_repo: string,
|
|
705
|
+
_commentId: number,
|
|
706
|
+
): Promise<Reaction[]> {
|
|
707
|
+
// GitLab: award emoji on notes(需要 noteable_iid,此处简化返回空)
|
|
708
|
+
return [];
|
|
709
|
+
}
|
|
710
|
+
|
|
659
711
|
async getIssueReactions(owner: string, repo: string, index: number): Promise<Reaction[]> {
|
|
660
712
|
const project = this.encodeProject(owner, repo);
|
|
661
713
|
try {
|