@shipfox/api-definitions 12.3.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 +11 -11
- package/CHANGELOG.md +45 -0
- package/dist/core/entities/index.d.ts +1 -1
- package/dist/core/entities/index.d.ts.map +1 -1
- package/dist/core/entities/index.js +1 -1
- package/dist/core/entities/index.js.map +1 -1
- package/dist/core/entities/integration-context.d.ts +4 -0
- package/dist/core/entities/integration-context.d.ts.map +1 -1
- package/dist/core/entities/integration-context.js.map +1 -1
- package/dist/core/entities/sync-state.d.ts +10 -4
- package/dist/core/entities/sync-state.d.ts.map +1 -1
- package/dist/core/entities/sync-state.js +26 -7
- package/dist/core/entities/sync-state.js.map +1 -1
- package/dist/core/entities/validation-diagnostic.d.ts +8 -0
- package/dist/core/entities/validation-diagnostic.d.ts.map +1 -0
- package/dist/core/entities/validation-diagnostic.js +3 -0
- package/dist/core/entities/validation-diagnostic.js.map +1 -0
- package/dist/core/entities/workflow-definition.d.ts +2 -0
- package/dist/core/entities/workflow-definition.d.ts.map +1 -1
- package/dist/core/entities/workflow-definition.js.map +1 -1
- package/dist/core/errors.d.ts +10 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/integrations.d.ts +3 -1
- package/dist/core/integrations.d.ts.map +1 -1
- package/dist/core/integrations.js +10 -2
- package/dist/core/integrations.js.map +1 -1
- package/dist/core/parse-definition.d.ts +4 -4
- package/dist/core/parse-definition.d.ts.map +1 -1
- package/dist/core/parse-definition.js +5 -5
- package/dist/core/parse-definition.js.map +1 -1
- package/dist/core/resolve-definition-at-ref.d.ts +66 -0
- package/dist/core/resolve-definition-at-ref.d.ts.map +1 -0
- package/dist/core/resolve-definition-at-ref.js +374 -0
- package/dist/core/resolve-definition-at-ref.js.map +1 -0
- package/dist/core/sync-definitions.d.ts +2 -2
- package/dist/core/sync-definitions.d.ts.map +1 -1
- package/dist/core/sync-definitions.js +5 -5
- package/dist/core/sync-definitions.js.map +1 -1
- package/dist/core/validate-definition.d.ts +3 -3
- package/dist/core/validate-definition.d.ts.map +1 -1
- package/dist/core/validate-definition.js +17 -12
- package/dist/core/validate-definition.js.map +1 -1
- package/dist/core/workflow-model/cron-trigger.d.ts +1 -1
- package/dist/core/workflow-model/cron-trigger.d.ts.map +1 -1
- package/dist/core/workflow-model/cron-trigger.js +9 -5
- package/dist/core/workflow-model/cron-trigger.js.map +1 -1
- package/dist/core/workflow-model/index.d.ts +1 -1
- package/dist/core/workflow-model/index.d.ts.map +1 -1
- package/dist/core/workflow-model/index.js.map +1 -1
- package/dist/core/workflow-model/invalid-workflow-model-error.d.ts +4 -2
- package/dist/core/workflow-model/invalid-workflow-model-error.d.ts.map +1 -1
- package/dist/core/workflow-model/invalid-workflow-model-error.js.map +1 -1
- package/dist/core/workflow-model/normalize-job-listening.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-job-listening.js +51 -30
- package/dist/core/workflow-model/normalize-job-listening.js.map +1 -1
- package/dist/core/workflow-model/normalize-triggers.d.ts +1 -1
- package/dist/core/workflow-model/normalize-triggers.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-triggers.js +48 -18
- package/dist/core/workflow-model/normalize-triggers.js.map +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.d.ts +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.js +3 -3
- package/dist/core/workflow-model/normalize-workflow-document.js.map +1 -1
- package/dist/core/workflow-model/parse-interpolation-field.d.ts.map +1 -1
- package/dist/core/workflow-model/parse-interpolation-field.js +1 -15
- package/dist/core/workflow-model/parse-interpolation-field.js.map +1 -1
- package/dist/core/workflow-model/validate-predicate-expression.d.ts +3 -1
- package/dist/core/workflow-model/validate-predicate-expression.d.ts.map +1 -1
- package/dist/core/workflow-model/validate-predicate-expression.js +3 -0
- package/dist/core/workflow-model/validate-predicate-expression.js.map +1 -1
- package/dist/core/workflow-model/validation-issue.d.ts +2 -1
- package/dist/core/workflow-model/validation-issue.d.ts.map +1 -1
- package/dist/core/workflow-model/validation-issue.js +12 -15
- package/dist/core/workflow-model/validation-issue.js.map +1 -1
- package/dist/db/db.d.ts +196 -4
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +3 -1
- package/dist/db/db.js.map +1 -1
- package/dist/db/definition-triggers.js +3 -1
- package/dist/db/definition-triggers.js.map +1 -1
- package/dist/db/definitions.d.ts +17 -0
- package/dist/db/definitions.d.ts.map +1 -1
- package/dist/db/definitions.js +190 -23
- package/dist/db/definitions.js.map +1 -1
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +2 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema/definitions.d.ts +17 -0
- package/dist/db/schema/definitions.d.ts.map +1 -1
- package/dist/db/schema/definitions.js +8 -0
- package/dist/db/schema/definitions.js.map +1 -1
- package/dist/db/schema/sync-states.d.ts +7 -3
- package/dist/db/schema/sync-states.d.ts.map +1 -1
- package/dist/db/schema/sync-states.js +5 -1
- package/dist/db/schema/sync-states.js.map +1 -1
- package/dist/db/schema/workflows.d.ts +78 -0
- package/dist/db/schema/workflows.d.ts.map +1 -0
- package/dist/db/schema/workflows.js +17 -0
- package/dist/db/schema/workflows.js.map +1 -0
- package/dist/db/sync-states.d.ts +2 -2
- package/dist/db/sync-states.d.ts.map +1 -1
- package/dist/db/sync-states.js +4 -4
- package/dist/db/sync-states.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +4 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +19 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/presentation/dto/definition.d.ts.map +1 -1
- package/dist/presentation/dto/definition.js +11 -4
- package/dist/presentation/dto/definition.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +9 -1
- package/dist/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +66 -2
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/presentation/routes/create-definition.d.ts.map +1 -1
- package/dist/presentation/routes/create-definition.js +16 -3
- package/dist/presentation/routes/create-definition.js.map +1 -1
- package/dist/presentation/routes/get-definition.d.ts.map +1 -1
- package/dist/presentation/routes/get-definition.js +18 -4
- package/dist/presentation/routes/get-definition.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/presentation/routes/validate-definition.js +3 -3
- package/dist/presentation/routes/validate-definition.js.map +1 -1
- package/dist/temporal/activities/sync-activities.d.ts +3 -3
- package/dist/temporal/activities/sync-activities.d.ts.map +1 -1
- package/dist/temporal/activities/sync-activities.js +7 -4
- package/dist/temporal/activities/sync-activities.js.map +1 -1
- package/dist/temporal/workflows/definition-sync.js +1 -1
- package/dist/temporal/workflows/definition-sync.js.map +1 -1
- package/dist/temporal/workflows/index.bundle.js +50 -22
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0003_nebulous_nemesis.sql +11 -0
- package/drizzle/0004_workflow_ref_lookup.sql +1 -0
- package/drizzle/meta/0003_snapshot.json +627 -0
- package/drizzle/meta/0004_snapshot.json +649 -0
- package/drizzle/meta/_journal.json +14 -0
- package/drizzle.config.ts +1 -0
- package/package.json +7 -7
- package/src/core/entities/index.ts +1 -1
- package/src/core/entities/integration-context.ts +4 -0
- package/src/core/entities/sync-state.test.ts +49 -0
- package/src/core/entities/sync-state.ts +35 -12
- package/src/core/entities/validation-diagnostic.ts +8 -0
- package/src/core/entities/workflow-definition.ts +2 -0
- package/src/core/errors.ts +26 -0
- package/src/core/index.ts +12 -0
- package/src/core/integrations.test.ts +38 -0
- package/src/core/integrations.ts +10 -1
- package/src/core/parse-definition-diagnostics.test.ts +47 -0
- package/src/core/parse-definition.test.ts +29 -5
- package/src/core/parse-definition.ts +7 -7
- package/src/core/resolve-definition-at-ref.test.ts +792 -0
- package/src/core/resolve-definition-at-ref.ts +551 -0
- package/src/core/sync-definitions.test.ts +61 -2
- package/src/core/sync-definitions.ts +7 -7
- package/src/core/validate-definition.test.ts +31 -22
- package/src/core/validate-definition.ts +21 -13
- package/src/core/workflow-model/cron-trigger.ts +6 -2
- package/src/core/workflow-model/index.ts +1 -0
- package/src/core/workflow-model/invalid-workflow-model-error.ts +5 -1
- package/src/core/workflow-model/normalize-job-listening.ts +55 -23
- package/src/core/workflow-model/normalize-triggers.ts +44 -15
- package/src/core/workflow-model/normalize-workflow-document.test.ts +494 -36
- package/src/core/workflow-model/normalize-workflow-document.ts +8 -4
- package/src/core/workflow-model/parse-interpolation-field.ts +1 -23
- package/src/core/workflow-model/validate-predicate-expression.ts +6 -0
- package/src/core/workflow-model/validation-issue.ts +8 -13
- package/src/db/db.ts +7 -1
- package/src/db/definition-triggers.test.ts +44 -0
- package/src/db/definition-triggers.ts +1 -1
- package/src/db/definitions.test.ts +300 -1
- package/src/db/definitions.ts +282 -28
- package/src/db/index.ts +2 -0
- package/src/db/schema/definitions.test.ts +1 -0
- package/src/db/schema/definitions.ts +11 -0
- package/src/db/schema/sync-states.ts +12 -3
- package/src/db/schema/workflows.ts +23 -0
- package/src/db/sync-states.test.ts +87 -18
- package/src/db/sync-states.ts +6 -6
- package/src/db/workflow-lineage-migration.test.ts +158 -0
- package/src/index.ts +3 -1
- package/src/metrics/index.ts +1 -0
- package/src/metrics/instance.test.ts +50 -0
- package/src/metrics/instance.ts +23 -0
- package/src/presentation/dto/definition.test.ts +89 -1
- package/src/presentation/dto/definition.ts +8 -5
- package/src/presentation/inter-module.test.ts +177 -0
- package/src/presentation/inter-module.ts +77 -4
- package/src/presentation/routes/create-definition.test.ts +38 -0
- package/src/presentation/routes/create-definition.ts +20 -3
- package/src/presentation/routes/get-definition.test.ts +125 -11
- package/src/presentation/routes/get-definition.ts +21 -4
- package/src/presentation/routes/list-definitions.test.ts +1 -1
- package/src/presentation/routes/validate-definition.test.ts +1 -1
- package/src/presentation/routes/validate-definition.ts +3 -3
- package/src/presentation/subscribers/on-integration-connection-available.test.ts +3 -0
- package/src/temporal/activities/sync-activities.test.ts +46 -2
- package/src/temporal/activities/sync-activities.ts +9 -7
- package/src/temporal/workflows/definition-sync.ts +1 -1
- package/test/factories/definition.ts +1 -0
- package/test/globalSetup.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/entities/validation-warning.d.ts +0 -6
- package/dist/core/entities/validation-warning.d.ts.map +0 -1
- package/dist/core/entities/validation-warning.js +0 -3
- package/dist/core/entities/validation-warning.js.map +0 -1
- package/src/core/entities/validation-warning.ts +0 -5
- package/src/core/parse-definition-warnings.test.ts +0 -33
|
@@ -1,11 +1,26 @@
|
|
|
1
|
+
import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
|
|
1
2
|
import {createWorkflowModelSnapshot} from '@shipfox/api-definitions-dto';
|
|
2
3
|
import {definitionsInterModuleContract} from '@shipfox/api-definitions-dto/inter-module';
|
|
3
|
-
import
|
|
4
|
+
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
|
|
5
|
+
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
|
|
6
|
+
import {
|
|
7
|
+
createInterModuleKnownError,
|
|
8
|
+
defineInterModulePresentation,
|
|
9
|
+
type InterModuleMethodContract,
|
|
10
|
+
type InterModulePresentation,
|
|
11
|
+
} from '@shipfox/inter-module';
|
|
12
|
+
import {DefinitionAtRefError, listDefinitionsAtRef, resolveDefinitionAtRef} from '#core/index.js';
|
|
4
13
|
import {getDefinitionById} from '#db/definitions.js';
|
|
5
14
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
export interface CreateDefinitionsInterModulePresentationParams {
|
|
16
|
+
projects: ProjectsModuleClient;
|
|
17
|
+
agent: AgentInterModuleClient;
|
|
18
|
+
integrations: IntegrationsModuleClient;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createDefinitionsInterModulePresentation(
|
|
22
|
+
params: CreateDefinitionsInterModulePresentationParams,
|
|
23
|
+
): InterModulePresentation<typeof definitionsInterModuleContract> {
|
|
9
24
|
return defineInterModulePresentation(definitionsInterModuleContract, {
|
|
10
25
|
getDefinitionForWorkflowRun: async ({definitionId}) => {
|
|
11
26
|
const definition = await getDefinitionById(definitionId);
|
|
@@ -14,6 +29,7 @@ export function createDefinitionsInterModulePresentation(): InterModulePresentat
|
|
|
14
29
|
return {
|
|
15
30
|
definition: {
|
|
16
31
|
id: definition.id,
|
|
32
|
+
workflowId: definition.workflowId,
|
|
17
33
|
projectId: definition.projectId,
|
|
18
34
|
name: definition.name,
|
|
19
35
|
model: createWorkflowModelSnapshot(definition.model),
|
|
@@ -21,5 +37,62 @@ export function createDefinitionsInterModulePresentation(): InterModulePresentat
|
|
|
21
37
|
},
|
|
22
38
|
};
|
|
23
39
|
},
|
|
40
|
+
resolveDefinitionAtRef: async (input, context) => {
|
|
41
|
+
try {
|
|
42
|
+
return await resolveDefinitionAtRef({...input, ...params, signal: context.signal});
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw toDefinitionAtRefKnownError(
|
|
45
|
+
definitionsInterModuleContract.methods.resolveDefinitionAtRef,
|
|
46
|
+
error,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
listDefinitionsAtRef: async (input, context) => {
|
|
51
|
+
try {
|
|
52
|
+
return await listDefinitionsAtRef({...input, ...params, signal: context.signal});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw toDefinitionAtRefKnownError(
|
|
55
|
+
definitionsInterModuleContract.methods.listDefinitionsAtRef,
|
|
56
|
+
error,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
24
60
|
});
|
|
25
61
|
}
|
|
62
|
+
|
|
63
|
+
function toDefinitionAtRefKnownError(method: InterModuleMethodContract, error: unknown): unknown {
|
|
64
|
+
if (!(error instanceof DefinitionAtRefError)) return error;
|
|
65
|
+
const {code, details} = error;
|
|
66
|
+
switch (code) {
|
|
67
|
+
case 'project-not-found':
|
|
68
|
+
return createInterModuleKnownError(method, 'project-not-found', {
|
|
69
|
+
projectId: String(details.projectId),
|
|
70
|
+
});
|
|
71
|
+
case 'ref-not-found':
|
|
72
|
+
return createInterModuleKnownError(method, 'ref-not-found', {ref: String(details.ref)});
|
|
73
|
+
case 'ref-invalid':
|
|
74
|
+
return createInterModuleKnownError(method, 'ref-invalid', {ref: String(details.ref)});
|
|
75
|
+
case 'ref-moved':
|
|
76
|
+
return createInterModuleKnownError(method, 'ref-moved', {
|
|
77
|
+
ref: String(details.ref),
|
|
78
|
+
expectedCommit: String(details.expectedCommit),
|
|
79
|
+
});
|
|
80
|
+
case 'file-not-found':
|
|
81
|
+
return createInterModuleKnownError(method, 'file-not-found', {
|
|
82
|
+
ref: String(details.ref),
|
|
83
|
+
configPath: String(details.configPath),
|
|
84
|
+
});
|
|
85
|
+
case 'content-too-large':
|
|
86
|
+
return createInterModuleKnownError(method, 'content-too-large', {
|
|
87
|
+
configPath: String(details.configPath),
|
|
88
|
+
});
|
|
89
|
+
case 'invalid-definition':
|
|
90
|
+
return createInterModuleKnownError(method, 'invalid-definition', {
|
|
91
|
+
errors: details.errors as Array<{message: string; path?: string}>,
|
|
92
|
+
});
|
|
93
|
+
case 'too-many-files':
|
|
94
|
+
return createInterModuleKnownError(method, 'too-many-files', {});
|
|
95
|
+
case 'source-unavailable':
|
|
96
|
+
return createInterModuleKnownError(method, 'source-unavailable', {});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -149,6 +149,8 @@ jobs:
|
|
|
149
149
|
capabilities: ['agent_tools' as const],
|
|
150
150
|
},
|
|
151
151
|
],
|
|
152
|
+
eventCatalogs: [],
|
|
153
|
+
fixedEventProviders: [],
|
|
152
154
|
defaultConnection: {
|
|
153
155
|
id: sourceConnectionId,
|
|
154
156
|
slug: 'github-main',
|
|
@@ -240,6 +242,42 @@ jobs:
|
|
|
240
242
|
expect(res.json().code).toBe('invalid-workflow-definition');
|
|
241
243
|
});
|
|
242
244
|
|
|
245
|
+
test('trigger-scoped validation errors remain client errors on create', async () => {
|
|
246
|
+
const res = await app.inject({
|
|
247
|
+
method: 'POST',
|
|
248
|
+
url: '/api/definitions',
|
|
249
|
+
payload: {
|
|
250
|
+
project_id: projectId,
|
|
251
|
+
config_path: 'test.yml',
|
|
252
|
+
yaml: `
|
|
253
|
+
name: Broken trigger
|
|
254
|
+
runner: ubuntu-latest
|
|
255
|
+
triggers:
|
|
256
|
+
nightly:
|
|
257
|
+
source: cron
|
|
258
|
+
event: tick
|
|
259
|
+
config:
|
|
260
|
+
schedule: "not a cron"
|
|
261
|
+
jobs:
|
|
262
|
+
build:
|
|
263
|
+
steps:
|
|
264
|
+
- run: echo hello
|
|
265
|
+
`,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(res.statusCode).toBe(400);
|
|
270
|
+
expect(res.json()).toMatchObject({
|
|
271
|
+
code: 'invalid-workflow-definition',
|
|
272
|
+
details: [
|
|
273
|
+
{
|
|
274
|
+
message: 'Cron trigger schedule must be a valid 5-field cron expression.',
|
|
275
|
+
path: 'triggers.nightly.config.schedule',
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
243
281
|
test('cyclic DAG returns 400 with dag error code', async () => {
|
|
244
282
|
const cyclicYaml = `
|
|
245
283
|
name: Cyclic
|
|
@@ -11,7 +11,11 @@ import {z} from 'zod';
|
|
|
11
11
|
import {DefinitionParseError} from '#core/errors.js';
|
|
12
12
|
import {hasAgentStepIntegrations} from '#core/has-agent-step-integrations.js';
|
|
13
13
|
import {loadIntegrationValidationContext} from '#core/integrations.js';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
type ParseDefinitionOptions,
|
|
16
|
+
parseDefinitionWithDiagnostics,
|
|
17
|
+
stripDefinitionDiagnostics,
|
|
18
|
+
} from '#core/parse-definition.js';
|
|
15
19
|
import {upsertDefinition} from '#db/definitions.js';
|
|
16
20
|
import {toDefinitionDto} from '#presentation/dto/index.js';
|
|
17
21
|
import {requireProjectAccess} from './project-access.js';
|
|
@@ -53,11 +57,11 @@ export function buildCreateDefinitionRoute(options: CreateDefinitionRouteOptions
|
|
|
53
57
|
const project = await requireProjectAccess(request, projectId, options.projects);
|
|
54
58
|
|
|
55
59
|
const agentValidationCatalog = await options.agent.getValidationCatalog({});
|
|
56
|
-
const structurallyParsed =
|
|
60
|
+
const structurallyParsed = parseDefinitionForCreate(yamlString, {agentValidationCatalog});
|
|
57
61
|
const {integrations} = options;
|
|
58
62
|
const parsed =
|
|
59
63
|
integrations !== undefined && hasAgentStepIntegrations(structurallyParsed.document)
|
|
60
|
-
?
|
|
64
|
+
? parseDefinitionForCreate(yamlString, {
|
|
61
65
|
agentValidationCatalog,
|
|
62
66
|
integrationValidationContext: await loadIntegrationValidationContext(
|
|
63
67
|
integrations,
|
|
@@ -84,3 +88,16 @@ export function buildCreateDefinitionRoute(options: CreateDefinitionRouteOptions
|
|
|
84
88
|
},
|
|
85
89
|
});
|
|
86
90
|
}
|
|
91
|
+
|
|
92
|
+
function parseDefinitionForCreate(yamlString: string, options: ParseDefinitionOptions) {
|
|
93
|
+
const parsed = parseDefinitionWithDiagnostics(yamlString, options);
|
|
94
|
+
const errors = parsed.diagnostics
|
|
95
|
+
.filter((diagnostic) => diagnostic.severity === 'error')
|
|
96
|
+
.map(({message, path}) => ({message, ...(path === undefined ? {} : {path})}));
|
|
97
|
+
|
|
98
|
+
if (errors.length > 0) {
|
|
99
|
+
throw new DefinitionParseError(errors[0]?.message ?? 'Invalid definition', errors);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return stripDefinitionDiagnostics(parsed);
|
|
103
|
+
}
|
|
@@ -3,23 +3,27 @@ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module'
|
|
|
3
3
|
import type {FastifyInstance} from 'fastify';
|
|
4
4
|
import Fastify from 'fastify';
|
|
5
5
|
import {serializerCompiler, validatorCompiler} from 'fastify-type-provider-zod';
|
|
6
|
+
import {softDeleteVcsDefinitionsNotIn} from '#db/definitions.js';
|
|
6
7
|
import {definitionFactory} from '#test/index.js';
|
|
7
8
|
import {buildGetDefinitionRoute} from './get-definition.js';
|
|
8
9
|
|
|
9
10
|
const projectAccessState = vi.hoisted(() => ({workspaceId: ''}));
|
|
10
11
|
|
|
12
|
+
const getProjectById = vi.fn<ProjectsModuleClient['getProjectById']>(({projectId}) =>
|
|
13
|
+
Promise.resolve({
|
|
14
|
+
project: {
|
|
15
|
+
id: projectId,
|
|
16
|
+
workspaceId: projectAccessState.workspaceId,
|
|
17
|
+
sourceConnectionId: crypto.randomUUID(),
|
|
18
|
+
sourceExternalRepositoryId: 'repo',
|
|
19
|
+
sourceDefaultBranch: 'main',
|
|
20
|
+
name: 'Project',
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
|
|
11
25
|
const projects = {
|
|
12
|
-
getProjectById
|
|
13
|
-
Promise.resolve({
|
|
14
|
-
project: {
|
|
15
|
-
id: projectId,
|
|
16
|
-
workspaceId: projectAccessState.workspaceId,
|
|
17
|
-
sourceConnectionId: crypto.randomUUID(),
|
|
18
|
-
sourceExternalRepositoryId: 'repo',
|
|
19
|
-
name: 'Project',
|
|
20
|
-
},
|
|
21
|
-
}),
|
|
22
|
-
),
|
|
26
|
+
getProjectById,
|
|
23
27
|
requireProjectForWorkspace: vi.fn(),
|
|
24
28
|
} as unknown as ProjectsModuleClient;
|
|
25
29
|
|
|
@@ -74,6 +78,116 @@ describe('GET /api/definitions/:id', () => {
|
|
|
74
78
|
expect(res.json().code).toBe('not-found');
|
|
75
79
|
});
|
|
76
80
|
|
|
81
|
+
test('returns 200 with the synced row when the id is a lineage id with a synced row', async () => {
|
|
82
|
+
const definition = await definitionFactory.create({
|
|
83
|
+
source: 'vcs',
|
|
84
|
+
ref: 'main',
|
|
85
|
+
configPath: '.shipfox/workflows/lineage.yml',
|
|
86
|
+
});
|
|
87
|
+
expect(definition.workflowId).not.toBe(definition.id);
|
|
88
|
+
|
|
89
|
+
const res = await app.inject({
|
|
90
|
+
method: 'GET',
|
|
91
|
+
url: `/api/definitions/${definition.workflowId}`,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(res.statusCode).toBe(200);
|
|
95
|
+
expect(res.json().id).toBe(definition.id);
|
|
96
|
+
expect(res.json().ref).toBe('main');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('returns 404 when the id is a lineage id without a synced row', async () => {
|
|
100
|
+
const definition = await definitionFactory.create({
|
|
101
|
+
source: 'vcs',
|
|
102
|
+
ref: 'main',
|
|
103
|
+
configPath: '.shipfox/workflows/removed.yml',
|
|
104
|
+
});
|
|
105
|
+
await softDeleteVcsDefinitionsNotIn({
|
|
106
|
+
projectId: definition.projectId,
|
|
107
|
+
workspaceId,
|
|
108
|
+
ref: 'main',
|
|
109
|
+
keepConfigPaths: [],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const res = await app.inject({
|
|
113
|
+
method: 'GET',
|
|
114
|
+
url: `/api/definitions/${definition.workflowId}`,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(res.statusCode).toBe(404);
|
|
118
|
+
expect(res.json().code).toBe('not-found');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('returns 404 when a lineage only has a non-default branch row', async () => {
|
|
122
|
+
const definition = await definitionFactory.create({
|
|
123
|
+
source: 'vcs',
|
|
124
|
+
ref: 'feature/lineage',
|
|
125
|
+
configPath: '.shipfox/workflows/non-default.yml',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const res = await app.inject({
|
|
129
|
+
method: 'GET',
|
|
130
|
+
url: `/api/definitions/${definition.workflowId}`,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(res.statusCode).toBe(404);
|
|
134
|
+
expect(res.json().code).toBe('not-found');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('returns the row from the project default branch when a lineage has multiple refs', async () => {
|
|
138
|
+
const projectId = crypto.randomUUID();
|
|
139
|
+
const main = await definitionFactory.create({
|
|
140
|
+
projectId,
|
|
141
|
+
source: 'vcs',
|
|
142
|
+
ref: 'main',
|
|
143
|
+
configPath: '.shipfox/workflows/multi-ref.yml',
|
|
144
|
+
name: 'Main Workflow',
|
|
145
|
+
});
|
|
146
|
+
await definitionFactory.create({
|
|
147
|
+
projectId,
|
|
148
|
+
source: 'vcs',
|
|
149
|
+
ref: 'feature/lineage',
|
|
150
|
+
configPath: '.shipfox/workflows/multi-ref.yml',
|
|
151
|
+
name: 'Feature Workflow',
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const res = await app.inject({
|
|
155
|
+
method: 'GET',
|
|
156
|
+
url: `/api/definitions/${main.workflowId}`,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(res.statusCode).toBe(200);
|
|
160
|
+
expect(res.json().id).toBe(main.id);
|
|
161
|
+
expect(res.json().name).toBe('Main Workflow');
|
|
162
|
+
expect(res.json().ref).toBe('main');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('returns 403 through the project-access gate for a lineage in another workspace', async () => {
|
|
166
|
+
const definition = await definitionFactory.create({
|
|
167
|
+
source: 'vcs',
|
|
168
|
+
ref: 'main',
|
|
169
|
+
configPath: '.shipfox/workflows/elsewhere.yml',
|
|
170
|
+
});
|
|
171
|
+
getProjectById.mockResolvedValueOnce({
|
|
172
|
+
project: {
|
|
173
|
+
id: definition.projectId,
|
|
174
|
+
workspaceId: crypto.randomUUID(),
|
|
175
|
+
sourceConnectionId: crypto.randomUUID(),
|
|
176
|
+
sourceExternalRepositoryId: 'repo',
|
|
177
|
+
sourceDefaultBranch: 'main',
|
|
178
|
+
name: 'Project',
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const res = await app.inject({
|
|
183
|
+
method: 'GET',
|
|
184
|
+
url: `/api/definitions/${definition.workflowId}`,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(res.statusCode).toBe(403);
|
|
188
|
+
expect(res.json().code).toBe('forbidden');
|
|
189
|
+
});
|
|
190
|
+
|
|
77
191
|
test('returns 400 for invalid UUID', async () => {
|
|
78
192
|
const res = await app.inject({
|
|
79
193
|
method: 'GET',
|
|
@@ -2,7 +2,11 @@ import {definitionResponseSchema} from '@shipfox/api-definitions-dto';
|
|
|
2
2
|
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
|
|
3
3
|
import {ClientError, defineRoute} from '@shipfox/node-fastify';
|
|
4
4
|
import {z} from 'zod';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getDefinitionById,
|
|
7
|
+
getDefinitionByWorkflowId,
|
|
8
|
+
getWorkflowLineageById,
|
|
9
|
+
} from '#db/definitions.js';
|
|
6
10
|
import {toDefinitionDto} from '#presentation/dto/index.js';
|
|
7
11
|
import {requireProjectAccess} from './project-access.js';
|
|
8
12
|
|
|
@@ -23,12 +27,25 @@ export function buildGetDefinitionRoute(projects: ProjectsModuleClient) {
|
|
|
23
27
|
const {id} = request.params;
|
|
24
28
|
const definition = await getDefinitionById(id);
|
|
25
29
|
|
|
26
|
-
if (
|
|
30
|
+
if (definition) {
|
|
31
|
+
await requireProjectAccess(request, definition.projectId, projects);
|
|
32
|
+
return toDefinitionDto(definition);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const lineage = await getWorkflowLineageById(id);
|
|
36
|
+
if (!lineage) {
|
|
37
|
+
throw new ClientError('Definition not found', 'not-found', {status: 404});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const project = await requireProjectAccess(request, lineage.projectId, projects);
|
|
41
|
+
const syncedDefinition = project.sourceDefaultBranch
|
|
42
|
+
? await getDefinitionByWorkflowId({workflowId: id, ref: project.sourceDefaultBranch})
|
|
43
|
+
: undefined;
|
|
44
|
+
if (!syncedDefinition) {
|
|
27
45
|
throw new ClientError('Definition not found', 'not-found', {status: 404});
|
|
28
46
|
}
|
|
29
|
-
await requireProjectAccess(request, definition.projectId, projects);
|
|
30
47
|
|
|
31
|
-
return toDefinitionDto(
|
|
48
|
+
return toDefinitionDto(syncedDefinition);
|
|
32
49
|
},
|
|
33
50
|
});
|
|
34
51
|
}
|
|
@@ -39,7 +39,7 @@ jobs:
|
|
|
39
39
|
expect(res.statusCode).toBe(200);
|
|
40
40
|
const body = res.json();
|
|
41
41
|
expect(body.valid).toBe(true);
|
|
42
|
-
expect(body.
|
|
42
|
+
expect(body.diagnostics).toEqual([]);
|
|
43
43
|
expect(body.workflow_document.name).toBe('Test');
|
|
44
44
|
expect(body.workflow_model.kind).toBe('workflow');
|
|
45
45
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
|
|
2
2
|
import {
|
|
3
3
|
definitionDtoSchema,
|
|
4
|
+
definitionValidationDiagnosticSchema,
|
|
4
5
|
definitionValidationErrorSchema,
|
|
5
|
-
definitionValidationWarningSchema,
|
|
6
6
|
} from '@shipfox/api-definitions-dto';
|
|
7
7
|
import {defineRoute} from '@shipfox/node-fastify';
|
|
8
8
|
import {z} from 'zod';
|
|
@@ -17,7 +17,7 @@ const validationResultSchema = z.union([
|
|
|
17
17
|
valid: z.literal(true),
|
|
18
18
|
workflow_document: definitionDtoSchema.shape.workflow_document,
|
|
19
19
|
workflow_model: definitionDtoSchema.shape.workflow_model,
|
|
20
|
-
|
|
20
|
+
diagnostics: z.array(definitionValidationDiagnosticSchema),
|
|
21
21
|
}),
|
|
22
22
|
z.object({
|
|
23
23
|
valid: z.literal(false),
|
|
@@ -47,7 +47,7 @@ export function buildValidateDefinitionRoute(agent: AgentInterModuleClient) {
|
|
|
47
47
|
valid: true as const,
|
|
48
48
|
workflow_document: result.definition.document,
|
|
49
49
|
workflow_model: result.definition.model,
|
|
50
|
-
|
|
50
|
+
diagnostics: result.diagnostics,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -40,6 +40,7 @@ describe('onIntegrationConnectionAvailable', () => {
|
|
|
40
40
|
workspaceId: first.workspaceId,
|
|
41
41
|
connectionId: crypto.randomUUID(),
|
|
42
42
|
slug: 'linear_shipfox',
|
|
43
|
+
capabilities: ['agent_tools'],
|
|
43
44
|
};
|
|
44
45
|
const event: DomainEvent<IntegrationConnectionAvailableEvent> = {
|
|
45
46
|
id: crypto.randomUUID(),
|
|
@@ -96,6 +97,7 @@ describe('onIntegrationConnectionAvailable', () => {
|
|
|
96
97
|
workspaceId,
|
|
97
98
|
connectionId: crypto.randomUUID(),
|
|
98
99
|
slug: 'linear_shipfox',
|
|
100
|
+
capabilities: ['agent_tools'],
|
|
99
101
|
};
|
|
100
102
|
|
|
101
103
|
await expect(
|
|
@@ -118,6 +120,7 @@ describe('onIntegrationConnectionAvailable', () => {
|
|
|
118
120
|
workspaceId: crypto.randomUUID(),
|
|
119
121
|
connectionId: crypto.randomUUID(),
|
|
120
122
|
slug: 'linear_shipfox',
|
|
123
|
+
capabilities: ['agent_tools'],
|
|
121
124
|
};
|
|
122
125
|
|
|
123
126
|
await expect(
|
|
@@ -26,6 +26,18 @@ jobs:
|
|
|
26
26
|
- run: pnpm test
|
|
27
27
|
`;
|
|
28
28
|
|
|
29
|
+
const warningEventInterpolation = '$'.concat('{{ event.x }}');
|
|
30
|
+
const warningYaml = [
|
|
31
|
+
'name: Warning only',
|
|
32
|
+
'runner: ubuntu-latest',
|
|
33
|
+
'jobs:',
|
|
34
|
+
' build:',
|
|
35
|
+
' steps:',
|
|
36
|
+
' - env:',
|
|
37
|
+
` MSG: '${warningEventInterpolation}'`,
|
|
38
|
+
' run: eval "$MSG"',
|
|
39
|
+
].join('\n');
|
|
40
|
+
|
|
29
41
|
function sourceControl(
|
|
30
42
|
overrides: Partial<DefinitionsSourceControl> = {},
|
|
31
43
|
): DefinitionsSourceControl {
|
|
@@ -224,7 +236,37 @@ describe('definition sync activities', () => {
|
|
|
224
236
|
|
|
225
237
|
expect(result.appliedCount).toBe(1);
|
|
226
238
|
expect(result.deletedCount).toBe(0);
|
|
227
|
-
expect(result.
|
|
239
|
+
expect(result.diagnostics).toEqual([]);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('adds the workflow file path to persisted diagnostics', async () => {
|
|
243
|
+
const source = sourceControl({
|
|
244
|
+
fetchFile: vi.fn(() =>
|
|
245
|
+
Promise.resolve({
|
|
246
|
+
path: '.shipfox/workflows/warning.yml',
|
|
247
|
+
ref: 'main',
|
|
248
|
+
content: warningYaml,
|
|
249
|
+
}),
|
|
250
|
+
),
|
|
251
|
+
});
|
|
252
|
+
const activities = createDefinitionSyncActivities(source, agent);
|
|
253
|
+
|
|
254
|
+
const result = await activities.fetchAndApplyDefinitionWorkflows({
|
|
255
|
+
projectId,
|
|
256
|
+
workspaceId: crypto.randomUUID(),
|
|
257
|
+
sourceConnectionId,
|
|
258
|
+
sourceExternalRepositoryId: 'gitea:gitea-owner/platform',
|
|
259
|
+
sourceRef: 'main',
|
|
260
|
+
paths: ['.shipfox/workflows/warning.yml'],
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
expect(result.diagnostics).toHaveLength(1);
|
|
264
|
+
expect(result.diagnostics[0]).toMatchObject({
|
|
265
|
+
code: 're-evaluating-command',
|
|
266
|
+
path: 'jobs.build.steps.0.run',
|
|
267
|
+
severity: 'warning',
|
|
268
|
+
filePath: '.shipfox/workflows/warning.yml',
|
|
269
|
+
});
|
|
228
270
|
});
|
|
229
271
|
|
|
230
272
|
it('translates DefinitionSyncPermanentError into a non-retryable ApplicationFailure', async () => {
|
|
@@ -364,11 +406,12 @@ describe('definition sync activities', () => {
|
|
|
364
406
|
sourceConnectionId,
|
|
365
407
|
sourceExternalRepositoryId: 'gitea:gitea-owner/platform',
|
|
366
408
|
sourceRef: 'main',
|
|
367
|
-
|
|
409
|
+
diagnostics: [
|
|
368
410
|
{
|
|
369
411
|
code: 're-evaluating-command',
|
|
370
412
|
message: 'Workflow data is re-executed as shell code.',
|
|
371
413
|
path: 'jobs.build.steps.0.run',
|
|
414
|
+
severity: 'warning',
|
|
372
415
|
},
|
|
373
416
|
],
|
|
374
417
|
});
|
|
@@ -386,6 +429,7 @@ describe('definition sync activities', () => {
|
|
|
386
429
|
code: 're-evaluating-command',
|
|
387
430
|
message: 'Workflow data is re-executed as shell code.',
|
|
388
431
|
path: 'jobs.build.steps.0.run',
|
|
432
|
+
severity: 'warning',
|
|
389
433
|
},
|
|
390
434
|
]);
|
|
391
435
|
});
|
|
@@ -3,7 +3,7 @@ import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/i
|
|
|
3
3
|
import {markErrorReported} from '@shipfox/node-error-monitoring';
|
|
4
4
|
import {Context} from '@temporalio/activity';
|
|
5
5
|
import {ApplicationFailure} from '@temporalio/common';
|
|
6
|
-
import type {
|
|
6
|
+
import type {DefinitionSyncDiagnostic, DefinitionSyncErrorCode} from '#core/entities/sync-state.js';
|
|
7
7
|
import {
|
|
8
8
|
classifySyncFailure,
|
|
9
9
|
discoverWorkflowFiles,
|
|
@@ -50,11 +50,11 @@ export interface DiscoverWorkflowsActivityResult {
|
|
|
50
50
|
export interface FetchAndApplyActivityResult {
|
|
51
51
|
appliedCount: number;
|
|
52
52
|
deletedCount: number;
|
|
53
|
-
|
|
53
|
+
diagnostics: DefinitionSyncDiagnostic[];
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export interface MarkSyncSucceededActivityInput extends SyncRefScopedInput {
|
|
57
|
-
|
|
57
|
+
diagnostics?: readonly DefinitionSyncDiagnostic[] | undefined;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export interface DefinitionSyncActivityOptions {
|
|
@@ -98,7 +98,7 @@ function createPrepareDefinitionSyncActivity(sourceControl: DefinitionsSourceCon
|
|
|
98
98
|
status: 'syncing',
|
|
99
99
|
lastErrorCode: null,
|
|
100
100
|
lastErrorMessage: null,
|
|
101
|
-
|
|
101
|
+
diagnostics: [],
|
|
102
102
|
startedAt: new Date(),
|
|
103
103
|
finishedAt: null,
|
|
104
104
|
});
|
|
@@ -169,7 +169,9 @@ function createFetchAndApplyActivity(
|
|
|
169
169
|
|
|
170
170
|
return {
|
|
171
171
|
...result,
|
|
172
|
-
|
|
172
|
+
diagnostics: definitions.flatMap((entry) =>
|
|
173
|
+
entry.diagnostics.map((diagnostic) => ({...diagnostic, filePath: entry.path})),
|
|
174
|
+
),
|
|
173
175
|
};
|
|
174
176
|
});
|
|
175
177
|
};
|
|
@@ -187,7 +189,7 @@ function createMarkSyncSucceededActivity() {
|
|
|
187
189
|
status: 'succeeded',
|
|
188
190
|
lastErrorCode: null,
|
|
189
191
|
lastErrorMessage: null,
|
|
190
|
-
|
|
192
|
+
diagnostics: input.diagnostics ?? [],
|
|
191
193
|
finishedAt: new Date(),
|
|
192
194
|
});
|
|
193
195
|
};
|
|
@@ -205,7 +207,7 @@ function createMarkSyncFailedActivity() {
|
|
|
205
207
|
status: 'failed',
|
|
206
208
|
lastErrorCode: input.code,
|
|
207
209
|
lastErrorMessage: input.message,
|
|
208
|
-
|
|
210
|
+
diagnostics: [],
|
|
209
211
|
finishedAt: new Date(),
|
|
210
212
|
});
|
|
211
213
|
};
|
|
@@ -65,7 +65,7 @@ export async function definitionSyncWorkflow(
|
|
|
65
65
|
const {paths} = await discoverDefinitionWorkflows(source);
|
|
66
66
|
const applied = await fetchAndApplyDefinitionWorkflows({...source, paths});
|
|
67
67
|
|
|
68
|
-
await markDefinitionSyncSucceeded({...source,
|
|
68
|
+
await markDefinitionSyncSucceeded({...source, diagnostics: applied.diagnostics});
|
|
69
69
|
|
|
70
70
|
return {
|
|
71
71
|
sourceRef,
|
package/test/globalSetup.ts
CHANGED
|
@@ -8,6 +8,7 @@ export async function setup() {
|
|
|
8
8
|
createPostgresClient();
|
|
9
9
|
|
|
10
10
|
await db().execute(sql`DROP TABLE IF EXISTS definitions_workflow_definitions CASCADE`);
|
|
11
|
+
await db().execute(sql`DROP TABLE IF EXISTS definitions_workflows CASCADE`);
|
|
11
12
|
await db().execute(sql`DROP TABLE IF EXISTS definitions_sync_states CASCADE`);
|
|
12
13
|
await db().execute(sql`DROP TABLE IF EXISTS definitions_outbox CASCADE`);
|
|
13
14
|
await db().execute(sql`DROP TYPE IF EXISTS definitions_sync_status CASCADE`);
|
|
@@ -17,6 +18,7 @@ export async function setup() {
|
|
|
17
18
|
await db().execute(sql`DROP TABLE IF EXISTS drizzle.__drizzle_migrations_definitions CASCADE`);
|
|
18
19
|
await runMigrations(db(), migrationsPath, '__drizzle_migrations_definitions');
|
|
19
20
|
await db().execute(sql`TRUNCATE TABLE definitions_workflow_definitions CASCADE`);
|
|
21
|
+
await db().execute(sql`TRUNCATE TABLE definitions_workflows CASCADE`);
|
|
20
22
|
await db().execute(sql`TRUNCATE TABLE definitions_sync_states CASCADE`);
|
|
21
23
|
await db().execute(sql`TRUNCATE TABLE definitions_outbox CASCADE`);
|
|
22
24
|
|