@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
|
@@ -23,7 +23,7 @@ export function normalizeWorkflowDocument(
|
|
|
23
23
|
integrationValidationContext?: IntegrationValidationContext | undefined;
|
|
24
24
|
stepSourceLocations?: WorkflowStepSourceLocationMap | undefined;
|
|
25
25
|
/** Provide a fresh array for each call to collect non-fatal validation issues. */
|
|
26
|
-
|
|
26
|
+
diagnostics?: WorkflowModelValidationIssue[] | undefined;
|
|
27
27
|
},
|
|
28
28
|
): WorkflowModel {
|
|
29
29
|
const issues: WorkflowModelValidationIssue[] = [];
|
|
@@ -65,10 +65,14 @@ export function normalizeWorkflowDocument(
|
|
|
65
65
|
|
|
66
66
|
validateCycles(document.jobs, jobIdBySourceName, issues);
|
|
67
67
|
|
|
68
|
-
const
|
|
69
|
-
|
|
68
|
+
const diagnostics = issues.filter(
|
|
69
|
+
(issue) => !(issue.severity === 'error' && issue.scope === 'definition'),
|
|
70
|
+
);
|
|
71
|
+
options.diagnostics?.push(...diagnostics);
|
|
70
72
|
|
|
71
|
-
const errors = issues.filter(
|
|
73
|
+
const errors = issues.filter(
|
|
74
|
+
(issue) => issue.severity === 'error' && issue.scope === 'definition',
|
|
75
|
+
);
|
|
72
76
|
if (errors.length > 0) throw new InvalidWorkflowModelError(errors);
|
|
73
77
|
|
|
74
78
|
return {
|
|
@@ -26,10 +26,10 @@ import {
|
|
|
26
26
|
import type {WorkflowFieldTemplate} from '../entities/workflow-model.js';
|
|
27
27
|
import type {
|
|
28
28
|
WorkflowModelValidationIssue,
|
|
29
|
-
WorkflowModelValidationIssueCode,
|
|
30
29
|
WorkflowModelValidationIssuePathSegment,
|
|
31
30
|
} from './invalid-workflow-model-error.js';
|
|
32
31
|
import {validateDirectJobReferences} from './validate-job-references.js';
|
|
32
|
+
import {issue} from './validation-issue.js';
|
|
33
33
|
|
|
34
34
|
export type StoredInterpolationField =
|
|
35
35
|
| 'run'
|
|
@@ -513,25 +513,3 @@ function formatList(values: readonly string[]): string {
|
|
|
513
513
|
function fieldLabel(field: WorkflowInterpolationField): string {
|
|
514
514
|
return `Workflow ${field}`;
|
|
515
515
|
}
|
|
516
|
-
|
|
517
|
-
function issue(params: {
|
|
518
|
-
code: WorkflowModelValidationIssueCode;
|
|
519
|
-
message: string;
|
|
520
|
-
path: readonly WorkflowModelValidationIssuePathSegment[];
|
|
521
|
-
details?: Readonly<Record<string, unknown>>;
|
|
522
|
-
}): WorkflowModelValidationIssue {
|
|
523
|
-
if (params.details === undefined) {
|
|
524
|
-
return {
|
|
525
|
-
code: params.code,
|
|
526
|
-
message: params.message,
|
|
527
|
-
path: params.path,
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
return {
|
|
532
|
-
code: params.code,
|
|
533
|
-
message: params.message,
|
|
534
|
-
path: params.path,
|
|
535
|
-
details: params.details,
|
|
536
|
-
};
|
|
537
|
-
}
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
WorkflowModelValidationIssue,
|
|
22
22
|
WorkflowModelValidationIssueCode,
|
|
23
23
|
WorkflowModelValidationIssuePathSegment,
|
|
24
|
+
WorkflowModelValidationIssueScope,
|
|
24
25
|
} from './invalid-workflow-model-error.js';
|
|
25
26
|
import {validateDirectJobReferences} from './validate-job-references.js';
|
|
26
27
|
import {issue} from './validation-issue.js';
|
|
@@ -35,6 +36,8 @@ export function validatePredicateExpression(params: {
|
|
|
35
36
|
issues: WorkflowModelValidationIssue[];
|
|
36
37
|
allowedJobReferences?: ReadonlySet<string>;
|
|
37
38
|
typeOverlay?: ExpressionTypeEnvironment | undefined;
|
|
39
|
+
/** Scope for the invalidCode issue; other issues keep the definition scope. */
|
|
40
|
+
scope?: WorkflowModelValidationIssueScope | undefined;
|
|
38
41
|
}): WorkflowExpression | undefined {
|
|
39
42
|
const site = getWorkflowPredicateFieldMinimumFillTarget(params.field);
|
|
40
43
|
const syntaxExpression = createSyntaxExpression(params);
|
|
@@ -150,6 +153,7 @@ function createSyntaxExpression(params: {
|
|
|
150
153
|
invalidCode: WorkflowModelValidationIssueCode;
|
|
151
154
|
invalidMessage: string;
|
|
152
155
|
issues: WorkflowModelValidationIssue[];
|
|
156
|
+
scope?: WorkflowModelValidationIssueScope | undefined;
|
|
153
157
|
}): WorkflowExpression | undefined {
|
|
154
158
|
try {
|
|
155
159
|
return createWorkflowExpression({
|
|
@@ -179,6 +183,7 @@ function invalidPredicateIssue(params: {
|
|
|
179
183
|
invalidMessage: string;
|
|
180
184
|
contextRoots: readonly string[];
|
|
181
185
|
reason?: string;
|
|
186
|
+
scope?: WorkflowModelValidationIssueScope | undefined;
|
|
182
187
|
}): WorkflowModelValidationIssue {
|
|
183
188
|
return issue({
|
|
184
189
|
code: params.invalidCode,
|
|
@@ -190,6 +195,7 @@ function invalidPredicateIssue(params: {
|
|
|
190
195
|
contextRoots: params.contextRoots,
|
|
191
196
|
reason: params.reason ?? 'Expression source did not parse or type-check.',
|
|
192
197
|
},
|
|
198
|
+
...(params.scope === undefined ? {} : {scope: params.scope}),
|
|
193
199
|
});
|
|
194
200
|
}
|
|
195
201
|
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
WorkflowModelValidationIssue,
|
|
3
3
|
WorkflowModelValidationIssueCode,
|
|
4
4
|
WorkflowModelValidationIssuePathSegment,
|
|
5
|
+
WorkflowModelValidationIssueScope,
|
|
5
6
|
WorkflowModelValidationIssueSeverity,
|
|
6
7
|
} from './invalid-workflow-model-error.js';
|
|
7
8
|
|
|
@@ -11,21 +12,15 @@ export function issue(params: {
|
|
|
11
12
|
path: readonly WorkflowModelValidationIssuePathSegment[];
|
|
12
13
|
details?: Readonly<Record<string, unknown>>;
|
|
13
14
|
severity?: WorkflowModelValidationIssueSeverity;
|
|
15
|
+
scope?: WorkflowModelValidationIssueScope;
|
|
14
16
|
}): WorkflowModelValidationIssue {
|
|
17
|
+
const severity = params.severity ?? 'error';
|
|
18
|
+
const scope = params.scope ?? 'definition';
|
|
19
|
+
const base = {code: params.code, message: params.message, path: params.path, severity, scope};
|
|
20
|
+
|
|
15
21
|
if (params.details === undefined) {
|
|
16
|
-
return
|
|
17
|
-
code: params.code,
|
|
18
|
-
message: params.message,
|
|
19
|
-
path: params.path,
|
|
20
|
-
...(params.severity === undefined ? {} : {severity: params.severity}),
|
|
21
|
-
};
|
|
22
|
+
return base;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
return {
|
|
25
|
-
code: params.code,
|
|
26
|
-
message: params.message,
|
|
27
|
-
path: params.path,
|
|
28
|
-
details: params.details,
|
|
29
|
-
...(params.severity === undefined ? {} : {severity: params.severity}),
|
|
30
|
-
};
|
|
25
|
+
return {...base, details: params.details};
|
|
31
26
|
}
|
package/src/db/db.ts
CHANGED
|
@@ -3,8 +3,14 @@ import {pgClient} from '@shipfox/node-postgres';
|
|
|
3
3
|
import {workflowDefinitions} from './schema/definitions.js';
|
|
4
4
|
import {definitionsOutbox} from './schema/outbox.js';
|
|
5
5
|
import {definitionSyncStates} from './schema/sync-states.js';
|
|
6
|
+
import {workflowWorkflows} from './schema/workflows.js';
|
|
6
7
|
|
|
7
|
-
export const schema = {
|
|
8
|
+
export const schema = {
|
|
9
|
+
workflowDefinitions,
|
|
10
|
+
definitionsOutbox,
|
|
11
|
+
definitionSyncStates,
|
|
12
|
+
workflowWorkflows,
|
|
13
|
+
};
|
|
8
14
|
|
|
9
15
|
let _db: NodePgDatabase<typeof schema> | undefined;
|
|
10
16
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {definitionResolvedEventSchema} from '@shipfox/api-definitions-dto';
|
|
1
2
|
import type {WorkflowModel, WorkflowModelTrigger} from '#core/entities/workflow-model.js';
|
|
2
3
|
import {definitionTriggersFor} from './definition-triggers.js';
|
|
3
4
|
|
|
@@ -85,6 +86,49 @@ describe('definitionTriggersFor', () => {
|
|
|
85
86
|
});
|
|
86
87
|
});
|
|
87
88
|
|
|
89
|
+
it('passes an omitted event through for integration triggers', () => {
|
|
90
|
+
const model = workflowModelWithTriggers([
|
|
91
|
+
{
|
|
92
|
+
id: 'on_any_github_event',
|
|
93
|
+
key: 'on_any_github_event',
|
|
94
|
+
source: 'github_acme',
|
|
95
|
+
},
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
const result = definitionTriggersFor(model);
|
|
99
|
+
|
|
100
|
+
expect(result).toEqual({
|
|
101
|
+
on_any_github_event: {
|
|
102
|
+
source: 'github_acme',
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
expect(result.on_any_github_event).not.toHaveProperty('event');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('round trips a trigger without an event through the definition event schema', () => {
|
|
109
|
+
const dto = definitionTriggersFor(
|
|
110
|
+
workflowModelWithTriggers([
|
|
111
|
+
{
|
|
112
|
+
id: 'on_any_github_event',
|
|
113
|
+
key: 'on_any_github_event',
|
|
114
|
+
source: 'github_acme',
|
|
115
|
+
},
|
|
116
|
+
]),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const result = definitionResolvedEventSchema.parse({
|
|
120
|
+
definitionId: '019e98ab-6656-7ca1-b9ad-1ca4442c479d',
|
|
121
|
+
projectId: '019e98ab-b90f-7265-b13c-8b441c991381',
|
|
122
|
+
workspaceId: '019e98ab-b90f-7265-b13c-8b441c991382',
|
|
123
|
+
configPath: '.shipfox/workflows/ci.yml',
|
|
124
|
+
triggers: dto,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(result.triggers).toEqual({
|
|
128
|
+
on_any_github_event: {source: 'github_acme'},
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
88
132
|
it('returns an empty object when the model has no triggers', () => {
|
|
89
133
|
const model = workflowModelWithTriggers([]);
|
|
90
134
|
|
|
@@ -6,7 +6,7 @@ export function definitionTriggersFor(model: WorkflowModel): Record<string, Trig
|
|
|
6
6
|
model.triggers.map((trigger) => {
|
|
7
7
|
const dto: TriggerDto = {
|
|
8
8
|
source: trigger.source,
|
|
9
|
-
event: trigger.event,
|
|
9
|
+
...(trigger.event === undefined ? {} : {event: trigger.event}),
|
|
10
10
|
};
|
|
11
11
|
if (trigger.inputs !== undefined) dto.with = trigger.inputs;
|
|
12
12
|
if (trigger.filter !== undefined) dto.filter = trigger.filter;
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
registerPublisher as registerInRegistry,
|
|
16
16
|
renewDispatchClaim as renewFromRegistry,
|
|
17
17
|
} from '@shipfox/node-module';
|
|
18
|
-
import {sql} from 'drizzle-orm';
|
|
18
|
+
import {eq, sql} from 'drizzle-orm';
|
|
19
19
|
import type {WorkflowDefinitionPayload} from '#core/entities/workflow-definition.js';
|
|
20
20
|
import {normalizeWorkflowDocument} from '#core/workflow-model/index.js';
|
|
21
21
|
import {agentValidationCatalog} from '#test/agent-validation-catalog.js';
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from './definitions.js';
|
|
31
31
|
import {workflowDefinitions} from './schema/definitions.js';
|
|
32
32
|
import {definitionsOutbox} from './schema/outbox.js';
|
|
33
|
+
import {workflowWorkflows} from './schema/workflows.js';
|
|
33
34
|
|
|
34
35
|
const MS_PER_DAY = 24 * 60 * 60 * 1_000;
|
|
35
36
|
let outboxRegistry = createOutboxRegistry();
|
|
@@ -390,6 +391,289 @@ describe('definition queries', () => {
|
|
|
390
391
|
});
|
|
391
392
|
});
|
|
392
393
|
|
|
394
|
+
describe('workflow lineage', () => {
|
|
395
|
+
test('upsertDefinition creates a lineage and returns its workflowId', async () => {
|
|
396
|
+
const definition = await upsertDefinition({
|
|
397
|
+
projectId,
|
|
398
|
+
workspaceId,
|
|
399
|
+
configPath: '.shipfox/workflows/lineage.yml',
|
|
400
|
+
name: 'Lineage',
|
|
401
|
+
...definitionFields('Lineage'),
|
|
402
|
+
source: 'vcs',
|
|
403
|
+
ref: 'main',
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
expect(definition.workflowId).toBeDefined();
|
|
407
|
+
const lineages = await db()
|
|
408
|
+
.select()
|
|
409
|
+
.from(workflowWorkflows)
|
|
410
|
+
.where(eq(workflowWorkflows.projectId, projectId));
|
|
411
|
+
expect(lineages).toHaveLength(1);
|
|
412
|
+
expect(lineages[0]?.id).toBe(definition.workflowId);
|
|
413
|
+
expect(lineages[0]?.configPath).toBe('.shipfox/workflows/lineage.yml');
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test('re-syncing the same config path reuses the lineage', async () => {
|
|
417
|
+
const first = await upsertDefinition({
|
|
418
|
+
projectId,
|
|
419
|
+
workspaceId,
|
|
420
|
+
configPath: 'a.yml',
|
|
421
|
+
name: 'A v1',
|
|
422
|
+
...definitionFields('A v1'),
|
|
423
|
+
source: 'vcs',
|
|
424
|
+
ref: 'main',
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const second = await upsertDefinition({
|
|
428
|
+
projectId,
|
|
429
|
+
workspaceId,
|
|
430
|
+
configPath: 'a.yml',
|
|
431
|
+
name: 'A v2',
|
|
432
|
+
...definitionFields('A v2'),
|
|
433
|
+
source: 'vcs',
|
|
434
|
+
ref: 'main',
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
expect(second.workflowId).toBe(first.workflowId);
|
|
438
|
+
const lineages = await db()
|
|
439
|
+
.select()
|
|
440
|
+
.from(workflowWorkflows)
|
|
441
|
+
.where(eq(workflowWorkflows.projectId, projectId));
|
|
442
|
+
expect(lineages).toHaveLength(1);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test('manual and VCS rows at the same config path share one lineage', async () => {
|
|
446
|
+
const manual = await upsertDefinition({
|
|
447
|
+
projectId,
|
|
448
|
+
workspaceId,
|
|
449
|
+
configPath: 'shared.yml',
|
|
450
|
+
name: 'Shared manual',
|
|
451
|
+
...definitionFields('Shared manual'),
|
|
452
|
+
});
|
|
453
|
+
const vcs = await upsertDefinition({
|
|
454
|
+
projectId,
|
|
455
|
+
workspaceId,
|
|
456
|
+
configPath: 'shared.yml',
|
|
457
|
+
name: 'Shared vcs',
|
|
458
|
+
...definitionFields('Shared vcs'),
|
|
459
|
+
source: 'vcs',
|
|
460
|
+
ref: 'main',
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
expect(vcs.workflowId).toBe(manual.workflowId);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test('applyVcsDefinitionsBatch creates the lineage and reuses it across syncs', async () => {
|
|
467
|
+
await applyVcsDefinitionsBatch({
|
|
468
|
+
projectId,
|
|
469
|
+
workspaceId,
|
|
470
|
+
ref: 'main',
|
|
471
|
+
upserts: [
|
|
472
|
+
{
|
|
473
|
+
configPath: 'batch.yml',
|
|
474
|
+
name: 'Batch',
|
|
475
|
+
...definitionFields('Batch'),
|
|
476
|
+
contentHash: 'h-1',
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
});
|
|
480
|
+
const firstLineages = await db()
|
|
481
|
+
.select()
|
|
482
|
+
.from(workflowWorkflows)
|
|
483
|
+
.where(eq(workflowWorkflows.projectId, projectId));
|
|
484
|
+
expect(firstLineages).toHaveLength(1);
|
|
485
|
+
|
|
486
|
+
await applyVcsDefinitionsBatch({
|
|
487
|
+
projectId,
|
|
488
|
+
workspaceId,
|
|
489
|
+
ref: 'main',
|
|
490
|
+
upserts: [
|
|
491
|
+
{
|
|
492
|
+
configPath: 'batch.yml',
|
|
493
|
+
name: 'Batch v2',
|
|
494
|
+
...definitionFields('Batch v2'),
|
|
495
|
+
contentHash: 'h-2',
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
});
|
|
499
|
+
const secondLineages = await db()
|
|
500
|
+
.select()
|
|
501
|
+
.from(workflowWorkflows)
|
|
502
|
+
.where(eq(workflowWorkflows.projectId, projectId));
|
|
503
|
+
expect(secondLineages).toHaveLength(1);
|
|
504
|
+
expect(secondLineages[0]?.id).toBe(firstLineages[0]?.id);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test('soft-deleting a synced row keeps the lineage', async () => {
|
|
508
|
+
await applyVcsDefinitionsBatch({
|
|
509
|
+
projectId,
|
|
510
|
+
workspaceId,
|
|
511
|
+
ref: 'main',
|
|
512
|
+
upserts: [
|
|
513
|
+
{configPath: 'gone.yml', name: 'Gone', ...definitionFields('Gone'), contentHash: 'h-1'},
|
|
514
|
+
],
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const deletedCount = await softDeleteVcsDefinitionsNotIn({
|
|
518
|
+
projectId,
|
|
519
|
+
workspaceId,
|
|
520
|
+
ref: 'main',
|
|
521
|
+
keepConfigPaths: [],
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
expect(deletedCount).toBe(1);
|
|
525
|
+
const lineages = await db()
|
|
526
|
+
.select()
|
|
527
|
+
.from(workflowWorkflows)
|
|
528
|
+
.where(eq(workflowWorkflows.projectId, projectId));
|
|
529
|
+
expect(lineages).toHaveLength(1);
|
|
530
|
+
expect(lineages[0]?.configPath).toBe('gone.yml');
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
test('pathless manual definitions reuse the project lineage across saves', async () => {
|
|
534
|
+
const first = await upsertDefinition({
|
|
535
|
+
projectId,
|
|
536
|
+
workspaceId,
|
|
537
|
+
name: 'No path',
|
|
538
|
+
...definitionFields('No path'),
|
|
539
|
+
});
|
|
540
|
+
const second = await upsertDefinition({
|
|
541
|
+
projectId,
|
|
542
|
+
workspaceId,
|
|
543
|
+
name: 'No path updated',
|
|
544
|
+
...definitionFields('No path updated'),
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const lineage = await db()
|
|
548
|
+
.select()
|
|
549
|
+
.from(workflowWorkflows)
|
|
550
|
+
.where(eq(workflowWorkflows.id, first.workflowId));
|
|
551
|
+
expect(lineage).toHaveLength(1);
|
|
552
|
+
expect(lineage[0]?.projectId).toBe(projectId);
|
|
553
|
+
expect(second.workflowId).toBe(first.workflowId);
|
|
554
|
+
|
|
555
|
+
const projectLineages = await db()
|
|
556
|
+
.select()
|
|
557
|
+
.from(workflowWorkflows)
|
|
558
|
+
.where(eq(workflowWorkflows.projectId, projectId));
|
|
559
|
+
expect(projectLineages).toHaveLength(1);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
test('materializes a legacy row when it is read', async () => {
|
|
563
|
+
const legacyId = crypto.randomUUID();
|
|
564
|
+
await db()
|
|
565
|
+
.insert(workflowDefinitions)
|
|
566
|
+
.values({
|
|
567
|
+
id: legacyId,
|
|
568
|
+
projectId,
|
|
569
|
+
configPath: 'legacy.yml',
|
|
570
|
+
name: 'Legacy',
|
|
571
|
+
definition: definitionFields('Legacy'),
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const definition = await getDefinitionById(legacyId);
|
|
575
|
+
|
|
576
|
+
expect(definition?.workflowId).toBeDefined();
|
|
577
|
+
const stored = await db()
|
|
578
|
+
.select({workflowId: workflowDefinitions.workflowId})
|
|
579
|
+
.from(workflowDefinitions)
|
|
580
|
+
.where(eq(workflowDefinitions.id, legacyId));
|
|
581
|
+
expect(stored[0]?.workflowId).toBe(definition?.workflowId);
|
|
582
|
+
|
|
583
|
+
const lineages = await db()
|
|
584
|
+
.select()
|
|
585
|
+
.from(workflowWorkflows)
|
|
586
|
+
.where(eq(workflowWorkflows.projectId, projectId));
|
|
587
|
+
expect(lineages).toHaveLength(1);
|
|
588
|
+
expect(lineages[0]?.configPath).toBe('legacy.yml');
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test('materializes legacy pathless rows into one project lineage', async () => {
|
|
592
|
+
const firstId = crypto.randomUUID();
|
|
593
|
+
const secondId = crypto.randomUUID();
|
|
594
|
+
await db()
|
|
595
|
+
.insert(workflowDefinitions)
|
|
596
|
+
.values([
|
|
597
|
+
{
|
|
598
|
+
id: firstId,
|
|
599
|
+
projectId,
|
|
600
|
+
configPath: null,
|
|
601
|
+
name: 'Legacy one',
|
|
602
|
+
definition: definitionFields('Legacy one'),
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
id: secondId,
|
|
606
|
+
projectId,
|
|
607
|
+
configPath: null,
|
|
608
|
+
name: 'Legacy two',
|
|
609
|
+
definition: definitionFields('Legacy two'),
|
|
610
|
+
},
|
|
611
|
+
]);
|
|
612
|
+
|
|
613
|
+
const definitions = await listDefinitionsByProject(projectId);
|
|
614
|
+
|
|
615
|
+
expect(definitions).toHaveLength(2);
|
|
616
|
+
expect(definitions[0]?.workflowId).toBe(definitions[1]?.workflowId);
|
|
617
|
+
const lineages = await db()
|
|
618
|
+
.select()
|
|
619
|
+
.from(workflowWorkflows)
|
|
620
|
+
.where(eq(workflowWorkflows.projectId, projectId));
|
|
621
|
+
expect(lineages).toHaveLength(1);
|
|
622
|
+
expect(lineages[0]?.configPath).toBeNull();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test('materializes an unchanged legacy VCS row during sync', async () => {
|
|
626
|
+
const legacyId = crypto.randomUUID();
|
|
627
|
+
await db()
|
|
628
|
+
.insert(workflowDefinitions)
|
|
629
|
+
.values({
|
|
630
|
+
id: legacyId,
|
|
631
|
+
projectId,
|
|
632
|
+
configPath: 'legacy-vcs.yml',
|
|
633
|
+
source: 'vcs',
|
|
634
|
+
ref: 'main',
|
|
635
|
+
name: 'Legacy VCS',
|
|
636
|
+
definition: definitionFields('Legacy VCS'),
|
|
637
|
+
contentHash: 'legacy-hash',
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
const result = await applyVcsDefinitionsBatch({
|
|
641
|
+
projectId,
|
|
642
|
+
workspaceId,
|
|
643
|
+
ref: 'main',
|
|
644
|
+
upserts: [
|
|
645
|
+
{
|
|
646
|
+
configPath: 'legacy-vcs.yml',
|
|
647
|
+
name: 'Legacy VCS',
|
|
648
|
+
...definitionFields('Legacy VCS'),
|
|
649
|
+
contentHash: 'legacy-hash',
|
|
650
|
+
},
|
|
651
|
+
],
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
expect(result.appliedCount).toBe(0);
|
|
655
|
+
const stored = await db()
|
|
656
|
+
.select({workflowId: workflowDefinitions.workflowId})
|
|
657
|
+
.from(workflowDefinitions)
|
|
658
|
+
.where(eq(workflowDefinitions.id, legacyId));
|
|
659
|
+
expect(stored[0]?.workflowId).toBeDefined();
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
test('getDefinitionById returns the workflowId', async () => {
|
|
663
|
+
const created = await upsertDefinition({
|
|
664
|
+
projectId,
|
|
665
|
+
workspaceId,
|
|
666
|
+
configPath: 'found.yml',
|
|
667
|
+
name: 'Found',
|
|
668
|
+
...definitionFields('Found'),
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
const found = await getDefinitionById(created.id);
|
|
672
|
+
|
|
673
|
+
expect(found?.workflowId).toBe(created.workflowId);
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
393
677
|
describe('getDefinitionById', () => {
|
|
394
678
|
test('returns the definition when found', async () => {
|
|
395
679
|
const created = await upsertDefinition({
|
|
@@ -519,6 +803,7 @@ describe('definition queries', () => {
|
|
|
519
803
|
});
|
|
520
804
|
|
|
521
805
|
expect(restored.id).toBe(original.id);
|
|
806
|
+
expect(restored.workflowId).toBe(original.workflowId);
|
|
522
807
|
expect(restored.name).toBe('Recovered v2');
|
|
523
808
|
expect(restored.deletedAt).toBeNull();
|
|
524
809
|
const visible = await listDefinitionsByProject(projectId);
|
|
@@ -573,6 +858,11 @@ describe('definition queries', () => {
|
|
|
573
858
|
expect(first.appliedCount).toBe(2);
|
|
574
859
|
expect(await listOutboxRowsForProject(projectId)).toHaveLength(2);
|
|
575
860
|
|
|
861
|
+
await db()
|
|
862
|
+
.update(workflowDefinitions)
|
|
863
|
+
.set({fetchedAt: new Date(0), updatedAt: new Date(0)})
|
|
864
|
+
.where(eq(workflowDefinitions.projectId, projectId));
|
|
865
|
+
|
|
576
866
|
const second = await applyVcsDefinitionsBatch({
|
|
577
867
|
projectId,
|
|
578
868
|
workspaceId,
|
|
@@ -585,6 +875,15 @@ describe('definition queries', () => {
|
|
|
585
875
|
|
|
586
876
|
expect(second.appliedCount).toBe(1);
|
|
587
877
|
expect(await listOutboxRowsForProject(projectId)).toHaveLength(3);
|
|
878
|
+
|
|
879
|
+
const refreshed = (
|
|
880
|
+
await db()
|
|
881
|
+
.select()
|
|
882
|
+
.from(workflowDefinitions)
|
|
883
|
+
.where(eq(workflowDefinitions.projectId, projectId))
|
|
884
|
+
).find((row) => row.configPath === 'a.yml');
|
|
885
|
+
expect(refreshed?.fetchedAt.getTime()).toBeGreaterThan(0);
|
|
886
|
+
expect(refreshed?.updatedAt.getTime()).toBeGreaterThan(0);
|
|
588
887
|
});
|
|
589
888
|
|
|
590
889
|
test('softDeleteVcsDefinitionsNotIn writes a DEFINITION_DELETED event per pruned row', async () => {
|