@shipfox/api-server 7.1.0 → 9.0.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +117 -0
- package/README.md +9 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules.d.ts +5 -0
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +8 -3
- package/dist/modules.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +35 -35
- package/src/auth-token-interoperability.test.ts +54 -0
- package/src/index.ts +1 -0
- package/src/modules.test.ts +68 -5
- package/src/modules.ts +13 -3
- package/test/published/_environment.mjs +61 -0
- package/test/published/composition.ts +19 -0
- package/test/published/development-conditions.development.mjs +3 -0
- package/test/published/login-methods-route.mjs +22 -0
- package/test/published/workflow-bundles.mjs +50 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@shipfox/api-server",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "9.0.1",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -11,9 +11,6 @@
|
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "dist/index.js",
|
|
13
13
|
"types": "dist/index.d.ts",
|
|
14
|
-
"imports": {
|
|
15
|
-
"#*": "./dist/*"
|
|
16
|
-
},
|
|
17
14
|
"exports": {
|
|
18
15
|
".": {
|
|
19
16
|
"types": "./dist/index.d.ts",
|
|
@@ -25,37 +22,40 @@
|
|
|
25
22
|
}
|
|
26
23
|
},
|
|
27
24
|
"dependencies": {
|
|
28
|
-
"@shipfox/annotations": "
|
|
29
|
-
"@shipfox/
|
|
30
|
-
"@shipfox/
|
|
31
|
-
"@shipfox/api-agent-dto": "
|
|
32
|
-
"@shipfox/api-auth": "
|
|
33
|
-
"@shipfox/api-auth-dto": "
|
|
34
|
-
"@shipfox/api-definitions
|
|
35
|
-
"@shipfox/api-
|
|
36
|
-
"@shipfox/api-
|
|
37
|
-
"@shipfox/api-
|
|
38
|
-
"@shipfox/api-integration-core": "
|
|
39
|
-
"@shipfox/api-
|
|
40
|
-
"@shipfox/api-logs": "
|
|
41
|
-
"@shipfox/api-projects
|
|
42
|
-
"@shipfox/api-
|
|
43
|
-
"@shipfox/api-runners
|
|
44
|
-
"@shipfox/api-
|
|
45
|
-
"@shipfox/api-
|
|
46
|
-
"@shipfox/api-
|
|
47
|
-
"@shipfox/api-
|
|
48
|
-
"@shipfox/api-
|
|
49
|
-
"@shipfox/api-
|
|
50
|
-
"@shipfox/api-
|
|
51
|
-
"@shipfox/api-workspaces": "
|
|
52
|
-
"@shipfox/config": "1.2.
|
|
53
|
-
"@shipfox/node-
|
|
54
|
-
"@shipfox/node-
|
|
55
|
-
"@shipfox/node-jwt": "0.3.
|
|
56
|
-
"@shipfox/node-
|
|
57
|
-
"@shipfox/node-
|
|
58
|
-
"@shipfox/node-
|
|
25
|
+
"@shipfox/annotations": "9.0.1",
|
|
26
|
+
"@shipfox/annotations-dto": "9.0.1",
|
|
27
|
+
"@shipfox/api-agent": "9.0.1",
|
|
28
|
+
"@shipfox/api-agent-dto": "9.0.1",
|
|
29
|
+
"@shipfox/api-auth": "9.0.1",
|
|
30
|
+
"@shipfox/api-auth-dto": "9.0.1",
|
|
31
|
+
"@shipfox/api-definitions": "9.0.1",
|
|
32
|
+
"@shipfox/api-definitions-dto": "9.0.1",
|
|
33
|
+
"@shipfox/api-dispatcher": "9.0.1",
|
|
34
|
+
"@shipfox/api-email-challenges": "1.1.0",
|
|
35
|
+
"@shipfox/api-integration-core": "9.0.1",
|
|
36
|
+
"@shipfox/api-integration-core-dto": "9.0.1",
|
|
37
|
+
"@shipfox/api-logs": "9.0.1",
|
|
38
|
+
"@shipfox/api-projects": "9.0.1",
|
|
39
|
+
"@shipfox/api-projects-dto": "9.0.1",
|
|
40
|
+
"@shipfox/api-runners": "9.0.1",
|
|
41
|
+
"@shipfox/api-runners-dto": "9.0.1",
|
|
42
|
+
"@shipfox/api-secrets": "9.0.1",
|
|
43
|
+
"@shipfox/api-secrets-dto": "9.0.1",
|
|
44
|
+
"@shipfox/api-triggers": "9.0.1",
|
|
45
|
+
"@shipfox/api-workflows": "9.0.1",
|
|
46
|
+
"@shipfox/api-workflows-dto": "9.0.1",
|
|
47
|
+
"@shipfox/api-workspaces-dto": "9.0.1",
|
|
48
|
+
"@shipfox/api-workspaces": "9.0.1",
|
|
49
|
+
"@shipfox/config": "1.2.3",
|
|
50
|
+
"@shipfox/node-error-monitoring": "0.2.1",
|
|
51
|
+
"@shipfox/node-fastify": "0.3.1",
|
|
52
|
+
"@shipfox/node-jwt": "0.3.1",
|
|
53
|
+
"@shipfox/node-module": "1.0.0",
|
|
54
|
+
"@shipfox/node-opentelemetry": "0.6.1",
|
|
55
|
+
"@shipfox/node-postgres": "0.4.3"
|
|
56
|
+
},
|
|
57
|
+
"imports": {
|
|
58
|
+
"#*": "./dist/*"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "shipfox-swc",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {createLeaseTokenAuthMethod, issueJobLeaseToken} from '@shipfox/api-auth';
|
|
2
|
+
import {AUTH_LEASED_JOB, requireLeasedJobContext} from '@shipfox/api-auth-context';
|
|
3
|
+
import {closeApp, createApp, defineRoute} from '@shipfox/node-fastify';
|
|
4
|
+
import {afterEach, describe, expect, it} from '@shipfox/vitest/vi';
|
|
5
|
+
|
|
6
|
+
vi.hoisted(() => {
|
|
7
|
+
process.env.AUTH_ROOT_KEY ??= 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=';
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const leaseRoute = defineRoute({
|
|
11
|
+
method: 'GET',
|
|
12
|
+
path: '/lease',
|
|
13
|
+
description: 'Auth composition test route.',
|
|
14
|
+
handler: (request) => {
|
|
15
|
+
const lease = requireLeasedJobContext(request);
|
|
16
|
+
return {job_id: lease.jobId, step_id: lease.currentStepId};
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('Auth token interoperability', () => {
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
await closeApp();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('accepts an Auth-issued step lease through the server route boundary', async () => {
|
|
26
|
+
const jobId = crypto.randomUUID();
|
|
27
|
+
const stepId = crypto.randomUUID();
|
|
28
|
+
const token = await issueJobLeaseToken({
|
|
29
|
+
jobId,
|
|
30
|
+
jobExecutionId: crypto.randomUUID(),
|
|
31
|
+
workflowRunId: crypto.randomUUID(),
|
|
32
|
+
workflowRunAttemptId: crypto.randomUUID(),
|
|
33
|
+
projectId: crypto.randomUUID(),
|
|
34
|
+
workspaceId: crypto.randomUUID(),
|
|
35
|
+
runnerSessionId: crypto.randomUUID(),
|
|
36
|
+
currentStepId: stepId,
|
|
37
|
+
currentStepAttempt: 1,
|
|
38
|
+
});
|
|
39
|
+
const app = await createApp({
|
|
40
|
+
auth: [createLeaseTokenAuthMethod()],
|
|
41
|
+
routes: [{prefix: '/runs/jobs/current', auth: AUTH_LEASED_JOB, routes: [leaseRoute]}],
|
|
42
|
+
swagger: false,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const res = await app.inject({
|
|
46
|
+
method: 'GET',
|
|
47
|
+
url: '/runs/jobs/current/lease',
|
|
48
|
+
headers: {authorization: `Bearer ${token}`},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(res.statusCode).toBe(200);
|
|
52
|
+
expect(res.json()).toEqual({job_id: jobId, step_id: stepId});
|
|
53
|
+
}, 15_000);
|
|
54
|
+
});
|
package/src/index.ts
CHANGED
package/src/modules.test.ts
CHANGED
|
@@ -2,15 +2,18 @@ import {annotationsInterModuleContract} from '@shipfox/annotations-dto/inter-mod
|
|
|
2
2
|
import {agentInterModuleContract} from '@shipfox/api-agent-dto/inter-module';
|
|
3
3
|
import {authInterModuleContract} from '@shipfox/api-auth-dto/inter-module';
|
|
4
4
|
import {definitionsInterModuleContract} from '@shipfox/api-definitions-dto/inter-module';
|
|
5
|
-
import {integrationsInterModuleContract} from '@shipfox/api-integration-core-dto';
|
|
6
|
-
import {projectsInterModuleContract} from '@shipfox/api-projects-dto';
|
|
5
|
+
import {integrationsInterModuleContract} from '@shipfox/api-integration-core-dto/inter-module';
|
|
6
|
+
import {projectsInterModuleContract} from '@shipfox/api-projects-dto/inter-module';
|
|
7
7
|
import {runnersInterModuleContract} from '@shipfox/api-runners-dto/inter-module';
|
|
8
8
|
import {
|
|
9
9
|
type SecretsInterModuleClient,
|
|
10
10
|
secretsInterModuleContract,
|
|
11
11
|
} from '@shipfox/api-secrets-dto/inter-module';
|
|
12
12
|
import {workflowsInterModuleContract} from '@shipfox/api-workflows-dto/inter-module';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
type WorkspacesInterModuleClient,
|
|
15
|
+
workspacesInterModuleContract,
|
|
16
|
+
} from '@shipfox/api-workspaces-dto/inter-module';
|
|
14
17
|
import {defineInterModulePresentation} from '@shipfox/inter-module';
|
|
15
18
|
import {defaultModules} from './modules.js';
|
|
16
19
|
|
|
@@ -30,6 +33,8 @@ const mocks = vi.hoisted(() => ({
|
|
|
30
33
|
deleteSecrets: vi.fn(),
|
|
31
34
|
getIntegrationConnectionById: vi.fn(),
|
|
32
35
|
getSecret: vi.fn(),
|
|
36
|
+
getWorkspaceCreator: vi.fn(),
|
|
37
|
+
listMembershipsForTokenClaims: vi.fn(),
|
|
33
38
|
setSecrets: vi.fn(),
|
|
34
39
|
}));
|
|
35
40
|
|
|
@@ -96,7 +101,8 @@ vi.mock('@shipfox/api-workspaces', () => ({
|
|
|
96
101
|
{
|
|
97
102
|
contract: workspacesInterModuleContract,
|
|
98
103
|
handlers: {
|
|
99
|
-
listMembershipsForTokenClaims:
|
|
104
|
+
listMembershipsForTokenClaims: mocks.listMembershipsForTokenClaims,
|
|
105
|
+
getWorkspaceCreator: mocks.getWorkspaceCreator,
|
|
100
106
|
preflightInvitationAcceptance: vi.fn(),
|
|
101
107
|
acceptInvitation: vi.fn(),
|
|
102
108
|
requireActiveMembership: vi.fn(),
|
|
@@ -123,6 +129,8 @@ describe('defaultModules', () => {
|
|
|
123
129
|
mocks.deleteSecrets.mockReset();
|
|
124
130
|
mocks.getIntegrationConnectionById.mockReset();
|
|
125
131
|
mocks.getSecret.mockReset();
|
|
132
|
+
mocks.getWorkspaceCreator.mockReset();
|
|
133
|
+
mocks.listMembershipsForTokenClaims.mockReset();
|
|
126
134
|
mocks.setSecrets.mockReset();
|
|
127
135
|
|
|
128
136
|
mocks.createIntegrationsContext.mockResolvedValue({
|
|
@@ -147,6 +155,8 @@ describe('defaultModules', () => {
|
|
|
147
155
|
mocks.createWorkspaceConnectionSnapshotLoader.mockReturnValue(vi.fn());
|
|
148
156
|
mocks.deleteSecrets.mockResolvedValue({deleted: 1});
|
|
149
157
|
mocks.getSecret.mockResolvedValue({value: 'secret'});
|
|
158
|
+
mocks.listMembershipsForTokenClaims.mockResolvedValue({memberships: []});
|
|
159
|
+
mocks.getWorkspaceCreator.mockResolvedValue({creatorUserId: null});
|
|
150
160
|
mocks.setSecrets.mockResolvedValue({});
|
|
151
161
|
mocks.createProjectsModule.mockReturnValue({
|
|
152
162
|
name: 'projects',
|
|
@@ -182,7 +192,11 @@ describe('defaultModules', () => {
|
|
|
182
192
|
interModulePresentations: [
|
|
183
193
|
{
|
|
184
194
|
contract: agentInterModuleContract,
|
|
185
|
-
handlers: {
|
|
195
|
+
handlers: {
|
|
196
|
+
getValidationCatalog: vi.fn(),
|
|
197
|
+
resolveAgentConfig: vi.fn(),
|
|
198
|
+
resolveRuntimeCredentials: vi.fn(),
|
|
199
|
+
},
|
|
186
200
|
},
|
|
187
201
|
],
|
|
188
202
|
});
|
|
@@ -248,6 +262,27 @@ describe('defaultModules', () => {
|
|
|
248
262
|
]);
|
|
249
263
|
});
|
|
250
264
|
|
|
265
|
+
it('registers one presentation for every composed inter-module client before sealing', async () => {
|
|
266
|
+
const modules = await defaultModules();
|
|
267
|
+
const presentationModules = modules.flatMap((module) =>
|
|
268
|
+
(module.interModulePresentations ?? []).map(({contract}) => contract.module),
|
|
269
|
+
);
|
|
270
|
+
const composedClientModules = [
|
|
271
|
+
agentInterModuleContract,
|
|
272
|
+
annotationsInterModuleContract,
|
|
273
|
+
authInterModuleContract,
|
|
274
|
+
definitionsInterModuleContract,
|
|
275
|
+
integrationsInterModuleContract,
|
|
276
|
+
projectsInterModuleContract,
|
|
277
|
+
runnersInterModuleContract,
|
|
278
|
+
secretsInterModuleContract,
|
|
279
|
+
workflowsInterModuleContract,
|
|
280
|
+
workspacesInterModuleContract,
|
|
281
|
+
].map((contract) => contract.module);
|
|
282
|
+
|
|
283
|
+
expect(presentationModules.sort()).toEqual(composedClientModules.sort());
|
|
284
|
+
});
|
|
285
|
+
|
|
251
286
|
it('injects Auth into the Runners module', async () => {
|
|
252
287
|
await defaultModules();
|
|
253
288
|
|
|
@@ -288,6 +323,34 @@ describe('defaultModules', () => {
|
|
|
288
323
|
]);
|
|
289
324
|
});
|
|
290
325
|
|
|
326
|
+
it('extends the default module list with the composed Workspaces client', async () => {
|
|
327
|
+
let workspaces: WorkspacesInterModuleClient | undefined;
|
|
328
|
+
const extensionModule = {name: 'cloud'};
|
|
329
|
+
const extension = vi.fn((options: {workspaces: WorkspacesInterModuleClient}) => {
|
|
330
|
+
workspaces = options.workspaces;
|
|
331
|
+
return [extensionModule];
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const modules = await defaultModules({extension});
|
|
335
|
+
const userId = crypto.randomUUID();
|
|
336
|
+
const memberships = await workspaces?.listMembershipsForTokenClaims({userId});
|
|
337
|
+
const workspaceId = crypto.randomUUID();
|
|
338
|
+
const creator = await workspaces?.getWorkspaceCreator({workspaceId});
|
|
339
|
+
|
|
340
|
+
expect(extension).toHaveBeenCalledWith({workspaces: expect.any(Object)});
|
|
341
|
+
expect(memberships).toEqual({memberships: []});
|
|
342
|
+
expect(creator).toEqual({creatorUserId: null});
|
|
343
|
+
expect(mocks.listMembershipsForTokenClaims).toHaveBeenCalledWith(
|
|
344
|
+
{userId},
|
|
345
|
+
expect.objectContaining({signal: expect.any(AbortSignal)}),
|
|
346
|
+
);
|
|
347
|
+
expect(mocks.getWorkspaceCreator).toHaveBeenCalledWith(
|
|
348
|
+
{workspaceId},
|
|
349
|
+
expect.objectContaining({signal: expect.any(AbortSignal)}),
|
|
350
|
+
);
|
|
351
|
+
expect(modules.at(-1)).toBe(extensionModule);
|
|
352
|
+
});
|
|
353
|
+
|
|
291
354
|
it('injects Workflows into integrations and logs and namespaces provider secrets', async () => {
|
|
292
355
|
await defaultModules();
|
|
293
356
|
|
package/src/modules.ts
CHANGED
|
@@ -13,10 +13,10 @@ import {definitionsInterModuleContract} from '@shipfox/api-definitions-dto/inter
|
|
|
13
13
|
import {dispatcherModule} from '@shipfox/api-dispatcher';
|
|
14
14
|
import {emailChallengesModule} from '@shipfox/api-email-challenges';
|
|
15
15
|
import {createIntegrationsContext, type WebhookDeliverySource} from '@shipfox/api-integration-core';
|
|
16
|
-
import {integrationsInterModuleContract} from '@shipfox/api-integration-core-dto';
|
|
16
|
+
import {integrationsInterModuleContract} from '@shipfox/api-integration-core-dto/inter-module';
|
|
17
17
|
import {createLogsModule} from '@shipfox/api-logs';
|
|
18
18
|
import {createProjectsModule} from '@shipfox/api-projects';
|
|
19
|
-
import {projectsInterModuleContract} from '@shipfox/api-projects-dto';
|
|
19
|
+
import {projectsInterModuleContract} from '@shipfox/api-projects-dto/inter-module';
|
|
20
20
|
import {createRunnersModule} from '@shipfox/api-runners';
|
|
21
21
|
import {runnersInterModuleContract} from '@shipfox/api-runners-dto/inter-module';
|
|
22
22
|
import {createSecretsModule} from '@shipfox/api-secrets';
|
|
@@ -25,7 +25,10 @@ import {createTriggersModule} from '@shipfox/api-triggers';
|
|
|
25
25
|
import {createWorkflowsModule} from '@shipfox/api-workflows';
|
|
26
26
|
import {workflowsInterModuleContract} from '@shipfox/api-workflows-dto/inter-module';
|
|
27
27
|
import {workspacesModule} from '@shipfox/api-workspaces';
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
type WorkspacesInterModuleClient,
|
|
30
|
+
workspacesInterModuleContract,
|
|
31
|
+
} from '@shipfox/api-workspaces-dto/inter-module';
|
|
29
32
|
import {reportError} from '@shipfox/node-error-monitoring';
|
|
30
33
|
import {durationToSeconds} from '@shipfox/node-jwt';
|
|
31
34
|
import type {ShipfoxModule} from '@shipfox/node-module';
|
|
@@ -38,9 +41,13 @@ import {logger} from '@shipfox/node-opentelemetry';
|
|
|
38
41
|
export interface DefaultModulesOptions {
|
|
39
42
|
webhookDeliverySource?: WebhookDeliverySource | undefined;
|
|
40
43
|
runnersModule?: DefaultRunnersModuleFactory | undefined;
|
|
44
|
+
extension?: DefaultModulesExtension | undefined;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
export type DefaultRunnersModuleFactory = (options: {auth: AuthInterModuleClient}) => ShipfoxModule;
|
|
48
|
+
export type DefaultModulesExtension = (options: {
|
|
49
|
+
workspaces: WorkspacesInterModuleClient;
|
|
50
|
+
}) => ShipfoxModule[];
|
|
44
51
|
|
|
45
52
|
export async function defaultModules(
|
|
46
53
|
options: DefaultModulesOptions = {},
|
|
@@ -151,8 +158,10 @@ export async function defaultModules(
|
|
|
151
158
|
const projectsModule = createProjectsModule({integrations: integrationsClient});
|
|
152
159
|
const definitionsModule = createDefinitionsModule({
|
|
153
160
|
projects: projectsClient,
|
|
161
|
+
agent: agentClient,
|
|
154
162
|
integrations: integrationsClient,
|
|
155
163
|
});
|
|
164
|
+
const extensionModules = options.extension?.({workspaces: workspacesClient}) ?? [];
|
|
156
165
|
|
|
157
166
|
const modules = [
|
|
158
167
|
emailChallengesModule,
|
|
@@ -181,6 +190,7 @@ export async function defaultModules(
|
|
|
181
190
|
}),
|
|
182
191
|
createTriggersModule({workflows: workflowsClient}),
|
|
183
192
|
dispatcherModule,
|
|
193
|
+
...extensionModules,
|
|
184
194
|
];
|
|
185
195
|
registerInterModulePresentations({transport: interModuleTransport, modules});
|
|
186
196
|
interModuleTransport.seal();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function publishedTestEnvironment() {
|
|
2
|
+
// Use the active workspace's isolated Postgres instead of a fixed CI port, which
|
|
3
|
+
// could point at an unrelated local database when this contract runs in a worktree.
|
|
4
|
+
const postgresHost = process.env.POSTGRES_HOST ?? '127.0.0.1';
|
|
5
|
+
const postgresPort = process.env.POSTGRES_PORT ?? '5432';
|
|
6
|
+
const postgresUsername = process.env.POSTGRES_USERNAME ?? 'shipfox';
|
|
7
|
+
const postgresPassword = process.env.POSTGRES_PASSWORD ?? 'password';
|
|
8
|
+
const postgresDatabase = 'api_test';
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
AUTH_ROOT_KEY: 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=',
|
|
12
|
+
DATABASE_URL: `postgres://${postgresUsername}:${postgresPassword}@${postgresHost}:${postgresPort}/${postgresDatabase}`,
|
|
13
|
+
GITEA_BASE_URL: 'https://gitea.example.com',
|
|
14
|
+
GITEA_SERVICE_TOKEN: 'external-consumer-token',
|
|
15
|
+
GITEA_SERVICE_USERNAME: 'shipfox-bot',
|
|
16
|
+
GITEA_WEBHOOK_SECRET: 'external-consumer-webhook-secret',
|
|
17
|
+
GITHUB_API_BASE_URL: 'https://api.github.com',
|
|
18
|
+
GITHUB_APP_CLIENT_ID: 'external-consumer-client-id',
|
|
19
|
+
GITHUB_APP_CLIENT_SECRET: 'external-consumer-client-secret',
|
|
20
|
+
GITHUB_APP_ID: '1',
|
|
21
|
+
GITHUB_APP_PRIVATE_KEY: 'external-consumer-private-key',
|
|
22
|
+
GITHUB_APP_SLUG: 'shipfox-external-consumer',
|
|
23
|
+
GITHUB_APP_USERNAME: 'shipfox-external-consumer',
|
|
24
|
+
GITHUB_APP_WEBHOOK_SECRET: 'external-consumer-webhook-secret',
|
|
25
|
+
GITHUB_INSTALL_STATE_SECRET: 'external-consumer-install-state-secret',
|
|
26
|
+
JIRA_OAUTH_CLIENT_ID: 'external-consumer-client-id',
|
|
27
|
+
JIRA_OAUTH_CLIENT_SECRET: 'external-consumer-client-secret',
|
|
28
|
+
JIRA_OAUTH_REDIRECT_URL: 'https://shipfox.example.com/integrations/jira/callback',
|
|
29
|
+
JIRA_WEBHOOK_BASE_URL: 'https://shipfox.example.com',
|
|
30
|
+
JIRA_WEBHOOK_SIGNING_SECRET: 'external-consumer-webhook-secret',
|
|
31
|
+
LINEAR_MCP_ENDPOINT: 'https://mcp.linear.app/mcp',
|
|
32
|
+
LINEAR_OAUTH_CLIENT_ID: 'external-consumer-client-id',
|
|
33
|
+
LINEAR_OAUTH_CLIENT_SECRET: 'external-consumer-client-secret',
|
|
34
|
+
LINEAR_OAUTH_REDIRECT_URL: 'https://shipfox.example.com/integrations/linear/callback',
|
|
35
|
+
LINEAR_WEBHOOK_SIGNING_SECRET: 'external-consumer-webhook-secret',
|
|
36
|
+
SLACK_API_BASE_URL: 'https://slack.example.com/api',
|
|
37
|
+
SLACK_OAUTH_CLIENT_ID: 'external-consumer-client-id',
|
|
38
|
+
SLACK_OAUTH_CLIENT_SECRET: 'external-consumer-client-secret',
|
|
39
|
+
SLACK_OAUTH_REDIRECT_URL: 'https://shipfox.example.com/integrations/slack/callback',
|
|
40
|
+
SLACK_SIGNING_SECRET: 'external-consumer-signing-secret',
|
|
41
|
+
LOG_STORAGE_S3_ACCESS_KEY_ID: 'external-consumer-access-key',
|
|
42
|
+
LOG_STORAGE_S3_BUCKET: 'shipfox-logs',
|
|
43
|
+
LOG_STORAGE_S3_ENDPOINT: 'http://127.0.0.1:3900',
|
|
44
|
+
LOG_STORAGE_S3_FORCE_PATH_STYLE: 'true',
|
|
45
|
+
LOG_STORAGE_S3_REGION: 'garage',
|
|
46
|
+
LOG_STORAGE_S3_SECRET_ACCESS_KEY: 'external-consumer-secret-key',
|
|
47
|
+
POSTGRES_DATABASE: postgresDatabase,
|
|
48
|
+
POSTGRES_HOST: postgresHost,
|
|
49
|
+
POSTGRES_MAX_CONNECTIONS: '5',
|
|
50
|
+
POSTGRES_PASSWORD: postgresPassword,
|
|
51
|
+
POSTGRES_PORT: postgresPort,
|
|
52
|
+
POSTGRES_USERNAME: postgresUsername,
|
|
53
|
+
SECRETS_ENCRYPTION_KEK: 'ZmVkY2JhOTg3NjU0MzIxMGZlZGNiYTk4NzY1NDMyMTA=',
|
|
54
|
+
SENTRY_APP_CLIENT_ID: 'external-consumer-client-id',
|
|
55
|
+
SENTRY_APP_CLIENT_SECRET: 'external-consumer-client-secret',
|
|
56
|
+
SENTRY_APP_SLUG: 'shipfox-external-consumer',
|
|
57
|
+
SENTRY_APP_VERIFY_INSTALL: 'true',
|
|
58
|
+
TEMPORAL_ADDRESS: '127.0.0.1:7233',
|
|
59
|
+
WORKSPACE_JWT_SECRET: 'external-consumer-workspace-secret',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {createRunnersModule} from '@shipfox/api-runners';
|
|
2
|
+
import {createServer, defaultModules} from '@shipfox/api-server';
|
|
3
|
+
|
|
4
|
+
void createServer({
|
|
5
|
+
modules: [
|
|
6
|
+
...(await defaultModules({
|
|
7
|
+
runnersModule: ({auth}) =>
|
|
8
|
+
createRunnersModule({
|
|
9
|
+
auth,
|
|
10
|
+
installationProvisioning: {
|
|
11
|
+
policy: {
|
|
12
|
+
filterEligibleWorkspaceIds: async (workspaceIds) => new Set(workspaceIds),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
}),
|
|
16
|
+
})),
|
|
17
|
+
{name: 'external-dummy'},
|
|
18
|
+
],
|
|
19
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const {loginMethodsResponseSchema} = await import('@shipfox/api-auth-dto');
|
|
2
|
+
const {createLoginMethodsRoute} = await import('@shipfox/api-server');
|
|
3
|
+
const {createApp} = await import('@shipfox/node-fastify');
|
|
4
|
+
|
|
5
|
+
const app = await createApp({
|
|
6
|
+
auth: [],
|
|
7
|
+
routes: [createLoginMethodsRoute({loginMethods: [{id: 'external-provider'}]})],
|
|
8
|
+
swagger: false,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const response = await app.inject({method: 'GET', url: '/auth/login-methods'});
|
|
13
|
+
if (response.statusCode !== 200) {
|
|
14
|
+
throw new Error('Packed login-method catalog route did not respond.');
|
|
15
|
+
}
|
|
16
|
+
const body = loginMethodsResponseSchema.parse(response.json());
|
|
17
|
+
if (body.login_methods[0]?.id !== 'external-provider') {
|
|
18
|
+
throw new Error('Packed login-method catalog route returned an unexpected contract.');
|
|
19
|
+
}
|
|
20
|
+
} finally {
|
|
21
|
+
await app.close();
|
|
22
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
process.env.INTEGRATIONS_ENABLE_SENTRY_PROVIDER = 'true';
|
|
2
|
+
process.env.NODE_ENV = 'production';
|
|
3
|
+
|
|
4
|
+
const {readdir, readFile} = await import('node:fs/promises');
|
|
5
|
+
const {dirname, join} = await import('node:path');
|
|
6
|
+
const {defaultModules} = await import('@shipfox/api-server');
|
|
7
|
+
const {loadProductionWorkflowBundle} = await import('@shipfox/node-temporal');
|
|
8
|
+
const modules = await defaultModules();
|
|
9
|
+
const workflowPaths = new Set(
|
|
10
|
+
modules.flatMap((module) => module.workers ?? []).map((worker) => worker.workflowsPath),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
if (!workflowPaths.size) throw new Error('Packed API server declares no workflow entrypoints.');
|
|
14
|
+
|
|
15
|
+
const bundles = new Map();
|
|
16
|
+
for (const workflowsPath of workflowPaths) {
|
|
17
|
+
const workflowBundle = loadProductionWorkflowBundle(workflowsPath);
|
|
18
|
+
bundles.set(workflowsPath, workflowBundle);
|
|
19
|
+
|
|
20
|
+
const code = await readFile(workflowBundle.codePath, 'utf8');
|
|
21
|
+
if (/@shipfox[/\\][^/\\]+[/\\]src[/\\]/u.test(code)) {
|
|
22
|
+
throw new Error(`Workflow bundle for ${workflowsPath} resolved a first-party source path.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const declaredCodePaths = new Set([...bundles.values()].map(({codePath}) => codePath));
|
|
27
|
+
if (declaredCodePaths.size !== workflowPaths.size) {
|
|
28
|
+
throw new Error('Packed API workflow entrypoints do not map one-to-one to prebuilt bundles.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const workflowDirectories = new Set(
|
|
32
|
+
[...workflowPaths].map((workflowsPath) => dirname(workflowsPath)),
|
|
33
|
+
);
|
|
34
|
+
const emittedBundles = (
|
|
35
|
+
await Promise.all(
|
|
36
|
+
[...workflowDirectories].map(async (directory) =>
|
|
37
|
+
(
|
|
38
|
+
await readdir(directory)
|
|
39
|
+
)
|
|
40
|
+
.filter((entry) => entry.endsWith('.bundle.js'))
|
|
41
|
+
.map((entry) => join(directory, entry)),
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
).flat();
|
|
45
|
+
const unreferencedBundles = emittedBundles.filter((codePath) => !declaredCodePaths.has(codePath));
|
|
46
|
+
if (unreferencedBundles.length) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Packed API contains unreferenced workflow bundles: ${unreferencedBundles.join(', ')}`,
|
|
49
|
+
);
|
|
50
|
+
}
|