@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,282 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
import { GitProviderService } from "./git-provider.service";
|
|
3
|
+
import type { GitProviderModuleOptions } from "./types";
|
|
4
|
+
import { GiteaAdapter } from "./adapters/gitea.adapter";
|
|
5
|
+
import { GithubAdapter } from "./adapters/github.adapter";
|
|
6
|
+
import { GitlabAdapter } from "./adapters/gitlab.adapter";
|
|
7
|
+
|
|
8
|
+
/** 直接实例化 service(绕过 NestJS DI) */
|
|
9
|
+
function createService(options: GitProviderModuleOptions): GitProviderService {
|
|
10
|
+
return new (GitProviderService as any)(options);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("GitProviderService", () => {
|
|
14
|
+
describe("createAdapter", () => {
|
|
15
|
+
it("provider=gitea 时应创建 GiteaAdapter", () => {
|
|
16
|
+
const service = createService({ provider: "gitea", baseUrl: "https://g.com", token: "t" });
|
|
17
|
+
expect((service as any).adapter).toBeInstanceOf(GiteaAdapter);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("provider=github 时应创建 GithubAdapter", () => {
|
|
21
|
+
const service = createService({
|
|
22
|
+
provider: "github",
|
|
23
|
+
baseUrl: "https://api.github.com",
|
|
24
|
+
token: "t",
|
|
25
|
+
});
|
|
26
|
+
expect((service as any).adapter).toBeInstanceOf(GithubAdapter);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("provider=gitlab 时应创建 GitlabAdapter", () => {
|
|
30
|
+
const service = createService({
|
|
31
|
+
provider: "gitlab",
|
|
32
|
+
baseUrl: "https://gitlab.com",
|
|
33
|
+
token: "t",
|
|
34
|
+
});
|
|
35
|
+
expect((service as any).adapter).toBeInstanceOf(GitlabAdapter);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("不支持的 provider 应抛出异常", () => {
|
|
39
|
+
expect(() =>
|
|
40
|
+
createService({ provider: "bitbucket" as any, baseUrl: "x", token: "t" }),
|
|
41
|
+
).toThrow("不支持的 Git Provider 类型: bitbucket");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("代理方法", () => {
|
|
46
|
+
let service: GitProviderService;
|
|
47
|
+
let mockAdapter: Record<string, ReturnType<typeof vi.fn>>;
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
service = createService({ provider: "gitea", baseUrl: "https://g.com", token: "t" });
|
|
51
|
+
mockAdapter = {
|
|
52
|
+
validateConfig: vi.fn(),
|
|
53
|
+
getRepository: vi.fn().mockResolvedValue({ id: 1 }),
|
|
54
|
+
getBranch: vi.fn().mockResolvedValue({ name: "main" }),
|
|
55
|
+
listBranchProtections: vi.fn().mockResolvedValue([]),
|
|
56
|
+
getBranchProtection: vi.fn().mockResolvedValue({}),
|
|
57
|
+
createBranchProtection: vi.fn().mockResolvedValue({}),
|
|
58
|
+
editBranchProtection: vi.fn().mockResolvedValue({}),
|
|
59
|
+
deleteBranchProtection: vi.fn().mockResolvedValue(undefined),
|
|
60
|
+
lockBranch: vi.fn().mockResolvedValue({}),
|
|
61
|
+
unlockBranch: vi.fn().mockResolvedValue(null),
|
|
62
|
+
unlockBranchSync: vi.fn(),
|
|
63
|
+
getPullRequest: vi.fn().mockResolvedValue({ id: 1 }),
|
|
64
|
+
editPullRequest: vi.fn().mockResolvedValue({}),
|
|
65
|
+
listPullRequests: vi.fn().mockResolvedValue([]),
|
|
66
|
+
listAllPullRequests: vi.fn().mockResolvedValue([]),
|
|
67
|
+
getPullRequestCommits: vi.fn().mockResolvedValue([]),
|
|
68
|
+
getPullRequestFiles: vi.fn().mockResolvedValue([]),
|
|
69
|
+
getPullRequestDiff: vi.fn().mockResolvedValue("diff"),
|
|
70
|
+
getCommit: vi.fn().mockResolvedValue({}),
|
|
71
|
+
getCompareDiff: vi.fn().mockResolvedValue("diff"),
|
|
72
|
+
getCommitDiff: vi.fn().mockResolvedValue("diff"),
|
|
73
|
+
getFileContent: vi.fn().mockResolvedValue("content"),
|
|
74
|
+
createIssue: vi.fn().mockResolvedValue({}),
|
|
75
|
+
listIssueComments: vi.fn().mockResolvedValue([]),
|
|
76
|
+
createIssueComment: vi.fn().mockResolvedValue({}),
|
|
77
|
+
updateIssueComment: vi.fn().mockResolvedValue({}),
|
|
78
|
+
deleteIssueComment: vi.fn().mockResolvedValue(undefined),
|
|
79
|
+
createPullReview: vi.fn().mockResolvedValue({}),
|
|
80
|
+
listPullReviews: vi.fn().mockResolvedValue([]),
|
|
81
|
+
deletePullReview: vi.fn().mockResolvedValue(undefined),
|
|
82
|
+
listPullReviewComments: vi.fn().mockResolvedValue([]),
|
|
83
|
+
getIssueCommentReactions: vi.fn().mockResolvedValue([]),
|
|
84
|
+
getIssueReactions: vi.fn().mockResolvedValue([]),
|
|
85
|
+
searchUsers: vi.fn().mockResolvedValue([]),
|
|
86
|
+
getTeamMembers: vi.fn().mockResolvedValue([]),
|
|
87
|
+
};
|
|
88
|
+
(service as any).adapter = mockAdapter;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("validateConfig 应代理到 adapter", () => {
|
|
92
|
+
service.validateConfig();
|
|
93
|
+
expect(mockAdapter.validateConfig).toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("getRepository 应代理到 adapter", async () => {
|
|
97
|
+
const result = await service.getRepository("o", "r");
|
|
98
|
+
expect(mockAdapter.getRepository).toHaveBeenCalledWith("o", "r");
|
|
99
|
+
expect(result).toEqual({ id: 1 });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("getPullRequest 应代理到 adapter", async () => {
|
|
103
|
+
await service.getPullRequest("o", "r", 42);
|
|
104
|
+
expect(mockAdapter.getPullRequest).toHaveBeenCalledWith("o", "r", 42);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("lockBranch 应代理到 adapter", async () => {
|
|
108
|
+
await service.lockBranch("o", "r", "main", { pushWhitelistUsernames: ["bot"] });
|
|
109
|
+
expect(mockAdapter.lockBranch).toHaveBeenCalledWith("o", "r", "main", {
|
|
110
|
+
pushWhitelistUsernames: ["bot"],
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("unlockBranchSync 应代理到 adapter", () => {
|
|
115
|
+
service.unlockBranchSync("o", "r", "main");
|
|
116
|
+
expect(mockAdapter.unlockBranchSync).toHaveBeenCalledWith("o", "r", "main");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("createPullReview 应代理到 adapter", async () => {
|
|
120
|
+
const opts = { event: "COMMENT" as const, body: "ok" };
|
|
121
|
+
await service.createPullReview("o", "r", 1, opts);
|
|
122
|
+
expect(mockAdapter.createPullReview).toHaveBeenCalledWith("o", "r", 1, opts);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("getFileContent 应代理到 adapter", async () => {
|
|
126
|
+
const result = await service.getFileContent("o", "r", "a.ts", "main");
|
|
127
|
+
expect(mockAdapter.getFileContent).toHaveBeenCalledWith("o", "r", "a.ts", "main");
|
|
128
|
+
expect(result).toBe("content");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("searchUsers 应代理到 adapter", async () => {
|
|
132
|
+
await service.searchUsers("test", 5);
|
|
133
|
+
expect(mockAdapter.searchUsers).toHaveBeenCalledWith("test", 5);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("getTeamMembers 应代理到 adapter", async () => {
|
|
137
|
+
await service.getTeamMembers(99);
|
|
138
|
+
expect(mockAdapter.getTeamMembers).toHaveBeenCalledWith(99);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("getBranch 应代理到 adapter", async () => {
|
|
142
|
+
await service.getBranch("o", "r", "main");
|
|
143
|
+
expect(mockAdapter.getBranch).toHaveBeenCalledWith("o", "r", "main");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("listBranchProtections 应代理到 adapter", async () => {
|
|
147
|
+
await service.listBranchProtections("o", "r");
|
|
148
|
+
expect(mockAdapter.listBranchProtections).toHaveBeenCalledWith("o", "r");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("getBranchProtection 应代理到 adapter", async () => {
|
|
152
|
+
await service.getBranchProtection("o", "r", "main");
|
|
153
|
+
expect(mockAdapter.getBranchProtection).toHaveBeenCalledWith("o", "r", "main");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("createBranchProtection 应代理到 adapter", async () => {
|
|
157
|
+
const opts = { branchName: "main" };
|
|
158
|
+
await service.createBranchProtection("o", "r", opts as any);
|
|
159
|
+
expect(mockAdapter.createBranchProtection).toHaveBeenCalledWith("o", "r", opts);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("editBranchProtection 应代理到 adapter", async () => {
|
|
163
|
+
const opts = { enablePush: true };
|
|
164
|
+
await service.editBranchProtection("o", "r", "main", opts as any);
|
|
165
|
+
expect(mockAdapter.editBranchProtection).toHaveBeenCalledWith("o", "r", "main", opts);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("deleteBranchProtection 应代理到 adapter", async () => {
|
|
169
|
+
await service.deleteBranchProtection("o", "r", "main");
|
|
170
|
+
expect(mockAdapter.deleteBranchProtection).toHaveBeenCalledWith("o", "r", "main");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("unlockBranch 应代理到 adapter", async () => {
|
|
174
|
+
await service.unlockBranch("o", "r", "main");
|
|
175
|
+
expect(mockAdapter.unlockBranch).toHaveBeenCalledWith("o", "r", "main");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("editPullRequest 应代理到 adapter", async () => {
|
|
179
|
+
const opts = { title: "new" };
|
|
180
|
+
await service.editPullRequest("o", "r", 1, opts as any);
|
|
181
|
+
expect(mockAdapter.editPullRequest).toHaveBeenCalledWith("o", "r", 1, opts);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("listPullRequests 应代理到 adapter", async () => {
|
|
185
|
+
await service.listPullRequests("o", "r", "open");
|
|
186
|
+
expect(mockAdapter.listPullRequests).toHaveBeenCalledWith("o", "r", "open");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("listAllPullRequests 应代理到 adapter", async () => {
|
|
190
|
+
await service.listAllPullRequests("o", "r", { state: "open" } as any);
|
|
191
|
+
expect(mockAdapter.listAllPullRequests).toHaveBeenCalledWith("o", "r", { state: "open" });
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("getPullRequestCommits 应代理到 adapter", async () => {
|
|
195
|
+
await service.getPullRequestCommits("o", "r", 1);
|
|
196
|
+
expect(mockAdapter.getPullRequestCommits).toHaveBeenCalledWith("o", "r", 1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("getPullRequestFiles 应代理到 adapter", async () => {
|
|
200
|
+
await service.getPullRequestFiles("o", "r", 1);
|
|
201
|
+
expect(mockAdapter.getPullRequestFiles).toHaveBeenCalledWith("o", "r", 1);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("getPullRequestDiff 应代理到 adapter", async () => {
|
|
205
|
+
await service.getPullRequestDiff("o", "r", 1);
|
|
206
|
+
expect(mockAdapter.getPullRequestDiff).toHaveBeenCalledWith("o", "r", 1);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("getCommit 应代理到 adapter", async () => {
|
|
210
|
+
await service.getCommit("o", "r", "abc");
|
|
211
|
+
expect(mockAdapter.getCommit).toHaveBeenCalledWith("o", "r", "abc");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("getCompareDiff 应代理到 adapter", async () => {
|
|
215
|
+
await service.getCompareDiff("o", "r", "a", "b");
|
|
216
|
+
expect(mockAdapter.getCompareDiff).toHaveBeenCalledWith("o", "r", "a", "b");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("getCommitDiff 应代理到 adapter", async () => {
|
|
220
|
+
await service.getCommitDiff("o", "r", "abc");
|
|
221
|
+
expect(mockAdapter.getCommitDiff).toHaveBeenCalledWith("o", "r", "abc");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("listRepositoryContents 应代理到 adapter", async () => {
|
|
225
|
+
mockAdapter.listRepositoryContents = vi.fn().mockResolvedValue([]);
|
|
226
|
+
await service.listRepositoryContents("o", "r", "src", "main");
|
|
227
|
+
expect(mockAdapter.listRepositoryContents).toHaveBeenCalledWith("o", "r", "src", "main");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("createIssue 应代理到 adapter", async () => {
|
|
231
|
+
const opts = { title: "bug" };
|
|
232
|
+
await service.createIssue("o", "r", opts as any);
|
|
233
|
+
expect(mockAdapter.createIssue).toHaveBeenCalledWith("o", "r", opts);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("listIssueComments 应代理到 adapter", async () => {
|
|
237
|
+
await service.listIssueComments("o", "r", 1);
|
|
238
|
+
expect(mockAdapter.listIssueComments).toHaveBeenCalledWith("o", "r", 1);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("createIssueComment 应代理到 adapter", async () => {
|
|
242
|
+
const opts = { body: "comment" };
|
|
243
|
+
await service.createIssueComment("o", "r", 1, opts as any);
|
|
244
|
+
expect(mockAdapter.createIssueComment).toHaveBeenCalledWith("o", "r", 1, opts);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("updateIssueComment 应代理到 adapter", async () => {
|
|
248
|
+
await service.updateIssueComment("o", "r", 1, "updated");
|
|
249
|
+
expect(mockAdapter.updateIssueComment).toHaveBeenCalledWith("o", "r", 1, "updated");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("deleteIssueComment 应代理到 adapter", async () => {
|
|
253
|
+
await service.deleteIssueComment("o", "r", 1);
|
|
254
|
+
expect(mockAdapter.deleteIssueComment).toHaveBeenCalledWith("o", "r", 1);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("listPullReviews 应代理到 adapter", async () => {
|
|
258
|
+
await service.listPullReviews("o", "r", 1);
|
|
259
|
+
expect(mockAdapter.listPullReviews).toHaveBeenCalledWith("o", "r", 1);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("deletePullReview 应代理到 adapter", async () => {
|
|
263
|
+
await service.deletePullReview("o", "r", 1, 2);
|
|
264
|
+
expect(mockAdapter.deletePullReview).toHaveBeenCalledWith("o", "r", 1, 2);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("listPullReviewComments 应代理到 adapter", async () => {
|
|
268
|
+
await service.listPullReviewComments("o", "r", 1, 2);
|
|
269
|
+
expect(mockAdapter.listPullReviewComments).toHaveBeenCalledWith("o", "r", 1, 2);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("getIssueCommentReactions 应代理到 adapter", async () => {
|
|
273
|
+
await service.getIssueCommentReactions("o", "r", 1);
|
|
274
|
+
expect(mockAdapter.getIssueCommentReactions).toHaveBeenCalledWith("o", "r", 1);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("getIssueReactions 应代理到 adapter", async () => {
|
|
278
|
+
await service.getIssueReactions("o", "r", 1);
|
|
279
|
+
expect(mockAdapter.getIssueReactions).toHaveBeenCalledWith("o", "r", 1);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
2
|
+
import type {
|
|
3
|
+
GitProvider,
|
|
4
|
+
LockBranchOptions,
|
|
5
|
+
ListPullRequestsOptions,
|
|
6
|
+
} from "./git-provider.interface";
|
|
7
|
+
import {
|
|
8
|
+
type GitProviderModuleOptions,
|
|
9
|
+
GIT_PROVIDER_MODULE_OPTIONS,
|
|
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
|
+
} from "./types";
|
|
31
|
+
import { GiteaAdapter } from "./adapters/gitea.adapter";
|
|
32
|
+
import { GithubAdapter } from "./adapters/github.adapter";
|
|
33
|
+
import { GitlabAdapter } from "./adapters/gitlab.adapter";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Git Provider 统一服务
|
|
37
|
+
* 根据配置的 provider 类型代理到对应的适配器实现
|
|
38
|
+
*/
|
|
39
|
+
@Injectable()
|
|
40
|
+
export class GitProviderService implements GitProvider {
|
|
41
|
+
protected readonly adapter: GitProvider;
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
@Inject(GIT_PROVIDER_MODULE_OPTIONS) protected readonly options: GitProviderModuleOptions,
|
|
45
|
+
) {
|
|
46
|
+
this.adapter = this.createAdapter(options);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 根据 provider 类型创建对应的适配器
|
|
51
|
+
*/
|
|
52
|
+
protected createAdapter(options: GitProviderModuleOptions): GitProvider {
|
|
53
|
+
switch (options.provider) {
|
|
54
|
+
case "gitea":
|
|
55
|
+
return new GiteaAdapter(options);
|
|
56
|
+
case "github":
|
|
57
|
+
return new GithubAdapter(options);
|
|
58
|
+
case "gitlab":
|
|
59
|
+
return new GitlabAdapter(options);
|
|
60
|
+
default:
|
|
61
|
+
throw new Error(`不支持的 Git Provider 类型: ${options.provider}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============ 配置验证 ============
|
|
66
|
+
|
|
67
|
+
validateConfig(): void {
|
|
68
|
+
this.adapter.validateConfig();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============ 仓库操作 ============
|
|
72
|
+
|
|
73
|
+
async getRepository(owner: string, repo: string): Promise<Repository> {
|
|
74
|
+
return this.adapter.getRepository(owner, repo);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============ 分支操作 ============
|
|
78
|
+
|
|
79
|
+
async getBranch(owner: string, repo: string, branch: string): Promise<Branch> {
|
|
80
|
+
return this.adapter.getBranch(owner, repo, branch);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============ 分支保护 ============
|
|
84
|
+
|
|
85
|
+
async listBranchProtections(owner: string, repo: string): Promise<BranchProtection[]> {
|
|
86
|
+
return this.adapter.listBranchProtections(owner, repo);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async getBranchProtection(owner: string, repo: string, name: string): Promise<BranchProtection> {
|
|
90
|
+
return this.adapter.getBranchProtection(owner, repo, name);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async createBranchProtection(
|
|
94
|
+
owner: string,
|
|
95
|
+
repo: string,
|
|
96
|
+
options: CreateBranchProtectionOption,
|
|
97
|
+
): Promise<BranchProtection> {
|
|
98
|
+
return this.adapter.createBranchProtection(owner, repo, options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async editBranchProtection(
|
|
102
|
+
owner: string,
|
|
103
|
+
repo: string,
|
|
104
|
+
name: string,
|
|
105
|
+
options: EditBranchProtectionOption,
|
|
106
|
+
): Promise<BranchProtection> {
|
|
107
|
+
return this.adapter.editBranchProtection(owner, repo, name, options);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async deleteBranchProtection(owner: string, repo: string, name: string): Promise<void> {
|
|
111
|
+
return this.adapter.deleteBranchProtection(owner, repo, name);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async lockBranch(
|
|
115
|
+
owner: string,
|
|
116
|
+
repo: string,
|
|
117
|
+
branch: string,
|
|
118
|
+
options?: LockBranchOptions,
|
|
119
|
+
): Promise<BranchProtection> {
|
|
120
|
+
return this.adapter.lockBranch(owner, repo, branch, options);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async unlockBranch(
|
|
124
|
+
owner: string,
|
|
125
|
+
repo: string,
|
|
126
|
+
branch: string,
|
|
127
|
+
): Promise<BranchProtection | null> {
|
|
128
|
+
return this.adapter.unlockBranch(owner, repo, branch);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
unlockBranchSync(owner: string, repo: string, branch: string): void {
|
|
132
|
+
this.adapter.unlockBranchSync(owner, repo, branch);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ============ Pull Request 操作 ============
|
|
136
|
+
|
|
137
|
+
async getPullRequest(owner: string, repo: string, index: number): Promise<PullRequest> {
|
|
138
|
+
return this.adapter.getPullRequest(owner, repo, index);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async editPullRequest(
|
|
142
|
+
owner: string,
|
|
143
|
+
repo: string,
|
|
144
|
+
index: number,
|
|
145
|
+
options: EditPullRequestOption,
|
|
146
|
+
): Promise<PullRequest> {
|
|
147
|
+
return this.adapter.editPullRequest(owner, repo, index, options);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async listPullRequests(
|
|
151
|
+
owner: string,
|
|
152
|
+
repo: string,
|
|
153
|
+
state?: "open" | "closed" | "all",
|
|
154
|
+
): Promise<PullRequest[]> {
|
|
155
|
+
return this.adapter.listPullRequests(owner, repo, state);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async listAllPullRequests(
|
|
159
|
+
owner: string,
|
|
160
|
+
repo: string,
|
|
161
|
+
options?: ListPullRequestsOptions,
|
|
162
|
+
): Promise<PullRequest[]> {
|
|
163
|
+
return this.adapter.listAllPullRequests(owner, repo, options);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async getPullRequestCommits(
|
|
167
|
+
owner: string,
|
|
168
|
+
repo: string,
|
|
169
|
+
index: number,
|
|
170
|
+
): Promise<PullRequestCommit[]> {
|
|
171
|
+
return this.adapter.getPullRequestCommits(owner, repo, index);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async getPullRequestFiles(owner: string, repo: string, index: number): Promise<ChangedFile[]> {
|
|
175
|
+
return this.adapter.getPullRequestFiles(owner, repo, index);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async getPullRequestDiff(owner: string, repo: string, index: number): Promise<string> {
|
|
179
|
+
return this.adapter.getPullRequestDiff(owner, repo, index);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ============ Commit 操作 ============
|
|
183
|
+
|
|
184
|
+
async getCommit(owner: string, repo: string, sha: string): Promise<CommitInfo> {
|
|
185
|
+
return this.adapter.getCommit(owner, repo, sha);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async getCompareDiff(
|
|
189
|
+
owner: string,
|
|
190
|
+
repo: string,
|
|
191
|
+
baseSha: string,
|
|
192
|
+
headSha: string,
|
|
193
|
+
): Promise<string> {
|
|
194
|
+
return this.adapter.getCompareDiff(owner, repo, baseSha, headSha);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async getCommitDiff(owner: string, repo: string, sha: string): Promise<string> {
|
|
198
|
+
return this.adapter.getCommitDiff(owner, repo, sha);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ============ 文件操作 ============
|
|
202
|
+
|
|
203
|
+
async getFileContent(
|
|
204
|
+
owner: string,
|
|
205
|
+
repo: string,
|
|
206
|
+
filepath: string,
|
|
207
|
+
ref?: string,
|
|
208
|
+
): Promise<string> {
|
|
209
|
+
return this.adapter.getFileContent(owner, repo, filepath, ref);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async listRepositoryContents(
|
|
213
|
+
owner: string,
|
|
214
|
+
repo: string,
|
|
215
|
+
path?: string,
|
|
216
|
+
ref?: string,
|
|
217
|
+
): Promise<RepositoryContent[]> {
|
|
218
|
+
return this.adapter.listRepositoryContents(owner, repo, path, ref);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ============ Issue 操作 ============
|
|
222
|
+
|
|
223
|
+
async createIssue(owner: string, repo: string, options: CreateIssueOption): Promise<Issue> {
|
|
224
|
+
return this.adapter.createIssue(owner, repo, options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async listIssueComments(owner: string, repo: string, index: number): Promise<IssueComment[]> {
|
|
228
|
+
return this.adapter.listIssueComments(owner, repo, index);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async createIssueComment(
|
|
232
|
+
owner: string,
|
|
233
|
+
repo: string,
|
|
234
|
+
index: number,
|
|
235
|
+
options: CreateIssueCommentOption,
|
|
236
|
+
): Promise<IssueComment> {
|
|
237
|
+
return this.adapter.createIssueComment(owner, repo, index, options);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async updateIssueComment(
|
|
241
|
+
owner: string,
|
|
242
|
+
repo: string,
|
|
243
|
+
commentId: number,
|
|
244
|
+
body: string,
|
|
245
|
+
): Promise<IssueComment> {
|
|
246
|
+
return this.adapter.updateIssueComment(owner, repo, commentId, body);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async deleteIssueComment(owner: string, repo: string, commentId: number): Promise<void> {
|
|
250
|
+
return this.adapter.deleteIssueComment(owner, repo, commentId);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ============ PR Review 操作 ============
|
|
254
|
+
|
|
255
|
+
async createPullReview(
|
|
256
|
+
owner: string,
|
|
257
|
+
repo: string,
|
|
258
|
+
index: number,
|
|
259
|
+
options: CreatePullReviewOption,
|
|
260
|
+
): Promise<PullReview> {
|
|
261
|
+
return this.adapter.createPullReview(owner, repo, index, options);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async listPullReviews(owner: string, repo: string, index: number): Promise<PullReview[]> {
|
|
265
|
+
return this.adapter.listPullReviews(owner, repo, index);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async deletePullReview(
|
|
269
|
+
owner: string,
|
|
270
|
+
repo: string,
|
|
271
|
+
index: number,
|
|
272
|
+
reviewId: number,
|
|
273
|
+
): Promise<void> {
|
|
274
|
+
return this.adapter.deletePullReview(owner, repo, index, reviewId);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async listPullReviewComments(
|
|
278
|
+
owner: string,
|
|
279
|
+
repo: string,
|
|
280
|
+
index: number,
|
|
281
|
+
reviewId: number,
|
|
282
|
+
): Promise<PullReviewComment[]> {
|
|
283
|
+
return this.adapter.listPullReviewComments(owner, repo, index, reviewId);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ============ Reaction 操作 ============
|
|
287
|
+
|
|
288
|
+
async getIssueCommentReactions(
|
|
289
|
+
owner: string,
|
|
290
|
+
repo: string,
|
|
291
|
+
commentId: number,
|
|
292
|
+
): Promise<Reaction[]> {
|
|
293
|
+
return this.adapter.getIssueCommentReactions(owner, repo, commentId);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async getIssueReactions(owner: string, repo: string, index: number): Promise<Reaction[]> {
|
|
297
|
+
return this.adapter.getIssueReactions(owner, repo, index);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ============ 用户操作 ============
|
|
301
|
+
|
|
302
|
+
async searchUsers(query: string, limit?: number): Promise<User[]> {
|
|
303
|
+
return this.adapter.searchUsers(query, limit);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async getTeamMembers(teamId: number): Promise<User[]> {
|
|
307
|
+
return this.adapter.getTeamMembers(teamId);
|
|
308
|
+
}
|
|
309
|
+
}
|