@uipath/maestro-builder-sdk 6.15.4 → 6.15.6

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.
@@ -259,6 +259,57 @@ const SLOT_FOR_LOCATION = {
259
259
  query: 'queryParameters',
260
260
  path: 'pathParameters',
261
261
  };
262
+ /**
263
+ * Fold a body keyed by the registry's dotted field ids into the nested request
264
+ * object those ids describe: `{"fields.project.key":"CE"}` → `{"fields":{"project":{"key":"CE"}}}`.
265
+ *
266
+ * `buildConnectorInputs` keeps the DOTTED keys on purpose — Flow's `bodyParameters` is a
267
+ * flat map and Flow's runtime (`convertDotNotationToNested`) nests it before the call.
268
+ * The Maestro BPMN runtime does not: `IntSvcSendTaskHandler` sends the `body` input as
269
+ * written, so a dotted key reaches the connector as a literal property name. Measured on
270
+ * flow-v2-run-12: the SDK's Jira `create-issue` body went out flat and Integration
271
+ * Service answered 400 `project: Specify a valid project ID or key`; the v1 arm, which
272
+ * writes the nested form by hand, created the ticket. A BPMN body therefore goes through
273
+ * this before it is serialised.
274
+ *
275
+ * A `[*]` segment names an array. On a leaf (`users[*]`) an array value is kept and a
276
+ * scalar becomes a one-element list, as the product's own conversion does; on an inner
277
+ * segment (`attachments[*].url`) each element of an array value becomes one object, and
278
+ * a scalar one. Later keys win over earlier ones at the same leaf; a nested object the
279
+ * author already wrote under a plain key is merged into, not replaced.
280
+ */
281
+ export function nestConnectorBody(flat) {
282
+ const out = {};
283
+ for (const [name, value] of Object.entries(flat)) {
284
+ const parts = connectorPath(name);
285
+ place(out, parts, 0, value);
286
+ }
287
+ return out;
288
+ }
289
+ function place(target, parts, index, value) {
290
+ const part = parts[index];
291
+ const last = index === parts.length - 1;
292
+ if (last) {
293
+ target[part.key] = part.array && !Array.isArray(value) ? [value] : value;
294
+ return;
295
+ }
296
+ if (part.array) {
297
+ const elements = Array.isArray(value) ? value : [value];
298
+ const existing = Array.isArray(target[part.key]) ? target[part.key] : [];
299
+ target[part.key] = elements.map((element, i) => {
300
+ const slot = isRecord(existing[i]) ? existing[i] : {};
301
+ place(slot, parts, index + 1, element);
302
+ return slot;
303
+ });
304
+ return;
305
+ }
306
+ const next = isRecord(target[part.key]) ? target[part.key] : {};
307
+ target[part.key] = next;
308
+ place(next, parts, index + 1, value);
309
+ }
310
+ function isRecord(value) {
311
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
312
+ }
262
313
  function connectorPath(name) {
263
314
  return name.split('.').map((raw) => ({
264
315
  key: raw.endsWith('[*]') ? raw.slice(0, -3) : raw,
@@ -9,12 +9,14 @@
9
9
  * `.waitForEvent()` node carries the same facts under its own names, and
10
10
  * `bpmn/check.ts` adapts one to the other.
11
11
  *
12
- * What BPMN does NOT carry is `filters` — payload filtering is a Flow surface —
13
- * so the `EVENT_FILTER_*` family simply does not fire there. The rules that do
14
- * are the ones about the SUBSCRIPTION: whether it resolves at all, whether a
15
- * generic event named its object, and whether its scope names parameters the
16
- * operation actually declares. `skill-bpmn-outlook-waitfor-email` in
17
- * `flow-v2-run-8` failed on exactly that last question.
12
+ * BPMN carries `filters` too, since flow-v2-run-12: both families write payload
13
+ * conditions into the subscription the platform reads (Flow's `filterExpression`,
14
+ * BPMN's `body.filters.expression`), lowered by the same grammar, so the
15
+ * `EVENT_FILTER_*` family fires for both. The subscription rules are the rest:
16
+ * whether it resolves at all, whether a generic event named its object, and whether
17
+ * its scope names parameters the operation actually declares.
18
+ * `skill-bpmn-outlook-waitfor-email` in `flow-v2-run-8` failed on exactly that last
19
+ * question, and in run 12 on having nowhere to put a payload condition.
18
20
  */
19
21
  import { type LookupResolutions } from './lookups.js';
20
22
  import { type ByoaEventFacts, type ByoaLedger } from './byoa.js';
@@ -9,12 +9,14 @@
9
9
  * `.waitForEvent()` node carries the same facts under its own names, and
10
10
  * `bpmn/check.ts` adapts one to the other.
11
11
  *
12
- * What BPMN does NOT carry is `filters` — payload filtering is a Flow surface —
13
- * so the `EVENT_FILTER_*` family simply does not fire there. The rules that do
14
- * are the ones about the SUBSCRIPTION: whether it resolves at all, whether a
15
- * generic event named its object, and whether its scope names parameters the
16
- * operation actually declares. `skill-bpmn-outlook-waitfor-email` in
17
- * `flow-v2-run-8` failed on exactly that last question.
12
+ * BPMN carries `filters` too, since flow-v2-run-12: both families write payload
13
+ * conditions into the subscription the platform reads (Flow's `filterExpression`,
14
+ * BPMN's `body.filters.expression`), lowered by the same grammar, so the
15
+ * `EVENT_FILTER_*` family fires for both. The subscription rules are the rest:
16
+ * whether it resolves at all, whether a generic event named its object, and whether
17
+ * its scope names parameters the operation actually declares.
18
+ * `skill-bpmn-outlook-waitfor-email` in `flow-v2-run-8` failed on exactly that last
19
+ * question, and in run 12 on having nowhere to put a payload condition.
18
20
  */
19
21
  import { isLookupToken, resolvedValue, unresolvedLookupMessage, } from './lookups.js';
20
22
  import { prepareCommand } from './cli-spelling.js';
@@ -27,9 +27,15 @@ export declare class Expr {
27
27
  */
28
28
  export declare function lit(value: unknown): Expr;
29
29
  /**
30
- * Reference a flow-level variable or input by name → `$vars.<name>`.
30
+ * Reference a flow variable or output by name → `$vars.<name>`.
31
31
  *
32
- * @param name - The variable or input as declared with `.var(...)` / `.input(...)`.
32
+ * @remarks
33
+ * Not for a flow INPUT: an input is published under the trigger's output, so read
34
+ * it with {@link input} (`$vars.start.output.<name>`). A bare `$vars.<input>` is
35
+ * null under any trigger but a manual one, and `check` and `uip maestro flow
36
+ * validate` both refuse it.
37
+ *
38
+ * @param name - The variable or output as declared with `.var(...)` / `.output(...)`.
33
39
  * @returns An {@link Expr} reading that variable at run time.
34
40
  */
35
41
  export declare function v(name: string): Expr;
package/dist/core/expr.js CHANGED
@@ -33,9 +33,15 @@ export function lit(value) {
33
33
  return new Expr(JSON.stringify(value), true, value);
34
34
  }
35
35
  /**
36
- * Reference a flow-level variable or input by name → `$vars.<name>`.
36
+ * Reference a flow variable or output by name → `$vars.<name>`.
37
37
  *
38
- * @param name - The variable or input as declared with `.var(...)` / `.input(...)`.
38
+ * @remarks
39
+ * Not for a flow INPUT: an input is published under the trigger's output, so read
40
+ * it with {@link input} (`$vars.start.output.<name>`). A bare `$vars.<input>` is
41
+ * null under any trigger but a manual one, and `check` and `uip maestro flow
42
+ * validate` both refuse it.
43
+ *
44
+ * @param name - The variable or output as declared with `.var(...)` / `.output(...)`.
39
45
  * @returns An {@link Expr} reading that variable at run time.
40
46
  */
41
47
  export function v(name) {
@@ -13,13 +13,11 @@
13
13
  * `Duplicate variable ID: "x" (appears 2 times)`). Before flow-builder-sdk#890,
14
14
  * `check` and `compile` were silent and wrote both rows.
15
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.
16
+ * One pair is NOT a collision: `.output({ x }) + .var('x')` with the same type is
17
+ * the loop-accumulator shape ("declare the result, then seed it"), and
18
+ * `planFlowGlobals` (`flow-variables.ts`) merges it into ONE `out` global that
19
+ * carries the var's default (flow-builder-sdk#899). Every other repeat of a name
20
+ * is reported with the sentence below.
23
21
  */
24
22
  export type GlobalKind = 'input' | 'output' | 'var';
25
23
  export interface GlobalNameCollision {
@@ -13,13 +13,11 @@
13
13
  * `Duplicate variable ID: "x" (appears 2 times)`). Before flow-builder-sdk#890,
14
14
  * `check` and `compile` were silent and wrote both rows.
15
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.
16
+ * One pair is NOT a collision: `.output({ x }) + .var('x')` with the same type is
17
+ * the loop-accumulator shape ("declare the result, then seed it"), and
18
+ * `planFlowGlobals` (`flow-variables.ts`) merges it into ONE `out` global that
19
+ * carries the var's default (flow-builder-sdk#899). Every other repeat of a name
20
+ * is reported with the sentence below.
23
21
  */
24
22
  /** The names declared more than once across `.input()`, `.output()` and `.var()`. */
25
23
  export function globalNameCollisions(inputs, outputs, vars) {
@@ -49,12 +47,5 @@ export function globalNameCollisionMessage({ name, kinds }) {
49
47
  const head = `"${name}" is declared ${kinds.length} times (${declared}). The .flow keeps inputs, outputs and `
50
48
  + `variables in one list, variables.globals[], so this compiles to ${kinds.length} globals with the id "${name}" `
51
49
  + `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
50
  return `${head} Rename one of them.`;
60
51
  }
package/dist/decompile.js CHANGED
@@ -1762,7 +1762,7 @@ function emitVariables(e, depth, flow, imp) {
1762
1762
  if (inputs.length)
1763
1763
  e.line(depth, `.input(${shape(inputs, true)})`);
1764
1764
  if (outputs.length)
1765
- e.line(depth, `.output(${shape(outputs, false)})`);
1765
+ e.line(depth, `.output(${shape(outputs, true)})`);
1766
1766
  for (const g of inouts) {
1767
1767
  const rich = declExpr(g, false, imp);
1768
1768
  const def = g.defaultValue !== undefined ? `, ${literal(g.defaultValue)}` : '';
@@ -2538,7 +2538,7 @@ function emitChildFlow(name, child, ctx) {
2538
2538
  };
2539
2539
  const parts = [`${ctx.imp.use('flow')}(${str(name)})`];
2540
2540
  // Variables.
2541
- const shape = (dir) => `{ ${globals.filter((g) => g.direction === dir).map((g) => `${key(g.name ?? g.id)}: ${declExpr(g, dir === 'in', ctx.imp)}`).join(', ')} }`;
2541
+ const shape = (dir) => `{ ${globals.filter((g) => g.direction === dir).map((g) => `${key(g.name ?? g.id)}: ${declExpr(g, dir !== 'inout', ctx.imp)}`).join(', ')} }`;
2542
2542
  if (globals.some((g) => g.direction === 'in'))
2543
2543
  parts.push(`.input(${shape('in')})`);
2544
2544
  if (globals.some((g) => g.direction === 'out'))
@@ -71,7 +71,11 @@ export interface VarSpec {
71
71
  subType?: string;
72
72
  /** JSON schema for complex type validation (object/array shapes). */
73
73
  schema?: Record<string, unknown>;
74
- /** Default value — inputs and vars only; the format forbids it on outputs. */
74
+ /**
75
+ * Default value — the variable's value before anything assigns it. On an
76
+ * output it is the starting value a `{ updates }` accumulator reads, e.g.
77
+ * `.output({ product: { type: types.number, default: 1 } })`.
78
+ */
75
79
  default?: unknown;
76
80
  }
77
81
  /**
@@ -377,7 +381,7 @@ export interface ReturnOptions extends NodeOptions {
377
381
  * `done` become `done` and `done2`.
378
382
  *
379
383
  * ```ts
380
- * .branch('checkTemperature', js`$vars.tempF > 60`,
384
+ * .branch('checkTemperature', js`${input('tempF')} > 60`,
381
385
  * (t) => t.return({}, { name: 'endNiceDay', label: 'Nice Day' }),
382
386
  * (e) => e.return({}, { name: 'endBringJacket', label: 'Bring a Jacket' }))
383
387
  * ```
@@ -741,7 +745,7 @@ declare class StepList {
741
745
  * ```ts
742
746
  * export default flow('draft-review')
743
747
  * .output({ summary: types.string })
744
- * // Not `.var('summary')`: an output and a var share one namespace (VARIABLE_NAME_COLLISION).
748
+ * // A working variable the arms assign; the return maps it onto the output.
745
749
  * .var('result', types.string)
746
750
  * .stepSwitch('review', hitl({ fields: [], outcomes: ['Publish', 'Revise'] }), [
747
751
  * { value: 'Publish', body: (b) => b.step('publish', script({ code: 'return "published";', returns: 'string' }), { updates: { result: out('publish') } }) },
@@ -1323,6 +1327,25 @@ declare class FlowBuilder extends StepList {
1323
1327
  /**
1324
1328
  * Declare the flow's outputs — what `.return(...)` binds values to.
1325
1329
  *
1330
+ * @remarks
1331
+ * An output is also a flow variable: `v('<name>')` reads it and a step's
1332
+ * `{ updates }` assigns it, so an accumulator needs no separate `.var()`. Give
1333
+ * it a starting value with the rich spelling, `{ type, default }`. A `.var()`
1334
+ * of the same name and type is merged into the output rather than emitted as a
1335
+ * second variable (its default becomes the output's); a different type is a
1336
+ * `check` error, `OUTPUT_VAR_MISMATCH`.
1337
+ *
1338
+ * @example
1339
+ * **A loop accumulator**
1340
+ * ```ts
1341
+ * flow('multiply')
1342
+ * .output({ product: { type: types.number, default: 1 } })
1343
+ * .loop('each', lit([2, 3, 4]), (b) => b
1344
+ * .step('mul', script({ code: 'return $vars.product * $vars.each.currentItem;', returns: 'number' }),
1345
+ * { updates: { product: out('mul') } }))
1346
+ * .return({ product: v('product') })
1347
+ * ```
1348
+ *
1326
1349
  * @param shape - Output names to type descriptors, e.g. `{ total: types.number }`.
1327
1350
  * @returns This builder, so calls chain.
1328
1351
  */
@@ -1384,7 +1407,7 @@ export declare function flow(id: string): FlowBuilder;
1384
1407
  * const reverse = flow('reverse-text')
1385
1408
  * .input({ text: types.string })
1386
1409
  * .output({ reversed: types.string })
1387
- * .step('rev', script({ code: 'return $vars.text.split("").reverse().join("");' }))
1410
+ * .step('rev', script({ code: 'return $vars.start.output.text.split("").reverse().join("");' }))
1388
1411
  * .return({ reversed: out('rev') });
1389
1412
  *
1390
1413
  * export default flow('reverse-outer')
package/dist/flow-sdk.js CHANGED
@@ -63,16 +63,14 @@ function assertShape(method, shape) {
63
63
  }
64
64
  /**
65
65
  * Normalize a `TypeDesc | VarSpec` declaration value into VarDecl fields.
66
- * Outputs reject `default` — the format only persists defaults on `in`/`inout`.
66
+ * Every direction takes a `default`: the format's `workflowVariableSchema` allows
67
+ * `defaultValue` on `in`, `out` and `inout` alike, and the v1 CLI's
68
+ * `variable add --direction out --default-value` writes one on an output.
67
69
  */
68
70
  function varDeclOf(method, name, value) {
69
71
  if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
70
72
  const spec = value;
71
73
  assertType(method, name, spec.type);
72
- if (method === 'output' && spec.default !== undefined) {
73
- throw new TypeError(`.output() declared ${JSON.stringify(name)} with a default, but the .flow format only persists `
74
- + `defaults on inputs and vars. Bind the value with .return() instead.`);
75
- }
76
74
  return {
77
75
  type: spec.type,
78
76
  ...(spec.default !== undefined ? { default: spec.default } : {}),
@@ -332,7 +330,7 @@ class StepList {
332
330
  * ```ts
333
331
  * export default flow('draft-review')
334
332
  * .output({ summary: types.string })
335
- * // Not `.var('summary')`: an output and a var share one namespace (VARIABLE_NAME_COLLISION).
333
+ * // A working variable the arms assign; the return maps it onto the output.
336
334
  * .var('result', types.string)
337
335
  * .stepSwitch('review', hitl({ fields: [], outcomes: ['Publish', 'Revise'] }), [
338
336
  * { value: 'Publish', body: (b) => b.step('publish', script({ code: 'return "published";', returns: 'string' }), { updates: { result: out('publish') } }) },
@@ -1096,6 +1094,25 @@ class FlowBuilder extends StepList {
1096
1094
  /**
1097
1095
  * Declare the flow's outputs — what `.return(...)` binds values to.
1098
1096
  *
1097
+ * @remarks
1098
+ * An output is also a flow variable: `v('<name>')` reads it and a step's
1099
+ * `{ updates }` assigns it, so an accumulator needs no separate `.var()`. Give
1100
+ * it a starting value with the rich spelling, `{ type, default }`. A `.var()`
1101
+ * of the same name and type is merged into the output rather than emitted as a
1102
+ * second variable (its default becomes the output's); a different type is a
1103
+ * `check` error, `OUTPUT_VAR_MISMATCH`.
1104
+ *
1105
+ * @example
1106
+ * **A loop accumulator**
1107
+ * ```ts
1108
+ * flow('multiply')
1109
+ * .output({ product: { type: types.number, default: 1 } })
1110
+ * .loop('each', lit([2, 3, 4]), (b) => b
1111
+ * .step('mul', script({ code: 'return $vars.product * $vars.each.currentItem;', returns: 'number' }),
1112
+ * { updates: { product: out('mul') } }))
1113
+ * .return({ product: v('product') })
1114
+ * ```
1115
+ *
1099
1116
  * @param shape - Output names to type descriptors, e.g. `{ total: types.number }`.
1100
1117
  * @returns This builder, so calls chain.
1101
1118
  */
@@ -1196,7 +1213,7 @@ export function flow(id) {
1196
1213
  * const reverse = flow('reverse-text')
1197
1214
  * .input({ text: types.string })
1198
1215
  * .output({ reversed: types.string })
1199
- * .step('rev', script({ code: 'return $vars.text.split("").reverse().join("");' }))
1216
+ * .step('rev', script({ code: 'return $vars.start.output.text.split("").reverse().join("");' }))
1200
1217
  * .return({ reversed: out('rev') });
1201
1218
  *
1202
1219
  * export default flow('reverse-outer')
@@ -11,3 +11,69 @@ export interface FlowGlobalDeclaration {
11
11
  }
12
12
  /** Read both supported Flow variable spellings into one declaration table. */
13
13
  export declare function readFlowGlobals(variables: unknown, strict?: boolean): FlowGlobalDeclaration[];
14
+ /** The declaration fields `planFlowGlobals` reads (structurally a `VarDecl`). */
15
+ export interface DeclaredVariable {
16
+ name: string;
17
+ type: string;
18
+ default?: unknown;
19
+ description?: string;
20
+ subType?: string;
21
+ schema?: Record<string, unknown>;
22
+ }
23
+ /** One reason the declared names cannot become one global each. */
24
+ export interface FlowGlobalsProblem {
25
+ code: 'VARIABLE_NAME_COLLISION' | 'OUTPUT_VAR_MISMATCH';
26
+ name: string;
27
+ message: string;
28
+ suggestion?: string;
29
+ }
30
+ /** What `variables.globals` will hold, before directions and trigger ids are stamped. */
31
+ export interface FlowGlobalsPlan<D extends DeclaredVariable = DeclaredVariable> {
32
+ /** Every `.output()`, in order. One with a same-named `.var()` carries that var's default. */
33
+ outputs: D[];
34
+ /** The `.var()` declarations NOT folded into an output, in order. */
35
+ vars: D[];
36
+ /** Names declared by both `.output()` and `.var()` and merged into one `out` global. */
37
+ merged: string[];
38
+ /** Collisions that would emit two globals with one id. Empty when the plan is emittable. */
39
+ problems: FlowGlobalsProblem[];
40
+ }
41
+ /**
42
+ * Turn a flow's `.input()` / `.output()` / `.var()` declarations into the
43
+ * globals the `.flow` file will carry — one per id, because the product's
44
+ * `flow validate` rejects a repeated id (`[variables.globals] Duplicate variable
45
+ * ID`), and the three methods share that one namespace.
46
+ *
47
+ * **An output and a var with the same name are one variable** (flow-builder-sdk#899).
48
+ * The natural loop accumulator is
49
+ * `.output({ product: types.number }).var('product', types.number, 1)`: declare
50
+ * the result, then seed it. Both declarations describe the same value, so they
51
+ * merge into ONE `direction: 'out'` global that carries the var's default.
52
+ *
53
+ * Why `out` and not `inout`, measured against `@uipath/flow-converter` 0.69 and
54
+ * `@uipath/maestro-sdk` (the code that turns a `.flow` into what runs):
55
+ * - only an `out` global is a flow OUTPUT at the root — `buildNodeOutputMappings`
56
+ * drops an End node's mapping for any id that is not `direction: 'out'`;
57
+ * - `inout` is published as an INPUT too — solution discovery puts it on the
58
+ * entry point's input schema — so `inout` would widen the caller's contract;
59
+ * - `{ updates }` on an `out` global is the same output mapping (`var: <id>`) as
60
+ * on an `inout` one, so a loop body can read and rewrite it mid-run;
61
+ * - the `default` on `<uipath:output>` is serialized (`collectVariablesFromNodes`)
62
+ * and a subflow seeds its `out` globals from `defaultValue ?? null`
63
+ * (`buildSeedTask`). The v1 CLI's `variable add --direction out
64
+ * --default-value` writes the same shape.
65
+ *
66
+ * Metadata: the output's `description` / `subType` / `schema` win, falling back
67
+ * to the var's; the default is whichever side declared one. A disagreement the
68
+ * merge would have to resolve by picking a side — a different `type`, two
69
+ * different defaults, two different `subType`s or `schema`s — is
70
+ * `OUTPUT_VAR_MISMATCH` instead, because silently choosing one changes what the
71
+ * flow computes. Every other repeated name (two vars, two outputs, an input
72
+ * reused as an output or var) stays `VARIABLE_NAME_COLLISION`
73
+ * (`core/global-names.ts`, flow-builder-sdk#890).
74
+ */
75
+ export declare function planFlowGlobals<D extends DeclaredVariable>(decl: {
76
+ inputs: readonly D[];
77
+ outputs: readonly D[];
78
+ vars: readonly D[];
79
+ }): FlowGlobalsPlan<D>;
@@ -1,3 +1,4 @@
1
+ import { globalNameCollisionMessage, globalNameCollisions } from './core/global-names.js';
1
2
  const CONTAINER_KEYS = new Set(['globals', 'nodes', 'variableUpdates']);
2
3
  const DIRECTIONS = new Set(['in', 'out', 'inout']);
3
4
  /** Read both supported Flow variable spellings into one declaration table. */
@@ -40,3 +41,86 @@ export function readFlowGlobals(variables, strict = false) {
40
41
  }
41
42
  return globals;
42
43
  }
44
+ const sameJson = (a, b) => JSON.stringify(a) === JSON.stringify(b);
45
+ /**
46
+ * Turn a flow's `.input()` / `.output()` / `.var()` declarations into the
47
+ * globals the `.flow` file will carry — one per id, because the product's
48
+ * `flow validate` rejects a repeated id (`[variables.globals] Duplicate variable
49
+ * ID`), and the three methods share that one namespace.
50
+ *
51
+ * **An output and a var with the same name are one variable** (flow-builder-sdk#899).
52
+ * The natural loop accumulator is
53
+ * `.output({ product: types.number }).var('product', types.number, 1)`: declare
54
+ * the result, then seed it. Both declarations describe the same value, so they
55
+ * merge into ONE `direction: 'out'` global that carries the var's default.
56
+ *
57
+ * Why `out` and not `inout`, measured against `@uipath/flow-converter` 0.69 and
58
+ * `@uipath/maestro-sdk` (the code that turns a `.flow` into what runs):
59
+ * - only an `out` global is a flow OUTPUT at the root — `buildNodeOutputMappings`
60
+ * drops an End node's mapping for any id that is not `direction: 'out'`;
61
+ * - `inout` is published as an INPUT too — solution discovery puts it on the
62
+ * entry point's input schema — so `inout` would widen the caller's contract;
63
+ * - `{ updates }` on an `out` global is the same output mapping (`var: <id>`) as
64
+ * on an `inout` one, so a loop body can read and rewrite it mid-run;
65
+ * - the `default` on `<uipath:output>` is serialized (`collectVariablesFromNodes`)
66
+ * and a subflow seeds its `out` globals from `defaultValue ?? null`
67
+ * (`buildSeedTask`). The v1 CLI's `variable add --direction out
68
+ * --default-value` writes the same shape.
69
+ *
70
+ * Metadata: the output's `description` / `subType` / `schema` win, falling back
71
+ * to the var's; the default is whichever side declared one. A disagreement the
72
+ * merge would have to resolve by picking a side — a different `type`, two
73
+ * different defaults, two different `subType`s or `schema`s — is
74
+ * `OUTPUT_VAR_MISMATCH` instead, because silently choosing one changes what the
75
+ * flow computes. Every other repeated name (two vars, two outputs, an input
76
+ * reused as an output or var) stays `VARIABLE_NAME_COLLISION`
77
+ * (`core/global-names.ts`, flow-builder-sdk#890).
78
+ */
79
+ export function planFlowGlobals(decl) {
80
+ const problems = [];
81
+ const varsByName = new Map(decl.vars.map((d) => [d.name, d]));
82
+ const mergeable = new Set();
83
+ for (const collision of globalNameCollisions(decl.inputs, decl.outputs, decl.vars)) {
84
+ const pair = collision.kinds.length === 2 && collision.kinds[0] === 'output' && collision.kinds[1] === 'var';
85
+ if (pair)
86
+ mergeable.add(collision.name);
87
+ else
88
+ problems.push({ code: 'VARIABLE_NAME_COLLISION', name: collision.name, message: globalNameCollisionMessage(collision) });
89
+ }
90
+ const merged = [];
91
+ const outputs = decl.outputs.map((o) => {
92
+ const vd = varsByName.get(o.name);
93
+ if (!vd || !mergeable.has(o.name))
94
+ return o;
95
+ const clash = [];
96
+ if (o.type !== vd.type)
97
+ clash.push(`the output is ${o.type} and the var is ${vd.type}`);
98
+ if (o.default !== undefined && vd.default !== undefined && !sameJson(o.default, vd.default))
99
+ clash.push(`the output's default is ${JSON.stringify(o.default)} and the var's is ${JSON.stringify(vd.default)}`);
100
+ if (o.subType !== undefined && vd.subType !== undefined && o.subType !== vd.subType)
101
+ clash.push(`the output's subType is ${JSON.stringify(o.subType)} and the var's is ${JSON.stringify(vd.subType)}`);
102
+ if (o.schema !== undefined && vd.schema !== undefined && !sameJson(o.schema, vd.schema))
103
+ clash.push(`the output and the var declare different schemas`);
104
+ if (clash.length) {
105
+ problems.push({
106
+ code: 'OUTPUT_VAR_MISMATCH', name: o.name,
107
+ message: `"${o.name}" is declared by both .output() and .var(), which the SDK merges into one output `
108
+ + `variable, but ${clash.join('; ')}. Make the two declarations agree, or drop the .var().`,
109
+ suggestion: `An output is assignable with { updates } on its own; declare its starting value with `
110
+ + `.output({ ${o.name}: { type: types.${o.type}, default: … } }).`,
111
+ });
112
+ return o;
113
+ }
114
+ merged.push(o.name);
115
+ return {
116
+ ...o,
117
+ ...(o.default === undefined && vd.default !== undefined ? { default: vd.default } : {}),
118
+ ...(o.description === undefined && vd.description !== undefined ? { description: vd.description } : {}),
119
+ ...(o.subType === undefined && vd.subType !== undefined ? { subType: vd.subType } : {}),
120
+ ...(o.schema === undefined && vd.schema !== undefined ? { schema: vd.schema } : {}),
121
+ };
122
+ });
123
+ const mergedSet = new Set(merged);
124
+ const vars = decl.vars.filter((vd) => !mergedSet.has(vd.name));
125
+ return { outputs, vars, merged, problems };
126
+ }
@@ -1,4 +1,4 @@
1
- import { reportResult } from '../cli-result.js';
1
+ import { printResult, reportResult } from '../cli-result.js';
2
2
  import { join } from "node:path";
3
3
  import { CURRENT_LINK, PULL_COMMAND, currentEntrySha, libraryCacheRoot, resolveCachedLibrary, resolveCachedMarkdown, } from "./cache.js";
4
4
  /**
@@ -37,7 +37,7 @@ export async function run(argv) {
37
37
  console.error(`No connector library in the cache yet. Run \`${PULL_COMMAND}\`.`);
38
38
  return 1;
39
39
  }
40
- console.log(value);
40
+ printResult(value);
41
41
  return 0;
42
42
  };
43
43
  if (argv.includes("--library-json"))
@@ -56,7 +56,7 @@ export async function run(argv) {
56
56
  libraryMarkdown: libraryMarkdown ?? null,
57
57
  };
58
58
  reportResult(all);
59
- console.log(JSON.stringify(all, null, 2));
59
+ printResult(JSON.stringify(all, null, 2));
60
60
  // Not an error: a caller asking for the whole picture is entitled to be told
61
61
  // the cache is empty, rather than getting a non-zero exit for a plain report.
62
62
  return 0;
@@ -1,4 +1,4 @@
1
- import { reportResult } from '../cli-result.js';
1
+ import { printResult, reportResult } from '../cli-result.js';
2
2
  import { readFile, stat } from "node:fs/promises";
3
3
  import { PULL_COMMAND, resolveLibraryDir } from "./cache.js";
4
4
  import { dirname, join } from "node:path";
@@ -155,7 +155,7 @@ export async function run(argv) {
155
155
  limit,
156
156
  });
157
157
  reportResult(result);
158
- console.log(JSON.stringify(result, null, 2));
158
+ printResult(JSON.stringify(result, null, 2));
159
159
  return 0;
160
160
  }
161
161
  catch (error) {