@uipath/maestro-builder-sdk 6.15.3 → 6.15.5

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.
@@ -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) {
package/dist/serialize.js CHANGED
@@ -16,7 +16,6 @@ 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 { globalNameCollisionMessage, globalNameCollisions } from './core/global-names.js';
20
19
  import { BindingRefusal, GUID_RE, bindingMissingMessage, bindingSelfNameMessage, bindingUndeclaredMessage, missingBindingLabels, undeclaredBinding } from './core/binding-messages.js';
21
20
  import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
22
21
  import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
@@ -46,6 +45,7 @@ import { declaredScriptShape, inferScriptShape } from './core/script-return.js';
46
45
  import { migrateToSchemaVersion } from './migrate.js';
47
46
  import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
48
47
  import { resolveSchemaVersion } from './schema-version.js';
48
+ import { planFlowGlobals } from './flow-variables.js';
49
49
  import { prepareCommand } from './core/cli-spelling.js';
50
50
  import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
51
51
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -3485,12 +3485,6 @@ function refuseUnverifiedByoa(opts, where, sub) {
3485
3485
  if (message)
3486
3486
  throw new ByoaRefusal(message);
3487
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
3488
  /**
3495
3489
  * Refuse a connector-bound node (connector step, event trigger, `.entryPoint`
3496
3490
  * onEvent, `waitForEvent`) that names no `connection` or no `folder` label.
@@ -3957,6 +3951,24 @@ function misplacedLookupMessage(token) {
3957
3951
  `a lookup() must be the whole value of a connector input, or an element of an array input. ` +
3958
3952
  `Emitting it here would send a raw {"__lookup": …} object where the connector expects an id.`);
3959
3953
  }
3954
+ /**
3955
+ * The flow's declarations as the globals it will emit: a same-named `.output()`
3956
+ * and `.var()` folded into one `out` global (flow-builder-sdk#899), every other
3957
+ * repeated name refused. `check()` reports the same problems as
3958
+ * VARIABLE_NAME_COLLISION / OUTPUT_VAR_MISMATCH; this throws too, so a path that skips the static pass
3959
+ * (`compile --no-check`, `emitFlow`, a direct `serialize()`) cannot write a
3960
+ * file `flow validate` rejects for a duplicate variable id.
3961
+ */
3962
+ function emittableGlobals(built, where) {
3963
+ // Entry-point inputs are rows of the same globals[], so they share the
3964
+ // namespace; `check` reports those as ENTRY_POINT_INPUT_COLLISION.
3965
+ const inputs = [...built.inputs, ...(built.entryPoints ?? []).flatMap((ep) => ep.inputs)];
3966
+ const plan = planFlowGlobals({ inputs, outputs: built.outputs, vars: built.vars });
3967
+ if (plan.problems.length) {
3968
+ throw new Error(`${where}: ${plan.problems.map((p) => `[${p.code}] ${p.message}`).join(' ')}`);
3969
+ }
3970
+ return plan;
3971
+ }
3960
3972
  function newScope(outputs, vars = []) {
3961
3973
  return {
3962
3974
  nodes: [],
@@ -4280,7 +4292,8 @@ export function serialize(built, opts = {}) {
4280
4292
  */
4281
4293
  const sidecars = opts.sidecars ?? [];
4282
4294
  const subflows = {};
4283
- const root = newScope(built.outputs, built.vars);
4295
+ const rootGlobals = emittableGlobals(built, `flow "${built.id}"`);
4296
+ const root = newScope(rootGlobals.outputs, rootGlobals.vars);
4284
4297
  // `input('x')` lowers eagerly to `$vars.start.output.x` — `start` being the
4285
4298
  // trigger node's default id. When the flow renames the trigger, register that
4286
4299
  // rename so the SAME pass that rewrites step-name references rewrites these
@@ -4404,7 +4417,8 @@ export function serialize(built, opts = {}) {
4404
4417
  * a null. It is emitted here, always, so it cannot be forgotten.
4405
4418
  */
4406
4419
  function emitSubflowBody(child, nodeId) {
4407
- const scope = newScope(child.outputs, child.vars);
4420
+ const childGlobals = emittableGlobals(child, `subflow "${nodeId}"`);
4421
+ const scope = newScope(childGlobals.outputs, childGlobals.vars);
4408
4422
  // Claim this copy's node ids UP FRONT, so the rename map is complete before
4409
4423
  // the first expression renders. A child's step names are free the first
4410
4424
  // time it runs; if any of them is already taken — which is what a second
@@ -4443,13 +4457,12 @@ export function serialize(built, opts = {}) {
4443
4457
  for (const t of tails)
4444
4458
  addEdge(scope, t, endId);
4445
4459
  }
4446
- refuseGlobalNameCollisions(child.inputs, child.outputs, child.vars);
4447
4460
  const globals = [];
4448
4461
  for (const inp of child.inputs)
4449
4462
  globals.push({ id: inp.name, name: inp.name, direction: 'in', type: inp.type, triggerNodeId: startId, ...varMeta(inp) });
4450
- for (const o of child.outputs)
4463
+ for (const o of childGlobals.outputs)
4451
4464
  globals.push({ id: o.name, name: o.name, direction: 'out', type: o.type, ...varMeta(o) });
4452
- for (const vd of child.vars)
4465
+ for (const vd of childGlobals.vars)
4453
4466
  globals.push({ id: vd.name, name: vd.name, direction: 'inout', type: vd.type, ...varMeta(vd) });
4454
4467
  resolveRejoins(scope);
4455
4468
  assertHitlExitInvariant(scope.nodes, scope.edges);
@@ -6531,16 +6544,13 @@ export function serialize(built, opts = {}) {
6531
6544
  }
6532
6545
  }
6533
6546
  }
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);
6537
6547
  const globals = [];
6538
6548
  const triggerNodeId = built.triggerId ?? 'start';
6539
6549
  for (const inp of built.inputs)
6540
6550
  globals.push({ id: inp.name, name: inp.name, direction: 'in', type: inp.type, triggerNodeId, ...varMeta(inp) });
6541
- for (const o of built.outputs)
6551
+ for (const o of rootGlobals.outputs)
6542
6552
  globals.push({ id: o.name, name: o.name, direction: 'out', type: o.type, ...varMeta(o) });
6543
- for (const vd of built.vars)
6553
+ for (const vd of rootGlobals.vars)
6544
6554
  globals.push({ id: vd.name, name: vd.name, direction: 'inout', type: vd.type, ...varMeta(vd) });
6545
6555
  for (const ep of entryPoints) {
6546
6556
  for (const inp of ep.inputs) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.15.3",
3
+ "version": "6.15.5",
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": "a85d11af254f270d645ec46d57994ccb800ebc1f"
95
+ "gitref": "7c0103ce6f6d970156e15bbc3bae0a3f88ca4195"
96
96
  }