@uipath/maestro-builder-sdk 5.3.1 → 5.4.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.
- package/dist/check.js +93 -0
- package/dist/core/actions.d.ts +8 -2
- package/package.json +2 -2
package/dist/check.js
CHANGED
|
@@ -104,6 +104,13 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
104
104
|
// (and their declared outcome ids), and which ids actually got a route.
|
|
105
105
|
const hitlOutcomeSteps = new Map();
|
|
106
106
|
const hitlOutcomeRouted = new Map();
|
|
107
|
+
// The other half of that bookkeeping: hitl steps that declare a CHOICE (more
|
|
108
|
+
// than one outcome) and did NOT opt into per-outcome exits, so the graph gives
|
|
109
|
+
// every outcome the same single exit. Whether that is a mistake depends on
|
|
110
|
+
// something no single step can see — whether anything downstream reads the
|
|
111
|
+
// chosen outcome — so the decision waits until the expressions are collected
|
|
112
|
+
// (see HITL_OUTCOMES_UNREACHABLE below).
|
|
113
|
+
const hitlChoiceNoPorts = new Map();
|
|
107
114
|
const dataFabricUpdates = [];
|
|
108
115
|
// Placeholder (`mock()`) step names. The platform's `core.logic.mock` declares
|
|
109
116
|
// one output whose `source` is the literal `"null"` — it publishes nothing —
|
|
@@ -322,6 +329,19 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
322
329
|
if (s.spec.kind === 'hitl') {
|
|
323
330
|
checkHitl(s.name, s.spec.inputs, diags);
|
|
324
331
|
hitlForms.set(s.name, s.spec.inputs);
|
|
332
|
+
const outcomes = s.spec.inputs?.outcomes ?? [];
|
|
333
|
+
// `action: 'End'` ends the PROCESS at the node, so a task whose every
|
|
334
|
+
// outcome ends it has nothing downstream to distinguish — one exit is
|
|
335
|
+
// the whole truth there, and the rule below would be a false positive.
|
|
336
|
+
const allEnd = outcomes.length > 0
|
|
337
|
+
&& outcomes.every((o) => typeof o !== 'string' && o?.action === 'End');
|
|
338
|
+
if (outcomes.length > 1 && !allEnd
|
|
339
|
+
&& s.spec.inputs?.outcomePorts !== true && s.spec.inputs?.exposeError !== true) {
|
|
340
|
+
hitlChoiceNoPorts.set(s.name, {
|
|
341
|
+
names: outcomes.map((o) => String((typeof o === 'string' ? o : o?.name) ?? '').trim()),
|
|
342
|
+
variant: s.spec.inputs?.variant,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
325
345
|
}
|
|
326
346
|
if (s.spec.kind === 'rpaWorkflow')
|
|
327
347
|
checkRpaWorkflow(s.name, s.spec.inputs, diags);
|
|
@@ -577,6 +597,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
577
597
|
});
|
|
578
598
|
}
|
|
579
599
|
}
|
|
600
|
+
reportUnreachableHitlOutcomes(hitlChoiceNoPorts, exprs, diags);
|
|
580
601
|
for (const { step, inputs } of dataFabricUpdates) {
|
|
581
602
|
const fromRead = inputs.record?.fromRead;
|
|
582
603
|
if (typeof fromRead === 'string' && !dataFabricReads.has(fromRead)) {
|
|
@@ -3821,6 +3842,78 @@ function outcomeSlug(name, index) {
|
|
|
3821
3842
|
.replace(/^-+|-+$/g, '');
|
|
3822
3843
|
return slug || `outcome-${index + 1}`;
|
|
3823
3844
|
}
|
|
3845
|
+
/**
|
|
3846
|
+
* HITL_OUTCOMES_UNREACHABLE — a human task offers the reviewer a CHOICE that
|
|
3847
|
+
* nothing downstream can act on.
|
|
3848
|
+
*
|
|
3849
|
+
* `outcomes` and `outcomePorts` are two halves of one decision, and only the
|
|
3850
|
+
* first half reads as load-bearing. Declaring outcomes without the flag emits
|
|
3851
|
+
* the node's 1.0 definition, whose ONE source handle is `completed`: the
|
|
3852
|
+
* outcomes still reach the reviewer and still appear in
|
|
3853
|
+
* `inputs.schema.outcomes`, and then every button the reviewer can press leaves
|
|
3854
|
+
* the node the same way. Two outcomes, one path — an approval whose approve and
|
|
3855
|
+
* reject arms are indistinguishable is a modelling error, not a style choice,
|
|
3856
|
+
* yet it compiles and `flow validate` answers `Valid` (flow-builder-sdk#735).
|
|
3857
|
+
*
|
|
3858
|
+
* The one thing that makes the single-exit shape correct is a read of the
|
|
3859
|
+
* chosen outcome: `out('<step>', 'Action')` feeding a `.switch()` is the
|
|
3860
|
+
* DEFAULT documented shape, and it is the only shape the quick-form,
|
|
3861
|
+
* action-app and document-validation variants have — they carry no per-outcome
|
|
3862
|
+
* definition for `outcomePorts` to select (HITL_OUTCOME_PORTS_VARIANT). So the
|
|
3863
|
+
* rule is not "outcomes without ports", which would refuse that shape; it is
|
|
3864
|
+
* "outcomes that no exit and no expression distinguishes". That is why it runs
|
|
3865
|
+
* here, after `walk`, rather than inside `checkHitl`: the deciding evidence is
|
|
3866
|
+
* the rest of the flow.
|
|
3867
|
+
*
|
|
3868
|
+
* A read counts when it can SEE the outcome — `.output.Action`, the whole
|
|
3869
|
+
* answer object (`out('<step>')` / `$vars.<step>.output`, which carries
|
|
3870
|
+
* `Action` into a script), `.status` (the platform's own projection of it,
|
|
3871
|
+
* redirected by HITL_READ_STATUS but still an author reaching for the outcome),
|
|
3872
|
+
* a bare `$vars.<step>`, or any bracket read, whose path this scan cannot
|
|
3873
|
+
* resolve. One named answer field (`$vars.<step>.output.note`) does not: it
|
|
3874
|
+
* says nothing about which button was pressed.
|
|
3875
|
+
*
|
|
3876
|
+
* A warning, not an error, for the same reason HITL_OUTCOME_UNROUTED is: the
|
|
3877
|
+
* artifact deploys and the task works — what is lost is the branch.
|
|
3878
|
+
*/
|
|
3879
|
+
function reportUnreachableHitlOutcomes(choices, exprs, diags) {
|
|
3880
|
+
if (choices.size === 0)
|
|
3881
|
+
return;
|
|
3882
|
+
const readsOutcome = new Set();
|
|
3883
|
+
for (const { js } of exprs) {
|
|
3884
|
+
for (const m of js.matchAll(/\$vars\.([A-Za-z0-9_]+)(?:\.([A-Za-z0-9_]+))?(?:\.([A-Za-z0-9_]+))?/g)) {
|
|
3885
|
+
const [, name, field, tail] = m;
|
|
3886
|
+
if (field === undefined || field === 'status')
|
|
3887
|
+
readsOutcome.add(name);
|
|
3888
|
+
else if (field === 'output' && (tail === undefined || tail === 'Action'))
|
|
3889
|
+
readsOutcome.add(name);
|
|
3890
|
+
}
|
|
3891
|
+
// `$vars.<step>['output']` — VARS_BRACKET_READ's shape. The path is not
|
|
3892
|
+
// statically resolvable here, so it counts as a read: this rule fails open.
|
|
3893
|
+
for (const m of js.matchAll(/\$vars\.([A-Za-z0-9_]+)\s*\[/g))
|
|
3894
|
+
readsOutcome.add(m[1]);
|
|
3895
|
+
}
|
|
3896
|
+
for (const [step, { names, variant }] of choices) {
|
|
3897
|
+
if (readsOutcome.has(step))
|
|
3898
|
+
continue;
|
|
3899
|
+
const ids = names.map((n, i) => outcomeSlug(n, i));
|
|
3900
|
+
diags.push({
|
|
3901
|
+
level: 'warning', code: 'HITL_OUTCOMES_UNREACHABLE', step,
|
|
3902
|
+
message: `Human task "${step}" declares ${names.length} outcomes (${names.join(', ')}), but nothing `
|
|
3903
|
+
+ `downstream tells them apart: the node emits its single 'completed' exit and every outcome leaves `
|
|
3904
|
+
+ `on it, and no expression reads out('${step}', 'Action') either. The outcomes reach the reviewer `
|
|
3905
|
+
+ `and the artifact; the decision does not reach the flow. `
|
|
3906
|
+
+ (variant === undefined
|
|
3907
|
+
? `Set outcomePorts: true to give each outcome its own exit (${ids.map((i) => `'outcome-${i}'`).join(', ')}), `
|
|
3908
|
+
+ `or switch on out('${step}', 'Action') to route it as data.`
|
|
3909
|
+
: `The "${variant}" variant has no per-outcome definition for outcomePorts to select, so switch on `
|
|
3910
|
+
+ `out('${step}', 'Action') to route it as data.`),
|
|
3911
|
+
suggestion: variant === undefined
|
|
3912
|
+
? 'outcomePorts: true'
|
|
3913
|
+
: `.switch('route', out('${step}', 'Action'), …)`,
|
|
3914
|
+
});
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3824
3917
|
/**
|
|
3825
3918
|
* A human task's form.
|
|
3826
3919
|
*
|
package/dist/core/actions.d.ts
CHANGED
|
@@ -564,7 +564,11 @@ export interface HitlInputs {
|
|
|
564
564
|
/**
|
|
565
565
|
* The completion buttons, e.g. `['Approve', 'Reject']`. At least one, or the
|
|
566
566
|
* reviewer has no way to complete the task and the flow waits forever. The
|
|
567
|
-
* first is the primary (default) one.
|
|
567
|
+
* first is the primary (default) one. Declaring them creates no EXITS: with
|
|
568
|
+
* more than one, set `outcomePorts` to fork in the graph, or read
|
|
569
|
+
* `out('<step>', 'Action')` to route as data. With neither, every outcome
|
|
570
|
+
* leaves on the same `completed` exit and nothing downstream can tell them
|
|
571
|
+
* apart — which `check` warns about as `HITL_OUTCOMES_UNREACHABLE`.
|
|
568
572
|
*
|
|
569
573
|
* Which one they pressed is `out('<step>', 'Action')` — that is what the
|
|
570
574
|
* platform's own routing conditions compare against, so branch on it.
|
|
@@ -578,7 +582,9 @@ export interface HitlInputs {
|
|
|
578
582
|
* Route each outcome from its OWN port instead of the single `completed`
|
|
579
583
|
* exit. Selects the node's **1.1** definition, whose exits are
|
|
580
584
|
* `outcome-<id>` handles (one per outcome; ids are the outcome names
|
|
581
|
-
* slugified, e.g. `'Approve'` → `outcome-approve`).
|
|
585
|
+
* slugified, e.g. `'Approve'` → `outcome-approve`). It REPLACES `completed`
|
|
586
|
+
* rather than adding to it, and it is not an optional refinement — see
|
|
587
|
+
* `outcomes` for the choice it is one half of.
|
|
582
588
|
*
|
|
583
589
|
* The FIRST (primary) outcome continues the main path; route the others with
|
|
584
590
|
* `.stepToList('outcome-<id>', …)`. Base variant only — the sub-typed nodes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docs.uipath.com/maestro",
|
|
@@ -87,5 +87,5 @@
|
|
|
87
87
|
"@types/node": "^22.7.0",
|
|
88
88
|
"esbuild": "^0.28.1"
|
|
89
89
|
},
|
|
90
|
-
"gitref": "
|
|
90
|
+
"gitref": "b5c108408c7198fa36dcaf2c8d84bf46b3c1ee72"
|
|
91
91
|
}
|