@shipfox/api-server 9.2.0 → 10.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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@shipfox/api-server",
3
3
  "license": "MIT",
4
4
  "private": false,
5
- "version": "9.2.0",
5
+ "version": "10.0.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -22,36 +22,36 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@shipfox/annotations": "9.2.0",
25
+ "@shipfox/annotations": "10.0.0",
26
26
  "@shipfox/annotations-dto": "9.0.2",
27
- "@shipfox/api-agent": "9.2.0",
28
- "@shipfox/api-agent-dto": "9.0.2",
29
- "@shipfox/api-auth": "9.2.0",
30
- "@shipfox/api-auth-dto": "9.2.0",
31
- "@shipfox/api-definitions": "9.2.0",
32
- "@shipfox/api-definitions-dto": "9.0.2",
33
- "@shipfox/api-dispatcher": "9.0.3",
34
- "@shipfox/api-email-challenges": "1.1.4",
35
- "@shipfox/api-integration-core": "9.2.0",
27
+ "@shipfox/api-agent": "10.0.0",
28
+ "@shipfox/api-agent-dto": "10.0.0",
29
+ "@shipfox/api-auth": "10.0.0",
30
+ "@shipfox/api-auth-dto": "10.0.0",
31
+ "@shipfox/api-definitions": "10.0.0",
32
+ "@shipfox/api-definitions-dto": "10.0.0",
33
+ "@shipfox/api-dispatcher": "10.0.0",
34
+ "@shipfox/api-email-challenges": "1.1.6",
35
+ "@shipfox/api-integration-core": "10.0.0",
36
36
  "@shipfox/api-integration-core-dto": "9.0.2",
37
- "@shipfox/api-logs": "9.2.0",
38
- "@shipfox/api-projects": "9.2.0",
39
- "@shipfox/api-projects-dto": "9.2.0",
40
- "@shipfox/api-runners": "9.2.0",
41
- "@shipfox/api-runners-dto": "9.0.2",
42
- "@shipfox/api-secrets": "9.2.0",
37
+ "@shipfox/api-logs": "10.0.0",
38
+ "@shipfox/api-projects": "10.0.0",
39
+ "@shipfox/api-projects-dto": "10.0.0",
40
+ "@shipfox/api-runners": "10.0.0",
41
+ "@shipfox/api-runners-dto": "10.0.0",
42
+ "@shipfox/api-secrets": "10.0.0",
43
43
  "@shipfox/api-secrets-dto": "9.0.2",
44
- "@shipfox/api-triggers": "9.2.0",
45
- "@shipfox/api-workflows": "9.2.0",
46
- "@shipfox/api-workflows-dto": "9.0.2",
47
- "@shipfox/api-workspaces-dto": "9.2.0",
48
- "@shipfox/api-workspaces": "9.2.0",
44
+ "@shipfox/api-triggers": "10.0.0",
45
+ "@shipfox/api-workflows": "10.0.0",
46
+ "@shipfox/api-workflows-dto": "10.0.0",
47
+ "@shipfox/api-workspaces-dto": "10.0.0",
48
+ "@shipfox/api-workspaces": "10.0.0",
49
49
  "@shipfox/config": "1.2.4",
50
50
  "@shipfox/node-error-monitoring": "0.3.0",
51
- "@shipfox/node-fastify": "0.3.3",
51
+ "@shipfox/node-fastify": "0.4.0",
52
52
  "@shipfox/node-jwt": "0.3.2",
53
- "@shipfox/node-module": "1.0.2",
54
- "@shipfox/node-opentelemetry": "0.6.2",
53
+ "@shipfox/node-module": "1.0.4",
54
+ "@shipfox/node-opentelemetry": "0.6.3",
55
55
  "@shipfox/node-postgres": "0.4.4"
56
56
  },
