@uipath/maestro-builder-sdk 6.14.3 → 6.14.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/bindings-v2.d.ts +26 -0
- package/dist/bindings-v2.js +111 -7
- package/dist/check.js +60 -7
- package/dist/core/binding-messages.d.ts +16 -1
- package/dist/core/binding-messages.js +21 -0
- package/dist/serialize.d.ts +28 -0
- package/dist/serialize.js +87 -4
- package/package.json +2 -2
package/dist/bindings-v2.d.ts
CHANGED
|
@@ -13,6 +13,22 @@ export interface BindingsV2Document {
|
|
|
13
13
|
resources: BindingsV2Resource[];
|
|
14
14
|
[k: string]: unknown;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Which connector each bound connection belongs to, keyed by connection id —
|
|
18
|
+
* decided by EVERY node on the connection, so the answer does not depend on
|
|
19
|
+
* which step comes first.
|
|
20
|
+
*
|
|
21
|
+
* `bindings.json` cannot answer this: `prepare --connection-id` writes a label,
|
|
22
|
+
* a resource key and a kind (`ConnectionId` / `FolderKey`), never the connector
|
|
23
|
+
* (see `core/binding-messages.ts`). The nodes are the only record of it, so
|
|
24
|
+
* all of them must agree. Two that do not — a Drive step and a Slack step on
|
|
25
|
+
* one connection, or managed HTTP whose `targetConnector` is not the connection's
|
|
26
|
+
* connector — are refused with `CONNECTION_CONNECTOR_CONFLICT`: at most one of
|
|
27
|
+
* them can run, and picking the first silently mislabels the solution resource.
|
|
28
|
+
*
|
|
29
|
+
* @throws BindingRefusal when two nodes on one connection name different connectors.
|
|
30
|
+
*/
|
|
31
|
+
export declare function connectionConnectors(flow: unknown): Map<string, string>;
|
|
16
32
|
/**
|
|
17
33
|
* The `connection` resources a compiled flow declares — one per distinct
|
|
18
34
|
* connection, in node order, attributed to the FIRST node that uses it.
|
|
@@ -20,6 +36,16 @@ export interface BindingsV2Document {
|
|
|
20
36
|
* Both halves of that are pack's behaviour, not a choice made here: a flow with
|
|
21
37
|
* two Outlook steps on one connection yields a single resource whose
|
|
22
38
|
* `ActivityName` is the earlier step's.
|
|
39
|
+
*
|
|
40
|
+
* What the resource IS does not follow node order: its `Connector` comes from
|
|
41
|
+
* {@link connectionConnectors} (every node on the connection, managed HTTP read
|
|
42
|
+
* through its `targetConnector`). Pack stamps the same value — measured with
|
|
43
|
+
* `uip maestro flow pack` on managed HTTP → Slack alone, before a native Slack
|
|
44
|
+
* step, and after one: `uipath-salesforce-slack` all three times, where this
|
|
45
|
+
* module used to stamp `uipath-uipath-http` for the first two (#866;
|
|
46
|
+
* uip 1.202.0-dev, 2026-09-30).
|
|
47
|
+
*
|
|
48
|
+
* @throws BindingRefusal when two nodes on one connection name different connectors.
|
|
23
49
|
*/
|
|
24
50
|
export declare function connectionResources(flow: unknown): BindingsV2Resource[];
|
|
25
51
|
/**
|
package/dist/bindings-v2.js
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* the connection rows.
|
|
43
43
|
*/
|
|
44
44
|
import { existsSync, readFileSync } from 'node:fs';
|
|
45
|
+
import { BindingRefusal, connectionConnectorConflictMessage } from './core/binding-messages.js';
|
|
45
46
|
/** The document version pack emits. */
|
|
46
47
|
const DOCUMENT_VERSION = '2.0';
|
|
47
48
|
/** The per-resource metadata version pack stamps. */
|
|
@@ -56,6 +57,93 @@ function str(value) {
|
|
|
56
57
|
function isConnection(resource) {
|
|
57
58
|
return typeof resource === 'string' && resource.toLowerCase() === 'connection';
|
|
58
59
|
}
|
|
60
|
+
/** The managed-HTTP wrapper's own connector key — never the connection's type when a target is named. */
|
|
61
|
+
const MANAGED_HTTP_CONNECTOR = 'uipath-uipath-http';
|
|
62
|
+
function nodeDetail(node) {
|
|
63
|
+
return isRecord(node.inputs) && isRecord(node.inputs.detail) ? node.inputs.detail : undefined;
|
|
64
|
+
}
|
|
65
|
+
/** The connection a node runs on: `connectionId`, else `connectionResourceId`. */
|
|
66
|
+
function nodeConnectionKey(detail) {
|
|
67
|
+
return str(detail.connectionId) ?? str(detail.connectionResourceId);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The connector a node's CONNECTION belongs to — not the connector the node is.
|
|
71
|
+
*
|
|
72
|
+
* The two differ for managed HTTP: its `detail.connector` is always the wrapper,
|
|
73
|
+
* `uipath-uipath-http`, and the connection it authenticates with is the one
|
|
74
|
+
* `bodyParameters.targetConnector` names (`uipath-google-drive`,
|
|
75
|
+
* `uipath-salesforce-slack`, …). Stamping the wrapper made `uip solution
|
|
76
|
+
* resources refresh` refuse the connection — "Requested type
|
|
77
|
+
* 'uipath-uipath-http'; the resource is registered as type
|
|
78
|
+
* 'uipath-google-drive'" (flow-builder-sdk#866). Every other family's
|
|
79
|
+
* `detail.connector` is its connection's connector.
|
|
80
|
+
*/
|
|
81
|
+
function nodeConnectionConnector(detail) {
|
|
82
|
+
const connector = str(detail.connector);
|
|
83
|
+
if (connector === MANAGED_HTTP_CONNECTOR && isRecord(detail.bodyParameters)) {
|
|
84
|
+
return str(detail.bodyParameters.targetConnector) ?? connector;
|
|
85
|
+
}
|
|
86
|
+
return connector;
|
|
87
|
+
}
|
|
88
|
+
/** Every node of the flow and of its inline subflows — they share one `bindings[]`. */
|
|
89
|
+
function allNodes(flow) {
|
|
90
|
+
const nodes = Array.isArray(flow.nodes) ? flow.nodes.filter(isRecord) : [];
|
|
91
|
+
if (isRecord(flow.subflows)) {
|
|
92
|
+
for (const sub of Object.values(flow.subflows)) {
|
|
93
|
+
if (isRecord(sub) && Array.isArray(sub.nodes))
|
|
94
|
+
nodes.push(...sub.nodes.filter(isRecord));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return nodes;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Which connector each bound connection belongs to, keyed by connection id —
|
|
101
|
+
* decided by EVERY node on the connection, so the answer does not depend on
|
|
102
|
+
* which step comes first.
|
|
103
|
+
*
|
|
104
|
+
* `bindings.json` cannot answer this: `prepare --connection-id` writes a label,
|
|
105
|
+
* a resource key and a kind (`ConnectionId` / `FolderKey`), never the connector
|
|
106
|
+
* (see `core/binding-messages.ts`). The nodes are the only record of it, so
|
|
107
|
+
* all of them must agree. Two that do not — a Drive step and a Slack step on
|
|
108
|
+
* one connection, or managed HTTP whose `targetConnector` is not the connection's
|
|
109
|
+
* connector — are refused with `CONNECTION_CONNECTOR_CONFLICT`: at most one of
|
|
110
|
+
* them can run, and picking the first silently mislabels the solution resource.
|
|
111
|
+
*
|
|
112
|
+
* @throws BindingRefusal when two nodes on one connection name different connectors.
|
|
113
|
+
*/
|
|
114
|
+
export function connectionConnectors(flow) {
|
|
115
|
+
const out = new Map();
|
|
116
|
+
if (!isRecord(flow))
|
|
117
|
+
return out;
|
|
118
|
+
const bound = new Set();
|
|
119
|
+
for (const b of Array.isArray(flow.bindings) ? flow.bindings.filter(isRecord) : []) {
|
|
120
|
+
const key = str(b.resourceKey);
|
|
121
|
+
if (key && isConnection(b.resource) && str(b.propertyAttribute)?.toLowerCase() === 'connectionid')
|
|
122
|
+
bound.add(key);
|
|
123
|
+
}
|
|
124
|
+
const users = new Map();
|
|
125
|
+
for (const node of allNodes(flow)) {
|
|
126
|
+
const detail = nodeDetail(node);
|
|
127
|
+
const key = detail && nodeConnectionKey(detail);
|
|
128
|
+
if (!detail || !key || !bound.has(key))
|
|
129
|
+
continue;
|
|
130
|
+
const connector = nodeConnectionConnector(detail);
|
|
131
|
+
if (!connector)
|
|
132
|
+
continue;
|
|
133
|
+
const byConnector = users.get(key) ?? new Map();
|
|
134
|
+
users.set(key, byConnector);
|
|
135
|
+
const steps = byConnector.get(connector) ?? [];
|
|
136
|
+
steps.push((isRecord(node.display) ? str(node.display.label) : undefined) ?? str(node.id) ?? '?');
|
|
137
|
+
byConnector.set(connector, steps);
|
|
138
|
+
}
|
|
139
|
+
for (const [key, byConnector] of users) {
|
|
140
|
+
if (byConnector.size > 1) {
|
|
141
|
+
throw new BindingRefusal('CONNECTION_CONNECTOR_CONFLICT', connectionConnectorConflictMessage(key, byConnector));
|
|
142
|
+
}
|
|
143
|
+
out.set(key, [...byConnector.keys()][0]);
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
59
147
|
/**
|
|
60
148
|
* The `connection` resources a compiled flow declares — one per distinct
|
|
61
149
|
* connection, in node order, attributed to the FIRST node that uses it.
|
|
@@ -63,6 +151,16 @@ function isConnection(resource) {
|
|
|
63
151
|
* Both halves of that are pack's behaviour, not a choice made here: a flow with
|
|
64
152
|
* two Outlook steps on one connection yields a single resource whose
|
|
65
153
|
* `ActivityName` is the earlier step's.
|
|
154
|
+
*
|
|
155
|
+
* What the resource IS does not follow node order: its `Connector` comes from
|
|
156
|
+
* {@link connectionConnectors} (every node on the connection, managed HTTP read
|
|
157
|
+
* through its `targetConnector`). Pack stamps the same value — measured with
|
|
158
|
+
* `uip maestro flow pack` on managed HTTP → Slack alone, before a native Slack
|
|
159
|
+
* step, and after one: `uipath-salesforce-slack` all three times, where this
|
|
160
|
+
* module used to stamp `uipath-uipath-http` for the first two (#866;
|
|
161
|
+
* uip 1.202.0-dev, 2026-09-30).
|
|
162
|
+
*
|
|
163
|
+
* @throws BindingRefusal when two nodes on one connection name different connectors.
|
|
66
164
|
*/
|
|
67
165
|
export function connectionResources(flow) {
|
|
68
166
|
if (!isRecord(flow))
|
|
@@ -78,25 +176,31 @@ export function connectionResources(flow) {
|
|
|
78
176
|
continue;
|
|
79
177
|
if (str(binding.propertyAttribute)?.toLowerCase() !== 'connectionid')
|
|
80
178
|
continue;
|
|
81
|
-
|
|
82
|
-
byKey.set(key, binding);
|
|
179
|
+
byKey.set(key, [...(byKey.get(key) ?? []), binding]);
|
|
83
180
|
}
|
|
84
181
|
if (byKey.size === 0)
|
|
85
182
|
return [];
|
|
183
|
+
const connectors = connectionConnectors(flow);
|
|
86
184
|
const nodes = Array.isArray(flow.nodes) ? flow.nodes.filter(isRecord) : [];
|
|
87
185
|
const resources = [];
|
|
88
186
|
const seen = new Set();
|
|
89
187
|
for (const node of nodes) {
|
|
90
|
-
const detail =
|
|
188
|
+
const detail = nodeDetail(node);
|
|
91
189
|
if (!detail)
|
|
92
190
|
continue;
|
|
93
|
-
const key =
|
|
191
|
+
const key = nodeConnectionKey(detail);
|
|
94
192
|
if (!key || seen.has(key))
|
|
95
193
|
continue;
|
|
96
|
-
const
|
|
97
|
-
if (!
|
|
194
|
+
const candidates = byKey.get(key);
|
|
195
|
+
if (!candidates)
|
|
98
196
|
continue;
|
|
99
197
|
seen.add(key);
|
|
198
|
+
const connector = connectors.get(key) ?? nodeConnectionConnector(detail) ?? '';
|
|
199
|
+
// The label is the FIRST ConnectionId binding on the connection, even when
|
|
200
|
+
// #865's fix emits one per referencing definition — measured against `uip
|
|
201
|
+
// maestro flow pack` (uip 1.202.0-dev) on both orders of the issue's repro: pack labels
|
|
202
|
+
// the resource after the earlier step's binding, like `ActivityName`.
|
|
203
|
+
const binding = candidates[0];
|
|
100
204
|
const displayLabel = str(binding.name) ?? key;
|
|
101
205
|
const label = isRecord(node.display) ? str(node.display.label) : undefined;
|
|
102
206
|
resources.push({
|
|
@@ -116,7 +220,7 @@ export function connectionResources(flow) {
|
|
|
116
220
|
BindingsVersion: BINDINGS_VERSION,
|
|
117
221
|
DisplayLabel: displayLabel,
|
|
118
222
|
UseConnectionService: 'true',
|
|
119
|
-
Connector:
|
|
223
|
+
Connector: connector,
|
|
120
224
|
},
|
|
121
225
|
});
|
|
122
226
|
}
|
package/dist/check.js
CHANGED
|
@@ -11,7 +11,7 @@ import { isLookupToken, resolvedValue, unresolvedLookupMessage, } from './core/l
|
|
|
11
11
|
import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
|
|
12
12
|
import { EVENT_FILTER_OPERATORS, readEventFilter, eventFilterProblem } from './event-filters.js';
|
|
13
13
|
import { extractRefs } from './core/expr-check.js';
|
|
14
|
-
import { bindingSelfNameMessage, bindingUndeclaredMessage, undeclaredBinding } from './core/binding-messages.js';
|
|
14
|
+
import { bindingSelfNameMessage, bindingUndeclaredMessage, connectionConnectorConflictMessage, undeclaredBinding } from './core/binding-messages.js';
|
|
15
15
|
import { isAnswerField } from './core/hitl-answers.js';
|
|
16
16
|
import { hitlDerivesOutcomeHandles, hitlExitModel, hitlRoutesPerOutcome } from './core/hitl-routing.js';
|
|
17
17
|
import { childStepLists, declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
|
|
@@ -20,7 +20,51 @@ import { compareSchemaVersions, schemaVersionRefusal } from './schema-version.js
|
|
|
20
20
|
import { prepareCommand, resolveFlagArg } from './core/cli-spelling.js';
|
|
21
21
|
import { EVENT_BYOA_UNVERIFIED, byoaUnverified } from './core/byoa.js';
|
|
22
22
|
export function check(built, opts = {}) {
|
|
23
|
-
|
|
23
|
+
const outer = connectionUsage;
|
|
24
|
+
connectionUsage = [];
|
|
25
|
+
try {
|
|
26
|
+
const diags = checkFlow(built, [], opts);
|
|
27
|
+
diags.push(...connectionConnectorConflicts(connectionUsage, opts));
|
|
28
|
+
return diags;
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
connectionUsage = outer;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The connection uses {@link checkBindingLabels} saw during the current
|
|
36
|
+
* `check()` — the flow and every child flow, which share one `bindings[]`.
|
|
37
|
+
* Module state rather than a `CheckOpts` field because `check` is synchronous
|
|
38
|
+
* and this is not an input; `check()` saves and restores it, so a nested call
|
|
39
|
+
* cannot mix two flows.
|
|
40
|
+
*/
|
|
41
|
+
let connectionUsage;
|
|
42
|
+
/**
|
|
43
|
+
* CONNECTION_CONNECTOR_CONFLICT — the rule `serialize` enforces on the emitted
|
|
44
|
+
* document (`connectionConnectors`, bindings-v2.ts), reported here with the same
|
|
45
|
+
* sentence so `check` does not print `✓ no issues` for a source `compile` then
|
|
46
|
+
* refuses. Labels are grouped by what they resolve to, so two labels bound to
|
|
47
|
+
* one connection id are one connection, as they are in the `.flow`.
|
|
48
|
+
*/
|
|
49
|
+
function connectionConnectorConflicts(uses, opts) {
|
|
50
|
+
const byConnection = new Map();
|
|
51
|
+
for (const use of uses) {
|
|
52
|
+
const key = opts.bindings?.resolve(use.label) ?? use.label;
|
|
53
|
+
const byConnector = byConnection.get(key) ?? new Map();
|
|
54
|
+
byConnection.set(key, byConnector);
|
|
55
|
+
byConnector.set(use.connector, [...(byConnector.get(use.connector) ?? []), use.step]);
|
|
56
|
+
}
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const [key, byConnector] of byConnection) {
|
|
59
|
+
if (byConnector.size < 2)
|
|
60
|
+
continue;
|
|
61
|
+
out.push({
|
|
62
|
+
level: 'error', code: 'CONNECTION_CONNECTOR_CONFLICT',
|
|
63
|
+
step: [...byConnector.values()][1][0],
|
|
64
|
+
message: connectionConnectorConflictMessage(key, byConnector),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
24
68
|
}
|
|
25
69
|
/**
|
|
26
70
|
* `ancestors` is the chain of child flows currently being checked, innermost
|
|
@@ -358,7 +402,10 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
358
402
|
const filled = (v) => typeof v === 'string' && v.trim() !== '';
|
|
359
403
|
if (pair?.managed && filled(pair.connection) && filled(pair.folder)) {
|
|
360
404
|
const httpStep = s.name;
|
|
361
|
-
|
|
405
|
+
const target = pair.targetConnector;
|
|
406
|
+
checkBindingLabels(`managed http "${httpStep}"`, pair, opts, (code, message) => diags.push({ level: 'error', code, step: httpStep, message }), undefined,
|
|
407
|
+
// The connector its CONNECTION belongs to: the target, not the wrapper.
|
|
408
|
+
{ step: httpStep, connector: typeof target === 'string' && target !== '' ? target : 'uipath-uipath-http' });
|
|
362
409
|
}
|
|
363
410
|
const seen = new Set();
|
|
364
411
|
for (const b of s.spec.inputs.branches ?? []) {
|
|
@@ -537,7 +584,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
537
584
|
});
|
|
538
585
|
checkEventSubscription(s.spec.subscription, `waitForEvent "${stepName}"`, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
|
|
539
586
|
checkEventResolution(s.spec.subscription, `waitForEvent "${stepName}"`, 'event', opts, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
|
|
540
|
-
checkBindingLabels(`waitForEvent "${stepName}"`, s.spec.subscription, opts, push('error'), eventTarget(s.spec.subscription));
|
|
587
|
+
checkBindingLabels(`waitForEvent "${stepName}"`, s.spec.subscription, opts, push('error'), eventTarget(s.spec.subscription), { step: stepName, connector: eventTarget(s.spec.subscription)?.key });
|
|
541
588
|
}
|
|
542
589
|
if (s.spec.kind === 'subflow') {
|
|
543
590
|
// Only a real child goes in the map: a missing one is reported by
|
|
@@ -1453,7 +1500,7 @@ function checkConnectorContract(step, spec, opts, diags) {
|
|
|
1453
1500
|
if (inputErr) {
|
|
1454
1501
|
diags.push({ level: 'error', code: 'CONNECTOR_INPUT', step, message: `Connector step "${step}": ${inputErr}` });
|
|
1455
1502
|
}
|
|
1456
|
-
checkBindingLabels(`Connector step "${step}"`, spec, opts, (code, message) => diags.push({ level: 'error', code, step, message }), { key: spec.key, action: spec.action });
|
|
1503
|
+
checkBindingLabels(`Connector step "${step}"`, spec, opts, (code, message) => diags.push({ level: 'error', code, step, message }), { key: spec.key, action: spec.action }, { step, connector: spec.key });
|
|
1457
1504
|
}
|
|
1458
1505
|
/** What `registry prepare` takes for an event subscription: its connector key and curated event name. */
|
|
1459
1506
|
function eventTarget(sub) {
|
|
@@ -1472,7 +1519,13 @@ function eventTarget(sub) {
|
|
|
1472
1519
|
* Silent without a bindings.json, and for a label that resolves to anything
|
|
1473
1520
|
* other than its own name (stub values are compile's CONNECTION_STUB warning).
|
|
1474
1521
|
*/
|
|
1475
|
-
function checkBindingLabels(where, labels, opts, report, target
|
|
1522
|
+
function checkBindingLabels(where, labels, opts, report, target,
|
|
1523
|
+
/** The step, and the connector its connection must belong to, for CONNECTION_CONNECTOR_CONFLICT. */
|
|
1524
|
+
use) {
|
|
1525
|
+
const label = labels?.connection;
|
|
1526
|
+
if (use?.connector && typeof label === 'string' && label !== '') {
|
|
1527
|
+
connectionUsage?.push({ step: use.step, label, connector: use.connector });
|
|
1528
|
+
}
|
|
1476
1529
|
const bindings = opts.bindings;
|
|
1477
1530
|
if (!bindings || !labels)
|
|
1478
1531
|
return;
|
|
@@ -2285,7 +2338,7 @@ function checkTrigger(spec, diags, opts = {}) {
|
|
|
2285
2338
|
const sub = spec.subscription;
|
|
2286
2339
|
checkEventSubscription(sub, 'trigger', at, push('warning'), eventParamsFor(sub, opts));
|
|
2287
2340
|
checkEventResolution(sub, 'trigger', 'trigger', opts, at, push('warning'), eventParamsFor(sub, opts));
|
|
2288
|
-
checkBindingLabels('event trigger', sub, opts, at, eventTarget(sub));
|
|
2341
|
+
checkBindingLabels('event trigger', sub, opts, at, eventTarget(sub), { step: 'trigger', connector: eventTarget(sub)?.key });
|
|
2289
2342
|
return;
|
|
2290
2343
|
}
|
|
2291
2344
|
// manual() is the explicit spelling of the default — nothing to validate.
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/** A tenant resource key — an Integration Service connection id or an Orchestrator folder key — is a GUID. */
|
|
2
2
|
export declare const GUID_RE: RegExp;
|
|
3
3
|
/** The diagnostic codes a label refusal carries, in `check` and on the error `serialize` throws. */
|
|
4
|
-
export type BindingRefusalCode = 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED'
|
|
4
|
+
export type BindingRefusalCode = 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED'
|
|
5
|
+
/** An emitted definition's `<bindings.NAME>` placeholder has no `bindings[]` entry named NAME (#865). */
|
|
6
|
+
| 'BINDING_PLACEHOLDER_UNRESOLVED'
|
|
7
|
+
/** Two steps on one connection disagree on which connector it belongs to (#866). */
|
|
8
|
+
| 'CONNECTION_CONNECTOR_CONFLICT';
|
|
5
9
|
/**
|
|
6
10
|
* What `serialize` throws for a label it refuses. It is an ordinary `Error` whose
|
|
7
11
|
* message is the sentence `check` prints; the `code` lets `compile --no-check`
|
|
@@ -58,3 +62,14 @@ export interface PrepareTarget {
|
|
|
58
62
|
* `prepare` command for this step's own connector and operation.
|
|
59
63
|
*/
|
|
60
64
|
export declare function bindingUndeclaredMessage(where: string, name: string, attribute: 'connection' | 'folder', declared: readonly string[], target?: PrepareTarget): string;
|
|
65
|
+
/**
|
|
66
|
+
* Why compile refuses one connection used by steps of different connectors —
|
|
67
|
+
* shared by `serialize` (which throws it, via `connectionConnectors` in
|
|
68
|
+
* `bindings-v2.ts`) and `check`, so both print the same sentence.
|
|
69
|
+
*
|
|
70
|
+
* `users` maps each connector to the steps that use the connection as that
|
|
71
|
+
* connector. For managed HTTP the connector is its `targetConnector` — the
|
|
72
|
+
* connection it authenticates with — not the `uipath-uipath-http` wrapper
|
|
73
|
+
* (flow-builder-sdk#866).
|
|
74
|
+
*/
|
|
75
|
+
export declare function connectionConnectorConflictMessage(connection: string, users: ReadonlyMap<string, readonly string[]>): string;
|
|
@@ -89,3 +89,24 @@ export function bindingUndeclaredMessage(where, name, attribute, declared, targe
|
|
|
89
89
|
+ `declares the label with a placeholder resourceKey instead, which compiles with a CONNECTION_STUB warning — `
|
|
90
90
|
+ `never an invented GUID.`;
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Why compile refuses one connection used by steps of different connectors —
|
|
94
|
+
* shared by `serialize` (which throws it, via `connectionConnectors` in
|
|
95
|
+
* `bindings-v2.ts`) and `check`, so both print the same sentence.
|
|
96
|
+
*
|
|
97
|
+
* `users` maps each connector to the steps that use the connection as that
|
|
98
|
+
* connector. For managed HTTP the connector is its `targetConnector` — the
|
|
99
|
+
* connection it authenticates with — not the `uipath-uipath-http` wrapper
|
|
100
|
+
* (flow-builder-sdk#866).
|
|
101
|
+
*/
|
|
102
|
+
export function connectionConnectorConflictMessage(connection, users) {
|
|
103
|
+
const who = [...users]
|
|
104
|
+
.map(([connector, steps]) => `${steps.map((s) => `"${s}"`).join(', ')} as ${connector}`)
|
|
105
|
+
.join('; ');
|
|
106
|
+
return `connection "${connection}" is used by steps of different connectors (${who}). An Integration Service `
|
|
107
|
+
+ `connection belongs to exactly one connector, so at most one of these steps can run on it, and the solution `
|
|
108
|
+
+ `resource would be declared as whichever came first. Give each connector its own connection label in `
|
|
109
|
+
+ `bindings.json (\`${prepareCommand('<connector-key>', '<action>')}\` binds one per connector). A managed `
|
|
110
|
+
+ `\`http({ managed: true, … })\` step on another connector's connection must name that connector in `
|
|
111
|
+
+ `\`targetConnector\`.`;
|
|
112
|
+
}
|
package/dist/serialize.d.ts
CHANGED
|
@@ -54,6 +54,34 @@ export declare function serializeWithSidecars(built: BuiltFlow, opts?: Serialize
|
|
|
54
54
|
}>;
|
|
55
55
|
};
|
|
56
56
|
export declare function serialize(built: BuiltFlow, opts?: SerializeOpts): any;
|
|
57
|
+
/**
|
|
58
|
+
* Every `<bindings.NAME>` placeholder a node's definition carries in its
|
|
59
|
+
* `model.context` must resolve to a `bindings[]` entry for THAT node.
|
|
60
|
+
*
|
|
61
|
+
* "Resolve" is Studio Web's rule, not a looser one: flow-v1
|
|
62
|
+
* `matchContextBindings` + `getInstanceBindingKey`
|
|
63
|
+
* (flow-schema/src/packaging/resolve-context.ts) take the entries whose
|
|
64
|
+
* `resource` EQUALS the definition's `model.bindings.resource`, scope them to the
|
|
65
|
+
* node's `inputs.detail.connectionResourceId` when that resource is `connection`
|
|
66
|
+
* (or undeclared), and look the placeholder up there BY NAME. Any miss blocks
|
|
67
|
+
* publish (MST-15010), and one that slips through reaches Studio as the literal
|
|
68
|
+
* placeholder: `invalid GUID <bindings.uipath-salesforce-slack connection>`.
|
|
69
|
+
* `uip maestro flow validate` does not check this.
|
|
70
|
+
*
|
|
71
|
+
* That is how #865 shipped — a per-call-site dedupe that let the first step on a
|
|
72
|
+
* shared connection claim it — and UV-16774 before it (a `Connection` resource
|
|
73
|
+
* spelling). Checking the finished document catches that class for every family
|
|
74
|
+
* and every emission order, whichever call site produced it.
|
|
75
|
+
*
|
|
76
|
+
* It holds for real definitions: of the 5,102 in the local registry index
|
|
77
|
+
* (2026-09-30), 4,999 reference a `<bindings.…>` name and every one of them
|
|
78
|
+
* declares that name in `model.bindings.values`, which is what the emitters
|
|
79
|
+
* close. Agent resource definitions are the one exemption (see below).
|
|
80
|
+
*
|
|
81
|
+
* @throws BindingRefusal `BINDING_PLACEHOLDER_UNRESOLVED`, naming each dangling
|
|
82
|
+
* placeholder and the step that carries it.
|
|
83
|
+
*/
|
|
84
|
+
export declare function assertBindingPlaceholdersResolve(flow: any): void;
|
|
57
85
|
/**
|
|
58
86
|
* What `compile` would refuse these connector inputs with, or `undefined` when it
|
|
59
87
|
* would accept them — the SAME code path `buildConnectorInputs` runs at emit time,
|
package/dist/serialize.js
CHANGED
|
@@ -15,6 +15,7 @@ import { FlowAction, FlowTrigger } from './core/node-classes.js';
|
|
|
15
15
|
import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
|
|
16
16
|
import { dataFabricUnknownInputs } from './core/typed-raw-node.js';
|
|
17
17
|
import { isAnswerField } from './core/hitl-answers.js';
|
|
18
|
+
import { connectionConnectors } from './bindings-v2.js';
|
|
18
19
|
import { BindingRefusal, GUID_RE, bindingSelfNameMessage, bindingUndeclaredMessage, undeclaredBinding } from './core/binding-messages.js';
|
|
19
20
|
import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
|
|
20
21
|
import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
|
|
@@ -2306,9 +2307,15 @@ function connectionResourceBindings(spec, existing) {
|
|
|
2306
2307
|
if (values.length === 0)
|
|
2307
2308
|
return { entries: [] };
|
|
2308
2309
|
const resourceKey = spec.resourceKey ?? spec.connectionId ?? '';
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2310
|
+
// Keyed on the NAME as well (flow-builder-sdk#865). The name is the definition's
|
|
2311
|
+
// own `<bindings.NAME>` placeholder, and two definitions sharing one connection
|
|
2312
|
+
// can spell it differently — managed HTTP says `uipath-uipath-http connection`,
|
|
2313
|
+
// the Slack connector `uipath-salesforce-slack connection`. Keyed on
|
|
2314
|
+
// resourceKey + attribute alone, whichever step came first claimed the
|
|
2315
|
+
// connection and the other node's placeholder never resolved: Studio showed
|
|
2316
|
+
// `invalid GUID <bindings.uipath-salesforce-slack connection>`.
|
|
2317
|
+
const have = (name, attr) => [...existing.values()].some((b) => b.resource === 'connection' && b.name === name && b.resourceKey === resourceKey
|
|
2318
|
+
&& b.propertyAttribute === attr);
|
|
2312
2319
|
const slug = spec.connectorKey.replace(/[^A-Za-z0-9]/g, '') || 'Connector';
|
|
2313
2320
|
let base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}`;
|
|
2314
2321
|
for (let n = 2; existing.has(`${base}Connection`) || existing.has(`${base}FolderKey`); n++) {
|
|
@@ -2317,7 +2324,7 @@ function connectionResourceBindings(spec, existing) {
|
|
|
2317
2324
|
const entries = values.flatMap((v) => {
|
|
2318
2325
|
const name = typeof v.name === 'string' ? v.name : '';
|
|
2319
2326
|
const attr = v.propertyAttribute;
|
|
2320
|
-
if (!name || (attr !== 'ConnectionId' && attr !== 'FolderKey') || have(attr))
|
|
2327
|
+
if (!name || (attr !== 'ConnectionId' && attr !== 'FolderKey') || have(name, attr))
|
|
2321
2328
|
return [];
|
|
2322
2329
|
const isConnection = attr === 'ConnectionId';
|
|
2323
2330
|
const dflt = isConnection ? spec.connectionId : spec.folderKey;
|
|
@@ -6280,8 +6287,84 @@ export function serialize(built, opts = {}) {
|
|
|
6280
6287
|
// Passing the target is what lets a version-conditional step exist at all —
|
|
6281
6288
|
// see migrate.ts's VERSION_CONDITIONAL ladder.
|
|
6282
6289
|
migrateToSchemaVersion(flow, schemaVersion);
|
|
6290
|
+
// Emitted-document invariants over connections, checked on the FINISHED file
|
|
6291
|
+
// so every family and every emission order is covered by one rule, not by
|
|
6292
|
+
// each call site remembering it (flow-builder-sdk#865/#866).
|
|
6293
|
+
assertBindingPlaceholdersResolve(flow);
|
|
6294
|
+
connectionConnectors(flow);
|
|
6283
6295
|
return flow;
|
|
6284
6296
|
}
|
|
6297
|
+
/**
|
|
6298
|
+
* Every `<bindings.NAME>` placeholder a node's definition carries in its
|
|
6299
|
+
* `model.context` must resolve to a `bindings[]` entry for THAT node.
|
|
6300
|
+
*
|
|
6301
|
+
* "Resolve" is Studio Web's rule, not a looser one: flow-v1
|
|
6302
|
+
* `matchContextBindings` + `getInstanceBindingKey`
|
|
6303
|
+
* (flow-schema/src/packaging/resolve-context.ts) take the entries whose
|
|
6304
|
+
* `resource` EQUALS the definition's `model.bindings.resource`, scope them to the
|
|
6305
|
+
* node's `inputs.detail.connectionResourceId` when that resource is `connection`
|
|
6306
|
+
* (or undeclared), and look the placeholder up there BY NAME. Any miss blocks
|
|
6307
|
+
* publish (MST-15010), and one that slips through reaches Studio as the literal
|
|
6308
|
+
* placeholder: `invalid GUID <bindings.uipath-salesforce-slack connection>`.
|
|
6309
|
+
* `uip maestro flow validate` does not check this.
|
|
6310
|
+
*
|
|
6311
|
+
* That is how #865 shipped — a per-call-site dedupe that let the first step on a
|
|
6312
|
+
* shared connection claim it — and UV-16774 before it (a `Connection` resource
|
|
6313
|
+
* spelling). Checking the finished document catches that class for every family
|
|
6314
|
+
* and every emission order, whichever call site produced it.
|
|
6315
|
+
*
|
|
6316
|
+
* It holds for real definitions: of the 5,102 in the local registry index
|
|
6317
|
+
* (2026-09-30), 4,999 reference a `<bindings.…>` name and every one of them
|
|
6318
|
+
* declares that name in `model.bindings.values`, which is what the emitters
|
|
6319
|
+
* close. Agent resource definitions are the one exemption (see below).
|
|
6320
|
+
*
|
|
6321
|
+
* @throws BindingRefusal `BINDING_PLACEHOLDER_UNRESOLVED`, naming each dangling
|
|
6322
|
+
* placeholder and the step that carries it.
|
|
6323
|
+
*/
|
|
6324
|
+
export function assertBindingPlaceholdersResolve(flow) {
|
|
6325
|
+
const bindings = Array.isArray(flow?.bindings) ? flow.bindings : [];
|
|
6326
|
+
const defs = new Map();
|
|
6327
|
+
for (const def of Array.isArray(flow?.definitions) ? flow.definitions : []) {
|
|
6328
|
+
defs.set(`${def?.nodeType}@${def?.version}`, def);
|
|
6329
|
+
}
|
|
6330
|
+
const nodes = [...(Array.isArray(flow?.nodes) ? flow.nodes : [])];
|
|
6331
|
+
for (const sub of Object.values(flow?.subflows ?? {})) {
|
|
6332
|
+
if (Array.isArray(sub?.nodes))
|
|
6333
|
+
nodes.push(...sub.nodes);
|
|
6334
|
+
}
|
|
6335
|
+
const dangling = [];
|
|
6336
|
+
for (const node of nodes) {
|
|
6337
|
+
// An agent's resource nodes (`uipath.agent.resource.tool.connector.…`) carry
|
|
6338
|
+
// the same placeholders, but they are resolved through the AGENT project —
|
|
6339
|
+
// `uip agent refresh` generates the connector tool's resource — and the
|
|
6340
|
+
// deployed corpus carries no flow `bindings[]` entry for them.
|
|
6341
|
+
if (typeof node?.type !== 'string' || node.type.startsWith('uipath.agent.resource.'))
|
|
6342
|
+
continue;
|
|
6343
|
+
const model = defs.get(`${node.type}@${node.typeVersion}`)?.model;
|
|
6344
|
+
if (!Array.isArray(model?.context))
|
|
6345
|
+
continue;
|
|
6346
|
+
const resource = model.bindings?.resource;
|
|
6347
|
+
const scope = resource === 'connection' || resource === undefined
|
|
6348
|
+
? node.inputs?.detail?.connectionResourceId ?? node.inputs?.connectionId ?? undefined
|
|
6349
|
+
: undefined;
|
|
6350
|
+
const candidates = resource === undefined ? bindings : bindings.filter((b) => b?.resource === resource);
|
|
6351
|
+
for (const row of model.context) {
|
|
6352
|
+
const name = typeof row?.value === 'string' ? /^<bindings\.(.+)>$/.exec(row.value)?.[1] : undefined;
|
|
6353
|
+
if (name === undefined)
|
|
6354
|
+
continue;
|
|
6355
|
+
if (candidates.some((b) => b?.name === name && (!scope || b.resourceKey === scope)))
|
|
6356
|
+
continue;
|
|
6357
|
+
dangling.push(`step "${node.display?.label ?? node.id}" (${node.type}): "${row.value}"`
|
|
6358
|
+
+ `${scope ? ` on connection ${scope}` : ''}`);
|
|
6359
|
+
}
|
|
6360
|
+
}
|
|
6361
|
+
if (dangling.length === 0)
|
|
6362
|
+
return;
|
|
6363
|
+
throw new BindingRefusal('BINDING_PLACEHOLDER_UNRESOLVED', `serialize: ${dangling.join('; ')} — no bindings[] entry of that name${dangling.length === 1 ? '' : ' (each)'} `
|
|
6364
|
+
+ `is emitted for the step, so Studio Web reads the placeholder itself as the value (`
|
|
6365
|
+
+ `"invalid GUID <bindings.…>") and refuses to publish. This is a serializer defect, not an authoring `
|
|
6366
|
+
+ `error; report it with the flow source.`);
|
|
6367
|
+
}
|
|
6285
6368
|
/**
|
|
6286
6369
|
* Validate a connector's activity inputs against its declared field list and
|
|
6287
6370
|
* return them ROUTED to the wire slots the platform reads. Catches the three
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.14.
|
|
3
|
+
"version": "6.14.4",
|
|
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",
|
|
@@ -92,5 +92,5 @@
|
|
|
92
92
|
"@types/node": "^22.7.0",
|
|
93
93
|
"esbuild": "^0.28.1"
|
|
94
94
|
},
|
|
95
|
-
"gitref": "
|
|
95
|
+
"gitref": "deacd660d0428eb3144e4b61a76bbfbbfc87cbe8"
|
|
96
96
|
}
|