@shipfox/api-integration-gitea 12.5.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +17 -0
- package/dist/api/client.d.ts +13 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +26 -0
- package/dist/api/client.js.map +1 -1
- package/dist/core/source-control.d.ts +2 -1
- package/dist/core/source-control.d.ts.map +1 -1
- package/dist/core/source-control.js +55 -1
- package/dist/core/source-control.js.map +1 -1
- package/dist/core/webhook.d.ts.map +1 -1
- package/dist/core/webhook.js +2 -2
- package/dist/core/webhook.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/api/client.test.ts +58 -0
- package/src/api/client.ts +41 -1
- package/src/connection-external-url.test.ts +2 -0
- package/src/core/connect.test.ts +2 -0
- package/src/core/source-control-clone-url.test.ts +2 -0
- package/src/core/source-control.test.ts +174 -0
- package/src/core/source-control.ts +57 -0
- package/src/core/webhook.ts +2 -1
- package/src/index.test.ts +1 -0
- package/src/index.ts +2 -1
- package/src/presentation/routes/connections.test.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-integration-gitea",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "14.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"drizzle-orm": "^0.45.2",
|
|
22
22
|
"@shipfox/api-auth-context": "12.2.0",
|
|
23
|
-
"@shipfox/api-integration-spi": "
|
|
24
|
-
"@shipfox/api-integration-gitea-dto": "
|
|
23
|
+
"@shipfox/api-integration-spi": "2.0.0",
|
|
24
|
+
"@shipfox/api-integration-gitea-dto": "14.0.0",
|
|
25
25
|
"@shipfox/config": "1.2.4",
|
|
26
26
|
"@shipfox/node-drizzle": "0.3.5",
|
|
27
27
|
"@shipfox/node-fastify": "0.4.2",
|
package/src/api/client.test.ts
CHANGED
|
@@ -115,6 +115,64 @@ describe('HttpGiteaApiClient', () => {
|
|
|
115
115
|
await expect(result).rejects.toMatchObject({reason: 'repository-not-found'});
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
it('gets a branch head commit', async () => {
|
|
119
|
+
fetchMock.mockResolvedValue(
|
|
120
|
+
jsonResponse({name: 'main', commit: {id: 'abc123', message: 'hi'}}),
|
|
121
|
+
);
|
|
122
|
+
const client = createGiteaApiClient();
|
|
123
|
+
|
|
124
|
+
const result = await client.getBranch({owner: 'shipfox', repo: 'platform', branch: 'main'});
|
|
125
|
+
|
|
126
|
+
expect(result).toEqual({commitSha: 'abc123'});
|
|
127
|
+
expect(requestedUrl().pathname).toBe('/api/v1/repos/shipfox/platform/branches/main');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('gets a tag head commit', async () => {
|
|
131
|
+
fetchMock.mockResolvedValue(jsonResponse({name: 'v1.0.0', commit: {sha: 'abc123'}}));
|
|
132
|
+
const client = createGiteaApiClient();
|
|
133
|
+
|
|
134
|
+
const result = await client.getTag({owner: 'shipfox', repo: 'platform', tag: 'v1.0.0'});
|
|
135
|
+
|
|
136
|
+
expect(result).toEqual({commitSha: 'abc123'});
|
|
137
|
+
expect(requestedUrl().pathname).toBe('/api/v1/repos/shipfox/platform/tags/v1.0.0');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('maps a missing branch to ref-not-found', async () => {
|
|
141
|
+
fetchMock.mockResolvedValue(jsonResponse({message: 'not found'}, {status: 404}));
|
|
142
|
+
const client = createGiteaApiClient();
|
|
143
|
+
|
|
144
|
+
const result = client.getBranch({owner: 'shipfox', repo: 'platform', branch: 'missing'});
|
|
145
|
+
|
|
146
|
+
await expect(result).rejects.toMatchObject({reason: 'ref-not-found'});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('maps a missing tag to ref-not-found', async () => {
|
|
150
|
+
fetchMock.mockResolvedValue(jsonResponse({message: 'not found'}, {status: 404}));
|
|
151
|
+
const client = createGiteaApiClient();
|
|
152
|
+
|
|
153
|
+
const result = client.getTag({owner: 'shipfox', repo: 'platform', tag: 'missing'});
|
|
154
|
+
|
|
155
|
+
await expect(result).rejects.toMatchObject({reason: 'ref-not-found'});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('rejects a branch response without a head commit', async () => {
|
|
159
|
+
fetchMock.mockResolvedValue(jsonResponse({name: 'main', commit: {}}));
|
|
160
|
+
const client = createGiteaApiClient();
|
|
161
|
+
|
|
162
|
+
const result = client.getBranch({owner: 'shipfox', repo: 'platform', branch: 'main'});
|
|
163
|
+
|
|
164
|
+
await expect(result).rejects.toMatchObject({reason: 'malformed-provider-response'});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('rejects a tag response without a head commit', async () => {
|
|
168
|
+
fetchMock.mockResolvedValue(jsonResponse({name: 'v1.0.0', commit: {}}));
|
|
169
|
+
const client = createGiteaApiClient();
|
|
170
|
+
|
|
171
|
+
const result = client.getTag({owner: 'shipfox', repo: 'platform', tag: 'v1.0.0'});
|
|
172
|
+
|
|
173
|
+
await expect(result).rejects.toMatchObject({reason: 'malformed-provider-response'});
|
|
174
|
+
});
|
|
175
|
+
|
|
118
176
|
it('lists the recursive tree, keeping blobs and dropping subtrees', async () => {
|
|
119
177
|
fetchMock.mockResolvedValue(
|
|
120
178
|
jsonResponse({
|
package/src/api/client.ts
CHANGED
|
@@ -41,6 +41,10 @@ export interface GiteaFileContent {
|
|
|
41
41
|
size: number;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export interface GiteaRefCommit {
|
|
45
|
+
commitSha: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
export interface GiteaApiClient {
|
|
45
49
|
listOrgRepositories(input: {
|
|
46
50
|
org: string;
|
|
@@ -49,6 +53,8 @@ export interface GiteaApiClient {
|
|
|
49
53
|
}): Promise<GiteaRepositoryPage>;
|
|
50
54
|
getRepository(input: {owner: string; repo: string}): Promise<GiteaRepository>;
|
|
51
55
|
resolveRef(input: {owner: string; repo: string; ref: string}): Promise<string>;
|
|
56
|
+
getBranch(input: {owner: string; repo: string; branch: string}): Promise<GiteaRefCommit>;
|
|
57
|
+
getTag(input: {owner: string; repo: string; tag: string}): Promise<GiteaRefCommit>;
|
|
52
58
|
listTree(input: {owner: string; repo: string; sha: string}): Promise<GiteaTree>;
|
|
53
59
|
fetchFileContent(input: {
|
|
54
60
|
owner: string;
|
|
@@ -113,6 +119,40 @@ class HttpGiteaApiClient implements GiteaApiClient {
|
|
|
113
119
|
return head.sha;
|
|
114
120
|
}
|
|
115
121
|
|
|
122
|
+
async getBranch(input: {owner: string; repo: string; branch: string}): Promise<GiteaRefCommit> {
|
|
123
|
+
const response = await this.request(
|
|
124
|
+
`repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/branches/${encodeURIComponent(input.branch)}`,
|
|
125
|
+
{},
|
|
126
|
+
{notFoundReason: 'ref-not-found'},
|
|
127
|
+
);
|
|
128
|
+
const data = await response.json();
|
|
129
|
+
const commit = isRecord(data) ? data.commit : undefined;
|
|
130
|
+
if (!isRecord(commit) || typeof commit.id !== 'string') {
|
|
131
|
+
throw new GiteaIntegrationProviderError(
|
|
132
|
+
'malformed-provider-response',
|
|
133
|
+
`Gitea branch ${input.branch} response is missing the head commit`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return {commitSha: commit.id};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async getTag(input: {owner: string; repo: string; tag: string}): Promise<GiteaRefCommit> {
|
|
140
|
+
const response = await this.request(
|
|
141
|
+
`repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/tags/${encodeURIComponent(input.tag)}`,
|
|
142
|
+
{},
|
|
143
|
+
{notFoundReason: 'ref-not-found'},
|
|
144
|
+
);
|
|
145
|
+
const data = await response.json();
|
|
146
|
+
const commit = isRecord(data) ? data.commit : undefined;
|
|
147
|
+
if (!isRecord(commit) || typeof commit.sha !== 'string') {
|
|
148
|
+
throw new GiteaIntegrationProviderError(
|
|
149
|
+
'malformed-provider-response',
|
|
150
|
+
`Gitea tag ${input.tag} response is missing the head commit`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return {commitSha: commit.sha};
|
|
154
|
+
}
|
|
155
|
+
|
|
116
156
|
async listTree(input: {owner: string; repo: string; sha: string}): Promise<GiteaTree> {
|
|
117
157
|
const response = await this.request(
|
|
118
158
|
`repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/git/trees/${encodeURIComponent(input.sha)}`,
|
|
@@ -264,7 +304,7 @@ class HttpGiteaApiClient implements GiteaApiClient {
|
|
|
264
304
|
}
|
|
265
305
|
}
|
|
266
306
|
|
|
267
|
-
type NotFoundReason = 'repository-not-found' | 'file-not-found';
|
|
307
|
+
type NotFoundReason = 'repository-not-found' | 'file-not-found' | 'ref-not-found';
|
|
268
308
|
|
|
269
309
|
function giteaHttpError(
|
|
270
310
|
response: Response,
|
|
@@ -6,6 +6,8 @@ function giteaClient(): GiteaApiClient {
|
|
|
6
6
|
listOrgRepositories: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
7
7
|
getRepository: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
8
8
|
resolveRef: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
9
|
+
getBranch: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
10
|
+
getTag: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
9
11
|
listTree: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
10
12
|
fetchFileContent: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
11
13
|
organizationExists: vi.fn(() => Promise.reject(new Error('not used'))),
|
package/src/core/connect.test.ts
CHANGED
|
@@ -8,6 +8,8 @@ function giteaClient(overrides: Partial<GiteaApiClient> = {}): GiteaApiClient {
|
|
|
8
8
|
listOrgRepositories: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
9
9
|
getRepository: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
10
10
|
resolveRef: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
11
|
+
getBranch: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
12
|
+
getTag: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
11
13
|
listTree: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
12
14
|
fetchFileContent: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
13
15
|
organizationExists: vi.fn(() => Promise.resolve(true)),
|
|
@@ -16,6 +16,8 @@ function giteaClient(repository: GiteaRepository): GiteaApiClient {
|
|
|
16
16
|
listOrgRepositories: vi.fn(),
|
|
17
17
|
getRepository: vi.fn(() => Promise.resolve(repository)),
|
|
18
18
|
resolveRef: vi.fn(),
|
|
19
|
+
getBranch: vi.fn(),
|
|
20
|
+
getTag: vi.fn(),
|
|
19
21
|
listTree: vi.fn(),
|
|
20
22
|
fetchFileContent: vi.fn(),
|
|
21
23
|
organizationExists: vi.fn(),
|
|
@@ -35,6 +35,8 @@ function giteaClient(overrides: Partial<GiteaApiClient> = {}): GiteaApiClient {
|
|
|
35
35
|
size: 58,
|
|
36
36
|
}),
|
|
37
37
|
),
|
|
38
|
+
getBranch: vi.fn(() => Promise.resolve({commitSha: 'branch-head'})),
|
|
39
|
+
getTag: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
38
40
|
organizationExists: vi.fn(() => Promise.resolve(true)),
|
|
39
41
|
...overrides,
|
|
40
42
|
};
|
|
@@ -168,6 +170,178 @@ describe('GiteaSourceControlProvider', () => {
|
|
|
168
170
|
expect(result).toBeNull();
|
|
169
171
|
});
|
|
170
172
|
|
|
173
|
+
it('resolves a branch ref to the commit it points at', async () => {
|
|
174
|
+
const gitea = giteaClient();
|
|
175
|
+
const provider = new GiteaSourceControlProvider(gitea);
|
|
176
|
+
|
|
177
|
+
const result = await provider.resolveRef({
|
|
178
|
+
connection: connection(),
|
|
179
|
+
externalRepositoryId: 'gitea:shipfox/platform',
|
|
180
|
+
ref: 'refs/heads/main',
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(result).toEqual({ref: 'refs/heads/main', commit: 'branch-head'});
|
|
184
|
+
expect(gitea.getBranch).toHaveBeenCalledWith({
|
|
185
|
+
owner: 'shipfox',
|
|
186
|
+
repo: 'platform',
|
|
187
|
+
branch: 'main',
|
|
188
|
+
});
|
|
189
|
+
expect(gitea.getTag).not.toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('does not cross namespaces for a missing namespaced branch ref', async () => {
|
|
193
|
+
const gitea = giteaClient({
|
|
194
|
+
getBranch: vi.fn(() =>
|
|
195
|
+
Promise.reject(new GiteaIntegrationProviderError('ref-not-found', 'no branch')),
|
|
196
|
+
),
|
|
197
|
+
getTag: vi.fn(() => Promise.resolve({commitSha: VALID_COMMIT})),
|
|
198
|
+
});
|
|
199
|
+
const provider = new GiteaSourceControlProvider(gitea);
|
|
200
|
+
|
|
201
|
+
const result = provider.resolveRef({
|
|
202
|
+
connection: connection(),
|
|
203
|
+
externalRepositoryId: 'gitea:shipfox/platform',
|
|
204
|
+
ref: 'refs/heads/release',
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
await expect(result).rejects.toMatchObject({reason: 'ref-not-found'});
|
|
208
|
+
expect(gitea.getBranch).toHaveBeenCalledWith({
|
|
209
|
+
owner: 'shipfox',
|
|
210
|
+
repo: 'platform',
|
|
211
|
+
branch: 'release',
|
|
212
|
+
});
|
|
213
|
+
expect(gitea.getTag).not.toHaveBeenCalled();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('uses the tag endpoint first for a tag ref', async () => {
|
|
217
|
+
const gitea = giteaClient({
|
|
218
|
+
getTag: vi.fn(() => Promise.resolve({commitSha: VALID_COMMIT})),
|
|
219
|
+
});
|
|
220
|
+
const provider = new GiteaSourceControlProvider(gitea);
|
|
221
|
+
|
|
222
|
+
const result = await provider.resolveRef({
|
|
223
|
+
connection: connection(),
|
|
224
|
+
externalRepositoryId: 'gitea:shipfox/platform',
|
|
225
|
+
ref: 'refs/tags/v1.0.0',
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(result).toEqual({ref: 'refs/tags/v1.0.0', commit: VALID_COMMIT});
|
|
229
|
+
expect(gitea.getTag).toHaveBeenCalledWith({
|
|
230
|
+
owner: 'shipfox',
|
|
231
|
+
repo: 'platform',
|
|
232
|
+
tag: 'v1.0.0',
|
|
233
|
+
});
|
|
234
|
+
expect(gitea.getBranch).not.toHaveBeenCalled();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('maps a ref that is neither branch nor tag to ref-not-found', async () => {
|
|
238
|
+
const gitea = giteaClient({
|
|
239
|
+
getBranch: vi.fn(() =>
|
|
240
|
+
Promise.reject(new GiteaIntegrationProviderError('ref-not-found', 'no branch')),
|
|
241
|
+
),
|
|
242
|
+
getTag: vi.fn(() =>
|
|
243
|
+
Promise.reject(new GiteaIntegrationProviderError('ref-not-found', 'no tag')),
|
|
244
|
+
),
|
|
245
|
+
});
|
|
246
|
+
const provider = new GiteaSourceControlProvider(gitea);
|
|
247
|
+
|
|
248
|
+
const result = provider.resolveRef({
|
|
249
|
+
connection: connection(),
|
|
250
|
+
externalRepositoryId: 'gitea:shipfox/platform',
|
|
251
|
+
ref: 'feature/missing',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await expect(result).rejects.toMatchObject({reason: 'ref-not-found'});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('falls back to a tag for an unnamespaced ref', async () => {
|
|
258
|
+
const gitea = giteaClient({
|
|
259
|
+
getBranch: vi.fn(() =>
|
|
260
|
+
Promise.reject(new GiteaIntegrationProviderError('ref-not-found', 'no branch')),
|
|
261
|
+
),
|
|
262
|
+
getTag: vi.fn(() => Promise.resolve({commitSha: VALID_COMMIT})),
|
|
263
|
+
});
|
|
264
|
+
const provider = new GiteaSourceControlProvider(gitea);
|
|
265
|
+
|
|
266
|
+
const result = await provider.resolveRef({
|
|
267
|
+
connection: connection(),
|
|
268
|
+
externalRepositoryId: 'gitea:shipfox/platform',
|
|
269
|
+
ref: 'feature/release',
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(result).toEqual({ref: 'feature/release', commit: VALID_COMMIT});
|
|
273
|
+
expect(gitea.getBranch).toHaveBeenCalledWith({
|
|
274
|
+
owner: 'shipfox',
|
|
275
|
+
repo: 'platform',
|
|
276
|
+
branch: 'feature/release',
|
|
277
|
+
});
|
|
278
|
+
expect(gitea.getTag).toHaveBeenCalledWith({
|
|
279
|
+
owner: 'shipfox',
|
|
280
|
+
repo: 'platform',
|
|
281
|
+
tag: 'feature/release',
|
|
282
|
+
});
|
|
283
|
+
expect(gitea.getRepository).not.toHaveBeenCalled();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('preserves repository-not-found when the repository is missing', async () => {
|
|
287
|
+
const gitea = giteaClient({
|
|
288
|
+
getBranch: vi.fn(() =>
|
|
289
|
+
Promise.reject(new GiteaIntegrationProviderError('ref-not-found', 'no branch')),
|
|
290
|
+
),
|
|
291
|
+
getRepository: vi.fn(() =>
|
|
292
|
+
Promise.reject(new GiteaIntegrationProviderError('repository-not-found', 'no repo')),
|
|
293
|
+
),
|
|
294
|
+
});
|
|
295
|
+
const provider = new GiteaSourceControlProvider(gitea);
|
|
296
|
+
|
|
297
|
+
const result = provider.resolveRef({
|
|
298
|
+
connection: connection(),
|
|
299
|
+
externalRepositoryId: 'gitea:shipfox/platform',
|
|
300
|
+
ref: 'refs/heads/main',
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await expect(result).rejects.toMatchObject({reason: 'repository-not-found'});
|
|
304
|
+
expect(gitea.getTag).not.toHaveBeenCalled();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it.each([
|
|
308
|
+
'a'.repeat(40),
|
|
309
|
+
'refs/pull/17/head',
|
|
310
|
+
'main',
|
|
311
|
+
'-evil',
|
|
312
|
+
])('rejects ref %s as ref-invalid', async (ref) => {
|
|
313
|
+
const gitea = giteaClient();
|
|
314
|
+
const provider = new GiteaSourceControlProvider(gitea);
|
|
315
|
+
|
|
316
|
+
const result = provider.resolveRef({
|
|
317
|
+
connection: connection(),
|
|
318
|
+
externalRepositoryId: 'gitea:shipfox/platform',
|
|
319
|
+
ref,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
await expect(result).rejects.toMatchObject({reason: 'ref-invalid'});
|
|
323
|
+
expect(gitea.getBranch).not.toHaveBeenCalled();
|
|
324
|
+
expect(gitea.getTag).not.toHaveBeenCalled();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('propagates non-not-found branch failures without a tag fallback', async () => {
|
|
328
|
+
const gitea = giteaClient({
|
|
329
|
+
getBranch: vi.fn(() =>
|
|
330
|
+
Promise.reject(new GiteaIntegrationProviderError('access-denied', 'denied')),
|
|
331
|
+
),
|
|
332
|
+
});
|
|
333
|
+
const provider = new GiteaSourceControlProvider(gitea);
|
|
334
|
+
|
|
335
|
+
const result = provider.resolveRef({
|
|
336
|
+
connection: connection(),
|
|
337
|
+
externalRepositoryId: 'gitea:shipfox/platform',
|
|
338
|
+
ref: 'refs/heads/main',
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
await expect(result).rejects.toMatchObject({reason: 'access-denied'});
|
|
342
|
+
expect(gitea.getTag).not.toHaveBeenCalled();
|
|
343
|
+
});
|
|
344
|
+
|
|
171
345
|
it('lists org repositories scoped to the connection account', async () => {
|
|
172
346
|
const gitea = giteaClient();
|
|
173
347
|
const provider = new GiteaSourceControlProvider(gitea);
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type IntegrationConnection,
|
|
12
12
|
isRecord,
|
|
13
13
|
isValidGitObjectId,
|
|
14
|
+
isValidResolvableRef,
|
|
14
15
|
isValidTriggerRef,
|
|
15
16
|
type ListFilesInput,
|
|
16
17
|
type ListRepositoriesInput,
|
|
@@ -21,6 +22,8 @@ import {
|
|
|
21
22
|
type RepositoryPage,
|
|
22
23
|
type RepositorySnapshot,
|
|
23
24
|
type RepositoryVisibility,
|
|
25
|
+
type ResolvedRef,
|
|
26
|
+
type ResolveRefInput,
|
|
24
27
|
type ResolveRepositoryInput,
|
|
25
28
|
type SourceControlProvider,
|
|
26
29
|
type TriggerReference,
|
|
@@ -32,6 +35,8 @@ import {GiteaIntegrationProviderError} from './errors.js';
|
|
|
32
35
|
type GiteaIntegrationConnection = IntegrationConnection<'gitea'>;
|
|
33
36
|
|
|
34
37
|
const TRAILING_SLASHES_RE = /\/+$/;
|
|
38
|
+
const REFS_HEADS_PREFIX = 'refs/heads/';
|
|
39
|
+
const REFS_TAGS_PREFIX = 'refs/tags/';
|
|
35
40
|
const SEARCH_PAGE_SIZE = 100;
|
|
36
41
|
const SEARCH_MAX_PAGES_PER_REQUEST = 5;
|
|
37
42
|
|
|
@@ -183,6 +188,44 @@ export class GiteaSourceControlProvider
|
|
|
183
188
|
};
|
|
184
189
|
}
|
|
185
190
|
|
|
191
|
+
async resolveRef(input: ResolveRefInput<GiteaIntegrationConnection>): Promise<ResolvedRef> {
|
|
192
|
+
if (!isValidResolvableRef(input.ref)) {
|
|
193
|
+
throw new GiteaIntegrationProviderError(
|
|
194
|
+
'ref-invalid',
|
|
195
|
+
`Gitea ref ${formatRefForMessage(input.ref)} is not a resolvable branch or tag name`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
const {owner, repo} = parseGiteaRepositoryLocator(
|
|
199
|
+
input.externalRepositoryId,
|
|
200
|
+
input.connection.externalAccountId,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const providerRef = providerRefName(input.ref);
|
|
204
|
+
const branchLookup = () => this.gitea.getBranch({owner, repo, branch: providerRef});
|
|
205
|
+
const tagLookup = () => this.gitea.getTag({owner, repo, tag: providerRef});
|
|
206
|
+
const lookups: Array<() => Promise<{commitSha: string}>> = input.ref.startsWith(
|
|
207
|
+
REFS_TAGS_PREFIX,
|
|
208
|
+
)
|
|
209
|
+
? [tagLookup]
|
|
210
|
+
: input.ref.startsWith(REFS_HEADS_PREFIX)
|
|
211
|
+
? [branchLookup]
|
|
212
|
+
: [branchLookup, tagLookup];
|
|
213
|
+
for (const lookup of lookups) {
|
|
214
|
+
try {
|
|
215
|
+
const resolved = await lookup();
|
|
216
|
+
return {ref: input.ref, commit: resolved.commitSha};
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (!isRefNotFound(error)) throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
await this.gitea.getRepository({owner, repo});
|
|
223
|
+
throw new GiteaIntegrationProviderError(
|
|
224
|
+
'ref-not-found',
|
|
225
|
+
`Gitea ref ${formatRefForMessage(input.ref)} does not resolve to a branch or tag`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
186
229
|
async createCheckoutSpec(
|
|
187
230
|
input: CreateCheckoutSpecInput<GiteaIntegrationConnection>,
|
|
188
231
|
): Promise<CheckoutSpec> {
|
|
@@ -225,6 +268,20 @@ function giteaEventActor(payload: Record<string, unknown>): string | null {
|
|
|
225
268
|
return nonEmptyString(sender?.login) ?? nonEmptyString(sender?.username);
|
|
226
269
|
}
|
|
227
270
|
|
|
271
|
+
function isRefNotFound(error: unknown): boolean {
|
|
272
|
+
return error instanceof GiteaIntegrationProviderError && error.reason === 'ref-not-found';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function providerRefName(ref: string): string {
|
|
276
|
+
if (ref.startsWith(REFS_HEADS_PREFIX)) return ref.slice(REFS_HEADS_PREFIX.length);
|
|
277
|
+
if (ref.startsWith(REFS_TAGS_PREFIX)) return ref.slice(REFS_TAGS_PREFIX.length);
|
|
278
|
+
return ref;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function formatRefForMessage(ref: string): string {
|
|
282
|
+
return JSON.stringify(ref);
|
|
283
|
+
}
|
|
284
|
+
|
|
228
285
|
function sameGiteaRepository(
|
|
229
286
|
first: Record<string, unknown>,
|
|
230
287
|
second: Record<string, unknown>,
|
package/src/core/webhook.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type GiteaPushPayloadDto,
|
|
3
3
|
giteaProviderKind,
|
|
4
4
|
giteaPushPayloadSchema,
|
|
5
|
+
giteaWebhookEventNames,
|
|
5
6
|
} from '@shipfox/api-integration-gitea-dto';
|
|
6
7
|
import {
|
|
7
8
|
buildProviderRepositoryId,
|
|
@@ -67,7 +68,7 @@ function isBranchDeletion(after: string): boolean {
|
|
|
67
68
|
export async function handleGiteaWebhook(
|
|
68
69
|
params: HandleGiteaWebhookParams,
|
|
69
70
|
): Promise<{outcome: HandleGiteaWebhookOutcome}> {
|
|
70
|
-
if (params.event
|
|
71
|
+
if (!giteaWebhookEventNames.some((eventName) => eventName === params.event)) {
|
|
71
72
|
await params.recordDeliveryOnly({
|
|
72
73
|
tx: params.tx,
|
|
73
74
|
provider: giteaProviderKind,
|
package/src/index.test.ts
CHANGED
|
@@ -13,6 +13,7 @@ describe('createGiteaIntegrationProvider', () => {
|
|
|
13
13
|
|
|
14
14
|
expect(provider.provider).toBe('gitea');
|
|
15
15
|
expect(provider.displayName).toBe('Gitea');
|
|
16
|
+
expect(provider.eventCatalog?.events.map((event) => event.name)).toEqual(['push']);
|
|
16
17
|
expect(provider.adapters.source_control).toBeInstanceOf(GiteaSourceControlProvider);
|
|
17
18
|
expect(provider.routes).toHaveLength(2);
|
|
18
19
|
expect(provider.routes.map((group) => group.prefix)).toEqual([
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {giteaProviderKind} from '@shipfox/api-integration-gitea-dto';
|
|
1
|
+
import {giteaEventCatalog, giteaProviderKind} from '@shipfox/api-integration-gitea-dto';
|
|
2
2
|
import type {
|
|
3
3
|
GetIntegrationConnectionByIdFn,
|
|
4
4
|
IntegrationConnection,
|
|
@@ -67,6 +67,7 @@ export function createGiteaIntegrationProvider(options: CreateGiteaIntegrationPr
|
|
|
67
67
|
return {
|
|
68
68
|
provider: giteaProviderKind,
|
|
69
69
|
displayName: 'Gitea',
|
|
70
|
+
eventCatalog: giteaEventCatalog,
|
|
70
71
|
adapters: {
|
|
71
72
|
source_control: new GiteaSourceControlProvider(gitea),
|
|
72
73
|
},
|
|
@@ -40,6 +40,8 @@ function giteaClient(overrides: Partial<GiteaApiClient> = {}): GiteaApiClient {
|
|
|
40
40
|
listOrgRepositories: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
41
41
|
getRepository: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
42
42
|
resolveRef: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
43
|
+
getBranch: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
44
|
+
getTag: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
43
45
|
listTree: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
44
46
|
fetchFileContent: vi.fn(() => Promise.reject(new Error('not used'))),
|
|
45
47
|
organizationExists: vi.fn(() => Promise.resolve(true)),
|