@uipath/maestro-builder-sdk 6.32.3 → 6.32.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/check.js CHANGED
@@ -51,6 +51,36 @@ export function check(built, opts = {}) {
51
51
  }
52
52
  return diags;
53
53
  }
54
+ /**
55
+ * A step's node id is its name with every character outside `[A-Za-z0-9_]`
56
+ * made `_`, and `compile` never renames one to make it unique (#1249). So two
57
+ * different names that give one id (`a b` and `a_b`), or a step of the flow
58
+ * itself named like its trigger, are an error here, as the same name twice is
59
+ * DUP_STEP. `serialize` refuses both too, for the paths that skip `check`.
60
+ * A subflow's own start is the SDK's to name, so `trigger` is the flow's only.
61
+ */
62
+ function checkStepIds(stepNames, trigger, diags) {
63
+ const byId = new Map();
64
+ for (const name of stepNames) {
65
+ const id = name.replace(/[^A-Za-z0-9_]/g, '_') || 'node';
66
+ const first = byId.get(id);
67
+ if (first !== undefined) {
68
+ diags.push({
69
+ level: 'error', code: 'STEP_ID_COLLISION', step: name,
70
+ message: `Steps "${first}" and "${name}" would both get node id "${id}". Give them names that differ in `
71
+ + 'letters, digits or "_".',
72
+ });
73
+ }
74
+ else if (id === trigger) {
75
+ diags.push({
76
+ level: 'error', code: 'STEP_ID_COLLISION', step: name,
77
+ message: `Step "${name}" has the trigger's node id "${id}". Rename the step.`,
78
+ });
79
+ }
80
+ if (first === undefined)
81
+ byId.set(id, name);
82
+ }
83
+ }
54
84
  /**
55
85
  * `ancestors` is the chain of child flows currently being checked, innermost
56
86
  * last — used to prefix a child's diagnostics with the step that runs it, and
@@ -898,6 +928,9 @@ function checkFlow(built, ancestors, opts = {}) {
898
928
  }
899
929
  walk(ep.steps);
900
930
  }
931
+ // After the entry-point loop, so prefix steps are in `stepNames`: serialize
932
+ // claims body and prefix step ids together (#1249).
933
+ checkStepIds(stepNames, ancestors.length === 0 ? triggerId : undefined, diags);
901
934
  if (built.entryPoints?.length && !built.steps.some((st) => 'name' in st && st.kind !== 'return')) {
902
935
  diags.push({
903
936
  level: 'error', code: 'ENTRY_POINT_NO_SHARED_BODY',
package/dist/serialize.js CHANGED
@@ -4166,9 +4166,9 @@ function terminateOutputs(scope) {
4166
4166
  /**
4167
4167
  * Rewrite `$vars.<step>` references through a scope's rename map.
4168
4168
  *
4169
- * A name maps to the ids claimed for it in order; a reference resolves to the
4170
- * first, which is the only sensible reading when a scope has two steps of the
4171
- * same name (an error in its own right — DUP_STEP).
4169
+ * A name maps to the id claimed for it (a list only for the type's sake: a
4170
+ * scope with two steps of one name is refused before anything renders, #1249);
4171
+ * a reference resolves to the first.
4172
4172
  */
4173
4173
  function renameRefs(text, rename) {
4174
4174
  if (rename.nodeIds.size === 0 && rename.errorEnvelopes.size === 0 && !rename.vars?.size)
@@ -4189,29 +4189,38 @@ function renameRefs(text, rename) {
4189
4189
  return `$vars.${nodeId}.${envelope}`;
4190
4190
  }));
4191
4191
  }
