@uipath/maestro-builder-sdk 6.37.0 → 6.39.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 +73 -72
- package/dist/api-members.md +334 -330
- package/dist/bpmn/bpmn-sdk.d.ts +33 -0
- package/dist/bpmn/bpmn-sdk.js +4 -0
- package/dist/bpmn/check.js +252 -0
- package/dist/bpmn/connector-lift-verify.d.ts +32 -0
- package/dist/bpmn/connector-lift-verify.js +220 -0
- package/dist/bpmn/connector.d.ts +12 -0
- package/dist/bpmn/connector.js +16 -1
- package/dist/bpmn/declared-vars.js +2 -2
- package/dist/bpmn/decompile-cli.js +68 -6
- package/dist/bpmn/decompile.d.ts +43 -0
- package/dist/bpmn/decompile.js +260 -3
- package/dist/bpmn/serialize.js +9 -0
- package/lib/library-json.samples/index.json +9 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/update-entity-record@1.0.0.json +132 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/update-entity-record@1.0.0.v1def.json +254 -0
- package/package.json +2 -2
package/dist/bpmn/bpmn-sdk.d.ts
CHANGED
|
@@ -163,6 +163,10 @@ export type BpmnNode = {
|
|
|
163
163
|
inputs: Record<string, unknown>;
|
|
164
164
|
/** Variable the response lands in; defaults to `<id>_response`. */
|
|
165
165
|
outputVar?: string;
|
|
166
|
+
/** Variable the error payload lands in; defaults to `<id>_Error`. */
|
|
167
|
+
errorVar?: string;
|
|
168
|
+
/** Extra mapped output rows, by the variable each writes — see `BpmnConnectorOpts.outputs`. */
|
|
169
|
+
outputs?: Record<string, string | ConnectorOutputRow>;
|
|
166
170
|
/** `=`-expression that, when true, skips this activity (`uipath:activity/@skipCondition`). */
|
|
167
171
|
skipCondition?: string;
|
|
168
172
|
}) | (ActivityNodeFields & {
|
|
@@ -3140,6 +3144,20 @@ declare abstract class ScopeBuilder {
|
|
|
3140
3144
|
vars(entries: VarEntry[]): this;
|
|
3141
3145
|
private declareVar;
|
|
3142
3146
|
}
|
|
3147
|
+
/**
|
|
3148
|
+
* One extra output row on a connector task, spelled out — the object form of an entry in
|
|
3149
|
+
* {@link BpmnConnectorOpts}' `outputs`.
|
|
3150
|
+
*/
|
|
3151
|
+
export interface ConnectorOutputRow {
|
|
3152
|
+
/** The `=`-expression against the result, e.g. `'=response.Id'`. */
|
|
3153
|
+
source: string;
|
|
3154
|
+
/** The row's `name`; defaults to the variable. The designer names a row after the field it reads. */
|
|
3155
|
+
name?: string;
|
|
3156
|
+
/** The row's `type`; defaults to the variable's declared type, else `string`. */
|
|
3157
|
+
type?: string;
|
|
3158
|
+
/** The designer's `custom="true"` mark on a row the author added in the variables panel. */
|
|
3159
|
+
custom?: boolean;
|
|
3160
|
+
}
|
|
3143
3161
|
/**
|
|
3144
3162
|
* `.connector()` options: the shared connector options, plus the BPMN-only
|
|
3145
3163
|
* display `name` and output-variable override.
|
|
@@ -3164,6 +3182,21 @@ export type BpmnConnectorOpts = ConnectorOpts & ActivityOpts & {
|
|
|
3164
3182
|
* always lands in `<id>_Error`.
|
|
3165
3183
|
*/
|
|
3166
3184
|
outputVar?: string;
|
|
3185
|
+
/**
|
|
3186
|
+
* Variable the error payload lands in. Defaults to `<id>_Error`, the name every
|
|
3187
|
+
* `.connector()` has always written; an import names whatever the file bound
|
|
3188
|
+
* (`error12`), so decompile can lift a connector task without renaming its variable.
|
|
3189
|
+
*/
|
|
3190
|
+
errorVar?: string;
|
|
3191
|
+
/**
|
|
3192
|
+
* Extra output rows beside the response and the Error, by the variable each
|
|
3193
|
+
* writes: `{ invoiceId: '=response.Id' }` writes `<uipath:output name="invoiceId"
|
|
3194
|
+
* source="=response.Id" var="invoiceId"/>`; the object form names and types the row
|
|
3195
|
+
* (`{ id5: { source: '=response.Id', name: 'Id', type: 'string' } }`), which is how a
|
|
3196
|
+
* designer-added row reads back from an import. Each variable is declared for you
|
|
3197
|
+
* unless you declared it.
|
|
3198
|
+
*/
|
|
3199
|
+
outputs?: Record<string, string | ConnectorOutputRow>;
|
|
3167
3200
|
};
|
|
3168
3201
|
/** A sub-process body: the same graph methods, plus an internal node builder. */
|
|
3169
3202
|
export declare class SubProcessBuilder extends ScopeBuilder {
|
package/dist/bpmn/bpmn-sdk.js
CHANGED
|
@@ -943,9 +943,13 @@ class ScopeBuilder {
|
|
|
943
943
|
object: spec.object,
|
|
944
944
|
inputs: spec.inputs,
|
|
945
945
|
outputVar: bpmnOpts.outputVar,
|
|
946
|
+
errorVar: bpmnOpts.errorVar,
|
|
947
|
+
outputs: bpmnOpts.outputs,
|
|
946
948
|
skipCondition: bpmnOpts.skipCondition,
|
|
949
|
+
tags: bpmnOpts.tags,
|
|
947
950
|
retry: bpmnOpts.retry,
|
|
948
951
|
loop: bpmnOpts.loop,
|
|
952
|
+
errorMapping: bpmnOpts.errorMapping,
|
|
949
953
|
}, body);
|
|
950
954
|
}
|
|
951
955
|
/**
|
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);
|
|
@@ -99,6 +100,21 @@ function checkConnectors(built, opts, diags) {
|
|
|
99
100
|
checkConnectorLookups(where, n, opts, EQUALS_EXPR_DIALECT, report);
|
|
100
101
|
checkConnectorSchema(where, n, opts, EQUALS_EXPR_DIALECT, report);
|
|
101
102
|
checkConnectorContract(where, n, opts, report);
|
|
103
|
+
// Two rows writing one variable: an `outputs` key that is also the response or
|
|
104
|
+
// error variable. Both rows would be emitted and the later write wins at run time.
|
|
105
|
+
const response = n.outputVar ?? `${n.id}_response`;
|
|
106
|
+
const error = n.errorVar ?? `${n.id}_Error`;
|
|
107
|
+
for (const varId of Object.keys(n.outputs ?? {})) {
|
|
108
|
+
if (varId !== response && varId !== error)
|
|
109
|
+
continue;
|
|
110
|
+
report({
|
|
111
|
+
level: 'error',
|
|
112
|
+
code: 'CONNECTOR_OUTPUT_VAR_CLASH',
|
|
113
|
+
message: `${where}: outputs maps "${varId}", which is already the task's ${varId === response ? 'response' : 'error'} variable ` +
|
|
114
|
+
`(\`${varId === response ? 'outputVar' : 'errorVar'}\`); two rows would write it. Map the extra row to another variable, ` +
|
|
115
|
+
`or drop it and read \`vars.${varId}\` directly.`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
102
118
|
}
|
|
103
119
|
}
|
|
104
120
|
/**
|
|
@@ -365,6 +381,242 @@ function checkFolderBindings(built, diags) {
|
|
|
365
381
|
}
|
|
366
382
|
}
|
|
367
383
|
}
|
|
384
|
+
/** The `method` vocabulary of the Integration Service activity catalog (`MethodName`). */
|
|
385
|
+
const INTSVC_METHODS = new Set(['GET', 'GETBYID', 'POST', 'PUT', 'PATCH', 'DELETE']);
|
|
386
|
+
/** The catalog's identity fields on an `Intsvc.ActivityExecution` task, in the order the message names them. */
|
|
387
|
+
const INTSVC_IDENTITY = ['connectorKey', 'objectName', 'operation', 'method', 'path'];
|
|
388
|
+
/**
|
|
389
|
+
* A hand-filled `Intsvc.ActivityExecution` task: what the catalog, the refresh and
|
|
390
|
+
* the runtime require of its context that `uip maestro bpmn validate` does not report.
|
|
391
|
+
*
|
|
392
|
+
* `.connector()` and `.dataService()` write this task from the library; an author
|
|
393
|
+
* who reaches for `.activity(id, 'Intsvc.ActivityExecution', …)` — the route the skill
|
|
394
|
+
* names for a connector the library lacks — fills the identity rows by hand from
|
|
395
|
+
* `uip is activities list <key>`, and the 2026-10-16 eval run measured what goes wrong
|
|
396
|
+
* (141 SDK-authored files, 109 such tasks): `objectName: 'N/A'` and `method: 'N/A'`
|
|
397
|
+
* copied from the catalog's GENERIC rows (5 tasks — `validate` reported them Valid, and
|
|
398
|
+
* they dispatch to nothing), an empty `operation` (5), `<METHOD_FROM_CLI>`-style template
|
|
399
|
+
* placeholders left in place, and a `connection` that `uip maestro bpmn refresh` then
|
|
400
|
+
* refused with `connection must reference a root binding as "=bindings.<id>"`.
|
|
401
|
+
*
|
|
402
|
+
* What the registry's own field notes say the rows are: `operation` is the catalog
|
|
403
|
+
* activity's `Name`, `objectName` its `ObjectName`, `method` its `MethodName`, `path` the
|
|
404
|
+
* request path `is resources describe` returns, and `connection` "points to a root binding
|
|
405
|
+
* for a Connection resource" with `folderKey` a second binding for the same key. Real
|
|
406
|
+
* exports agree on the shape and differ on one spelling: `operation` is the catalog Name
|
|
407
|
+
* on the canvas (`UpdateEntityRecordV2`) and the CRUD verb in 94 of 108 hand-written v1
|
|
408
|
+
* tasks (`Create`, `Retrieve`), so that field is held to "an identifier", not a list.
|
|
409
|
+
* `method` IS a closed list: GET, GETBYID, POST, PUT, PATCH, DELETE, measured over every
|
|
410
|
+
* task in both arms and the corpus.
|
|
411
|
+
*
|
|
412
|
+
* Errors where the task cannot dispatch or the platform refuses it; warnings where it
|
|
413
|
+
* deploys but the SDK cannot tell it is right.
|
|
414
|
+
*/
|
|
415
|
+
function checkIntsvcActivities(built, diags) {
|
|
416
|
+
// The bindings the compiled process will carry: the ones declared with `.binding()`,
|
|
417
|
+
// and the ones `serialize` synthesizes for every `.connector()`, `.eventTrigger()` /
|
|
418
|
+
// `.waitForEvent()` and external task from the symbolic names they read (ids equal
|
|
419
|
+
// to the names, `connector.ts` `bindingRef`) — a hand-filled task may reuse those.
|
|
420
|
+
const declared = new Map(built.bindings.map((b) => [b.id, { resource: String(b.resource ?? ''), propertyAttribute: String(b.propertyAttribute ?? '') }]));
|
|
421
|
+
for (const n of allNodes(built.nodes)) {
|
|
422
|
+
if (n.kind !== 'connector' && n.kind !== 'connectorEvent' && n.kind !== 'externalTask')
|
|
423
|
+
continue;
|
|
424
|
+
if (n.connection && !declared.has(n.connection))
|
|
425
|
+
declared.set(n.connection, { resource: 'Connection', propertyAttribute: 'ConnectionId' });
|
|
426
|
+
if (n.folder && !declared.has(n.folder))
|
|
427
|
+
declared.set(n.folder, { resource: 'Connection', propertyAttribute: 'folderKey' });
|
|
428
|
+
}
|
|
429
|
+
const expr = (v) => typeof v === 'string' && v.startsWith('=');
|
|
430
|
+
const text = (v) => (typeof v === 'string' ? v : v === undefined || v === null ? undefined : String(v));
|
|
431
|
+
for (const n of allNodes(built.nodes)) {
|
|
432
|
+
if (n.kind !== 'typed' || n.type !== 'Intsvc.ActivityExecution')
|
|
433
|
+
continue;
|
|
434
|
+
// `contextRows`, when given, REPLACE `context` at emission (typed-node.ts), so only
|
|
435
|
+
// the rows are the task's context then; a record field beside them is not emitted.
|
|
436
|
+
const ctx = {};
|
|
437
|
+
if (n.contextRows)
|
|
438
|
+
for (const row of n.contextRows)
|
|
439
|
+
ctx[row.name] = row.value ?? row.body ?? '';
|
|
440
|
+
else
|
|
441
|
+
Object.assign(ctx, n.context);
|
|
442
|
+
const where = n.id;
|
|
443
|
+
const key = text(ctx['connectorKey']) ?? '<connector-key>';
|
|
444
|
+
// 1. A catalog placeholder: `N/A` from a generic row, or a template's `<…>`.
|
|
445
|
+
for (const field of INTSVC_IDENTITY) {
|
|
446
|
+
const v = text(ctx[field]);
|
|
447
|
+
if (v === undefined)
|
|
448
|
+
continue;
|
|
449
|
+
const placeholder = /^n\/a$/i.test(v.trim()) || /^(<|<)[^<>]*(>|>)$/.test(v.trim());
|
|
450
|
+
if (!placeholder)
|
|
451
|
+
continue;
|
|
452
|
+
diags.push({
|
|
453
|
+
level: 'error',
|
|
454
|
+
code: 'INTSVC_CATALOG_PLACEHOLDER',
|
|
455
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) has ${field}="${v}", which dispatches to nothing. ` +
|
|
456
|
+
(/^n\/a$/i.test(v.trim())
|
|
457
|
+
? `"N/A" is what \`uip is activities list ${key}\` prints on a GENERIC record verb (Create/Retrieve/Update/Delete Record), ` +
|
|
458
|
+
`whose object is chosen at design time: set objectName to an object from \`uip is resources list ${key} --connection-id <id>\`, ` +
|
|
459
|
+
`method to that verb's HTTP method, and path from \`uip is resources describe ${key} <object> --operation <Verb> --connection-id <id>\`. ` +
|
|
460
|
+
`For a curated activity, objectName and method are the row's ObjectName and MethodName.`
|
|
461
|
+
: `Replace the placeholder with the catalog's value (\`uip is activities list ${key} --output json\`: Name → operation, ObjectName → objectName, MethodName → method; ` +
|
|
462
|
+
`\`uip is resources describe\` → path).`) +
|
|
463
|
+
` Or let the library write the task: \`uip maestro registry prepare ${key} --action <name>\` then \`.connector()\`.`,
|
|
464
|
+
where,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
// 2. `method` is the catalog's closed vocabulary.
|
|
468
|
+
const method = text(ctx['method']);
|
|
469
|
+
// An empty one is EMPTY_REQUIRED_FIELD's (the registry marks it required), as for `operation`.
|
|
470
|
+
if (method !== undefined && method.trim() !== '' && !expr(method) && !/^n\/a$/i.test(method.trim()) && !/^(<|<)/.test(method.trim()) && !INTSVC_METHODS.has(method.trim())) {
|
|
471
|
+
diags.push({
|
|
472
|
+
level: 'error',
|
|
473
|
+
code: 'INTSVC_METHOD_UNKNOWN',
|
|
474
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) has method="${method}"; the activity catalog's MethodName is one of ` +
|
|
475
|
+
`${[...INTSVC_METHODS].join(', ')}` +
|
|
476
|
+
(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).`),
|
|
477
|
+
where,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
// 3. `operation` is the catalog activity's Name (or the CRUD verb hand-written exports use): an identifier.
|
|
481
|
+
const operation = text(ctx['operation']);
|
|
482
|
+
if (operation !== undefined && operation.trim() !== '' && !expr(operation) && !/^n\/a$/i.test(operation.trim()) && !/^(<|<)/.test(operation.trim())
|
|
483
|
+
&& !/^[A-Za-z][A-Za-z0-9_]*$/.test(operation.trim())) {
|
|
484
|
+
diags.push({
|
|
485
|
+
level: 'error',
|
|
486
|
+
code: 'INTSVC_OPERATION_NOT_IDENTIFIER',
|
|
487
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) has operation="${operation}"; the registry defines it as the activity catalog's Name ` +
|
|
488
|
+
`(\`uip is activities list ${key}\`: CreateTestSet, QueryEntityRecordsCurated) — an identifier, not a REST slug or a label.`,
|
|
489
|
+
where,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
// 4. `objectName` spelled as a REST path: validates, dispatches to nothing. Only a
|
|
493
|
+
// `/` says so — `::` is in curated ObjectNames too (`v2::webSearch`, airdk).
|
|
494
|
+
const objectName = text(ctx['objectName']);
|
|
495
|
+
if (objectName !== undefined && !expr(objectName) && objectName.includes('/')) {
|
|
496
|
+
diags.push({
|
|
497
|
+
level: 'warning',
|
|
498
|
+
code: 'INTSVC_OBJECT_NAME_SLUG',
|
|
499
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) has objectName="${objectName}", which reads as a REST path, not an object. The platform matches ` +
|
|
500
|
+
`on the catalog's ObjectName (\`uip is activities list ${key}\`: TestSet, UploadAttachment, v2::webSearch); a path validates and then dispatches to nothing.`,
|
|
501
|
+
where,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
// 5. The path's placeholders each need a `target: 'path'` input row of that name.
|
|
505
|
+
const path = text(ctx['path']);
|
|
506
|
+
if (path !== undefined && !expr(path)) {
|
|
507
|
+
if (!path.startsWith('/')) {
|
|
508
|
+
diags.push({
|
|
509
|
+
level: 'warning',
|
|
510
|
+
code: 'INTSVC_PATH_NOT_ABSOLUTE',
|
|
511
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) has path="${path}"; the request path \`is resources describe\` returns starts with "/" (e.g. "/v2/{ProjectId}/testsets").`,
|
|
512
|
+
where,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
const params = [...path.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1]);
|
|
516
|
+
if (params.length > 0) {
|
|
517
|
+
// Bound by a row of that name targeting the path, or by a path AGGREGATE —
|
|
518
|
+
// `inputs: { pathParameters: { ProjectId } }` emits one `target="pathParameters"`
|
|
519
|
+
// row holding every placeholder (an expression-valued aggregate binds them all).
|
|
520
|
+
const PATH_TARGETS = new Set(['path', 'pathvariable', 'pathparameters']);
|
|
521
|
+
const bound = new Set();
|
|
522
|
+
let aggregateExpression = false;
|
|
523
|
+
const aggregate = (value) => {
|
|
524
|
+
if (expr(value))
|
|
525
|
+
aggregateExpression = true;
|
|
526
|
+
else if (value && typeof value === 'object')
|
|
527
|
+
for (const k of Object.keys(value))
|
|
528
|
+
bound.add(k);
|
|
529
|
+
};
|
|
530
|
+
for (const r of n.inputRows ?? []) {
|
|
531
|
+
const target = (r.target ?? '').toLowerCase();
|
|
532
|
+
if (target === 'pathparameters' || target === 'pathvariable' || PATH_TARGETS.has(r.name.toLowerCase()))
|
|
533
|
+
aggregate(r.body ?? r.value);
|
|
534
|
+
if (PATH_TARGETS.has(target))
|
|
535
|
+
bound.add(r.name);
|
|
536
|
+
}
|
|
537
|
+
if (!n.inputRows)
|
|
538
|
+
for (const key of ['pathParameters', 'pathVariable'])
|
|
539
|
+
if (key in (n.inputs ?? {}))
|
|
540
|
+
aggregate(n.inputs[key]);
|
|
541
|
+
const unbound = aggregateExpression ? [] : params.filter((p) => !bound.has(p));
|
|
542
|
+
if (unbound.length > 0) {
|
|
543
|
+
diags.push({
|
|
544
|
+
level: 'warning',
|
|
545
|
+
code: 'INTSVC_PATH_PARAM_UNBOUND',
|
|
546
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) path "${path}" has placeholder(s) ${unbound.map((p) => `{${p}}`).join(', ')} with no input row ` +
|
|
547
|
+
`of that name targeting the path. Write \`inputs: { pathParameters: { ${unbound[0]}: '…' } }\` (the other fields stay ` +
|
|
548
|
+
`beside it), or an \`inputRows\` row \`{ name: '${unbound[0]}', type: 'string', value: '…', target: 'path' }\` — a bare ` +
|
|
549
|
+
`\`inputs: { ${unbound[0]} }\` lands in the body and leaves the placeholder unfilled.`,
|
|
550
|
+
where,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// 6. The connection and folder rows: binding references to declared Connection bindings.
|
|
556
|
+
const bindingRow = (field, attribute) => {
|
|
557
|
+
const v = text(ctx[field]);
|
|
558
|
+
if (v === undefined || v.trim() === '')
|
|
559
|
+
return;
|
|
560
|
+
if (!v.startsWith('=bindings.')) {
|
|
561
|
+
// A literal gets the binding to declare with that value; any other
|
|
562
|
+
// expression (`=vars.connId`) is told what refresh needs instead — a
|
|
563
|
+
// connection is resolved from a root binding, never computed at run time.
|
|
564
|
+
const label = field === 'connection' ? 'conn' : 'folder';
|
|
565
|
+
const refusal = `\`uip maestro bpmn refresh\` refuses it with \`${field} must reference a root binding as "=bindings.<id>"\``;
|
|
566
|
+
diags.push({
|
|
567
|
+
level: 'error',
|
|
568
|
+
code: 'INTSVC_CONNECTION_NOT_BINDING',
|
|
569
|
+
message: expr(v)
|
|
570
|
+
? `"${n.id}" (Intsvc.ActivityExecution) has ${field}="${v}", an expression; ${refusal}. A ${field} is resolved from a root binding, ` +
|
|
571
|
+
`not computed: declare \`.binding('${label}', { resource: 'Connection', propertyAttribute: '${attribute}', value: '<${field === 'connection' ? 'connection id' : 'folder key'}>' })\` ` +
|
|
572
|
+
`and write ${field}: '=bindings.${label}'.`
|
|
573
|
+
: `"${n.id}" (Intsvc.ActivityExecution) has ${field}="${v}", a literal; ${refusal}. Declare ` +
|
|
574
|
+
`\`.binding('${label}', { resource: 'Connection', propertyAttribute: '${attribute}', value: '${v}' })\` ` +
|
|
575
|
+
`and write ${field}: '=bindings.${label}'.`,
|
|
576
|
+
where,
|
|
577
|
+
});
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const id = v.slice('=bindings.'.length);
|
|
581
|
+
const b = declared.get(id);
|
|
582
|
+
if (!b) {
|
|
583
|
+
diags.push({
|
|
584
|
+
level: 'error',
|
|
585
|
+
code: 'INTSVC_BINDING_UNDECLARED',
|
|
586
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) reads ${field}="${v}" but the process declares no binding "${id}" ` +
|
|
587
|
+
`(\`uip maestro bpmn validate\` reports MISSING_BINDING). Declare it at the root: ` +
|
|
588
|
+
`\`.binding('${id}', { resource: 'Connection', propertyAttribute: '${attribute}', value: '<${field === 'connection' ? 'connection id' : 'folder key'}>' })\`` +
|
|
589
|
+
(field === 'folderKey' ? ' — keyed by its connection; the builder pairs it with the connection this task reads.' : '.'),
|
|
590
|
+
where,
|
|
591
|
+
});
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const attr = b.propertyAttribute;
|
|
595
|
+
const resource = b.resource;
|
|
596
|
+
if (attr.toLowerCase() !== attribute.toLowerCase() || resource.toLowerCase() !== 'connection') {
|
|
597
|
+
diags.push({
|
|
598
|
+
level: 'error',
|
|
599
|
+
code: 'INTSVC_BINDING_MISMATCH',
|
|
600
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) reads ${field}="${v}", but binding "${id}" is resource "${resource}" / ` +
|
|
601
|
+
`propertyAttribute "${attr}"; the ${field} row needs resource "Connection" / propertyAttribute "${attribute}".`,
|
|
602
|
+
where,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
bindingRow('connection', 'ConnectionId');
|
|
607
|
+
bindingRow('folderKey', 'folderKey');
|
|
608
|
+
if (text(ctx['connection']) === undefined) {
|
|
609
|
+
diags.push({
|
|
610
|
+
level: 'warning',
|
|
611
|
+
code: 'INTSVC_CONNECTION_MISSING',
|
|
612
|
+
message: `"${n.id}" (Intsvc.ActivityExecution) names no connection. The registry says to omit \`connection\` and \`folderKey\` only ` +
|
|
613
|
+
`when the activity uses none; a connector activity almost always does. Declare a Connection binding and read it as ` +
|
|
614
|
+
`connection: '=bindings.<id>' (\`uip is connections list ${key} --all-folders --output json\` lists the ids).`,
|
|
615
|
+
where,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
368
620
|
/**
|
|
369
621
|
* Every `id` in the document must be unique — including across KINDS.
|
|
370
622
|
*
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Library } from '../core/library.js';
|
|
2
|
+
import type { ConnectorLiftSummary } from './decompile.js';
|
|
3
|
+
type Moddle = any;
|
|
4
|
+
/** The outcome of verifying one decompile's lifted connector tasks. */
|
|
5
|
+
export interface ConnectorLiftVerdict {
|
|
6
|
+
/** Lifted elements whose recompiled task differs beyond the regenerated fields, with what differed. */
|
|
7
|
+
failed: Map<string, string>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The part of a connector task that a lift must reproduce, as one comparable
|
|
11
|
+
* string. Row order does not matter (rows are sorted by name); a scalar row's
|
|
12
|
+
* `type` attribute and the response row's schema body do not either.
|
|
13
|
+
*/
|
|
14
|
+
export declare function canonicalConnectorTask(el: Moddle): string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether each lifted connector task recompiles to the task the file carried, the
|
|
17
|
+
* regenerated fields aside.
|
|
18
|
+
*
|
|
19
|
+
* Each call is compiled ON ITS OWN — the decompiled preamble (bindings, variables,
|
|
20
|
+
* schemas), a start event, the one `.connector()` line, an end event — rather than
|
|
21
|
+
* the whole process, so a lift the library refuses (a body field it does not carry)
|
|
22
|
+
* is the only one refused, and a process with no other way to compile (a start event
|
|
23
|
+
* missing its connection, say) still gets its connector tasks verified.
|
|
24
|
+
*
|
|
25
|
+
* @param originalXml - The `.bpmn` that was decompiled.
|
|
26
|
+
* @param summary - What the lift did: the ids, the calls, and the preamble to compile them under.
|
|
27
|
+
* @param sdkEntry - Absolute path of the `bpmn-sdk.js` to build against.
|
|
28
|
+
* @param library - The connector library the lift named operations through.
|
|
29
|
+
* @returns The lifted elements that did not come back the same, each with the reason.
|
|
30
|
+
*/
|
|
31
|
+
export declare function verifyConnectorLifts(originalXml: string, summary: Pick<ConnectorLiftSummary, 'lifted' | 'calls' | 'preamble'>, sdkEntry: string, library: Library): Promise<ConnectorLiftVerdict>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* connector-lift-verify — the check that makes decompile's connector lift safe to write.
|
|
3
|
+
*
|
|
4
|
+
* A connector task written back as `.connector(key, action, inputs, …)` is NOT
|
|
5
|
+
* byte-identical on recompile, by design: the SDK regenerates what the canvas wrote
|
|
6
|
+
* and the file carried — the `metadata` blob (`designTimeMetadata`, `metadataState`,
|
|
7
|
+
* `telemetryData`), the `operation` verb, the `type` of each scalar row, the response
|
|
8
|
+
* row's schema body, `activityConfigurationVersion`. Everything else about the task
|
|
9
|
+
* must come back exactly: the element and its name, every other context row, every
|
|
10
|
+
* payload row's name, target and value, every output row's name, source and variable.
|
|
11
|
+
* This builds the lifted source, compiles it with the same library, and compares each
|
|
12
|
+
* lifted element against the original under exactly that list — so a lift that lost
|
|
13
|
+
* an input, picked the wrong action or renamed a variable is refused, element by
|
|
14
|
+
* element, and `decompile-cli` writes those verbatim with the reason.
|
|
15
|
+
*
|
|
16
|
+
* Same stance as `lift-verify.ts` for `--style nested`: the lift's rules are an
|
|
17
|
+
* argument, this is the measurement.
|
|
18
|
+
*/
|
|
19
|
+
import BpmnModdle from 'bpmn-moddle';
|
|
20
|
+
import { createRequire } from 'node:module';
|
|
21
|
+
import { buildSource } from './lift-verify.js';
|
|
22
|
+
import { serialize } from './serialize.js';
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
const uipathDescriptor = require('./uipath-moddle.v1.json');
|
|
25
|
+
/**
|
|
26
|
+
* Context rows the SDK regenerates from the library on compile; not compared.
|
|
27
|
+
* `activity` is the op identity the SDK writes beside `objectName` (the platform
|
|
28
|
+
* validator requires it, exports omit it) and `operation` the library's verb where
|
|
29
|
+
* the canvas writes the curated name; `metadata` is the canvas blob; the
|
|
30
|
+
* configuration version is #1287's stamp.
|
|
31
|
+
*/
|
|
32
|
+
const REGENERATED_CONTEXT = new Set(['metadata', 'operation', 'activity', 'activityConfigurationVersion']);
|
|
33
|
+
function stableJson(value) {
|
|
34
|
+
return JSON.stringify(value, (_key, v) => v && typeof v === 'object' && !Array.isArray(v)
|
|
35
|
+
? Object.fromEntries(Object.keys(v).sort().map((k) => [k, v[k]]))
|
|
36
|
+
: v);
|
|
37
|
+
}
|
|
38
|
+
/** A row body as a comparable string: parsed JSON when it is JSON, the text otherwise. */
|
|
39
|
+
function bodyText(body) {
|
|
40
|
+
if (body === undefined || body === null)
|
|
41
|
+
return undefined;
|
|
42
|
+
const text = String(body);
|
|
43
|
+
try {
|
|
44
|
+
return stableJson(JSON.parse(text));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function payloadOf(el) {
|
|
51
|
+
return (el.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Activity');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The part of a connector task that a lift must reproduce, as one comparable
|
|
55
|
+
* string. Row order does not matter (rows are sorted by name); a scalar row's
|
|
56
|
+
* `type` attribute and the response row's schema body do not either.
|
|
57
|
+
*/
|
|
58
|
+
export function canonicalConnectorTask(el) {
|
|
59
|
+
const payload = payloadOf(el);
|
|
60
|
+
const context = Object.fromEntries((payload?.context?.input ?? [])
|
|
61
|
+
.filter((r) => !REGENERATED_CONTEXT.has(r.name))
|
|
62
|
+
.map((r) => [r.name, r.body !== undefined ? bodyText(r.body) : (r.value ?? '')]));
|
|
63
|
+
const inputs = (payload?.input ?? [])
|
|
64
|
+
.map((r) => ({ name: r.name, target: r.target ?? null, value: r.body !== undefined ? bodyText(r.body) : String(r.value ?? '') }))
|
|
65
|
+
// `.connector()` always writes the request body row, `{}` when nothing fills it;
|
|
66
|
+
// an export writes none for an operation with no body. The same request either way.
|
|
67
|
+
.filter((r) => !(r.name === 'body' && r.target === 'body' && r.value === '{}'))
|
|
68
|
+
.sort((a, b) => String(a.name).localeCompare(String(b.name)));
|
|
69
|
+
const outputs = (payload?.output ?? [])
|
|
70
|
+
.map((r) => ({ name: r.name, source: r.source ?? null, var: r.var ?? null, custom: r.custom ?? null, target: r.target ?? null }))
|
|
71
|
+
.sort((a, b) => String(a.name).localeCompare(String(b.name)) || String(a.var).localeCompare(String(b.var)));
|
|
72
|
+
// The activity's other extensions — tags, retry, error mapping — and its loop
|
|
73
|
+
// characteristics: options `.connector()` takes, so a lift that lost one is refused.
|
|
74
|
+
const extensions = (el.extensionElements?.values ?? [])
|
|
75
|
+
.filter((x) => x.$type !== 'uipath:Activity' && x.$type !== 'uipath:EntryPointId')
|
|
76
|
+
.map(plain)
|
|
77
|
+
.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
78
|
+
return stableJson({
|
|
79
|
+
element: el.$type,
|
|
80
|
+
name: el.name ?? null,
|
|
81
|
+
type: payload?.type?.value ?? null,
|
|
82
|
+
skipCondition: payload?.skipCondition ?? null,
|
|
83
|
+
context,
|
|
84
|
+
inputs,
|
|
85
|
+
outputs,
|
|
86
|
+
extensions,
|
|
87
|
+
loop: el.loopCharacteristics ? plain(el.loopCharacteristics) : null,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/** A moddle element as plain data: its own properties, recursively, without the `$`-prefixed bookkeeping. */
|
|
91
|
+
function plain(value) {
|
|
92
|
+
if (Array.isArray(value))
|
|
93
|
+
return value.map(plain);
|
|
94
|
+
if (value && typeof value === 'object') {
|
|
95
|
+
const out = { $type: value.$type };
|
|
96
|
+
for (const [k, v] of Object.entries(value)) {
|
|
97
|
+
if (k.startsWith('$'))
|
|
98
|
+
continue;
|
|
99
|
+
out[k] = plain(v);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Every variable id the document declares or a node's output row writes. An isolated
|
|
107
|
+
* call still reads the process's variables in its expressions (`vars.response11[0].Id`),
|
|
108
|
+
* and the build's expression check refuses a read of an undeclared one — so the ones
|
|
109
|
+
* the preamble does not already declare are stubbed into the verification source.
|
|
110
|
+
*/
|
|
111
|
+
function declaredVariableIds(root) {
|
|
112
|
+
const ids = new Set();
|
|
113
|
+
const fromExt = (owner) => {
|
|
114
|
+
for (const ext of owner.extensionElements?.values ?? []) {
|
|
115
|
+
if (ext.$type !== 'uipath:Variables')
|
|
116
|
+
continue;
|
|
117
|
+
for (const list of [ext.input, ext.inputOutput, ext.output])
|
|
118
|
+
for (const v of list ?? [])
|
|
119
|
+
if (v.id)
|
|
120
|
+
ids.add(v.id);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const walk = (container) => {
|
|
124
|
+
fromExt(container);
|
|
125
|
+
for (const el of container.flowElements ?? []) {
|
|
126
|
+
fromExt(el);
|
|
127
|
+
for (const ext of el.extensionElements?.values ?? [])
|
|
128
|
+
for (const row of ext.output ?? [])
|
|
129
|
+
if (row.var)
|
|
130
|
+
ids.add(row.var);
|
|
131
|
+
if (el.$type === 'bpmn:SubProcess')
|
|
132
|
+
walk(el);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
for (const r of root.rootElements ?? []) {
|
|
136
|
+
if (r.$type === 'bpmn:Process')
|
|
137
|
+
walk(r);
|
|
138
|
+
else
|
|
139
|
+
fromExt(r);
|
|
140
|
+
}
|
|
141
|
+
return [...ids];
|
|
142
|
+
}
|
|
143
|
+
function elementsById(root) {
|
|
144
|
+
const out = new Map();
|
|
145
|
+
const walk = (container) => {
|
|
146
|
+
for (const el of container.flowElements ?? []) {
|
|
147
|
+
out.set(el.id, el);
|
|
148
|
+
if (el.$type === 'bpmn:SubProcess')
|
|
149
|
+
walk(el);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
for (const r of root.rootElements ?? [])
|
|
153
|
+
if (r.$type === 'bpmn:Process')
|
|
154
|
+
walk(r);
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Whether each lifted connector task recompiles to the task the file carried, the
|
|
159
|
+
* regenerated fields aside.
|
|
160
|
+
*
|
|
161
|
+
* Each call is compiled ON ITS OWN — the decompiled preamble (bindings, variables,
|
|
162
|
+
* schemas), a start event, the one `.connector()` line, an end event — rather than
|
|
163
|
+
* the whole process, so a lift the library refuses (a body field it does not carry)
|
|
164
|
+
* is the only one refused, and a process with no other way to compile (a start event
|
|
165
|
+
* missing its connection, say) still gets its connector tasks verified.
|
|
166
|
+
*
|
|
167
|
+
* @param originalXml - The `.bpmn` that was decompiled.
|
|
168
|
+
* @param summary - What the lift did: the ids, the calls, and the preamble to compile them under.
|
|
169
|
+
* @param sdkEntry - Absolute path of the `bpmn-sdk.js` to build against.
|
|
170
|
+
* @param library - The connector library the lift named operations through.
|
|
171
|
+
* @returns The lifted elements that did not come back the same, each with the reason.
|
|
172
|
+
*/
|
|
173
|
+
export async function verifyConnectorLifts(originalXml, summary, sdkEntry, library) {
|
|
174
|
+
const failed = new Map();
|
|
175
|
+
if (summary.lifted.length === 0)
|
|
176
|
+
return { failed };
|
|
177
|
+
const moddle = new BpmnModdle({ uipath: uipathDescriptor });
|
|
178
|
+
const original = (await moddle.fromXML(originalXml, { lax: true })).rootElement;
|
|
179
|
+
const before = elementsById(original);
|
|
180
|
+
// Declared by the preamble means a `.var()` call or a `.vars([{ id, … }])` entry — not
|
|
181
|
+
// a mention in `.preserve({ variableOrder })`, which lists derived declarations the
|
|
182
|
+
// preamble does not write.
|
|
183
|
+
const declares = (v) => {
|
|
184
|
+
const q = JSON.stringify(v).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
185
|
+
return new RegExp(`\\.var\\(${q}|\\{ id: ${q}, (?:name|type|direction)`).test(summary.preamble);
|
|
186
|
+
};
|
|
187
|
+
const stubs = declaredVariableIds(original)
|
|
188
|
+
.filter((v) => !declares(v))
|
|
189
|
+
.map((v) => ` .var(${JSON.stringify(v)}, "object")`)
|
|
190
|
+
.join('\n');
|
|
191
|
+
for (const id of summary.lifted) {
|
|
192
|
+
const call = summary.calls.get(id);
|
|
193
|
+
const a = before.get(id);
|
|
194
|
+
if (!call || !a) {
|
|
195
|
+
failed.set(id, call ? 'not in the original' : 'the lift recorded no call for it');
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const source = `${summary.preamble}\n${stubs ? `${stubs}\n` : ''} .startEvent("verify_start")\n ${call}\n .endEvent("verify_end")\n` +
|
|
199
|
+
` .sequenceFlow("verify_start", ${JSON.stringify(id)})\n .sequenceFlow(${JSON.stringify(id)}, "verify_end")\n .build();\n`;
|
|
200
|
+
let recompiled;
|
|
201
|
+
try {
|
|
202
|
+
recompiled = await serialize(await buildSource(source, sdkEntry), { library });
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
// The first three lines: the refusal and, for a build error, the diagnostic under it.
|
|
206
|
+
failed.set(id, `the lifted call did not compile: ${e.message.split('\n').slice(0, 3).join(' ').trim()}`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const b = elementsById((await moddle.fromXML(recompiled, { lax: true })).rootElement).get(id);
|
|
210
|
+
if (!b) {
|
|
211
|
+
failed.set(id, 'not in the recompiled process');
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const ca = canonicalConnectorTask(a);
|
|
215
|
+
const cb = canonicalConnectorTask(b);
|
|
216
|
+
if (ca !== cb)
|
|
217
|
+
failed.set(id, `recompiled task differs:\n was ${ca}\n now ${cb}`);
|
|
218
|
+
}
|
|
219
|
+
return { failed };
|
|
220
|
+
}
|
package/dist/bpmn/connector.d.ts
CHANGED
|
@@ -33,6 +33,18 @@ export interface ConnectorCtx {
|
|
|
33
33
|
library?: Library;
|
|
34
34
|
bindings?: Bindings;
|
|
35
35
|
bindingSink: Map<string, Record<string, unknown>>;
|
|
36
|
+
/**
|
|
37
|
+
* The bindings the author declared in source (`.binding()`), by id. A connector
|
|
38
|
+
* naming one of them takes its `resourceKey` and `default` from the declaration,
|
|
39
|
+
* the way it takes them from a bindings.json entry: without this, a `.binding('c',
|
|
40
|
+
* { value: '<guid>' })` paired with `connection: 'c'` wrote `resourceKey="c"` into
|
|
41
|
+
* the task's context — the id where every real export writes the connection's key.
|
|
42
|
+
*/
|
|
43
|
+
declaredBindings?: ReadonlyMap<string, {
|
|
44
|
+
name?: string;
|
|
45
|
+
default?: string;
|
|
46
|
+
resourceKey?: string;
|
|
47
|
+
}>;
|
|
36
48
|
/**
|
|
37
49
|
* Declared Maestro `type` for a variable id, for typing an author-mapped output
|
|
38
50
|
* row. Optional so the connector emitter (which writes its own rows) is
|
package/dist/bpmn/connector.js
CHANGED
|
@@ -116,6 +116,21 @@ export function createConnectorTask(ctx, n) {
|
|
|
116
116
|
},
|
|
117
117
|
}),
|
|
118
118
|
}),
|
|
119
|
+
// Extra mapped rows — `outputs: { <var>: '=response.<field>' }`, or the object
|
|
120
|
+
// form naming and typing the row — after the two the library implies, where
|
|
121
|
+
// every export puts the rows an author added in the designer.
|
|
122
|
+
...Object.entries(n.outputs ?? {}).map(([varId, spec]) => {
|
|
123
|
+
const row = typeof spec === 'string' ? { source: spec } : spec;
|
|
124
|
+
const props = {
|
|
125
|
+
name: row.name ?? varId,
|
|
126
|
+
type: row.type ?? ctx.varTypeOf?.(varId) ?? 'string',
|
|
127
|
+
source: row.source,
|
|
128
|
+
var: varId,
|
|
129
|
+
};
|
|
130
|
+
if (row.custom)
|
|
131
|
+
props.custom = true;
|
|
132
|
+
return m.create('uipath:Output', props);
|
|
133
|
+
}),
|
|
119
134
|
],
|
|
120
135
|
});
|
|
121
136
|
return m.create('bpmn:SendTask', {
|
|
@@ -180,7 +195,7 @@ export function connectorBindingRef(ctx, name, resource, propertyAttribute, disp
|
|
|
180
195
|
function bindingRef(ctx, name, resource, propertyAttribute, displayName, companionKey) {
|
|
181
196
|
if (!name)
|
|
182
197
|
return undefined;
|
|
183
|
-
const entry = ctx.bindings?.entry(name);
|
|
198
|
+
const entry = (ctx.bindings?.entry(name) ?? ctx.declaredBindings?.get(name));
|
|
184
199
|
const id = entry?.id ?? name;
|
|
185
200
|
// `default` is the deploy-time VALUE the runtime resolves, not the author's symbolic
|
|
186
201
|
// name for the binding. An entry that states only a `resourceKey` — which is what a
|