@uipath/maestro-builder-sdk 6.46.1 → 6.46.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.
@@ -18,6 +18,8 @@ import { resolveEventWhere } from '../core/lookups.js';
18
18
  import { resolveCaseSubscription } from './subscription.js';
19
19
  import { externalDescriptorRefusal } from './external.js';
20
20
  import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
21
+ import { resourceIdentity } from './resource-identity.js';
22
+ import { ownEntry } from '../core/own-entry.js';
21
23
  import { resolveSchemaVersion } from '../schema-version.js';
22
24
  import { caseJsonPreservation, caseJsonPreservationErrors, } from './preserve.js';
23
25
  import { readEventFilter, eventFilterProblem, eventFilterTreeLeaf } from '../core/event-filters.js';
@@ -80,7 +82,8 @@ export function serialize(built, opts = {}) {
80
82
  bindings: opts.bindings,
81
83
  bindingSink: new Map(),
82
84
  varCompanions: new Map(),
83
- connectorOutputCounts: new Map(),
85
+ outputIds: new Set(),
86
+ ownOutputVars: ownOutputVars(built),
84
87
  slaId: new Map(),
85
88
  slaIdByName: new Map(),
86
89
  escalationId: new Map(),
@@ -248,7 +251,49 @@ export function serialize(built, opts = {}) {
248
251
  }
249
252
  : {}),
250
253
  };
251
- return applyAuthorLayout(applyPreservation(result, caseJsonPreservation(built)), built.layout);
254
+ return stampConnectorConfigVersion(applyAuthorLayout(applyPreservation(result, caseJsonPreservation(built)), built.layout));
255
+ }
256
+ /** The task types whose `data.context[]` carries an Integration Service activity configuration. */
257
+ const CONNECTOR_CONFIG_TASK_TYPES = new Set([
258
+ 'execute-connector-activity',
259
+ 'wait-for-connector',
260
+ 'external-agent',
261
+ 'external-workflow',
262
+ ]);
263
+ const V2_CONFIG_ROW = { name: 'activityConfigurationVersion', value: 'v2', type: 'string' };
264
+ /**
265
+ * Flagged-V2 plans: mark each connector task's and event trigger's context
266
+ * `activityConfigurationVersion: v2`, as the Designer and CLI do. Runs
267
+ * post-preservation because the flag is preserved metadata.
268
+ *
269
+ * Only a context bound to a connection is marked. A placeholder bag
270
+ * (`connectorKey` + `operation`, or a trigger's lone `objectName`) has no
271
+ * configuration panel to pick. A context that already carries the row keeps it.
272
+ */
273
+ function stampConnectorConfigVersion(result) {
274
+ const metadata = result.metadata;
275
+ if (metadata?.intsvcActivityConfig !== 'v2')
276
+ return result;
277
+ const stamp = (context) => {
278
+ if (!Array.isArray(context))
279
+ return;
280
+ if (!context.some((row) => row?.name === 'connection'))
281
+ return;
282
+ if (context.some((row) => row?.name === V2_CONFIG_ROW.name))
283
+ return;
284
+ context.push({ ...V2_CONFIG_ROW });
285
+ };
286
+ for (const node of result.nodes ?? []) {
287
+ if (node?.type === 'case-management:Trigger' || node?.type === 'uipath.case.trigger')
288
+ stamp(node.data?.[CASE_FORMAT_PROFILE.triggerBagField]?.context);
289
+ for (const lane of node?.data?.tasks ?? []) {
290
+ for (const task of Array.isArray(lane) ? lane : []) {
291
+ if (CONNECTOR_CONFIG_TASK_TYPES.has(task?.type))
292
+ stamp(task.data?.context);
293
+ }
294
+ }
295
+ }
296
+ return result;
252
297
  }
253
298
  const CASE_NODE_LAYOUT_FIELDS = ['position', 'style', 'measured', 'width', 'height', 'zIndex'];
