@uipath/maestro-builder-sdk 6.36.1 → 6.38.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 +85 -83
- package/dist/api-members.md +339 -335
- package/dist/bpmn/bpmn-sdk.d.ts +33 -0
- package/dist/bpmn/bpmn-sdk.js +4 -0
- package/dist/bpmn/check.js +79 -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 +19 -1
- package/dist/bpmn/declared-vars.js +10 -3
- package/dist/bpmn/decompile-cli.js +68 -6
- package/dist/bpmn/decompile.d.ts +43 -0
- package/dist/bpmn/decompile.js +274 -9
- package/dist/bpmn/intsvc.d.ts +9 -10
- package/dist/bpmn/intsvc.js +31 -18
- package/dist/bpmn/serialize.js +31 -24
- package/dist/cli-library.js +3 -1
- package/dist/core/cli-library.d.ts +8 -2
- package/dist/core/cli-library.js +6 -4
- package/dist/core/connector-resolve-error.d.ts +7 -1
- package/dist/core/event-checks.js +2 -2
- package/dist/core/lookups.d.ts +22 -0
- package/dist/core/lookups.js +34 -1
- package/dist/core/synthesized-definition.d.ts +15 -0
- package/dist/core/synthesized-definition.js +22 -0
- package/dist/registry/library-build/context.d.ts +16 -0
- package/dist/registry/library-build/context.js +32 -5
- package/dist/registry/prepare/actions.d.ts +15 -0
- package/dist/registry/prepare/actions.js +12 -1
- package/dist/registry/prepare/integration-service.d.ts +105 -0
- package/dist/registry/prepare/integration-service.js +218 -0
- package/dist/registry/prepare/messages.d.ts +41 -1
- package/dist/registry/prepare/messages.js +82 -1
- package/dist/registry/prepare/pipeline.js +73 -15
- package/dist/registry/uip/commands.d.ts +8 -0
- package/dist/registry/uip/commands.js +12 -0
- package/dist/serialize.js +11 -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
|
@@ -69,6 +69,7 @@ export function check(built, opts = {}) {
|
|
|
69
69
|
checkFolderBindings(built, diags);
|
|
70
70
|
checkEntryPointId(built, diags);
|
|
71
71
|
checkMethodParentheses(built, diags);
|
|
72
|
+
checkConnectorRows(built, diags);
|
|
72
73
|
checkConnectors(built, opts, diags);
|
|
73
74
|
checkConnectorEvents(built, opts, diags);
|
|
74
75
|
return diags;
|
|
@@ -98,6 +99,21 @@ function checkConnectors(built, opts, diags) {
|
|
|
98
99
|
checkConnectorLookups(where, n, opts, EQUALS_EXPR_DIALECT, report);
|
|
99
100
|
checkConnectorSchema(where, n, opts, EQUALS_EXPR_DIALECT, report);
|
|
100
101
|
checkConnectorContract(where, n, opts, report);
|
|
102
|
+
// Two rows writing one variable: an `outputs` key that is also the response or
|
|
103
|
+
// error variable. Both rows would be emitted and the later write wins at run time.
|
|
104
|
+
const response = n.outputVar ?? `${n.id}_response`;
|
|
105
|
+
const error = n.errorVar ?? `${n.id}_Error`;
|
|
106
|
+
for (const varId of Object.keys(n.outputs ?? {})) {
|
|
107
|
+
if (varId !== response && varId !== error)
|
|
108
|
+
continue;
|
|
109
|
+
report({
|
|
110
|
+
level: 'error',
|
|
111
|
+
code: 'CONNECTOR_OUTPUT_VAR_CLASH',
|
|
112
|
+
message: `${where}: outputs maps "${varId}", which is already the task's ${varId === response ? 'response' : 'error'} variable ` +
|
|
113
|
+
`(\`${varId === response ? 'outputVar' : 'errorVar'}\`); two rows would write it. Map the extra row to another variable, ` +
|
|
114
|
+
`or drop it and read \`vars.${varId}\` directly.`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
101
117
|
}
|
|
102
118
|
}
|
|
103
119
|
/**
|
|
@@ -219,6 +235,69 @@ function checkMethodParentheses(built, diags) {
|
|
|
219
235
|
};
|
|
220
236
|
walk(built.nodes, built.flows);
|
|
221
237
|
}
|
|
238
|
+
/** The Integration Service activity types, whose payload rows the platform reads only by `target`. */
|
|
239
|
+
const CONNECTOR_ACTIVITY_TYPES = new Set(['Intsvc.ActivityExecution', 'Intsvc.AsyncExecution']);
|
|
240
|
+
/**
|
|
241
|
+
* The targets the platform's reader knows (`UiPathExtensionsProvider`): anything else —
|
|
242
|
+
* an empty string included — falls through its switch to `Body`, and the row becomes the
|
|
243
|
+
* whole request body. `pathParameters` / `queryParameters` are the aggregate forms.
|
|
244
|
+
*/
|
|
245
|
+
const PLATFORM_ROW_TARGETS = new Set(['body', 'bodyField', 'header', 'path', 'query', 'file', 'pathParameters', 'queryParameters']);
|
|
246
|
+
/**
|
|
247
|
+
* A connector activity's payload rows, as the platform reads them.
|
|
248
|
+
*
|
|
249
|
+
* Integration Service reads a connector's payload rows by target only, and its reader
|
|
250
|
+
* maps a missing, empty or unknown target to `Body` — so such a row does not vanish,
|
|
251
|
+
* it REPLACES the request body. The 2026-10-16 eval run's agent wrote
|
|
252
|
+
* `inputRows: [{ name: 'script', type: 'string', value }]` for a JDBC query: it
|
|
253
|
+
* validated, and faulted live with `Value for required parameter 'script' not found`,
|
|
254
|
+
* while the v1 arm's `<uipath:input name="body" target="body">{"script":"…"}</uipath:input>`
|
|
255
|
+
* ran. `inputs: { script }` folds a request field into the body row, so the remedy is
|
|
256
|
+
* the plain record for a body field, or the row's own target when it is a real slot.
|
|
257
|
+
* And because an `inputRows` list REPLACES `inputs` at emission (typed-node.ts), a
|
|
258
|
+
* node that sets both silently drops every `inputs` field — the same live fault.
|
|
259
|
+
*/
|
|
260
|
+
function checkConnectorRows(built, diags) {
|
|
261
|
+
for (const n of allNodes(built.nodes)) {
|
|
262
|
+
if (n.kind !== 'typed' || !CONNECTOR_ACTIVITY_TYPES.has(n.type))
|
|
263
|
+
continue;
|
|
264
|
+
if (n.inputRows && Object.keys(n.inputs ?? {}).length > 0) {
|
|
265
|
+
diags.push({
|
|
266
|
+
level: 'error',
|
|
267
|
+
code: 'CONNECTOR_INPUTS_SHADOWED',
|
|
268
|
+
where: n.id,
|
|
269
|
+
message: `Connector activity "${n.id}" sets both \`inputs\` (${Object.keys(n.inputs).map((k) => `"${k}"`).join(', ')}) and \`inputRows\`; ` +
|
|
270
|
+
`an \`inputRows\` list replaces \`inputs\` entirely at emission, so those fields are never sent. Spell them as rows too ` +
|
|
271
|
+
`(a body field as \`{ name: 'body', type: 'json', target: 'body', body: { … } }\`), or drop \`inputRows\` and write every field in \`inputs\`.`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
for (const row of n.inputRows ?? []) {
|
|
275
|
+
const target = row.target ?? '';
|
|
276
|
+
if (target === '') {
|
|
277
|
+
diags.push({
|
|
278
|
+
level: 'error',
|
|
279
|
+
code: 'CONNECTOR_INPUT_ROW_NO_TARGET',
|
|
280
|
+
where: n.id,
|
|
281
|
+
message: `Connector activity "${n.id}" input row "${row.name}" has no target. Integration Service reads a connector's ` +
|
|
282
|
+
`payload rows by target, and its reader maps a missing or empty one to the request body — so this row validates and ` +
|
|
283
|
+
`then becomes the WHOLE body, replacing the body row. Give the row its target — \`target: 'body'\` folds it into ` +
|
|
284
|
+
`the request body, \`path\`/\`query\`/\`file\` are the other slots — or move EVERY row to \`inputs: { ${row.name}: … }\` ` +
|
|
285
|
+
`and drop \`inputRows\`: an \`inputRows\` list, when present, replaces \`inputs\` entirely.`,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
else if (!PLATFORM_ROW_TARGETS.has(target)) {
|
|
289
|
+
diags.push({
|
|
290
|
+
level: 'error',
|
|
291
|
+
code: 'CONNECTOR_INPUT_ROW_TARGET_UNKNOWN',
|
|
292
|
+
where: n.id,
|
|
293
|
+
message: `Connector activity "${n.id}" input row "${row.name}" has target "${target}", which the platform's reader does not know; ` +
|
|
294
|
+
`it falls through to the request body, so this row would replace the body. Use one of ` +
|
|
295
|
+
`${[...PLATFORM_ROW_TARGETS].map((t) => `'${t}'`).join(', ')}.`,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
222
301
|
const GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
223
302
|
/**
|
|
224
303
|
* `.metadata({ entryPointId })` must be a GUID.
|
|
@@ -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
|
@@ -26,6 +26,9 @@ export function createConnectorTask(ctx, n) {
|
|
|
26
26
|
// authored for rather than an inferred one.
|
|
27
27
|
ctxInput(m, 'operation', 'string', op.name ?? op.objectName ?? n.action),
|
|
28
28
|
ctxInput(m, 'connectorKey', 'string', rc.connectorKey),
|
|
29
|
+
// The registry template's own value, and the one the CLI's V1 packaging path keys
|
|
30
|
+
// on; V2 (`Data.V2`) is tracked in https://github.com/UiPath/builder-sdk/issues/1298, blocked on UiPath/cli#5209.
|
|
31
|
+
ctxInput(m, 'activityConfigurationVersion', 'string', 'v1'),
|
|
29
32
|
];
|
|
30
33
|
const connRef = bindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
|
|
31
34
|
if (connRef) {
|
|
@@ -113,6 +116,21 @@ export function createConnectorTask(ctx, n) {
|
|
|
113
116
|
},
|
|
114
117
|
}),
|
|
115
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
|
+
}),
|
|
116
134
|
],
|
|
117
135
|
});
|
|
118
136
|
return m.create('bpmn:SendTask', {
|
|
@@ -177,7 +195,7 @@ export function connectorBindingRef(ctx, name, resource, propertyAttribute, disp
|
|
|
177
195
|
function bindingRef(ctx, name, resource, propertyAttribute, displayName, companionKey) {
|
|
178
196
|
if (!name)
|
|
179
197
|
return undefined;
|
|
180
|
-
const entry = ctx.bindings?.entry(name);
|
|
198
|
+
const entry = (ctx.bindings?.entry(name) ?? ctx.declaredBindings?.get(name));
|
|
181
199
|
const id = entry?.id ?? name;
|
|
182
200
|
// `default` is the deploy-time VALUE the runtime resolves, not the author's symbolic
|
|
183
201
|
// name for the binding. An entry that states only a `resourceKey` — which is what a
|
|
@@ -15,7 +15,7 @@ import { typedNodeAdditionalOutputVars, typedNodeOutputVar } from './typed-node.
|
|
|
15
15
|
* @internal
|
|
16
16
|
*/
|
|
17
17
|
export function connectorOutputVars(n) {
|
|
18
|
-
return { response: n.outputVar ?? `${n.id}_response`, error: `${n.id}_Error` };
|
|
18
|
+
return { response: n.outputVar ?? `${n.id}_response`, error: n.errorVar ?? `${n.id}_Error` };
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* The variables a typed node writes — the ONE definition of that set.
|
|
@@ -54,12 +54,19 @@ export function typedNodeDeclaredVars(n) {
|
|
|
54
54
|
export function nodeDeclaredVars(n) {
|
|
55
55
|
if (n.kind === 'connector') {
|
|
56
56
|
const { response, error } = connectorOutputVars(n);
|
|
57
|
-
return [response, error];
|
|
57
|
+
return [response, error, ...Object.keys(n.outputs ?? {})];
|
|
58
58
|
}
|
|
59
59
|
if (n.kind === 'typed')
|
|
60
60
|
return typedNodeDeclaredVars(n);
|
|
61
|
-
|
|
61
|
+
// Spelled-out rows REPLACE the derived ones, so they are the whole answer — the
|
|
62
|
+
// same rule as `typedNodeDeclaredVars`. Reading only `outputs` here left a script
|
|
63
|
+
// task authored with `outputRows` (the form decompile writes) declaring nothing,
|
|
64
|
+
// so every `vars.<row.var>` downstream was EXPR_UNDECLARED_REF.
|
|
65
|
+
if (n.kind === 'scriptTask') {
|
|
66
|
+
if (n.outputRows)
|
|
67
|
+
return n.outputRows.map((r) => r.var).filter((v) => v !== undefined);
|
|
62
68
|
return Object.keys(n.outputs);
|
|
69
|
+
}
|
|
63
70
|
if (n.kind === 'task')
|
|
64
71
|
return Object.keys(n.set);
|
|
65
72
|
return [];
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* node --experimental-strip-types decompile-cli.js <Name.bpmn> [-o Name.bpmn.ts] [--import <specifier>] [--style explicit|nested] [--keep-flow-ids] [--keep-unused-bindings]
|
|
7
|
+
* [--library <library-json dir>] [--connectors-local <overlay dir>] [--connectors lift|verbatim]
|
|
8
|
+
*
|
|
9
|
+
* `--connectors lift` (the default when a library opens — `--library`, else the
|
|
10
|
+
* cache `registry pull` filled) writes a connector task the library can name as
|
|
11
|
+
* `.connector(key, action, inputs, { connection, folder, … })`, and verifies each
|
|
12
|
+
* lift by recompiling: a task that does not come back the same, the fields the
|
|
13
|
+
* SDK regenerates aside, is written verbatim with the reason. `--connectors
|
|
14
|
+
* verbatim` keeps every task as the file spells it, which is also what happens
|
|
15
|
+
* with no library at all.
|
|
7
16
|
*
|
|
8
17
|
* The reverse of compile-cli: reads a `.bpmn` and emits builder source whose
|
|
9
18
|
* default export re-serializes to the same XML. `--import` sets the module
|
|
@@ -29,6 +38,8 @@ import { defaultSourceOut, writeSource } from '../workdir.js';
|
|
|
29
38
|
import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
|
|
30
39
|
import { decompile } from './decompile.js';
|
|
31
40
|
import { sameGraph } from './lift-verify.js';
|
|
41
|
+
import { verifyConnectorLifts } from './connector-lift-verify.js';
|
|
42
|
+
import { openCliLibrary } from '../core/cli-library.js';
|
|
32
43
|
import { BPMN_IMPORT } from '../package-name.js';
|
|
33
44
|
import { fileURLToPath } from 'node:url';
|
|
34
45
|
/**
|
|
@@ -62,9 +73,14 @@ export async function run(argv) {
|
|
|
62
73
|
return 2;
|
|
63
74
|
}
|
|
64
75
|
}
|
|
76
|
+
const connectors = flagValue(argv, '--connectors') ?? 'lift';
|
|
77
|
+
if (connectors !== 'lift' && connectors !== 'verbatim') {
|
|
78
|
+
console.error(`decompile: --connectors must be lift or verbatim, not "${connectors}"`);
|
|
79
|
+
return 2;
|
|
80
|
+
}
|
|
65
81
|
const keepFlowIds = argv.includes('--keep-flow-ids');
|
|
66
82
|
const keepUnusedBindings = argv.includes('--keep-unused-bindings');
|
|
67
|
-
const flags = new Set(['-o', '--import', '--style']);
|
|
83
|
+
const flags = new Set(['-o', '--import', '--style', '--library', '--connectors-local', '--connectors']);
|
|
68
84
|
const bare = new Set(['--keep-flow-ids', '--keep-unused-bindings']);
|
|
69
85
|
const input = argv.find((a, i) => !bare.has(a) && !flags.has(a) && !flags.has(argv[i - 1]));
|
|
70
86
|
if (!input) {
|
|
@@ -74,8 +90,34 @@ export async function run(argv) {
|
|
|
74
90
|
const xml = readFileSync(input, 'utf8');
|
|
75
91
|
const importSpecifier = flagValue(argv, '--import') ?? BPMN_IMPORT;
|
|
76
92
|
let pruned = [];
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
// The connector lift: name each connector task through the library, then keep only
|
|
94
|
+
// the lifts that verify. One decompile to plan, one verify, and — only when a lift
|
|
95
|
+
// was refused — one more decompile with those tasks pinned verbatim. The pinned set
|
|
96
|
+
// then rides into the nested-style passes below, so the flat and nested sources the
|
|
97
|
+
// nested verify compares make the same connector decisions.
|
|
98
|
+
const library = connectors === 'lift' ? openCliLibrary(argv) : undefined;
|
|
99
|
+
const sdkEntry = fileURLToPath(new URL('./bpmn-sdk.js', import.meta.url));
|
|
100
|
+
let connectorSummary;
|
|
101
|
+
let verbatimConnectors = new Set();
|
|
102
|
+
const base0 = {
|
|
103
|
+
importSpecifier,
|
|
104
|
+
keepUnusedBindings,
|
|
105
|
+
onPrune: (ids) => (pruned = ids),
|
|
106
|
+
...(library ? { library, connectors: 'lift' } : {}),
|
|
107
|
+
onConnectorLift: (s) => (connectorSummary = s),
|
|
108
|
+
};
|
|
109
|
+
let source = await decompile(xml, { ...base0, verbatimConnectors });
|
|
110
|
+
if (library && connectorSummary && connectorSummary.lifted.length > 0) {
|
|
111
|
+
const verdict = await verifyConnectorLifts(xml, connectorSummary, sdkEntry, library);
|
|
112
|
+
if (verdict.failed.size > 0) {
|
|
113
|
+
verbatimConnectors = new Set(verdict.failed.keys());
|
|
114
|
+
source = await decompile(xml, { ...base0, verbatimConnectors });
|
|
115
|
+
// The summary from the second pass says "did not verify"; carry the measured reason.
|
|
116
|
+
if (connectorSummary) {
|
|
117
|
+
connectorSummary.verbatim = connectorSummary.verbatim.map((v) => ({ id: v.id, reason: verdict.failed.get(v.id) ?? v.reason }));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
79
121
|
let lifted;
|
|
80
122
|
let fallback;
|
|
81
123
|
if (style === 'nested') {
|
|
@@ -83,10 +125,11 @@ export async function run(argv) {
|
|
|
83
125
|
// package's own entry, whatever specifier the written file carries — the check is
|
|
84
126
|
// about the graph, not about module resolution. The flow ids the lift renamed are
|
|
85
127
|
// the one sanctioned difference.
|
|
86
|
-
const sdkEntry = fileURLToPath(new URL('./bpmn-sdk.js', import.meta.url));
|
|
87
128
|
for (const sequenceMode of [true, false]) {
|
|
88
129
|
let summary;
|
|
89
|
-
const nested = await decompile(xml, {
|
|
130
|
+
const nested = await decompile(xml, {
|
|
131
|
+
...base0, verbatimConnectors, style: 'nested', relabelFlows: !keepFlowIds, sequenceMode, onLift: (s) => (summary = s),
|
|
132
|
+
});
|
|
90
133
|
const renamed = new Map((summary?.renamedFlows ?? []).map((r) => [r.from, r.to]));
|
|
91
134
|
const verdict = await sameGraph(source, nested, sdkEntry, renamed);
|
|
92
135
|
if (verdict.same) {
|
|
@@ -107,6 +150,15 @@ export async function run(argv) {
|
|
|
107
150
|
console.log(`decompile: dropped ${pruned.length} binding(s) nothing references — ${pruned.slice(0, 8).join(', ')}` +
|
|
108
151
|
`${pruned.length > 8 ? `, and ${pruned.length - 8} more` : ''}. Pass --keep-unused-bindings to echo them.`);
|
|
109
152
|
}
|
|
153
|
+
if (connectorSummary && (connectorSummary.lifted.length || connectorSummary.verbatim.length)) {
|
|
154
|
+
const kept = connectorSummary.verbatim.length
|
|
155
|
+
? `; verbatim: ${connectorSummary.verbatim.map((v) => `${v.id} (${v.reason.split('\n')[0]})`).join('; ')}`
|
|
156
|
+
: '';
|
|
157
|
+
console.log(`decompile: connectors — lifted ${connectorSummary.lifted.length} to .connector()${kept}`);
|
|
158
|
+
}
|
|
159
|
+
else if (connectors === 'lift' && !library) {
|
|
160
|
+
console.log('decompile: no connector library found, connector tasks written verbatim — pass --library <library-json dir> to lift them');
|
|
161
|
+
}
|
|
110
162
|
if (lifted) {
|
|
111
163
|
const flat = lifted.leftFlat.length ? `; left flat: ${lifted.leftFlat.map((r) => `${r.id} (${r.reason})`).join('; ')}` : '';
|
|
112
164
|
const renamed = lifted.renamedFlows.length
|
|
@@ -118,7 +170,17 @@ export async function run(argv) {
|
|
|
118
170
|
else if (fallback) {
|
|
119
171
|
console.log(`decompile: --style nested could not be verified, wrote the flat form — ${fallback}`);
|
|
120
172
|
}
|
|
121
|
-
reportResult({
|
|
173
|
+
reportResult({
|
|
174
|
+
path: resolve(out),
|
|
175
|
+
style: lifted ? 'nested' : 'explicit',
|
|
176
|
+
...(lifted ? { lifted } : {}),
|
|
177
|
+
...(fallback ? { fallback } : {}),
|
|
178
|
+
// What the lift did, when a library was in play: the ids, not the preamble and
|
|
179
|
+
// calls the verifier consumed.
|
|
180
|
+
...(library && connectorSummary && (connectorSummary.lifted.length || connectorSummary.verbatim.length)
|
|
181
|
+
? { connectors: { lifted: connectorSummary.lifted, verbatim: connectorSummary.verbatim } }
|
|
182
|
+
: {}),
|
|
183
|
+
});
|
|
122
184
|
return 0;
|
|
123
185
|
}
|
|
124
186
|
runWhenInvokedDirectly(import.meta.url, 'decompile', run);
|