@uipath/maestro-builder-sdk 6.15.2 → 6.15.3

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 CHANGED
@@ -9,9 +9,10 @@ import { Expr, SCHEDULE_PRESETS, DELAY_PRESETS, VARIANT_OPERATION, parseIxpProje
9
9
  import { FlowNode } from './core/node-classes.js';
10
10
  import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
11
11
  import { checkEventResolution, checkEventSubscription, eventParamsFor, } from './core/event-checks.js';
12
- import { FLOW_EXPR_DIALECT, checkBindingLabels, checkConnectorContract as coreCheckConnectorContract, checkConnectorLookups as coreCheckConnectorLookups, checkConnectorSchema as coreCheckConnectorSchema, collectConnectionUses, connectionConnectorConflicts, } from './core/connector-checks.js';
12
+ import { FLOW_EXPR_DIALECT, checkBindingLabels, checkBindingPresence, checkConnectorContract as coreCheckConnectorContract, checkConnectorLookups as coreCheckConnectorLookups, checkConnectorSchema as coreCheckConnectorSchema, collectConnectionUses, connectionConnectorConflicts, } from './core/connector-checks.js';
13
13
  import { prepareCommand } from './core/cli-spelling.js';
14
14
  import { isAnswerField } from './core/hitl-answers.js';
15
+ import { globalNameCollisionMessage, globalNameCollisions } from './core/global-names.js';
15
16
  import { hitlDerivesOutcomeHandles, hitlExitModel, hitlRoutesPerOutcome } from './core/hitl-routing.js';
16
17
  import { childStepLists, declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
17
18
  import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
@@ -36,6 +37,11 @@ function checkFlow(built, ancestors, opts = {}) {
36
37
  diags.push({ level: 'error', code: 'NO_ID', message: 'flow() requires an id.' });
37
38
  checkSchemaVersion(built, ancestors, diags);
38
39
  checkNodeUpdates(built, diags);
40
+ // Inputs, outputs and vars are one namespace in the .flow (variables.globals[]);
41
+ // `serialize` throws the same sentence (flow-builder-sdk#890).
42
+ for (const collision of globalNameCollisions(built.inputs ?? [], built.outputs ?? [], built.vars ?? [])) {
43
+ diags.push({ level: 'error', code: 'VARIABLE_NAME_COLLISION', message: globalNameCollisionMessage(collision) });
44
+ }
39
45
  const maybeCustomTrigger = built.trigger;
40
46
  if (maybeCustomTrigger !== undefined && !('kind' in maybeCustomTrigger) && maybeCustomTrigger instanceof FlowNode) {
41
47
  // A custom FlowTrigger subclass reports through its own diagnostics() hook;
@@ -543,6 +549,7 @@ function checkFlow(built, ancestors, opts = {}) {
543
549
  checkEventSubscription(s.spec.subscription, `waitForEvent "${stepName}"`, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
544
550
  checkEventResolution(s.spec.subscription, `waitForEvent "${stepName}"`, 'event', opts, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
545
551
  checkBindingLabels(`waitForEvent "${stepName}"`, s.spec.subscription, opts, push('error'), eventTarget(s.spec.subscription), { id: stepName, connector: eventTarget(s.spec.subscription)?.key });
552
+ checkBindingPresence(`waitForEvent "${stepName}"`, s.spec.subscription, push('error'), eventTarget(s.spec.subscription));
546
553
  }
547
554
  if (s.spec.kind === 'subflow') {
548
555
  // Only a real child goes in the map: a missing one is reported by
@@ -848,7 +855,7 @@ function checkFlow(built, ancestors, opts = {}) {
848
855
  }
849
856
  }
850
857
  else {
851
- checkTrigger(ep.trigger, diags, opts);
858
+ checkTrigger(ep.trigger, diags, opts, `onEvent() entry point "${ep.id}"`);
852
859
  }
853
860
  for (const inp of ep.inputs) {
854
861
  const collides = built.inputs.some((g) => g.name === inp.name)
@@ -1257,9 +1264,15 @@ function checkConnectorSchema(step, spec, opts, diags) {
1257
1264
  const { where, report } = connectorSite(step, diags);
1258
1265
  coreCheckConnectorSchema(where, spec, opts, FLOW_EXPR_DIALECT, report);
1259
1266
  }
1267
+ /**
1268
+ * The core contract (fields with a library, names with a bindings.json), plus
1269
+ * BINDING_MISSING, which needs neither: an absent label is wrong whatever the
1270
+ * file says.
1271
+ */
1260
1272
  function checkConnectorContract(step, spec, opts, diags) {
1261
1273
  const { where, report } = connectorSite(step, diags);
1262
1274
  coreCheckConnectorContract(where, spec, opts, report, step);
1275
+ checkBindingPresence(`connector "${step}"`, spec, (code, message) => report({ level: 'error', code, message }), { key: spec.key, action: spec.action });
1263
1276
  }
1264
1277
  /** Reject a wrapper hop when the runtime shape of a list has been measured. */
1265
1278
  function checkLoopListEnvelope(loop, connectors, opts, diags) {
@@ -1647,7 +1660,7 @@ function checkNodeUpdates(built, diags) {
1647
1660
  };
1648
1661
  visit(built.steps);
1649
1662
  }
1650
- function checkTrigger(spec, diags, opts = {}) {
1663
+ function checkTrigger(spec, diags, opts = {}, where) {
1651
1664
  const push = (level) => (code, message, suggestion) => {
1652
1665
  diags.push({ level, code, message, step: 'trigger', ...(suggestion ? { suggestion } : {}) });
1653
1666
  };
@@ -1659,6 +1672,7 @@ function checkTrigger(spec, diags, opts = {}) {
1659
1672
  checkEventSubscription(sub, 'trigger', at, push('warning'), eventParamsFor(sub, opts));
1660
1673
  checkEventResolution(sub, 'trigger', 'trigger', opts, at, push('warning'), eventParamsFor(sub, opts));
1661
1674
  checkBindingLabels('event trigger', sub, opts, at, eventTarget(sub), { id: 'trigger', connector: eventTarget(sub)?.key });
1675
+ checkBindingPresence(where ?? 'event trigger', sub, at, eventTarget(sub));
1662
1676
  return;
1663
1677
  }
1664
1678
  // manual() is the explicit spelling of the default — nothing to validate.
@@ -1675,7 +1689,7 @@ function checkTrigger(spec, diags, opts = {}) {
1675
1689
  if (spec?.kind !== 'scheduled') {
1676
1690
  at('TRIGGER_UNKNOWN_KIND', `.trigger() was given ${JSON.stringify(spec?.kind)}, which is not a trigger. `
1677
1691
  + `Wrap it in a factory: scheduled({ every: 'R/PT1H' }) for a schedule, `
1678
- + `onEvent({ connector, event, where }) to start on a connector event, formTrigger() to start from a `
1692
+ + `onEvent({ connector, event, where, connection, folder }) to start on a connector event, formTrigger() to start from a `
1679
1693
  + `submitted form, or conversationTrigger() to start on a new conversation. Omit .trigger() entirely `
1680
1694
  + `for the manual trigger (the default).`, `scheduled({ every: 'R/PT1H' })`);
1681
1695
  return;
@@ -187,9 +187,12 @@ export interface ScriptInputs {
187
187
  export interface ConnectorOpts {
188
188
  /** Connector action version (defaults to the library's newest match). */
189
189
  version?: string;
190
- /** Symbolic connection name declared in bindings.json (→ ConnectionId). */
190
+ /**
191
+ * Symbolic connection name declared in bindings.json (→ ConnectionId). Required with
192
+ * `folder`: `check` refuses a connector step without both (BINDING_MISSING).
193
+ */
191
194
  connection?: string;
192
- /** Symbolic folder name declared in bindings.json (→ connectionFolderKey). */
195
+ /** Symbolic folder name declared in bindings.json (→ connectionFolderKey). Required with `connection`. */
193
196
  folder?: string;
194
197
  /**
195
198
  * Which OBJECT a **generic** operation addresses — e.g. ServiceNow's "List All
@@ -1593,7 +1596,8 @@ export interface InlineAgentInputs {
1593
1596
  * ```ts
1594
1597
  * tools: [
1595
1598
  * { kind: 'builtin', tool: 'summarize' },
1596
- * { kind: 'connector', connector: 'uipath-atlassian-jira', operation: 'create-issue' },
1599
+ * { kind: 'connector', connector: 'uipath-atlassian-jira', operation: 'create-issue',
1600
+ * connection: 'jira', folder: 'shared' }, // bindings.json labels, both required
1597
1601
  * { kind: 'process', key: '6235d64e-b190-4b7a-8918-723df964c068',
1598
1602
  * name: 'Create TestCase', folderPath: 'Shared',
1599
1603
  * inputs: { title: 'string' }, returns: { id: 'string' } },
@@ -3420,9 +3424,12 @@ export interface TriggerOptions<W = Record<string, string>> {
3420
3424
  object?: string;
3421
3425
  /** Optional filters on the payload. Omit to take every event in scope. */
3422
3426
  filters?: EventFilter[];
3423
- /** bindings.json id for the connection (defaults to the flow's single binding). */
3427
+ /**
3428
+ * bindings.json id for the connection. Required with `folder`: `check` refuses an event
3429
+ * node without both (BINDING_MISSING) — it would compile to `connectionId: null`.
3430
+ */
3424
3431
  connection?: string;
3425
- /** bindings.json id for the folder key. */
3432
+ /** bindings.json id for the folder key. Required with `connection` (BINDING_MISSING). */
3426
3433
  folder?: string;
3427
3434
  /** Override the descriptor's version. */
3428
3435
  version?: string;
@@ -3460,9 +3467,12 @@ export interface EventSubscription {
3460
3467
  object?: string;
3461
3468
  /** Optional filters on the payload. Omit to take every event in scope. */
3462
3469
  filters?: EventFilter[];
3463
- /** bindings.json id for the connection (defaults to the flow's single binding). */
3470
+ /**
3471
+ * bindings.json id for the connection. Required with `folder`: `check` refuses an event
3472
+ * node without both (BINDING_MISSING) — it would compile to `connectionId: null`.
3473
+ */
3464
3474
  connection?: string;
3465
- /** bindings.json id for the folder key. */
3475
+ /** bindings.json id for the folder key. Required with `connection` (BINDING_MISSING). */
3466
3476
  folder?: string;
3467
3477
  version?: string;
3468
3478
  }
@@ -3493,6 +3503,7 @@ export declare function subscriptionFrom<W>(a: EventSubscription | TriggerDescri
3493
3503
  * event: 'email-received',
3494
3504
  * where: { parentFolderId: inboxId },
3495
3505
  * filters: [{ field: 'subject', contains: 'TestWaitFor' }],
3506
+ * connection: 'outlook', folder: 'shared', // bindings.json labels, both required
3496
3507
  * }))
3497
3508
  * ```
3498
3509
  *
@@ -1090,8 +1090,9 @@ export function connector(a, b, c, d) {
1090
1090
  export function subscriptionFrom(a, opts) {
1091
1091
  if (!isTriggerDescriptor(a))
1092
1092
  return a;
1093
- // No options at all is legal: an operation with no event parameters (HTTP
1094
- // Webhook) has nothing to put in them, and the descriptor carries the version.
1093
+ // No `where` is legal: an operation with no event parameters (HTTP Webhook)
1094
+ // has nothing to put in it, and the descriptor carries the version. The
1095
+ // `connection` / `folder` labels are still required (BINDING_MISSING).
1095
1096
  return {
1096
1097
  connector: a.connector,
1097
1098
  event: a.event,
@@ -5,7 +5,9 @@ export type BindingRefusalCode = 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED'
5
5
  /** An emitted definition's `<bindings.NAME>` placeholder has no `bindings[]` entry named NAME (#865). */
6
6
  | 'BINDING_PLACEHOLDER_UNRESOLVED'
7
7
  /** Two steps on one connection disagree on which connector it belongs to (#866). */
8
- | 'CONNECTION_CONNECTOR_CONFLICT';
8
+ | 'CONNECTION_CONNECTOR_CONFLICT'
9
+ /** A connector-bound node names no `connection` or no `folder` label (#890). */
10
+ | 'BINDING_MISSING';
9
11
  /**
10
12
  * What `serialize` throws for a label it refuses. It is an ordinary `Error` whose
11
13
  * message is the sentence `check` prints; the `code` lets `compile --no-check`
@@ -73,3 +75,22 @@ export declare function bindingUndeclaredMessage(where: string, name: string, at
73
75
  * (flow-builder-sdk#866).
74
76
  */
75
77
  export declare function connectionConnectorConflictMessage(connection: string, users: ReadonlyMap<string, readonly string[]>): string;
78
+ /**
79
+ * Which of a connector-bound node's two labels the author left out.
80
+ *
81
+ * A connector step, an event trigger, an `.entryPoint` onEvent and a
82
+ * `waitForEvent` all call Integration Service on a tenant connection, so the
83
+ * node needs BOTH halves: `connection` (→ `detail.connectionId`) and `folder`
84
+ * (→ `detail.folderKey`). Omitting a label used to compile the node with
85
+ * `connectionId: null` and no binding at all — `check` and `compile` were
86
+ * silent, and only `uip maestro flow validate` refused it ("A connection is
87
+ * required to run this activity." / "FolderKey is required for the connection
88
+ * binding.", flow-builder-sdk#890). Managed HTTP is NOT one of these sites: its
89
+ * pair is optional by design (absent = an unmanaged request).
90
+ */
91
+ export declare function missingBindingLabels(labels: {
92
+ connection?: unknown;
93
+ folder?: unknown;
94
+ } | undefined): ('connection' | 'folder')[];
95
+ /** Why compile refuses a connector-bound node with no `connection` / `folder` label, and how to bind it. */
96
+ export declare function bindingMissingMessage(where: string, missing: readonly ('connection' | 'folder')[], target?: PrepareTarget): string;
@@ -110,3 +110,30 @@ export function connectionConnectorConflictMessage(connection, users) {
110
110
  + `\`http({ managed: true, … })\` step on another connector's connection must name that connector in `
111
111
  + `\`targetConnector\`.`;
112
112
  }
113
+ /**
114
+ * Which of a connector-bound node's two labels the author left out.
115
+ *
116
+ * A connector step, an event trigger, an `.entryPoint` onEvent and a
117
+ * `waitForEvent` all call Integration Service on a tenant connection, so the
118
+ * node needs BOTH halves: `connection` (→ `detail.connectionId`) and `folder`
119
+ * (→ `detail.folderKey`). Omitting a label used to compile the node with
120
+ * `connectionId: null` and no binding at all — `check` and `compile` were
121
+ * silent, and only `uip maestro flow validate` refused it ("A connection is
122
+ * required to run this activity." / "FolderKey is required for the connection
123
+ * binding.", flow-builder-sdk#890). Managed HTTP is NOT one of these sites: its
124
+ * pair is optional by design (absent = an unmanaged request).
125
+ */
126
+ export function missingBindingLabels(labels) {
127
+ const blank = (v) => typeof v !== 'string' || v.trim() === '';
128
+ return ['connection', 'folder'].filter((k) => blank(labels?.[k]));
129
+ }
130
+ /** Why compile refuses a connector-bound node with no `connection` / `folder` label, and how to bind it. */
131
+ export function bindingMissingMessage(where, missing, target) {
132
+ return `${where} has no ${missing.map((m) => `\`${m}\``).join(' or ')}. It calls Integration Service on a tenant `
133
+ + `connection, so the node needs both labels — \`connection\` (the connection id) and \`folder\` (its folder key), `
134
+ + `each naming a bindings.json entry. Without them the .flow carries \`connectionId: null\` and `
135
+ + `\`uip maestro flow validate\` refuses it ("A connection is required to run this activity." / "FolderKey is `
136
+ + `required for the connection binding."). \`${prepareFor(target)}\` finds the connection and writes both entries; `
137
+ + `with nothing real to bind yet, declare both labels in bindings.json with a placeholder resourceKey (a `
138
+ + `CONNECTION_STUB warning, never an invented GUID).`;
139
+ }
@@ -185,6 +185,17 @@ use?: {
185
185
  id: string;
186
186
  connector: string | undefined;
187
187
  }): void;
188
+ /**
189
+ * BINDING_MISSING: a connector-bound node with no `connection` or no `folder`
190
+ * label — the sentence compile throws (see `requireBindingPair` in serialize).
191
+ * Unlike {@link checkBindingLabels} this needs no bindings.json: an ABSENT label
192
+ * is wrong whatever the file says, because the node then compiles with
193
+ * `connectionId: null` and `flow validate` refuses it (flow-builder-sdk#890).
194
+ */
195
+ export declare function checkBindingPresence(where: string, labels: {
196
+ connection?: unknown;
197
+ folder?: unknown;
198
+ } | undefined, report: (code: 'BINDING_MISSING', message: string) => void, target?: PrepareTarget): void;
188
199
  /**
189
200
  * The two phrases every lookup diagnostic needs: what the field sends, and how
190
201
  * to resolve it.
@@ -26,7 +26,7 @@ import { isLookupToken, resolvedValue, } from './lookups.js';
26
26
  import { Expr } from './expr.js';
27
27
  import { extractRefs } from './expr-check.js';
28
28
  import { prepareCommand, resolveFlagArg } from './cli-spelling.js';
29
- import { bindingSelfNameMessage, bindingUndeclaredMessage, connectionConnectorConflictMessage, undeclaredBinding, } from './binding-messages.js';
29
+ import { bindingMissingMessage, bindingSelfNameMessage, bindingUndeclaredMessage, connectionConnectorConflictMessage, missingBindingLabels, undeclaredBinding, } from './binding-messages.js';
30
30
  /**
31
31
  * Flow: an input is an {@link Expr} object. A non-literal one is runtime; a
32
32
  * literal one (`lit(…)`) carries the constant it was built from.
@@ -372,6 +372,18 @@ use) {
372
372
  }
373
373
  }
374
374
  }
375
+ /**
376
+ * BINDING_MISSING: a connector-bound node with no `connection` or no `folder`
377
+ * label — the sentence compile throws (see `requireBindingPair` in serialize).
378
+ * Unlike {@link checkBindingLabels} this needs no bindings.json: an ABSENT label
379
+ * is wrong whatever the file says, because the node then compiles with
380
+ * `connectionId: null` and `flow validate` refuses it (flow-builder-sdk#890).
381
+ */
382
+ export function checkBindingPresence(where, labels, report, target) {
383
+ const missing = missingBindingLabels(labels);
384
+ if (missing.length > 0)
385
+ report('BINDING_MISSING', bindingMissingMessage(where, missing, target));
386
+ }
375
387
  /**
376
388
  * The two phrases every lookup diagnostic needs: what the field sends, and how
377
389
  * to resolve it.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * One namespace for a flow's declared variables.
3
+ *
4
+ * `.input()`, `.output()` and `.var()` each look like their own namespace in the
5
+ * builder, but the `.flow` stores all three as rows of ONE list,
6
+ * `variables.globals[]`, keyed by `id`:
7
+ *
8
+ * .input({ x }) → { id: 'x', direction: 'in' }
9
+ * .output({ x }) → { id: 'x', direction: 'out' }
10
+ * .var('x') → { id: 'x', direction: 'inout' }
11
+ *
12
+ * The product refuses two rows with one id (`uip maestro flow validate`:
13
+ * `Duplicate variable ID: "x" (appears 2 times)`). Before flow-builder-sdk#890,
14
+ * `check` and `compile` were silent and wrote both rows.
15
+ *
16
+ * The common shape is `.output({ summary }) + .var('summary')` — "the output
17
+ * the arms assign". The remedy keeps the output and renames the var:
18
+ * `.var('draftSummary')` + `.return({ summary: v('draftSummary') })`. The output
19
+ * then stays a declared `out` global — the direction the Flow docs and the eval
20
+ * checkers (`direction == "out"`) treat as the flow's output contract. Dropping the name from `.output()` also
21
+ * validates — a `.var()` is an `inout` global, which the product counts as an
22
+ * output too — but the flow then has no declared `out` for it.
23
+ */
24
+ export type GlobalKind = 'input' | 'output' | 'var';
25
+ export interface GlobalNameCollision {
26
+ name: string;
27
+ /** Every declaration of the name, in declaration order (`input`, then `output`, then `var`). */
28
+ kinds: GlobalKind[];
29
+ }
30
+ /** The names declared more than once across `.input()`, `.output()` and `.var()`. */
31
+ export declare function globalNameCollisions(inputs: readonly {
32
+ name: string;
33
+ }[], outputs: readonly {
34
+ name: string;
35
+ }[], vars: readonly {
36
+ name: string;
37
+ }[]): GlobalNameCollision[];
38
+ /** The sentence `check` (VARIABLE_NAME_COLLISION) prints and `serialize` throws for one collision. */
39
+ export declare function globalNameCollisionMessage({ name, kinds }: GlobalNameCollision): string;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * One namespace for a flow's declared variables.
3
+ *
4
+ * `.input()`, `.output()` and `.var()` each look like their own namespace in the
5
+ * builder, but the `.flow` stores all three as rows of ONE list,
6
+ * `variables.globals[]`, keyed by `id`:
7
+ *
8
+ * .input({ x }) → { id: 'x', direction: 'in' }
9
+ * .output({ x }) → { id: 'x', direction: 'out' }
10
+ * .var('x') → { id: 'x', direction: 'inout' }
11
+ *
12
+ * The product refuses two rows with one id (`uip maestro flow validate`:
13
+ * `Duplicate variable ID: "x" (appears 2 times)`). Before flow-builder-sdk#890,
14
+ * `check` and `compile` were silent and wrote both rows.
15
+ *
16
+ * The common shape is `.output({ summary }) + .var('summary')` — "the output
17
+ * the arms assign". The remedy keeps the output and renames the var:
18
+ * `.var('draftSummary')` + `.return({ summary: v('draftSummary') })`. The output
19
+ * then stays a declared `out` global — the direction the Flow docs and the eval
20
+ * checkers (`direction == "out"`) treat as the flow's output contract. Dropping the name from `.output()` also
21
+ * validates — a `.var()` is an `inout` global, which the product counts as an
22
+ * output too — but the flow then has no declared `out` for it.
23
+ */
24
+ /** The names declared more than once across `.input()`, `.output()` and `.var()`. */
25
+ export function globalNameCollisions(inputs, outputs, vars) {
26
+ const seen = new Map();
27
+ const add = (kind, list) => {
28
+ for (const { name } of list) {
29
+ const kinds = seen.get(name);
30
+ if (kinds)
31
+ kinds.push(kind);
32
+ else
33
+ seen.set(name, [kind]);
34
+ }
35
+ };
36
+ add('input', inputs);
37
+ add('output', outputs);
38
+ add('var', vars);
39
+ return [...seen].filter(([, kinds]) => kinds.length > 1).map(([name, kinds]) => ({ name, kinds }));
40
+ }
41
+ const SPELLING = {
42
+ input: (name) => `.input({ ${name} })`,
43
+ output: (name) => `.output({ ${name} })`,
44
+ var: (name) => `.var('${name}')`,
45
+ };
46
+ /** The sentence `check` (VARIABLE_NAME_COLLISION) prints and `serialize` throws for one collision. */
47
+ export function globalNameCollisionMessage({ name, kinds }) {
48
+ const declared = kinds.map((k) => SPELLING[k](name)).join(' and ');
49
+ const head = `"${name}" is declared ${kinds.length} times (${declared}). The .flow keeps inputs, outputs and `
50
+ + `variables in one list, variables.globals[], so this compiles to ${kinds.length} globals with the id "${name}" `
51
+ + `and \`uip maestro flow validate\` refuses it: Duplicate variable ID: "${name}".`;
52
+ const distinct = new Set(kinds);
53
+ if (distinct.size === 2 && distinct.has('output') && distinct.has('var')) {
54
+ return `${head} Keep the output and give the variable its own name: `
55
+ + `.var('${name}Value', …) with { updates: { ${name}Value: … } }, then .return({ ${name}: v('${name}Value') }). `
56
+ + `(Dropping "${name}" from .output() also validates — a .var() compiles to an \`inout\` global, which the `
57
+ + `product counts as an output — but then the flow declares no \`out\` variable for it.)`;
58
+ }
59
+ return `${head} Rename one of them.`;
60
+ }
package/dist/decompile.js CHANGED
@@ -37,6 +37,8 @@ import { inferScriptShape } from './core/script-return.js';
37
37
  import { DEFAULT_TRIGGER_ID } from './core/expr.js';
38
38
  import { parseIxpProjectType } from './core/actions.js';
39
39
  import { readFlowGlobals } from './flow-variables.js';
40
+ import { missingBindingLabels } from './core/binding-messages.js';
41
+ import { prepareCommand } from './core/cli-spelling.js';
40
42
  import { OUTCOME_HANDLE_NODE_TYPES } from './core/hitl-routing.js';
41
43
  import { eventFilterFromTreeLeaf } from './core/event-filters.js';
42
44
  /**
@@ -1517,6 +1519,25 @@ function emitHitl(node, imp, inputNames, graph) {
1517
1519
  * `filterExpression` string, but reading the structured copies avoids parsing a
1518
1520
  * string this file generated.
1519
1521
  */
1522
+ /**
1523
+ * A connector-bound node the file carries UNBOUND: Studio Web saves a draft whose
1524
+ * connection was never picked as `connectionId: null`. The source cannot invent
1525
+ * labels for it, and `compile` refuses a node without both (BINDING_MISSING, the
1526
+ * rule `flow validate` applies too: flow-builder-sdk#890). So the source says so
1527
+ * at the node and the run warns once, instead of handing back a file whose first
1528
+ * compile fails with no context. Returns the inline marker, or '' when bound.
1529
+ */
1530
+ function unboundMarker(node, labels, key, action) {
1531
+ const missing = missingBindingLabels(labels);
1532
+ if (missing.length === 0)
1533
+ return '';
1534
+ const names = missing.join(' and ');
1535
+ warnOnce(`node ${str(node.id)} (${node.type}) has no ${missing.join(' or ')} in the .flow (an unbound draft). `
1536
+ + `The source leaves ${missing.length > 1 ? 'them' : 'it'} out, and compile refuses the node (BINDING_MISSING) `
1537
+ + `until it names both labels: run \`${prepareCommand(key, action ?? '<action>')}\`, then pass `
1538
+ + `\`connection\` and \`folder\`.`);
1539
+ return ` /* TODO: bind ${names}; compile refuses this node until it has both labels (BINDING_MISSING) */`;
1540
+ }
1520
1541
  function emitEventSubscription(node, imp, inputNames) {
1521
1542
  const prefix = node.type.startsWith('uipath.connector.event.') ? 'uipath.connector.event.'
1522
1543
  : node.type.startsWith('uipath.connector.trigger.') ? 'uipath.connector.trigger.'
@@ -1574,7 +1595,7 @@ function emitEventSubscription(node, imp, inputNames) {
1574
1595
  ['filters', filterRows.length ? `[${filterRows.join(', ')}]` : undefined],
1575
1596
  ['connection', conn !== undefined ? str(conn) : undefined],
1576
1597
  ['folder', folder !== undefined ? str(folder) : undefined],
1577
- ]);
1598
+ ]) + unboundMarker(node, { connection: conn, folder }, connector, event);
1578
1599
  }
1579
1600
  function emitPublishedResource(node, imp, inputNames) {
1580
1601
  const FAMILIES = [
@@ -1658,7 +1679,8 @@ function emitConnector(node, imp, inputNames, o) {
1658
1679
  ]);
1659
1680
  const inputsSrc = objLit(flat) || '{}';
1660
1681
  const args = [str(split.key), str(split.action), inputsSrc, ...(optsSrc ? [optsSrc] : [])];
1661
- return `${imp.use('connector')}(${args.join(', ')})`;
1682
+ return `${imp.use('connector')}(${args.join(', ')})`
1683
+ + unboundMarker(node, { connection, folder }, split.key, split.action);
1662
1684
  }
1663
1685
  /** Best-effort placeholder for a node type the reverse-map does not yet cover. */
1664
1686
  function unsupportedSpec(node, imp, o) {
@@ -148,6 +148,7 @@ export declare function manual(): TriggerSpec;
148
148
  * connector: 'uipath-microsoft-outlook365',
149
149
  * event: 'email-received',
150
150
  * where: { parentFolderId: inboxId },
151
+ * connection: 'outlook', folder: 'shared', // bindings.json labels, both required
151
152
  * }))
152
153
  * .step('extract', script({ code: 'return { subject: $vars.start.output.subject };' }))
153
154
  * .build();
@@ -740,12 +741,13 @@ declare class StepList {
740
741
  * ```ts
741
742
  * export default flow('draft-review')
742
743
  * .output({ summary: types.string })
743
- * .var('summary', types.string)
744
+ * // Not `.var('summary')`: an output and a var share one namespace (VARIABLE_NAME_COLLISION).
745
+ * .var('result', types.string)
744
746
  * .stepSwitch('review', hitl({ fields: [], outcomes: ['Publish', 'Revise'] }), [
745
- * { value: 'Publish', body: (b) => b.step('publish', script({ code: 'return "published";', returns: 'string' }), { updates: { summary: out('publish') } }) },
746
- * { value: 'Revise', body: (b) => b.step('sendBack', script({ code: 'return "revise";', returns: 'string' }), { updates: { summary: out('sendBack') } }) },
747
+ * { value: 'Publish', body: (b) => b.step('publish', script({ code: 'return "published";', returns: 'string' }), { updates: { result: out('publish') } }) },
748
+ * { value: 'Revise', body: (b) => b.step('sendBack', script({ code: 'return "revise";', returns: 'string' }), { updates: { result: out('sendBack') } }) },
747
749
  * ])
748
- * .return({ summary: v('summary') })
750
+ * .return({ summary: v('result') })
749
751
  * .build();
750
752
  * ```
751
753
  */
package/dist/flow-sdk.js CHANGED
@@ -332,12 +332,13 @@ class StepList {
332
332
  * ```ts
333
333
  * export default flow('draft-review')
334
334
  * .output({ summary: types.string })
335
- * .var('summary', types.string)
335
+ * // Not `.var('summary')`: an output and a var share one namespace (VARIABLE_NAME_COLLISION).
336
+ * .var('result', types.string)
336
337
  * .stepSwitch('review', hitl({ fields: [], outcomes: ['Publish', 'Revise'] }), [
337
- * { value: 'Publish', body: (b) => b.step('publish', script({ code: 'return "published";', returns: 'string' }), { updates: { summary: out('publish') } }) },
338
- * { value: 'Revise', body: (b) => b.step('sendBack', script({ code: 'return "revise";', returns: 'string' }), { updates: { summary: out('sendBack') } }) },
338
+ * { value: 'Publish', body: (b) => b.step('publish', script({ code: 'return "published";', returns: 'string' }), { updates: { result: out('publish') } }) },
339
+ * { value: 'Revise', body: (b) => b.step('sendBack', script({ code: 'return "revise";', returns: 'string' }), { updates: { result: out('sendBack') } }) },
339
340
  * ])
340
- * .return({ summary: v('summary') })
341
+ * .return({ summary: v('result') })
341
342
  * .build();
342
343
  * ```
343
344
  */
package/dist/serialize.js CHANGED
@@ -16,7 +16,8 @@ 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
18
  import { connectionConnectors } from './bindings-v2.js';
19
- import { BindingRefusal, GUID_RE, bindingSelfNameMessage, bindingUndeclaredMessage, undeclaredBinding } from './core/binding-messages.js';
19
+ import { globalNameCollisionMessage, globalNameCollisions } from './core/global-names.js';
20
+ import { BindingRefusal, GUID_RE, bindingMissingMessage, bindingSelfNameMessage, bindingUndeclaredMessage, missingBindingLabels, undeclaredBinding } from './core/binding-messages.js';
20
21
  import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
21
22
  import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
22
23
  import { declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
@@ -3484,6 +3485,27 @@ function refuseUnverifiedByoa(opts, where, sub) {
3484
3485
  if (message)
3485
3486
  throw new ByoaRefusal(message);
3486
3487
  }
3488
+ /** Refuse a name declared twice across `.input()` / `.output()` / `.var()` — one `variables.globals[]` id each. */
3489
+ function refuseGlobalNameCollisions(inputs, outputs, vars) {
3490
+ const [first] = globalNameCollisions(inputs, outputs, vars);
3491
+ if (first)
3492
+ throw new Error(globalNameCollisionMessage(first));
3493
+ }
3494
+ /**
3495
+ * Refuse a connector-bound node (connector step, event trigger, `.entryPoint`
3496
+ * onEvent, `waitForEvent`) that names no `connection` or no `folder` label.
3497
+ * (An inline agent's connector tool is #875's: `check` refuses it as
3498
+ * INLINE_AGENT_TOOL_CONNECTOR_NO_CONNECTION, and serialize leaves it unbound.)
3499
+ * `resolveBinding` returns `undefined` for an absent label, which used to reach
3500
+ * the node as `detail.connectionId: null` with no binding — valid to compile,
3501
+ * refused by `flow validate` (flow-builder-sdk#890). `check` reports the same
3502
+ * sentence first (BINDING_MISSING).
3503
+ */
3504
+ function requireBindingPair(where, labels, target) {
3505
+ const missing = missingBindingLabels(labels);
3506
+ if (missing.length > 0)
3507
+ throw new BindingRefusal('BINDING_MISSING', bindingMissingMessage(where, missing, target));
3508
+ }
3487
3509
  /**
3488
3510
  * Resolve a symbolic connection/folder name to the resource key that is inlined
3489
3511
  * as `connectionId` / `connectionFolderKey`.
@@ -3709,7 +3731,8 @@ function flattenWhere(where, declared) {
3709
3731
  walk(where, '');
3710
3732
  return out;
3711
3733
  }
3712
- function eventDetail(rc, sub, nodeId, opts) {
3734
+ function eventDetail(rc, sub, nodeId, opts, site) {
3735
+ requireBindingPair(site, sub, eventTarget(sub));
3713
3736
  const conn = resolveBinding(opts, sub.connection, 'connection', `event node "${nodeId}"`, eventTarget(sub));
3714
3737
  const folder = resolveBinding(opts, sub.folder, 'folder', `event node "${nodeId}"`, eventTarget(sub));
3715
3738
  const op = (rc.entry?.operation ?? {});
@@ -4420,6 +4443,7 @@ export function serialize(built, opts = {}) {
4420
4443
  for (const t of tails)
4421
4444
  addEdge(scope, t, endId);
4422
4445
  }
4446
+ refuseGlobalNameCollisions(child.inputs, child.outputs, child.vars);
4423
4447
  const globals = [];
4424
4448
  for (const inp of child.inputs)
4425
4449
  globals.push({ id: inp.name, name: inp.name, direction: 'in', type: inp.type, triggerNodeId: startId, ...varMeta(inp) });
@@ -4508,7 +4532,7 @@ export function serialize(built, opts = {}) {
4508
4532
  // `detail` (see eventDetail).
4509
4533
  entryPointId: stableId(`${built.id}:start:entryPoint`),
4510
4534
  isDefaultEntryPoint: true,
4511
- detail: eventDetail(eventTriggerRc, built.trigger.subscription, triggerId, opts),
4535
+ detail: eventDetail(eventTriggerRc, built.trigger.subscription, triggerId, opts, 'event trigger'),
4512
4536
  }
4513
4537
  : built.trigger?.kind === 'scheduled'
4514
4538
  ? scheduleInputs(built.trigger.inputs, triggerId)
@@ -4658,6 +4682,7 @@ export function serialize(built, opts = {}) {
4658
4682
  const id = stepUid(scope, step.name);
4659
4683
  const node = makeNode(rc.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def: rc.v1def });
4660
4684
  declareListOutputType(node, rc);
4685
+ requireBindingPair(`connector "${step.name}"`, spec, { key: spec.key, action: spec.action });
4661
4686
  const conn = resolveBinding(opts, spec.connection, 'connection', `connector "${step.name}"`, { key: spec.key, action: spec.action });
4662
4687
  const folder = resolveBinding(opts, spec.folder, 'folder', `connector "${step.name}"`, { key: spec.key, action: spec.action });
4663
4688
  // The platform reaches the connection through the flow's `bindings[]`,
@@ -5614,7 +5639,7 @@ export function serialize(built, opts = {}) {
5614
5639
  refuseUnverifiedByoa(opts, `waitForEvent "${step.name}"`, sub);
5615
5640
  const id = stepUid(scope, step.name);
5616
5641
  const node = makeNode(rc.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def: rc.v1def });
5617
- node.inputs = { detail: eventDetail(rc, sub, id, opts) };
5642
+ node.inputs = { detail: eventDetail(rc, sub, id, opts, `waitForEvent "${step.name}"`) };
5618
5643
  // Same connection-binding closure as the connector branch (G-19).
5619
5644
  const { entries: waitConnBindings } = connectionResourceBindings({
5620
5645
  def: rc.v1def,
@@ -6471,7 +6496,7 @@ export function serialize(built, opts = {}) {
6471
6496
  node = makeNode(rc.nodeType, ep.id, 'Event trigger', { def: rc.v1def, requestedVersion });
6472
6497
  node.inputs = {
6473
6498
  entryPointId: stableId(`${built.id}:${ep.id}:entryPoint`),
6474
- detail: eventDetail(rc, sub, ep.id, opts),
6499
+ detail: eventDetail(rc, sub, ep.id, opts, `onEvent() entry point "${ep.id}"`),
6475
6500
  };
6476
6501
  const { entries } = connectionResourceBindings({
6477
6502
  def: rc.v1def,
@@ -6506,6 +6531,9 @@ export function serialize(built, opts = {}) {
6506
6531
  }
6507
6532
  }
6508
6533
  }
6534
+ // Entry-point inputs are rows of the same globals[] (pushed below), so they
6535
+ // share the namespace; `check` reports these as ENTRY_POINT_INPUT_COLLISION.
6536
+ refuseGlobalNameCollisions([...built.inputs, ...entryPoints.flatMap((ep) => ep.inputs)], built.outputs, built.vars);
6509
6537
  const globals = [];
6510
6538
  const triggerNodeId = built.triggerId ?? 'start';
6511
6539
  for (const inp of built.inputs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.15.2",
3
+ "version": "6.15.3",
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": "0b7e3bb5985faa13fef763c45e3d69ef07a4d346"
95
+ "gitref": "a85d11af254f270d645ec46d57994ccb800ebc1f"
96
96
  }