@midscene/test 1.12.4-beta-20260904041024.0 → 1.12.4
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/dist/es/android/index.mjs +50 -106
- package/dist/es/android/index.mjs.map +1 -1
- package/dist/es/cli/index.mjs +99 -149
- package/dist/es/cli/index.mjs.map +1 -1
- package/dist/es/harmony/index.mjs +119 -0
- package/dist/es/harmony/index.mjs.map +1 -0
- package/dist/es/index.mjs +36 -87
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/ios/index.mjs +51 -101
- package/dist/es/ios/index.mjs.map +1 -1
- package/dist/es/midscene/index.mjs +37 -235
- package/dist/es/midscene/index.mjs.map +1 -1
- package/dist/es/playwright/index.mjs +14 -10
- package/dist/es/playwright/index.mjs.map +1 -1
- package/dist/es/test-cli.mjs +102 -150
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/android/index.js +151 -178
- package/dist/lib/android/index.js.map +1 -1
- package/dist/lib/cli/index.js +99 -149
- package/dist/lib/cli/index.js.map +1 -1
- package/dist/lib/harmony/index.js +188 -0
- package/dist/lib/harmony/index.js.map +1 -0
- package/dist/lib/index.js +37 -100
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/ios/index.js +152 -173
- package/dist/lib/ios/index.js.map +1 -1
- package/dist/lib/midscene/index.js +264 -412
- package/dist/lib/midscene/index.js.map +1 -1
- package/dist/lib/playwright/index.js +14 -10
- package/dist/lib/playwright/index.js.map +1 -1
- package/dist/lib/test-cli.js +102 -150
- package/dist/lib/test-cli.js.map +1 -1
- package/dist/types/android/index.d.ts +27 -101
- package/dist/types/cli/index.d.ts +9 -32
- package/dist/types/harmony/index.d.ts +124 -0
- package/dist/types/index.d.ts +18 -45
- package/dist/types/ios/index.d.ts +28 -107
- package/dist/types/midscene/index.d.ts +54 -153
- package/dist/types/playwright/index.d.ts +9 -63
- package/package.json +25 -6
package/dist/lib/cli/index.js
CHANGED
|
@@ -235,7 +235,15 @@ const validateDefinition = (options)=>{
|
|
|
235
235
|
if ('string' != typeof options.name || 0 === options.name.trim().length) throw new NodeDefinitionError('Node name must be a non-empty string.');
|
|
236
236
|
validateOptionalText(options.title, 'title', options.name);
|
|
237
237
|
validateOptionalText(options.description, "description", options.name);
|
|
238
|
+
if (void 0 !== options.stringInputKey && false !== options.stringInputKey && ('string' != typeof options.stringInputKey || 0 === options.stringInputKey.trim().length)) throw new NodeDefinitionError(`Node "${options.name}" stringInputKey must be a non-empty string or false.`, {
|
|
239
|
+
node: options.name,
|
|
240
|
+
field: 'stringInputKey'
|
|
241
|
+
});
|
|
238
242
|
validateInputSchema(options.inputSchema, options.name);
|
|
243
|
+
if ('string' == typeof options.stringInputKey && options.inputSchema instanceof v4_namespaceObject.z.ZodObject && !(options.stringInputKey in options.inputSchema.shape)) throw new NodeDefinitionError(`Node "${options.name}" stringInputKey must reference a field in inputSchema.`, {
|
|
244
|
+
node: options.name,
|
|
245
|
+
field: 'stringInputKey'
|
|
246
|
+
});
|
|
239
247
|
if ('function' != typeof options.execute) throw new NodeDefinitionError(`Node "${options.name}" must provide an execute function.`, {
|
|
240
248
|
node: options.name
|
|
241
249
|
});
|
|
@@ -398,10 +406,11 @@ const platforms = new Set([
|
|
|
398
406
|
'web',
|
|
399
407
|
'android',
|
|
400
408
|
'ios',
|
|
409
|
+
'harmony',
|
|
401
410
|
'computer'
|
|
402
411
|
]);
|
|
403
412
|
const validatePlatform = (value, label)=>{
|
|
404
|
-
if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, computer.`);
|
|
413
|
+
if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, harmony, computer.`);
|
|
405
414
|
return value;
|
|
406
415
|
};
|
|
407
416
|
const validateProjectSetup = (value, projectPlatform, label)=>{
|
|
@@ -696,88 +705,6 @@ const createProjectRuntime = (options)=>{
|
|
|
696
705
|
}
|
|
697
706
|
};
|
|
698
707
|
};
|
|
699
|
-
const supportedMetaKeys = new Set([
|
|
700
|
-
'timeout',
|
|
701
|
-
'continue-on-error'
|
|
702
|
-
]);
|
|
703
|
-
function isMapping(value) {
|
|
704
|
-
return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
|
|
705
|
-
}
|
|
706
|
-
function formatStep(index) {
|
|
707
|
-
return `step ${index + 1}`;
|
|
708
|
-
}
|
|
709
|
-
function normalizeMeta(value, index) {
|
|
710
|
-
if (void 0 === value) return {
|
|
711
|
-
continueOnError: false
|
|
712
|
-
};
|
|
713
|
-
if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
|
|
714
|
-
index
|
|
715
|
-
});
|
|
716
|
-
for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
|
|
717
|
-
index,
|
|
718
|
-
key
|
|
719
|
-
});
|
|
720
|
-
const timeout = value.timeout;
|
|
721
|
-
if (void 0 !== timeout && ('number' != typeof timeout || !Number.isFinite(timeout) || timeout <= 0)) throw new WorkflowParseError(`${formatStep(index)} "$.timeout" must be a positive number of milliseconds.`, {
|
|
722
|
-
index,
|
|
723
|
-
timeout
|
|
724
|
-
});
|
|
725
|
-
const continueOnError = value['continue-on-error'];
|
|
726
|
-
if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
|
|
727
|
-
index,
|
|
728
|
-
continueOnError
|
|
729
|
-
});
|
|
730
|
-
return {
|
|
731
|
-
...void 0 === timeout ? {} : {
|
|
732
|
-
timeoutMs: timeout
|
|
733
|
-
},
|
|
734
|
-
continueOnError: continueOnError ?? false
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
function validateCommonNodeInput(input, index) {
|
|
738
|
-
if (void 0 !== input.prompt && 'string' != typeof input.prompt) throw new WorkflowParseError(`${formatStep(index)} "prompt" must be a string.`, {
|
|
739
|
-
index,
|
|
740
|
-
prompt: input.prompt
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
function normalizeStep(value, index = 0) {
|
|
744
|
-
if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
|
|
745
|
-
index
|
|
746
|
-
});
|
|
747
|
-
const entries = Object.entries(value);
|
|
748
|
-
if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
|
|
749
|
-
index,
|
|
750
|
-
nodes: Object.keys(value)
|
|
751
|
-
});
|
|
752
|
-
const [node, rawValue] = entries[0];
|
|
753
|
-
if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
|
|
754
|
-
index
|
|
755
|
-
});
|
|
756
|
-
if ('string' == typeof rawValue) return {
|
|
757
|
-
node,
|
|
758
|
-
input: {
|
|
759
|
-
prompt: rawValue
|
|
760
|
-
},
|
|
761
|
-
meta: {
|
|
762
|
-
continueOnError: false
|
|
763
|
-
}
|
|
764
|
-
};
|
|
765
|
-
if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
|
|
766
|
-
index,
|
|
767
|
-
node
|
|
768
|
-
});
|
|
769
|
-
const { $: rawMeta, ...input } = rawValue;
|
|
770
|
-
validateCommonNodeInput(input, index);
|
|
771
|
-
return {
|
|
772
|
-
node,
|
|
773
|
-
input,
|
|
774
|
-
meta: normalizeMeta(rawMeta, index)
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
function normalizeSteps(steps) {
|
|
778
|
-
if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
|
|
779
|
-
return steps.map((step, index)=>normalizeStep(step, index));
|
|
780
|
-
}
|
|
781
708
|
const assertJsonSerializable = (value, label, node)=>{
|
|
782
709
|
const visit = (candidate, path, seen)=>{
|
|
783
710
|
if (null === candidate || 'string' == typeof candidate || 'boolean' == typeof candidate) return;
|
|
@@ -800,45 +727,6 @@ const assertJsonSerializable = (value, label, node)=>{
|
|
|
800
727
|
throw new NodeExecutionError(node, error);
|
|
801
728
|
}
|
|
802
729
|
};
|
|
803
|
-
const cloneAndFreezeJson = (value)=>{
|
|
804
|
-
if (Array.isArray(value)) return Object.freeze(value.map(cloneAndFreezeJson));
|
|
805
|
-
if ('object' == typeof value && null !== value) return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child])=>[
|
|
806
|
-
key,
|
|
807
|
-
cloneAndFreezeJson(child)
|
|
808
|
-
])));
|
|
809
|
-
return value;
|
|
810
|
-
};
|
|
811
|
-
const toHistoryEntry = (result, scope)=>{
|
|
812
|
-
assertJsonSerializable(result.input, 'input', result.node);
|
|
813
|
-
if (result.output?.data !== void 0) assertJsonSerializable(result.output.data, 'output.data', result.node);
|
|
814
|
-
return Object.freeze({
|
|
815
|
-
scope,
|
|
816
|
-
phase: result.phase,
|
|
817
|
-
stepIndex: result.stepIndex,
|
|
818
|
-
node: result.node,
|
|
819
|
-
input: cloneAndFreezeJson(result.input),
|
|
820
|
-
...'string' == typeof result.input?.prompt ? {
|
|
821
|
-
intent: result.input.prompt
|
|
822
|
-
} : {},
|
|
823
|
-
status: 'success' === result.status ? 'passed' : 'failed',
|
|
824
|
-
...result.output?.summary === void 0 ? {} : {
|
|
825
|
-
summary: result.output.summary
|
|
826
|
-
},
|
|
827
|
-
...result.output?.data === void 0 ? {} : {
|
|
828
|
-
data: cloneAndFreezeJson(result.output.data)
|
|
829
|
-
},
|
|
830
|
-
...result.error ? {
|
|
831
|
-
error: Object.freeze({
|
|
832
|
-
name: result.error.name,
|
|
833
|
-
message: result.error.message
|
|
834
|
-
})
|
|
835
|
-
} : {}
|
|
836
|
-
});
|
|
837
|
-
};
|
|
838
|
-
const createHistory = (prefix, results, scope)=>Object.freeze([
|
|
839
|
-
...prefix,
|
|
840
|
-
...results.map((result)=>toHistoryEntry(result, scope))
|
|
841
|
-
]);
|
|
842
730
|
function validateNodeOutput(output, node) {
|
|
843
731
|
if (void 0 === output) return;
|
|
844
732
|
if ('object' != typeof output || null === output || Array.isArray(output)) throw new NodeExecutionError(node, new TypeError('Node output must be an object or undefined.'));
|
|
@@ -860,7 +748,6 @@ function createStepResultBase(step, startedAt, phase, stepIndex) {
|
|
|
860
748
|
};
|
|
861
749
|
}
|
|
862
750
|
async function executeNode(step, execute, phase, stepIndex, options) {
|
|
863
|
-
validateCommonNodeInput(step.input, stepIndex);
|
|
864
751
|
const startedAt = new Date();
|
|
865
752
|
const abortController = new AbortController();
|
|
866
753
|
const parentSignal = options.parentSignal;
|
|
@@ -944,7 +831,6 @@ async function executeStep(step, node, target, context, execution = {}) {
|
|
|
944
831
|
$: step.meta,
|
|
945
832
|
signal,
|
|
946
833
|
context,
|
|
947
|
-
history: execution.history ?? Object.freeze([]),
|
|
948
834
|
onTeardown: (teardown)=>{
|
|
949
835
|
if (!execution.onTeardown) throw new NodeExecutionError(step.node, new Error('The current execution scope cannot register teardown.'));
|
|
950
836
|
execution.onTeardown(step.node, teardown);
|
|
@@ -981,7 +867,6 @@ function createDocumentRuntime(document, options) {
|
|
|
981
867
|
const startedAt = new Date();
|
|
982
868
|
const beforeAll = [];
|
|
983
869
|
const afterAll = [];
|
|
984
|
-
const completedNodes = [];
|
|
985
870
|
const nodes = {
|
|
986
871
|
beforeAll: document.lifecycle.beforeAll.map((step)=>options.resolveNode(step.node)),
|
|
987
872
|
afterAll: document.lifecycle.afterAll.map((step)=>options.resolveNode(step.node))
|
|
@@ -1012,7 +897,7 @@ function createDocumentRuntime(document, options) {
|
|
|
1012
897
|
projectId: document.projectId,
|
|
1013
898
|
projectName: project.name,
|
|
1014
899
|
sourcePath: document.sourcePath,
|
|
1015
|
-
status:
|
|
900
|
+
status: beforeAll.some((step)=>'failed' === step.status) || afterAll.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
1016
901
|
startedAt: startedAt.toISOString(),
|
|
1017
902
|
endedAt: endedAt.toISOString(),
|
|
1018
903
|
durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
|
|
@@ -1041,10 +926,7 @@ function createDocumentRuntime(document, options) {
|
|
|
1041
926
|
projectName: project.name,
|
|
1042
927
|
sourcePath: document.sourcePath,
|
|
1043
928
|
phase,
|
|
1044
|
-
stepIndex
|
|
1045
|
-
completedNodes: Object.freeze([
|
|
1046
|
-
...completedNodes
|
|
1047
|
-
])
|
|
929
|
+
stepIndex
|
|
1048
930
|
};
|
|
1049
931
|
const stepInfo = {
|
|
1050
932
|
scope: 'document',
|
|
@@ -1057,7 +939,6 @@ function createDocumentRuntime(document, options) {
|
|
|
1057
939
|
scope: 'document',
|
|
1058
940
|
document: documentContext
|
|
1059
941
|
}, context, {
|
|
1060
|
-
history: createHistory([], completedNodes, 'document'),
|
|
1061
942
|
signal: executionSignal,
|
|
1062
943
|
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
1063
944
|
onTeardown (node, teardown) {
|
|
@@ -1080,7 +961,6 @@ function createDocumentRuntime(document, options) {
|
|
|
1080
961
|
});
|
|
1081
962
|
await options.onStepResult?.(stepInfo, result);
|
|
1082
963
|
results.push(result);
|
|
1083
|
-
completedNodes.push(result);
|
|
1084
964
|
if ('failed' === result.status && !result.continuedAfterError) break;
|
|
1085
965
|
}
|
|
1086
966
|
};
|
|
@@ -1088,9 +968,6 @@ function createDocumentRuntime(document, options) {
|
|
|
1088
968
|
get context () {
|
|
1089
969
|
return context;
|
|
1090
970
|
},
|
|
1091
|
-
get history () {
|
|
1092
|
-
return createHistory([], completedNodes, 'document');
|
|
1093
|
-
},
|
|
1094
971
|
get canRunCases () {
|
|
1095
972
|
return started && !signal.aborted && !beforeAll.some((step)=>'failed' === step.status);
|
|
1096
973
|
},
|
|
@@ -1150,7 +1027,6 @@ async function runCollectedCase(collectedCase, options) {
|
|
|
1150
1027
|
const beforeEach = [];
|
|
1151
1028
|
const steps = [];
|
|
1152
1029
|
const afterEach = [];
|
|
1153
|
-
const completedNodes = [];
|
|
1154
1030
|
const teardownStack = [];
|
|
1155
1031
|
let acceptingTeardowns = true;
|
|
1156
1032
|
const onTeardown = (node, teardown)=>{
|
|
@@ -1179,13 +1055,7 @@ async function runCollectedCase(collectedCase, options) {
|
|
|
1179
1055
|
sourcePath: collectedCase.sourcePath,
|
|
1180
1056
|
caseIndex: collectedCase.caseIndex,
|
|
1181
1057
|
phase,
|
|
1182
|
-
stepIndex
|
|
1183
|
-
completedSteps: Object.freeze([
|
|
1184
|
-
...steps
|
|
1185
|
-
]),
|
|
1186
|
-
completedNodes: Object.freeze([
|
|
1187
|
-
...completedNodes
|
|
1188
|
-
])
|
|
1058
|
+
stepIndex
|
|
1189
1059
|
};
|
|
1190
1060
|
const stepInfo = {
|
|
1191
1061
|
scope: 'case',
|
|
@@ -1198,14 +1068,12 @@ async function runCollectedCase(collectedCase, options) {
|
|
|
1198
1068
|
scope: 'case',
|
|
1199
1069
|
case: caseContext
|
|
1200
1070
|
}, options.context, {
|
|
1201
|
-
history: createHistory(options.documentHistory ?? [], completedNodes, 'case'),
|
|
1202
1071
|
signal,
|
|
1203
1072
|
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
1204
1073
|
onTeardown
|
|
1205
1074
|
});
|
|
1206
1075
|
await options.onStepResult?.(stepInfo, result);
|
|
1207
1076
|
results.push(result);
|
|
1208
|
-
completedNodes.push(result);
|
|
1209
1077
|
if ('failed' === result.status && !result.continuedAfterError) break;
|
|
1210
1078
|
}
|
|
1211
1079
|
};
|
|
@@ -1245,7 +1113,7 @@ async function runCollectedCase(collectedCase, options) {
|
|
|
1245
1113
|
name: collectedCase.definition.name,
|
|
1246
1114
|
sourcePath: collectedCase.sourcePath,
|
|
1247
1115
|
caseIndex: collectedCase.caseIndex,
|
|
1248
|
-
status:
|
|
1116
|
+
status: beforeEach.some((step)=>'failed' === step.status) || steps.some((step)=>'failed' === step.status) || afterEach.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
1249
1117
|
beforeEach,
|
|
1250
1118
|
steps,
|
|
1251
1119
|
afterEach,
|
|
@@ -1315,7 +1183,6 @@ async function runWorkflowDocument(document, options) {
|
|
|
1315
1183
|
context: runtime.context,
|
|
1316
1184
|
projectName,
|
|
1317
1185
|
attemptIndex,
|
|
1318
|
-
documentHistory: runtime.history,
|
|
1319
1186
|
signal: options.signal,
|
|
1320
1187
|
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
1321
1188
|
onStepStart: options.onStepStart,
|
|
@@ -1362,6 +1229,89 @@ async function runWorkflowDocument(document, options) {
|
|
|
1362
1229
|
};
|
|
1363
1230
|
}
|
|
1364
1231
|
const external_js_yaml_namespaceObject = require("js-yaml");
|
|
1232
|
+
const supportedMetaKeys = new Set([
|
|
1233
|
+
'timeout',
|
|
1234
|
+
'continue-on-error'
|
|
1235
|
+
]);
|
|
1236
|
+
function isMapping(value) {
|
|
1237
|
+
return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
|
|
1238
|
+
}
|
|
1239
|
+
function formatStep(index) {
|
|
1240
|
+
return `step ${index + 1}`;
|
|
1241
|
+
}
|
|
1242
|
+
function normalizeMeta(value, index) {
|
|
1243
|
+
if (void 0 === value) return {
|
|
1244
|
+
continueOnError: false
|
|
1245
|
+
};
|
|
1246
|
+
if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
|
|
1247
|
+
index
|
|
1248
|
+
});
|
|
1249
|
+
for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
|
|
1250
|
+
index,
|
|
1251
|
+
key
|
|
1252
|
+
});
|
|
1253
|
+
const timeout = value.timeout;
|
|
1254
|
+
if (void 0 !== timeout && ('number' != typeof timeout || !Number.isFinite(timeout) || timeout <= 0)) throw new WorkflowParseError(`${formatStep(index)} "$.timeout" must be a positive number of milliseconds.`, {
|
|
1255
|
+
index,
|
|
1256
|
+
timeout
|
|
1257
|
+
});
|
|
1258
|
+
const continueOnError = value['continue-on-error'];
|
|
1259
|
+
if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
|
|
1260
|
+
index,
|
|
1261
|
+
continueOnError
|
|
1262
|
+
});
|
|
1263
|
+
return {
|
|
1264
|
+
...void 0 === timeout ? {} : {
|
|
1265
|
+
timeoutMs: timeout
|
|
1266
|
+
},
|
|
1267
|
+
continueOnError: continueOnError ?? false
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
function normalizeStep(value, index = 0, resolveNode) {
|
|
1271
|
+
if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
|
|
1272
|
+
index
|
|
1273
|
+
});
|
|
1274
|
+
const entries = Object.entries(value);
|
|
1275
|
+
if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
|
|
1276
|
+
index,
|
|
1277
|
+
nodes: Object.keys(value)
|
|
1278
|
+
});
|
|
1279
|
+
const [node, rawValue] = entries[0];
|
|
1280
|
+
if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
|
|
1281
|
+
index
|
|
1282
|
+
});
|
|
1283
|
+
if ('string' == typeof rawValue) {
|
|
1284
|
+
const resolvedNode = resolveNode?.(node);
|
|
1285
|
+
const stringInputKey = resolvedNode ? resolvedNode.stringInputKey : 'prompt';
|
|
1286
|
+
if (false === stringInputKey || void 0 === stringInputKey) throw new WorkflowParseError(`${formatStep(index)} node "${node}" does not accept string shorthand.`, {
|
|
1287
|
+
index,
|
|
1288
|
+
node
|
|
1289
|
+
});
|
|
1290
|
+
return {
|
|
1291
|
+
node,
|
|
1292
|
+
input: {
|
|
1293
|
+
[stringInputKey]: rawValue
|
|
1294
|
+
},
|
|
1295
|
+
meta: {
|
|
1296
|
+
continueOnError: false
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
|
|
1301
|
+
index,
|
|
1302
|
+
node
|
|
1303
|
+
});
|
|
1304
|
+
const { $: rawMeta, ...input } = rawValue;
|
|
1305
|
+
return {
|
|
1306
|
+
node,
|
|
1307
|
+
input,
|
|
1308
|
+
meta: normalizeMeta(rawMeta, index)
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function normalizeSteps(steps, resolveNode) {
|
|
1312
|
+
if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
|
|
1313
|
+
return steps.map((step, index)=>normalizeStep(step, index, resolveNode));
|
|
1314
|
+
}
|
|
1365
1315
|
const interpolationExpression = /\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}|\$\{([^{}]+)\}/g;
|
|
1366
1316
|
const exactEnvExpression = /^\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}$/;
|
|
1367
1317
|
const exactProjectExpression = /^\$\{([^{}]+)\}$/;
|
|
@@ -1465,7 +1415,7 @@ function collectWorkflowDocument(source, options) {
|
|
|
1465
1415
|
if (!Array.isArray(value)) throw new WorkflowParseError(`Workflow document ${phase} must be an array.`, {
|
|
1466
1416
|
phase
|
|
1467
1417
|
});
|
|
1468
|
-
return normalizeSteps(value).map((normalized, stepIndex)=>{
|
|
1418
|
+
return normalizeSteps(value, options.resolveNode).map((normalized, stepIndex)=>{
|
|
1469
1419
|
const resolved = options.resolveNode(normalized.node);
|
|
1470
1420
|
if (!resolved) throw new WorkflowParseError(`Workflow document ${phase} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
|
|
1471
1421
|
phase,
|
|
@@ -1501,7 +1451,7 @@ function collectWorkflowDocument(source, options) {
|
|
|
1501
1451
|
if (!Array.isArray(tags) || tags.some((tag)=>'string' != typeof tag || 0 === tag.trim().length)) throw new WorkflowParseError(`Case ${caseIndex + 1} tags must be an array of non-empty strings.`, {
|
|
1502
1452
|
caseIndex
|
|
1503
1453
|
});
|
|
1504
|
-
const steps = normalizeSteps(definition.steps).map((normalized, stepIndex)=>{
|
|
1454
|
+
const steps = normalizeSteps(definition.steps, options.resolveNode).map((normalized, stepIndex)=>{
|
|
1505
1455
|
if (!options.resolveNode(normalized.node)) throw new WorkflowParseError(`Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
|
|
1506
1456
|
caseIndex,
|
|
1507
1457
|
stepIndex,
|