@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,499 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { parseDiffText } from "../../git-sdk/git-sdk-diff.utils";
|
|
3
|
+
import type {
|
|
4
|
+
GitProvider,
|
|
5
|
+
LockBranchOptions,
|
|
6
|
+
ListPullRequestsOptions,
|
|
7
|
+
} from "../git-provider.interface";
|
|
8
|
+
import type {
|
|
9
|
+
GitProviderModuleOptions,
|
|
10
|
+
BranchProtection,
|
|
11
|
+
CreateBranchProtectionOption,
|
|
12
|
+
EditBranchProtectionOption,
|
|
13
|
+
Branch,
|
|
14
|
+
Repository,
|
|
15
|
+
PullRequest,
|
|
16
|
+
PullRequestCommit,
|
|
17
|
+
ChangedFile,
|
|
18
|
+
CommitInfo,
|
|
19
|
+
IssueComment,
|
|
20
|
+
CreateIssueCommentOption,
|
|
21
|
+
CreateIssueOption,
|
|
22
|
+
Issue,
|
|
23
|
+
CreatePullReviewOption,
|
|
24
|
+
PullReview,
|
|
25
|
+
PullReviewComment,
|
|
26
|
+
Reaction,
|
|
27
|
+
EditPullRequestOption,
|
|
28
|
+
User,
|
|
29
|
+
RepositoryContent,
|
|
30
|
+
} from "../types";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Gitea 平台适配器
|
|
34
|
+
*/
|
|
35
|
+
export class GiteaAdapter implements GitProvider {
|
|
36
|
+
protected readonly baseUrl: string;
|
|
37
|
+
protected readonly token: string;
|
|
38
|
+
|
|
39
|
+
constructor(protected readonly options: GitProviderModuleOptions) {
|
|
40
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
41
|
+
this.token = options.token;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
validateConfig(): void {
|
|
45
|
+
if (!this.options?.baseUrl) {
|
|
46
|
+
throw new Error("缺少配置 gitProvider.baseUrl (环境变量 GIT_PROVIDER_URL)");
|
|
47
|
+
}
|
|
48
|
+
if (!this.options?.token) {
|
|
49
|
+
throw new Error("缺少配置 gitProvider.token (环境变量 GIT_PROVIDER_TOKEN)");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
54
|
+
const url = `${this.baseUrl}/api/v1${path}`;
|
|
55
|
+
const headers: Record<string, string> = {
|
|
56
|
+
Authorization: `token ${this.token}`,
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
};
|
|
59
|
+
const response = await fetch(url, {
|
|
60
|
+
method,
|
|
61
|
+
headers,
|
|
62
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
63
|
+
});
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const errorText = await response.text();
|
|
66
|
+
throw new Error(`Gitea API error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
67
|
+
}
|
|
68
|
+
if (response.status === 204) {
|
|
69
|
+
return {} as T;
|
|
70
|
+
}
|
|
71
|
+
return response.json() as Promise<T>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
protected async fetchText(url: string): Promise<string> {
|
|
75
|
+
const response = await fetch(url, {
|
|
76
|
+
headers: { Authorization: `token ${this.token}` },
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const errorText = await response.text();
|
|
80
|
+
throw new Error(`Gitea API error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
81
|
+
}
|
|
82
|
+
return response.text();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============ 仓库操作 ============
|
|
86
|
+
|
|
87
|
+
async getRepository(owner: string, repo: string): Promise<Repository> {
|
|
88
|
+
return this.request<Repository>("GET", `/repos/${owner}/${repo}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============ 分支操作 ============
|
|
92
|
+
|
|
93
|
+
async getBranch(owner: string, repo: string, branch: string): Promise<Branch> {
|
|
94
|
+
return this.request<Branch>(
|
|
95
|
+
"GET",
|
|
96
|
+
`/repos/${owner}/${repo}/branches/${encodeURIComponent(branch)}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============ 分支保护 ============
|
|
101
|
+
|
|
102
|
+
async listBranchProtections(owner: string, repo: string): Promise<BranchProtection[]> {
|
|
103
|
+
return this.request<BranchProtection[]>("GET", `/repos/${owner}/${repo}/branch_protections`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async getBranchProtection(owner: string, repo: string, name: string): Promise<BranchProtection> {
|
|
107
|
+
return this.request<BranchProtection>(
|
|
108
|
+
"GET",
|
|
109
|
+
`/repos/${owner}/${repo}/branch_protections/${encodeURIComponent(name)}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async createBranchProtection(
|
|
114
|
+
owner: string,
|
|
115
|
+
repo: string,
|
|
116
|
+
options: CreateBranchProtectionOption,
|
|
117
|
+
): Promise<BranchProtection> {
|
|
118
|
+
return this.request<BranchProtection>(
|
|
119
|
+
"POST",
|
|
120
|
+
`/repos/${owner}/${repo}/branch_protections`,
|
|
121
|
+
options,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async editBranchProtection(
|
|
126
|
+
owner: string,
|
|
127
|
+
repo: string,
|
|
128
|
+
name: string,
|
|
129
|
+
options: EditBranchProtectionOption,
|
|
130
|
+
): Promise<BranchProtection> {
|
|
131
|
+
return this.request<BranchProtection>(
|
|
132
|
+
"PATCH",
|
|
133
|
+
`/repos/${owner}/${repo}/branch_protections/${encodeURIComponent(name)}`,
|
|
134
|
+
options,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async deleteBranchProtection(owner: string, repo: string, name: string): Promise<void> {
|
|
139
|
+
await this.request<void>(
|
|
140
|
+
"DELETE",
|
|
141
|
+
`/repos/${owner}/${repo}/branch_protections/${encodeURIComponent(name)}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async lockBranch(
|
|
146
|
+
owner: string,
|
|
147
|
+
repo: string,
|
|
148
|
+
branch: string,
|
|
149
|
+
options?: LockBranchOptions,
|
|
150
|
+
): Promise<BranchProtection> {
|
|
151
|
+
const protections = await this.listBranchProtections(owner, repo);
|
|
152
|
+
const existing = protections.find((p) => p.rule_name === branch || p.branch_name === branch);
|
|
153
|
+
const pushWhitelistUsernames = options?.pushWhitelistUsernames;
|
|
154
|
+
const hasWhitelist = pushWhitelistUsernames && pushWhitelistUsernames.length > 0;
|
|
155
|
+
const protectionOptions = hasWhitelist
|
|
156
|
+
? {
|
|
157
|
+
enable_push: true,
|
|
158
|
+
enable_push_whitelist: true,
|
|
159
|
+
push_whitelist_usernames: pushWhitelistUsernames,
|
|
160
|
+
}
|
|
161
|
+
: { enable_push: false };
|
|
162
|
+
if (existing) {
|
|
163
|
+
return this.editBranchProtection(
|
|
164
|
+
owner,
|
|
165
|
+
repo,
|
|
166
|
+
existing.rule_name || branch,
|
|
167
|
+
protectionOptions,
|
|
168
|
+
);
|
|
169
|
+
} else {
|
|
170
|
+
return this.createBranchProtection(owner, repo, {
|
|
171
|
+
rule_name: branch,
|
|
172
|
+
branch_name: branch,
|
|
173
|
+
...protectionOptions,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async unlockBranch(
|
|
179
|
+
owner: string,
|
|
180
|
+
repo: string,
|
|
181
|
+
branch: string,
|
|
182
|
+
): Promise<BranchProtection | null> {
|
|
183
|
+
const protections = await this.listBranchProtections(owner, repo);
|
|
184
|
+
const existing = protections.find((p) => p.rule_name === branch || p.branch_name === branch);
|
|
185
|
+
if (existing) {
|
|
186
|
+
await this.deleteBranchProtection(owner, repo, existing.rule_name || branch);
|
|
187
|
+
return existing;
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
unlockBranchSync(owner: string, repo: string, branch: string): void {
|
|
193
|
+
try {
|
|
194
|
+
const listResult = execSync(
|
|
195
|
+
`curl -s -X GET "${this.baseUrl}/api/v1/repos/${owner}/${repo}/branch_protections" -H "Authorization: token ${this.token}"`,
|
|
196
|
+
{ encoding: "utf-8" },
|
|
197
|
+
);
|
|
198
|
+
const protections = JSON.parse(listResult) as Array<{
|
|
199
|
+
rule_name?: string;
|
|
200
|
+
branch_name?: string;
|
|
201
|
+
}>;
|
|
202
|
+
const existing = protections.find((p) => p.rule_name === branch || p.branch_name === branch);
|
|
203
|
+
if (existing) {
|
|
204
|
+
const ruleName = existing.rule_name || branch;
|
|
205
|
+
execSync(
|
|
206
|
+
`curl -s -X DELETE "${this.baseUrl}/api/v1/repos/${owner}/${repo}/branch_protections/${ruleName}" -H "Authorization: token ${this.token}"`,
|
|
207
|
+
{ encoding: "utf-8" },
|
|
208
|
+
);
|
|
209
|
+
console.log(`✅ 分支已解锁(同步): ${ruleName}`);
|
|
210
|
+
} else {
|
|
211
|
+
console.log(`✅ 分支本身没有保护规则,无需解锁`);
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error("⚠️ 同步解锁分支失败:", error instanceof Error ? error.message : error);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ============ Pull Request 操作 ============
|
|
219
|
+
|
|
220
|
+
async getPullRequest(owner: string, repo: string, index: number): Promise<PullRequest> {
|
|
221
|
+
return this.request<PullRequest>("GET", `/repos/${owner}/${repo}/pulls/${index}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async editPullRequest(
|
|
225
|
+
owner: string,
|
|
226
|
+
repo: string,
|
|
227
|
+
index: number,
|
|
228
|
+
options: EditPullRequestOption,
|
|
229
|
+
): Promise<PullRequest> {
|
|
230
|
+
return this.request<PullRequest>("PATCH", `/repos/${owner}/${repo}/pulls/${index}`, options);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async listPullRequests(
|
|
234
|
+
owner: string,
|
|
235
|
+
repo: string,
|
|
236
|
+
state?: "open" | "closed" | "all",
|
|
237
|
+
): Promise<PullRequest[]> {
|
|
238
|
+
const query = state ? `?state=${state}` : "";
|
|
239
|
+
return this.request<PullRequest[]>("GET", `/repos/${owner}/${repo}/pulls${query}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async listAllPullRequests(
|
|
243
|
+
owner: string,
|
|
244
|
+
repo: string,
|
|
245
|
+
options?: ListPullRequestsOptions,
|
|
246
|
+
): Promise<PullRequest[]> {
|
|
247
|
+
const allPRs: PullRequest[] = [];
|
|
248
|
+
let page = 1;
|
|
249
|
+
const limit = 50;
|
|
250
|
+
while (true) {
|
|
251
|
+
const params = new URLSearchParams();
|
|
252
|
+
params.set("page", String(page));
|
|
253
|
+
params.set("limit", String(limit));
|
|
254
|
+
if (options?.state) params.set("state", options.state);
|
|
255
|
+
if (options?.sort) params.set("sort", options.sort);
|
|
256
|
+
if (options?.milestone) params.set("milestone", String(options.milestone));
|
|
257
|
+
if (options?.labels?.length) params.set("labels", options.labels.join(","));
|
|
258
|
+
const prs = await this.request<PullRequest[]>(
|
|
259
|
+
"GET",
|
|
260
|
+
`/repos/${owner}/${repo}/pulls?${params.toString()}`,
|
|
261
|
+
);
|
|
262
|
+
if (!prs || prs.length === 0) break;
|
|
263
|
+
allPRs.push(...prs);
|
|
264
|
+
if (prs.length < limit) break;
|
|
265
|
+
page++;
|
|
266
|
+
}
|
|
267
|
+
return allPRs;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async getPullRequestCommits(
|
|
271
|
+
owner: string,
|
|
272
|
+
repo: string,
|
|
273
|
+
index: number,
|
|
274
|
+
): Promise<PullRequestCommit[]> {
|
|
275
|
+
const allCommits: PullRequestCommit[] = [];
|
|
276
|
+
let page = 1;
|
|
277
|
+
const limit = 50;
|
|
278
|
+
while (true) {
|
|
279
|
+
const commits = await this.request<PullRequestCommit[]>(
|
|
280
|
+
"GET",
|
|
281
|
+
`/repos/${owner}/${repo}/pulls/${index}/commits?page=${page}&limit=${limit}`,
|
|
282
|
+
);
|
|
283
|
+
if (!commits || commits.length === 0) break;
|
|
284
|
+
allCommits.push(...commits);
|
|
285
|
+
if (commits.length < limit) break;
|
|
286
|
+
page++;
|
|
287
|
+
}
|
|
288
|
+
return allCommits;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async getPullRequestFiles(owner: string, repo: string, index: number): Promise<ChangedFile[]> {
|
|
292
|
+
const files = await this.request<ChangedFile[]>(
|
|
293
|
+
"GET",
|
|
294
|
+
`/repos/${owner}/${repo}/pulls/${index}/files`,
|
|
295
|
+
);
|
|
296
|
+
const needsPatch = files.some((f) => !f.patch && f.status !== "deleted");
|
|
297
|
+
if (!needsPatch) {
|
|
298
|
+
return files;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
const diffText = await this.getPullRequestDiff(owner, repo, index);
|
|
302
|
+
const diffFiles = parseDiffText(diffText);
|
|
303
|
+
const patchMap = new Map(diffFiles.map((f) => [f.filename, f.patch]));
|
|
304
|
+
for (const file of files) {
|
|
305
|
+
if (!file.patch && file.filename) {
|
|
306
|
+
file.patch = patchMap.get(file.filename);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.warn(`警告: 无法获取 PR diff 来填充 patch 字段:`, error);
|
|
311
|
+
}
|
|
312
|
+
return files;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async getPullRequestDiff(owner: string, repo: string, index: number): Promise<string> {
|
|
316
|
+
return this.fetchText(`${this.baseUrl}/api/v1/repos/${owner}/${repo}/pulls/${index}.diff`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ============ Commit 操作 ============
|
|
320
|
+
|
|
321
|
+
async getCommit(owner: string, repo: string, sha: string): Promise<CommitInfo> {
|
|
322
|
+
return this.request<CommitInfo>("GET", `/repos/${owner}/${repo}/git/commits/${sha}`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async getCompareDiff(
|
|
326
|
+
owner: string,
|
|
327
|
+
repo: string,
|
|
328
|
+
baseSha: string,
|
|
329
|
+
headSha: string,
|
|
330
|
+
): Promise<string> {
|
|
331
|
+
return this.fetchText(
|
|
332
|
+
`${this.baseUrl}/api/v1/repos/${owner}/${repo}/compare/${baseSha}...${headSha}.diff`,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async getCommitDiff(owner: string, repo: string, sha: string): Promise<string> {
|
|
337
|
+
return this.fetchText(`${this.baseUrl}/api/v1/repos/${owner}/${repo}/git/commits/${sha}.diff`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ============ 文件操作 ============
|
|
341
|
+
|
|
342
|
+
async getFileContent(
|
|
343
|
+
owner: string,
|
|
344
|
+
repo: string,
|
|
345
|
+
filepath: string,
|
|
346
|
+
ref?: string,
|
|
347
|
+
): Promise<string> {
|
|
348
|
+
const query = ref ? `?ref=${encodeURIComponent(ref)}` : "";
|
|
349
|
+
const url = `${this.baseUrl}/api/v1/repos/${owner}/${repo}/raw/${encodeURIComponent(filepath)}${query}`;
|
|
350
|
+
const response = await fetch(url, {
|
|
351
|
+
headers: { Authorization: `token ${this.token}` },
|
|
352
|
+
});
|
|
353
|
+
if (!response.ok) {
|
|
354
|
+
if (response.status === 404) {
|
|
355
|
+
return "";
|
|
356
|
+
}
|
|
357
|
+
const errorText = await response.text();
|
|
358
|
+
throw new Error(`Gitea API error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
359
|
+
}
|
|
360
|
+
return response.text();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async listRepositoryContents(
|
|
364
|
+
owner: string,
|
|
365
|
+
repo: string,
|
|
366
|
+
path = "",
|
|
367
|
+
ref?: string,
|
|
368
|
+
): Promise<RepositoryContent[]> {
|
|
369
|
+
const encodedPath = path ? `/${encodeURIComponent(path)}` : "";
|
|
370
|
+
const query = ref ? `?ref=${encodeURIComponent(ref)}` : "";
|
|
371
|
+
const result = await this.request<
|
|
372
|
+
Array<{ name: string; path: string; type: string; size: number; download_url?: string }>
|
|
373
|
+
>("GET", `/repos/${owner}/${repo}/contents${encodedPath}${query}`);
|
|
374
|
+
return result.map((item) => ({
|
|
375
|
+
name: item.name,
|
|
376
|
+
path: item.path,
|
|
377
|
+
type: item.type === "dir" ? ("dir" as const) : ("file" as const),
|
|
378
|
+
size: item.size,
|
|
379
|
+
download_url: item.download_url,
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ============ Issue 操作 ============
|
|
384
|
+
|
|
385
|
+
async createIssue(owner: string, repo: string, options: CreateIssueOption): Promise<Issue> {
|
|
386
|
+
return this.request<Issue>("POST", `/repos/${owner}/${repo}/issues`, options);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async listIssueComments(owner: string, repo: string, index: number): Promise<IssueComment[]> {
|
|
390
|
+
return this.request<IssueComment[]>("GET", `/repos/${owner}/${repo}/issues/${index}/comments`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async createIssueComment(
|
|
394
|
+
owner: string,
|
|
395
|
+
repo: string,
|
|
396
|
+
index: number,
|
|
397
|
+
options: CreateIssueCommentOption,
|
|
398
|
+
): Promise<IssueComment> {
|
|
399
|
+
return this.request<IssueComment>(
|
|
400
|
+
"POST",
|
|
401
|
+
`/repos/${owner}/${repo}/issues/${index}/comments`,
|
|
402
|
+
options,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async updateIssueComment(
|
|
407
|
+
owner: string,
|
|
408
|
+
repo: string,
|
|
409
|
+
commentId: number,
|
|
410
|
+
body: string,
|
|
411
|
+
): Promise<IssueComment> {
|
|
412
|
+
return this.request<IssueComment>(
|
|
413
|
+
"PATCH",
|
|
414
|
+
`/repos/${owner}/${repo}/issues/comments/${commentId}`,
|
|
415
|
+
{ body },
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async deleteIssueComment(owner: string, repo: string, commentId: number): Promise<void> {
|
|
420
|
+
await this.request<void>("DELETE", `/repos/${owner}/${repo}/issues/comments/${commentId}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ============ PR Review 操作 ============
|
|
424
|
+
|
|
425
|
+
async createPullReview(
|
|
426
|
+
owner: string,
|
|
427
|
+
repo: string,
|
|
428
|
+
index: number,
|
|
429
|
+
options: CreatePullReviewOption,
|
|
430
|
+
): Promise<PullReview> {
|
|
431
|
+
return this.request<PullReview>(
|
|
432
|
+
"POST",
|
|
433
|
+
`/repos/${owner}/${repo}/pulls/${index}/reviews`,
|
|
434
|
+
options,
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async listPullReviews(owner: string, repo: string, index: number): Promise<PullReview[]> {
|
|
439
|
+
return this.request<PullReview[]>("GET", `/repos/${owner}/${repo}/pulls/${index}/reviews`);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async deletePullReview(
|
|
443
|
+
owner: string,
|
|
444
|
+
repo: string,
|
|
445
|
+
index: number,
|
|
446
|
+
reviewId: number,
|
|
447
|
+
): Promise<void> {
|
|
448
|
+
await this.request<void>(
|
|
449
|
+
"DELETE",
|
|
450
|
+
`/repos/${owner}/${repo}/pulls/${index}/reviews/${reviewId}`,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async listPullReviewComments(
|
|
455
|
+
owner: string,
|
|
456
|
+
repo: string,
|
|
457
|
+
index: number,
|
|
458
|
+
reviewId: number,
|
|
459
|
+
): Promise<PullReviewComment[]> {
|
|
460
|
+
return this.request<PullReviewComment[]>(
|
|
461
|
+
"GET",
|
|
462
|
+
`/repos/${owner}/${repo}/pulls/${index}/reviews/${reviewId}/comments`,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ============ Reaction 操作 ============
|
|
467
|
+
|
|
468
|
+
async getIssueCommentReactions(
|
|
469
|
+
owner: string,
|
|
470
|
+
repo: string,
|
|
471
|
+
commentId: number,
|
|
472
|
+
): Promise<Reaction[]> {
|
|
473
|
+
return this.request<Reaction[]>(
|
|
474
|
+
"GET",
|
|
475
|
+
`/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async getIssueReactions(owner: string, repo: string, index: number): Promise<Reaction[]> {
|
|
480
|
+
return this.request<Reaction[]>("GET", `/repos/${owner}/${repo}/issues/${index}/reactions`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ============ 用户操作 ============
|
|
484
|
+
|
|
485
|
+
async searchUsers(query: string, limit = 10): Promise<User[]> {
|
|
486
|
+
const params = new URLSearchParams();
|
|
487
|
+
params.set("q", query);
|
|
488
|
+
params.set("limit", String(limit));
|
|
489
|
+
const result = await this.request<{ data: User[] }>(
|
|
490
|
+
"GET",
|
|
491
|
+
`/users/search?${params.toString()}`,
|
|
492
|
+
);
|
|
493
|
+
return result.data || [];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async getTeamMembers(teamId: number): Promise<User[]> {
|
|
497
|
+
return this.request<User[]>("GET", `/teams/${teamId}/members`);
|
|
498
|
+
}
|
|
499
|
+
}
|