4192
- /** Every step name a flow declares in its own scope (not its children's). */
4193
- function stepNamesOf(steps, out = []) {
4192
+ /**
4193
+ * Every step name a flow declares in its own scope (not its children's),
4194
+ * port paths (`.stepToList`, `.onError`) included: their steps are nodes of
4195
+ * this scope too, so they need an id claimed with the rest.
4196
+ */
4197
+ function stepNamesOf(steps, out = [], terminates) {
4194
4198
  for (const s of steps) {
4195
4199
  // `return` carries a synthetic `name` but creates no node named after it (an
4196
4200
  // End id is claimed as `end`); `stepToRef` has no `name` at all (it is one edge).
4197
4201
  if ('name' in s && s.kind !== 'return')
4198
4202
  out.push(s.name);
4203
+ // How many `.terminate`s hold each name, so a clash gets the right remedy (#1239).
4204
+ if (s.kind === 'terminate' && terminates)
4205
+ terminates.set(s.name, (terminates.get(s.name) ?? 0) + 1);
4206
+ if (s.kind === 'stepToList')
4207
+ stepNamesOf(s.body, out, terminates);
4199
4208
  if (s.kind === 'branch') {
4200
- stepNamesOf(s.then, out);
4201
- stepNamesOf(s.otherwise, out);
4209
+ stepNamesOf(s.then, out, terminates);
4210
+ stepNamesOf(s.otherwise, out, terminates);
4202
4211
  }
4203
4212
  else if (s.kind === 'switch') {
4204
- s.cases.forEach((c) => stepNamesOf(c.body, out));
4205
- stepNamesOf(s.default ?? [], out);
4213
+ s.cases.forEach((c) => stepNamesOf(c.body, out, terminates));
4214
+ stepNamesOf(s.default ?? [], out, terminates);
4206
4215
  }
4207
4216
  else if (s.kind === 'stepSwitch')
4208
- s.cases.forEach((c) => stepNamesOf(c.body, out));
4217
+ s.cases.forEach((c) => stepNamesOf(c.body, out, terminates));
4209
4218
  else if (s.kind === 'parallel')
4210
- s.arms.forEach((a) => stepNamesOf(a, out));
4219
+ s.arms.forEach((a) => stepNamesOf(a, out, terminates));
4211
4220
  else if (s.kind === 'loop')
4212
- stepNamesOf(s.body, out);
4221
+ stepNamesOf(s.body, out, terminates);
4213
4222
  else if (s.kind === 'doWhile')
4214
- stepNamesOf(s.body, out);
4223
+ stepNamesOf(s.body, out, terminates);
4215
4224
  }
4216
4225
  return out;
4217
4226
  }
@@ -4412,19 +4421,22 @@ export function serialize(built, opts = {}) {
4412
4421
  root.rename.nodeIds.set('start', [built.triggerId]);
4413
4422
  }
4414
4423
  // Every node id emitted anywhere in the file. `start` is taken before
4415
- // anything else: the root trigger's id is fixed, so a step named `start` must
4416
- // be renamed rather than silently collide with it.
4424
+ // anything else: the root trigger's id is fixed, so a step named like the
4425
+ // trigger is refused (claimStepIds).
4417
4426
  const takenIds = new Set([built.triggerId ?? 'start']);
4418
4427
  /** Per scope, each named `.return` End emitted so far and its content. */
4419
4428
  const namedEnds = new Map();
4420
4429
  /** Per scope, the names a `.terminate` claimed: which remedy a later clash gets (#1239). */
4421
4430
  const terminateNames = new Map();
4422
4431
  let edgeSeq = 0;
4423
- /** Claim a globally unique node id, honouring the scope's rename map. */
4424
- function uid(scope, base) {
4425
- const pre = scope.pending.get(base);
4426
- if (pre && pre.length > 0)
4427
- return pre.shift();
4432
+ /**
4433
+ * Claim a globally unique node id for a node the SDK names itself: an End,
4434
+ * a subflow's Start, an agent's context or tool node, an error-port End.
4435
+ * Those ids are the SDK's to choose, so a clash is settled by `claimId`'s
4436
+ * numeric suffix (`end`, `end2`, …). A step's id never comes from here; see
4437
+ * `stepUid`.
4438
+ */
4439
+ function uid(_scope, base) {
4428
4440
  return claimId(base);
4429
4441
  }
4430
4442
  function claimId(base) {
@@ -4499,18 +4511,82 @@ export function serialize(built, opts = {}) {
4499
4511
  }
4500
4512
  }
4501
4513
  /**
4502
- * `uid` for a node that IS an author's step, recording which id that step got.
4514
+ * Claim the node ids for every step of one scope, BEFORE any node of the
4515
+ * scope (or of a subflow it calls) is emitted, so an author's step name
4516
+ * always gets its id ahead of the ids the SDK makes up (#1249).
4517
+ *
4518
+ * A step's id is its name: the author's name, or a decompiled file's own
4519
+ * node id. It is never quietly renamed:
4520
+ *
4521
+ * - Two steps whose names give the same id (`.step('x')` twice, or `a b`
4522
+ * beside `a_b`) are refused. `check` reports them as DUP_STEP /
4523
+ * STEP_ID_COLLISION; this is the backstop for every path that skips it
4524
+ * (`compile --no-check`, the draft-tolerant merge compile, a direct
4525
+ * `serialize()`). Renaming the second to `x2` left `.stepToRef('x')` and
4526
+ * `$vars.x` pointing at the first, with no word said.
4527
+ * - In the flow itself, a step named like the trigger is refused the same
4528
+ * way (STEP_ID_COLLISION).
4529
+ * - A subflow body whose ids are already taken (a second copy of the same
4530
+ * child, or a child step sharing a name with a caller's step) is the one
4531
+ * place ids move: the whole copy is prefixed with the calling step's name,
4532
+ * and `rename` points its expressions at the prefixed ids.
4533
+ */
4534
+ function claimStepIds(scope, names, where, copyPrefix, alsoIds = [], terminates = new Map()) {
4535
+ const seen = new Map();
4536
+ for (const n of names) {
4537
+ const safe = n.replace(/[^A-Za-z0-9_]/g, '_') || 'node';
4538
+ const first = seen.get(safe);
4539
+ if (first !== undefined) {
4540
+ // A name a `.terminate` holds gets the same words `check` gives (#1239):
4541
+ // two Terminates -> end the other paths at one; a step and a Terminate
4542
+ // -> rename one.
4543
+ const t = first === n ? (terminates.get(n) ?? 0) : 0;
4544
+ throw new Error(first !== n
4545
+ ? `${where}: [STEP_ID_COLLISION] steps "${first}" and "${n}" would both get node id "${safe}". Give them `
4546
+ + 'names that differ in letters, digits or "_".'
4547
+ : t >= 2 ? `${where}: [DUP_STEP] ${terminateTwiceMessage(n)}`
4548
+ : t === 1 ? `${where}: [DUP_STEP] ${terminateStepClashMessage(n)}`
4549
+ : `${where}: [DUP_STEP] two steps are named "${n}". A step name is its node's id and \`.stepToRef\` / `
4550
+ + `\`$vars\` name it, so compile will not rename the second one "${n}2". Give each step its own name; to `
4551
+ + `send several paths to one step, keep one and end the others with .stepToRef('${n}').`);
4552
+ }
4553
+ seen.set(safe, n);
4554
+ }
4555
+ // A subflow copy's named Ends (`alsoIds`) decide the prefix with its steps (#1253).
4556
+ const prefix = copyPrefix !== undefined && [...seen.keys(), ...alsoIds].some((id) => takenIds.has(id)) ? copyPrefix : '';
4557
+ for (const [safe, n] of seen) {
4558
+ if (!prefix && takenIds.has(safe)) {
4559
+ throw new Error(`${where}: [STEP_ID_COLLISION] step "${n}" has the trigger's node id "${safe}", and compile will not `
4560
+ + `rename it "${safe}2". Rename the step.`);
4561
+ }
4562
+ const id = claimId(prefix + safe);
4563
+ if (copyPrefix !== undefined)
4564
+ (scope.rename.nodeIds.get(n) ?? scope.rename.nodeIds.set(n, []).get(n)).push(id);
4565
+ scope.pending.set(n, id);
4566
+ }
4567
+ return prefix;
4568
+ }
4569
+ /**
4570
+ * The node id of an author's step: the one `claimStepIds` claimed for it,
4571
+ * recorded so `.stepToRef('<step>')` can find it.
4503
4572
  *
4504
4573
  * `.stepToRef('<step>')` names a step, and the id it was emitted under is not
4505
- * derivable from the name: `uid` sanitizes, de-duplicates against every id in
4506
- * the file, and hands a subflow COPY a prefixed one from the rename map. So
4507
- * the mapping is recorded where it is decided rather than reconstructed later
4508
- * — the derived-names lesson (G-20), one layer in.
4574
+ * always the name: a subflow COPY's steps are prefixed. So the mapping is
4575
+ * recorded where it is decided rather than reconstructed later — the
4576
+ * derived-names lesson (G-20), one layer in.
4509
4577
  */
4510
4578
  function stepUid(scope, stepName) {
4511
- const id = uid(scope, stepName);
4512
- if (!scope.stepNodeId.has(stepName))
4513
- scope.stepNodeId.set(stepName, id);
4579
+ // Unreachable after `claimStepIds`, which refuses a repeated name first;
4580
+ // kept so no future emission path can bring back the silent `<name>2`.
4581
+ if (scope.stepNodeId.has(stepName)) {
4582
+ throw new Error(`step "${stepName}" is emitted twice in one scope (internal: DUP_STEP reached stepUid).`);
4583
+ }
4584
+ const id = scope.pending.get(stepName);
4585
+ if (id === undefined) {
4586
+ throw new Error(`step "${stepName}" has no claimed node id (internal: stepNamesOf missed a step kind).`);
4587
+ }
4588
+ scope.pending.delete(stepName);
4589
+ scope.stepNodeId.set(stepName, id);
4514
4590
  return id;
4515
4591
  }
4516
4592
  /**
@@ -4680,14 +4756,9 @@ export function serialize(built, opts = {}) {
4680
4756
  // namespace the `subflows` map keeps.
4681
4757
  // A child's named Ends belong to the copy too, so they decide the prefix
4682
4758
  // with its steps and move with it (#1253).
4683
- const names = stepNamesOf(child.steps);
4684
4759
  const childNodes = namedNodesOf(child.steps);
4685
- const prefix = [...names, ...childNodes.ends.map(safeNodeId)].some((n) => takenIds.has(n)) ? `${nodeId}_` : '';
4686
- for (const n of names) {
4687
- const id = claimId(prefix + n);
4688
- (scope.rename.nodeIds.get(n) ?? scope.rename.nodeIds.set(n, []).get(n)).push(id);
4689
- (scope.pending.get(n) ?? scope.pending.set(n, []).get(n)).push(id);
4690
- }
4760
+ const childTerminates = new Map();
4761
+ const prefix = claimStepIds(scope, stepNamesOf(child.steps, [], childTerminates), `subflow "${nodeId}"`, `${nodeId}_`, childNodes.ends.map(safeNodeId), childTerminates);
4691
4762
  claimEndIds(scope, childNodes, `subflow "${nodeId}"`, [], prefix);
4692
4763
  const startId = uid(scope, `${nodeId}Start`);
4693
4764
  // A CHILD's inputs are published as the child's own start node, whose id is
@@ -6653,10 +6724,17 @@ export function serialize(built, opts = {}) {
6653
6724
  }
6654
6725
  return tails;
6655
6726
  }
6656
- // The flow's named Ends (entry-point prefixes included: they emit into the
6657
- // root scope) are claimed before anything is emitted, so an id the SDK makes
6658
- // up is the one that moves (#1253).
6727
+ // The flow's own step ids (entry-point prefixes included: they emit into the
6728
+ // root scope) are claimed before anything else but the trigger, so a subflow
6729
+ // body or an SDK-made node that shares a step's name is the one that moves.
6730
+ // Its named Ends are claimed right after, before anything is emitted, so an
6731
+ // id the SDK makes up is the one that moves (#1253).
6659
6732
  {
6733
+ const rootTerminates = new Map();
6734
+ const rootSteps = stepNamesOf(built.steps, [], rootTerminates);
6735
+ for (const ep of built.entryPoints ?? [])
6736
+ stepNamesOf(ep.steps, rootSteps, rootTerminates);
6737
+ claimStepIds(root, rootSteps, `flow "${built.id}"`, undefined, [], rootTerminates);
6660
6738
  const seenEnds = new Set();
6661
6739
  const rootNodes = namedNodesOf(built.steps, undefined, seenEnds);
6662
6740
  for (const ep of built.entryPoints ?? [])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.32.3",
3
+ "version": "6.32.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",
@@ -93,5 +93,5 @@
93
93
  "@types/node": "^22.7.0",
94
94
  "esbuild": "^0.28.1"
95
95
  },
96
- "gitref": "ae1239906aef3bdd220cbea1ba2a09845f9fd4c7"
96
+ "gitref": "9a7e5f98ca054b05f74ac655b8725bf4e3b50e89"
97
97
  }