@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.
- package/dist/case/case-sdk.d.ts +6 -6
- package/dist/case/case-sdk.js +6 -6
- package/dist/case/check.js +11 -0
- package/dist/case/compile-cli.js +132 -70
- package/dist/case/decompile.js +25 -9
- package/dist/case/resource-identity.d.ts +9 -1
- package/dist/case/resource-identity.js +10 -2
- package/dist/case/serialize.js +188 -23
- package/package.json +2 -2
package/dist/case/case-sdk.d.ts
CHANGED
|
@@ -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?: {
|
package/dist/case/case-sdk.js
CHANGED
|
@@ -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) {
|
package/dist/case/check.js
CHANGED
|
@@ -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({
|
package/dist/case/compile-cli.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
|
|
319
|
-
if (
|
|
320
|
-
group
|
|
321
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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)
|
package/dist/case/decompile.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
610
|
-
const
|
|
611
|
-
const
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
}
|
package/dist/case/serialize.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = `=${
|
|
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
|
-
/**
|
|
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
|
|
1136
|
-
const field =
|
|
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
|
-
/**
|
|
1164
|
-
|
|
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 ===
|
|
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:${
|
|
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:
|
|
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
|
|
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:
|
|
1219
|
-
folderPath:
|
|
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
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
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.
|
|
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": "
|
|
96
|
+
"gitref": "b763fc50a8d6430ce9a6e3e3e6b79b7c8645a4b1"
|
|
97
97
|
}
|