@shipfox/api-integration-core 13.1.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 +4 -4
- package/CHANGELOG.md +31 -0
- package/dist/core/event-catalogs.d.ts +18 -0
- package/dist/core/event-catalogs.d.ts.map +1 -0
- package/dist/core/event-catalogs.js +27 -0
- package/dist/core/event-catalogs.js.map +1 -0
- package/dist/core/providers/registry.d.ts +1 -0
- package/dist/core/providers/registry.d.ts.map +1 -1
- package/dist/core/providers/registry.js +4 -1
- package/dist/core/providers/registry.js.map +1 -1
- package/dist/core/providers/source-control.d.ts +1 -1
- package/dist/core/providers/source-control.d.ts.map +1 -1
- package/dist/core/providers/source-control.js.map +1 -1
- package/dist/core/source-control-service.d.ts +5 -1
- package/dist/core/source-control-service.d.ts.map +1 -1
- package/dist/core/source-control-service.js +12 -0
- package/dist/core/source-control-service.js.map +1 -1
- package/dist/db/connections.d.ts +4 -1
- package/dist/db/connections.d.ts.map +1 -1
- package/dist/db/connections.js +15 -6
- package/dist/db/connections.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +2 -0
- package/dist/metrics/instance.js.map +1 -1
- package/dist/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +27 -5
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/presentation/routes/errors.d.ts.map +1 -1
- package/dist/presentation/routes/errors.js +1 -1
- package/dist/presentation/routes/errors.js.map +1 -1
- package/dist/presentation/routes/manage-connections.d.ts.map +1 -1
- package/dist/presentation/routes/manage-connections.js +7 -3
- package/dist/presentation/routes/manage-connections.js.map +1 -1
- package/dist/providers/gitea.d.ts.map +1 -1
- package/dist/providers/gitea.js +5 -1
- package/dist/providers/gitea.js.map +1 -1
- package/dist/providers/github.d.ts.map +1 -1
- package/dist/providers/github.js +6 -5
- package/dist/providers/github.js.map +1 -1
- package/dist/providers/jira.d.ts.map +1 -1
- package/dist/providers/jira.js +7 -2
- package/dist/providers/jira.js.map +1 -1
- package/dist/providers/linear.d.ts.map +1 -1
- package/dist/providers/linear.js +6 -4
- package/dist/providers/linear.js.map +1 -1
- package/dist/providers/sentry.d.ts.map +1 -1
- package/dist/providers/sentry.js +3 -1
- package/dist/providers/sentry.js.map +1 -1
- package/dist/providers/slack.d.ts.map +1 -1
- package/dist/providers/slack.js +6 -4
- package/dist/providers/slack.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/core/agent-tool-selection.test.ts +4 -0
- package/src/core/event-catalogs.test.ts +16 -0
- package/src/core/event-catalogs.ts +34 -0
- package/src/core/providers/registry.test.ts +4 -0
- package/src/core/providers/registry.ts +9 -3
- package/src/core/providers/source-control.ts +2 -0
- package/src/core/secret-cleanup.test.ts +1 -0
- package/src/core/source-control-service.test.ts +48 -0
- package/src/core/source-control-service.ts +16 -0
- package/src/db/connections.test.ts +167 -0
- package/src/db/connections.ts +21 -4
- package/src/db/secret-cleanups.test.ts +1 -0
- package/src/index.ts +2 -0
- package/src/metrics/instance.ts +2 -0
- package/src/presentation/inter-module.test.ts +219 -0
- package/src/presentation/inter-module.ts +28 -3
- package/src/presentation/routes/errors.ts +3 -0
- package/src/presentation/routes/list-connections.test.ts +13 -1
- package/src/presentation/routes/list-repositories.test.ts +9 -0
- package/src/presentation/routes/manage-connections.test.ts +52 -0
- package/src/presentation/routes/manage-connections.ts +6 -2
- package/src/providers/gitea.ts +5 -0
- package/src/providers/github.ts +6 -1
- package/src/providers/jira.test.ts +3 -0
- package/src/providers/jira.ts +6 -0
- package/src/providers/linear.test.ts +3 -0
- package/src/providers/linear.ts +6 -1
- package/src/providers/modules.test.ts +64 -1
- package/src/providers/sentry.ts +3 -0
- package/src/providers/slack.test.ts +4 -0
- package/src/providers/slack.ts +6 -1
- package/test/route-utils.ts +4 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import {INTEGRATION_CONNECTION_AVAILABLE} from '@shipfox/api-integration-core-dto';
|
|
2
|
+
import {sql} from 'drizzle-orm';
|
|
1
3
|
import {createIntegrationProviderRegistry} from '#core/providers/registry.js';
|
|
2
4
|
import {processIntegrationSecretCleanups} from '#core/secret-cleanup.js';
|
|
3
5
|
import {getIntegrationConnectionById, upsertIntegrationConnection} from '#db/connections.js';
|
|
6
|
+
import {db} from '#db/db.js';
|
|
7
|
+
import {integrationsOutbox} from '#db/schema/outbox.js';
|
|
4
8
|
import {listIntegrationSecretCleanups} from '#db/secret-cleanups.js';
|
|
5
9
|
import {createTestApp, sourceProvider, useIntegrationRouteTest} from '#test/route-utils.js';
|
|
6
10
|
|
|
@@ -15,6 +19,7 @@ describe('PATCH /integration-connections/:connectionId', () => {
|
|
|
15
19
|
externalAccountId: 'gitea-owner',
|
|
16
20
|
slug: 'gitea_owner',
|
|
17
21
|
displayName: 'Gitea',
|
|
22
|
+
capabilities: ['source_control'],
|
|
18
23
|
});
|
|
19
24
|
|
|
20
25
|
const res = await app.inject({
|
|
@@ -31,6 +36,41 @@ describe('PATCH /integration-connections/:connectionId', () => {
|
|
|
31
36
|
expect(reloaded?.lifecycleStatus).toBe('disabled');
|
|
32
37
|
});
|
|
33
38
|
|
|
39
|
+
it('publishes provider capabilities when reactivating a connection', async () => {
|
|
40
|
+
const app = await createTestApp([sourceProvider()]);
|
|
41
|
+
const connection = await upsertIntegrationConnection({
|
|
42
|
+
workspaceId: context.workspaceId,
|
|
43
|
+
provider: 'gitea',
|
|
44
|
+
externalAccountId: 'gitea-owner',
|
|
45
|
+
slug: 'gitea_owner',
|
|
46
|
+
displayName: 'Gitea',
|
|
47
|
+
lifecycleStatus: 'disabled',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const res = await app.inject({
|
|
51
|
+
method: 'PATCH',
|
|
52
|
+
url: `/integration-connections/${connection.id}`,
|
|
53
|
+
headers: {authorization: 'Bearer user'},
|
|
54
|
+
payload: {lifecycle_status: 'active'},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const [event] = await db()
|
|
58
|
+
.select({payload: integrationsOutbox.payload})
|
|
59
|
+
.from(integrationsOutbox)
|
|
60
|
+
.where(
|
|
61
|
+
sql`${integrationsOutbox.eventType} = ${INTEGRATION_CONNECTION_AVAILABLE} AND ${integrationsOutbox.payload}->>'connectionId' = ${connection.id}`,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(res.statusCode).toBe(200);
|
|
65
|
+
expect(event?.payload).toEqual({
|
|
66
|
+
provider: 'gitea',
|
|
67
|
+
workspaceId: context.workspaceId,
|
|
68
|
+
connectionId: connection.id,
|
|
69
|
+
slug: 'gitea_owner',
|
|
70
|
+
capabilities: ['source_control'],
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
34
74
|
it('returns not-found for a missing connection', async () => {
|
|
35
75
|
const app = await createTestApp([sourceProvider()]);
|
|
36
76
|
|
|
@@ -53,6 +93,7 @@ describe('PATCH /integration-connections/:connectionId', () => {
|
|
|
53
93
|
externalAccountId: 'gitea-owner',
|
|
54
94
|
slug: 'gitea_owner',
|
|
55
95
|
displayName: 'Gitea',
|
|
96
|
+
capabilities: ['source_control'],
|
|
56
97
|
});
|
|
57
98
|
|
|
58
99
|
const res = await app.inject({
|
|
@@ -75,6 +116,7 @@ describe('PATCH /integration-connections/:connectionId', () => {
|
|
|
75
116
|
externalAccountId: 'gitea-owner',
|
|
76
117
|
slug: 'gitea_owner',
|
|
77
118
|
displayName: 'Gitea',
|
|
119
|
+
capabilities: ['source_control'],
|
|
78
120
|
});
|
|
79
121
|
|
|
80
122
|
const res = await app.inject({
|
|
@@ -100,6 +142,7 @@ describe('DELETE /integration-connections/:connectionId', () => {
|
|
|
100
142
|
externalAccountId: 'gitea-owner',
|
|
101
143
|
slug: 'gitea_owner',
|
|
102
144
|
displayName: 'Gitea',
|
|
145
|
+
capabilities: ['source_control'],
|
|
103
146
|
});
|
|
104
147
|
|
|
105
148
|
const res = await app.inject({
|
|
@@ -133,6 +176,7 @@ describe('DELETE /integration-connections/:connectionId', () => {
|
|
|
133
176
|
externalAccountId: 'T123',
|
|
134
177
|
slug: 'slack_acme',
|
|
135
178
|
displayName: 'Slack Acme',
|
|
179
|
+
capabilities: [],
|
|
136
180
|
});
|
|
137
181
|
|
|
138
182
|
const res = await app.inject({
|
|
@@ -183,6 +227,7 @@ describe('DELETE /integration-connections/:connectionId', () => {
|
|
|
183
227
|
externalAccountId: 'T123',
|
|
184
228
|
slug: 'slack_acme',
|
|
185
229
|
displayName: 'Slack Acme',
|
|
230
|
+
capabilities: [],
|
|
186
231
|
});
|
|
187
232
|
|
|
188
233
|
const res = await app.inject({
|
|
@@ -234,6 +279,7 @@ describe('DELETE /integration-connections/:connectionId', () => {
|
|
|
234
279
|
externalAccountId: 'T123',
|
|
235
280
|
slug: 'slack_acme',
|
|
236
281
|
displayName: 'Slack Acme',
|
|
282
|
+
capabilities: [],
|
|
237
283
|
});
|
|
238
284
|
|
|
239
285
|
const res = await app.inject({
|
|
@@ -264,6 +310,7 @@ describe('DELETE /integration-connections/:connectionId', () => {
|
|
|
264
310
|
externalAccountId: 'T123',
|
|
265
311
|
slug: 'slack_acme',
|
|
266
312
|
displayName: 'Slack Acme',
|
|
313
|
+
capabilities: [],
|
|
267
314
|
});
|
|
268
315
|
|
|
269
316
|
const res = await app.inject({
|
|
@@ -297,6 +344,7 @@ describe('DELETE /integration-connections/:connectionId', () => {
|
|
|
297
344
|
externalAccountId: 'T123',
|
|
298
345
|
slug: 'slack_acme',
|
|
299
346
|
displayName: 'Slack Acme',
|
|
347
|
+
capabilities: [],
|
|
300
348
|
});
|
|
301
349
|
|
|
302
350
|
const res = await app.inject({
|
|
@@ -328,6 +376,7 @@ describe('DELETE /integration-connections/:connectionId', () => {
|
|
|
328
376
|
externalAccountId: 'T123',
|
|
329
377
|
slug: 'slack_acme',
|
|
330
378
|
displayName: 'Slack Acme',
|
|
379
|
+
capabilities: [],
|
|
331
380
|
});
|
|
332
381
|
|
|
333
382
|
const res = await app.inject({
|
|
@@ -356,6 +405,7 @@ describe('DELETE /integration-connections/:connectionId', () => {
|
|
|
356
405
|
externalAccountId: 'T123',
|
|
357
406
|
slug: 'slack_acme',
|
|
358
407
|
displayName: 'Slack Acme',
|
|
408
|
+
capabilities: [],
|
|
359
409
|
});
|
|
360
410
|
|
|
361
411
|
const res = await app.inject({
|
|
@@ -396,6 +446,7 @@ describe('DELETE /integration-connections/:connectionId', () => {
|
|
|
396
446
|
externalAccountId: 'T123',
|
|
397
447
|
slug: 'slack_acme',
|
|
398
448
|
displayName: 'Slack Acme',
|
|
449
|
+
capabilities: [],
|
|
399
450
|
});
|
|
400
451
|
|
|
401
452
|
const res = await app.inject({
|
|
@@ -443,6 +494,7 @@ describe('DELETE /integration-connections/:connectionId', () => {
|
|
|
443
494
|
externalAccountId: 'T123',
|
|
444
495
|
slug: 'slack_acme',
|
|
445
496
|
displayName: 'Slack Acme',
|
|
497
|
+
capabilities: [],
|
|
446
498
|
});
|
|
447
499
|
|
|
448
500
|
const res = await app.inject({
|
|
@@ -40,16 +40,20 @@ export function createUpdateIntegrationConnectionRoute(registry: IntegrationProv
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
requireWorkspaceAccess({request, workspaceId: connection.workspaceId});
|
|
43
|
+
const provider = registry
|
|
44
|
+
.list()
|
|
45
|
+
.find((candidate) => candidate.provider === connection.provider);
|
|
46
|
+
const capabilities = provider?.capabilities ?? [];
|
|
43
47
|
const updated = await updateIntegrationConnectionLifecycleStatus({
|
|
44
48
|
id: connection.id,
|
|
45
49
|
lifecycleStatus: request.body.lifecycle_status,
|
|
50
|
+
...(request.body.lifecycle_status === 'active' ? {capabilities} : {}),
|
|
46
51
|
});
|
|
47
52
|
if (!updated) {
|
|
48
53
|
throw new ClientError('Integration connection not found', 'not-found', {status: 404});
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
return toIntegrationConnectionDto(updated, {capabilities: provider?.capabilities ?? []});
|
|
56
|
+
return toIntegrationConnectionDto(updated, {capabilities});
|
|
53
57
|
},
|
|
54
58
|
});
|
|
55
59
|
}
|
package/src/providers/gitea.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type {ConnectGiteaConnectionInput} from '@shipfox/api-integration-gitea';
|
|
2
2
|
import type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';
|
|
3
3
|
import {config} from '#config.js';
|
|
4
|
+
import type {IntegrationCapability} from '#core/entities/provider.js';
|
|
5
|
+
import {getIntegrationProviderCapabilities} from '#core/providers/registry.js';
|
|
4
6
|
import {
|
|
5
7
|
getIntegrationConnectionById,
|
|
6
8
|
resolveUniqueConnectionSlug,
|
|
@@ -19,6 +21,7 @@ async function loadGiteaModuleParts(): Promise<IntegrationModuleParts> {
|
|
|
19
21
|
db: giteaDb,
|
|
20
22
|
migrationsPath: giteaMigrationsPath,
|
|
21
23
|
} = await import('@shipfox/api-integration-gitea');
|
|
24
|
+
let providerCapabilities: IntegrationCapability[] = [];
|
|
22
25
|
|
|
23
26
|
async function getExistingGiteaConnection(input: {
|
|
24
27
|
org: string;
|
|
@@ -53,6 +56,7 @@ async function loadGiteaModuleParts(): Promise<IntegrationModuleParts> {
|
|
|
53
56
|
slug,
|
|
54
57
|
displayName: input.displayName,
|
|
55
58
|
lifecycleStatus: 'active',
|
|
59
|
+
capabilities: providerCapabilities,
|
|
56
60
|
},
|
|
57
61
|
{tx},
|
|
58
62
|
);
|
|
@@ -78,6 +82,7 @@ async function loadGiteaModuleParts(): Promise<IntegrationModuleParts> {
|
|
|
78
82
|
getIntegrationConnectionById,
|
|
79
83
|
coreDb: db,
|
|
80
84
|
});
|
|
85
|
+
providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);
|
|
81
86
|
|
|
82
87
|
return {
|
|
83
88
|
provider: integrationProvider,
|
package/src/providers/github.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type {ConnectGithubInstallationInput} from '@shipfox/api-integration-github';
|
|
2
2
|
import type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';
|
|
3
3
|
import {config} from '#config.js';
|
|
4
|
+
import type {IntegrationCapability} from '#core/entities/provider.js';
|
|
5
|
+
import {getIntegrationProviderCapabilities} from '#core/providers/registry.js';
|
|
4
6
|
import {
|
|
5
7
|
getIntegrationConnectionById,
|
|
6
8
|
resolveUniqueConnectionSlug,
|
|
@@ -55,6 +57,7 @@ async function loadGithubModuleParts(
|
|
|
55
57
|
}
|
|
56
58
|
: undefined,
|
|
57
59
|
});
|
|
60
|
+
let providerCapabilities: IntegrationCapability[] = [];
|
|
58
61
|
|
|
59
62
|
async function getExistingGithubConnection(input: {
|
|
60
63
|
installationId: string;
|
|
@@ -91,6 +94,7 @@ async function loadGithubModuleParts(
|
|
|
91
94
|
slug,
|
|
92
95
|
displayName: input.displayName,
|
|
93
96
|
lifecycleStatus: 'active',
|
|
97
|
+
capabilities: providerCapabilities,
|
|
94
98
|
},
|
|
95
99
|
{tx},
|
|
96
100
|
);
|
|
@@ -123,6 +127,7 @@ async function loadGithubModuleParts(
|
|
|
123
127
|
? {requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership}
|
|
124
128
|
: {}),
|
|
125
129
|
});
|
|
130
|
+
providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);
|
|
126
131
|
|
|
127
132
|
return {
|
|
128
133
|
provider: integrationProvider,
|
|
@@ -131,7 +136,7 @@ async function loadGithubModuleParts(
|
|
|
131
136
|
createGithubE2eRoutes({
|
|
132
137
|
getExistingGithubConnection,
|
|
133
138
|
connectGithubInstallation,
|
|
134
|
-
connectionCapabilities:
|
|
139
|
+
connectionCapabilities: providerCapabilities,
|
|
135
140
|
}),
|
|
136
141
|
],
|
|
137
142
|
database: {
|
|
@@ -46,6 +46,7 @@ describe('jiraProviderModule', () => {
|
|
|
46
46
|
externalAccountId: cloudId,
|
|
47
47
|
slug: 'jira_acme',
|
|
48
48
|
displayName: 'Jira Acme',
|
|
49
|
+
capabilities: ['agent_tools'],
|
|
49
50
|
});
|
|
50
51
|
await upsertJiraInstallation({
|
|
51
52
|
connectionId: connection.id,
|
|
@@ -88,6 +89,7 @@ describe('jiraProviderModule', () => {
|
|
|
88
89
|
externalAccountId: cloudId,
|
|
89
90
|
slug: `jira_${cloudId}`,
|
|
90
91
|
displayName: 'Jira Acme',
|
|
92
|
+
capabilities: ['agent_tools'],
|
|
91
93
|
});
|
|
92
94
|
await upsertJiraInstallation({
|
|
93
95
|
connectionId: connection.id,
|
|
@@ -159,6 +161,7 @@ describe('jiraProviderModule', () => {
|
|
|
159
161
|
externalAccountId: cloudId,
|
|
160
162
|
slug: 'jira_acme_again',
|
|
161
163
|
displayName: 'Jira Acme',
|
|
164
|
+
capabilities: ['agent_tools'],
|
|
162
165
|
});
|
|
163
166
|
await upsertJiraInstallation({
|
|
164
167
|
connectionId: replacement.id,
|
package/src/providers/jira.ts
CHANGED
|
@@ -5,6 +5,8 @@ import type {
|
|
|
5
5
|
} from '@shipfox/api-integration-jira';
|
|
6
6
|
import type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';
|
|
7
7
|
import {config} from '#config.js';
|
|
8
|
+
import type {IntegrationCapability} from '#core/entities/provider.js';
|
|
9
|
+
import {getIntegrationProviderCapabilities} from '#core/providers/registry.js';
|
|
8
10
|
import {
|
|
9
11
|
deleteIntegrationConnection,
|
|
10
12
|
getIntegrationConnectionById,
|
|
@@ -45,6 +47,7 @@ async function loadJiraModuleParts(
|
|
|
45
47
|
withJiraWebhookRegistrationLock,
|
|
46
48
|
} = await import('@shipfox/api-integration-jira');
|
|
47
49
|
const jira = createJiraApiClient();
|
|
50
|
+
let providerCapabilities: IntegrationCapability[] = [];
|
|
48
51
|
|
|
49
52
|
async function getExistingJiraConnection(input: {
|
|
50
53
|
cloudId: string;
|
|
@@ -80,6 +83,7 @@ async function loadJiraModuleParts(
|
|
|
80
83
|
slug,
|
|
81
84
|
displayName: input.displayName,
|
|
82
85
|
lifecycleStatus: 'active',
|
|
86
|
+
capabilities: providerCapabilities,
|
|
83
87
|
},
|
|
84
88
|
{tx},
|
|
85
89
|
);
|
|
@@ -222,6 +226,7 @@ async function loadJiraModuleParts(
|
|
|
222
226
|
{
|
|
223
227
|
id: connectionId,
|
|
224
228
|
lifecycleStatus: 'active',
|
|
229
|
+
capabilities: providerCapabilities,
|
|
225
230
|
},
|
|
226
231
|
tx === undefined ? {} : {tx: tx as IntegrationTx},
|
|
227
232
|
);
|
|
@@ -240,6 +245,7 @@ async function loadJiraModuleParts(
|
|
|
240
245
|
: {}),
|
|
241
246
|
},
|
|
242
247
|
});
|
|
248
|
+
providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);
|
|
243
249
|
|
|
244
250
|
return {
|
|
245
251
|
provider: integrationProvider,
|
|
@@ -36,6 +36,7 @@ describe('linearProviderModule', () => {
|
|
|
36
36
|
externalAccountId: organizationId,
|
|
37
37
|
slug: 'linear_acme',
|
|
38
38
|
displayName: 'Linear Acme',
|
|
39
|
+
capabilities: ['agent_tools'],
|
|
39
40
|
});
|
|
40
41
|
await upsertLinearInstallation({
|
|
41
42
|
connectionId: connection.id,
|
|
@@ -67,6 +68,7 @@ describe('linearProviderModule', () => {
|
|
|
67
68
|
externalAccountId: organizationId,
|
|
68
69
|
slug: 'linear_acme_again',
|
|
69
70
|
displayName: 'Linear Acme',
|
|
71
|
+
capabilities: ['agent_tools'],
|
|
70
72
|
});
|
|
71
73
|
await upsertLinearInstallation({
|
|
72
74
|
connectionId: replacement.id,
|
|
@@ -107,6 +109,7 @@ describe('linearProviderModule', () => {
|
|
|
107
109
|
externalAccountId: organizationId,
|
|
108
110
|
slug: 'linear_acme',
|
|
109
111
|
displayName: 'Linear Acme',
|
|
112
|
+
capabilities: ['agent_tools'],
|
|
110
113
|
});
|
|
111
114
|
await upsertLinearInstallation({
|
|
112
115
|
connectionId: connection.id,
|
package/src/providers/linear.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
} from '@shipfox/api-integration-linear';
|
|
5
5
|
import type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';
|
|
6
6
|
import {config} from '#config.js';
|
|
7
|
+
import type {IntegrationCapability} from '#core/entities/provider.js';
|
|
8
|
+
import {getIntegrationProviderCapabilities} from '#core/providers/registry.js';
|
|
7
9
|
import {
|
|
8
10
|
deleteIntegrationConnection,
|
|
9
11
|
getIntegrationConnectionById,
|
|
@@ -36,6 +38,7 @@ async function loadLinearModuleParts(
|
|
|
36
38
|
migrationsPath: linearMigrationsPath,
|
|
37
39
|
upsertLinearInstallation,
|
|
38
40
|
} = await import('@shipfox/api-integration-linear');
|
|
41
|
+
let providerCapabilities: IntegrationCapability[] = [];
|
|
39
42
|
|
|
40
43
|
async function getExistingLinearConnection(input: {
|
|
41
44
|
organizationId: string;
|
|
@@ -72,6 +75,7 @@ async function loadLinearModuleParts(
|
|
|
72
75
|
slug,
|
|
73
76
|
displayName: input.displayName,
|
|
74
77
|
lifecycleStatus: 'active',
|
|
78
|
+
capabilities: providerCapabilities,
|
|
75
79
|
},
|
|
76
80
|
{tx},
|
|
77
81
|
);
|
|
@@ -160,6 +164,7 @@ async function loadLinearModuleParts(
|
|
|
160
164
|
: {}),
|
|
161
165
|
},
|
|
162
166
|
});
|
|
167
|
+
providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);
|
|
163
168
|
|
|
164
169
|
return {
|
|
165
170
|
provider: integrationProvider,
|
|
@@ -170,7 +175,7 @@ async function loadLinearModuleParts(
|
|
|
170
175
|
getExistingLinearConnection,
|
|
171
176
|
connectLinearInstallation,
|
|
172
177
|
disconnectLinearInstallation,
|
|
173
|
-
connectionCapabilities:
|
|
178
|
+
connectionCapabilities: providerCapabilities,
|
|
174
179
|
}),
|
|
175
180
|
],
|
|
176
181
|
database: {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import {INTEGRATION_CONNECTION_AVAILABLE} from '@shipfox/api-integration-core-dto';
|
|
2
|
+
import {closeApp, createApp} from '@shipfox/node-fastify';
|
|
3
|
+
import {eq, sql} from 'drizzle-orm';
|
|
4
|
+
import {db} from '#db/db.js';
|
|
5
|
+
import {integrationConnections} from '#db/schema/connections.js';
|
|
6
|
+
import {integrationsOutbox} from '#db/schema/outbox.js';
|
|
7
|
+
|
|
1
8
|
describe('loadEnabledProviderModules', () => {
|
|
2
|
-
afterEach(() => {
|
|
9
|
+
afterEach(async () => {
|
|
10
|
+
await closeApp();
|
|
3
11
|
vi.unstubAllEnvs();
|
|
4
12
|
vi.resetModules();
|
|
5
13
|
});
|
|
@@ -63,4 +71,59 @@ describe('loadEnabledProviderModules', () => {
|
|
|
63
71
|
expect(parts.map((part) => part.provider.provider)).toEqual(['jira', 'cron', 'webhook']);
|
|
64
72
|
expect(parts[0]?.provider).toMatchObject({provider: 'jira', displayName: 'Jira'});
|
|
65
73
|
});
|
|
74
|
+
|
|
75
|
+
it('publishes registry-derived capabilities for a GitHub connect flow', async () => {
|
|
76
|
+
vi.stubEnv('INTEGRATIONS_ENABLE_GITHUB_PROVIDER', 'true');
|
|
77
|
+
vi.resetModules();
|
|
78
|
+
|
|
79
|
+
const {createPostgresClient} = await import('@shipfox/node-postgres');
|
|
80
|
+
createPostgresClient();
|
|
81
|
+
const {loadEnabledProviderModules} = await import('#providers/modules.js');
|
|
82
|
+
const parts = await loadEnabledProviderModules();
|
|
83
|
+
const githubPart = parts.find((part) => part.provider.provider === 'github');
|
|
84
|
+
if (!githubPart?.e2eRoutes) throw new Error('GitHub E2E routes are not configured');
|
|
85
|
+
|
|
86
|
+
const workspaceId = crypto.randomUUID();
|
|
87
|
+
const installationId = Number.parseInt(crypto.randomUUID().replaceAll('-', '').slice(0, 8), 16);
|
|
88
|
+
try {
|
|
89
|
+
const app = await createApp({routes: githubPart.e2eRoutes, swagger: false});
|
|
90
|
+
const response = await app.inject({
|
|
91
|
+
method: 'POST',
|
|
92
|
+
url: '/integrations/github-connections',
|
|
93
|
+
payload: {
|
|
94
|
+
workspace_id: workspaceId,
|
|
95
|
+
installation_id: installationId,
|
|
96
|
+
account_login: 'shipfox-e2e',
|
|
97
|
+
display_name: 'GitHub E2E',
|
|
98
|
+
installer_user_id: crypto.randomUUID(),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const connection = response.json();
|
|
102
|
+
|
|
103
|
+
expect(response.statusCode).toBe(201);
|
|
104
|
+
expect(connection).toMatchObject({id: expect.any(String)});
|
|
105
|
+
|
|
106
|
+
const [event] = await db()
|
|
107
|
+
.select({payload: integrationsOutbox.payload})
|
|
108
|
+
.from(integrationsOutbox)
|
|
109
|
+
.where(
|
|
110
|
+
sql`${integrationsOutbox.eventType} = ${INTEGRATION_CONNECTION_AVAILABLE} AND ${integrationsOutbox.payload}->>'connectionId' = ${connection.id}`,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(event?.payload).toEqual({
|
|
114
|
+
provider: 'github',
|
|
115
|
+
workspaceId,
|
|
116
|
+
connectionId: connection.id,
|
|
117
|
+
slug: 'github_shipfox_e2e',
|
|
118
|
+
capabilities: ['source_control', 'agent_tools'],
|
|
119
|
+
});
|
|
120
|
+
} finally {
|
|
121
|
+
await db()
|
|
122
|
+
.delete(integrationsOutbox)
|
|
123
|
+
.where(sql`${integrationsOutbox.payload}->>'workspaceId' = ${workspaceId}`);
|
|
124
|
+
await db()
|
|
125
|
+
.delete(integrationConnections)
|
|
126
|
+
.where(eq(integrationConnections.workspaceId, workspaceId));
|
|
127
|
+
}
|
|
128
|
+
});
|
|
66
129
|
});
|
package/src/providers/sentry.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {ConnectSentryInstallationInput} from '@shipfox/api-integration-sentry';
|
|
2
2
|
import type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';
|
|
3
3
|
import {config} from '#config.js';
|
|
4
|
+
import type {IntegrationCapability} from '#core/entities/provider.js';
|
|
4
5
|
import {
|
|
5
6
|
getIntegrationConnectionById,
|
|
6
7
|
resolveUniqueConnectionSlug,
|
|
@@ -22,6 +23,7 @@ async function loadSentryModuleParts(): Promise<IntegrationModuleParts> {
|
|
|
22
23
|
db: sentryDb,
|
|
23
24
|
migrationsPath: sentryMigrationsPath,
|
|
24
25
|
} = await import('@shipfox/api-integration-sentry');
|
|
26
|
+
const providerCapabilities: IntegrationCapability[] = [];
|
|
25
27
|
|
|
26
28
|
async function getConnectionById(
|
|
27
29
|
id: string,
|
|
@@ -54,6 +56,7 @@ async function loadSentryModuleParts(): Promise<IntegrationModuleParts> {
|
|
|
54
56
|
slug,
|
|
55
57
|
displayName: input.displayName,
|
|
56
58
|
lifecycleStatus: 'active',
|
|
59
|
+
capabilities: providerCapabilities,
|
|
57
60
|
},
|
|
58
61
|
{tx},
|
|
59
62
|
);
|
|
@@ -38,6 +38,7 @@ describe('slackProviderModule', () => {
|
|
|
38
38
|
externalAccountId: teamId,
|
|
39
39
|
slug: `slack_${teamId}`,
|
|
40
40
|
displayName: 'Slack Acme',
|
|
41
|
+
capabilities: ['agent_tools'],
|
|
41
42
|
});
|
|
42
43
|
await upsertSlackInstallation({
|
|
43
44
|
connectionId: connection.id,
|
|
@@ -90,6 +91,7 @@ describe('slackProviderModule', () => {
|
|
|
90
91
|
externalAccountId: teamId,
|
|
91
92
|
slug: `slack_${teamId}`,
|
|
92
93
|
displayName: 'Slack Acme',
|
|
94
|
+
capabilities: ['agent_tools'],
|
|
93
95
|
});
|
|
94
96
|
await upsertSlackInstallation({
|
|
95
97
|
connectionId: connection.id,
|
|
@@ -121,6 +123,7 @@ describe('slackProviderModule', () => {
|
|
|
121
123
|
externalAccountId: teamId,
|
|
122
124
|
slug: `slack_${teamId}_again`,
|
|
123
125
|
displayName: 'Slack Acme',
|
|
126
|
+
capabilities: ['agent_tools'],
|
|
124
127
|
});
|
|
125
128
|
await upsertSlackInstallation({
|
|
126
129
|
connectionId: replacement.id,
|
|
@@ -167,6 +170,7 @@ describe('slackProviderModule', () => {
|
|
|
167
170
|
externalAccountId: teamId,
|
|
168
171
|
slug: `slack_${teamId}`,
|
|
169
172
|
displayName: 'Slack Acme',
|
|
173
|
+
capabilities: ['agent_tools'],
|
|
170
174
|
});
|
|
171
175
|
await upsertSlackInstallation({
|
|
172
176
|
connectionId: connection.id,
|
package/src/providers/slack.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
} from '@shipfox/api-integration-slack';
|
|
5
5
|
import type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';
|
|
6
6
|
import {config} from '#config.js';
|
|
7
|
+
import type {IntegrationCapability} from '#core/entities/provider.js';
|
|
8
|
+
import {getIntegrationProviderCapabilities} from '#core/providers/registry.js';
|
|
7
9
|
import {
|
|
8
10
|
deleteIntegrationConnection,
|
|
9
11
|
getIntegrationConnectionById,
|
|
@@ -39,6 +41,7 @@ async function loadSlackModuleParts(
|
|
|
39
41
|
slackSecretsNamespace,
|
|
40
42
|
upsertSlackInstallation,
|
|
41
43
|
} = await import('@shipfox/api-integration-slack');
|
|
44
|
+
let providerCapabilities: IntegrationCapability[] = [];
|
|
42
45
|
|
|
43
46
|
async function getExistingSlackConnection(input: {
|
|
44
47
|
teamId: string;
|
|
@@ -75,6 +78,7 @@ async function loadSlackModuleParts(
|
|
|
75
78
|
slug,
|
|
76
79
|
displayName: input.displayName,
|
|
77
80
|
lifecycleStatus: 'active',
|
|
81
|
+
capabilities: providerCapabilities,
|
|
78
82
|
},
|
|
79
83
|
{tx},
|
|
80
84
|
);
|
|
@@ -163,6 +167,7 @@ async function loadSlackModuleParts(
|
|
|
163
167
|
: {}),
|
|
164
168
|
},
|
|
165
169
|
});
|
|
170
|
+
providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);
|
|
166
171
|
|
|
167
172
|
return {
|
|
168
173
|
provider: integrationProvider,
|
|
@@ -173,7 +178,7 @@ async function loadSlackModuleParts(
|
|
|
173
178
|
getExistingSlackConnection,
|
|
174
179
|
connectSlackInstallation,
|
|
175
180
|
disconnectSlackInstallation,
|
|
176
|
-
connectionCapabilities:
|
|
181
|
+
connectionCapabilities: providerCapabilities,
|
|
177
182
|
}),
|
|
178
183
|
],
|
|
179
184
|
database: {
|
package/test/route-utils.ts
CHANGED
|
@@ -69,6 +69,10 @@ export function sourceProvider(overrides: Partial<IntegrationProvider> = {}): In
|
|
|
69
69
|
throw new Error('not used');
|
|
70
70
|
},
|
|
71
71
|
resolveTriggerReference: () => null,
|
|
72
|
+
resolveRef: async () => {
|
|
73
|
+
await Promise.resolve();
|
|
74
|
+
throw new Error('not used');
|
|
75
|
+
},
|
|
72
76
|
},
|
|
73
77
|
},
|
|
74
78
|
...overrides,
|