@uipath/maestro-builder-sdk 6.46.2 → 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.
@@ -765,7 +765,7 @@ declare class TaskBuilder {
765
765
  * Reference a published Maestro process.
766
766
  *
767
767
  * @param name - The published process's name.
768
- * @param opts - `folder` — the Orchestrator folder it lives in.
768
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
769
769
  * @returns This builder, so calls chain.
770
770
  */
771
771
  process(name: string, opts?: {
@@ -788,7 +788,7 @@ declare class TaskBuilder {
788
788
  * Reference a published agent.
789
789
  *
790
790
  * @param name - The published agent's name.
791
- * @param opts - `folder` — the Orchestrator folder it lives in.
791
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
792
792
  * @returns This builder, so calls chain.
793
793
  */
794
794
  agent(name: string, opts?: {
@@ -798,7 +798,7 @@ declare class TaskBuilder {
798
798
  * Reference a published RPA process.
799
799
  *
800
800
  * @param name - The published RPA process's name.
801
- * @param opts - `folder` — the Orchestrator folder it lives in.
801
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
802
802
  * @returns This builder, so calls chain.
803
803
  */
804
804
  rpa(name: string, opts?: {
@@ -808,7 +808,7 @@ declare class TaskBuilder {
808
808
  * Reference a published API workflow.
809
809
  *
810
810
  * @param name - The published API workflow's name.
811
- * @param opts - `folder` — the Orchestrator folder it lives in.
811
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
812
812
  * @returns This builder, so calls chain.
813
813
  */
814
814
  apiWorkflow(name: string, opts?: {
@@ -820,7 +820,7 @@ declare class TaskBuilder {
820
820
  * io-binding as reference-mode tasks.
821
821
  *
822
822
  * @param name - The published child case's name.
823
- * @param opts - `folder` — the Orchestrator folder it lives in.
823
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
824
824
  * @returns This builder, so calls chain.
825
825
  */
826
826
  caseManagement(name: string, opts?: {
@@ -830,7 +830,7 @@ declare class TaskBuilder {
830
830
  * Reference a published Maestro Flow.
831
831
  *
832
832
  * @param name - The published Flow's name.
833
- * @param opts - `folder` — the Orchestrator folder it lives in.
833
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
834
834
  * @returns This builder, so calls chain.
835
835
  */
836
836
  flowProcess(name: string, opts?: {
@@ -190,7 +190,7 @@ class TaskBuilder {
190
190
  * Reference a published Maestro process.
191
191
  *
192
192
  * @param name - The published process's name.
193
- * @param opts - `folder` — the Orchestrator folder it lives in.
193
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
194
194
  * @returns This builder, so calls chain.
195
195
  */
196
196
  process(name, opts) {
@@ -216,7 +216,7 @@ class TaskBuilder {
216
216
  * Reference a published agent.
217
217
  *
218
218
  * @param name - The published agent's name.
219
- * @param opts - `folder` — the Orchestrator folder it lives in.
219
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
220
220
  * @returns This builder, so calls chain.
221
221
  */
222
222
  agent(name, opts) {
@@ -226,7 +226,7 @@ class TaskBuilder {
226
226
  * Reference a published RPA process.
227
227
  *
228
228
  * @param name - The published RPA process's name.
229
- * @param opts - `folder` — the Orchestrator folder it lives in.
229
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
230
230
  * @returns This builder, so calls chain.
231
231
  */
232
232
  rpa(name, opts) {
@@ -236,7 +236,7 @@ class TaskBuilder {
236
236
  * Reference a published API workflow.
237
237
  *
238
238
  * @param name - The published API workflow's name.
239
- * @param opts - `folder` — the Orchestrator folder it lives in.
239
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
240
240
  * @returns This builder, so calls chain.
241
241
  */
242
242
  apiWorkflow(name, opts) {
@@ -248,7 +248,7 @@ class TaskBuilder {
248
248
  * io-binding as reference-mode tasks.
249
249
  *
250
250
  * @param name - The published child case's name.
251
- * @param opts - `folder` — the Orchestrator folder it lives in.
251
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
252
252
  * @returns This builder, so calls chain.
253
253
  */
254
254
  caseManagement(name, opts) {
@@ -258,7 +258,7 @@ class TaskBuilder {
258
258
  * Reference a published Maestro Flow.
259
259
  *
260
260
  * @param name - The published Flow's name.
261
- * @param opts - `folder` — the Orchestrator folder it lives in.
261
+ * @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
262
262
  * @returns This builder, so calls chain.
263
263
  */
264
264
  flowProcess(name, opts) {
@@ -483,6 +483,17 @@ function checkStage(stage, diags, refStage, refPlacement) {
483
483
  message: `Task "${t.name}" binding "${'var' in invalidBinding ? invalidBinding.var : invalidBinding.name}" has unsupported type ${JSON.stringify(invalidBinding.type)}. Use a type name such as "string" or "jsonSchema"; jsonSchema(...) is only for declaring a structured case variable.`,
484
484
  where: t.name,
485
485
  });
486
+ // The builder binds a resource task itself: it mints the name/folderPath
487
+ // binding pair from the literal name and folder. A `=bindings.<id>` string
488
+ // passed as either becomes part of the binding key, which then names no
489
+ // resource (`=bindings.f.=bindings.n`) and dangles at deploy.
490
+ if (t.ref && [t.ref.name, t.ref.folderPath].some((v) => v.trim().startsWith('=bindings.')))
491
+ diags.push({
492
+ level: 'error',
493
+ code: 'RESOURCE_REF_IS_BINDING',
494
+ message: `Task "${t.name}" passes a "=bindings.<id>" reference as its resource name or folder. Pass the resource's own name and its folder, e.g. .agent('Contract Analysis', { folder: 'Shared/Legal' }); compile writes the name/folderPath binding pair keyed '<folder>.<name>' and points the task at it.`,
495
+ where: t.name,
496
+ });
486
497
  if (t.kind === 'business-rule') {
487
498
  if (!t.ref || !t.ref.name.trim() || !t.ref.folderPath.trim())
488
499
  diags.push({
@@ -292,7 +292,8 @@ function syncEntryPoints(caseplan, outPath) {
292
292
  * artifact. `resources refresh` reads bindings_v2.json, not caseplan.json, and
293
293
  * succeeds with zero warnings when the sidecar is stale or empty.
294
294
  *
295
- * Non-connector resources come from root binding pairs and referenced tasks.
295
+ * Non-connector resources come from the root binding rows only, as in
296
+ * `uip maestro case bindings sync`.
296
297
  * A connector's connection and folder relationship comes from the context[]
297
298
  * that references both binding ids.
298
299
  * No-op when compiling outside a scaffolded project.
@@ -308,65 +309,29 @@ function syncBindingsV2(caseplan, outPath) {
308
309
  .filter((binding) => typeof binding.id === 'string')
309
310
  .map((binding) => [binding.id, binding]));
310
311
  const resources = [];
311
- const grouped = new Map();
312
+ // One entry per (resource, resourceKey) group of root binding rows, in the
313
+ // shape `uip maestro case bindings sync` derives (`deriveCaseBindingsV2` in
314
+ // cli case-bindings-service.ts). `validate --strict` compares the two
315
+ // entry by entry as serialized JSON, so field order matters as well as
316
+ // field values.
317
+ const groups = new Map();
312
318
  for (const binding of bindings) {
313
319
  const resource = stringValue(binding.resource);
314
320
  const key = stringValue(binding.resourceKey);
315
- if (!resource || !key || resource.toLowerCase() === 'connection')
321
+ if (!resource || !key || typeof binding.propertyAttribute !== 'string')
322
+ continue;
323
+ if (resource.toLowerCase() === 'connection')
316
324
  continue;
317
325
  const groupKey = `${resource}\0${key}`;
318
- let group = grouped.get(groupKey);
319
- if (!group) {
320
- group = { resource, key, value: {} };
321
- grouped.set(groupKey, group);
322
- }
323
- const property = stringValue(binding.propertyAttribute) ?? stringValue(binding.name);
324
- // An in-solution resource binds an empty folderPath in the caseplan, while
325
- // the sidecar names its folder by the sentinel the key carries — the split
326
- // `uip maestro case bindings sync` writes (resource-identity.ts).
327
- const defaultValue = property === 'folderPath' && isInSolutionKey(key)
328
- ? SOLUTION_FOLDER
329
- : binding.default ?? key;
330
- if (property)
331
- group.value[property] = { defaultValue };
332
- const subType = stringValue(binding.resourceSubType);
333
- if (subType)
334
- group.subType = subType;
326
+ const group = groups.get(groupKey);
327
+ if (group)
328
+ group.push(binding);
329
+ else
330
+ groups.set(groupKey, [binding]);
335
331
  }
336
- const referenceTaskSubTypes = new Map([
337
- ['process', 'ProcessOrchestration'],
338
- ['agent', 'Agent'],
339
- ['rpa', undefined],
340
- ['api-workflow', 'Api'],
341
- ['case-management', 'CaseManagement'],
342
- ['flow-process', 'ProcessOrchestration'],
343
- ]);
344
332
  const seenConnections = new Set();
345
333
  const connectionResources = [];
346
334
  visitRecords(caseplan, (record) => {
347
- const type = stringValue(record.type);
348
- const data = isRecord(record.data) ? record.data : undefined;
349
- if (type && data && referenceTaskSubTypes.has(type)) {
350
- const name = referencedBindingValue(data.name, byId);
351
- const folderPath = referencedBindingValue(data.folderPath, byId);
352
- if (name && folderPath) {
353
- const key = referencedBindingResourceKey(data.name, byId)
354
- ?? referencedBindingResourceKey(data.folderPath, byId)
355
- ?? `${folderPath}.${name}`;
356
- const groupKey = `process\0${key}`;
357
- if (!grouped.has(groupKey)) {
358
- grouped.set(groupKey, {
359
- resource: 'process',
360
- key,
361
- value: {
362
- name: { defaultValue: name },
363
- folderPath: { defaultValue: folderPath },
364
- },
365
- subType: referenceTaskSubTypes.get(type),
366
- });
367
- }
368
- }
369
- }
370
335
  if (!Array.isArray(record.context))
371
336
  return;
372
337
  const context = new Map(record.context
@@ -389,23 +354,126 @@ function syncBindingsV2(caseplan, outPath) {
389
354
  metadata: { connector },
390
355
  });
391
356
  });
392
- for (const group of grouped.values()) {
393
- resources.push({
394
- resource: group.resource,
395
- key: group.key,
396
- value: group.value,
397
- // A BusinessRule entry carries the bindings version deploy needs to
398
- // override its folder — the metadata `uip maestro case bindings sync`
399
- // writes for it (no subType).
400
- metadata: group.subType
401
- ? { subType: group.subType }
402
- : group.resource === 'BusinessRule' ? { BindingsVersion: '2.2' } : {},
403
- });
404
- }
357
+ const labels = bindingLabels(caseplan.nodes);
358
+ for (const group of groups.values())
359
+ resources.push(sidecarResourceEntry(group, labels));
405
360
  resources.push(...connectionResources);
406
361
  writeJsonFile(sidecarPath, { version: '2.0', resources });
407
362
  return resources.length;
408
363
  }
364
+ /**
365
+ * The Case Designer's label for each value slot of an app or process entry.
366
+ * These resources get the Designer shape (cli#5003, MST-16424): an `id`, each
367
+ * slot flagged and labelled, and the `BindingsVersion` / `SolutionsSupport`
368
+ * metadata without which deploy cannot override the entry.
369
+ */
370
+ const DESIGNER_SLOT_NAMES = new Map([
371
+ ['app', { name: 'App name', folderPath: 'Folder path', solutionsSupport: true }],
372
+ ['process', { name: 'Process name', folderPath: 'Folder path', solutionsSupport: true }],
373
+ ['ixpDeployment', { name: 'name', folderPath: 'Folder path', solutionsSupport: false }],
374
+ ]);
375
+ /** The bindings version deploy needs to override an entry. */
376
+ const RESOURCE_BINDINGS_VERSION = '2.2';
377
+ /**
378
+ * One sidecar entry for a group of root binding rows that share a resource and
379
+ * a resource key. The subtype comes from the rows only: a Flow's rows carry
380
+ * none, so its entry has none either, as the Designer saves it.
381
+ *
382
+ * `metadata.ActivityName` labels a Designer-shaped entry with the first task
383
+ * that references one of its rows ({@link bindingLabels}).
384
+ */
385
+ function sidecarResourceEntry(group, labels) {
386
+ const resource = group[0].resource;
387
+ const key = group[0].resourceKey;
388
+ const attributeDefault = (attribute) => {
389
+ const found = group.find((binding) => binding.propertyAttribute === attribute)?.default;
390
+ return typeof found === 'string' ? found : undefined;
391
+ };
392
+ // An in-solution resource binds an empty folderPath in the caseplan, while
393
+ // the sidecar names its folder by the sentinel the key carries.
394
+ const folderPath = isInSolutionKey(key) ? SOLUTION_FOLDER : attributeDefault('folderPath');
395
+ const name = attributeDefault('name');
396
+ const subType = group.map((binding) => binding.resourceSubType).find((found) => found !== undefined);
397
+ const designer = DESIGNER_SLOT_NAMES.get(resource);
398
+ if (designer) {
399
+ const value = {};
400
+ if (folderPath !== undefined) {
401
+ value.folderPath = { defaultValue: folderPath, isExpression: false, displayName: designer.folderPath };
402
+ }
403
+ if (name !== undefined) {
404
+ value.name = { defaultValue: name, isExpression: false, displayName: designer.name };
405
+ }
406
+ const activityName = group
407
+ .map((binding) => (typeof binding.id === 'string' ? labels.get(binding.id) : undefined))
408
+ .find((found) => found !== undefined);
409
+ return {
410
+ resource,
411
+ key,
412
+ id: `${resource}${key}`,
413
+ value,
414
+ metadata: {
415
+ ...(activityName !== undefined && { ActivityName: activityName }),
416
+ BindingsVersion: RESOURCE_BINDINGS_VERSION,
417
+ DisplayLabel: 'name',
418
+ ...(designer.solutionsSupport && { SolutionsSupport: 'true' }),
419
+ ...(subType !== undefined && { subType }),
420
+ },
421
+ };
422
+ }
423
+ const value = {};
424
+ if (name !== undefined)
425
+ value.name = { defaultValue: name };
426
+ if (folderPath !== undefined)
427
+ value.folderPath = { defaultValue: folderPath };
428
+ // A solution-resource picker (such as a Data Fabric entity) carries its folder key as a slot of its own.
429
+ const folderKey = attributeDefault('folderKey');
430
+ if (folderKey !== undefined)
431
+ value.folderKey = { defaultValue: folderKey };
432
+ return {
433
+ resource,
434
+ key,
435
+ value,
436
+ metadata: {
437
+ BindingsVersion: RESOURCE_BINDINGS_VERSION,
438
+ ...(subType !== undefined && { subType }),
439
+ },
440
+ };
441
+ }
442
+ /**
443
+ * The label of the first object that references each binding id
444
+ * (`=bindings.<id>`), in document order: the nearest enclosing `displayName`,
445
+ * `display.label` or `label`. The same walk as the CLI's
446
+ * `collectBindingLabels`, so both name an entry after the same task.
447
+ */
448
+ function bindingLabels(nodes) {
449
+ const found = new Map();
450
+ const ownLabel = (node) => {
451
+ const display = isRecord(node.display) ? node.display.label : undefined;
452
+ return [node.displayName, display, node.label].map(stringValue).find((label) => label !== undefined);
453
+ };
454
+ const walk = (node, label) => {
455
+ if (typeof node === 'string') {
456
+ if (label !== undefined && node.startsWith('=bindings.')) {
457
+ const id = node.slice('=bindings.'.length);
458
+ if (!found.has(id))
459
+ found.set(id, label);
460
+ }
461
+ return;
462
+ }
463
+ if (Array.isArray(node)) {
464
+ for (const item of node)
465
+ walk(item, label);
466
+ return;
467
+ }
468
+ if (!isRecord(node))
469
+ return;
470
+ const scoped = ownLabel(node) ?? label;
471
+ for (const value of Object.values(node))
472
+ walk(value, scoped);
473
+ };
474
+ walk(nodes ?? [], undefined);
475
+ return found;
476
+ }
409
477
  function referencedBindingValue(value, bindings) {
410
478
  const ref = stringValue(value);
411
479
  if (!ref)
@@ -415,12 +483,6 @@ function referencedBindingValue(value, bindings) {
415
483
  const binding = bindings.get(ref.slice('=bindings.'.length));
416
484
  return binding && (stringValue(binding.default) ?? stringValue(binding.resourceKey));
417
485
  }
418
- function referencedBindingResourceKey(value, bindings) {
419
- const ref = stringValue(value);
420
- if (!ref?.startsWith('=bindings.'))
421
- return undefined;
422
- return stringValue(bindings.get(ref.slice('=bindings.'.length))?.resourceKey);
423
- }
424
486
  function visitRecords(value, visit) {
425
487
  if (Array.isArray(value)) {
426
488
  for (const item of value)
@@ -344,11 +344,24 @@ function bindingName(value, refs) {
344
344
  const id = value.slice('=bindings.'.length);
345
345
  return refs.bindingById.get(id)?.id ?? id;
346
346
  }
347
- function bindingDefault(value, refs) {
347
+ /**
348
+ * The literal a resource task's `data.name` / `data.folderPath` names: the
349
+ * value itself, or the `default` of the root binding row a `=bindings.<id>`
350
+ * reference points at. A reference the plan's root `bindings[]` cannot answer
351
+ * (no such row, or a row with no `default`) is refused: the plan names no
352
+ * resource there, and emitting the reference as the name would only fail
353
+ * again at compile.
354
+ */
355
+ function bindingDefault(value, refs, owner, field) {
348
356
  if (typeof value !== 'string' || !value.startsWith('=bindings.'))
349
357
  return value;
350
358
  const id = value.slice('=bindings.'.length);
351
- return refs.bindingById.get(id)?.default ?? value;
359
+ const row = refs.bindingById.get(id);
360
+ if (typeof row?.default !== 'string')
361
+ throw new Error(`${owner} data.${field} is "${value}", but the plan's root bindings ${row ? `row "${id}" has no default` : `declare no row "${id}"`}, `
362
+ + `so decompile cannot recover the resource ${field === 'name' ? 'name' : 'folder'}. `
363
+ + `Give the task a name/folderPath binding pair whose rows carry the resource's name and folder as their defaults (what Studio Web writes), then decompile again.`);
364
+ return row.default;
352
365
  }
353
366
  /**
354
367
  * The folder a resource task's `folderPath` reference names, as the author
@@ -357,13 +370,13 @@ function bindingDefault(value, refs) {
357
370
  * (resource-identity.ts), so lift the sentinel back rather than the empty
358
371
  * default, which would recompile to the key `.<name>`.
359
372
  */
360
- function bindingFolder(value, refs) {
373
+ function bindingFolder(value, refs, owner) {
361
374
  if (typeof value === 'string' && value.startsWith('=bindings.')) {
362
375
  const row = refs.bindingById.get(value.slice('=bindings.'.length));
363
376
  if (typeof row?.resourceKey === 'string' && isInSolutionKey(row.resourceKey))
364
377
  return SOLUTION_FOLDER;
365
378
  }
366
- return bindingDefault(value, refs);
379
+ return bindingDefault(value, refs, owner, 'folderPath');
367
380
  }
368
381
  function selectNextStageByExit(plan, refs) {
369
382
  const raw = plan.metadata?.waitForHumanSelectNextStageDFConnector;
@@ -606,9 +619,12 @@ function emitTaskKind(task, refs, library) {
606
619
  throw new Error(`unresolved ${kind} task "${task.displayName ?? task.id ?? '?'}" cannot carry input or output bindings.`);
607
620
  return `.unresolved(${tsLiteral(kind)})`;
608
621
  }
609
- const name = bindingDefault(data.name, refs);
610
- const folderPath = bindingDefault(data.folderPath, refs);
611
- const folder = folderPath ? `, ${opts([['folder', tsLiteral(folderPath)]])}` : '';
622
+ const owner = `${kind} task "${task.displayName ?? task.id ?? '?'}"`;
623
+ const name = bindingDefault(data.name, refs, owner, 'name');
624
+ const folderPath = bindingFolder(data.folderPath, refs, owner);
625
+ // No folder already means the case's own solution folder
626
+ // (resource-identity.ts), so an in-solution resource decompiles to none.
627
+ const folder = folderPath && folderPath !== SOLUTION_FOLDER ? `, ${opts([['folder', tsLiteral(folderPath)]])}` : '';
612
628
  return `.${method}(${tsLiteral(name ?? '')}${folder})`;
613
629
  }
614
630
  if (isAuthorableBusinessRule(task))
@@ -674,8 +690,8 @@ function isBusinessRuleOwnOutput(row) {
674
690
  function businessRuleCall(task, refs) {
675
691
  const data = task.data ?? {};
676
692
  const owner = `business-rule task "${task.displayName ?? task.id ?? '?'}"`;
677
- const name = bindingDefault(data.name, refs);
678
- const folderPath = bindingFolder(data.folderPath, refs);
693
+ const name = bindingDefault(data.name, refs, owner, 'name');
694
+ const folderPath = bindingFolder(data.folderPath, refs, owner);
679
695
  if (typeof name !== 'string' || !name || typeof folderPath !== 'string')
680
696
  throw new Error(`${owner} must name its rule and folder (data.name / data.folderPath).`);
681
697
  const outputRow = (Array.isArray(data.outputs) ? data.outputs : [])
@@ -30,7 +30,15 @@ export interface ResourceIdentity {
30
30
  /** The `default` of the `folderPath` binding row. */
31
31
  folderPath: string;
32
32
  }
33
- /** The binding identity of resource `name` authored in `folder`. */
33
+ /**
34
+ * The binding identity of resource `name` authored in `folder`. No folder
35
+ * means the case's own solution folder, the same as `solution_folder`:
36
+ *
37
+ * .process('notify') -> resourceKey 'solution_folder.notify', folderPath ''
38
+ *
39
+ * (A literal `.notify` key would name no folder, and deploy and `resources
40
+ * refresh` could not match it.)
41
+ */
34
42
  export declare function resourceIdentity(name: string, folder: string): ResourceIdentity;
35
43
  /** True for the key of a resource built inside the same solution. */
36
44
  export declare function isInSolutionKey(resourceKey: string): boolean;
@@ -25,9 +25,17 @@
25
25
  */
26
26
  export const SOLUTION_FOLDER = 'solution_folder';
27
27
  const SOLUTION_FOLDER_KEY_PREFIX = `${SOLUTION_FOLDER}.`;
28
- /** The binding identity of resource `name` authored in `folder`. */
28
+ /**
29
+ * The binding identity of resource `name` authored in `folder`. No folder
30
+ * means the case's own solution folder, the same as `solution_folder`:
31
+ *
32
+ * .process('notify') -> resourceKey 'solution_folder.notify', folderPath ''
33
+ *
34
+ * (A literal `.notify` key would name no folder, and deploy and `resources
35
+ * refresh` could not match it.)
36
+ */
29
37
  export function resourceIdentity(name, folder) {
30
- if (folder === SOLUTION_FOLDER)
38
+ if (folder === SOLUTION_FOLDER || !folder.trim())
31
39
  return { resourceKey: `${SOLUTION_FOLDER_KEY_PREFIX}${name}`, folderPath: '' };
32
40
  return { resourceKey: `${folder}.${name}`, folderPath: folder };
33
41
  }
@@ -19,6 +19,7 @@ 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
21
  import { resourceIdentity } from './resource-identity.js';
22
+ import { ownEntry } from '../core/own-entry.js';
22
23
  import { resolveSchemaVersion } from '../schema-version.js';
23
24
  import { caseJsonPreservation, caseJsonPreservationErrors, } from './preserve.js';
24
25
  import { readEventFilter, eventFilterProblem, eventFilterTreeLeaf } from '../core/event-filters.js';
@@ -81,7 +82,8 @@ export function serialize(built, opts = {}) {
81
82
  bindings: opts.bindings,
82
83
  bindingSink: new Map(),
83
84
  varCompanions: new Map(),
84
- connectorOutputCounts: new Map(),
85
+ outputIds: new Set(),
86
+ ownOutputVars: ownOutputVars(built),
85
87
  slaId: new Map(),
86
88
  slaIdByName: new Map(),
87
89
  escalationId: new Map(),
@@ -523,6 +525,18 @@ function applyPreservedIdentities(result, preservation) {
523
525
  const triggerNodes = nodes.filter((node) => node.type === 'case-management:Trigger' || node.type === 'uipath.case.trigger');
524
526
  (identities.triggers ?? []).forEach((trigger, index) => take(triggerNodes[index], trigger.id));
525
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);
526
540
  for (const stageIdentity of identities.stages ?? []) {
527
541
  const stage = stages.find((candidate) => candidate.data?.label === stageIdentity.label);
528
542
  take(stage, stageIdentity.id);
@@ -547,7 +561,19 @@ function applyPreservedIdentities(result, preservation) {
547
561
  // A positional match never takes a row a name match owns.
548
562
  : named.has(outputRows[index]?.name) ? undefined : outputRows[index];
549
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;
550
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
+ }
551
577
  if (typeof currentId === 'string' && typeof row.id === 'string' && target?.value === currentId) {
552
578
  target.value = row.id;
553
579
  }
@@ -884,7 +910,12 @@ function taskData(task, resolver, taskId, stageId, ids) {
884
910
  // companion so `=vars.<var>` resolves downstream.
885
911
  if (!task.ref)
886
912
  return {};
887
- 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 };
888
919
  const elementId = `${stageId}-${taskId}`;
889
920
  if (task.inputs?.length)
890
921
  data.inputs = task.inputs.map((inp, i) => emitTaskInput(inp, elementId, ids, `${elementId}:in:${i}:${inp.name}`));
@@ -1104,10 +1135,15 @@ function emitActionField(f, outputElementId, resolver) {
1104
1135
  if (f.value !== undefined)
1105
1136
  row.value = f.value;
1106
1137
  if (outputElementId !== undefined && f.value !== undefined) {
1107
- 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;
1108
1142
  row.var = f.value;
1143
+ if (id !== f.value)
1144
+ row.originalVar = id;
1109
1145
  row.source = `=${f.name}`;
1110
- row.target = `=${f.value}`;
1146
+ row.target = `=${id}`;
1111
1147
  row.elementId = outputElementId;
1112
1148
  if (resolver && !resolver.varCompanions.has(f.value)) {
1113
1149
  resolver.varCompanions.set(f.value, {
@@ -1130,13 +1166,43 @@ function emitTaskInput(inp, elementId, ids, seed) {
1130
1166
  const slot = ids.make('v', 8, seed);
1131
1167
  return { name: inp.name, type: inp.type, id: slot, var: slot, elementId, value: inp.value };
1132
1168
  }
1133
- /** 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
+ */
1134
1190
  function emitTaskOutput(o, elementId, ids, seed, resolver) {
1135
- const slot = ids.make('o', 8, seed);
1136
- const field = o.source.replace(/^=/, '').split('.').pop() || o.source;
1191
+ const path = o.source.replace(/^=/, '');
1192
+ const field = path.split('.').pop() || o.source;
1137
1193
  // Register the readable companion once (declared vars take precedence in the merge).
1138
1194
  if (!resolver.varCompanions.has(o.var))
1139
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));
1140
1206
  return {
1141
1207
  name: field,
1142
1208
  type: o.type,
@@ -1160,25 +1226,62 @@ const BUSINESS_RULE_TASK_VERSION = 'v3';
1160
1226
  * pattern) is a different namespace, and a binding that uses it is not a
1161
1227
  * BusinessRule resource to the bindings_v2 reader. */
1162
1228
  const BUSINESS_RULE_RESOURCE = 'BusinessRule';
1163
- /** Mint (once per resourceKey + attribute) one root `BusinessRule` binding. */
1164
- 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) {
1165
1251
  for (const entry of resolver.bindingSink.values()) {
1166
- if (entry.resource === BUSINESS_RULE_RESOURCE && entry.resourceKey === resourceKey
1252
+ if (entry.resource === binding.resource && entry.resourceKey === resourceKey
1167
1253
  && entry.propertyAttribute === propertyAttribute)
1168
1254
  return entry.id;
1169
1255
  }
1170
- 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`.
1171
1259
  resolver.bindingSink.set(id, {
1172
1260
  id,
1173
1261
  name: propertyAttribute,
1174
1262
  type: 'string',
1175
- resource: BUSINESS_RULE_RESOURCE,
1263
+ resource: binding.resource,
1264
+ ...(binding.subType ? { resourceSubType: binding.subType } : {}),
1176
1265
  resourceKey,
1177
1266
  default: value,
1178
1267
  propertyAttribute,
1179
1268
  });
1180
1269
  return id;
1181
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
+ }
1182
1285
  /**
1183
1286
  * The `data` of a `business-rule` task, as Studio Web writes it and as the
1184
1287
  * CLI's `tasks describe --type business-rule` + splice produce it:
@@ -1194,12 +1297,7 @@ function businessRuleBinding(resolver, ids, resourceKey, propertyAttribute, valu
1194
1297
  * (`=<Decision>.<column>`).
1195
1298
  */
1196
1299
  function businessRuleTaskData(task, resolver, elementId, ids) {
1197
- const ref = task.ref;
1198
- // An in-solution rule (`folder: 'solution_folder'`) keeps the sentinel in its
1199
- // key and binds an empty folderPath — see resource-identity.ts.
1200
- const { resourceKey, folderPath } = resourceIdentity(ref.name, ref.folderPath);
1201
- const nameId = businessRuleBinding(resolver, ids, resourceKey, 'name', ref.name);
1202
- const folderId = businessRuleBinding(resolver, ids, resourceKey, 'folderPath', folderPath);
1300
+ const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids);
1203
1301
  const ownRow = (name, source, seed, extra = {}) => {
1204
1302
  const slot = ids.make('v', 8, `${elementId}:${seed}`);
1205
1303
  return { name, type: 'jsonSchema', id: slot, var: slot, value: slot, source, target: `=${slot}`, elementId, ...extra };
@@ -1215,8 +1313,8 @@ function businessRuleTaskData(task, resolver, elementId, ids) {
1215
1313
  ...(task.outputs ?? []).map((o, i) => emitTaskOutput(o, elementId, ids, `${elementId}:out:${i}:${o.var}`, resolver)),
1216
1314
  ];
1217
1315
  return {
1218
- name: `=bindings.${nameId}`,
1219
- folderPath: `=bindings.${folderId}`,
1316
+ name: nameRef,
1317
+ folderPath: folderRef,
1220
1318
  version: BUSINESS_RULE_TASK_VERSION,
1221
1319
  inputs: (task.inputs ?? []).map((inp, i) => emitTaskInput(inp, elementId, ids, `${elementId}:in:${i}:${inp.name}`)),
1222
1320
  outputs,
@@ -1477,9 +1575,76 @@ function connectorMetadata(rc, op) {
1477
1575
  /** The `outputs[]`: a `response` json-schema from the op's output schema, plus a
1478
1576
  * standard `Error` schema. */
1479
1577
  function connectorOutputVar(resolver, base) {
1480
- const occurrence = (resolver.connectorOutputCounts.get(base) ?? 0) + 1;
1481
- resolver.connectorOutputCounts.set(base, occurrence);
1482
- 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('');
1483
1648
  }
1484
1649
  /** The `Error` output's schema: the designer's (and the CLI's) `ERROR_OUTPUT_SCHEMA`. */
1485
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.2",
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": "c3c40cdfae94553b2d63ec072fec2c1b1f45f805"
96
+ "gitref": "b763fc50a8d6430ce9a6e3e3e6b79b7c8645a4b1"
97
97
  }