@withone/cli 1.23.0 → 1.24.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.
|
@@ -447,6 +447,22 @@ var execAsync = promisify(exec);
|
|
|
447
447
|
function sleep2(ms) {
|
|
448
448
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
449
449
|
}
|
|
450
|
+
var StepTimeoutError = class extends Error {
|
|
451
|
+
errorCode = "TIMEOUT";
|
|
452
|
+
constructor(stepId, timeoutMs) {
|
|
453
|
+
super(`Step "${stepId}" exceeded timeout of ${timeoutMs}ms`);
|
|
454
|
+
this.name = "StepTimeoutError";
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
function withTimeout(promise, timeoutMs, stepId) {
|
|
458
|
+
let timer;
|
|
459
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
460
|
+
timer = setTimeout(() => reject(new StepTimeoutError(stepId, timeoutMs)), timeoutMs);
|
|
461
|
+
});
|
|
462
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
463
|
+
if (timer) clearTimeout(timer);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
450
466
|
function computeRetryDelay(onError, attempt) {
|
|
451
467
|
const base = onError.retryDelayMs ?? 1e3;
|
|
452
468
|
const max = onError.maxDelayMs ?? 3e4;
|
|
@@ -898,7 +914,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
|
|
|
898
914
|
if (flowStack.includes(resolvedKey)) {
|
|
899
915
|
throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
|
|
900
916
|
}
|
|
901
|
-
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-
|
|
917
|
+
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-LCDHSLUT.js");
|
|
902
918
|
const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
|
|
903
919
|
const subContext = await executeFlow(
|
|
904
920
|
subFlow,
|
|
@@ -910,10 +926,35 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
|
|
|
910
926
|
void 0,
|
|
911
927
|
[...flowStack, resolvedKey]
|
|
912
928
|
);
|
|
929
|
+
const finalStep = subFlow.steps[subFlow.steps.length - 1];
|
|
930
|
+
const finalOutput = finalStep ? subContext.steps[finalStep.id]?.output : void 0;
|
|
931
|
+
let flattenedOutput;
|
|
932
|
+
if (finalOutput && typeof finalOutput === "object" && !Array.isArray(finalOutput)) {
|
|
933
|
+
const collisions = Object.keys(finalOutput).filter(
|
|
934
|
+
(k) => k in subContext.steps
|
|
935
|
+
);
|
|
936
|
+
if (collisions.length > 0) {
|
|
937
|
+
options.onEvent?.({
|
|
938
|
+
event: "flow:warning",
|
|
939
|
+
message: `Sub-flow "${resolvedKey}" final step output fields [${collisions.join(", ")}] collide with sub-step ids \u2014 flattened fields take precedence.`
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
flattenedOutput = {
|
|
943
|
+
...subContext.steps,
|
|
944
|
+
...finalOutput,
|
|
945
|
+
_steps: subContext.steps
|
|
946
|
+
};
|
|
947
|
+
} else {
|
|
948
|
+
flattenedOutput = {
|
|
949
|
+
...subContext.steps,
|
|
950
|
+
_steps: subContext.steps,
|
|
951
|
+
...finalOutput !== void 0 ? { _finalOutput: finalOutput } : {}
|
|
952
|
+
};
|
|
953
|
+
}
|
|
913
954
|
return {
|
|
914
955
|
status: "success",
|
|
915
|
-
output:
|
|
916
|
-
response:
|
|
956
|
+
output: flattenedOutput,
|
|
957
|
+
response: flattenedOutput
|
|
917
958
|
};
|
|
918
959
|
}
|
|
919
960
|
async function executePaginateStep(step, context, api, permissions, allowedActionIds, options) {
|
|
@@ -1031,47 +1072,37 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
|
|
|
1031
1072
|
context.steps[step.id] = result2;
|
|
1032
1073
|
return result2;
|
|
1033
1074
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
break;
|
|
1066
|
-
case "paginate":
|
|
1067
|
-
result = await executePaginateStep(step, context, api, permissions, allowedActionIds, options);
|
|
1068
|
-
break;
|
|
1069
|
-
case "bash":
|
|
1070
|
-
result = await executeBashStep(step, context, options);
|
|
1071
|
-
break;
|
|
1072
|
-
default:
|
|
1073
|
-
throw new Error(`Unknown step type: ${step.type}`);
|
|
1074
|
-
}
|
|
1075
|
+
const dispatch = async () => {
|
|
1076
|
+
switch (step.type) {
|
|
1077
|
+
case "action":
|
|
1078
|
+
return await executeActionStep(step, context, api, permissions, allowedActionIds);
|
|
1079
|
+
case "transform":
|
|
1080
|
+
return executeTransformStep(step, context);
|
|
1081
|
+
case "code":
|
|
1082
|
+
return await executeCodeStep(step, context, options);
|
|
1083
|
+
case "condition":
|
|
1084
|
+
return await executeConditionStep(step, context, api, permissions, allowedActionIds, options, flowStack);
|
|
1085
|
+
case "loop":
|
|
1086
|
+
return await executeLoopStep(step, context, api, permissions, allowedActionIds, options, flowStack);
|
|
1087
|
+
case "parallel":
|
|
1088
|
+
return await executeParallelStep(step, context, api, permissions, allowedActionIds, options, flowStack);
|
|
1089
|
+
case "file-read":
|
|
1090
|
+
return executeFileReadStep(step, context);
|
|
1091
|
+
case "file-write":
|
|
1092
|
+
return executeFileWriteStep(step, context);
|
|
1093
|
+
case "while":
|
|
1094
|
+
return await executeWhileStep(step, context, api, permissions, allowedActionIds, options, flowStack);
|
|
1095
|
+
case "flow":
|
|
1096
|
+
return await executeSubflowStep(step, context, api, permissions, allowedActionIds, options, flowStack);
|
|
1097
|
+
case "paginate":
|
|
1098
|
+
return await executePaginateStep(step, context, api, permissions, allowedActionIds, options);
|
|
1099
|
+
case "bash":
|
|
1100
|
+
return await executeBashStep(step, context, options);
|
|
1101
|
+
default:
|
|
1102
|
+
throw new Error(`Unknown step type: ${step.type}`);
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
const result = step.timeoutMs ? await withTimeout(dispatch(), step.timeoutMs, step.id) : await dispatch();
|
|
1075
1106
|
result.durationMs = Date.now() - startTime;
|
|
1076
1107
|
if (attempt > 1) {
|
|
1077
1108
|
result.retries = attempt - 1;
|
|
@@ -1093,10 +1124,13 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
|
|
|
1093
1124
|
const errorMessage = lastError?.message || "Unknown error";
|
|
1094
1125
|
const strategy = step.onError?.strategy || "fail";
|
|
1095
1126
|
const retriesUsed = Math.max(0, maxAttempts - 1);
|
|
1127
|
+
const isTimeout = lastError instanceof StepTimeoutError;
|
|
1128
|
+
const errorCode = lastError?.errorCode;
|
|
1096
1129
|
if (strategy === "continue") {
|
|
1097
1130
|
const result = {
|
|
1098
|
-
status: "failed",
|
|
1131
|
+
status: isTimeout ? "timeout" : "failed",
|
|
1099
1132
|
error: errorMessage,
|
|
1133
|
+
...errorCode ? { errorCode } : {},
|
|
1100
1134
|
durationMs: Date.now() - startTime,
|
|
1101
1135
|
retries: retriesUsed
|
|
1102
1136
|
};
|
|
@@ -1105,8 +1139,9 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
|
|
|
1105
1139
|
}
|
|
1106
1140
|
if (strategy === "fallback" && step.onError?.fallbackStepId) {
|
|
1107
1141
|
const result = {
|
|
1108
|
-
status: "failed",
|
|
1142
|
+
status: isTimeout ? "timeout" : "failed",
|
|
1109
1143
|
error: errorMessage,
|
|
1144
|
+
...errorCode ? { errorCode } : {},
|
|
1110
1145
|
durationMs: Date.now() - startTime,
|
|
1111
1146
|
retries: retriesUsed
|
|
1112
1147
|
};
|
|
@@ -1243,7 +1278,8 @@ var FLOW_SCHEMA = {
|
|
|
1243
1278
|
name: { type: "string", required: true, description: "Human-readable step label" },
|
|
1244
1279
|
type: { type: "string", required: true, description: "Step type (determines which config object is required)" },
|
|
1245
1280
|
if: { type: "string", required: false, description: "JS expression \u2014 skip step if falsy" },
|
|
1246
|
-
unless: { type: "string", required: false, description: "JS expression \u2014 skip step if truthy" }
|
|
1281
|
+
unless: { type: "string", required: false, description: "JS expression \u2014 skip step if truthy" },
|
|
1282
|
+
timeoutMs: { type: "number", required: false, description: 'Wall-clock timeout (ms). On expiry the step fails with errorCode:"TIMEOUT"; with onError:continue the result gets status:"timeout".' }
|
|
1247
1283
|
},
|
|
1248
1284
|
stepTypes: [
|
|
1249
1285
|
{
|
|
@@ -1751,6 +1787,39 @@ Every completed step produces both \`output\` and \`response\`:
|
|
|
1751
1787
|
- **Code/transform steps**: \`output\` is the return value. \`response\` is an alias for \`output\`.
|
|
1752
1788
|
- **In practice**: Use \`$.steps.stepId.response\` for action steps (API data) and \`$.steps.stepId.output\` for code/transform steps (computed data). Both work interchangeably, but using the semantically correct one makes flows easier to read.
|
|
1753
1789
|
|
|
1790
|
+
### Step result metadata (\`status\`, \`error\`, \`errorCode\`)
|
|
1791
|
+
|
|
1792
|
+
Every step result also exposes execution metadata that downstream steps can inspect:
|
|
1793
|
+
|
|
1794
|
+
| Field | Values | When set |
|
|
1795
|
+
|-------|--------|----------|
|
|
1796
|
+
| \`$.steps.X.status\` | \`"success"\` \\| \`"skipped"\` \\| \`"failed"\` \\| \`"timeout"\` | Always |
|
|
1797
|
+
| \`$.steps.X.error\` | error message string | When status is \`failed\` or \`timeout\` |
|
|
1798
|
+
| \`$.steps.X.errorCode\` | machine-readable code (e.g. \`"TIMEOUT"\`) | When the error has a code |
|
|
1799
|
+
| \`$.steps.X.durationMs\` | number | Always |
|
|
1800
|
+
| \`$.steps.X.retries\` | number | When the step was retried |
|
|
1801
|
+
|
|
1802
|
+
This lets downstream steps distinguish \`skipped\` (\`if\` condition false) from \`failed\` (error, \`onError:continue\`) from \`timeout\` (exceeded \`timeoutMs\`) \u2014 e.g. \`"if": "$.steps.enrichment.status === 'timeout'"\` to retry with a longer window.
|
|
1803
|
+
|
|
1804
|
+
### Sub-flow output (flattened)
|
|
1805
|
+
|
|
1806
|
+
When a step has \`type: "flow"\`, the sub-flow's final step output is flattened onto the parent step's \`output\`:
|
|
1807
|
+
|
|
1808
|
+
\`\`\`jsonc
|
|
1809
|
+
// Sub-flow "sub-consts" has a final step "load" that returns { CHART_URL, API_KEY }
|
|
1810
|
+
|
|
1811
|
+
// Preferred (flattened):
|
|
1812
|
+
"{{$.steps.loadConfig.output.CHART_URL}}"
|
|
1813
|
+
|
|
1814
|
+
// Legacy nested path (still works for backward compatibility):
|
|
1815
|
+
"{{$.steps.loadConfig.output.load.output.CHART_URL}}"
|
|
1816
|
+
|
|
1817
|
+
// Escape hatch for programmatic access to the full sub-flow steps map:
|
|
1818
|
+
"{{$.steps.loadConfig.output._steps.load.output.CHART_URL}}"
|
|
1819
|
+
\`\`\`
|
|
1820
|
+
|
|
1821
|
+
If a sub-step id collides with a flattened field name, the flattened field wins and the engine emits a \`flow:warning\` event.
|
|
1822
|
+
|
|
1754
1823
|
## Error Handling
|
|
1755
1824
|
|
|
1756
1825
|
\`\`\`json
|
package/dist/index.js
CHANGED