254
299
  /**
@@ -480,6 +525,18 @@ function applyPreservedIdentities(result, preservation) {
480
525
  const triggerNodes = nodes.filter((node) => node.type === 'case-management:Trigger' || node.type === 'uipath.case.trigger');
481
526
  (identities.triggers ?? []).forEach((trigger, index) => take(triggerNodes[index], trigger.id));
482
527
  const stages = nodes.filter((node) => String(node.type).endsWith('Stage'));
528
+ // Every task-output row id in the plan, kept current as preserved ids are
529
+ // taken. Output ids are unique plan-wide (`claimOutputId`); a preserved id that
530
+ // another row already holds would undo that. Example: an older SDK wrote two
531
+ // action rows both `{ id: 'action', var: 'action' }`; this pass gives the
532
+ // second `action2`, and taking its preserved `action` would make it a duplicate.
533
+ const heldOutputIds = new Set();
534
+ for (const stage of stages)
535
+ for (const lane of stage.data?.tasks ?? [])
536
+ for (const task of lane)
537
+ for (const row of task?.data?.outputs ?? [])
538
+ if (typeof row?.id === 'string')
539
+ heldOutputIds.add(row.id);
483
540
  for (const stageIdentity of identities.stages ?? []) {
484
541
  const stage = stages.find((candidate) => candidate.data?.label === stageIdentity.label);
485
542
  take(stage, stageIdentity.id);
@@ -504,7 +561,19 @@ function applyPreservedIdentities(result, preservation) {
504
561
  // A positional match never takes a row a name match owns.
505
562
  : named.has(outputRows[index]?.name) ? undefined : outputRows[index];
506
563
  const currentId = target?.id;
564
+ // An own-name row's id IS its variable's name (`emitTaskOutput`). A
565
+ // preserved id that differs — an `o<hash>` slot from a plan an older
566
+ // SDK wrote — would rename the variable out from under every
567
+ // `=vars.<name>` reader, so the typed rule wins and the row migrates.
568
+ if (target && OWN_NAME_OUTPUT_ROWS.has(target) && row.id !== target.id)
569
+ return;
570
+ if (target && typeof row.id === 'string' && row.id !== currentId && heldOutputIds.has(row.id))
571
+ return;
507
572
  take(target, row.id);
573
+ if (target && typeof row.id === 'string' && typeof currentId === 'string') {
574
+ heldOutputIds.delete(currentId);
575
+ heldOutputIds.add(row.id);
576
+ }
508
577
  if (typeof currentId === 'string' && typeof row.id === 'string' && target?.value === currentId) {
509
578
  target.value = row.id;
510
579
  }
@@ -841,7 +910,12 @@ function taskData(task, resolver, taskId, stageId, ids) {
841
910
  // companion so `=vars.<var>` resolves downstream.
842
911
  if (!task.ref)
843
912
  return {};
844
- const data = { name: task.ref.name, folderPath: task.ref.folderPath };
913
+ // The resource is named through a name/folderPath pair of root bindings,
914
+ // never literals: `validate` refuses a literal (CASE_MGMT_RESOURCE_TASK_
915
+ // LITERAL_BINDING), and deploy re-targets a resource only through its
916
+ // binding (#1240).
917
+ const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids);
918
+ const data = { name: nameRef, folderPath: folderRef };
845
919
  const elementId = `${stageId}-${taskId}`;
846
920
  if (task.inputs?.length)
847
921
  data.inputs = task.inputs.map((inp, i) => emitTaskInput(inp, elementId, ids, `${elementId}:in:${i}:${inp.name}`));
@@ -1061,10 +1135,15 @@ function emitActionField(f, outputElementId, resolver) {
1061
1135
  if (f.value !== undefined)
1062
1136
  row.value = f.value;
1063
1137
  if (outputElementId !== undefined && f.value !== undefined) {
1064
- row.id = f.value;
1138
+ // `id == var` like the Designer; a second form output into the same case
1139
+ // variable takes a suffixed id and becomes a reassigned row (`originalVar`).
1140
+ const id = resolver ? claimOutputId(resolver, f.value) : f.value;
1141
+ row.id = id;
1065
1142
  row.var = f.value;
1143
+ if (id !== f.value)
1144
+ row.originalVar = id;
1066
1145
  row.source = `=${f.name}`;
1067
- row.target = `=${f.value}`;
1146
+ row.target = `=${id}`;
1068
1147
  row.elementId = outputElementId;
1069
1148
  if (resolver && !resolver.varCompanions.has(f.value)) {
1070
1149
  resolver.varCompanions.set(f.value, {
@@ -1087,13 +1166,43 @@ function emitTaskInput(inp, elementId, ids, seed) {
1087
1166
  const slot = ids.make('v', 8, seed);
1088
1167
  return { name: inp.name, type: inp.type, id: slot, var: slot, elementId, value: inp.value };
1089
1168
  }
1090
- /** One `data.outputs[]` row (the extract shape): `source` field → the `var` case variable. */
1169
+ /** Rows `emitTaskOutput` wrote in the own-name shape (`id == var`); see applyPreservedIdentities. */
1170
+ const OWN_NAME_OUTPUT_ROWS = new WeakSet();
1171
+ /**
1172
+ * One `data.outputs[]` row: `source` field → the `var` case variable. Two shapes,
1173
+ * as the Case Designer writes them:
1174
+ *
1175
+ * - **own name** — the output keeps its field's name (a top-level `source` whose
1176
+ * field is the `var`, or the Designer's camelCase of it, either one optionally
1177
+ * with a numeric suffix: `=APIOutput1` into `APIOutput1`, `aPIOutput1` or
1178
+ * `aPIOutput12`; see `keepsOwnName`). The row's `id` IS the `var`, with no
1179
+ * `originalVar`: `{ id: 'APIOutput1', var: 'APIOutput1', target: '=APIOutput1' }`.
1180
+ * - **reassigned** — the output feeds a differently named variable (`=age` into
1181
+ * `estimatedAge`, or any nested `=Error.Message`). The row keeps its own minted
1182
+ * slot `id`, points `var` at the case variable, and repeats the slot in
1183
+ * `originalVar`, which is how the Designer recognises a reassignment.
1184
+ *
1185
+ * Output ids are unique across the plan (`claimOutputId`). When an own-name id
1186
+ * is already taken — a second task writing the same variable — the row takes
1187
+ * the suffixed id (`APIOutput12`) and falls back to the reassigned shape, so its
1188
+ * `var` still names the shared case variable.
1189
+ */
1091
1190
  function emitTaskOutput(o, elementId, ids, seed, resolver) {
1092
- const slot = ids.make('o', 8, seed);
1093
- const field = o.source.replace(/^=/, '').split('.').pop() || o.source;
1191
+ const path = o.source.replace(/^=/, '');
1192
+ const field = path.split('.').pop() || o.source;
1094
1193
  // Register the readable companion once (declared vars take precedence in the merge).
1095
1194
  if (!resolver.varCompanions.has(o.var))
1096
1195
  resolver.varCompanions.set(o.var, { id: o.var, name: o.var, type: o.type, elementId: 'root', custom: true });
1196
+ if (isOwnNameOutput(o)) {
1197
+ const id = claimOutputId(resolver, o.var);
1198
+ if (id === o.var) {
1199
+ const row = { name: field, type: o.type, id, var: o.var, value: o.var, source: o.source, target: `=${id}`, elementId };
1200
+ OWN_NAME_OUTPUT_ROWS.add(row);
1201
+ return row;
1202
+ }
1203
+ return { name: field, type: o.type, id, var: o.var, originalVar: id, value: o.var, source: o.source, target: `=${id}`, elementId };
1204
+ }
1205
+ const slot = claimOutputId(resolver, ids.make('o', 8, seed));
1097
1206
  return {
1098
1207
  name: field,
1099
1208
  type: o.type,
@@ -1117,32 +1226,70 @@ const BUSINESS_RULE_TASK_VERSION = 'v3';
1117
1226
  * pattern) is a different namespace, and a binding that uses it is not a
1118
1227
  * BusinessRule resource to the bindings_v2 reader. */
1119
1228
  const BUSINESS_RULE_RESOURCE = 'BusinessRule';
1120
- /** Mint (once per resourceKey + attribute) one root `BusinessRule` binding. */
1121
- function businessRuleBinding(resolver, ids, resourceKey, propertyAttribute, value) {
1229
+ /**
1230
+ * The root binding `resource` and `resourceSubType` each published-resource
1231
+ * task kind is bound by — the Studio Web / v1-skill table (`bindings/impl-json.md`
1232
+ * § Per Task Type). Every kind but the business rule binds as a `process`; the
1233
+ * subtype tells the platform which kind of process. An RPA process and a Flow
1234
+ * carry no subtype on the binding.
1235
+ */
1236
+ const REFERENCE_BINDING = {
1237
+ process: { resource: 'process', subType: 'ProcessOrchestration' },
1238
+ agent: { resource: 'process', subType: 'Agent' },
1239
+ rpa: { resource: 'process' },
1240
+ 'api-workflow': { resource: 'process', subType: 'Api' },
1241
+ 'case-management': { resource: 'process', subType: 'CaseManagement' },
1242
+ 'flow-process': { resource: 'process' },
1243
+ };
1244
+ /**
1245
+ * Mint (once per resource + resourceKey + attribute) one root binding row of a
1246
+ * resource task's name/folderPath pair, and return its id. Two tasks on one
1247
+ * resource share the pair: a second pair under the same key is what validate
1248
+ * reads as two resources claiming one binding (CASE_MGMT_BINDING_KEY_SHARED).
1249
+ */
1250
+ function resourceBinding(resolver, ids, binding, resourceKey, propertyAttribute, value) {
1122
1251
  for (const entry of resolver.bindingSink.values()) {
1123
- if (entry.resource === BUSINESS_RULE_RESOURCE && entry.resourceKey === resourceKey
1252
+ if (entry.resource === binding.resource && entry.resourceKey === resourceKey
1124
1253
  && entry.propertyAttribute === propertyAttribute)
1125
1254
  return entry.id;
1126
1255
  }
1127
- const id = ids.make('b', 8, `binding:${BUSINESS_RULE_RESOURCE}:${resourceKey}:${propertyAttribute}`);
1256
+ const id = ids.make('b', 8, `binding:${binding.resource}:${resourceKey}:${propertyAttribute}`);
1257
+ // All seven fields: Studio Web fails to render a case whose binding lacks
1258
+ // `name` or `type`.
1128
1259
  resolver.bindingSink.set(id, {
1129
1260
  id,
1130
1261
  name: propertyAttribute,
1131
1262
  type: 'string',
1132
- resource: BUSINESS_RULE_RESOURCE,
1263
+ resource: binding.resource,
1264
+ ...(binding.subType ? { resourceSubType: binding.subType } : {}),
1133
1265
  resourceKey,
1134
1266
  default: value,
1135
1267
  propertyAttribute,
1136
1268
  });
1137
1269
  return id;
1138
1270
  }
1271
+ /** The `=bindings.<id>` name/folderPath references of a published-resource task. */
1272
+ function referenceBindings(kind, ref, resolver, ids) {
1273
+ const binding = kind === 'business-rule'
1274
+ ? { resource: BUSINESS_RULE_RESOURCE }
1275
+ : ownEntry(REFERENCE_BINDING, kind);
1276
+ if (!binding)
1277
+ throw new Error(`task kind "${kind}" is not a published-resource task.`);
1278
+ // An in-solution resource (`folder: 'solution_folder'`) keeps the sentinel
1279
+ // in its key and binds an empty folderPath — see resource-identity.ts.
1280
+ const { resourceKey, folderPath } = resourceIdentity(ref.name, ref.folderPath);
1281
+ const nameId = resourceBinding(resolver, ids, binding, resourceKey, 'name', ref.name);
1282
+ const folderId = resourceBinding(resolver, ids, binding, resourceKey, 'folderPath', folderPath);
1283
+ return { nameRef: `=bindings.${nameId}`, folderRef: `=bindings.${folderId}` };
1284
+ }
1139
1285
  /**
1140
1286
  * The `data` of a `business-rule` task, as Studio Web writes it and as the
1141
1287
  * CLI's `tasks describe --type business-rule` + splice produce it:
1142
1288
  *
1143
1289
  * - `name` / `folderPath` are `=bindings.<id>` references to a `BusinessRule`
1144
1290
  * binding pair keyed `<folder>.<name>` (the runtime resolves the rule through
1145
- * them; `validate --strict` refuses literals);
1291
+ * them; `validate --strict` refuses literals). An in-solution rule is keyed
1292
+ * `solution_folder.<name>` with an empty folderPath default;
1146
1293
  * - `version: 'v3'`;
1147
1294
  * - `outputs` always starts with `output` (`source: '=result'`, the whole
1148
1295
  * decision-keyed result) and `Error` (`source: '=Error'`), followed by the
@@ -1150,10 +1297,7 @@ function businessRuleBinding(resolver, ids, resourceKey, propertyAttribute, valu
1150
1297
  * (`=<Decision>.<column>`).
1151
1298
  */
1152
1299
  function businessRuleTaskData(task, resolver, elementId, ids) {
1153
- const ref = task.ref;
1154
- const resourceKey = `${ref.folderPath}.${ref.name}`;
1155
- const nameId = businessRuleBinding(resolver, ids, resourceKey, 'name', ref.name);
1156
- const folderId = businessRuleBinding(resolver, ids, resourceKey, 'folderPath', ref.folderPath);
1300
+ const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids);
1157
1301
  const ownRow = (name, source, seed, extra = {}) => {
1158
1302
  const slot = ids.make('v', 8, `${elementId}:${seed}`);
1159
1303
  return { name, type: 'jsonSchema', id: slot, var: slot, value: slot, source, target: `=${slot}`, elementId, ...extra };
@@ -1169,8 +1313,8 @@ function businessRuleTaskData(task, resolver, elementId, ids) {
1169
1313
  ...(task.outputs ?? []).map((o, i) => emitTaskOutput(o, elementId, ids, `${elementId}:out:${i}:${o.var}`, resolver)),
1170
1314
  ];
1171
1315
  return {
1172
- name: `=bindings.${nameId}`,
1173
- folderPath: `=bindings.${folderId}`,
1316
+ name: nameRef,
1317
+ folderPath: folderRef,
1174
1318
  version: BUSINESS_RULE_TASK_VERSION,
1175
1319
  inputs: (task.inputs ?? []).map((inp, i) => emitTaskInput(inp, elementId, ids, `${elementId}:in:${i}:${inp.name}`)),
1176
1320
  outputs,
@@ -1431,9 +1575,76 @@ function connectorMetadata(rc, op) {
1431
1575
  /** The `outputs[]`: a `response` json-schema from the op's output schema, plus a
1432
1576
  * standard `Error` schema. */
1433
1577
  function connectorOutputVar(resolver, base) {
1434
- const occurrence = (resolver.connectorOutputCounts.get(base) ?? 0) + 1;
1435
- resolver.connectorOutputCounts.set(base, occurrence);
1436
- return occurrence === 1 ? base : `${base}${occurrence}`;
1578
+ return claimOutputId(resolver, base);
1579
+ }
1580
+ /**
1581
+ * Claim a task-output row id from the document-wide pool, the Designer's
1582
+ * uniqueness rule: the first claimant of `base` gets `base`, later ones get
1583
+ * `base2`, `base3`, … (the counter starts at 2 — the Designer-saved plan has
1584
+ * `action` / `action2` and `error` / `error2`). Every output emitter (resource,
1585
+ * action and connector tasks) claims through here, so no two task outputs in a
1586
+ * plan share an id.
1587
+ */
1588
+ function claimOutputId(resolver, base) {
1589
+ let id = base;
1590
+ // A suffix never lands on a variable another output keeps as its own name
1591
+ // (`ownOutputVars`): with `aPIOutput1`, a second writer of it, and a task that
1592
+ // writes `aPIOutput12` itself, the second writer takes `aPIOutput13` whatever
1593
+ // order the tasks come in.
1594
+ for (let n = 2; resolver.outputIds.has(id) || (id !== base && resolver.ownOutputVars.has(id)); n++)
1595
+ id = `${base}${n}`;
1596
+ resolver.outputIds.add(id);
1597
+ return id;
1598
+ }
1599
+ /** Whether a task output is written in the own-name shape (see `emitTaskOutput`). */
1600
+ function isOwnNameOutput(o) {
1601
+ const path = o.source.replace(/^=/, '');
1602
+ return !path.includes('.') && keepsOwnName(o.var, path);
1603
+ }
1604
+ /**
1605
+ * Every variable a task in the plan writes under its own name: own-name task
1606
+ * outputs and action form outputs (whose row id is the variable). Collected
1607
+ * before any row is emitted, so `claimOutputId` can keep suffixes off them.
1608
+ */
1609
+ function ownOutputVars(built) {
1610
+ const vars = new Set();
1611
+ for (const stage of built.stages)
1612
+ for (const lane of stage.lanes)
1613
+ for (const task of lane) {
1614
+ for (const o of task.outputs ?? [])
1615
+ if (isOwnNameOutput(o))
1616
+ vars.add(o.var);
1617
+ for (const f of task.action?.outputs ?? [])
1618
+ if (f.value !== undefined)
1619
+ vars.add(f.value);
1620
+ }
1621
+ return vars;
1622
+ }
1623
+ /**
1624
+ * Whether an output into `name` keeps its field's name: `name` is the field, or
1625
+ * the Designer's camelCase of it, optionally followed by a numeric suffix. The
1626
+ * suffix is how the Designer names the variable a second writer of the same field
1627
+ * gets (`=APIOutput1` → `aPIOutput12`, `=Action` → `action2`): it saves that row
1628
+ * as `{ id: 'aPIOutput12', var: 'aPIOutput12' }` with no `originalVar`, so the
1629
+ * suffixed spelling is an own-name output too, and round-trips as one.
1630
+ */
1631
+ function keepsOwnName(name, field) {
1632
+ // Only an identifier field: `camelizeName` drops other characters, so `items[0]`
1633
+ // would otherwise match `items0`. The suffix is the Designer's counter, which
1634
+ // starts at 2 and has no leading zero: for the field `APIOutput1`, `APIOutput12`
1635
+ // matches; `APIOutput11` (suffix 1) and `APIOutput102` (suffix 02) do not.
1636
+ if (!/^[A-Za-z_]\w*$/.test(field))
1637
+ return false;
1638
+ return [field, camelizeName(field)].some((base) => name === base || (name.startsWith(base) && /^(?:[2-9]|[1-9]\d+)$/.test(name.slice(base.length))));
1639
+ }
1640
+ /**
1641
+ * The Designer's `camelizeDisplayName`: drop non-identifier characters, split on
1642
+ * spaces/underscores, lower the first letter of the first word and upper the
1643
+ * first letter of the rest (`APIOutput1` → `aPIOutput1`).
1644
+ */
1645
+ function camelizeName(name) {
1646
+ const words = name.replace(/[^a-zA-Z0-9_ ]/g, '').split(/[\s_]+/).filter(Boolean);
1647
+ return words.map((w, i) => (i === 0 ? w.charAt(0).toLowerCase() : w.charAt(0).toUpperCase()) + w.slice(1)).join('');
1437
1648
  }
1438
1649
  /** The `Error` output's schema: the designer's (and the CLI's) `ERROR_OUTPUT_SCHEMA`. */
1439
1650
  const ERROR_OUTPUT_SCHEMA = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.46.1",
3
+ "version": "6.46.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",
@@ -93,5 +93,5 @@
93
93
  "@types/node": "^22.7.0",
94
94
  "esbuild": "^0.28.1"
95
95
  },
96
- "gitref": "c6f5b341abc0adecb1e1dd3bffee2126aa0f01cc"
96
+ "gitref": "b763fc50a8d6430ce9a6e3e3e6b79b7c8645a4b1"
97
97
  }