@shipfox/api-integration-gitea 11.0.0 → 12.2.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 +31 -0
- package/dist/core/errors.d.ts.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 +59 -1
- package/dist/core/source-control.js.map +1 -1
- package/dist/core/webhook.d.ts.map +1 -1
- package/dist/db/db.d.ts +2 -2
- package/dist/db/schema/connections.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/presentation/dto/integrations.d.ts +3 -3
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/core/source-control.test.ts +115 -0
- package/src/core/source-control.ts +74 -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": "12.2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"drizzle-orm": "^0.45.2",
|
|
22
|
-
"@shipfox/api-auth-context": "
|
|
23
|
-
"@shipfox/api-integration-spi": "0.
|
|
24
|
-
"@shipfox/api-integration-gitea-dto": "
|
|
22
|
+
"@shipfox/api-auth-context": "12.2.0",
|
|
23
|
+
"@shipfox/api-integration-spi": "1.0.1",
|
|
24
|
+
"@shipfox/api-integration-gitea-dto": "12.2.0",
|
|
25
25
|
"@shipfox/config": "1.2.4",
|
|
26
|
-
"@shipfox/node-drizzle": "0.3.
|
|
27
|
-
"@shipfox/node-fastify": "0.4.
|
|
28
|
-
"@shipfox/node-opentelemetry": "0.6.
|
|
29
|
-
"@shipfox/node-postgres": "0.
|
|
26
|
+
"@shipfox/node-drizzle": "0.3.5",
|
|
27
|
+
"@shipfox/node-fastify": "0.4.2",
|
|
28
|
+
"@shipfox/node-opentelemetry": "0.6.4",
|
|
29
|
+
"@shipfox/node-postgres": "0.5.0"
|
|
30
30
|
},
|
|
31
31
|
"imports": {
|
|
32
32
|
"#*": "./dist/*"
|
|
@@ -3,6 +3,8 @@ import type {GiteaApiClient, GiteaRepository} from '#api/client.js';
|
|
|
3
3
|
import {GiteaIntegrationProviderError} from './errors.js';
|
|
4
4
|
import {GiteaSourceControlProvider} from './source-control.js';
|
|
5
5
|
|
|
6
|
+
const VALID_COMMIT = 'a'.repeat(40);
|
|
7
|
+
|
|
6
8
|
const REPOSITORY: GiteaRepository = {
|
|
7
9
|
ownerLogin: 'shipfox',
|
|
8
10
|
name: 'platform',
|
|
@@ -53,6 +55,119 @@ function connection(): IntegrationConnection<'gitea'> {
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
describe('GiteaSourceControlProvider', () => {
|
|
58
|
+
it('normalizes push trigger references', () => {
|
|
59
|
+
const provider = new GiteaSourceControlProvider(giteaClient());
|
|
60
|
+
|
|
61
|
+
const result = provider.resolveTriggerReference({
|
|
62
|
+
ref: 'refs/heads/feature/review',
|
|
63
|
+
after: VALID_COMMIT,
|
|
64
|
+
repository: {full_name: 'shipfox/platform'},
|
|
65
|
+
sender: {login: 'noe'},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(result).toEqual({
|
|
69
|
+
externalRepositoryId: 'gitea:shipfox/platform',
|
|
70
|
+
ref: 'refs/heads/feature/review',
|
|
71
|
+
commit: VALID_COMMIT,
|
|
72
|
+
actor: 'noe',
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('falls back to the sender username when no login is present', () => {
|
|
77
|
+
const provider = new GiteaSourceControlProvider(giteaClient());
|
|
78
|
+
|
|
79
|
+
const result = provider.resolveTriggerReference({
|
|
80
|
+
ref: 'refs/heads/main',
|
|
81
|
+
after: VALID_COMMIT,
|
|
82
|
+
repository: {full_name: 'shipfox/platform'},
|
|
83
|
+
sender: {username: 'noe'},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(result).toMatchObject({actor: 'noe'});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('resolves a null actor when the payload names no sender', () => {
|
|
90
|
+
const provider = new GiteaSourceControlProvider(giteaClient());
|
|
91
|
+
|
|
92
|
+
const result = provider.resolveTriggerReference({
|
|
93
|
+
ref: 'refs/heads/main',
|
|
94
|
+
after: VALID_COMMIT,
|
|
95
|
+
repository: {full_name: 'shipfox/platform'},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(result).toMatchObject({actor: null});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('normalizes pull-request trigger references from the head repository', () => {
|
|
102
|
+
const provider = new GiteaSourceControlProvider(giteaClient());
|
|
103
|
+
|
|
104
|
+
const result = provider.resolveTriggerReference({
|
|
105
|
+
repository: {full_name: 'shipfox/platform'},
|
|
106
|
+
sender: {login: 'noe'},
|
|
107
|
+
pull_request: {
|
|
108
|
+
number: 17,
|
|
109
|
+
head: {
|
|
110
|
+
sha: VALID_COMMIT,
|
|
111
|
+
repo: {full_name: 'shipfox/platform'},
|
|
112
|
+
},
|
|
113
|
+
base: {repo: {full_name: 'shipfox/platform'}},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(result).toEqual({
|
|
118
|
+
externalRepositoryId: 'gitea:shipfox/platform',
|
|
119
|
+
ref: 'refs/pull/17/head',
|
|
120
|
+
commit: VALID_COMMIT,
|
|
121
|
+
actor: 'noe',
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it.each([
|
|
126
|
+
undefined,
|
|
127
|
+
{},
|
|
128
|
+
{ref: 'refs/heads/main', after: VALID_COMMIT},
|
|
129
|
+
{
|
|
130
|
+
ref: 'refs/heads/feature branch',
|
|
131
|
+
after: VALID_COMMIT,
|
|
132
|
+
repository: {full_name: 'shipfox/platform'},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
ref: 'refs/heads/feature..branch',
|
|
136
|
+
after: VALID_COMMIT,
|
|
137
|
+
repository: {full_name: 'shipfox/platform'},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
ref: 'refs/heads/main',
|
|
141
|
+
after: '0'.repeat(40),
|
|
142
|
+
repository: {full_name: 'shipfox/platform'},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
ref: 'refs/heads/main',
|
|
146
|
+
after: 'not-a-commit',
|
|
147
|
+
repository: {full_name: 'shipfox/platform'},
|
|
148
|
+
},
|
|
149
|
+
{ref: 'refs/heads/main', after: VALID_COMMIT, repository: {full_name: 'shipfox'}},
|
|
150
|
+
{
|
|
151
|
+
ref: 'refs/heads/main',
|
|
152
|
+
after: VALID_COMMIT,
|
|
153
|
+
repository: {full_name: 'shipfox/platform/extra'},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
repository: {full_name: 'shipfox/platform'},
|
|
157
|
+
pull_request: {
|
|
158
|
+
number: 17,
|
|
159
|
+
head: {sha: VALID_COMMIT, repo: {full_name: 'fork/platform'}},
|
|
160
|
+
base: {repo: {full_name: 'shipfox/platform'}},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
])('returns null for an unresolvable or unsafe trigger payload', (payload) => {
|
|
164
|
+
const provider = new GiteaSourceControlProvider(giteaClient());
|
|
165
|
+
|
|
166
|
+
const result = provider.resolveTriggerReference(payload);
|
|
167
|
+
|
|
168
|
+
expect(result).toBeNull();
|
|
169
|
+
});
|
|
170
|
+
|
|
56
171
|
it('lists org repositories scoped to the connection account', async () => {
|
|
57
172
|
const gitea = giteaClient();
|
|
58
173
|
const provider = new GiteaSourceControlProvider(gitea);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {Buffer} from 'node:buffer';
|
|
2
2
|
import {giteaProviderKind} from '@shipfox/api-integration-gitea-dto';
|
|
3
3
|
import {
|
|
4
|
+
asRecord,
|
|
4
5
|
buildProviderRepositoryId,
|
|
5
6
|
type CheckoutSpec,
|
|
6
7
|
type CreateCheckoutSpecInput,
|
|
@@ -8,15 +9,21 @@ import {
|
|
|
8
9
|
type FilePage,
|
|
9
10
|
type FileSnapshot,
|
|
10
11
|
type IntegrationConnection,
|
|
12
|
+
isRecord,
|
|
13
|
+
isValidGitObjectId,
|
|
14
|
+
isValidTriggerRef,
|
|
11
15
|
type ListFilesInput,
|
|
12
16
|
type ListRepositoriesInput,
|
|
13
17
|
MAX_REPOSITORY_FILE_BYTES,
|
|
18
|
+
nonEmptyString,
|
|
14
19
|
parseProviderRepositoryId,
|
|
20
|
+
positiveInteger,
|
|
15
21
|
type RepositoryPage,
|
|
16
22
|
type RepositorySnapshot,
|
|
17
23
|
type RepositoryVisibility,
|
|
18
24
|
type ResolveRepositoryInput,
|
|
19
25
|
type SourceControlProvider,
|
|
26
|
+
type TriggerReference,
|
|
20
27
|
} from '@shipfox/api-integration-spi';
|
|
21
28
|
import type {GiteaApiClient, GiteaRepository} from '#api/client.js';
|
|
22
29
|
import {config, giteaCloneBaseOrigin} from '#config.js';
|
|
@@ -134,6 +141,48 @@ export class GiteaSourceControlProvider
|
|
|
134
141
|
};
|
|
135
142
|
}
|
|
136
143
|
|
|
144
|
+
resolveTriggerReference(payload: unknown): TriggerReference | null {
|
|
145
|
+
if (!isRecord(payload)) return null;
|
|
146
|
+
|
|
147
|
+
const actor = giteaEventActor(payload);
|
|
148
|
+
const pullRequest = asRecord(payload.pull_request);
|
|
149
|
+
if (pullRequest) {
|
|
150
|
+
const head = asRecord(pullRequest.head);
|
|
151
|
+
const repository = asRecord(head?.repo);
|
|
152
|
+
const base = asRecord(pullRequest.base);
|
|
153
|
+
const baseRepository = asRecord(base?.repo) ?? asRecord(payload.repository);
|
|
154
|
+
if (!repository || !baseRepository || !sameGiteaRepository(repository, baseRepository)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const repositoryId = giteaRepositoryId(repository);
|
|
158
|
+
const number = positiveInteger(pullRequest.number);
|
|
159
|
+
const commit = nonEmptyString(head?.sha);
|
|
160
|
+
const ref = number === null ? null : `refs/pull/${number}/head`;
|
|
161
|
+
if (!repositoryId || !ref || !commit) return null;
|
|
162
|
+
const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);
|
|
163
|
+
if (!hasValidReference) return null;
|
|
164
|
+
return {
|
|
165
|
+
externalRepositoryId: buildProviderRepositoryId(giteaProviderKind, repositoryId),
|
|
166
|
+
ref,
|
|
167
|
+
commit,
|
|
168
|
+
actor,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const repositoryId = giteaRepositoryId(asRecord(payload.repository));
|
|
173
|
+
const ref = nonEmptyString(payload.ref);
|
|
174
|
+
const commit = nonEmptyString(payload.after);
|
|
175
|
+
if (!repositoryId || !ref || !commit) return null;
|
|
176
|
+
const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);
|
|
177
|
+
if (!hasValidReference) return null;
|
|
178
|
+
return {
|
|
179
|
+
externalRepositoryId: buildProviderRepositoryId(giteaProviderKind, repositoryId),
|
|
180
|
+
ref,
|
|
181
|
+
commit,
|
|
182
|
+
actor,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
137
186
|
async createCheckoutSpec(
|
|
138
187
|
input: CreateCheckoutSpecInput<GiteaIntegrationConnection>,
|
|
139
188
|
): Promise<CheckoutSpec> {
|
|
@@ -160,6 +209,31 @@ export class GiteaSourceControlProvider
|
|
|
160
209
|
}
|
|
161
210
|
}
|
|
162
211
|
|
|
212
|
+
function giteaRepositoryId(repository: Record<string, unknown> | null): string | null {
|
|
213
|
+
const fullName = nonEmptyString(repository?.full_name);
|
|
214
|
+
if (!fullName) return null;
|
|
215
|
+
const separatorIndex = fullName.indexOf('/');
|
|
216
|
+
const owner = separatorIndex > 0 ? fullName.slice(0, separatorIndex) : '';
|
|
217
|
+
const repo = separatorIndex > 0 ? fullName.slice(separatorIndex + 1) : '';
|
|
218
|
+
return owner && repo && !repo.includes('/') ? `${owner}/${repo}` : null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Gitea mirrors GitHub's webhook envelope but names the handle `username` on some events
|
|
222
|
+
// and `login` on others, so both are read before giving up on an actor.
|
|
223
|
+
function giteaEventActor(payload: Record<string, unknown>): string | null {
|
|
224
|
+
const sender = asRecord(payload.sender);
|
|
225
|
+
return nonEmptyString(sender?.login) ?? nonEmptyString(sender?.username);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function sameGiteaRepository(
|
|
229
|
+
first: Record<string, unknown>,
|
|
230
|
+
second: Record<string, unknown>,
|
|
231
|
+
): boolean {
|
|
232
|
+
const firstId = giteaRepositoryId(first);
|
|
233
|
+
const secondId = giteaRepositoryId(second);
|
|
234
|
+
return Boolean(firstId && secondId && firstId.toLowerCase() === secondId.toLowerCase());
|
|
235
|
+
}
|
|
236
|
+
|
|
163
237
|
function createCheckoutRepositoryUrl(cloneUrl: string): string {
|
|
164
238
|
if (!giteaCloneBaseOrigin) return cloneUrl;
|
|
165
239
|
|