@spaceflow/core 0.1.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/CHANGELOG.md +1176 -0
- package/README.md +105 -0
- package/nest-cli.json +10 -0
- package/package.json +128 -0
- package/rspack.config.mjs +62 -0
- package/src/__mocks__/@opencode-ai/sdk.js +9 -0
- package/src/__mocks__/c12.ts +3 -0
- package/src/app.module.ts +18 -0
- package/src/config/ci.config.ts +29 -0
- package/src/config/config-loader.ts +101 -0
- package/src/config/config-reader.module.ts +16 -0
- package/src/config/config-reader.service.ts +133 -0
- package/src/config/feishu.config.ts +35 -0
- package/src/config/git-provider.config.ts +29 -0
- package/src/config/index.ts +29 -0
- package/src/config/llm.config.ts +110 -0
- package/src/config/schema-generator.service.ts +129 -0
- package/src/config/spaceflow.config.ts +292 -0
- package/src/config/storage.config.ts +33 -0
- package/src/extension-system/extension.interface.ts +221 -0
- package/src/extension-system/index.ts +1 -0
- package/src/index.ts +80 -0
- package/src/locales/en/translation.json +11 -0
- package/src/locales/zh-cn/translation.json +11 -0
- package/src/shared/claude-setup/claude-setup.module.ts +8 -0
- package/src/shared/claude-setup/claude-setup.service.ts +131 -0
- package/src/shared/claude-setup/index.ts +2 -0
- package/src/shared/editor-config/index.ts +23 -0
- package/src/shared/feishu-sdk/feishu-sdk.module.ts +77 -0
- package/src/shared/feishu-sdk/feishu-sdk.service.ts +130 -0
- package/src/shared/feishu-sdk/fieshu-card.service.ts +139 -0
- package/src/shared/feishu-sdk/index.ts +4 -0
- package/src/shared/feishu-sdk/types/card-action.ts +132 -0
- package/src/shared/feishu-sdk/types/card.ts +64 -0
- package/src/shared/feishu-sdk/types/common.ts +22 -0
- package/src/shared/feishu-sdk/types/index.ts +46 -0
- package/src/shared/feishu-sdk/types/message.ts +35 -0
- package/src/shared/feishu-sdk/types/module.ts +21 -0
- package/src/shared/feishu-sdk/types/user.ts +77 -0
- package/src/shared/git-provider/adapters/gitea.adapter.spec.ts +473 -0
- package/src/shared/git-provider/adapters/gitea.adapter.ts +499 -0
- package/src/shared/git-provider/adapters/github.adapter.spec.ts +341 -0
- package/src/shared/git-provider/adapters/github.adapter.ts +830 -0
- package/src/shared/git-provider/adapters/gitlab.adapter.ts +839 -0
- package/src/shared/git-provider/adapters/index.ts +3 -0
- package/src/shared/git-provider/detect-provider.spec.ts +195 -0
- package/src/shared/git-provider/detect-provider.ts +112 -0
- package/src/shared/git-provider/git-provider.interface.ts +188 -0
- package/src/shared/git-provider/git-provider.module.ts +73 -0
- package/src/shared/git-provider/git-provider.service.spec.ts +282 -0
- package/src/shared/git-provider/git-provider.service.ts +309 -0
- package/src/shared/git-provider/index.ts +7 -0
- package/src/shared/git-provider/parse-repo-url.spec.ts +221 -0
- package/src/shared/git-provider/parse-repo-url.ts +155 -0
- package/src/shared/git-provider/types.ts +434 -0
- package/src/shared/git-sdk/git-sdk-diff.utils.spec.ts +344 -0
- package/src/shared/git-sdk/git-sdk-diff.utils.ts +151 -0
- package/src/shared/git-sdk/git-sdk.module.ts +8 -0
- package/src/shared/git-sdk/git-sdk.service.ts +235 -0
- package/src/shared/git-sdk/git-sdk.types.ts +25 -0
- package/src/shared/git-sdk/index.ts +4 -0
- package/src/shared/i18n/i18n.spec.ts +96 -0
- package/src/shared/i18n/i18n.ts +86 -0
- package/src/shared/i18n/index.ts +1 -0
- package/src/shared/i18n/locale-detect.ts +134 -0
- package/src/shared/llm-jsonput/index.ts +94 -0
- package/src/shared/llm-jsonput/types.ts +17 -0
- package/src/shared/llm-proxy/adapters/claude-code.adapter.spec.ts +131 -0
- package/src/shared/llm-proxy/adapters/claude-code.adapter.ts +208 -0
- package/src/shared/llm-proxy/adapters/index.ts +4 -0
- package/src/shared/llm-proxy/adapters/llm-adapter.interface.ts +23 -0
- package/src/shared/llm-proxy/adapters/open-code.adapter.ts +342 -0
- package/src/shared/llm-proxy/adapters/openai.adapter.spec.ts +215 -0
- package/src/shared/llm-proxy/adapters/openai.adapter.ts +153 -0
- package/src/shared/llm-proxy/index.ts +6 -0
- package/src/shared/llm-proxy/interfaces/config.interface.ts +32 -0
- package/src/shared/llm-proxy/interfaces/index.ts +4 -0
- package/src/shared/llm-proxy/interfaces/message.interface.ts +48 -0
- package/src/shared/llm-proxy/interfaces/session.interface.ts +28 -0
- package/src/shared/llm-proxy/llm-proxy.module.ts +140 -0
- package/src/shared/llm-proxy/llm-proxy.service.spec.ts +303 -0
- package/src/shared/llm-proxy/llm-proxy.service.ts +132 -0
- package/src/shared/llm-proxy/llm-session.spec.ts +111 -0
- package/src/shared/llm-proxy/llm-session.ts +109 -0
- package/src/shared/llm-proxy/stream-logger.ts +97 -0
- package/src/shared/logger/index.ts +11 -0
- package/src/shared/logger/logger.interface.ts +93 -0
- package/src/shared/logger/logger.spec.ts +178 -0
- package/src/shared/logger/logger.ts +175 -0
- package/src/shared/logger/renderers/plain.renderer.ts +116 -0
- package/src/shared/logger/renderers/tui.renderer.ts +162 -0
- package/src/shared/mcp/index.ts +332 -0
- package/src/shared/output/index.ts +2 -0
- package/src/shared/output/output.module.ts +9 -0
- package/src/shared/output/output.service.ts +97 -0
- package/src/shared/package-manager/index.ts +115 -0
- package/src/shared/parallel/index.ts +1 -0
- package/src/shared/parallel/parallel-executor.ts +169 -0
- package/src/shared/rspack-config/index.ts +1 -0
- package/src/shared/rspack-config/rspack-config.ts +157 -0
- package/src/shared/source-utils/index.ts +130 -0
- package/src/shared/spaceflow-dir/index.ts +158 -0
- package/src/shared/storage/adapters/file.adapter.ts +113 -0
- package/src/shared/storage/adapters/index.ts +3 -0
- package/src/shared/storage/adapters/memory.adapter.ts +50 -0
- package/src/shared/storage/adapters/storage-adapter.interface.ts +48 -0
- package/src/shared/storage/index.ts +4 -0
- package/src/shared/storage/storage.module.ts +150 -0
- package/src/shared/storage/storage.service.ts +293 -0
- package/src/shared/storage/types.ts +51 -0
- package/src/shared/verbose/index.ts +73 -0
- package/test/app.e2e-spec.ts +22 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +25 -0
- package/tsconfig.skill.json +18 -0
- package/vitest.config.ts +58 -0
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import type {
|
|
3
|
+
GitProvider,
|
|
4
|
+
LockBranchOptions,
|
|
5
|
+
ListPullRequestsOptions,
|
|
6
|
+
} from "../git-provider.interface";
|
|
7
|
+
import type {
|
|
8
|
+
GitProviderModuleOptions,
|
|
9
|
+
BranchProtection,
|
|
10
|
+
CreateBranchProtectionOption,
|
|
11
|
+
EditBranchProtectionOption,
|
|
12
|
+
Branch,
|
|
13
|
+
Repository,
|
|
14
|
+
PullRequest,
|
|
15
|
+
PullRequestCommit,
|
|
16
|
+
ChangedFile,
|
|
17
|
+
CommitInfo,
|
|
18
|
+
IssueComment,
|
|
19
|
+
CreateIssueCommentOption,
|
|
20
|
+
CreateIssueOption,
|
|
21
|
+
Issue,
|
|
22
|
+
CreatePullReviewOption,
|
|
23
|
+
PullReview,
|
|
24
|
+
PullReviewComment,
|
|
25
|
+
Reaction,
|
|
26
|
+
EditPullRequestOption,
|
|
27
|
+
User,
|
|
28
|
+
RepositoryContent,
|
|
29
|
+
} from "../types";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* GitHub 平台适配器
|
|
33
|
+
*/
|
|
34
|
+
export class GithubAdapter implements GitProvider {
|
|
35
|
+
protected readonly baseUrl: string;
|
|
36
|
+
protected readonly token: string;
|
|
37
|
+
|
|
38
|
+
constructor(protected readonly options: GitProviderModuleOptions) {
|
|
39
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
40
|
+
this.token = options.token;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
validateConfig(): void {
|
|
44
|
+
if (!this.options?.baseUrl) {
|
|
45
|
+
throw new Error("缺少配置 gitProvider.baseUrl (环境变量 GIT_PROVIDER_URL)");
|
|
46
|
+
}
|
|
47
|
+
if (!this.options?.token) {
|
|
48
|
+
throw new Error("缺少配置 gitProvider.token (环境变量 GIT_PROVIDER_TOKEN)");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
53
|
+
const url = `${this.baseUrl}${path}`;
|
|
54
|
+
const headers: Record<string, string> = {
|
|
55
|
+
Authorization: `Bearer ${this.token}`,
|
|
56
|
+
Accept: "application/vnd.github+json",
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
59
|
+
};
|
|
60
|
+
const response = await fetch(url, {
|
|
61
|
+
method,
|
|
62
|
+
headers,
|
|
63
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
const errorText = await response.text();
|
|
67
|
+
throw new Error(`GitHub API error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
68
|
+
}
|
|
69
|
+
if (response.status === 204) {
|
|
70
|
+
return {} as T;
|
|
71
|
+
}
|
|
72
|
+
return response.json() as Promise<T>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected async fetchText(url: string): Promise<string> {
|
|
76
|
+
const response = await fetch(url, {
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${this.token}`,
|
|
79
|
+
Accept: "application/vnd.github.v3.diff",
|
|
80
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const errorText = await response.text();
|
|
85
|
+
throw new Error(`GitHub API error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
86
|
+
}
|
|
87
|
+
return response.text();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============ 仓库操作 ============
|
|
91
|
+
|
|
92
|
+
async getRepository(owner: string, repo: string): Promise<Repository> {
|
|
93
|
+
const result = await this.request<Record<string, unknown>>("GET", `/repos/${owner}/${repo}`);
|
|
94
|
+
return this.mapRepository(result);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============ 分支操作 ============
|
|
98
|
+
|
|
99
|
+
async getBranch(owner: string, repo: string, branch: string): Promise<Branch> {
|
|
100
|
+
const result = await this.request<Record<string, unknown>>(
|
|
101
|
+
"GET",
|
|
102
|
+
`/repos/${owner}/${repo}/branches/${encodeURIComponent(branch)}`,
|
|
103
|
+
);
|
|
104
|
+
return this.mapBranch(result);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============ 分支保护 ============
|
|
108
|
+
|
|
109
|
+
async listBranchProtections(owner: string, repo: string): Promise<BranchProtection[]> {
|
|
110
|
+
try {
|
|
111
|
+
const rules = await this.request<Array<Record<string, unknown>>>(
|
|
112
|
+
"GET",
|
|
113
|
+
`/repos/${owner}/${repo}/rules/rulesets`,
|
|
114
|
+
);
|
|
115
|
+
return rules.map((rule) => this.mapRulesetToProtection(rule));
|
|
116
|
+
} catch {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async getBranchProtection(owner: string, repo: string, name: string): Promise<BranchProtection> {
|
|
122
|
+
const result = await this.request<Record<string, unknown>>(
|
|
123
|
+
"GET",
|
|
124
|
+
`/repos/${owner}/${repo}/branches/${encodeURIComponent(name)}/protection`,
|
|
125
|
+
);
|
|
126
|
+
return this.mapGithubProtection(result, name);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async createBranchProtection(
|
|
130
|
+
owner: string,
|
|
131
|
+
repo: string,
|
|
132
|
+
options: CreateBranchProtectionOption,
|
|
133
|
+
): Promise<BranchProtection> {
|
|
134
|
+
const branchName = options.branch_name || options.rule_name || "";
|
|
135
|
+
const body = this.buildGithubProtectionBody(options);
|
|
136
|
+
const result = await this.request<Record<string, unknown>>(
|
|
137
|
+
"PUT",
|
|
138
|
+
`/repos/${owner}/${repo}/branches/${encodeURIComponent(branchName)}/protection`,
|
|
139
|
+
body,
|
|
140
|
+
);
|
|
141
|
+
return this.mapGithubProtection(result, branchName);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async editBranchProtection(
|
|
145
|
+
owner: string,
|
|
146
|
+
repo: string,
|
|
147
|
+
name: string,
|
|
148
|
+
options: EditBranchProtectionOption,
|
|
149
|
+
): Promise<BranchProtection> {
|
|
150
|
+
const body = this.buildGithubProtectionBody(options);
|
|
151
|
+
const result = await this.request<Record<string, unknown>>(
|
|
152
|
+
"PUT",
|
|
153
|
+
`/repos/${owner}/${repo}/branches/${encodeURIComponent(name)}/protection`,
|
|
154
|
+
body,
|
|
155
|
+
);
|
|
156
|
+
return this.mapGithubProtection(result, name);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async deleteBranchProtection(owner: string, repo: string, name: string): Promise<void> {
|
|
160
|
+
await this.request<void>(
|
|
161
|
+
"DELETE",
|
|
162
|
+
`/repos/${owner}/${repo}/branches/${encodeURIComponent(name)}/protection`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async lockBranch(
|
|
167
|
+
owner: string,
|
|
168
|
+
repo: string,
|
|
169
|
+
branch: string,
|
|
170
|
+
options?: LockBranchOptions,
|
|
171
|
+
): Promise<BranchProtection> {
|
|
172
|
+
const pushWhitelistUsernames = options?.pushWhitelistUsernames;
|
|
173
|
+
const hasWhitelist = pushWhitelistUsernames && pushWhitelistUsernames.length > 0;
|
|
174
|
+
const body: Record<string, unknown> = {
|
|
175
|
+
required_status_checks: null,
|
|
176
|
+
enforce_admins: true,
|
|
177
|
+
required_pull_request_reviews: null,
|
|
178
|
+
restrictions: hasWhitelist
|
|
179
|
+
? { users: pushWhitelistUsernames, teams: [] }
|
|
180
|
+
: { users: [], teams: [] },
|
|
181
|
+
};
|
|
182
|
+
const result = await this.request<Record<string, unknown>>(
|
|
183
|
+
"PUT",
|
|
184
|
+
`/repos/${owner}/${repo}/branches/${encodeURIComponent(branch)}/protection`,
|
|
185
|
+
body,
|
|
186
|
+
);
|
|
187
|
+
return this.mapGithubProtection(result, branch);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async unlockBranch(
|
|
191
|
+
owner: string,
|
|
192
|
+
repo: string,
|
|
193
|
+
branch: string,
|
|
194
|
+
): Promise<BranchProtection | null> {
|
|
195
|
+
try {
|
|
196
|
+
const existing = await this.getBranchProtection(owner, repo, branch);
|
|
197
|
+
await this.deleteBranchProtection(owner, repo, branch);
|
|
198
|
+
return existing;
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
unlockBranchSync(owner: string, repo: string, branch: string): void {
|
|
205
|
+
try {
|
|
206
|
+
execSync(
|
|
207
|
+
`curl -s -X DELETE "${this.baseUrl}/repos/${owner}/${repo}/branches/${branch}/protection" -H "Authorization: Bearer ${this.token}" -H "Accept: application/vnd.github+json"`,
|
|
208
|
+
{ encoding: "utf-8" },
|
|
209
|
+
);
|
|
210
|
+
console.log(`✅ 分支已解锁(同步): ${branch}`);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error("⚠️ 同步解锁分支失败:", error instanceof Error ? error.message : error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ============ Pull Request 操作 ============
|
|
217
|
+
|
|
218
|
+
async getPullRequest(owner: string, repo: string, index: number): Promise<PullRequest> {
|
|
219
|
+
const result = await this.request<Record<string, unknown>>(
|
|
220
|
+
"GET",
|
|
221
|
+
`/repos/${owner}/${repo}/pulls/${index}`,
|
|
222
|
+
);
|
|
223
|
+
return this.mapPullRequest(result);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async editPullRequest(
|
|
227
|
+
owner: string,
|
|
228
|
+
repo: string,
|
|
229
|
+
index: number,
|
|
230
|
+
options: EditPullRequestOption,
|
|
231
|
+
): Promise<PullRequest> {
|
|
232
|
+
const result = await this.request<Record<string, unknown>>(
|
|
233
|
+
"PATCH",
|
|
234
|
+
`/repos/${owner}/${repo}/pulls/${index}`,
|
|
235
|
+
options,
|
|
236
|
+
);
|
|
237
|
+
return this.mapPullRequest(result);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async listPullRequests(
|
|
241
|
+
owner: string,
|
|
242
|
+
repo: string,
|
|
243
|
+
state?: "open" | "closed" | "all",
|
|
244
|
+
): Promise<PullRequest[]> {
|
|
245
|
+
const query = state ? `?state=${state}` : "";
|
|
246
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
247
|
+
"GET",
|
|
248
|
+
`/repos/${owner}/${repo}/pulls${query}`,
|
|
249
|
+
);
|
|
250
|
+
return results.map((pr) => this.mapPullRequest(pr));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async listAllPullRequests(
|
|
254
|
+
owner: string,
|
|
255
|
+
repo: string,
|
|
256
|
+
options?: ListPullRequestsOptions,
|
|
257
|
+
): Promise<PullRequest[]> {
|
|
258
|
+
const allPRs: PullRequest[] = [];
|
|
259
|
+
let page = 1;
|
|
260
|
+
const perPage = 100;
|
|
261
|
+
while (true) {
|
|
262
|
+
const params = new URLSearchParams();
|
|
263
|
+
params.set("page", String(page));
|
|
264
|
+
params.set("per_page", String(perPage));
|
|
265
|
+
if (options?.state) params.set("state", options.state);
|
|
266
|
+
if (options?.sort) params.set("sort", this.mapSortParam(options.sort));
|
|
267
|
+
if (options?.labels?.length) params.set("labels", options.labels.join(","));
|
|
268
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
269
|
+
"GET",
|
|
270
|
+
`/repos/${owner}/${repo}/pulls?${params.toString()}`,
|
|
271
|
+
);
|
|
272
|
+
if (!results || results.length === 0) break;
|
|
273
|
+
allPRs.push(...results.map((pr) => this.mapPullRequest(pr)));
|
|
274
|
+
if (results.length < perPage) break;
|
|
275
|
+
page++;
|
|
276
|
+
}
|
|
277
|
+
return allPRs;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async getPullRequestCommits(
|
|
281
|
+
owner: string,
|
|
282
|
+
repo: string,
|
|
283
|
+
index: number,
|
|
284
|
+
): Promise<PullRequestCommit[]> {
|
|
285
|
+
const allCommits: PullRequestCommit[] = [];
|
|
286
|
+
let page = 1;
|
|
287
|
+
const perPage = 100;
|
|
288
|
+
while (true) {
|
|
289
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
290
|
+
"GET",
|
|
291
|
+
`/repos/${owner}/${repo}/pulls/${index}/commits?page=${page}&per_page=${perPage}`,
|
|
292
|
+
);
|
|
293
|
+
if (!results || results.length === 0) break;
|
|
294
|
+
allCommits.push(...results.map((c) => this.mapCommit(c)));
|
|
295
|
+
if (results.length < perPage) break;
|
|
296
|
+
page++;
|
|
297
|
+
}
|
|
298
|
+
return allCommits;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async getPullRequestFiles(owner: string, repo: string, index: number): Promise<ChangedFile[]> {
|
|
302
|
+
const allFiles: ChangedFile[] = [];
|
|
303
|
+
let page = 1;
|
|
304
|
+
const perPage = 100;
|
|
305
|
+
while (true) {
|
|
306
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
307
|
+
"GET",
|
|
308
|
+
`/repos/${owner}/${repo}/pulls/${index}/files?page=${page}&per_page=${perPage}`,
|
|
309
|
+
);
|
|
310
|
+
if (!results || results.length === 0) break;
|
|
311
|
+
allFiles.push(...results.map((f) => this.mapChangedFile(f)));
|
|
312
|
+
if (results.length < perPage) break;
|
|
313
|
+
page++;
|
|
314
|
+
}
|
|
315
|
+
return allFiles;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async getPullRequestDiff(owner: string, repo: string, index: number): Promise<string> {
|
|
319
|
+
return this.fetchText(`${this.baseUrl}/repos/${owner}/${repo}/pulls/${index}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ============ Commit 操作 ============
|
|
323
|
+
|
|
324
|
+
async getCommit(owner: string, repo: string, sha: string): Promise<CommitInfo> {
|
|
325
|
+
const result = await this.request<Record<string, unknown>>(
|
|
326
|
+
"GET",
|
|
327
|
+
`/repos/${owner}/${repo}/commits/${sha}`,
|
|
328
|
+
);
|
|
329
|
+
const commit = this.mapCommit(result);
|
|
330
|
+
const files = ((result.files as Array<Record<string, unknown>>) || []).map((f) =>
|
|
331
|
+
this.mapChangedFile(f),
|
|
332
|
+
);
|
|
333
|
+
return { ...commit, files };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async getCompareDiff(
|
|
337
|
+
owner: string,
|
|
338
|
+
repo: string,
|
|
339
|
+
baseSha: string,
|
|
340
|
+
headSha: string,
|
|
341
|
+
): Promise<string> {
|
|
342
|
+
return this.fetchText(`${this.baseUrl}/repos/${owner}/${repo}/compare/${baseSha}...${headSha}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async getCommitDiff(owner: string, repo: string, sha: string): Promise<string> {
|
|
346
|
+
return this.fetchText(`${this.baseUrl}/repos/${owner}/${repo}/commits/${sha}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ============ 文件操作 ============
|
|
350
|
+
|
|
351
|
+
async getFileContent(
|
|
352
|
+
owner: string,
|
|
353
|
+
repo: string,
|
|
354
|
+
filepath: string,
|
|
355
|
+
ref?: string,
|
|
356
|
+
): Promise<string> {
|
|
357
|
+
const query = ref ? `?ref=${encodeURIComponent(ref)}` : "";
|
|
358
|
+
try {
|
|
359
|
+
const result = await this.request<{ content?: string; encoding?: string }>(
|
|
360
|
+
"GET",
|
|
361
|
+
`/repos/${owner}/${repo}/contents/${filepath}${query}`,
|
|
362
|
+
);
|
|
363
|
+
if (result.content && result.encoding === "base64") {
|
|
364
|
+
return Buffer.from(result.content, "base64").toString("utf-8");
|
|
365
|
+
}
|
|
366
|
+
return result.content || "";
|
|
367
|
+
} catch (error) {
|
|
368
|
+
if (error instanceof Error && error.message.includes("404")) {
|
|
369
|
+
return "";
|
|
370
|
+
}
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async listRepositoryContents(
|
|
376
|
+
owner: string,
|
|
377
|
+
repo: string,
|
|
378
|
+
path = "",
|
|
379
|
+
ref?: string,
|
|
380
|
+
): Promise<RepositoryContent[]> {
|
|
381
|
+
const encodedPath = path ? `/${path}` : "";
|
|
382
|
+
const query = ref ? `?ref=${encodeURIComponent(ref)}` : "";
|
|
383
|
+
const result = await this.request<Array<Record<string, unknown>>>(
|
|
384
|
+
"GET",
|
|
385
|
+
`/repos/${owner}/${repo}/contents${encodedPath}${query}`,
|
|
386
|
+
);
|
|
387
|
+
return result.map((item) => ({
|
|
388
|
+
name: item.name as string,
|
|
389
|
+
path: item.path as string,
|
|
390
|
+
type: (item.type as string) === "dir" ? ("dir" as const) : ("file" as const),
|
|
391
|
+
size: item.size as number,
|
|
392
|
+
download_url: item.download_url as string,
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ============ Issue 操作 ============
|
|
397
|
+
|
|
398
|
+
async createIssue(owner: string, repo: string, options: CreateIssueOption): Promise<Issue> {
|
|
399
|
+
const body: Record<string, unknown> = {
|
|
400
|
+
title: options.title,
|
|
401
|
+
body: options.body,
|
|
402
|
+
assignees: options.assignees,
|
|
403
|
+
labels: options.labels,
|
|
404
|
+
milestone: options.milestone,
|
|
405
|
+
};
|
|
406
|
+
const result = await this.request<Record<string, unknown>>(
|
|
407
|
+
"POST",
|
|
408
|
+
`/repos/${owner}/${repo}/issues`,
|
|
409
|
+
body,
|
|
410
|
+
);
|
|
411
|
+
return this.mapIssue(result);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async listIssueComments(owner: string, repo: string, index: number): Promise<IssueComment[]> {
|
|
415
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
416
|
+
"GET",
|
|
417
|
+
`/repos/${owner}/${repo}/issues/${index}/comments`,
|
|
418
|
+
);
|
|
419
|
+
return results.map((c) => this.mapIssueComment(c));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async createIssueComment(
|
|
423
|
+
owner: string,
|
|
424
|
+
repo: string,
|
|
425
|
+
index: number,
|
|
426
|
+
options: CreateIssueCommentOption,
|
|
427
|
+
): Promise<IssueComment> {
|
|
428
|
+
const result = await this.request<Record<string, unknown>>(
|
|
429
|
+
"POST",
|
|
430
|
+
`/repos/${owner}/${repo}/issues/${index}/comments`,
|
|
431
|
+
{ body: options.body },
|
|
432
|
+
);
|
|
433
|
+
return this.mapIssueComment(result);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async updateIssueComment(
|
|
437
|
+
owner: string,
|
|
438
|
+
repo: string,
|
|
439
|
+
commentId: number,
|
|
440
|
+
body: string,
|
|
441
|
+
): Promise<IssueComment> {
|
|
442
|
+
const result = await this.request<Record<string, unknown>>(
|
|
443
|
+
"PATCH",
|
|
444
|
+
`/repos/${owner}/${repo}/issues/comments/${commentId}`,
|
|
445
|
+
{ body },
|
|
446
|
+
);
|
|
447
|
+
return this.mapIssueComment(result);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async deleteIssueComment(owner: string, repo: string, commentId: number): Promise<void> {
|
|
451
|
+
await this.request<void>("DELETE", `/repos/${owner}/${repo}/issues/comments/${commentId}`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// ============ PR Review 操作 ============
|
|
455
|
+
|
|
456
|
+
async createPullReview(
|
|
457
|
+
owner: string,
|
|
458
|
+
repo: string,
|
|
459
|
+
index: number,
|
|
460
|
+
options: CreatePullReviewOption,
|
|
461
|
+
): Promise<PullReview> {
|
|
462
|
+
const body: Record<string, unknown> = {
|
|
463
|
+
event: this.mapReviewEvent(options.event),
|
|
464
|
+
body: options.body,
|
|
465
|
+
commit_id: options.commit_id,
|
|
466
|
+
};
|
|
467
|
+
if (options.comments?.length) {
|
|
468
|
+
body.comments = options.comments.map((c) => ({
|
|
469
|
+
path: c.path,
|
|
470
|
+
body: c.body,
|
|
471
|
+
position: c.new_position,
|
|
472
|
+
}));
|
|
473
|
+
}
|
|
474
|
+
const result = await this.request<Record<string, unknown>>(
|
|
475
|
+
"POST",
|
|
476
|
+
`/repos/${owner}/${repo}/pulls/${index}/reviews`,
|
|
477
|
+
body,
|
|
478
|
+
);
|
|
479
|
+
return this.mapPullReview(result);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async listPullReviews(owner: string, repo: string, index: number): Promise<PullReview[]> {
|
|
483
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
484
|
+
"GET",
|
|
485
|
+
`/repos/${owner}/${repo}/pulls/${index}/reviews`,
|
|
486
|
+
);
|
|
487
|
+
return results.map((r) => this.mapPullReview(r));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async deletePullReview(
|
|
491
|
+
owner: string,
|
|
492
|
+
repo: string,
|
|
493
|
+
index: number,
|
|
494
|
+
reviewId: number,
|
|
495
|
+
): Promise<void> {
|
|
496
|
+
await this.request<void>(
|
|
497
|
+
"DELETE",
|
|
498
|
+
`/repos/${owner}/${repo}/pulls/${index}/reviews/${reviewId}`,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async listPullReviewComments(
|
|
503
|
+
owner: string,
|
|
504
|
+
repo: string,
|
|
505
|
+
index: number,
|
|
506
|
+
reviewId: number,
|
|
507
|
+
): Promise<PullReviewComment[]> {
|
|
508
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
509
|
+
"GET",
|
|
510
|
+
`/repos/${owner}/${repo}/pulls/${index}/reviews/${reviewId}/comments`,
|
|
511
|
+
);
|
|
512
|
+
return results.map((c) => this.mapPullReviewComment(c));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ============ Reaction 操作 ============
|
|
516
|
+
|
|
517
|
+
async getIssueCommentReactions(
|
|
518
|
+
owner: string,
|
|
519
|
+
repo: string,
|
|
520
|
+
commentId: number,
|
|
521
|
+
): Promise<Reaction[]> {
|
|
522
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
523
|
+
"GET",
|
|
524
|
+
`/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`,
|
|
525
|
+
);
|
|
526
|
+
return results.map((r) => this.mapReaction(r));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async getIssueReactions(owner: string, repo: string, index: number): Promise<Reaction[]> {
|
|
530
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
531
|
+
"GET",
|
|
532
|
+
`/repos/${owner}/${repo}/issues/${index}/reactions`,
|
|
533
|
+
);
|
|
534
|
+
return results.map((r) => this.mapReaction(r));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// ============ 用户操作 ============
|
|
538
|
+
|
|
539
|
+
async searchUsers(query: string, limit = 10): Promise<User[]> {
|
|
540
|
+
const params = new URLSearchParams();
|
|
541
|
+
params.set("q", query);
|
|
542
|
+
params.set("per_page", String(limit));
|
|
543
|
+
const result = await this.request<{ items: Array<Record<string, unknown>> }>(
|
|
544
|
+
"GET",
|
|
545
|
+
`/search/users?${params.toString()}`,
|
|
546
|
+
);
|
|
547
|
+
return (result.items || []).map((u) => this.mapUser(u));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async getTeamMembers(teamId: number): Promise<User[]> {
|
|
551
|
+
const results = await this.request<Array<Record<string, unknown>>>(
|
|
552
|
+
"GET",
|
|
553
|
+
`/teams/${teamId}/members`,
|
|
554
|
+
);
|
|
555
|
+
return results.map((u) => this.mapUser(u));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ============ 映射辅助方法 ============
|
|
559
|
+
|
|
560
|
+
protected mapRepository(data: Record<string, unknown>): Repository {
|
|
561
|
+
const owner = data.owner as Record<string, unknown> | undefined;
|
|
562
|
+
return {
|
|
563
|
+
id: data.id as number,
|
|
564
|
+
owner: owner
|
|
565
|
+
? {
|
|
566
|
+
id: owner.id as number,
|
|
567
|
+
login: owner.login as string,
|
|
568
|
+
full_name: owner.full_name as string,
|
|
569
|
+
}
|
|
570
|
+
: undefined,
|
|
571
|
+
name: data.name as string,
|
|
572
|
+
full_name: data.full_name as string,
|
|
573
|
+
default_branch: data.default_branch as string,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
protected mapBranch(data: Record<string, unknown>): Branch {
|
|
578
|
+
const commit = data.commit as Record<string, unknown> | undefined;
|
|
579
|
+
const commitObj = commit?.commit as Record<string, unknown> | undefined;
|
|
580
|
+
return {
|
|
581
|
+
name: data.name as string,
|
|
582
|
+
protected: data.protected as boolean,
|
|
583
|
+
commit: commit
|
|
584
|
+
? {
|
|
585
|
+
id: commit.sha as string,
|
|
586
|
+
message: commitObj?.message as string,
|
|
587
|
+
}
|
|
588
|
+
: undefined,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
protected mapPullRequest(data: Record<string, unknown>): PullRequest {
|
|
593
|
+
const head = data.head as Record<string, unknown> | undefined;
|
|
594
|
+
const base = data.base as Record<string, unknown> | undefined;
|
|
595
|
+
const user = data.user as Record<string, unknown> | undefined;
|
|
596
|
+
const reviewers = data.requested_reviewers as Array<Record<string, unknown>> | undefined;
|
|
597
|
+
const teams = data.requested_teams as Array<Record<string, unknown>> | undefined;
|
|
598
|
+
return {
|
|
599
|
+
id: data.id as number,
|
|
600
|
+
number: data.number as number,
|
|
601
|
+
title: data.title as string,
|
|
602
|
+
body: data.body as string,
|
|
603
|
+
state: data.state as string,
|
|
604
|
+
head: head
|
|
605
|
+
? {
|
|
606
|
+
ref: head.ref as string,
|
|
607
|
+
sha: head.sha as string,
|
|
608
|
+
repo: head.repo ? this.mapRepository(head.repo as Record<string, unknown>) : undefined,
|
|
609
|
+
}
|
|
610
|
+
: undefined,
|
|
611
|
+
base: base
|
|
612
|
+
? {
|
|
613
|
+
ref: base.ref as string,
|
|
614
|
+
sha: base.sha as string,
|
|
615
|
+
repo: base.repo ? this.mapRepository(base.repo as Record<string, unknown>) : undefined,
|
|
616
|
+
}
|
|
617
|
+
: undefined,
|
|
618
|
+
user: user ? { id: user.id as number, login: user.login as string } : undefined,
|
|
619
|
+
requested_reviewers: reviewers?.map((r) => ({
|
|
620
|
+
id: r.id as number,
|
|
621
|
+
login: r.login as string,
|
|
622
|
+
})),
|
|
623
|
+
requested_reviewers_teams: teams?.map((t) => ({
|
|
624
|
+
id: t.id as number,
|
|
625
|
+
name: t.name as string,
|
|
626
|
+
})),
|
|
627
|
+
created_at: data.created_at as string,
|
|
628
|
+
updated_at: data.updated_at as string,
|
|
629
|
+
merged_at: data.merged_at as string,
|
|
630
|
+
merge_base: data.merge_commit_sha as string,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
protected mapCommit(data: Record<string, unknown>): PullRequestCommit {
|
|
635
|
+
const commit = data.commit as Record<string, unknown> | undefined;
|
|
636
|
+
const author = commit?.author as Record<string, unknown> | undefined;
|
|
637
|
+
const ghAuthor = data.author as Record<string, unknown> | undefined;
|
|
638
|
+
const ghCommitter = data.committer as Record<string, unknown> | undefined;
|
|
639
|
+
return {
|
|
640
|
+
sha: data.sha as string,
|
|
641
|
+
commit: commit
|
|
642
|
+
? {
|
|
643
|
+
message: commit.message as string,
|
|
644
|
+
author: author
|
|
645
|
+
? {
|
|
646
|
+
name: author.name as string,
|
|
647
|
+
email: author.email as string,
|
|
648
|
+
date: author.date as string,
|
|
649
|
+
}
|
|
650
|
+
: undefined,
|
|
651
|
+
}
|
|
652
|
+
: undefined,
|
|
653
|
+
author: ghAuthor ? { id: ghAuthor.id as number, login: ghAuthor.login as string } : undefined,
|
|
654
|
+
committer: ghCommitter
|
|
655
|
+
? { id: ghCommitter.id as number, login: ghCommitter.login as string }
|
|
656
|
+
: undefined,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
protected mapChangedFile(data: Record<string, unknown>): ChangedFile {
|
|
661
|
+
return {
|
|
662
|
+
filename: data.filename as string,
|
|
663
|
+
status: data.status as string,
|
|
664
|
+
additions: data.additions as number,
|
|
665
|
+
deletions: data.deletions as number,
|
|
666
|
+
changes: data.changes as number,
|
|
667
|
+
patch: data.patch as string,
|
|
668
|
+
raw_url: data.raw_url as string,
|
|
669
|
+
contents_url: data.contents_url as string,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
protected mapIssueComment(data: Record<string, unknown>): IssueComment {
|
|
674
|
+
const user = data.user as Record<string, unknown> | undefined;
|
|
675
|
+
return {
|
|
676
|
+
id: data.id as number,
|
|
677
|
+
body: data.body as string,
|
|
678
|
+
user: user ? { id: user.id as number, login: user.login as string } : undefined,
|
|
679
|
+
created_at: data.created_at as string,
|
|
680
|
+
updated_at: data.updated_at as string,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
protected mapIssue(data: Record<string, unknown>): Issue {
|
|
685
|
+
const user = data.user as Record<string, unknown> | undefined;
|
|
686
|
+
const labels = data.labels as Array<Record<string, unknown>> | undefined;
|
|
687
|
+
const assignees = data.assignees as Array<Record<string, unknown>> | undefined;
|
|
688
|
+
const milestone = data.milestone as Record<string, unknown> | undefined;
|
|
689
|
+
return {
|
|
690
|
+
id: data.id as number,
|
|
691
|
+
number: data.number as number,
|
|
692
|
+
title: data.title as string,
|
|
693
|
+
body: data.body as string,
|
|
694
|
+
state: data.state as string,
|
|
695
|
+
user: user ? { id: user.id as number, login: user.login as string } : undefined,
|
|
696
|
+
labels: labels?.map((l) => ({
|
|
697
|
+
id: l.id as number,
|
|
698
|
+
name: l.name as string,
|
|
699
|
+
color: l.color as string,
|
|
700
|
+
})),
|
|
701
|
+
assignees: assignees?.map((a) => ({ id: a.id as number, login: a.login as string })),
|
|
702
|
+
milestone: milestone
|
|
703
|
+
? { id: milestone.id as number, title: milestone.title as string }
|
|
704
|
+
: undefined,
|
|
705
|
+
created_at: data.created_at as string,
|
|
706
|
+
updated_at: data.updated_at as string,
|
|
707
|
+
closed_at: data.closed_at as string,
|
|
708
|
+
html_url: data.html_url as string,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
protected mapPullReview(data: Record<string, unknown>): PullReview {
|
|
713
|
+
const user = data.user as Record<string, unknown> | undefined;
|
|
714
|
+
return {
|
|
715
|
+
id: data.id as number,
|
|
716
|
+
body: data.body as string,
|
|
717
|
+
state: data.state as string,
|
|
718
|
+
user: user ? { id: user.id as number, login: user.login as string } : undefined,
|
|
719
|
+
created_at: data.submitted_at as string,
|
|
720
|
+
updated_at: data.submitted_at as string,
|
|
721
|
+
commit_id: data.commit_id as string,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
protected mapPullReviewComment(data: Record<string, unknown>): PullReviewComment {
|
|
726
|
+
const user = data.user as Record<string, unknown> | undefined;
|
|
727
|
+
return {
|
|
728
|
+
id: data.id as number,
|
|
729
|
+
body: data.body as string,
|
|
730
|
+
path: data.path as string,
|
|
731
|
+
position: data.position as number,
|
|
732
|
+
original_position: data.original_position as number,
|
|
733
|
+
commit_id: data.commit_id as string,
|
|
734
|
+
original_commit_id: data.original_commit_id as string,
|
|
735
|
+
diff_hunk: data.diff_hunk as string,
|
|
736
|
+
pull_request_review_id: data.pull_request_review_id as number,
|
|
737
|
+
user: user ? { id: user.id as number, login: user.login as string } : undefined,
|
|
738
|
+
created_at: data.created_at as string,
|
|
739
|
+
updated_at: data.updated_at as string,
|
|
740
|
+
html_url: data.html_url as string,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
protected mapReaction(data: Record<string, unknown>): Reaction {
|
|
745
|
+
const user = data.user as Record<string, unknown> | undefined;
|
|
746
|
+
return {
|
|
747
|
+
user: user ? { id: user.id as number, login: user.login as string } : undefined,
|
|
748
|
+
content: data.content as string,
|
|
749
|
+
created_at: data.created_at as string,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
protected mapUser(data: Record<string, unknown>): User {
|
|
754
|
+
return {
|
|
755
|
+
id: data.id as number,
|
|
756
|
+
login: data.login as string,
|
|
757
|
+
full_name: data.name as string,
|
|
758
|
+
email: data.email as string,
|
|
759
|
+
avatar_url: data.avatar_url as string,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
protected mapGithubProtection(
|
|
764
|
+
data: Record<string, unknown>,
|
|
765
|
+
branchName: string,
|
|
766
|
+
): BranchProtection {
|
|
767
|
+
const requiredReviews = data.required_pull_request_reviews as
|
|
768
|
+
| Record<string, unknown>
|
|
769
|
+
| undefined;
|
|
770
|
+
return {
|
|
771
|
+
branch_name: branchName,
|
|
772
|
+
rule_name: branchName,
|
|
773
|
+
required_approvals: requiredReviews?.required_approving_review_count as number,
|
|
774
|
+
dismiss_stale_approvals: requiredReviews?.dismiss_stale_reviews as boolean,
|
|
775
|
+
enable_push: true,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
protected mapRulesetToProtection(data: Record<string, unknown>): BranchProtection {
|
|
780
|
+
return {
|
|
781
|
+
rule_name: data.name as string,
|
|
782
|
+
branch_name: data.name as string,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
protected buildGithubProtectionBody(
|
|
787
|
+
options: CreateBranchProtectionOption | EditBranchProtectionOption,
|
|
788
|
+
): Record<string, unknown> {
|
|
789
|
+
const body: Record<string, unknown> = {
|
|
790
|
+
required_status_checks: options.enable_status_check
|
|
791
|
+
? { strict: true, contexts: options.status_check_contexts || [] }
|
|
792
|
+
: null,
|
|
793
|
+
enforce_admins: options.block_admin_merge_override ?? false,
|
|
794
|
+
required_pull_request_reviews: options.required_approvals
|
|
795
|
+
? {
|
|
796
|
+
required_approving_review_count: options.required_approvals,
|
|
797
|
+
dismiss_stale_reviews: options.dismiss_stale_approvals ?? false,
|
|
798
|
+
}
|
|
799
|
+
: null,
|
|
800
|
+
restrictions: options.enable_push_whitelist
|
|
801
|
+
? {
|
|
802
|
+
users: options.push_whitelist_usernames || [],
|
|
803
|
+
teams: options.push_whitelist_teams || [],
|
|
804
|
+
}
|
|
805
|
+
: null,
|
|
806
|
+
};
|
|
807
|
+
return body;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
protected mapReviewEvent(event?: string): string {
|
|
811
|
+
const eventMap: Record<string, string> = {
|
|
812
|
+
APPROVE: "APPROVE",
|
|
813
|
+
REQUEST_CHANGES: "REQUEST_CHANGES",
|
|
814
|
+
COMMENT: "COMMENT",
|
|
815
|
+
PENDING: "PENDING",
|
|
816
|
+
};
|
|
817
|
+
return event ? eventMap[event] || event : "COMMENT";
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
protected mapSortParam(sort: string): string {
|
|
821
|
+
const sortMap: Record<string, string> = {
|
|
822
|
+
oldest: "created",
|
|
823
|
+
recentupdate: "updated",
|
|
824
|
+
leastupdate: "updated",
|
|
825
|
+
mostcomment: "comments",
|
|
826
|
+
leastcomment: "comments",
|
|
827
|
+
};
|
|
828
|
+
return sortMap[sort] || "created";
|
|
829
|
+
}
|
|
830
|
+
}
|