@uipath/maestro-builder-sdk 6.38.0 → 6.40.0
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/api-index.md +72 -72
- package/dist/api-members.md +337 -333
- package/dist/bpmn/bpmn-sdk.d.ts +36 -1
- package/dist/bpmn/bpmn-sdk.js +4 -0
- package/dist/bpmn/check.js +255 -8
- package/dist/bpmn/decompile.js +68 -2
- package/dist/bpmn/entry-point-id.d.ts +9 -0
- package/dist/bpmn/entry-point-id.js +35 -0
- package/dist/bpmn/serialize.js +53 -10
- package/package.json +2 -2
package/dist/bpmn/bpmn-sdk.d.ts
CHANGED
|
@@ -112,6 +112,8 @@ export type BpmnNode = {
|
|
|
112
112
|
* BPMN's default, interrupting, and writes no attribute.
|
|
113
113
|
*/
|
|
114
114
|
interrupting?: boolean;
|
|
115
|
+
/** A start event's own `uipath:entryPointId`; `false` writes none. */
|
|
116
|
+
entryPointId?: string | false;
|
|
115
117
|
} | {
|
|
116
118
|
kind: 'boundaryEvent';
|
|
117
119
|
id: string;
|
|
@@ -175,6 +177,8 @@ export type BpmnNode = {
|
|
|
175
177
|
name?: string;
|
|
176
178
|
/** The registry extension type, e.g. `'Intsvc.UnifiedHttpRequest'`. */
|
|
177
179
|
type: string;
|
|
180
|
+
/** When emitted as a `bpmn:startEvent`: its `uipath:entryPointId`; `false` writes none. */
|
|
181
|
+
entryPointId?: string | false;
|
|
178
182
|
/** `uipath:context` values, by field name. */
|
|
179
183
|
context: Record<string, unknown>;
|
|
180
184
|
/** The payload, shaped by the type's `inputPattern`. */
|
|
@@ -203,6 +207,8 @@ export type BpmnNode = {
|
|
|
203
207
|
kind: 'connectorEvent';
|
|
204
208
|
id: string;
|
|
205
209
|
name?: string;
|
|
210
|
+
/** A trigger's `uipath:entryPointId`; `false` writes none. */
|
|
211
|
+
entryPointId?: string | false;
|
|
206
212
|
/**
|
|
207
213
|
* `trigger` STARTS the process (`Intsvc.EventTrigger`, a `bpmn:startEvent`
|
|
208
214
|
* carrying a message event definition); `event` pauses one mid-flow
|
|
@@ -539,8 +545,15 @@ export interface ProcessMetadata {
|
|
|
539
545
|
* The root start event's package entry-point id (`uipath:entryPointId`). A GUID —
|
|
540
546
|
* the one `bpmn init` assigned, or `crypto.randomUUID()`; `validate` refuses any
|
|
541
547
|
* other value and `check` reports `ENTRYPOINT_ID_NOT_GUID`.
|
|
548
|
+
*
|
|
549
|
+
* Optional since every start event gets a stable derived id when none is set
|
|
550
|
+
* (`entry-point-id.ts`): the canvas writes one on every start, and
|
|
551
|
+
* `uip maestro bpmn refresh` requires one on every root manual start. `false`
|
|
552
|
+
* writes none on the root start — what `decompile` emits for an import that
|
|
553
|
+
* carried none, so the round trip stays faithful. A start event other than the
|
|
554
|
+
* first root one takes its id on the event itself (`StartOpts.entryPointId`).
|
|
542
555
|
*/
|
|
543
|
-
entryPointId?: string;
|
|
556
|
+
entryPointId?: string | false;
|
|
544
557
|
/** Free-form document tags (`uipath:tags` / `uipath:tag`). */
|
|
545
558
|
tags?: string[];
|
|
546
559
|
/** Case-management marker (`uipath:caseManagement`) — `version` plus its body value. */
|
|
@@ -685,6 +698,15 @@ export interface StartOpts {
|
|
|
685
698
|
* {@link ScopeBuilder.eventSubProcess} sets this from its own `interrupting` option.
|
|
686
699
|
*/
|
|
687
700
|
interrupting?: boolean;
|
|
701
|
+
/**
|
|
702
|
+
* This start event's `uipath:entryPointId`. Unset, a root start derives a stable id
|
|
703
|
+
* from the process and event ids — which therefore changes if either is renamed, so
|
|
704
|
+
* pin the old id here when renaming in a deployed process; `false` writes none (what
|
|
705
|
+
* `decompile` emits for an imported start that carried none). The first root start
|
|
706
|
+
* may also take it from `.metadata({ entryPointId })`. A start inside a sub-process
|
|
707
|
+
* derives nothing (the canvas strips ids there) and writes only a value set here.
|
|
708
|
+
*/
|
|
709
|
+
entryPointId?: string | false;
|
|
688
710
|
}
|
|
689
711
|
/**
|
|
690
712
|
* Options for `.endEvent()`. `terminate`, `error`, or `message` picks the event
|
|
@@ -1497,6 +1519,11 @@ export interface ConnectorEventOpts extends ActivityOpts {
|
|
|
1497
1519
|
outputs?: Record<string, string>;
|
|
1498
1520
|
/** `=`-expression that skips the node when truthy. */
|
|
1499
1521
|
skipCondition?: string;
|
|
1522
|
+
/**
|
|
1523
|
+
* `.eventTrigger()` only: the start event's `uipath:entryPointId`. Unset, a stable
|
|
1524
|
+
* id is derived; `false` writes none. Ignored by `.waitForEvent()`.
|
|
1525
|
+
*/
|
|
1526
|
+
entryPointId?: string | false;
|
|
1500
1527
|
}
|
|
1501
1528
|
/** Options for `.externalAgent()` / `.externalWorkflow()`. */
|
|
1502
1529
|
export interface ExternalTaskOpts extends ActivityOpts {
|
|
@@ -1607,6 +1634,12 @@ export interface ActivityNodeOpts extends ActivityOpts {
|
|
|
1607
1634
|
* writing `.activity()` by hand wants the defaults.
|
|
1608
1635
|
*/
|
|
1609
1636
|
contextVerbatim?: boolean;
|
|
1637
|
+
/**
|
|
1638
|
+
* When the registry type is emitted as a `bpmn:startEvent` (a trigger such as
|
|
1639
|
+
* `Intsvc.EventTrigger`): its `uipath:entryPointId`. Unset, a stable id is derived;
|
|
1640
|
+
* `false` writes none, as `decompile` emits for an imported start that carried none.
|
|
1641
|
+
*/
|
|
1642
|
+
entryPointId?: string | false;
|
|
1610
1643
|
}
|
|
1611
1644
|
/**
|
|
1612
1645
|
* The BPMN task elements that are legal with NO `uipath:*` payload.
|
|
@@ -1892,6 +1925,8 @@ export interface EventSubProcessOpts {
|
|
|
1892
1925
|
startName?: string;
|
|
1893
1926
|
/** A payload on the start event, spelled out by hand — see {@link ExtensionPayload}. Exclusive with `errorVar`. */
|
|
1894
1927
|
startPayload?: ExtensionPayload;
|
|
1928
|
+
/** The nested start event's `uipath:entryPointId`, written only when set: the canvas strips ids inside a sub-process, so none is derived. */
|
|
1929
|
+
startEntryPointId?: string | false;
|
|
1895
1930
|
/** `uipath:tags` on the sub-process — see {@link ActivityOpts.tags}. */
|
|
1896
1931
|
tags?: Record<string, string>;
|
|
1897
1932
|
/** A `uipath:mapping` on the sub-process itself — see {@link SubProcessOpts.payload}. */
|
package/dist/bpmn/bpmn-sdk.js
CHANGED
|
@@ -697,6 +697,7 @@ class ScopeBuilder {
|
|
|
697
697
|
return this.push({
|
|
698
698
|
kind: 'startEvent',
|
|
699
699
|
id,
|
|
700
|
+
entryPointId: opts.entryPointId,
|
|
700
701
|
name: opts.name,
|
|
701
702
|
payload: opts.payload,
|
|
702
703
|
interrupting: opts.interrupting === false ? false : undefined,
|
|
@@ -1754,6 +1755,7 @@ class ScopeBuilder {
|
|
|
1754
1755
|
return this.push({
|
|
1755
1756
|
kind: 'connectorEvent',
|
|
1756
1757
|
id,
|
|
1758
|
+
entryPointId: opts.entryPointId,
|
|
1757
1759
|
name: opts.name,
|
|
1758
1760
|
subscribe: 'trigger',
|
|
1759
1761
|
key: connector,
|
|
@@ -1907,6 +1909,7 @@ class ScopeBuilder {
|
|
|
1907
1909
|
outputRows: opts.outputRows,
|
|
1908
1910
|
skipCondition: opts.skipCondition,
|
|
1909
1911
|
contextVerbatim: opts.contextVerbatim,
|
|
1912
|
+
entryPointId: opts.entryPointId,
|
|
1910
1913
|
tags: opts.tags,
|
|
1911
1914
|
retry: opts.retry,
|
|
1912
1915
|
errorMapping: opts.errorMapping,
|
|
@@ -2036,6 +2039,7 @@ class ScopeBuilder {
|
|
|
2036
2039
|
sb._begin({
|
|
2037
2040
|
kind: 'startEvent',
|
|
2038
2041
|
id: startId,
|
|
2042
|
+
entryPointId: opts.startEntryPointId,
|
|
2039
2043
|
name: opts.startName,
|
|
2040
2044
|
event,
|
|
2041
2045
|
payload: opts.startPayload ?? (capture !== undefined ? errorCapturePayload(capture, 'startEvent') : undefined),
|
package/dist/bpmn/check.js
CHANGED
|
@@ -67,6 +67,7 @@ export function check(built, opts = {}) {
|
|
|
67
67
|
checkVariableOrder(built, diags);
|
|
68
68
|
checkDocumentIds(built, diags);
|
|
69
69
|
checkFolderBindings(built, diags);
|
|
70
|
+
checkIntsvcActivities(built, diags);
|
|
70
71
|
checkEntryPointId(built, diags);
|
|
71
72
|
checkMethodParentheses(built, diags);
|
|
72
73
|
checkConnectorRows(built, diags);
|
|
@@ -309,15 +310,25 @@ const GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
309
310
|
* main one agent spent its whole turn budget there. Reported here, at the first rung.
|
|
310
311
|
*/
|
|
311
312
|
function checkEntryPointId(built, diags) {
|
|
313
|
+
const report = (where, id, at) => {
|
|
314
|
+
diags.push({
|
|
315
|
+
level: 'error',
|
|
316
|
+
code: 'ENTRYPOINT_ID_NOT_GUID',
|
|
317
|
+
message: `${where} "${id}" is not a GUID; \`uip maestro bpmn validate\` refuses it. Use the id \`bpmn init\` ` +
|
|
318
|
+
`assigned (decompile the stub it wrote to carry it), a fresh one from \`crypto.randomUUID()\`, or leave it ` +
|
|
319
|
+
`unset and the SDK derives a stable one.`,
|
|
320
|
+
...(at !== undefined ? { where: at } : {}),
|
|
321
|
+
});
|
|
322
|
+
};
|
|
312
323
|
const id = built.metadata?.entryPointId;
|
|
313
|
-
if (id ===
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
`
|
|
320
|
-
}
|
|
324
|
+
if (typeof id === 'string' && !GUID.test(id))
|
|
325
|
+
report('metadata.entryPointId', id);
|
|
326
|
+
for (const n of allNodes(built.nodes)) {
|
|
327
|
+
if (n.kind !== 'startEvent' && n.kind !== 'connectorEvent' && n.kind !== 'typed')
|
|
328
|
+
continue;
|
|
329
|
+
if (typeof n.entryPointId === 'string' && !GUID.test(n.entryPointId))
|
|
330
|
+
report(`"${n.id}" entryPointId`, n.entryPointId, n.id);
|
|
331
|
+
}
|
|
321
332
|
}
|
|
322
333
|
/**
|
|
323
334
|
* A `folderKey` binding must be keyed by its CONNECTION, not by the folder.
|
|
@@ -380,6 +391,242 @@ function checkFolderBindings(built, diags) {
|
|
|
380
391
|
}
|
|
381
392
|
}
|
|
382
393
|
}
|
|
394
|
+
/** The `method` vocabulary of the Integration Service activity catalog (`MethodName`). */
|
|
395
|
+
const INTSVC_METHODS = new Set(['GET', 'GETBYID', 'POST', 'PUT', 'PATCH', 'DELETE']);
|
|
396
|
+
/** The catalog's identity fields on an `Intsvc.ActivityExecution` task, in the order the message names them. */
|
|
397
|
+
const INTSVC_IDENTITY = ['connectorKey', 'objectName', 'operation', 'method', 'path'];
|
|
398
|
+
/**
|
|
399
|
+
* A hand-filled `Intsvc.ActivityExecution` task: what the catalog, the refresh and
|
|
400
|
+
* the runtime require of its context that `uip maestro bpmn validate` does not report.
|
|
401
|
+
*
|
|
402
|
+
* `.connector()` and `.dataService()` write this task from the library; an author
|
|
403
|
+
* who reaches for `.activity(id, 'Intsvc.ActivityExecution', …)` — the route the skill
|
|
404
|
+
* names for a connector the library lacks — fills the identity rows by hand from
|
|
405
|
+
* `uip is activities list <key>`, and the 2026-10-16 eval run measured what goes wrong
|
|
406
|
+
* (141 SDK-authored files, 109 such tasks): `objectName: 'N/A'` and `method: 'N/A'`
|
|
407
|
+
* copied from the catalog's GENERIC rows (5 tasks — `validate` reported them Valid, and
|
|
408
|
+
* they dispatch to nothing), an empty `operation` (5), `<METHOD_FROM_CLI>`-style template
|
|
409
|
+
* placeholders left in place, and a `connection` that `uip maestro bpmn refresh` then
|
|
410
|
+
* refused with `connection must reference a root binding as "=bindings.<id>"`.
|
|
411
|
+
*
|
|
412
|
+
* What the registry's own field notes say the rows are: `operation` is the catalog
|
|
413
|
+
* activity's `Name`, `objectName` its `ObjectName`, `method` its `MethodName`, `path` the
|
|
414
|
+
* request path `is resources describe` returns, and `connection` "points to a root binding
|
|
415
|
+
* for a Connection resource" with `folderKey` a second binding for the same key. Real
|
|
416
|
+
* exports agree on the shape and differ on one spelling: `operation` is the catalog Name
|
|
417
|
+
* on the canvas (`UpdateEntityRecordV2`) and the CRUD verb in 94 of 108 hand-written v1
|
|
418
|
+
* tasks (`Create`, `Retrieve`), so that field is held to "an identifier", not a list.
|
|
419
|
+
* `method` IS a closed list: GET, GETBYID, POST, PUT, PATCH, DELETE, measured over every
|
|
420
|
+
* task in both arms and the corpus.
|
|
421
|
+
*
|
|
422
|
+
* Errors where the task cannot dispatch or the platform refuses it; warnings where it
|
|
423
|
+
* deploys but the SDK cannot tell it is right.
|
|
424
|
+
*/
|
|
425
|
+
function checkIntsvcActivities(built, diags) {
|
|
426
|
+
// The bindings the compiled process will carry: the ones declared with `.binding()`,
|
|
427
|
+
// and the ones `serialize` synthesizes for every `.connector()`, `.eventTrigger()` /
|
|
428
|
+
// `.waitForEvent()` and external task from the symbolic names they read (ids equal
|
|
429
|
+
// to the names, `connector.ts` `bindingRef`) — a hand-filled task may reuse those.
|
|
430
|
+
const declared = new Map(built.bindings.map((b) => [b.id, { resource: String(b.resource ?? ''), propertyAttribute: String(b.propertyAttribute ?? '') }]));
|
|
431
|
+
for (const n of allNodes(built.nodes)) {
|
|
432
|
+
if (n.kind !== 'connector' && n.kind !== 'connectorEvent' && n.kind !== 'externalTask')
|
|
433
|
+
continue;
|
|
434
|
+
if (n.connection && !declared.has(n.connection))
|
|
435
|
+
declared.set(n.connection, { resource: 'Connection', propertyAttribute: 'ConnectionId' });
|
|
436
|
+
if (n.folder && !declared.has(n.folder))
|
|
437
|
+
declared.set(n.folder, { resource: 'Connection', propertyAttribute: 'folderKey' });
|
|
438
|
+
}
|
|
439
|
+
const expr = (v) => typeof v === 'string' && v.startsWith('=');
|
|
440
|
+
const text = (v) => (typeof v === 'string' ? v : v === undefined || v === null ? undefined : String(v));
|
|
441
|
+
for (const n of allNodes(built.nodes)) {
|
|
442
|
+
if (n.kind !== 'typed' || n.type !== 'Intsvc.ActivityExecution')
|
|
443
|
+
continue;
|
|
444
|
+
// `contextRows`, when given, REPLACE `context` at emission (typed-node.ts), so only
|
|
445
|
+
// the rows are the task's context then; a record field beside them is not emitted.
|
|
446
|
+
const ctx = {};
|
|
447
|
+
if (n.contextRows)
|
|
448
|
+
for (const row of n.contextRows)
|
|
449
|
+
ctx[row.name] = row.value ?? row.body ?? '';
|
|
450
|
+
else
|
|
451
|
+
Object.assign(ctx, n.context);
|
|
452
|
+
const where = n.id;
|
|
453
|
+
const key = text(ctx['connectorKey']) ?? '<connector-key>';
|
|
454
|
+
// 1. A catalog placeholder: `N/A` from a generic row, or a template's `<…>`.
|
|
455
|
+
for (const field of INTSVC_IDENTITY) {
|
|
456
|
+
const v = text(ctx[field]);
|
|
457
|
+
if (v === undefined)
|
|
458
|
+
continue;
|
|
459
|
+
const placeholder = /^n\/a$/i.test(v.trim()) || /^(<|<)[^<>]*(>|>)$/.test(v.trim());
|
|
460
|
+
if (!placeholder)
|
|
461
|
+
continue;
|
|
462
|
+
diags.push({
|
|
463
|
+
level: 'error',
|
|
464
|
+
code: 'INTSVC_CATALOG_PLACEHOLDER',
|
|
465
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) has ${field}="${v}", which dispatches to nothing. ` +
|
|
466
|
+
(/^n\/a$/i.test(v.trim())
|
|
467
|
+
? `"N/A" is what \`uip is activities list ${key}\` prints on a GENERIC record verb (Create/Retrieve/Update/Delete Record), ` +
|
|
468
|
+
`whose object is chosen at design time: set objectName to an object from \`uip is resources list ${key} --connection-id <id>\`, ` +
|
|
469
|
+
`method to that verb's HTTP method, and path from \`uip is resources describe ${key} <object> --operation <Verb> --connection-id <id>\`. ` +
|
|
470
|
+
`For a curated activity, objectName and method are the row's ObjectName and MethodName.`
|
|
471
|
+
: `Replace the placeholder with the catalog's value (\`uip is activities list ${key} --output json\`: Name → operation, ObjectName → objectName, MethodName → method; ` +
|
|
472
|
+
`\`uip is resources describe\` → path).`) +
|
|
473
|
+
` Or let the library write the task: \`uip maestro registry prepare ${key} --action <name>\` then \`.connector()\`.`,
|
|
474
|
+
where,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
// 2. `method` is the catalog's closed vocabulary.
|
|
478
|
+
const method = text(ctx['method']);
|
|
479
|
+
// An empty one is EMPTY_REQUIRED_FIELD's (the registry marks it required), as for `operation`.
|
|
480
|
+
if (method !== undefined && method.trim() !== '' && !expr(method) && !/^n\/a$/i.test(method.trim()) && !/^(<|<)/.test(method.trim()) && !INTSVC_METHODS.has(method.trim())) {
|
|
481
|
+
diags.push({
|
|
482
|
+
level: 'error',
|
|
483
|
+
code: 'INTSVC_METHOD_UNKNOWN',
|
|
484
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) has method="${method}"; the activity catalog's MethodName is one of ` +
|
|
485
|
+
`${[...INTSVC_METHODS].join(', ')}` +
|
|
486
|
+
(INTSVC_METHODS.has(method.trim().toUpperCase()) ? ` — write it in upper case: "${method.trim().toUpperCase()}".` : `. Read it from \`uip is activities list ${key} --output json\` (MethodName).`),
|
|
487
|
+
where,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
// 3. `operation` is the catalog activity's Name (or the CRUD verb hand-written exports use): an identifier.
|
|
491
|
+
const operation = text(ctx['operation']);
|
|
492
|
+
if (operation !== undefined && operation.trim() !== '' && !expr(operation) && !/^n\/a$/i.test(operation.trim()) && !/^(<|<)/.test(operation.trim())
|
|
493
|
+
&& !/^[A-Za-z][A-Za-z0-9_]*$/.test(operation.trim())) {
|
|
494
|
+
diags.push({
|
|
495
|
+
level: 'error',
|
|
496
|
+
code: 'INTSVC_OPERATION_NOT_IDENTIFIER',
|
|
497
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) has operation="${operation}"; the registry defines it as the activity catalog's Name ` +
|
|
498
|
+
`(\`uip is activities list ${key}\`: CreateTestSet, QueryEntityRecordsCurated) — an identifier, not a REST slug or a label.`,
|
|
499
|
+
where,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
// 4. `objectName` spelled as a REST path: validates, dispatches to nothing. Only a
|
|
503
|
+
// `/` says so — `::` is in curated ObjectNames too (`v2::webSearch`, airdk).
|
|
504
|
+
const objectName = text(ctx['objectName']);
|
|
505
|
+
if (objectName !== undefined && !expr(objectName) && objectName.includes('/')) {
|
|
506
|
+
diags.push({
|
|
507
|
+
level: 'warning',
|
|
508
|
+
code: 'INTSVC_OBJECT_NAME_SLUG',
|
|
509
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) has objectName="${objectName}", which reads as a REST path, not an object. The platform matches ` +
|
|
510
|
+
`on the catalog's ObjectName (\`uip is activities list ${key}\`: TestSet, UploadAttachment, v2::webSearch); a path validates and then dispatches to nothing.`,
|
|
511
|
+
where,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
// 5. The path's placeholders each need a `target: 'path'` input row of that name.
|
|
515
|
+
const path = text(ctx['path']);
|
|
516
|
+
if (path !== undefined && !expr(path)) {
|
|
517
|
+
if (!path.startsWith('/')) {
|
|
518
|
+
diags.push({
|
|
519
|
+
level: 'warning',
|
|
520
|
+
code: 'INTSVC_PATH_NOT_ABSOLUTE',
|
|
521
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) has path="${path}"; the request path \`is resources describe\` returns starts with "/" (e.g. "/v2/{ProjectId}/testsets").`,
|
|
522
|
+
where,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
const params = [...path.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1]);
|
|
526
|
+
if (params.length > 0) {
|
|
527
|
+
// Bound by a row of that name targeting the path, or by a path AGGREGATE —
|
|
528
|
+
// `inputs: { pathParameters: { ProjectId } }` emits one `target="pathParameters"`
|
|
529
|
+
// row holding every placeholder (an expression-valued aggregate binds them all).
|
|
530
|
+
const PATH_TARGETS = new Set(['path', 'pathvariable', 'pathparameters']);
|
|
531
|
+
const bound = new Set();
|
|
532
|
+
let aggregateExpression = false;
|
|
533
|
+
const aggregate = (value) => {
|
|
534
|
+
if (expr(value))
|
|
535
|
+
aggregateExpression = true;
|
|
536
|
+
else if (value && typeof value === 'object')
|
|
537
|
+
for (const k of Object.keys(value))
|
|
538
|
+
bound.add(k);
|
|
539
|
+
};
|
|
540
|
+
for (const r of n.inputRows ?? []) {
|
|
541
|
+
const target = (r.target ?? '').toLowerCase();
|
|
542
|
+
if (target === 'pathparameters' || target === 'pathvariable' || PATH_TARGETS.has(r.name.toLowerCase()))
|
|
543
|
+
aggregate(r.body ?? r.value);
|
|
544
|
+
if (PATH_TARGETS.has(target))
|
|
545
|
+
bound.add(r.name);
|
|
546
|
+
}
|
|
547
|
+
if (!n.inputRows)
|
|
548
|
+
for (const key of ['pathParameters', 'pathVariable'])
|
|
549
|
+
if (key in (n.inputs ?? {}))
|
|
550
|
+
aggregate(n.inputs[key]);
|
|
551
|
+
const unbound = aggregateExpression ? [] : params.filter((p) => !bound.has(p));
|
|
552
|
+
if (unbound.length > 0) {
|
|
553
|
+
diags.push({
|
|
554
|
+
level: 'warning',
|
|
555
|
+
code: 'INTSVC_PATH_PARAM_UNBOUND',
|
|
556
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) path "${path}" has placeholder(s) ${unbound.map((p) => `{${p}}`).join(', ')} with no input row ` +
|
|
557
|
+
`of that name targeting the path. Write \`inputs: { pathParameters: { ${unbound[0]}: '…' } }\` (the other fields stay ` +
|
|
558
|
+
`beside it), or an \`inputRows\` row \`{ name: '${unbound[0]}', type: 'string', value: '…', target: 'path' }\` — a bare ` +
|
|
559
|
+
`\`inputs: { ${unbound[0]} }\` lands in the body and leaves the placeholder unfilled.`,
|
|
560
|
+
where,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// 6. The connection and folder rows: binding references to declared Connection bindings.
|
|
566
|
+
const bindingRow = (field, attribute) => {
|
|
567
|
+
const v = text(ctx[field]);
|
|
568
|
+
if (v === undefined || v.trim() === '')
|
|
569
|
+
return;
|
|
570
|
+
if (!v.startsWith('=bindings.')) {
|
|
571
|
+
// A literal gets the binding to declare with that value; any other
|
|
572
|
+
// expression (`=vars.connId`) is told what refresh needs instead — a
|
|
573
|
+
// connection is resolved from a root binding, never computed at run time.
|
|
574
|
+
const label = field === 'connection' ? 'conn' : 'folder';
|
|
575
|
+
const refusal = `\`uip maestro bpmn refresh\` refuses it with \`${field} must reference a root binding as "=bindings.<id>"\``;
|
|
576
|
+
diags.push({
|
|
577
|
+
level: 'error',
|
|
578
|
+
code: 'INTSVC_CONNECTION_NOT_BINDING',
|
|
579
|
+
message: expr(v)
|
|
580
|
+
? `"${n.id}" (Intsvc.ActivityExecution) has ${field}="${v}", an expression; ${refusal}. A ${field} is resolved from a root binding, ` +
|
|
581
|
+
`not computed: declare \`.binding('${label}', { resource: 'Connection', propertyAttribute: '${attribute}', value: '<${field === 'connection' ? 'connection id' : 'folder key'}>' })\` ` +
|
|
582
|
+
`and write ${field}: '=bindings.${label}'.`
|
|
583
|
+
: `"${n.id}" (Intsvc.ActivityExecution) has ${field}="${v}", a literal; ${refusal}. Declare ` +
|
|
584
|
+
`\`.binding('${label}', { resource: 'Connection', propertyAttribute: '${attribute}', value: '${v}' })\` ` +
|
|
585
|
+
`and write ${field}: '=bindings.${label}'.`,
|
|
586
|
+
where,
|
|
587
|
+
});
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const id = v.slice('=bindings.'.length);
|
|
591
|
+
const b = declared.get(id);
|
|
592
|
+
if (!b) {
|
|
593
|
+
diags.push({
|
|
594
|
+
level: 'error',
|
|
595
|
+
code: 'INTSVC_BINDING_UNDECLARED',
|
|
596
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) reads ${field}="${v}" but the process declares no binding "${id}" ` +
|
|
597
|
+
`(\`uip maestro bpmn validate\` reports MISSING_BINDING). Declare it at the root: ` +
|
|
598
|
+
`\`.binding('${id}', { resource: 'Connection', propertyAttribute: '${attribute}', value: '<${field === 'connection' ? 'connection id' : 'folder key'}>' })\`` +
|
|
599
|
+
(field === 'folderKey' ? ' — keyed by its connection; the builder pairs it with the connection this task reads.' : '.'),
|
|
600
|
+
where,
|
|
601
|
+
});
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const attr = b.propertyAttribute;
|
|
605
|
+
const resource = b.resource;
|
|
606
|
+
if (attr.toLowerCase() !== attribute.toLowerCase() || resource.toLowerCase() !== 'connection') {
|
|
607
|
+
diags.push({
|
|
608
|
+
level: 'error',
|
|
609
|
+
code: 'INTSVC_BINDING_MISMATCH',
|
|
610
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) reads ${field}="${v}", but binding "${id}" is resource "${resource}" / ` +
|
|
611
|
+
`propertyAttribute "${attr}"; the ${field} row needs resource "Connection" / propertyAttribute "${attribute}".`,
|
|
612
|
+
where,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
bindingRow('connection', 'ConnectionId');
|
|
617
|
+
bindingRow('folderKey', 'folderKey');
|
|
618
|
+
if (text(ctx['connection']) === undefined) {
|
|
619
|
+
diags.push({
|
|
620
|
+
level: 'warning',
|
|
621
|
+
code: 'INTSVC_CONNECTION_MISSING',
|
|
622
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) names no connection. The registry says to omit \`connection\` and \`folderKey\` only ` +
|
|
623
|
+
`when the activity uses none; a connector activity almost always does. Declare a Connection binding and read it as ` +
|
|
624
|
+
`connection: '=bindings.<id>' (\`uip is connections list ${key} --all-folders --output json\` lists the ids).`,
|
|
625
|
+
where,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
383
630
|
/**
|
|
384
631
|
* Every `id` in the document must be unique — including across KINDS.
|
|
385
632
|
*
|
package/dist/bpmn/decompile.js
CHANGED
|
@@ -61,6 +61,7 @@ import { BPMN_SCHEMA_VERSIONS } from './format-profile.js';
|
|
|
61
61
|
import { preserveSchemaVersion } from '../schema-version.js';
|
|
62
62
|
import { commentText } from '../core/comment-text.js';
|
|
63
63
|
import { tsLiteral, tsText } from '../core/ts-literal.js';
|
|
64
|
+
import { defaultEntryPointId } from './entry-point-id.js';
|
|
64
65
|
const require = createRequire(import.meta.url);
|
|
65
66
|
const uipathDescriptor = require('./uipath-moddle.v1.json');
|
|
66
67
|
/**
|
|
@@ -717,6 +718,47 @@ function emitOutputRows(payload) {
|
|
|
717
718
|
return `[${rendered.join(', ')}]`;
|
|
718
719
|
}
|
|
719
720
|
// ─── Element emitters ─────────────────────────────────────────────────────────
|
|
721
|
+
/**
|
|
722
|
+
* The process being decompiled, for the entry-point spelling below: the builder id
|
|
723
|
+
* the derived default is seeded from, the root start events, and the one root start
|
|
724
|
+
* whose id `.metadata({ entryPointId })` spells — the only root start, when there is
|
|
725
|
+
* exactly one. With several, every root start spells its own on the event, because
|
|
726
|
+
* `serialize` hands `.metadata()`'s id to the first root start in CHAIN order and the
|
|
727
|
+
* chain is emitted manual-first, which need not be document order.
|
|
728
|
+
*/
|
|
729
|
+
let runEntryPoints = {
|
|
730
|
+
builderProcessId: '',
|
|
731
|
+
rootStartIds: new Set(),
|
|
732
|
+
};
|
|
733
|
+
/**
|
|
734
|
+
* How an imported start event's `uipath:entryPointId` is spelled on the event's own
|
|
735
|
+
* call, or `undefined` for none. A root start: the value as text when the file
|
|
736
|
+
* carries one the SDK would not derive; nothing when it equals the derived default
|
|
737
|
+
* (compile writes it back unprompted); `false` when the file carries none, so the
|
|
738
|
+
* recompile adds nothing. The lone root start is `.metadata()`'s and gets nothing
|
|
739
|
+
* here. A nested start derives nothing on compile, so it is spelled only when the
|
|
740
|
+
* file carries a value.
|
|
741
|
+
*/
|
|
742
|
+
function entryPointOption(el) {
|
|
743
|
+
if (localName(el.$type) !== 'startEvent' || el.id === runEntryPoints.metadataStartId)
|
|
744
|
+
return undefined;
|
|
745
|
+
const legacy = runEntryPoints.processLevel;
|
|
746
|
+
const value = entryPointValueOf(el) ?? (legacy !== undefined && legacy.startId === el.id ? legacy.value : undefined);
|
|
747
|
+
if (!runEntryPoints.rootStartIds.has(el.id))
|
|
748
|
+
return value === undefined ? undefined : tsText(value);
|
|
749
|
+
return entryPointText(el.id, value);
|
|
750
|
+
}
|
|
751
|
+
/** The `uipath:entryPointId` value a start event carries, if any. */
|
|
752
|
+
function entryPointValueOf(el) {
|
|
753
|
+
const row = (el.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:EntryPointId');
|
|
754
|
+
return row?.value !== undefined ? String(row.value) : undefined;
|
|
755
|
+
}
|
|
756
|
+
/** `entryPointOption`'s spelling for a ROOT start event id and the value it carries. */
|
|
757
|
+
function entryPointText(startId, value) {
|
|
758
|
+
if (value === undefined)
|
|
759
|
+
return 'false';
|
|
760
|
+
return value === defaultEntryPointId(runEntryPoints.builderProcessId, startId) ? undefined : tsText(value);
|
|
761
|
+
}
|
|
720
762
|
/**
|
|
721
763
|
* A registry-typed node.
|
|
722
764
|
*
|
|
@@ -776,6 +818,7 @@ function emitTypedNode(chain, el, payload, type) {
|
|
|
776
818
|
['outputs', verbatimRows === undefined ? record(extras) : undefined],
|
|
777
819
|
['skipCondition', payload.skipCondition !== undefined ? tsText(payload.skipCondition) : undefined],
|
|
778
820
|
['contextVerbatim', Object.keys(ctx).length ? 'true' : undefined],
|
|
821
|
+
['entryPointId', entryPointOption(el)],
|
|
779
822
|
['tags', tagsOf(el)],
|
|
780
823
|
['retry', emitRetry(retryOf(el))],
|
|
781
824
|
['errorMapping', emitErrorMapping(errorMappingOf(el))],
|
|
@@ -1489,6 +1532,7 @@ function renderEventSubProcess(plan, sp, depth, indent) {
|
|
|
1489
1532
|
],
|
|
1490
1533
|
['startId', start.id !== `${sp.id}_start` ? tsText(start.id) : undefined],
|
|
1491
1534
|
['startName', start.name !== undefined ? tsText(start.name) : undefined],
|
|
1535
|
+
['startEntryPointId', entryPointOption(start)],
|
|
1492
1536
|
['startPayload', payload && type && capture === undefined ? opts([['type', tsText(type)], ['outputRows', emitOutputRows(payload) ?? '[]']]) : undefined],
|
|
1493
1537
|
['tags', tagsOf(sp)],
|
|
1494
1538
|
['payload', spPayload && spType ? opts([['type', tsText(spType)], ['outputRows', emitOutputRows(spPayload) ?? '[]']]) : undefined],
|
|
@@ -1665,6 +1709,7 @@ function emitTriggerStartEvent(chain, el, payload, type) {
|
|
|
1665
1709
|
['name', el.name !== undefined ? tsText(el.name) : undefined],
|
|
1666
1710
|
...eventOptions(el),
|
|
1667
1711
|
['trigger', trigger],
|
|
1712
|
+
['entryPointId', entryPointOption(el)],
|
|
1668
1713
|
])})`);
|
|
1669
1714
|
}
|
|
1670
1715
|
/**
|
|
@@ -1748,6 +1793,7 @@ function emitEvent(chain, el, kind, payload, type) {
|
|
|
1748
1793
|
// Only a start event can be non-interrupting, and only the exception is
|
|
1749
1794
|
// spelled — the builder's default is BPMN's.
|
|
1750
1795
|
['interrupting', kind === 'startEvent' && el.isInterrupting === false ? 'false' : undefined],
|
|
1796
|
+
['entryPointId', entryPointOption(el)],
|
|
1751
1797
|
['payload', eventPayload],
|
|
1752
1798
|
]))})`);
|
|
1753
1799
|
}
|
|
@@ -2086,6 +2132,26 @@ export async function decompile(xml, options = {}) {
|
|
|
2086
2132
|
const migration = findExt('uipath:MigrationVersion');
|
|
2087
2133
|
const rootStart = (process.flowElements ?? []).find((x) => x.$type === 'bpmn:StartEvent');
|
|
2088
2134
|
const entryPoint = (rootStart?.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:EntryPointId') ?? findExt('uipath:EntryPointId');
|
|
2135
|
+
const rootStarts = (process.flowElements ?? []).filter((x) => x.$type === 'bpmn:StartEvent');
|
|
2136
|
+
// `bpmn(process.id)` is what the source will say, so it is the seed on both sides.
|
|
2137
|
+
const builderId = String(process.id ?? '');
|
|
2138
|
+
const processLevelEntryPoint = findExt('uipath:EntryPointId');
|
|
2139
|
+
runEntryPoints = {
|
|
2140
|
+
builderProcessId: builderId,
|
|
2141
|
+
rootStartIds: new Set(rootStarts.map((x) => x.id)),
|
|
2142
|
+
metadataStartId: rootStarts.length === 1 ? rootStart?.id : undefined,
|
|
2143
|
+
...(rootStarts.length > 1 && rootStart !== undefined && processLevelEntryPoint?.value !== undefined
|
|
2144
|
+
&& !(rootStart.extensionElements?.values ?? []).some((x) => x.$type === 'uipath:EntryPointId')
|
|
2145
|
+
? { processLevel: { startId: rootStart.id, value: String(processLevelEntryPoint.value) } }
|
|
2146
|
+
: {}),
|
|
2147
|
+
};
|
|
2148
|
+
// A lone root start's id is `.metadata()`'s: the file's own value when the SDK would
|
|
2149
|
+
// not derive it, `false` when the file carries none (so the recompile adds none), and
|
|
2150
|
+
// nothing when it equals the derived default. With several root starts each spells
|
|
2151
|
+
// its own on the event (see `runEntryPoints`).
|
|
2152
|
+
const rootEntryPointText = rootStarts.length === 1 && rootStart !== undefined
|
|
2153
|
+
? entryPointText(rootStart.id, entryPoint?.value !== undefined ? String(entryPoint.value) : undefined)
|
|
2154
|
+
: undefined;
|
|
2089
2155
|
const tagsExt = findExt('uipath:Tags');
|
|
2090
2156
|
const caseMgmt = findExt('uipath:CaseManagement');
|
|
2091
2157
|
const tagValues = (tagsExt?.tags ?? []).map((t) => t.value).filter((v) => typeof v === 'string');
|
|
@@ -2103,13 +2169,13 @@ export async function decompile(xml, options = {}) {
|
|
|
2103
2169
|
const schema = preserveSchemaVersion(BPMN_SCHEMA_VERSIONS, migration?.version);
|
|
2104
2170
|
if (schema.call)
|
|
2105
2171
|
chain.add(schema.call);
|
|
2106
|
-
if (
|
|
2172
|
+
if (rootEntryPointText !== undefined ||
|
|
2107
2173
|
tagValues.length > 0 ||
|
|
2108
2174
|
caseMgmt !== undefined ||
|
|
2109
2175
|
nonExecutable) {
|
|
2110
2176
|
chain.add(`.metadata(${opts([
|
|
2111
2177
|
['executable', nonExecutable ? 'false' : undefined],
|
|
2112
|
-
['entryPointId',
|
|
2178
|
+
['entryPointId', rootEntryPointText],
|
|
2113
2179
|
['tags', tagValues.length > 0 ? `[${tagValues.map((t) => tsText(t)).join(', ')}]` : undefined],
|
|
2114
2180
|
[
|
|
2115
2181
|
'caseManagement',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The default `uipath:entryPointId` for one start event.
|
|
3
|
+
*
|
|
4
|
+
* @param builderProcessId - The id `bpmn(id)` was called with — not the `bpmn:process` id
|
|
5
|
+
* on the wire, which `processId()` folds every non-identifier name to `Process_1`, so
|
|
6
|
+
* two such processes would share every start's id.
|
|
7
|
+
* @param startEventId - The start event's element id.
|
|
8
|
+
*/
|
|
9
|
+
export declare function defaultEntryPointId(builderProcessId: string, startEventId: string): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The entry-point id a start event carries when nobody chose one.
|
|
3
|
+
*
|
|
4
|
+
* The canvas writes `uipath:entryPointId` on every root start event, and the CLI's
|
|
5
|
+
* packaging (`uip maestro bpmn refresh`, UiPath/cli `bpmn-packaging/entry-points.ts`)
|
|
6
|
+
* requires exactly one, a GUID, on every root manual start — "read from the BPMN,
|
|
7
|
+
* never minted, so the derived entry-points.json is stable across runs".
|
|
8
|
+
*
|
|
9
|
+
* A root start with no id of its own gets this one: the first 128 bits of SHA-256
|
|
10
|
+
* over the builder's process id and the start event id, in GUID shape — the derivation
|
|
11
|
+
* the Case compiler uses for an entry point's `uniqueId`. It is the same on every
|
|
12
|
+
* compile and machine, differs between processes and between starts, and is
|
|
13
|
+
* reproducible by `decompile`, which omits an id equal to it.
|
|
14
|
+
*
|
|
15
|
+
* It is derived from two names, so it CHANGES when either is renamed. The entry point's
|
|
16
|
+
* `uniqueId` is what Orchestrator rebinds a release by on upgrade
|
|
17
|
+
* (`ReleasesManager.cs`, `UniqueId`): rename `bpmn('Invoice')` or its start in a
|
|
18
|
+
* process that is already deployed, and on the next upgrade Orchestrator finds no
|
|
19
|
+
* entry point with the old `uniqueId` and silently rebinds the release to the main
|
|
20
|
+
* entry point. Pin the old id on the start (`.startEvent(id, { entryPointId })` or
|
|
21
|
+
* `.metadata({ entryPointId })`) when renaming either; `decompile` of the deployed
|
|
22
|
+
* file carries it.
|
|
23
|
+
*/
|
|
24
|
+
import { sha256Uuid } from '../core/stable-id.js';
|
|
25
|
+
/**
|
|
26
|
+
* The default `uipath:entryPointId` for one start event.
|
|
27
|
+
*
|
|
28
|
+
* @param builderProcessId - The id `bpmn(id)` was called with — not the `bpmn:process` id
|
|
29
|
+
* on the wire, which `processId()` folds every non-identifier name to `Process_1`, so
|
|
30
|
+
* two such processes would share every start's id.
|
|
31
|
+
* @param startEventId - The start event's element id.
|
|
32
|
+
*/
|
|
33
|
+
export function defaultEntryPointId(builderProcessId, startEventId) {
|
|
34
|
+
return sha256Uuid(`uipath:entryPointId/${builderProcessId}/${startEventId}`);
|
|
35
|
+
}
|