@shipfox/api-workflows 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/README.md +12 -5
- package/dist/core/checkout.d.ts +4 -2
- package/dist/core/checkout.d.ts.map +1 -1
- package/dist/core/checkout.js +7 -2
- package/dist/core/checkout.js.map +1 -1
- package/dist/core/entities/job.d.ts +1 -1
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/job.js.map +1 -1
- package/dist/core/entities/workflow-run.d.ts +23 -4
- package/dist/core/entities/workflow-run.d.ts.map +1 -1
- package/dist/core/entities/workflow-run.js.map +1 -1
- package/dist/core/run-workflow.js +1 -1
- package/dist/core/run-workflow.js.map +1 -1
- package/dist/db/db.d.ts +72 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/schema/workflow-runs.d.ts +45 -1
- package/dist/db/schema/workflow-runs.d.ts.map +1 -1
- package/dist/db/schema/workflow-runs.js +42 -1
- package/dist/db/schema/workflow-runs.js.map +1 -1
- package/dist/db/workflow-runs/jobs.d.ts +3 -1
- package/dist/db/workflow-runs/jobs.d.ts.map +1 -1
- package/dist/db/workflow-runs/jobs.js +12 -3
- package/dist/db/workflow-runs/jobs.js.map +1 -1
- package/dist/db/workflow-runs/queries.d.ts +3 -2
- package/dist/db/workflow-runs/queries.d.ts.map +1 -1
- package/dist/db/workflow-runs/queries.js +3 -0
- package/dist/db/workflow-runs/queries.js.map +1 -1
- package/dist/presentation/dto/workflow-run.d.ts.map +1 -1
- package/dist/presentation/dto/workflow-run.js +14 -0
- package/dist/presentation/dto/workflow-run.js.map +1 -1
- package/dist/presentation/routes/checkout-token.d.ts.map +1 -1
- package/dist/presentation/routes/checkout-token.js +2 -1
- package/dist/presentation/routes/checkout-token.js.map +1 -1
- package/dist/presentation/routes/get-run-aggregates.d.ts.map +1 -1
- package/dist/presentation/routes/get-run-aggregates.js +2 -1
- package/dist/presentation/routes/get-run-aggregates.js.map +1 -1
- package/dist/presentation/routes/leased-step.d.ts +3 -1
- package/dist/presentation/routes/leased-step.d.ts.map +1 -1
- package/dist/presentation/routes/leased-step.js +2 -1
- package/dist/presentation/routes/leased-step.js.map +1 -1
- package/dist/presentation/routes/list-runs.d.ts.map +1 -1
- package/dist/presentation/routes/list-runs.js +2 -1
- package/dist/presentation/routes/list-runs.js.map +1 -1
- package/dist/presentation/routes/project-access.d.ts +1 -0
- package/dist/presentation/routes/project-access.d.ts.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0004_dev_run_origin.sql +12 -0
- package/drizzle/meta/0004_snapshot.json +1773 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +10 -10
- package/src/core/checkout.test.ts +198 -1
- package/src/core/checkout.ts +18 -5
- package/src/core/entities/job.ts +1 -1
- package/src/core/entities/workflow-run.ts +22 -4
- package/src/core/job-transition/decide-job-activation.test.ts +2 -0
- package/src/core/run-workflow.test.ts +50 -1
- package/src/core/run-workflow.ts +1 -1
- package/src/core/workflow-run-creation.test.ts +2 -0
- package/src/db/job-listeners.test.ts +17 -0
- package/src/db/schema/workflow-runs.ts +58 -0
- package/src/db/workflow-runs/jobs.ts +17 -3
- package/src/db/workflow-runs/queries.ts +6 -1
- package/src/db/workflow-runs/run-create.test.ts +39 -0
- package/src/presentation/dto/workflow-run.ts +23 -1
- package/src/presentation/routes/checkout-token.test.ts +45 -0
- package/src/presentation/routes/checkout-token.ts +2 -1
- package/src/presentation/routes/get-run-aggregates.test.ts +56 -0
- package/src/presentation/routes/get-run-aggregates.ts +2 -0
- package/src/presentation/routes/get-step-secrets.test.ts +1 -0
- package/src/presentation/routes/leased-step.ts +7 -1
- package/src/presentation/routes/list-runs.test.ts +152 -0
- package/src/presentation/routes/list-runs.ts +2 -0
- package/test/factories/workflow-run.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-workflows",
|
|
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",
|
|
@@ -25,28 +25,28 @@
|
|
|
25
25
|
"zod": "^4.4.3",
|
|
26
26
|
"@shipfox/api-auth-context": "12.2.0",
|
|
27
27
|
"@shipfox/api-auth-dto": "12.0.0",
|
|
28
|
-
"@shipfox/api-agent-dto": "13.1.0",
|
|
29
28
|
"@shipfox/annotations-dto": "12.3.0",
|
|
30
|
-
"@shipfox/api-
|
|
31
|
-
"@shipfox/api-
|
|
32
|
-
"@shipfox/api-integration-core-dto": "
|
|
33
|
-
"@shipfox/api-runners-dto": "
|
|
29
|
+
"@shipfox/api-agent-dto": "14.0.0",
|
|
30
|
+
"@shipfox/api-definitions-dto": "14.0.0",
|
|
31
|
+
"@shipfox/api-integration-core-dto": "14.0.0",
|
|
32
|
+
"@shipfox/api-runners-dto": "14.0.0",
|
|
33
|
+
"@shipfox/api-projects-dto": "14.0.0",
|
|
34
|
+
"@shipfox/api-workflows-dto": "14.0.0",
|
|
34
35
|
"@shipfox/api-secrets-dto": "12.0.0",
|
|
35
|
-
"@shipfox/api-workflows-dto": "13.1.0",
|
|
36
36
|
"@shipfox/api-workspaces-dto": "12.0.0",
|
|
37
|
-
"@shipfox/config": "1.2.4",
|
|
38
37
|
"@shipfox/inter-module": "0.2.3",
|
|
39
|
-
"@shipfox/expression": "2.2.
|
|
38
|
+
"@shipfox/expression": "2.2.1",
|
|
40
39
|
"@shipfox/node-drizzle": "0.3.5",
|
|
41
40
|
"@shipfox/node-error-monitoring": "0.3.0",
|
|
42
41
|
"@shipfox/node-fastify": "0.4.2",
|
|
43
42
|
"@shipfox/node-module": "1.0.6",
|
|
44
43
|
"@shipfox/node-opentelemetry": "0.6.4",
|
|
45
44
|
"@shipfox/node-outbox": "0.2.6",
|
|
45
|
+
"@shipfox/config": "1.2.4",
|
|
46
46
|
"@shipfox/node-postgres": "0.5.0",
|
|
47
47
|
"@shipfox/node-temporal": "0.4.5",
|
|
48
48
|
"@shipfox/runner-labels": "0.2.0",
|
|
49
|
-
"@shipfox/workflow-document": "3.0
|
|
49
|
+
"@shipfox/workflow-document": "3.1.0"
|
|
50
50
|
},
|
|
51
51
|
"imports": {
|
|
52
52
|
"#*": "./dist/*"
|
|
@@ -3,9 +3,24 @@ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module'
|
|
|
3
3
|
import {projectFactory} from '#test/factories/project.js';
|
|
4
4
|
import {createStepCheckoutSpec} from './checkout.js';
|
|
5
5
|
import type {Step} from './entities/step.js';
|
|
6
|
-
import type {WorkflowRunTriggerReference} from './entities/workflow-run.js';
|
|
6
|
+
import type {WorkflowRunDevSource, WorkflowRunTriggerReference} from './entities/workflow-run.js';
|
|
7
7
|
import {CheckoutConfigInvalidError, CheckoutIntentUnresolvedError} from './errors.js';
|
|
8
8
|
|
|
9
|
+
const syncedRun = {origin: 'synced', devSource: null} as const;
|
|
10
|
+
|
|
11
|
+
function devRun(commit: string): {origin: 'dev'; devSource: WorkflowRunDevSource} {
|
|
12
|
+
return {
|
|
13
|
+
origin: 'dev',
|
|
14
|
+
devSource: {
|
|
15
|
+
ref: 'fix-triage-prompt',
|
|
16
|
+
commit,
|
|
17
|
+
configPath: '.shipfox/workflows/triage-sentry.yml',
|
|
18
|
+
initiatedByUserId: crypto.randomUUID(),
|
|
19
|
+
replayOfEventId: null,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
9
24
|
const getProjectById = vi.fn();
|
|
10
25
|
const resolveCheckoutTarget = vi.fn();
|
|
11
26
|
const projects = {
|
|
@@ -46,6 +61,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
46
61
|
});
|
|
47
62
|
|
|
48
63
|
const result = await createStepCheckoutSpec({
|
|
64
|
+
run: syncedRun,
|
|
49
65
|
step,
|
|
50
66
|
workspaceId: project.workspaceId,
|
|
51
67
|
projectId: project.id,
|
|
@@ -96,6 +112,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
96
112
|
});
|
|
97
113
|
|
|
98
114
|
const result = await createStepCheckoutSpec({
|
|
115
|
+
run: syncedRun,
|
|
99
116
|
step,
|
|
100
117
|
workspaceId: project.workspaceId,
|
|
101
118
|
projectId: project.id,
|
|
@@ -134,6 +151,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
134
151
|
});
|
|
135
152
|
|
|
136
153
|
await createStepCheckoutSpec({
|
|
154
|
+
run: syncedRun,
|
|
137
155
|
step,
|
|
138
156
|
workspaceId: project.workspaceId,
|
|
139
157
|
projectId: project.id,
|
|
@@ -167,6 +185,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
167
185
|
});
|
|
168
186
|
|
|
169
187
|
await createStepCheckoutSpec({
|
|
188
|
+
run: syncedRun,
|
|
170
189
|
step,
|
|
171
190
|
workspaceId: project.workspaceId,
|
|
172
191
|
projectId: project.id,
|
|
@@ -200,6 +219,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
200
219
|
});
|
|
201
220
|
|
|
202
221
|
await createStepCheckoutSpec({
|
|
222
|
+
run: syncedRun,
|
|
203
223
|
step,
|
|
204
224
|
workspaceId: project.workspaceId,
|
|
205
225
|
projectId: project.id,
|
|
@@ -216,6 +236,41 @@ describe('createStepCheckoutSpec', () => {
|
|
|
216
236
|
});
|
|
217
237
|
});
|
|
218
238
|
|
|
239
|
+
it('falls through to the dev commit when a same-project trigger has no commit', async () => {
|
|
240
|
+
const project = projectFactory.build();
|
|
241
|
+
const devCommit = 'b'.repeat(40);
|
|
242
|
+
const triggerReference = {...triggerReferenceFor(project.id), commit: null};
|
|
243
|
+
const step = checkoutStep({permissions: {contents: 'read'}});
|
|
244
|
+
getProjectById.mockResolvedValue({project});
|
|
245
|
+
resolveCheckoutTarget.mockResolvedValue({
|
|
246
|
+
projectId: project.id,
|
|
247
|
+
connectionId: project.sourceConnectionId,
|
|
248
|
+
externalRepositoryId: project.sourceExternalRepositoryId,
|
|
249
|
+
});
|
|
250
|
+
createCheckoutSpec.mockResolvedValue({
|
|
251
|
+
repositoryUrl: 'https://github.com/acme/repo.git',
|
|
252
|
+
ref: devCommit,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
await createStepCheckoutSpec({
|
|
256
|
+
run: devRun(devCommit),
|
|
257
|
+
step,
|
|
258
|
+
workspaceId: project.workspaceId,
|
|
259
|
+
projectId: project.id,
|
|
260
|
+
triggerReference,
|
|
261
|
+
integrations: integrations as IntegrationsModuleClient,
|
|
262
|
+
projects: projects as ProjectsModuleClient,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(createCheckoutSpec).toHaveBeenCalledWith({
|
|
266
|
+
workspaceId: project.workspaceId,
|
|
267
|
+
connectionId: project.sourceConnectionId,
|
|
268
|
+
externalRepositoryId: project.sourceExternalRepositoryId,
|
|
269
|
+
ref: devCommit,
|
|
270
|
+
permissions: {contents: 'read'},
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
219
274
|
it('keeps the provider default ref for a cross-repository target', async () => {
|
|
220
275
|
const project = projectFactory.build();
|
|
221
276
|
const targetProjectId = crypto.randomUUID();
|
|
@@ -232,6 +287,142 @@ describe('createStepCheckoutSpec', () => {
|
|
|
232
287
|
});
|
|
233
288
|
|
|
234
289
|
await createStepCheckoutSpec({
|
|
290
|
+
run: syncedRun,
|
|
291
|
+
step,
|
|
292
|
+
workspaceId: project.workspaceId,
|
|
293
|
+
projectId: project.id,
|
|
294
|
+
triggerReference: triggerReferenceFor(project.id),
|
|
295
|
+
integrations: integrations as IntegrationsModuleClient,
|
|
296
|
+
projects: projects as ProjectsModuleClient,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
expect(createCheckoutSpec).toHaveBeenCalledWith({
|
|
300
|
+
workspaceId: project.workspaceId,
|
|
301
|
+
connectionId: expect.any(String),
|
|
302
|
+
externalRepositoryId: 'github:412',
|
|
303
|
+
permissions: {contents: 'read'},
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('checks out the dev commit for a dev run without a trigger reference', async () => {
|
|
308
|
+
const project = projectFactory.build();
|
|
309
|
+
const devCommit = 'b'.repeat(40);
|
|
310
|
+
const step = checkoutStep({permissions: {contents: 'read'}});
|
|
311
|
+
getProjectById.mockResolvedValue({project});
|
|
312
|
+
resolveCheckoutTarget.mockResolvedValue({
|
|
313
|
+
projectId: project.id,
|
|
314
|
+
connectionId: project.sourceConnectionId,
|
|
315
|
+
externalRepositoryId: project.sourceExternalRepositoryId,
|
|
316
|
+
});
|
|
317
|
+
createCheckoutSpec.mockResolvedValue({
|
|
318
|
+
repositoryUrl: 'https://github.com/acme/repo.git',
|
|
319
|
+
ref: devCommit,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
await createStepCheckoutSpec({
|
|
323
|
+
run: devRun(devCommit),
|
|
324
|
+
step,
|
|
325
|
+
workspaceId: project.workspaceId,
|
|
326
|
+
projectId: project.id,
|
|
327
|
+
integrations: integrations as IntegrationsModuleClient,
|
|
328
|
+
projects: projects as ProjectsModuleClient,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
expect(createCheckoutSpec).toHaveBeenCalledWith({
|
|
332
|
+
workspaceId: project.workspaceId,
|
|
333
|
+
connectionId: project.sourceConnectionId,
|
|
334
|
+
externalRepositoryId: project.sourceExternalRepositoryId,
|
|
335
|
+
ref: devCommit,
|
|
336
|
+
permissions: {contents: 'read'},
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('prefers the event commit over the dev commit on a dev replay of a same-project push', async () => {
|
|
341
|
+
const project = projectFactory.build();
|
|
342
|
+
const eventCommit = 'c'.repeat(40);
|
|
343
|
+
const triggerReference = {...triggerReferenceFor(project.id), commit: eventCommit};
|
|
344
|
+
const step = checkoutStep({permissions: {contents: 'read'}});
|
|
345
|
+
getProjectById.mockResolvedValue({project});
|
|
346
|
+
resolveCheckoutTarget.mockResolvedValue({
|
|
347
|
+
projectId: project.id,
|
|
348
|
+
connectionId: project.sourceConnectionId,
|
|
349
|
+
externalRepositoryId: project.sourceExternalRepositoryId,
|
|
350
|
+
});
|
|
351
|
+
createCheckoutSpec.mockResolvedValue({
|
|
352
|
+
repositoryUrl: 'https://github.com/acme/repo.git',
|
|
353
|
+
ref: eventCommit,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
await createStepCheckoutSpec({
|
|
357
|
+
run: devRun('b'.repeat(40)),
|
|
358
|
+
step,
|
|
359
|
+
workspaceId: project.workspaceId,
|
|
360
|
+
projectId: project.id,
|
|
361
|
+
triggerReference,
|
|
362
|
+
integrations: integrations as IntegrationsModuleClient,
|
|
363
|
+
projects: projects as ProjectsModuleClient,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
expect(createCheckoutSpec).toHaveBeenCalledWith({
|
|
367
|
+
workspaceId: project.workspaceId,
|
|
368
|
+
connectionId: project.sourceConnectionId,
|
|
369
|
+
externalRepositoryId: project.sourceExternalRepositoryId,
|
|
370
|
+
ref: eventCommit,
|
|
371
|
+
permissions: {contents: 'read'},
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('preserves an explicit ref on a dev run', async () => {
|
|
376
|
+
const project = projectFactory.build();
|
|
377
|
+
const explicitRef = 'refs/heads/feature/checkout';
|
|
378
|
+
const step = checkoutStep({ref: explicitRef, permissions: {contents: 'read'}});
|
|
379
|
+
getProjectById.mockResolvedValue({project});
|
|
380
|
+
resolveCheckoutTarget.mockResolvedValue({
|
|
381
|
+
projectId: project.id,
|
|
382
|
+
connectionId: project.sourceConnectionId,
|
|
383
|
+
externalRepositoryId: project.sourceExternalRepositoryId,
|
|
384
|
+
});
|
|
385
|
+
createCheckoutSpec.mockResolvedValue({
|
|
386
|
+
repositoryUrl: 'https://github.com/acme/repo.git',
|
|
387
|
+
ref: explicitRef,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
await createStepCheckoutSpec({
|
|
391
|
+
run: devRun('b'.repeat(40)),
|
|
392
|
+
step,
|
|
393
|
+
workspaceId: project.workspaceId,
|
|
394
|
+
projectId: project.id,
|
|
395
|
+
triggerReference: triggerReferenceFor(project.id),
|
|
396
|
+
integrations: integrations as IntegrationsModuleClient,
|
|
397
|
+
projects: projects as ProjectsModuleClient,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
expect(createCheckoutSpec).toHaveBeenCalledWith({
|
|
401
|
+
workspaceId: project.workspaceId,
|
|
402
|
+
connectionId: project.sourceConnectionId,
|
|
403
|
+
externalRepositoryId: project.sourceExternalRepositoryId,
|
|
404
|
+
ref: explicitRef,
|
|
405
|
+
permissions: {contents: 'read'},
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('keeps the provider default ref for a cross-project target in a dev run', async () => {
|
|
410
|
+
const project = projectFactory.build();
|
|
411
|
+
const targetProjectId = crypto.randomUUID();
|
|
412
|
+
const step = checkoutStep({project: targetProjectId});
|
|
413
|
+
getProjectById.mockResolvedValue({project});
|
|
414
|
+
resolveCheckoutTarget.mockResolvedValue({
|
|
415
|
+
projectId: targetProjectId,
|
|
416
|
+
connectionId: crypto.randomUUID(),
|
|
417
|
+
externalRepositoryId: 'github:412',
|
|
418
|
+
});
|
|
419
|
+
createCheckoutSpec.mockResolvedValue({
|
|
420
|
+
repositoryUrl: 'https://github.com/acme/target.git',
|
|
421
|
+
ref: 'main',
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
await createStepCheckoutSpec({
|
|
425
|
+
run: devRun('b'.repeat(40)),
|
|
235
426
|
step,
|
|
236
427
|
workspaceId: project.workspaceId,
|
|
237
428
|
projectId: project.id,
|
|
@@ -263,6 +454,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
263
454
|
});
|
|
264
455
|
|
|
265
456
|
await createStepCheckoutSpec({
|
|
457
|
+
run: syncedRun,
|
|
266
458
|
step,
|
|
267
459
|
workspaceId: project.workspaceId,
|
|
268
460
|
projectId: project.id,
|
|
@@ -294,6 +486,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
294
486
|
});
|
|
295
487
|
|
|
296
488
|
await createStepCheckoutSpec({
|
|
489
|
+
run: syncedRun,
|
|
297
490
|
step,
|
|
298
491
|
workspaceId: project.workspaceId,
|
|
299
492
|
projectId: project.id,
|
|
@@ -330,6 +523,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
330
523
|
});
|
|
331
524
|
|
|
332
525
|
await createStepCheckoutSpec({
|
|
526
|
+
run: syncedRun,
|
|
333
527
|
step,
|
|
334
528
|
workspaceId: project.workspaceId,
|
|
335
529
|
projectId: project.id,
|
|
@@ -350,6 +544,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
350
544
|
{connection: 'github'},
|
|
351
545
|
])('rejects invalid checkout target shape: %j', async (checkout) => {
|
|
352
546
|
const act = createStepCheckoutSpec({
|
|
547
|
+
run: syncedRun,
|
|
353
548
|
step: checkoutStep(checkout),
|
|
354
549
|
workspaceId: crypto.randomUUID(),
|
|
355
550
|
projectId: crypto.randomUUID(),
|
|
@@ -368,6 +563,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
368
563
|
getProjectById.mockResolvedValue({project: null});
|
|
369
564
|
|
|
370
565
|
const act = createStepCheckoutSpec({
|
|
566
|
+
run: syncedRun,
|
|
371
567
|
step: checkoutStep({}),
|
|
372
568
|
workspaceId: crypto.randomUUID(),
|
|
373
569
|
projectId,
|
|
@@ -387,6 +583,7 @@ describe('createStepCheckoutSpec', () => {
|
|
|
387
583
|
resolveConnection.mockResolvedValue(null);
|
|
388
584
|
|
|
389
585
|
const act = createStepCheckoutSpec({
|
|
586
|
+
run: syncedRun,
|
|
390
587
|
step,
|
|
391
588
|
workspaceId: project.workspaceId,
|
|
392
589
|
projectId: project.id,
|
package/src/core/checkout.ts
CHANGED
|
@@ -3,7 +3,10 @@ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module'
|
|
|
3
3
|
import {checkoutTargetValidationIssues} from '@shipfox/workflow-document';
|
|
4
4
|
import {z} from 'zod';
|
|
5
5
|
import type {Step} from '#core/entities/step.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
WorkflowRunOriginState,
|
|
8
|
+
WorkflowRunTriggerReference,
|
|
9
|
+
} from '#core/entities/workflow-run.js';
|
|
7
10
|
import {CheckoutConfigInvalidError, CheckoutIntentUnresolvedError} from './errors.js';
|
|
8
11
|
|
|
9
12
|
const checkoutConfigSchema = z
|
|
@@ -39,6 +42,7 @@ export async function createStepCheckoutSpec({
|
|
|
39
42
|
workspaceId,
|
|
40
43
|
projectId,
|
|
41
44
|
triggerReference,
|
|
45
|
+
run,
|
|
42
46
|
integrations,
|
|
43
47
|
projects,
|
|
44
48
|
}: {
|
|
@@ -46,6 +50,8 @@ export async function createStepCheckoutSpec({
|
|
|
46
50
|
workspaceId: string;
|
|
47
51
|
projectId: string;
|
|
48
52
|
triggerReference?: WorkflowRunTriggerReference | null | undefined;
|
|
53
|
+
/** The run's origin state; a dev run checks out its dev commit by default. */
|
|
54
|
+
run: WorkflowRunOriginState;
|
|
49
55
|
integrations: IntegrationsModuleClient;
|
|
50
56
|
projects: ProjectsModuleClient;
|
|
51
57
|
}): Promise<{
|
|
@@ -79,11 +85,18 @@ export async function createStepCheckoutSpec({
|
|
|
79
85
|
},
|
|
80
86
|
target,
|
|
81
87
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
// Explicit step refs win, followed by same-project event commits (including
|
|
89
|
+
// replays), then the pinned dev commit for the run's own project. Other targets
|
|
90
|
+
// intentionally use the provider default branch.
|
|
91
|
+
const triggerCommitRef =
|
|
92
|
+
triggerReference?.project?.id === resolvedTarget.projectId
|
|
85
93
|
? (triggerReference.commit ?? undefined)
|
|
86
|
-
: undefined
|
|
94
|
+
: undefined;
|
|
95
|
+
const devCommitRef =
|
|
96
|
+
run.origin === 'dev' && resolvedTarget.projectId === projectId
|
|
97
|
+
? run.devSource.commit
|
|
98
|
+
: undefined;
|
|
99
|
+
const ref = checkout.ref ?? triggerCommitRef ?? devCommitRef;
|
|
87
100
|
const response = await integrations.createCheckoutSpec({
|
|
88
101
|
workspaceId,
|
|
89
102
|
connectionId: resolvedTarget.connectionId,
|
package/src/core/entities/job.ts
CHANGED
|
@@ -5,6 +5,24 @@ import type {WorkflowRunAttempt} from './workflow-run-attempt.js';
|
|
|
5
5
|
|
|
6
6
|
export type WorkflowRunStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
7
7
|
|
|
8
|
+
export type WorkflowRunOrigin = 'synced' | 'dev';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Why a dev run was created: the ref and pinned commit the definition came from, the
|
|
12
|
+
* file that ran, the user who started it, and the journaled event it replays when any.
|
|
13
|
+
*/
|
|
14
|
+
export interface WorkflowRunDevSource {
|
|
15
|
+
ref: string;
|
|
16
|
+
commit: string;
|
|
17
|
+
configPath: string;
|
|
18
|
+
initiatedByUserId: string;
|
|
19
|
+
replayOfEventId: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type WorkflowRunOriginState =
|
|
23
|
+
| {origin: 'synced'; devSource: null}
|
|
24
|
+
| {origin: 'dev'; devSource: WorkflowRunDevSource};
|
|
25
|
+
|
|
8
26
|
export type TerminalWorkflowRunStatus = Extract<
|
|
9
27
|
WorkflowRunStatus,
|
|
10
28
|
'succeeded' | 'failed' | 'cancelled'
|
|
@@ -60,7 +78,7 @@ export type TriggerPayload =
|
|
|
60
78
|
data: unknown;
|
|
61
79
|
};
|
|
62
80
|
|
|
63
|
-
export
|
|
81
|
+
export type WorkflowRun = WorkflowRunOriginState & {
|
|
64
82
|
id: string;
|
|
65
83
|
workspaceId: string;
|
|
66
84
|
projectId: string;
|
|
@@ -89,7 +107,7 @@ export interface WorkflowRun {
|
|
|
89
107
|
updatedAt: Date;
|
|
90
108
|
startedAt: Date | null;
|
|
91
109
|
finishedAt: Date | null;
|
|
92
|
-
}
|
|
110
|
+
};
|
|
93
111
|
|
|
94
112
|
export interface StepDetail extends Step {
|
|
95
113
|
attempts: StepAttempt[];
|
|
@@ -103,11 +121,11 @@ export interface WorkflowJobDetail extends Job {
|
|
|
103
121
|
jobExecutions: JobExecutionDetail[];
|
|
104
122
|
}
|
|
105
123
|
|
|
106
|
-
export
|
|
124
|
+
export type WorkflowRunDetail = WorkflowRun & {
|
|
107
125
|
runAttempt: WorkflowRunAttempt;
|
|
108
126
|
latestAttempt: number;
|
|
109
127
|
jobs: WorkflowJobDetail[];
|
|
110
128
|
/** Whether any job execution of this attempt reached its runner, decided here so the detail
|
|
111
129
|
* and the list cannot answer it differently. */
|
|
112
130
|
hasStartedJobExecution: boolean;
|
|
113
|
-
}
|
|
131
|
+
};
|
|
@@ -23,6 +23,12 @@ type DefinitionForWorkflowRun = NonNullable<
|
|
|
23
23
|
let definitionResponse: DefinitionForWorkflowRun | null = null;
|
|
24
24
|
const definitions: DefinitionsInterModuleClient = {
|
|
25
25
|
getDefinitionForWorkflowRun: async () => ({definition: definitionResponse}),
|
|
26
|
+
resolveDefinitionAtRef: () => {
|
|
27
|
+
throw new Error('resolveDefinitionAtRef is not used by runWorkflow');
|
|
28
|
+
},
|
|
29
|
+
listDefinitionsAtRef: () => {
|
|
30
|
+
throw new Error('listDefinitionsAtRef is not used by runWorkflow');
|
|
31
|
+
},
|
|
26
32
|
};
|
|
27
33
|
|
|
28
34
|
function buildDefinition(
|
|
@@ -31,6 +37,7 @@ function buildDefinition(
|
|
|
31
37
|
const {model = workflowModel(), ...definitionOverrides} = overrides;
|
|
32
38
|
return {
|
|
33
39
|
id: crypto.randomUUID(),
|
|
40
|
+
workflowId: crypto.randomUUID(),
|
|
34
41
|
projectId: crypto.randomUUID(),
|
|
35
42
|
name: 'Test Workflow',
|
|
36
43
|
model: createWorkflowModelSnapshot(model),
|
|
@@ -79,7 +86,7 @@ describe('runWorkflow', () => {
|
|
|
79
86
|
|
|
80
87
|
expect(run.id).toBeDefined();
|
|
81
88
|
expect(run.projectId).toBe(projectId);
|
|
82
|
-
expect(run.definitionId).toBe(definition.
|
|
89
|
+
expect(run.definitionId).toBe(definition.workflowId);
|
|
83
90
|
expect(run.name).toBe(definition.name);
|
|
84
91
|
expect(run.status).toBe('pending');
|
|
85
92
|
expect(run.triggerProvider).toBeNull();
|
|
@@ -151,6 +158,48 @@ describe('runWorkflow', () => {
|
|
|
151
158
|
expect(run.sourceSnapshot).toEqual(sourceSnapshot);
|
|
152
159
|
});
|
|
153
160
|
|
|
161
|
+
test('numbers runs by the workflow lineage id, not the definition row id', async () => {
|
|
162
|
+
const lineageId = crypto.randomUUID();
|
|
163
|
+
// A lineage whose id differs from the definition row id, as happens after
|
|
164
|
+
// the backfill when a lineage id diverges from the synced row it was born from.
|
|
165
|
+
const definition = buildDefinition({projectId, id: crypto.randomUUID(), workflowId: lineageId});
|
|
166
|
+
definitionResponse = definition;
|
|
167
|
+
|
|
168
|
+
const first = await runWorkflow(definitions, {
|
|
169
|
+
workspaceId,
|
|
170
|
+
projectId,
|
|
171
|
+
definitionId: definition.id,
|
|
172
|
+
triggerPayload: manualPayload(),
|
|
173
|
+
agent,
|
|
174
|
+
});
|
|
175
|
+
const second = await runWorkflow(definitions, {
|
|
176
|
+
workspaceId,
|
|
177
|
+
projectId,
|
|
178
|
+
definitionId: definition.id,
|
|
179
|
+
triggerPayload: manualPayload(),
|
|
180
|
+
agent,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(first.definitionId).toBe(lineageId);
|
|
184
|
+
expect(second.definitionId).toBe(lineageId);
|
|
185
|
+
expect(first.number).toBe(1);
|
|
186
|
+
expect(second.number).toBe(2);
|
|
187
|
+
|
|
188
|
+
// A later definition row that resolves to the same lineage continues the sequence.
|
|
189
|
+
const resynced = buildDefinition({projectId, id: crypto.randomUUID(), workflowId: lineageId});
|
|
190
|
+
definitionResponse = resynced;
|
|
191
|
+
const third = await runWorkflow(definitions, {
|
|
192
|
+
workspaceId,
|
|
193
|
+
projectId,
|
|
194
|
+
definitionId: resynced.id,
|
|
195
|
+
triggerPayload: manualPayload(),
|
|
196
|
+
agent,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(third.definitionId).toBe(lineageId);
|
|
200
|
+
expect(third.number).toBe(3);
|
|
201
|
+
});
|
|
202
|
+
|
|
154
203
|
test('throws DefinitionNotFoundError for unknown definition', async () => {
|
|
155
204
|
definitionResponse = null;
|
|
156
205
|
|
package/src/core/run-workflow.ts
CHANGED
|
@@ -44,7 +44,7 @@ export async function runWorkflow(
|
|
|
44
44
|
return createWorkflowRun({
|
|
45
45
|
workspaceId: params.workspaceId,
|
|
46
46
|
projectId: params.projectId,
|
|
47
|
-
definitionId: definition.
|
|
47
|
+
definitionId: definition.workflowId,
|
|
48
48
|
name: definition.name,
|
|
49
49
|
model,
|
|
50
50
|
triggerPayload: params.triggerPayload,
|
|
@@ -144,6 +144,8 @@ function workflowRun(params: {inputs?: Record<string, unknown> | null} = {}): Wo
|
|
|
144
144
|
workflowName: 'Test workflow',
|
|
145
145
|
nameOverride: null,
|
|
146
146
|
status: 'pending',
|
|
147
|
+
origin: 'synced',
|
|
148
|
+
devSource: null,
|
|
147
149
|
currentAttempt: 1,
|
|
148
150
|
triggerProvider: null,
|
|
149
151
|
triggerSource: triggerPayload.source,
|
|
@@ -278,6 +278,23 @@ describe('activateJobListener', () => {
|
|
|
278
278
|
expect(result.executionCount).toBe(2);
|
|
279
279
|
});
|
|
280
280
|
|
|
281
|
+
it('projects matchers with the event omitted', async () => {
|
|
282
|
+
const job = await createInactiveListeningJobWithMatchers({
|
|
283
|
+
on: [{source: 'github_acme'}],
|
|
284
|
+
until: [{source: 'github_acme', filter: 'event.action == "closed"'}],
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await activateJobListener({jobId: job.id, expectedVersion: job.version});
|
|
288
|
+
|
|
289
|
+
const payload = await activatedPayload(job.id);
|
|
290
|
+
expectListeningPayload(payload);
|
|
291
|
+
expect(payload.on[0]).toEqual({source: 'github_acme'});
|
|
292
|
+
expect(payload.until?.[0]).toEqual({
|
|
293
|
+
source: 'github_acme',
|
|
294
|
+
filter: 'event.action == "closed"',
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
281
298
|
it('omits filter snapshots for matchers without non-event roots', async () => {
|
|
282
299
|
const job = await createInactiveListeningJobWithMatchers({
|
|
283
300
|
on: [{source: 'github', event: 'pull_request'}],
|