57
57
  "imports": {
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export {
2
+ type DefaultAuthModuleFactory,
2
3
  type DefaultModulesExtension,
3
4
  type DefaultModulesOptions,
4
5
  type DefaultRunnersModuleFactory,
@@ -8,5 +8,5 @@ const {version} = createRequire(import.meta.url)('../package.json') as {version:
8
8
  await startInstanceInstrumentation({
9
9
  serviceName: 'api',
10
10
  serviceVersion: version,
11
- instrumentations: {fastify: true, http: true, pg: true, awsSdk: true},
11
+ instrumentations: {fastify: true, http: true, pg: true, awsSdk: true, pino: true},
12
12
  });
@@ -21,6 +21,7 @@ const mocks = vi.hoisted(() => ({
21
21
  buildAgentToolCatalogs: vi.fn(),
22
22
  buildAgentToolSelectionCatalogs: vi.fn(),
23
23
  createAgentModule: vi.fn(),
24
+ createAuthModule: vi.fn(),
24
25
  createDefinitionsModule: vi.fn(),
25
26
  createIntegrationsContext: vi.fn(),
26
27
  createLogsModule: vi.fn(),
@@ -29,11 +30,13 @@ const mocks = vi.hoisted(() => ({
29
30
  createSecretsModule: vi.fn(),
30
31
  createTriggersModule: vi.fn(),
31
32
  createWorkflowsModule: vi.fn(),
33
+ createWorkspacesModule: vi.fn(),
32
34
  createWorkspaceConnectionSnapshotLoader: vi.fn(),
33
35
  deleteSecrets: vi.fn(),
34
36
  getIntegrationConnectionById: vi.fn(),
35
37
  getSecret: vi.fn(),
36
38
  getWorkspaceCreator: vi.fn(),
39
+ getWorkspaceOperatingState: vi.fn(),
37
40
  listMembershipsForTokenClaims: vi.fn(),
38
41
  setSecrets: vi.fn(),
39
42
  }));
@@ -50,20 +53,7 @@ vi.mock('@shipfox/annotations', () => ({
50
53
  },
51
54
  }));
52
55
  vi.mock('@shipfox/api-auth', () => ({
53
- createAuthModule: () => ({
54
- name: 'auth',
55
- interModulePresentations: [
56
- {
57
- contract: authInterModuleContract,
58
- handlers: {
59
- mintJobLeaseToken: vi.fn(),
60
- mintRunnerSessionToken: vi.fn(),
61
- getCurrentAdminRole: vi.fn(),
62
- requireAdminRole: vi.fn(),
63
- },
64
- },
65
- ],
66
- }),
56
+ createAuthModule: mocks.createAuthModule,
67
57
  }));
68
58
  vi.mock('@shipfox/api-agent', () => ({createAgentModule: mocks.createAgentModule}));
69
59
  vi.mock('@shipfox/api-auth/config', () => ({
@@ -97,21 +87,7 @@ vi.mock('@shipfox/api-workflows', () => ({
97
87
  createWorkflowsModule: mocks.createWorkflowsModule,
98
88
  }));
99
89
  vi.mock('@shipfox/api-workspaces', () => ({
100
- workspacesModule: {
101
- name: 'workspaces',
102
- interModulePresentations: [
103
- {
104
- contract: workspacesInterModuleContract,
105
- handlers: {
106
- listMembershipsForTokenClaims: mocks.listMembershipsForTokenClaims,
107
- getWorkspaceCreator: mocks.getWorkspaceCreator,
108
- preflightInvitationAcceptance: vi.fn(),
109
- acceptInvitation: vi.fn(),
110
- requireActiveMembership: vi.fn(),
111
- },
112
- },
113
- ],
114
- },
90
+ createWorkspacesModule: mocks.createWorkspacesModule,
115
91
  }));
116
92
 
117
93
  describe('defaultModules', () => {
@@ -119,6 +95,7 @@ describe('defaultModules', () => {
119
95
  mocks.buildAgentToolCatalogs.mockReset();
120
96
  mocks.buildAgentToolSelectionCatalogs.mockReset();
121
97
  mocks.createAgentModule.mockReset();
98
+ mocks.createAuthModule.mockReset();
122
99
  mocks.createDefinitionsModule.mockReset();
123
100
  mocks.createIntegrationsContext.mockReset();
124
101
  mocks.createLogsModule.mockReset();
@@ -127,6 +104,7 @@ describe('defaultModules', () => {
127
104
  mocks.createSecretsModule.mockReset();
128
105
  mocks.createTriggersModule.mockReset();
129
106
  mocks.createWorkflowsModule.mockReset();
107
+ mocks.createWorkspacesModule.mockReset();
130
108
  mocks.createWorkspaceConnectionSnapshotLoader.mockReset();
131
109
  mocks.deleteSecrets.mockReset();
132
110
  mocks.getIntegrationConnectionById.mockReset();
@@ -155,6 +133,20 @@ describe('defaultModules', () => {
155
133
  mocks.buildAgentToolCatalogs.mockResolvedValue(new Map());
156
134
  mocks.buildAgentToolSelectionCatalogs.mockResolvedValue(new Map());
157
135
  mocks.createWorkspaceConnectionSnapshotLoader.mockReturnValue(vi.fn());
136
+ mocks.createAuthModule.mockReturnValue({
137
+ name: 'auth',
138
+ interModulePresentations: [
139
+ {
140
+ contract: authInterModuleContract,
141
+ handlers: {
142
+ mintJobLeaseToken: vi.fn(),
143
+ mintRunnerSessionToken: vi.fn(),
144
+ getCurrentAdminRole: vi.fn(),
145
+ requireAdminRole: vi.fn(),
146
+ },
147
+ },
148
+ ],
149
+ });
158
150
  mocks.deleteSecrets.mockResolvedValue({deleted: 1});
159
151
  mocks.getSecret.mockResolvedValue({value: 'secret'});
160
152
  mocks.listMembershipsForTokenClaims.mockResolvedValue({memberships: []});
@@ -165,6 +157,7 @@ describe('defaultModules', () => {
165
157
  interModulePresentations: [
166
158
  defineInterModulePresentation(projectsInterModuleContract, {
167
159
  getProjectById: () => ({project: null}),
160
+ getWorkspaceProjectCounts: () => ({counts: []}),
168
161
  requireProjectForWorkspace: () => ({
169
162
  project: {
170
163
  id: crypto.randomUUID(),
@@ -211,6 +204,7 @@ describe('defaultModules', () => {
211
204
  cancelJobs: vi.fn(),
212
205
  enqueueJobExecution: vi.fn(),
213
206
  getEffectiveRunnerToolCapabilities: vi.fn(),
207
+ getWorkspaceJobCounts: vi.fn(),
214
208
  getLeaseState: vi.fn(),
215
209
  releaseJobExecution: vi.fn(),
216
210
  },
@@ -241,6 +235,22 @@ describe('defaultModules', () => {
241
235
  ],
242
236
  });
243
237
  mocks.createTriggersModule.mockReturnValue({name: 'triggers'});
238
+ mocks.createWorkspacesModule.mockReturnValue({
239
+ name: 'workspaces',
240
+ interModulePresentations: [
241
+ {
242
+ contract: workspacesInterModuleContract,
243
+ handlers: {
244
+ listMembershipsForTokenClaims: mocks.listMembershipsForTokenClaims,
245
+ getWorkspaceCreator: mocks.getWorkspaceCreator,
246
+ getWorkspaceOperatingState: mocks.getWorkspaceOperatingState,
247
+ preflightInvitationAcceptance: vi.fn(),
248
+ acceptInvitation: vi.fn(),
249
+ requireActiveMembership: vi.fn(),
250
+ },
251
+ },
252
+ ],
253
+ });
244
254
  });
245
255
 
246
256
  it('returns the API modules in lifecycle order', async () => {
@@ -291,6 +301,43 @@ describe('defaultModules', () => {
291
301
  expect(mocks.createRunnersModule).toHaveBeenCalledWith({auth: expect.any(Object)});
292
302
  });
293
303
 
304
+ it('uses the default Auth module factory when none is supplied', async () => {
305
+ await defaultModules();
306
+
307
+ expect(mocks.createAuthModule).toHaveBeenCalledWith({workspaces: expect.any(Object)});
308
+ });
309
+
310
+ it('composes Auth with the shared Workspaces client and registers the supplied module', async () => {
311
+ const signupPolicy = {isSignupAllowed: vi.fn().mockResolvedValue({allowed: true})};
312
+ const customAuthModule = mocks.createAuthModule();
313
+ mocks.createAuthModule.mockClear();
314
+ let extensionWorkspaces: WorkspacesInterModuleClient | undefined;
315
+ const extension = vi.fn(({workspaces}: {workspaces: WorkspacesInterModuleClient}) => {
316
+ extensionWorkspaces = workspaces;
317
+ return [];
318
+ });
319
+ const authModule = vi.fn(({workspaces}: {workspaces: WorkspacesInterModuleClient}) =>
320
+ mocks.createAuthModule({workspaces, signupPolicy}),
321
+ );
322
+
323
+ const modules = await defaultModules({authModule, extension});
324
+ const authWorkspaces = authModule.mock.calls[0]?.[0].workspaces;
325
+
326
+ expect(authModule).toHaveBeenCalledWith({workspaces: expect.any(Object)});
327
+ expect(mocks.createAuthModule).toHaveBeenCalledWith({
328
+ workspaces: authWorkspaces,
329
+ signupPolicy,
330
+ });
331
+ expect(extension).toHaveBeenCalledWith({workspaces: authWorkspaces});
332
+ expect(extensionWorkspaces).toBe(authWorkspaces);
333
+ expect(modules).toContain(customAuthModule);
334
+ expect(
335
+ modules.flatMap((module) =>
336
+ (module.interModulePresentations ?? []).map(({contract}) => contract.module),
337
+ ),
338
+ ).toContain(authInterModuleContract.module);
339
+ });
340
+
294
341
  it('lets a host replace only the Runners module with the composed Auth client', async () => {
295
342
  const policy = {filterEligibleWorkspaceIds: vi.fn().mockResolvedValue(new Set<string>())};
296
343
  const runnersModule = mocks.createRunnersModule({});
package/src/modules.ts CHANGED
@@ -24,7 +24,7 @@ import {secretsInterModuleContract} from '@shipfox/api-secrets-dto/inter-module'
24
24
  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
- import {workspacesModule} from '@shipfox/api-workspaces';
27
+ import {createWorkspacesModule} from '@shipfox/api-workspaces';
28
28
  import {
29
29
  type WorkspacesInterModuleClient,
30
30
  workspacesInterModuleContract,
@@ -40,10 +40,14 @@ import {logger} from '@shipfox/node-opentelemetry';
40
40
 
41
41
  export interface DefaultModulesOptions {
42
42
  webhookDeliverySource?: WebhookDeliverySource | undefined;
43
+ authModule?: DefaultAuthModuleFactory | undefined;
43
44
  runnersModule?: DefaultRunnersModuleFactory | undefined;
44
45
  extension?: DefaultModulesExtension | undefined;
45
46
  }
46
47
 
48
+ export type DefaultAuthModuleFactory = (options: {
49
+ workspaces: WorkspacesInterModuleClient;
50
+ }) => ShipfoxModule;
47
51
  export type DefaultRunnersModuleFactory = (options: {auth: AuthInterModuleClient}) => ShipfoxModule;
48
52
  export type DefaultModulesExtension = (options: {
49
53
  workspaces: WorkspacesInterModuleClient;
@@ -165,8 +169,8 @@ export async function defaultModules(
165
169
 
166
170
  const modules = [
167
171
  emailChallengesModule,
168
- createAuthModule({workspaces: workspacesClient}),
169
- workspacesModule,
172
+ (options.authModule ?? createAuthModule)({workspaces: workspacesClient}),
173
+ createWorkspacesModule({auth: authClient, projects: projectsClient, runners: runnersClient}),
170
174
  createSecretsModule(projectsClient),
171
175
  createAgentModule({secrets: secretsClient}),
172
176
  integrations.module,
@@ -180,6 +184,7 @@ export async function defaultModules(
180
184
  projects: projectsClient,
181
185
  runners: runnersClient,
182
186
  secrets: secretsClient,
187
+ workspaces: workspacesClient,
183
188
  integrations: integrationsClient,
184
189
  }),
185
190
  annotationsModule,