@midscene/test 1.12.4-beta-20260903111111.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/test-cli.js
CHANGED
|
@@ -17,7 +17,7 @@ workflow:
|
|
|
17
17
|
continue-on-error: false
|
|
18
18
|
\`\`\`
|
|
19
19
|
|
|
20
|
-
A string step value
|
|
20
|
+
A Node may map a string step value to one declared input field. Each Node section states whether it supports this shorthand and names the target field.`;
|
|
21
21
|
const sortJsonValue = (value)=>{
|
|
22
22
|
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
23
23
|
if ('object' != typeof value || null === value) return value;
|
|
@@ -65,6 +65,8 @@ const renderNode = (node, warnings)=>{
|
|
|
65
65
|
warnings.push(`node "${node.name}" has no description`);
|
|
66
66
|
sections.push("> Description not declared. Do not infer this node's behavior.");
|
|
67
67
|
}
|
|
68
|
+
if (false === node.stringInputKey || void 0 === node.stringInputKey) sections.push('**String shorthand:** Not supported by this Node.');
|
|
69
|
+
else sections.push(`**String shorthand:** Maps to \`{ ${node.stringInputKey}: value }\`.`);
|
|
68
70
|
sections.push('### Input Schema');
|
|
69
71
|
if (node.inputSchema) sections.push(`\`\`\`json\n${JSON.stringify(toJsonSchema(node), null, 2)}\n\`\`\``);
|
|
70
72
|
else {
|
|
@@ -290,7 +292,15 @@ const validateDefinition = (options)=>{
|
|
|
290
292
|
if ('string' != typeof options.name || 0 === options.name.trim().length) throw new NodeDefinitionError('Node name must be a non-empty string.');
|
|
291
293
|
validateOptionalText(options.title, 'title', options.name);
|
|
292
294
|
validateOptionalText(options.description, "description", options.name);
|
|
295
|
+
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.`, {
|
|
296
|
+
node: options.name,
|
|
297
|
+
field: 'stringInputKey'
|
|
298
|
+
});
|
|
293
299
|
validateInputSchema(options.inputSchema, options.name);
|
|
300
|
+
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.`, {
|
|
301
|
+
node: options.name,
|
|
302
|
+
field: 'stringInputKey'
|
|
303
|
+
});
|
|
294
304
|
if ('function' != typeof options.execute) throw new NodeDefinitionError(`Node "${options.name}" must provide an execute function.`, {
|
|
295
305
|
node: options.name
|
|
296
306
|
});
|
|
@@ -451,10 +461,11 @@ const platforms = new Set([
|
|
|
451
461
|
'web',
|
|
452
462
|
'android',
|
|
453
463
|
'ios',
|
|
464
|
+
'harmony',
|
|
454
465
|
'computer'
|
|
455
466
|
]);
|
|
456
467
|
const validatePlatform = (value, label)=>{
|
|
457
|
-
if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, computer.`);
|
|
468
|
+
if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, harmony, computer.`);
|
|
458
469
|
return value;
|
|
459
470
|
};
|
|
460
471
|
const validateProjectSetup = (value, projectPlatform, label)=>{
|
|
@@ -748,88 +759,6 @@ const createProjectRuntime = (options)=>{
|
|
|
748
759
|
}
|
|
749
760
|
};
|
|
750
761
|
};
|
|
751
|
-
const supportedMetaKeys = new Set([
|
|
752
|
-
'timeout',
|
|
753
|
-
'continue-on-error'
|
|
754
|
-
]);
|
|
755
|
-
function isMapping(value) {
|
|
756
|
-
return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
|
|
757
|
-
}
|
|
758
|
-
function formatStep(index) {
|
|
759
|
-
return `step ${index + 1}`;
|
|
760
|
-
}
|
|
761
|
-
function normalizeMeta(value, index) {
|
|
762
|
-
if (void 0 === value) return {
|
|
763
|
-
continueOnError: false
|
|
764
|
-
};
|
|
765
|
-
if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
|
|
766
|
-
index
|
|
767
|
-
});
|
|
768
|
-
for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
|
|
769
|
-
index,
|
|
770
|
-
key
|
|
771
|
-
});
|
|
772
|
-
const timeout = value.timeout;
|
|
773
|
-
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.`, {
|
|
774
|
-
index,
|
|
775
|
-
timeout
|
|
776
|
-
});
|
|
777
|
-
const continueOnError = value['continue-on-error'];
|
|
778
|
-
if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
|
|
779
|
-
index,
|
|
780
|
-
continueOnError
|
|
781
|
-
});
|
|
782
|
-
return {
|
|
783
|
-
...void 0 === timeout ? {} : {
|
|
784
|
-
timeoutMs: timeout
|
|
785
|
-
},
|
|
786
|
-
continueOnError: continueOnError ?? false
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
function validateCommonNodeInput(input, index) {
|
|
790
|
-
if (void 0 !== input.prompt && 'string' != typeof input.prompt) throw new WorkflowParseError(`${formatStep(index)} "prompt" must be a string.`, {
|
|
791
|
-
index,
|
|
792
|
-
prompt: input.prompt
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
function normalizeStep(value, index = 0) {
|
|
796
|
-
if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
|
|
797
|
-
index
|
|
798
|
-
});
|
|
799
|
-
const entries = Object.entries(value);
|
|
800
|
-
if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
|
|
801
|
-
index,
|
|
802
|
-
nodes: Object.keys(value)
|
|
803
|
-
});
|
|
804
|
-
const [node, rawValue] = entries[0];
|
|
805
|
-
if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
|
|
806
|
-
index
|
|
807
|
-
});
|
|
808
|
-
if ('string' == typeof rawValue) return {
|
|
809
|
-
node,
|
|
810
|
-
input: {
|
|
811
|
-
prompt: rawValue
|
|
812
|
-
},
|
|
813
|
-
meta: {
|
|
814
|
-
continueOnError: false
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
|
-
if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
|
|
818
|
-
index,
|
|
819
|
-
node
|
|
820
|
-
});
|
|
821
|
-
const { $: rawMeta, ...input } = rawValue;
|
|
822
|
-
validateCommonNodeInput(input, index);
|
|
823
|
-
return {
|
|
824
|
-
node,
|
|
825
|
-
input,
|
|
826
|
-
meta: normalizeMeta(rawMeta, index)
|
|
827
|
-
};
|
|
828
|
-
}
|
|
829
|
-
function normalizeSteps(steps) {
|
|
830
|
-
if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
|
|
831
|
-
return steps.map((step, index)=>normalizeStep(step, index));
|
|
832
|
-
}
|
|
833
762
|
const assertJsonSerializable = (value, label, node)=>{
|
|
834
763
|
const visit = (candidate, path, seen)=>{
|
|
835
764
|
if (null === candidate || 'string' == typeof candidate || 'boolean' == typeof candidate) return;
|
|
@@ -852,45 +781,6 @@ const assertJsonSerializable = (value, label, node)=>{
|
|
|
852
781
|
throw new NodeExecutionError(node, error);
|
|
853
782
|
}
|
|
854
783
|
};
|
|
855
|
-
const cloneAndFreezeJson = (value)=>{
|
|
856
|
-
if (Array.isArray(value)) return Object.freeze(value.map(cloneAndFreezeJson));
|
|
857
|
-
if ('object' == typeof value && null !== value) return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child])=>[
|
|
858
|
-
key,
|
|
859
|
-
cloneAndFreezeJson(child)
|
|
860
|
-
])));
|
|
861
|
-
return value;
|
|
862
|
-
};
|
|
863
|
-
const toHistoryEntry = (result, scope)=>{
|
|
864
|
-
assertJsonSerializable(result.input, 'input', result.node);
|
|
865
|
-
if (result.output?.data !== void 0) assertJsonSerializable(result.output.data, 'output.data', result.node);
|
|
866
|
-
return Object.freeze({
|
|
867
|
-
scope,
|
|
868
|
-
phase: result.phase,
|
|
869
|
-
stepIndex: result.stepIndex,
|
|
870
|
-
node: result.node,
|
|
871
|
-
input: cloneAndFreezeJson(result.input),
|
|
872
|
-
...'string' == typeof result.input?.prompt ? {
|
|
873
|
-
intent: result.input.prompt
|
|
874
|
-
} : {},
|
|
875
|
-
status: 'success' === result.status ? 'passed' : 'failed',
|
|
876
|
-
...result.output?.summary === void 0 ? {} : {
|
|
877
|
-
summary: result.output.summary
|
|
878
|
-
},
|
|
879
|
-
...result.output?.data === void 0 ? {} : {
|
|
880
|
-
data: cloneAndFreezeJson(result.output.data)
|
|
881
|
-
},
|
|
882
|
-
...result.error ? {
|
|
883
|
-
error: Object.freeze({
|
|
884
|
-
name: result.error.name,
|
|
885
|
-
message: result.error.message
|
|
886
|
-
})
|
|
887
|
-
} : {}
|
|
888
|
-
});
|
|
889
|
-
};
|
|
890
|
-
const createHistory = (prefix, results, scope)=>Object.freeze([
|
|
891
|
-
...prefix,
|
|
892
|
-
...results.map((result)=>toHistoryEntry(result, scope))
|
|
893
|
-
]);
|
|
894
784
|
function validateNodeOutput(output, node) {
|
|
895
785
|
if (void 0 === output) return;
|
|
896
786
|
if ('object' != typeof output || null === output || Array.isArray(output)) throw new NodeExecutionError(node, new TypeError('Node output must be an object or undefined.'));
|
|
@@ -912,7 +802,6 @@ function createStepResultBase(step, startedAt, phase, stepIndex) {
|
|
|
912
802
|
};
|
|
913
803
|
}
|
|
914
804
|
async function executeNode(step, execute, phase, stepIndex, options) {
|
|
915
|
-
validateCommonNodeInput(step.input, stepIndex);
|
|
916
805
|
const startedAt = new Date();
|
|
917
806
|
const abortController = new AbortController();
|
|
918
807
|
const parentSignal = options.parentSignal;
|
|
@@ -996,7 +885,6 @@ async function executeStep(step, node, target, context, execution = {}) {
|
|
|
996
885
|
$: step.meta,
|
|
997
886
|
signal,
|
|
998
887
|
context,
|
|
999
|
-
history: execution.history ?? Object.freeze([]),
|
|
1000
888
|
onTeardown: (teardown)=>{
|
|
1001
889
|
if (!execution.onTeardown) throw new NodeExecutionError(step.node, new Error('The current execution scope cannot register teardown.'));
|
|
1002
890
|
execution.onTeardown(step.node, teardown);
|
|
@@ -1033,7 +921,6 @@ function createDocumentRuntime(document, options) {
|
|
|
1033
921
|
const startedAt = new Date();
|
|
1034
922
|
const beforeAll = [];
|
|
1035
923
|
const afterAll = [];
|
|
1036
|
-
const completedNodes = [];
|
|
1037
924
|
const nodes = {
|
|
1038
925
|
beforeAll: document.lifecycle.beforeAll.map((step)=>options.resolveNode(step.node)),
|
|
1039
926
|
afterAll: document.lifecycle.afterAll.map((step)=>options.resolveNode(step.node))
|
|
@@ -1064,7 +951,7 @@ function createDocumentRuntime(document, options) {
|
|
|
1064
951
|
projectId: document.projectId,
|
|
1065
952
|
projectName: project.name,
|
|
1066
953
|
sourcePath: document.sourcePath,
|
|
1067
|
-
status:
|
|
954
|
+
status: beforeAll.some((step)=>'failed' === step.status) || afterAll.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
1068
955
|
startedAt: startedAt.toISOString(),
|
|
1069
956
|
endedAt: endedAt.toISOString(),
|
|
1070
957
|
durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
|
|
@@ -1093,10 +980,7 @@ function createDocumentRuntime(document, options) {
|
|
|
1093
980
|
projectName: project.name,
|
|
1094
981
|
sourcePath: document.sourcePath,
|
|
1095
982
|
phase,
|
|
1096
|
-
stepIndex
|
|
1097
|
-
completedNodes: Object.freeze([
|
|
1098
|
-
...completedNodes
|
|
1099
|
-
])
|
|
983
|
+
stepIndex
|
|
1100
984
|
};
|
|
1101
985
|
const stepInfo = {
|
|
1102
986
|
scope: 'document',
|
|
@@ -1109,7 +993,6 @@ function createDocumentRuntime(document, options) {
|
|
|
1109
993
|
scope: 'document',
|
|
1110
994
|
document: documentContext
|
|
1111
995
|
}, context, {
|
|
1112
|
-
history: createHistory([], completedNodes, 'document'),
|
|
1113
996
|
signal: executionSignal,
|
|
1114
997
|
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
1115
998
|
onTeardown (node, teardown) {
|
|
@@ -1132,7 +1015,6 @@ function createDocumentRuntime(document, options) {
|
|
|
1132
1015
|
});
|
|
1133
1016
|
await options.onStepResult?.(stepInfo, result);
|
|
1134
1017
|
results.push(result);
|
|
1135
|
-
completedNodes.push(result);
|
|
1136
1018
|
if ('failed' === result.status && !result.continuedAfterError) break;
|
|
1137
1019
|
}
|
|
1138
1020
|
};
|
|
@@ -1140,9 +1022,6 @@ function createDocumentRuntime(document, options) {
|
|
|
1140
1022
|
get context () {
|
|
1141
1023
|
return context;
|
|
1142
1024
|
},
|
|
1143
|
-
get history () {
|
|
1144
|
-
return createHistory([], completedNodes, 'document');
|
|
1145
|
-
},
|
|
1146
1025
|
get canRunCases () {
|
|
1147
1026
|
return started && !signal.aborted && !beforeAll.some((step)=>'failed' === step.status);
|
|
1148
1027
|
},
|
|
@@ -1202,7 +1081,6 @@ async function runCollectedCase(collectedCase, options) {
|
|
|
1202
1081
|
const beforeEach = [];
|
|
1203
1082
|
const steps = [];
|
|
1204
1083
|
const afterEach = [];
|
|
1205
|
-
const completedNodes = [];
|
|
1206
1084
|
const teardownStack = [];
|
|
1207
1085
|
let acceptingTeardowns = true;
|
|
1208
1086
|
const onTeardown = (node, teardown)=>{
|
|
@@ -1231,13 +1109,7 @@ async function runCollectedCase(collectedCase, options) {
|
|
|
1231
1109
|
sourcePath: collectedCase.sourcePath,
|
|
1232
1110
|
caseIndex: collectedCase.caseIndex,
|
|
1233
1111
|
phase,
|
|
1234
|
-
stepIndex
|
|
1235
|
-
completedSteps: Object.freeze([
|
|
1236
|
-
...steps
|
|
1237
|
-
]),
|
|
1238
|
-
completedNodes: Object.freeze([
|
|
1239
|
-
...completedNodes
|
|
1240
|
-
])
|
|
1112
|
+
stepIndex
|
|
1241
1113
|
};
|
|
1242
1114
|
const stepInfo = {
|
|
1243
1115
|
scope: 'case',
|
|
@@ -1250,14 +1122,12 @@ async function runCollectedCase(collectedCase, options) {
|
|
|
1250
1122
|
scope: 'case',
|
|
1251
1123
|
case: caseContext
|
|
1252
1124
|
}, options.context, {
|
|
1253
|
-
history: createHistory(options.documentHistory ?? [], completedNodes, 'case'),
|
|
1254
1125
|
signal,
|
|
1255
1126
|
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
1256
1127
|
onTeardown
|
|
1257
1128
|
});
|
|
1258
1129
|
await options.onStepResult?.(stepInfo, result);
|
|
1259
1130
|
results.push(result);
|
|
1260
|
-
completedNodes.push(result);
|
|
1261
1131
|
if ('failed' === result.status && !result.continuedAfterError) break;
|
|
1262
1132
|
}
|
|
1263
1133
|
};
|
|
@@ -1297,7 +1167,7 @@ async function runCollectedCase(collectedCase, options) {
|
|
|
1297
1167
|
name: collectedCase.definition.name,
|
|
1298
1168
|
sourcePath: collectedCase.sourcePath,
|
|
1299
1169
|
caseIndex: collectedCase.caseIndex,
|
|
1300
|
-
status:
|
|
1170
|
+
status: beforeEach.some((step)=>'failed' === step.status) || steps.some((step)=>'failed' === step.status) || afterEach.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
1301
1171
|
beforeEach,
|
|
1302
1172
|
steps,
|
|
1303
1173
|
afterEach,
|
|
@@ -1367,7 +1237,6 @@ async function runWorkflowDocument(document, options) {
|
|
|
1367
1237
|
context: runtime.context,
|
|
1368
1238
|
projectName,
|
|
1369
1239
|
attemptIndex,
|
|
1370
|
-
documentHistory: runtime.history,
|
|
1371
1240
|
signal: options.signal,
|
|
1372
1241
|
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
1373
1242
|
onStepStart: options.onStepStart,
|
|
@@ -1414,6 +1283,89 @@ async function runWorkflowDocument(document, options) {
|
|
|
1414
1283
|
};
|
|
1415
1284
|
}
|
|
1416
1285
|
const external_js_yaml_namespaceObject = require("js-yaml");
|
|
1286
|
+
const supportedMetaKeys = new Set([
|
|
1287
|
+
'timeout',
|
|
1288
|
+
'continue-on-error'
|
|
1289
|
+
]);
|
|
1290
|
+
function isMapping(value) {
|
|
1291
|
+
return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
|
|
1292
|
+
}
|
|
1293
|
+
function formatStep(index) {
|
|
1294
|
+
return `step ${index + 1}`;
|
|
1295
|
+
}
|
|
1296
|
+
function normalizeMeta(value, index) {
|
|
1297
|
+
if (void 0 === value) return {
|
|
1298
|
+
continueOnError: false
|
|
1299
|
+
};
|
|
1300
|
+
if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
|
|
1301
|
+
index
|
|
1302
|
+
});
|
|
1303
|
+
for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
|
|
1304
|
+
index,
|
|
1305
|
+
key
|
|
1306
|
+
});
|
|
1307
|
+
const timeout = value.timeout;
|
|
1308
|
+
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.`, {
|
|
1309
|
+
index,
|
|
1310
|
+
timeout
|
|
1311
|
+
});
|
|
1312
|
+
const continueOnError = value['continue-on-error'];
|
|
1313
|
+
if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
|
|
1314
|
+
index,
|
|
1315
|
+
continueOnError
|
|
1316
|
+
});
|
|
1317
|
+
return {
|
|
1318
|
+
...void 0 === timeout ? {} : {
|
|
1319
|
+
timeoutMs: timeout
|
|
1320
|
+
},
|
|
1321
|
+
continueOnError: continueOnError ?? false
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
function normalizeStep(value, index = 0, resolveNode) {
|
|
1325
|
+
if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
|
|
1326
|
+
index
|
|
1327
|
+
});
|
|
1328
|
+
const entries = Object.entries(value);
|
|
1329
|
+
if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
|
|
1330
|
+
index,
|
|
1331
|
+
nodes: Object.keys(value)
|
|
1332
|
+
});
|
|
1333
|
+
const [node, rawValue] = entries[0];
|
|
1334
|
+
if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
|
|
1335
|
+
index
|
|
1336
|
+
});
|
|
1337
|
+
if ('string' == typeof rawValue) {
|
|
1338
|
+
const resolvedNode = resolveNode?.(node);
|
|
1339
|
+
const stringInputKey = resolvedNode ? resolvedNode.stringInputKey : 'prompt';
|
|
1340
|
+
if (false === stringInputKey || void 0 === stringInputKey) throw new WorkflowParseError(`${formatStep(index)} node "${node}" does not accept string shorthand.`, {
|
|
1341
|
+
index,
|
|
1342
|
+
node
|
|
1343
|
+
});
|
|
1344
|
+
return {
|
|
1345
|
+
node,
|
|
1346
|
+
input: {
|
|
1347
|
+
[stringInputKey]: rawValue
|
|
1348
|
+
},
|
|
1349
|
+
meta: {
|
|
1350
|
+
continueOnError: false
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
|
|
1355
|
+
index,
|
|
1356
|
+
node
|
|
1357
|
+
});
|
|
1358
|
+
const { $: rawMeta, ...input } = rawValue;
|
|
1359
|
+
return {
|
|
1360
|
+
node,
|
|
1361
|
+
input,
|
|
1362
|
+
meta: normalizeMeta(rawMeta, index)
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
function normalizeSteps(steps, resolveNode) {
|
|
1366
|
+
if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
|
|
1367
|
+
return steps.map((step, index)=>normalizeStep(step, index, resolveNode));
|
|
1368
|
+
}
|
|
1417
1369
|
const interpolationExpression = /\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}|\$\{([^{}]+)\}/g;
|
|
1418
1370
|
const exactEnvExpression = /^\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}$/;
|
|
1419
1371
|
const exactProjectExpression = /^\$\{([^{}]+)\}$/;
|
|
@@ -1517,7 +1469,7 @@ function collectWorkflowDocument(source, options) {
|
|
|
1517
1469
|
if (!Array.isArray(value)) throw new WorkflowParseError(`Workflow document ${phase} must be an array.`, {
|
|
1518
1470
|
phase
|
|
1519
1471
|
});
|
|
1520
|
-
return normalizeSteps(value).map((normalized, stepIndex)=>{
|
|
1472
|
+
return normalizeSteps(value, options.resolveNode).map((normalized, stepIndex)=>{
|
|
1521
1473
|
const resolved = options.resolveNode(normalized.node);
|
|
1522
1474
|
if (!resolved) throw new WorkflowParseError(`Workflow document ${phase} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
|
|
1523
1475
|
phase,
|
|
@@ -1553,7 +1505,7 @@ function collectWorkflowDocument(source, options) {
|
|
|
1553
1505
|
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.`, {
|
|
1554
1506
|
caseIndex
|
|
1555
1507
|
});
|
|
1556
|
-
const steps = normalizeSteps(definition.steps).map((normalized, stepIndex)=>{
|
|
1508
|
+
const steps = normalizeSteps(definition.steps, options.resolveNode).map((normalized, stepIndex)=>{
|
|
1557
1509
|
if (!options.resolveNode(normalized.node)) throw new WorkflowParseError(`Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
|
|
1558
1510
|
caseIndex,
|
|
1559
1511
|
stepIndex,
|