@withone/cli 1.24.0 → 1.25.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.
|
@@ -914,7 +914,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
|
|
|
914
914
|
if (flowStack.includes(resolvedKey)) {
|
|
915
915
|
throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
|
|
916
916
|
}
|
|
917
|
-
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-
|
|
917
|
+
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-MXRNWG4Y.js");
|
|
918
918
|
const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
|
|
919
919
|
const subContext = await executeFlow(
|
|
920
920
|
subFlow,
|
|
@@ -1027,6 +1027,35 @@ async function executeBashStep(step, context, options) {
|
|
|
1027
1027
|
response: { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0 }
|
|
1028
1028
|
};
|
|
1029
1029
|
}
|
|
1030
|
+
function isMissing(value) {
|
|
1031
|
+
if (value === void 0 || value === null) return true;
|
|
1032
|
+
if (typeof value === "string" && value.length === 0) return true;
|
|
1033
|
+
if (Array.isArray(value) && value.length === 0) return true;
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
function explainMissing(selector, context) {
|
|
1037
|
+
const parts = selector.slice(2).split(".");
|
|
1038
|
+
if (parts[0] !== "steps" || parts.length < 2) return "";
|
|
1039
|
+
const stepId = parts[1].replace(/\[.*$/, "");
|
|
1040
|
+
const upstream = context.steps[stepId];
|
|
1041
|
+
if (!upstream) return ` (upstream step "${stepId}" has not run)`;
|
|
1042
|
+
if (upstream.status === "skipped") return ` (upstream step "${stepId}" was skipped)`;
|
|
1043
|
+
if (upstream.status === "failed") return ` (upstream step "${stepId}" failed: ${upstream.error ?? "unknown error"})`;
|
|
1044
|
+
if (upstream.status === "timeout") return ` (upstream step "${stepId}" timed out)`;
|
|
1045
|
+
return "";
|
|
1046
|
+
}
|
|
1047
|
+
function checkRequires(step, context) {
|
|
1048
|
+
if (!step.requires || step.requires.length === 0) return;
|
|
1049
|
+
for (const selector of step.requires) {
|
|
1050
|
+
const value = resolveSelector(selector, context);
|
|
1051
|
+
if (isMissing(value)) {
|
|
1052
|
+
const why = explainMissing(selector, context);
|
|
1053
|
+
throw new Error(
|
|
1054
|
+
`Step "${step.id}" requires ${selector} but it resolved to ${value === void 0 ? "undefined" : value === null ? "null" : Array.isArray(value) ? "an empty array" : "an empty string"}${why}`
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1030
1059
|
async function executeSingleStep(step, context, api, permissions, allowedActionIds, options, flowStack = []) {
|
|
1031
1060
|
if (step.if) {
|
|
1032
1061
|
const condResult = evaluateExpression(step.if, context);
|
|
@@ -1060,6 +1089,7 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
|
|
|
1060
1089
|
});
|
|
1061
1090
|
await sleep2(delay);
|
|
1062
1091
|
}
|
|
1092
|
+
checkRequires(step, context);
|
|
1063
1093
|
if (options.mock && (step.type === "action" || step.type === "paginate" || step.type === "bash")) {
|
|
1064
1094
|
const resolvedConfig = step[step.type] ? resolveValue(step[step.type], context) : {};
|
|
1065
1095
|
options.onEvent?.({ event: "step:mock", stepId: step.id, type: step.type, config: resolvedConfig });
|
|
@@ -1279,7 +1309,8 @@ var FLOW_SCHEMA = {
|
|
|
1279
1309
|
type: { type: "string", required: true, description: "Step type (determines which config object is required)" },
|
|
1280
1310
|
if: { type: "string", required: false, description: "JS expression \u2014 skip step if falsy" },
|
|
1281
1311
|
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".' }
|
|
1312
|
+
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".' },
|
|
1313
|
+
requires: { type: "array", required: false, description: "Presence preconditions: array of $.input.X or $.steps.X.output... selectors that must resolve to a non-empty value before the step runs. Failures honor onError." }
|
|
1283
1314
|
},
|
|
1284
1315
|
stepTypes: [
|
|
1285
1316
|
{
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
loadFlowWithMeta,
|
|
17
17
|
resolveFlowPath,
|
|
18
18
|
saveFlow
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-ONGJ2QKB.js";
|
|
20
20
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -2320,6 +2320,20 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2320
2320
|
errors.push({ path: `${path8}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
|
|
2321
2321
|
continue;
|
|
2322
2322
|
}
|
|
2323
|
+
if (s.requires !== void 0) {
|
|
2324
|
+
if (!Array.isArray(s.requires)) {
|
|
2325
|
+
errors.push({ path: `${path8}.requires`, message: '"requires" must be an array of selector strings (e.g. ["$.steps.foo.output.bar"])' });
|
|
2326
|
+
} else {
|
|
2327
|
+
for (let r = 0; r < s.requires.length; r++) {
|
|
2328
|
+
const sel = s.requires[r];
|
|
2329
|
+
if (typeof sel !== "string") {
|
|
2330
|
+
errors.push({ path: `${path8}.requires[${r}]`, message: '"requires" entry must be a selector string' });
|
|
2331
|
+
} else if (!sel.startsWith("$.")) {
|
|
2332
|
+
errors.push({ path: `${path8}.requires[${r}]`, message: `"requires" entry "${sel}" must be a selector starting with "$." (e.g. "$.steps.foo.output.bar")` });
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2323
2337
|
if (s.onError && typeof s.onError === "object") {
|
|
2324
2338
|
const oe = s.onError;
|
|
2325
2339
|
if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
|
|
@@ -2529,6 +2543,14 @@ function validateSelectorReferences(flow2) {
|
|
|
2529
2543
|
function checkStep(step, pathPrefix, preceding2) {
|
|
2530
2544
|
if (step.if) checkSelectors(extractSelectors(step.if), `${pathPrefix}.if`, preceding2);
|
|
2531
2545
|
if (step.unless) checkSelectors(extractSelectors(step.unless), `${pathPrefix}.unless`, preceding2);
|
|
2546
|
+
if (Array.isArray(step.requires)) {
|
|
2547
|
+
const reqs = step.requires;
|
|
2548
|
+
reqs.forEach((sel, i) => {
|
|
2549
|
+
if (typeof sel === "string" && sel.startsWith("$.")) {
|
|
2550
|
+
checkSelectors([sel], `${pathPrefix}.requires[${i}]`, preceding2);
|
|
2551
|
+
}
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2532
2554
|
const descriptor = getStepTypeDescriptor(step.type);
|
|
2533
2555
|
if (descriptor) {
|
|
2534
2556
|
const config2 = step[descriptor.configKey];
|
package/package.json
CHANGED
|
@@ -424,6 +424,35 @@ Alternatively, pass values as environment variables (also shell-safe) and refere
|
|
|
424
424
|
}
|
|
425
425
|
```
|
|
426
426
|
|
|
427
|
+
## Step Input Contracts (`requires`)
|
|
428
|
+
|
|
429
|
+
Declare the data a step depends on so the engine fails fast — with a useful error — when an upstream value is missing. Without `requires`, a skipped or failed upstream step silently leaves `undefined` in the context and the consumer either crashes deep in user code or burns an LLM call on empty input.
|
|
430
|
+
|
|
431
|
+
```json
|
|
432
|
+
{
|
|
433
|
+
"id": "summarizeFounder",
|
|
434
|
+
"type": "code",
|
|
435
|
+
"requires": [
|
|
436
|
+
"$.steps.fetchProfile.output.bio",
|
|
437
|
+
"$.input.founderName"
|
|
438
|
+
],
|
|
439
|
+
"code": { "module": "lib/summarize.mjs" }
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Each entry is a `$.input.X` or `$.steps.X.output...` selector. A selector is considered missing when it resolves to `undefined`, `null`, `""`, or `[]` (empty objects are allowed). On a miss, the engine throws **before** the step runs:
|
|
444
|
+
|
|
445
|
+
```
|
|
446
|
+
Step "summarizeFounder" requires $.steps.fetchProfile.output.bio but it
|
|
447
|
+
resolved to undefined (upstream step "fetchProfile" was skipped)
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
The "because…" suffix tells you exactly why — skipped, failed, or timed out — so you can fix the upstream wiring instead of guessing.
|
|
451
|
+
|
|
452
|
+
`requires` failures honor the step's `onError` strategy: pair `requires` with `onError: { strategy: "continue" }` to skip optional consumers gracefully, or leave the default `fail` to halt the flow on contract violations.
|
|
453
|
+
|
|
454
|
+
Forward references are caught at flow load time: if `requires` points at a step declared after the current step, validation rejects the flow.
|
|
455
|
+
|
|
427
456
|
## Error Handling
|
|
428
457
|
|
|
429
458
|
```json
|