@ripplo/testing 0.7.9 → 0.7.11
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/DSL.md +66 -32
- package/dist/{engine-BfvzXgLg.d.ts → engine-Dv3trweR.d.ts} +249 -45
- package/dist/express.d.ts +1 -1
- package/dist/express.js +45 -21
- package/dist/index.d.ts +10 -10
- package/dist/index.js +649 -85
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -108,7 +108,13 @@ function not(input) {
|
|
|
108
108
|
if (isConditionInput(input)) {
|
|
109
109
|
return condLeaf({ kind: "not", predicate: input.predicate });
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
const predicate = toPredicate(input);
|
|
112
|
+
if (predicate.kind === "when") {
|
|
113
|
+
throw new Error(
|
|
114
|
+
"when() cannot be wrapped in not() \u2014 express the negation in branch conditions"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return leaf({ kind: "not", predicate });
|
|
112
118
|
}
|
|
113
119
|
function and(...conditions) {
|
|
114
120
|
return condLeaf({ kind: "and", predicates: conditions.map((c) => c.predicate) });
|
|
@@ -118,26 +124,52 @@ function count(entity2) {
|
|
|
118
124
|
is: (n) => condLeaf({ entity: entity2.name, kind: "count", value: n })
|
|
119
125
|
};
|
|
120
126
|
}
|
|
121
|
-
function
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
function branch(name) {
|
|
128
|
+
const close = (condition) => (consequence) => {
|
|
129
|
+
const resolved = toPredicate(consequence);
|
|
130
|
+
if (containsWhen(resolved)) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`branch "${name}" nests a when() inside its consequence \u2014 whens are one level deep; split the scenario into another workflow`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return { __branch: true, branch: { condition, consequence: resolved, name } };
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
expect: close(void 0),
|
|
139
|
+
if: (condition) => ({
|
|
140
|
+
expect: close(condition.predicate)
|
|
141
|
+
})
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function when(...branches) {
|
|
145
|
+
if (branches.length === 0) {
|
|
146
|
+
throw new Error("when() needs at least one branch(...)");
|
|
147
|
+
}
|
|
148
|
+
const rows = branches.map((input) => input.branch);
|
|
149
|
+
const fallbacks = rows.filter((row2) => row2.condition == null);
|
|
150
|
+
if (fallbacks.length > 1) {
|
|
151
|
+
throw new Error("when() allows one unconditional branch \u2014 give the others .if(...) conditions");
|
|
152
|
+
}
|
|
153
|
+
if (fallbacks.length === 1 && rows.at(-1)?.condition != null) {
|
|
154
|
+
throw new Error("when() requires the unconditional branch to come last");
|
|
136
155
|
}
|
|
137
|
-
|
|
156
|
+
const dup = rows.find((row2, index) => rows.findIndex((r) => r.name === row2.name) !== index);
|
|
157
|
+
if (dup != null) {
|
|
158
|
+
throw new Error(`when() branch name "${dup.name}" is used twice \u2014 branch names must be unique`);
|
|
159
|
+
}
|
|
160
|
+
return leaf({ branches: rows, kind: "when" });
|
|
138
161
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
162
|
+
function containsWhen(predicate) {
|
|
163
|
+
if (predicate.kind === "when") {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (predicate.kind === "not") {
|
|
167
|
+
return containsWhen(predicate.predicate);
|
|
168
|
+
}
|
|
169
|
+
if (predicate.kind === "and") {
|
|
170
|
+
return predicate.predicates.some((p) => containsWhen(p));
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
141
173
|
}
|
|
142
174
|
|
|
143
175
|
// src/util.ts
|
|
@@ -676,14 +708,17 @@ function assertPredicateScoped(predicate, given) {
|
|
|
676
708
|
if (predicate.kind !== "when") {
|
|
677
709
|
return;
|
|
678
710
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
711
|
+
predicate.branches.forEach((row2) => {
|
|
712
|
+
const names = row2.condition == null ? [] : conditionSingletons(row2.condition);
|
|
713
|
+
names.forEach((name) => {
|
|
714
|
+
if (!given.has(name)) {
|
|
715
|
+
throw new Error(
|
|
716
|
+
`when() conditions on singleton "${name}", which is not in the workflow's given \u2014 add ${name}.of(...) to given`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
assertPredicateScoped(row2.consequence, given);
|
|
685
721
|
});
|
|
686
|
-
assertPredicateScoped(predicate.consequence, given);
|
|
687
722
|
}
|
|
688
723
|
function conditionSingletons(predicate) {
|
|
689
724
|
if (predicate.kind === "singleton") {
|
|
@@ -724,7 +759,7 @@ function assignParams(bindings) {
|
|
|
724
759
|
return {
|
|
725
760
|
counts: { ...acc.counts, [token.base]: ordinal + 1 },
|
|
726
761
|
names: new Map([...acc.names, [b, name]]),
|
|
727
|
-
params: { ...acc.params, [name]: {
|
|
762
|
+
params: { ...acc.params, [name]: { valueSpace: token.valueSpace } }
|
|
728
763
|
};
|
|
729
764
|
},
|
|
730
765
|
{ counts: {}, names: /* @__PURE__ */ new Map(), params: {} }
|
|
@@ -802,11 +837,10 @@ function predicateBindings(predicate) {
|
|
|
802
837
|
return predicateBindings(predicate.predicate);
|
|
803
838
|
}
|
|
804
839
|
case "when": {
|
|
805
|
-
return [
|
|
806
|
-
...predicateBindings(
|
|
807
|
-
...predicateBindings(
|
|
808
|
-
|
|
809
|
-
];
|
|
840
|
+
return predicate.branches.flatMap((row2) => [
|
|
841
|
+
...row2.condition == null ? [] : predicateBindings(row2.condition),
|
|
842
|
+
...predicateBindings(row2.consequence)
|
|
843
|
+
]);
|
|
810
844
|
}
|
|
811
845
|
case "and": {
|
|
812
846
|
return predicate.predicates.flatMap((p) => predicateBindings(p));
|
|
@@ -958,9 +992,11 @@ function resolvePredicate(predicate, ctx) {
|
|
|
958
992
|
case "when": {
|
|
959
993
|
return {
|
|
960
994
|
...predicate,
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
995
|
+
branches: predicate.branches.map((row2) => ({
|
|
996
|
+
condition: row2.condition == null ? void 0 : resolveCondition(row2.condition, ctx),
|
|
997
|
+
consequence: resolvePredicate(row2.consequence, ctx),
|
|
998
|
+
name: row2.name
|
|
999
|
+
}))
|
|
964
1000
|
};
|
|
965
1001
|
}
|
|
966
1002
|
case "and": {
|
|
@@ -1004,9 +1040,34 @@ function whereMap(map, ctx) {
|
|
|
1004
1040
|
function resolveWhere(value2, ctx) {
|
|
1005
1041
|
return isWithin2(value2) ? { ...value2, selection: { ...value2.selection, where: whereMap(value2.selection.where, ctx) } } : resolveValue(value2, ctx);
|
|
1006
1042
|
}
|
|
1043
|
+
function resolveCondition(condition, ctx) {
|
|
1044
|
+
switch (condition.kind) {
|
|
1045
|
+
case "singleton": {
|
|
1046
|
+
return {
|
|
1047
|
+
...condition,
|
|
1048
|
+
assertion: {
|
|
1049
|
+
...condition.assertion,
|
|
1050
|
+
value: resolveValue(condition.assertion.value, ctx)
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
case "count": {
|
|
1055
|
+
return condition;
|
|
1056
|
+
}
|
|
1057
|
+
case "not": {
|
|
1058
|
+
return { ...condition, predicate: resolveCondition(condition.predicate, ctx) };
|
|
1059
|
+
}
|
|
1060
|
+
case "and": {
|
|
1061
|
+
return {
|
|
1062
|
+
...condition,
|
|
1063
|
+
predicates: condition.predicates.map((p) => resolveCondition(p, ctx))
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1007
1068
|
|
|
1008
|
-
// src/
|
|
1009
|
-
function
|
|
1069
|
+
// src/workflow.ts
|
|
1070
|
+
function workflow(intent, fn) {
|
|
1010
1071
|
const sourcePath = captureSourcePath();
|
|
1011
1072
|
if (fn == null) {
|
|
1012
1073
|
return { spec: stubSpec(intent, sourcePath) };
|
|
@@ -1024,6 +1085,7 @@ function test(intent, fn) {
|
|
|
1024
1085
|
sourcePath,
|
|
1025
1086
|
steps: final.steps,
|
|
1026
1087
|
stub: false,
|
|
1088
|
+
tests: [],
|
|
1027
1089
|
world: final.world
|
|
1028
1090
|
}
|
|
1029
1091
|
};
|
|
@@ -1040,32 +1102,32 @@ function stubSpec(intent, sourcePath) {
|
|
|
1040
1102
|
sourcePath,
|
|
1041
1103
|
steps: [],
|
|
1042
1104
|
stub: true,
|
|
1105
|
+
tests: [],
|
|
1043
1106
|
world: []
|
|
1044
1107
|
};
|
|
1045
1108
|
}
|
|
1046
|
-
var
|
|
1109
|
+
var WORKFLOWS_ANCHOR_PATTERN = /[/\\]\.ripplo[/\\]workflows[/\\]([^):]+?)(?::\d+:\d+\)?)?$/;
|
|
1047
1110
|
function captureSourcePath() {
|
|
1048
1111
|
const stack = new Error("capture").stack;
|
|
1049
1112
|
if (stack == null) {
|
|
1050
1113
|
return void 0;
|
|
1051
1114
|
}
|
|
1052
|
-
const match = stack.split("\n").map((line) =>
|
|
1115
|
+
const match = stack.split("\n").map((line) => WORKFLOWS_ANCHOR_PATTERN.exec(line)).find((m) => m != null);
|
|
1053
1116
|
const captured = match?.[1];
|
|
1054
1117
|
return captured == null ? void 0 : captured.replaceAll("\\", "/");
|
|
1055
1118
|
}
|
|
1056
1119
|
function slugify(intent) {
|
|
1057
1120
|
const slug = intent.toLowerCase().replaceAll(/[^a-z0-9]+/g, " ").trim().split(" ").join("-");
|
|
1058
1121
|
if (slug.length === 0) {
|
|
1059
|
-
throw new Error(`
|
|
1122
|
+
throw new Error(`workflow intent "${intent}" slugifies to an empty string`);
|
|
1060
1123
|
}
|
|
1061
1124
|
return slug;
|
|
1062
1125
|
}
|
|
1063
1126
|
|
|
1064
1127
|
// src/params.ts
|
|
1065
|
-
function arbitrary(field2
|
|
1128
|
+
function arbitrary(field2) {
|
|
1066
1129
|
const token = {
|
|
1067
1130
|
base: `${field2.entity}_${field2.field}`,
|
|
1068
|
-
example,
|
|
1069
1131
|
valueSpace: field2.valueSpaceName
|
|
1070
1132
|
};
|
|
1071
1133
|
return paramBinding(token);
|
|
@@ -1357,6 +1419,32 @@ var withinSchema = z2.object({
|
|
|
1357
1419
|
selection: selectionSchema
|
|
1358
1420
|
});
|
|
1359
1421
|
var wait = z2.union([budgetSchema, z2.undefined()]).optional().transform((value2) => value2);
|
|
1422
|
+
var singletonPredicateSchema = z2.object({
|
|
1423
|
+
assertion: singletonAssertionSchema,
|
|
1424
|
+
kind: z2.literal("singleton"),
|
|
1425
|
+
singleton: z2.string().min(1),
|
|
1426
|
+
wait
|
|
1427
|
+
});
|
|
1428
|
+
var countPredicateSchema = z2.object({
|
|
1429
|
+
entity: z2.string().min(1),
|
|
1430
|
+
kind: z2.literal("count"),
|
|
1431
|
+
value: z2.number().int().nonnegative()
|
|
1432
|
+
});
|
|
1433
|
+
var conditionSchema = z2.lazy(
|
|
1434
|
+
() => z2.discriminatedUnion("kind", [
|
|
1435
|
+
singletonPredicateSchema,
|
|
1436
|
+
countPredicateSchema,
|
|
1437
|
+
z2.object({ kind: z2.literal("not"), predicate: conditionSchema }),
|
|
1438
|
+
z2.object({ kind: z2.literal("and"), predicates: z2.array(conditionSchema) })
|
|
1439
|
+
])
|
|
1440
|
+
);
|
|
1441
|
+
var whenBranchSchema = z2.lazy(
|
|
1442
|
+
() => z2.object({
|
|
1443
|
+
condition: z2.union([conditionSchema, z2.undefined()]).optional().transform((value2) => value2),
|
|
1444
|
+
consequence: predicateSchema,
|
|
1445
|
+
name: z2.string().min(1)
|
|
1446
|
+
})
|
|
1447
|
+
);
|
|
1360
1448
|
var predicateSchema = z2.lazy(
|
|
1361
1449
|
() => z2.discriminatedUnion("kind", [
|
|
1362
1450
|
z2.object({ kind: z2.literal("visible"), locator: locatorSchema, wait }),
|
|
@@ -1365,12 +1453,7 @@ var predicateSchema = z2.lazy(
|
|
|
1365
1453
|
z2.object({ kind: z2.literal("focused"), locator: locatorSchema, wait }),
|
|
1366
1454
|
z2.object({ kind: z2.literal("value"), locator: locatorSchema, value: stringValueSchema, wait }),
|
|
1367
1455
|
z2.object({ kind: z2.literal("text"), locator: locatorSchema, value: stringValueSchema, wait }),
|
|
1368
|
-
|
|
1369
|
-
assertion: singletonAssertionSchema,
|
|
1370
|
-
kind: z2.literal("singleton"),
|
|
1371
|
-
singleton: z2.string().min(1),
|
|
1372
|
-
wait
|
|
1373
|
-
}),
|
|
1456
|
+
singletonPredicateSchema,
|
|
1374
1457
|
z2.object({
|
|
1375
1458
|
kind: z2.literal("browser"),
|
|
1376
1459
|
name: browserSingletonSchema,
|
|
@@ -1386,17 +1469,8 @@ var predicateSchema = z2.lazy(
|
|
|
1386
1469
|
}),
|
|
1387
1470
|
z2.object({ kind: z2.literal("not"), predicate: predicateSchema }),
|
|
1388
1471
|
z2.object({ kind: z2.literal("and"), predicates: z2.array(predicateSchema) }),
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
kind: z2.literal("count"),
|
|
1392
|
-
value: z2.number().int().nonnegative()
|
|
1393
|
-
}),
|
|
1394
|
-
z2.object({
|
|
1395
|
-
condition: predicateSchema,
|
|
1396
|
-
consequence: predicateSchema,
|
|
1397
|
-
kind: z2.literal("when"),
|
|
1398
|
-
otherwise: z2.union([predicateSchema, z2.undefined()]).optional().transform((value2) => value2)
|
|
1399
|
-
})
|
|
1472
|
+
countPredicateSchema,
|
|
1473
|
+
z2.object({ branches: z2.array(whenBranchSchema), kind: z2.literal("when") })
|
|
1400
1474
|
])
|
|
1401
1475
|
);
|
|
1402
1476
|
|
|
@@ -1485,7 +1559,6 @@ var stepSchema = z4.object({
|
|
|
1485
1559
|
expect: z4.array(predicateSchema).default([])
|
|
1486
1560
|
});
|
|
1487
1561
|
var paramSchema = z4.object({
|
|
1488
|
-
example: primitiveSchema.optional(),
|
|
1489
1562
|
valueSpace: z4.string().min(1)
|
|
1490
1563
|
});
|
|
1491
1564
|
var setupSchema = z4.object({
|
|
@@ -1497,7 +1570,19 @@ var absenceSchema = z4.object({
|
|
|
1497
1570
|
entity: z4.string().min(1),
|
|
1498
1571
|
where: z4.record(z4.string().min(1), setValueSchema)
|
|
1499
1572
|
});
|
|
1500
|
-
var
|
|
1573
|
+
var resolvedTestSchema = z4.object({
|
|
1574
|
+
absent: z4.array(absenceSchema).default([]),
|
|
1575
|
+
exclusive: z4.array(z4.string().min(1)).default([]),
|
|
1576
|
+
intent: z4.string().min(1),
|
|
1577
|
+
name: z4.string().min(1),
|
|
1578
|
+
params: z4.record(z4.string().min(1), paramSchema),
|
|
1579
|
+
singletons: z4.record(z4.string().min(1), setValueSchema).default({}),
|
|
1580
|
+
slug: z4.string().min(1),
|
|
1581
|
+
steps: z4.array(stepSchema).default([]),
|
|
1582
|
+
workflow: z4.string().min(1),
|
|
1583
|
+
world: z4.array(setupSchema).default([])
|
|
1584
|
+
});
|
|
1585
|
+
var workflowSchema = z4.object({
|
|
1501
1586
|
absent: z4.array(absenceSchema).default([]),
|
|
1502
1587
|
exclusive: z4.array(z4.string().min(1)).default([]),
|
|
1503
1588
|
intent: z4.string().min(1),
|
|
@@ -1508,6 +1593,7 @@ var testSchema = z4.object({
|
|
|
1508
1593
|
sourcePath: z4.string().min(1).optional(),
|
|
1509
1594
|
steps: z4.array(stepSchema).default([]),
|
|
1510
1595
|
stub: z4.boolean().default(false),
|
|
1596
|
+
tests: z4.array(resolvedTestSchema).default([]),
|
|
1511
1597
|
world: z4.array(setupSchema).default([])
|
|
1512
1598
|
});
|
|
1513
1599
|
var fixtureEntrySchema = z4.object({
|
|
@@ -1518,8 +1604,8 @@ var lockfileSchema = z4.object({
|
|
|
1518
1604
|
entities: z4.array(entitySchemaSchema),
|
|
1519
1605
|
fixtures: z4.record(z4.string().min(1), fixtureEntrySchema).default({}),
|
|
1520
1606
|
singletons: z4.array(singletonSchemaSchema).default([]),
|
|
1521
|
-
|
|
1522
|
-
|
|
1607
|
+
valueSpaces: z4.array(valueSpaceSchema),
|
|
1608
|
+
workflows: z4.array(workflowSchema)
|
|
1523
1609
|
});
|
|
1524
1610
|
var lockfileCodec = defineCodec({ name: "ripplo-lockfile", schema: lockfileSchema });
|
|
1525
1611
|
|
|
@@ -1531,6 +1617,9 @@ var stepDescriptorSchema = z5.object({
|
|
|
1531
1617
|
target: z5.string(),
|
|
1532
1618
|
value: z5.string()
|
|
1533
1619
|
});
|
|
1620
|
+
function slugify2(name) {
|
|
1621
|
+
return name.toLowerCase().replaceAll(/[^a-z0-9]/g, "-").split("-").filter((part) => part.length > 0).join("-");
|
|
1622
|
+
}
|
|
1534
1623
|
|
|
1535
1624
|
// ../spec/src/session.ts
|
|
1536
1625
|
import { z as z6 } from "zod";
|
|
@@ -1618,17 +1707,491 @@ function mountClientEngine(ripplo, impls, { enabled: enabled2 }) {
|
|
|
1618
1707
|
Reflect.set(globalThis, CLIENT_MOUNT_KEY, createClientEngine(ripplo, impls));
|
|
1619
1708
|
}
|
|
1620
1709
|
|
|
1710
|
+
// src/expand.ts
|
|
1711
|
+
var MAX_CANDIDATES = 4096;
|
|
1712
|
+
function singletonValuesOf(singletons) {
|
|
1713
|
+
return new Map(
|
|
1714
|
+
singletons.flatMap((singleton2) => {
|
|
1715
|
+
const space = singleton2.valueSpaces.find((s) => s.name === singleton2.schema.valueSpace);
|
|
1716
|
+
return space?.values == null ? [] : [[singleton2.schema.name, space.values]];
|
|
1717
|
+
})
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
function expandWorkflow(workflow2, options) {
|
|
1721
|
+
if (workflow2.stub) {
|
|
1722
|
+
return { ...workflow2, tests: [] };
|
|
1723
|
+
}
|
|
1724
|
+
assertUniqueBranchNames(workflow2);
|
|
1725
|
+
const targets = collectTargets(workflow2.steps);
|
|
1726
|
+
const whenIds = new Map(targets.map((target, index) => [target.when, index]));
|
|
1727
|
+
const sims = proposeCandidates(workflow2, options).map((candidate) => simulate(workflow2, candidate)).filter((sim) => sim != null);
|
|
1728
|
+
if (targets.length === 0) {
|
|
1729
|
+
return { ...workflow2, tests: [requireMainTest(workflow2, sims)] };
|
|
1730
|
+
}
|
|
1731
|
+
const { tests } = targets.reduce(
|
|
1732
|
+
(acc, target) => coverTarget({ acc, sims, target, whenIds, workflow: workflow2 }),
|
|
1733
|
+
{ covered: /* @__PURE__ */ new Set(), tests: [] }
|
|
1734
|
+
);
|
|
1735
|
+
return { ...workflow2, tests };
|
|
1736
|
+
}
|
|
1737
|
+
function resolveValidTest(workflow2, sim, name) {
|
|
1738
|
+
const test = resolveTest(workflow2, sim, name);
|
|
1739
|
+
return danglingRefHeads(test).length === 0 ? test : null;
|
|
1740
|
+
}
|
|
1741
|
+
function danglingRefHeads(test) {
|
|
1742
|
+
const known = /* @__PURE__ */ new Set([
|
|
1743
|
+
...test.world.map((setup) => setup.as),
|
|
1744
|
+
...Object.keys(test.params),
|
|
1745
|
+
...test.steps.flatMap((step) => createdAliasesIn(step))
|
|
1746
|
+
]);
|
|
1747
|
+
return [...new Set(collectRefObjects(test).map((ref) => headOf(ref)))].filter(
|
|
1748
|
+
(head) => !known.has(head)
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
function createdAliasesIn(step) {
|
|
1752
|
+
return step.expect.flatMap((predicate) => {
|
|
1753
|
+
if (predicate.kind !== "state" || predicate.assertion.kind === "deleted") {
|
|
1754
|
+
return [];
|
|
1755
|
+
}
|
|
1756
|
+
return [predicate.assertion.as];
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
function coverTarget({ acc, sims, target, whenIds, workflow: workflow2 }) {
|
|
1760
|
+
const key2 = choiceKey(whenIds, target.when, target.index);
|
|
1761
|
+
if (acc.covered.has(key2)) {
|
|
1762
|
+
return { covered: acc.covered, tests: acc.tests };
|
|
1763
|
+
}
|
|
1764
|
+
const picked = sims.filter((s) => s.choices.get(target.when) === target.index).reduce((found, s) => {
|
|
1765
|
+
if (found != null) {
|
|
1766
|
+
return found;
|
|
1767
|
+
}
|
|
1768
|
+
const test = resolveValidTest(workflow2, s, target.name);
|
|
1769
|
+
return test == null ? null : { sim: s, test };
|
|
1770
|
+
}, null);
|
|
1771
|
+
if (picked == null) {
|
|
1772
|
+
throw new Error(
|
|
1773
|
+
`workflow "${workflow2.name}": branch "${target.name}" is unreachable \u2014 no combination of optional entities and singleton values reaches it`
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
const reached = [...picked.sim.choices].map(([when2, index]) => choiceKey(whenIds, when2, index));
|
|
1777
|
+
return {
|
|
1778
|
+
covered: /* @__PURE__ */ new Set([...acc.covered, ...reached]),
|
|
1779
|
+
tests: [...acc.tests, picked.test]
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
function choiceKey(whenIds, when2, index) {
|
|
1783
|
+
const id2 = whenIds.get(when2);
|
|
1784
|
+
if (id2 == null) {
|
|
1785
|
+
throw new Error("internal: when node missing from target index");
|
|
1786
|
+
}
|
|
1787
|
+
return `${String(id2)}:${String(index)}`;
|
|
1788
|
+
}
|
|
1789
|
+
function requireMainTest(workflow2, sims) {
|
|
1790
|
+
const test = sims.reduce(
|
|
1791
|
+
(found, sim) => found ?? resolveValidTest(workflow2, sim, "main"),
|
|
1792
|
+
null
|
|
1793
|
+
);
|
|
1794
|
+
if (test == null) {
|
|
1795
|
+
throw new Error(
|
|
1796
|
+
`workflow "${workflow2.name}": no valid seed found \u2014 steps may reference optional entities no candidate provides, or a condition mentions values the solver cannot pin`
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
return test;
|
|
1800
|
+
}
|
|
1801
|
+
function assertUniqueBranchNames(workflow2) {
|
|
1802
|
+
const names = collectTargets(workflow2.steps).map((target) => target.name);
|
|
1803
|
+
const dup = names.find((name, index) => names.indexOf(name) !== index);
|
|
1804
|
+
if (dup != null) {
|
|
1805
|
+
throw new Error(
|
|
1806
|
+
`workflow "${workflow2.name}": branch name "${dup}" is used twice \u2014 branch names must be unique within a workflow`
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
function collectTargets(steps) {
|
|
1811
|
+
return steps.flatMap((step) => step.expect.flatMap((predicate) => targetsIn(predicate)));
|
|
1812
|
+
}
|
|
1813
|
+
function targetsIn(predicate) {
|
|
1814
|
+
if (predicate.kind === "not") {
|
|
1815
|
+
return targetsIn(predicate.predicate);
|
|
1816
|
+
}
|
|
1817
|
+
if (predicate.kind === "and") {
|
|
1818
|
+
return predicate.predicates.flatMap((p) => targetsIn(p));
|
|
1819
|
+
}
|
|
1820
|
+
if (predicate.kind !== "when") {
|
|
1821
|
+
return [];
|
|
1822
|
+
}
|
|
1823
|
+
return predicate.branches.flatMap((row2, index) => [
|
|
1824
|
+
{ index, name: row2.name, when: predicate },
|
|
1825
|
+
...targetsIn(row2.consequence)
|
|
1826
|
+
]);
|
|
1827
|
+
}
|
|
1828
|
+
function proposeCandidates(workflow2, options) {
|
|
1829
|
+
const subsets = maybeSubsets(workflow2.maybe);
|
|
1830
|
+
const pinSets = pinAssignments(workflow2, options);
|
|
1831
|
+
if (subsets.length * pinSets.length > MAX_CANDIDATES) {
|
|
1832
|
+
throw new Error(
|
|
1833
|
+
`workflow "${workflow2.name}": too many optional entities and singleton values to solve \u2014 split the workflow or convert maybe(...) entities to of(...)`
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
return subsets.flatMap((maybes) => pinSets.map((pins) => ({ maybes, pins })));
|
|
1837
|
+
}
|
|
1838
|
+
function maybeSubsets(maybe) {
|
|
1839
|
+
const masks = Array.from({ length: 1 << maybe.length }, (_, mask) => mask);
|
|
1840
|
+
return masks.map((mask) => ({ mask, size: maybe.filter((_, i) => (mask & 1 << i) !== 0).length })).toSorted((a, b) => a.size === b.size ? a.mask - b.mask : a.size - b.size).map(({ mask }) => maybe.filter((_, i) => (mask & 1 << i) !== 0));
|
|
1841
|
+
}
|
|
1842
|
+
function pinAssignments(workflow2, options) {
|
|
1843
|
+
const domains = pinDomains(workflow2, options);
|
|
1844
|
+
return Object.entries(domains).reduce(
|
|
1845
|
+
(acc, [param, domain]) => acc.flatMap((pins) => domain.map((value2) => ({ ...pins, [param]: value2 }))),
|
|
1846
|
+
[{}]
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
function pinDomains(workflow2, options) {
|
|
1850
|
+
const literals = conditionSingletonLiterals(workflow2.steps);
|
|
1851
|
+
return Object.entries(workflow2.singletons).reduce((acc, [name, value2]) => {
|
|
1852
|
+
const param = paramRefOf(value2);
|
|
1853
|
+
const compared = literals.get(name);
|
|
1854
|
+
if (param == null || compared == null) {
|
|
1855
|
+
return acc;
|
|
1856
|
+
}
|
|
1857
|
+
const enumValues = options.singletonValues.get(name);
|
|
1858
|
+
return { ...acc, [param]: withComplements(compared, enumValues) };
|
|
1859
|
+
}, {});
|
|
1860
|
+
}
|
|
1861
|
+
function conditionSingletonLiterals(steps) {
|
|
1862
|
+
const pairs = steps.flatMap(
|
|
1863
|
+
(step) => step.expect.flatMap((predicate) => conditionLiteralsIn(predicate))
|
|
1864
|
+
);
|
|
1865
|
+
return pairs.reduce(
|
|
1866
|
+
(acc, [name, literal]) => new Map([...acc, [name, [.../* @__PURE__ */ new Set([...acc.get(name) ?? [], literal])]]]),
|
|
1867
|
+
/* @__PURE__ */ new Map()
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
function conditionLiteralsIn(predicate) {
|
|
1871
|
+
if (predicate.kind === "not") {
|
|
1872
|
+
return conditionLiteralsIn(predicate.predicate);
|
|
1873
|
+
}
|
|
1874
|
+
if (predicate.kind === "and") {
|
|
1875
|
+
return predicate.predicates.flatMap((p) => conditionLiteralsIn(p));
|
|
1876
|
+
}
|
|
1877
|
+
if (predicate.kind !== "when") {
|
|
1878
|
+
return [];
|
|
1879
|
+
}
|
|
1880
|
+
return predicate.branches.flatMap((row2) => [
|
|
1881
|
+
...row2.condition == null ? [] : singletonLiteralsInCondition(row2.condition),
|
|
1882
|
+
...conditionLiteralsIn(row2.consequence)
|
|
1883
|
+
]);
|
|
1884
|
+
}
|
|
1885
|
+
function singletonLiteralsInCondition(condition) {
|
|
1886
|
+
switch (condition.kind) {
|
|
1887
|
+
case "singleton": {
|
|
1888
|
+
const value2 = condition.assertion.value;
|
|
1889
|
+
return value2 == null || typeof value2 !== "object" ? [[condition.singleton, value2]] : [];
|
|
1890
|
+
}
|
|
1891
|
+
case "count": {
|
|
1892
|
+
return [];
|
|
1893
|
+
}
|
|
1894
|
+
case "not": {
|
|
1895
|
+
return singletonLiteralsInCondition(condition.predicate);
|
|
1896
|
+
}
|
|
1897
|
+
case "and": {
|
|
1898
|
+
return condition.predicates.flatMap((p) => singletonLiteralsInCondition(p));
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
function paramRefOf(value2) {
|
|
1903
|
+
if (value2 == null || typeof value2 !== "object" || !("ref" in value2)) {
|
|
1904
|
+
return void 0;
|
|
1905
|
+
}
|
|
1906
|
+
return value2.ref.includes(".") ? void 0 : value2.ref;
|
|
1907
|
+
}
|
|
1908
|
+
function withComplements(values, enumValues) {
|
|
1909
|
+
if (enumValues != null) {
|
|
1910
|
+
return [.../* @__PURE__ */ new Set([...values, ...enumValues])];
|
|
1911
|
+
}
|
|
1912
|
+
const complements = values.flatMap((value2) => {
|
|
1913
|
+
if (typeof value2 === "boolean") {
|
|
1914
|
+
return [!value2];
|
|
1915
|
+
}
|
|
1916
|
+
if (typeof value2 === "number") {
|
|
1917
|
+
return [value2 + 1];
|
|
1918
|
+
}
|
|
1919
|
+
if (typeof value2 === "string") {
|
|
1920
|
+
return [syntheticDistinct(value2, values)];
|
|
1921
|
+
}
|
|
1922
|
+
return [];
|
|
1923
|
+
});
|
|
1924
|
+
return [.../* @__PURE__ */ new Set([...values, ...complements])];
|
|
1925
|
+
}
|
|
1926
|
+
function syntheticDistinct(seed, taken) {
|
|
1927
|
+
const candidate = `${seed}-alt`;
|
|
1928
|
+
return taken.includes(candidate) ? syntheticDistinct(candidate, taken) : candidate;
|
|
1929
|
+
}
|
|
1930
|
+
function simulate(workflow2, candidate) {
|
|
1931
|
+
const initial = {
|
|
1932
|
+
choices: /* @__PURE__ */ new Map(),
|
|
1933
|
+
state: {
|
|
1934
|
+
rows: [...workflow2.world, ...candidate.maybes],
|
|
1935
|
+
singles: seedSingles(workflow2.singletons, candidate.pins)
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
const folded = workflow2.steps.reduce(
|
|
1939
|
+
(acc, step) => acc == null ? null : stepSim(acc, step),
|
|
1940
|
+
initial
|
|
1941
|
+
);
|
|
1942
|
+
return folded == null ? null : { candidate, choices: folded.choices };
|
|
1943
|
+
}
|
|
1944
|
+
function seedSingles(singletons, pins) {
|
|
1945
|
+
return Object.fromEntries(
|
|
1946
|
+
Object.entries(singletons).map(([name, value2]) => {
|
|
1947
|
+
const param = paramRefOf(value2);
|
|
1948
|
+
return [name, param != null && param in pins ? pins[param] ?? null : value2];
|
|
1949
|
+
})
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
function stepSim(acc, step) {
|
|
1953
|
+
const rows = step.expect.filter((p) => p.kind === "state").reduce((current, effect) => applyEffect(current, effect), acc.state.rows);
|
|
1954
|
+
const preWhens = { rows, singles: acc.state.singles };
|
|
1955
|
+
const whens = step.expect.filter((p) => p.kind === "when");
|
|
1956
|
+
const resolved = whens.reduce((current, when2) => current == null ? null : foldWhen(current, when2, preWhens), {
|
|
1957
|
+
choices: acc.choices,
|
|
1958
|
+
sets: {}
|
|
1959
|
+
});
|
|
1960
|
+
if (resolved == null) {
|
|
1961
|
+
return null;
|
|
1962
|
+
}
|
|
1963
|
+
const immediate = singletonSets(step.expect);
|
|
1964
|
+
return {
|
|
1965
|
+
choices: resolved.choices,
|
|
1966
|
+
state: { rows, singles: { ...preWhens.singles, ...immediate, ...resolved.sets } }
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
function foldWhen(acc, when2, state) {
|
|
1970
|
+
const picked = pickBranch(when2, state);
|
|
1971
|
+
if (picked === "unknown") {
|
|
1972
|
+
return null;
|
|
1973
|
+
}
|
|
1974
|
+
if (picked == null) {
|
|
1975
|
+
return acc;
|
|
1976
|
+
}
|
|
1977
|
+
const row2 = when2.branches[picked];
|
|
1978
|
+
if (row2 == null) {
|
|
1979
|
+
return acc;
|
|
1980
|
+
}
|
|
1981
|
+
const choices = new Map([...acc.choices, [when2, picked]]);
|
|
1982
|
+
const consequence = row2.consequence;
|
|
1983
|
+
if (consequence.kind === "when") {
|
|
1984
|
+
return foldWhen({ choices, sets: acc.sets }, consequence, state);
|
|
1985
|
+
}
|
|
1986
|
+
return { choices, sets: { ...acc.sets, ...singletonSets([consequence]) } };
|
|
1987
|
+
}
|
|
1988
|
+
function pickBranch(when2, state) {
|
|
1989
|
+
return when2.branches.reduce((picked, row2, index) => {
|
|
1990
|
+
if (picked !== void 0) {
|
|
1991
|
+
return picked;
|
|
1992
|
+
}
|
|
1993
|
+
if (row2.condition == null) {
|
|
1994
|
+
return index;
|
|
1995
|
+
}
|
|
1996
|
+
const holds = evalCondition(row2.condition, state);
|
|
1997
|
+
if (holds === "unknown") {
|
|
1998
|
+
return "unknown";
|
|
1999
|
+
}
|
|
2000
|
+
return holds ? index : void 0;
|
|
2001
|
+
}, void 0);
|
|
2002
|
+
}
|
|
2003
|
+
function evalCondition(condition, state) {
|
|
2004
|
+
switch (condition.kind) {
|
|
2005
|
+
case "count": {
|
|
2006
|
+
return state.rows.filter((row2) => row2.entity === condition.entity).length === condition.value;
|
|
2007
|
+
}
|
|
2008
|
+
case "singleton": {
|
|
2009
|
+
return compareValues(state.singles[condition.singleton], condition.assertion.value);
|
|
2010
|
+
}
|
|
2011
|
+
case "not": {
|
|
2012
|
+
return negate(evalCondition(condition.predicate, state));
|
|
2013
|
+
}
|
|
2014
|
+
case "and": {
|
|
2015
|
+
return conjoin(condition.predicates.map((p) => evalCondition(p, state)));
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
function compareValues(seeded, wanted) {
|
|
2020
|
+
if (seeded === void 0) {
|
|
2021
|
+
return "unknown";
|
|
2022
|
+
}
|
|
2023
|
+
const seededRef = isRefValue(seeded);
|
|
2024
|
+
const wantedRef = isRefValue(wanted);
|
|
2025
|
+
if (seededRef != null && wantedRef != null) {
|
|
2026
|
+
return seededRef === wantedRef ? true : "unknown";
|
|
2027
|
+
}
|
|
2028
|
+
if (seededRef != null || wantedRef != null || isTemplateValue(seeded) || isTemplateValue(wanted)) {
|
|
2029
|
+
return "unknown";
|
|
2030
|
+
}
|
|
2031
|
+
return sameSetValue(seeded, wanted);
|
|
2032
|
+
}
|
|
2033
|
+
function isRefValue(value2) {
|
|
2034
|
+
return value2 != null && typeof value2 === "object" && "ref" in value2 ? value2.ref : void 0;
|
|
2035
|
+
}
|
|
2036
|
+
function isTemplateValue(value2) {
|
|
2037
|
+
return value2 != null && typeof value2 === "object" && "template" in value2;
|
|
2038
|
+
}
|
|
2039
|
+
function negate(value2) {
|
|
2040
|
+
return value2 === "unknown" ? "unknown" : !value2;
|
|
2041
|
+
}
|
|
2042
|
+
function conjoin(values) {
|
|
2043
|
+
if (values.includes(false)) {
|
|
2044
|
+
return false;
|
|
2045
|
+
}
|
|
2046
|
+
return values.every((v2) => v2 === true) ? true : "unknown";
|
|
2047
|
+
}
|
|
2048
|
+
function singletonSets(predicates) {
|
|
2049
|
+
return Object.fromEntries(
|
|
2050
|
+
predicates.flatMap((predicate) => {
|
|
2051
|
+
if (predicate.kind === "singleton") {
|
|
2052
|
+
return [[predicate.singleton, predicate.assertion.value]];
|
|
2053
|
+
}
|
|
2054
|
+
return [];
|
|
2055
|
+
})
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
function applyEffect(rows, effect) {
|
|
2059
|
+
if (effect.assertion.kind === "created") {
|
|
2060
|
+
return [
|
|
2061
|
+
...rows,
|
|
2062
|
+
{ as: effect.assertion.as, entity: effect.entity, set: effect.assertion.props }
|
|
2063
|
+
];
|
|
2064
|
+
}
|
|
2065
|
+
if (effect.assertion.kind === "deleted") {
|
|
2066
|
+
return rows.filter((row2) => row2.entity !== effect.entity || !matchesKey(row2, effect.key, rows));
|
|
2067
|
+
}
|
|
2068
|
+
return rows;
|
|
2069
|
+
}
|
|
2070
|
+
function matchesKey(row2, key2, rows) {
|
|
2071
|
+
return Object.entries(key2).every(([field2, want]) => matchesField({ field: field2, row: row2, rows, want }));
|
|
2072
|
+
}
|
|
2073
|
+
function matchesField({ field: field2, row: row2, rows, want }) {
|
|
2074
|
+
if (isWithinValue(want)) {
|
|
2075
|
+
const candidates = rows.filter(
|
|
2076
|
+
(candidate) => candidate.entity === want.selection.entity && matchesKey(candidate, want.selection.where, rows)
|
|
2077
|
+
).map((candidate) => fieldValue(candidate, want.field));
|
|
2078
|
+
const own = fieldValue(row2, field2);
|
|
2079
|
+
return candidates.some((candidate) => valuesEqual(own, candidate));
|
|
2080
|
+
}
|
|
2081
|
+
return valuesEqual(fieldValue(row2, field2), want);
|
|
2082
|
+
}
|
|
2083
|
+
function isWithinValue(value2) {
|
|
2084
|
+
return value2 != null && typeof value2 === "object" && "kind" in value2;
|
|
2085
|
+
}
|
|
2086
|
+
function fieldValue(row2, field2) {
|
|
2087
|
+
return row2.set[field2] ?? { ref: `${row2.as}.${field2}` };
|
|
2088
|
+
}
|
|
2089
|
+
function valuesEqual(a, b) {
|
|
2090
|
+
const aRef = isRefValue(a);
|
|
2091
|
+
const bRef = isRefValue(b);
|
|
2092
|
+
if (aRef != null || bRef != null) {
|
|
2093
|
+
return aRef === bRef;
|
|
2094
|
+
}
|
|
2095
|
+
if (isTemplateValue(a) || isTemplateValue(b)) {
|
|
2096
|
+
return false;
|
|
2097
|
+
}
|
|
2098
|
+
return sameSetValue(a, b);
|
|
2099
|
+
}
|
|
2100
|
+
function resolveTest(workflow2, sim, name) {
|
|
2101
|
+
const steps = workflow2.steps.map((step) => ({
|
|
2102
|
+
action: step.action,
|
|
2103
|
+
expect: step.expect.flatMap((predicate) => resolvePredicate2(predicate, sim.choices))
|
|
2104
|
+
}));
|
|
2105
|
+
const world = [...workflow2.world, ...sim.candidate.maybes];
|
|
2106
|
+
const singletons = seedSingles(workflow2.singletons, sim.candidate.pins);
|
|
2107
|
+
const used = usedRefHeads({ singletons, steps, workflow: workflow2, world });
|
|
2108
|
+
return {
|
|
2109
|
+
absent: workflow2.absent,
|
|
2110
|
+
exclusive: workflow2.exclusive,
|
|
2111
|
+
intent: workflow2.intent,
|
|
2112
|
+
name,
|
|
2113
|
+
params: Object.fromEntries(
|
|
2114
|
+
Object.entries(workflow2.params).filter(([param]) => used.has(param))
|
|
2115
|
+
),
|
|
2116
|
+
singletons,
|
|
2117
|
+
slug: slugify2(name),
|
|
2118
|
+
steps,
|
|
2119
|
+
workflow: workflow2.name,
|
|
2120
|
+
world
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
function resolvePredicate2(predicate, choices) {
|
|
2124
|
+
if (predicate.kind === "not") {
|
|
2125
|
+
const inner = resolvePredicate2(predicate.predicate, choices);
|
|
2126
|
+
return inner.map((p) => ({ kind: "not", predicate: p }));
|
|
2127
|
+
}
|
|
2128
|
+
if (predicate.kind === "and") {
|
|
2129
|
+
return [
|
|
2130
|
+
{
|
|
2131
|
+
kind: "and",
|
|
2132
|
+
predicates: predicate.predicates.flatMap((p) => resolvePredicate2(p, choices))
|
|
2133
|
+
}
|
|
2134
|
+
];
|
|
2135
|
+
}
|
|
2136
|
+
if (predicate.kind !== "when") {
|
|
2137
|
+
return [predicate];
|
|
2138
|
+
}
|
|
2139
|
+
const picked = choices.get(predicate);
|
|
2140
|
+
const row2 = picked == null ? void 0 : predicate.branches[picked];
|
|
2141
|
+
return row2 == null ? [] : resolvePredicate2(row2.consequence, choices);
|
|
2142
|
+
}
|
|
2143
|
+
function usedRefHeads({ singletons, steps, workflow: workflow2, world }) {
|
|
2144
|
+
const values = [
|
|
2145
|
+
...world.flatMap((setup) => Object.values(setup.set)),
|
|
2146
|
+
...workflow2.absent.flatMap((absence) => Object.values(absence.where)),
|
|
2147
|
+
...Object.values(singletons),
|
|
2148
|
+
...steps.flatMap((step) => stepRefValues(step))
|
|
2149
|
+
];
|
|
2150
|
+
return new Set(values.flatMap((value2) => refStrings(value2)).map((ref) => headOf(ref)));
|
|
2151
|
+
}
|
|
2152
|
+
function stepRefValues(step) {
|
|
2153
|
+
return collectRefObjects(step).map((ref) => ({ ref }));
|
|
2154
|
+
}
|
|
2155
|
+
function collectRefObjects(node) {
|
|
2156
|
+
if (Array.isArray(node)) {
|
|
2157
|
+
return node.flatMap((item) => collectRefObjects(item));
|
|
2158
|
+
}
|
|
2159
|
+
if (node == null || typeof node !== "object") {
|
|
2160
|
+
return [];
|
|
2161
|
+
}
|
|
2162
|
+
if ("ref" in node && typeof node.ref === "string" && Object.keys(node).length === 1) {
|
|
2163
|
+
return [node.ref];
|
|
2164
|
+
}
|
|
2165
|
+
return Object.values(node).flatMap((value2) => collectRefObjects(value2));
|
|
2166
|
+
}
|
|
2167
|
+
function refStrings(value2) {
|
|
2168
|
+
const ref = isRefValue(value2);
|
|
2169
|
+
if (ref != null) {
|
|
2170
|
+
return [ref];
|
|
2171
|
+
}
|
|
2172
|
+
if (value2 != null && typeof value2 === "object" && "template" in value2) {
|
|
2173
|
+
return value2.template.flatMap((segment) => typeof segment === "string" ? [] : [segment.ref]);
|
|
2174
|
+
}
|
|
2175
|
+
return [];
|
|
2176
|
+
}
|
|
2177
|
+
function headOf(ref) {
|
|
2178
|
+
const dot = ref.indexOf(".");
|
|
2179
|
+
return dot === -1 ? ref : ref.slice(0, dot);
|
|
2180
|
+
}
|
|
2181
|
+
|
|
1621
2182
|
// src/build.ts
|
|
1622
2183
|
function buildLockfile(input) {
|
|
1623
2184
|
assertUniqueNames(input.entities);
|
|
1624
2185
|
const lockfile = lockfileSchema.parse({
|
|
1625
2186
|
entities: input.entities.map((handle) => handle.schema),
|
|
1626
2187
|
singletons: input.singletons.map((handle) => handle.schema),
|
|
1627
|
-
tests: input.tests.map((ripploTest) => ripploTest.spec),
|
|
1628
2188
|
valueSpaces: dedupeByName([
|
|
1629
2189
|
...input.entities.flatMap((handle) => handle.valueSpaces),
|
|
1630
2190
|
...input.singletons.flatMap((handle) => handle.valueSpaces)
|
|
1631
|
-
])
|
|
2191
|
+
]),
|
|
2192
|
+
workflows: input.workflows.map(
|
|
2193
|
+
(ripploWorkflow) => expandWorkflow(ripploWorkflow.spec, { singletonValues: singletonValuesOf(input.singletons) })
|
|
2194
|
+
)
|
|
1632
2195
|
});
|
|
1633
2196
|
assertNoContradictions(lockfile);
|
|
1634
2197
|
return lockfile;
|
|
@@ -1645,20 +2208,20 @@ function dedupeByName(spaces) {
|
|
|
1645
2208
|
return [...byName.values()];
|
|
1646
2209
|
}
|
|
1647
2210
|
function assertNoContradictions(lockfile) {
|
|
1648
|
-
lockfile.
|
|
1649
|
-
assertNoContradiction(
|
|
1650
|
-
assertNoDanglingRefs(
|
|
2211
|
+
lockfile.workflows.forEach((workflow2) => {
|
|
2212
|
+
assertNoContradiction(workflow2);
|
|
2213
|
+
assertNoDanglingRefs(workflow2);
|
|
1651
2214
|
});
|
|
1652
2215
|
}
|
|
1653
|
-
function assertNoContradiction(
|
|
1654
|
-
const setups = [...
|
|
1655
|
-
|
|
2216
|
+
function assertNoContradiction(workflow2) {
|
|
2217
|
+
const setups = [...workflow2.world, ...workflow2.maybe];
|
|
2218
|
+
workflow2.absent.forEach((absence) => {
|
|
1656
2219
|
const clash = setups.find(
|
|
1657
2220
|
(setup) => setup.entity === absence.entity && whereMatches(absence.where, setup.set)
|
|
1658
2221
|
);
|
|
1659
2222
|
if (clash != null) {
|
|
1660
2223
|
throw new Error(
|
|
1661
|
-
`test "${
|
|
2224
|
+
`test "${workflow2.name}": creates a "${absence.entity}" ("${clash.as}") that a none(${absence.entity}, \u2026) in its world forbids`
|
|
1662
2225
|
);
|
|
1663
2226
|
}
|
|
1664
2227
|
});
|
|
@@ -1669,19 +2232,19 @@ function whereMatches(where, set) {
|
|
|
1669
2232
|
function sameValue(a, b) {
|
|
1670
2233
|
return b !== void 0 && sameSetValue(a, b);
|
|
1671
2234
|
}
|
|
1672
|
-
function assertNoDanglingRefs(
|
|
1673
|
-
const aliases = new Set([...
|
|
1674
|
-
const paramKeys = new Set(Object.keys(
|
|
2235
|
+
function assertNoDanglingRefs(workflow2) {
|
|
2236
|
+
const aliases = new Set([...workflow2.world, ...workflow2.maybe].map((setup) => setup.as));
|
|
2237
|
+
const paramKeys = new Set(Object.keys(workflow2.params));
|
|
1675
2238
|
const fieldSets = [
|
|
1676
|
-
...
|
|
1677
|
-
...
|
|
1678
|
-
...
|
|
2239
|
+
...workflow2.world.map((setup) => setup.set),
|
|
2240
|
+
...workflow2.maybe.map((setup) => setup.set),
|
|
2241
|
+
...workflow2.absent.map((absence) => absence.where)
|
|
1679
2242
|
];
|
|
1680
2243
|
fieldSets.forEach((set) => {
|
|
1681
|
-
assertSetRefs(
|
|
2244
|
+
assertSetRefs(workflow2.name, set, aliases, paramKeys);
|
|
1682
2245
|
});
|
|
1683
2246
|
}
|
|
1684
|
-
function assertSetRefs(
|
|
2247
|
+
function assertSetRefs(workflowName, set, aliases, paramKeys) {
|
|
1685
2248
|
Object.values(set).forEach((value2) => {
|
|
1686
2249
|
if (!isRef(value2) || paramKeys.has(value2.ref)) {
|
|
1687
2250
|
return;
|
|
@@ -1693,7 +2256,7 @@ function assertSetRefs(testName, set, aliases, paramKeys) {
|
|
|
1693
2256
|
const aliasPath = value2.ref.slice(0, lastDot);
|
|
1694
2257
|
if (!aliases.has(aliasPath)) {
|
|
1695
2258
|
throw new Error(
|
|
1696
|
-
`test "${
|
|
2259
|
+
`test "${workflowName}": ref "${value2.ref}" points at unknown alias "${aliasPath}"`
|
|
1697
2260
|
);
|
|
1698
2261
|
}
|
|
1699
2262
|
});
|
|
@@ -1709,10 +2272,10 @@ function createRipplo(input) {
|
|
|
1709
2272
|
lockfile: buildLockfile({
|
|
1710
2273
|
entities: input.entities,
|
|
1711
2274
|
singletons: input.singletons,
|
|
1712
|
-
|
|
2275
|
+
workflows: input.workflows
|
|
1713
2276
|
}),
|
|
1714
2277
|
singletons: input.singletons,
|
|
1715
|
-
|
|
2278
|
+
workflows: input.workflows
|
|
1716
2279
|
};
|
|
1717
2280
|
}
|
|
1718
2281
|
export {
|
|
@@ -1723,6 +2286,7 @@ export {
|
|
|
1723
2286
|
and,
|
|
1724
2287
|
arbitrary,
|
|
1725
2288
|
banner,
|
|
2289
|
+
branch,
|
|
1726
2290
|
button,
|
|
1727
2291
|
cell,
|
|
1728
2292
|
changed,
|
|
@@ -1784,7 +2348,6 @@ export {
|
|
|
1784
2348
|
table,
|
|
1785
2349
|
tablist,
|
|
1786
2350
|
tabpanel,
|
|
1787
|
-
test,
|
|
1788
2351
|
testId,
|
|
1789
2352
|
text,
|
|
1790
2353
|
textbox,
|
|
@@ -1799,5 +2362,6 @@ export {
|
|
|
1799
2362
|
viewport,
|
|
1800
2363
|
visible,
|
|
1801
2364
|
when,
|
|
1802
|
-
within
|
|
2365
|
+
within,
|
|
2366
|
+
workflow
|
|
1803
2367
|
};
|