@uipath/maestro-builder-sdk 5.2.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/LICENSE +21 -0
- package/README.md +322 -0
- package/dist/bindings-v2.d.ts +37 -0
- package/dist/bindings-v2.js +155 -0
- package/dist/bindings.d.ts +6 -0
- package/dist/bindings.js +6 -0
- package/dist/bpmn/bpmn-expr-check.d.ts +41 -0
- package/dist/bpmn/bpmn-expr-check.js +218 -0
- package/dist/bpmn/bpmn-sdk.d.ts +2257 -0
- package/dist/bpmn/bpmn-sdk.js +1476 -0
- package/dist/bpmn/check-cli.d.ts +3 -0
- package/dist/bpmn/check-cli.js +32 -0
- package/dist/bpmn/check.d.ts +25 -0
- package/dist/bpmn/check.js +430 -0
- package/dist/bpmn/compile-cli.d.ts +3 -0
- package/dist/bpmn/compile-cli.js +60 -0
- package/dist/bpmn/connector.d.ts +56 -0
- package/dist/bpmn/connector.js +199 -0
- package/dist/bpmn/decompile-cli.d.ts +8 -0
- package/dist/bpmn/decompile-cli.js +54 -0
- package/dist/bpmn/decompile.d.ts +6 -0
- package/dist/bpmn/decompile.js +1108 -0
- package/dist/bpmn/format-profile.d.ts +27 -0
- package/dist/bpmn/format-profile.js +11 -0
- package/dist/bpmn/intsvc.d.ts +77 -0
- package/dist/bpmn/intsvc.js +155 -0
- package/dist/bpmn/load.d.ts +4 -0
- package/dist/bpmn/load.js +22 -0
- package/dist/bpmn/merge-cli.d.ts +3 -0
- package/dist/bpmn/merge-cli.js +45 -0
- package/dist/bpmn/merge.d.ts +19 -0
- package/dist/bpmn/merge.js +293 -0
- package/dist/bpmn/registry-types.json +1869 -0
- package/dist/bpmn/serialize.d.ts +28 -0
- package/dist/bpmn/serialize.js +757 -0
- package/dist/bpmn/tidy-cli.d.ts +2 -0
- package/dist/bpmn/tidy-cli.js +39 -0
- package/dist/bpmn/typed-node.d.ts +382 -0
- package/dist/bpmn/typed-node.js +551 -0
- package/dist/bpmn/uipath-moddle.v1.json +823 -0
- package/dist/bpmn/xml-ranges.d.ts +45 -0
- package/dist/bpmn/xml-ranges.js +151 -0
- package/dist/case/case-expr-check.d.ts +35 -0
- package/dist/case/case-expr-check.js +85 -0
- package/dist/case/case-sdk.d.ts +1282 -0
- package/dist/case/case-sdk.js +923 -0
- package/dist/case/check-cli.d.ts +3 -0
- package/dist/case/check-cli.js +31 -0
- package/dist/case/check.d.ts +18 -0
- package/dist/case/check.js +456 -0
- package/dist/case/compile-cli.d.ts +3 -0
- package/dist/case/compile-cli.js +291 -0
- package/dist/case/decompile-cli.d.ts +9 -0
- package/dist/case/decompile-cli.js +117 -0
- package/dist/case/decompile.d.ts +187 -0
- package/dist/case/decompile.js +1170 -0
- package/dist/case/entry-points.d.ts +79 -0
- package/dist/case/entry-points.js +118 -0
- package/dist/case/format-profile.d.ts +60 -0
- package/dist/case/format-profile.js +79 -0
- package/dist/case/ids.d.ts +24 -0
- package/dist/case/ids.js +76 -0
- package/dist/case/load.d.ts +12 -0
- package/dist/case/load.js +32 -0
- package/dist/case/preserve.d.ts +133 -0
- package/dist/case/preserve.js +105 -0
- package/dist/case/serialize.d.ts +10 -0
- package/dist/case/serialize.js +1414 -0
- package/dist/check-cli.d.ts +3 -0
- package/dist/check-cli.js +118 -0
- package/dist/check.d.ts +146 -0
- package/dist/check.js +4898 -0
- package/dist/cli/commands/bpmn.d.ts +2 -0
- package/dist/cli/commands/bpmn.js +73 -0
- package/dist/cli/commands/case.d.ts +2 -0
- package/dist/cli/commands/case.js +60 -0
- package/dist/cli/commands/flow.d.ts +2 -0
- package/dist/cli/commands/flow.js +99 -0
- package/dist/cli/commands/registry.d.ts +10 -0
- package/dist/cli/commands/registry.js +170 -0
- package/dist/cli/common.d.ts +30 -0
- package/dist/cli/common.js +92 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli-run.d.ts +2 -0
- package/dist/cli-run.js +42 -0
- package/dist/compile-cli.d.ts +3 -0
- package/dist/compile-cli.js +160 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +201 -0
- package/dist/core/actions.d.ts +3244 -0
- package/dist/core/actions.js +1062 -0
- package/dist/core/binding-messages.d.ts +1 -0
- package/dist/core/binding-messages.js +16 -0
- package/dist/core/bindings.d.ts +12 -0
- package/dist/core/bindings.js +67 -0
- package/dist/core/ceql.d.ts +73 -0
- package/dist/core/ceql.js +272 -0
- package/dist/core/cli-spelling.d.ts +39 -0
- package/dist/core/cli-spelling.js +62 -0
- package/dist/core/connector-raw-node.d.ts +21 -0
- package/dist/core/connector-raw-node.js +47 -0
- package/dist/core/connectors.d.ts +184 -0
- package/dist/core/connectors.js +128 -0
- package/dist/core/expr-check.d.ts +93 -0
- package/dist/core/expr-check.js +186 -0
- package/dist/core/expr.d.ts +323 -0
- package/dist/core/expr.js +400 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.js +10 -0
- package/dist/core/library.d.ts +440 -0
- package/dist/core/library.js +1183 -0
- package/dist/core/list-envelope.d.ts +45 -0
- package/dist/core/list-envelope.js +97 -0
- package/dist/core/lookup-store.d.ts +22 -0
- package/dist/core/lookup-store.js +85 -0
- package/dist/core/lookups.d.ts +227 -0
- package/dist/core/lookups.js +231 -0
- package/dist/core/node-classes.d.ts +234 -0
- package/dist/core/node-classes.js +158 -0
- package/dist/core/script-return.d.ts +68 -0
- package/dist/core/script-return.js +197 -0
- package/dist/core/stable-id.d.ts +28 -0
- package/dist/core/stable-id.js +37 -0
- package/dist/core-definitions.json +16249 -0
- package/dist/decompile-cli.d.ts +3 -0
- package/dist/decompile-cli.js +131 -0
- package/dist/decompile.d.ts +138 -0
- package/dist/decompile.js +2482 -0
- package/dist/emit.d.ts +10 -0
- package/dist/emit.js +60 -0
- package/dist/event-filters.d.ts +130 -0
- package/dist/event-filters.js +137 -0
- package/dist/flow-expr-check.d.ts +17 -0
- package/dist/flow-expr-check.js +193 -0
- package/dist/flow-sdk.d.ts +1341 -0
- package/dist/flow-sdk.js +1164 -0
- package/dist/flow-variables.d.ts +13 -0
- package/dist/flow-variables.js +42 -0
- package/dist/format-profile.d.ts +72 -0
- package/dist/format-profile.js +97 -0
- package/dist/generators/_connections.py +287 -0
- package/dist/generators/_events.py +355 -0
- package/dist/generators/_lookups.py +114 -0
- package/dist/generators/_naming.py +185 -0
- package/dist/generators/_objects.py +299 -0
- package/dist/generators/_resolve.py +273 -0
- package/dist/generators/convert_library_to_md.py +1441 -0
- package/dist/generators/extract_library.py +125 -0
- package/dist/generators/generate_connectors_ts.py +936 -0
- package/dist/generators/generate_library_json.py +2527 -0
- package/dist/generators/generate_triggers_ts.py +110 -0
- package/dist/generators/materialize_registry.py +155 -0
- package/dist/generators/prepare_connector.py +1688 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/ixp-definition.json +1982 -0
- package/dist/library.d.ts +6 -0
- package/dist/library.js +6 -0
- package/dist/load.d.ts +16 -0
- package/dist/load.js +95 -0
- package/dist/merge-cli.d.ts +3 -0
- package/dist/merge-cli.js +47 -0
- package/dist/merge.d.ts +125 -0
- package/dist/merge.js +0 -0
- package/dist/migrate.d.ts +15 -0
- package/dist/migrate.js +245 -0
- package/dist/node-runtime.d.ts +3 -0
- package/dist/node-runtime.js +22 -0
- package/dist/package-name.d.ts +17 -0
- package/dist/package-name.js +17 -0
- package/dist/ref-resolve.d.ts +77 -0
- package/dist/ref-resolve.js +170 -0
- package/dist/registry/cache.d.ts +55 -0
- package/dist/registry/cache.js +134 -0
- package/dist/registry/curated-overlay.d.ts +16 -0
- package/dist/registry/curated-overlay.js +62 -0
- package/dist/registry/extractor.d.ts +22 -0
- package/dist/registry/extractor.js +18 -0
- package/dist/registry/materialize-bin.d.ts +2 -0
- package/dist/registry/materialize-bin.js +7 -0
- package/dist/registry/materialize-cli.d.ts +1 -0
- package/dist/registry/materialize-cli.js +46 -0
- package/dist/registry/materializer.d.ts +23 -0
- package/dist/registry/materializer.js +34 -0
- package/dist/registry/path-cli.d.ts +1 -0
- package/dist/registry/path-cli.js +56 -0
- package/dist/registry/prepare-cli.d.ts +62 -0
- package/dist/registry/prepare-cli.js +185 -0
- package/dist/registry/pull-cli.d.ts +1 -0
- package/dist/registry/pull-cli.js +274 -0
- package/dist/registry/python.d.ts +26 -0
- package/dist/registry/python.js +46 -0
- package/dist/registry/search-cli.d.ts +52 -0
- package/dist/registry/search-cli.js +159 -0
- package/dist/schema-version.d.ts +165 -0
- package/dist/schema-version.js +148 -0
- package/dist/serialize.d.ts +64 -0
- package/dist/serialize.js +6398 -0
- package/lib/README.md +112 -0
- package/lib/library-json.samples/index.json +158 -0
- package/lib/library-json.samples/invariant-collections.json +83 -0
- package/lib/library-json.samples/list-envelopes.json +123 -0
- package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.json +131 -0
- package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.v1def.json +306 -0
- package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.json +86 -0
- package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.v1def.json +208 -0
- package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.json +2040 -0
- package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.v1def.json +195 -0
- package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.json +169 -0
- package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.v1def.json +209 -0
- package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.json +63 -0
- package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.v1def.json +262 -0
- package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.json +63 -0
- package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.v1def.json +213 -0
- package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.json +227 -0
- package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.v1def.json +373 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.json +116 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.v1def.json +369 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.json +56 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.v1def.json +256 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.json +116 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.v1def.json +320 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.json +56 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.v1def.json +207 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.json +107 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.v1def.json +264 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.json +142 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.v1def.json +306 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.json +107 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.v1def.json +217 -0
- package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.json +85 -0
- package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.v1def.json +209 -0
- package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.json +755 -0
- package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.v1def.json +513 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.json +122 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.v1def.json +227 -0
- package/package.json +85 -0
|
@@ -0,0 +1,1108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bpmn/decompile — the reverse of `serialize.ts`: turn a real UiPath `.bpmn` back
|
|
3
|
+
* into `.bpmn.ts` builder source whose default export re-serializes to the **same**
|
|
4
|
+
* XML.
|
|
5
|
+
*
|
|
6
|
+
* Phase 4 of `docs/BPMN_COVERAGE_DESIGN.md`. A BPMN process is a flat graph of
|
|
7
|
+
* elements plus edges, and every builder method is 1-1 with an element — so this
|
|
8
|
+
* is a straight structural walk of the moddle tree, with no control-flow inference
|
|
9
|
+
* and no AST parsing. Expressions are opaque strings on both sides, so they emit
|
|
10
|
+
* as string literals; no expression parser is needed.
|
|
11
|
+
*
|
|
12
|
+
* **The round-trip target is byte-identical XML**, not merely structural
|
|
13
|
+
* equivalence — `serialize` is deterministic, so anything less means something was
|
|
14
|
+
* lost. `bpmn-decompile.test.ts` asserts exactly that over the whole corpus, which
|
|
15
|
+
* is what keeps the claims below honest.
|
|
16
|
+
*
|
|
17
|
+
* What does NOT survive, and why:
|
|
18
|
+
*
|
|
19
|
+
* - **`bpmndi` diagram layout.** The SDK emits none and `tidy` regenerates it, so
|
|
20
|
+
* there is nothing to preserve. A decompiled file recompiles to semantic-only
|
|
21
|
+
* XML; re-run `tidy` for a canvas.
|
|
22
|
+
* - **Comments, method order, and formatting.** Regenerated, not preserved.
|
|
23
|
+
*
|
|
24
|
+
* Everything else round-trips: events and their definitions, all four gateways,
|
|
25
|
+
* script/variable tasks, sub-processes (recursively), boundary events, flows and
|
|
26
|
+
* conditions, variables, bindings, retry, loops, skip conditions, and every
|
|
27
|
+
* registry-typed node — connector tasks included.
|
|
28
|
+
*
|
|
29
|
+
* ## Connector tasks, and why they used to be refused
|
|
30
|
+
*
|
|
31
|
+
* `Intsvc.ActivityExecution` was refused on the grounds that it carries three things
|
|
32
|
+
* no builder surface modelled. One of the three was never really a blocker, and the
|
|
33
|
+
* other two were emitter gaps rather than facts about connectors:
|
|
34
|
+
*
|
|
35
|
+
* 1. **The library's ACTION id** — the XML has `objectName="send_message_to_user_v2"`
|
|
36
|
+
* and `operation="Create"`, never the library's `send-message-to-user`. True, and
|
|
37
|
+
* it only matters if the output has to be a `.connector(key, action, …)` call.
|
|
38
|
+
* It does not: the generic `.activity()` with `contextVerbatim` reproduces the
|
|
39
|
+
* context rows exactly as written, and no library is consulted. Refusing over this
|
|
40
|
+
* treated "cannot recover the prettiest source form" as "cannot decompile".
|
|
41
|
+
* 2. **PER-INPUT targets** — `pathParameters` / `queryParameters` / `body` each carry
|
|
42
|
+
* their own `target`. This was a real bug, and not in this file: `separateInputs`
|
|
43
|
+
* applied the spec's single `inputTarget` to every sibling row, collapsing all
|
|
44
|
+
* three onto `target="body"`. `bpmn/connector.ts` never hit it because it writes
|
|
45
|
+
* its own rows with the targets hardcoded.
|
|
46
|
+
* 3. **Output rows carrying a JSON RESPONSE SCHEMA in their body** — a real surface
|
|
47
|
+
* gap. `outputs` is a variable→expression map with nowhere to put a type or a
|
|
48
|
+
* body, so the schema had nowhere to live. `TypedOutputRow` is that surface.
|
|
49
|
+
*
|
|
50
|
+
* With 2 and 3 closed, a connector round-trips byte-identically and needs NO library
|
|
51
|
+
* on the way back, because everything it needs is already in the artifact. Measured
|
|
52
|
+
* on real Slack and Customer.io operations, the second of which exercises the
|
|
53
|
+
* path/query split.
|
|
54
|
+
*/
|
|
55
|
+
import BpmnModdle from 'bpmn-moddle';
|
|
56
|
+
import { createRequire } from 'node:module';
|
|
57
|
+
import { registryType } from './typed-node.js';
|
|
58
|
+
import { errorSchema, errorSchemaWithResponse } from './bpmn-sdk.js';
|
|
59
|
+
import { BPMN_SCHEMA_VERSIONS } from './format-profile.js';
|
|
60
|
+
import { preserveSchemaVersion } from '../schema-version.js';
|
|
61
|
+
const require = createRequire(import.meta.url);
|
|
62
|
+
const uipathDescriptor = require('./uipath-moddle.v1.json');
|
|
63
|
+
// ─── Source emitters ──────────────────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* A JS/TS string literal.
|
|
66
|
+
*
|
|
67
|
+
* `JSON.stringify` escapes quotes, backslashes and control characters, so a value
|
|
68
|
+
* out of an untrusted `.bpmn` — a label, an expression, a URL — can never break
|
|
69
|
+
* out of the literal and become code in the emitted source. U+2028/U+2029 are
|
|
70
|
+
* escaped too: legal in JSON, historically illegal in JS string literals.
|
|
71
|
+
*/
|
|
72
|
+
function str(s) {
|
|
73
|
+
return JSON.stringify(String(s ?? '')).replace(/[\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16)}`);
|
|
74
|
+
}
|
|
75
|
+
/** An arbitrary JSON value as an inert TS literal. Never executable. */
|
|
76
|
+
function literal(v) {
|
|
77
|
+
return JSON.stringify(v ?? null);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Schema bodies that need no declaration, because the platform defines them.
|
|
81
|
+
*
|
|
82
|
+
* `V2EndEventBody.BuildErrorCoreResult` in PO.BpmnEngine builds the `Error` entry
|
|
83
|
+
* with "the six expected fields", and separately mentions "the generic failure
|
|
84
|
+
* mapping shape, which includes a `response` key". Those are the two forms below —
|
|
85
|
+
* 243 and 42 byte-identical copies across five unrelated customer exports.
|
|
86
|
+
*
|
|
87
|
+
* Matched on exact bytes, so a contract that changes simply stops matching and the
|
|
88
|
+
* body is declared like any other. Nothing is coerced into the constant.
|
|
89
|
+
*/
|
|
90
|
+
const WELL_KNOWN_SCHEMAS = [
|
|
91
|
+
['errorSchema', JSON.stringify(errorSchema)],
|
|
92
|
+
['errorSchemaWithResponse', JSON.stringify(errorSchemaWithResponse)],
|
|
93
|
+
];
|
|
94
|
+
/** The SDK export that reproduces this body exactly, if one does. */
|
|
95
|
+
function wellKnownSchema(body) {
|
|
96
|
+
const text = String(body);
|
|
97
|
+
return WELL_KNOWN_SCHEMAS.find(([, json]) => json === text)?.[0];
|
|
98
|
+
}
|
|
99
|
+
/** An identifier for a shared schema, from the variable name that carries it. */
|
|
100
|
+
function schemaIdFrom(name, taken) {
|
|
101
|
+
const base = `${identifierize(name ?? 'shared')}Schema`;
|
|
102
|
+
let id = base;
|
|
103
|
+
for (let n = 2; taken.has(id); n++)
|
|
104
|
+
id = `${base}${n}`;
|
|
105
|
+
taken.add(id);
|
|
106
|
+
return id;
|
|
107
|
+
}
|
|
108
|
+
/** Decide the plan by counting how often each body appears across the document. */
|
|
109
|
+
function planSchemas(rows) {
|
|
110
|
+
const plan = { ref: new Map(), declare: [], imports: new Set() };
|
|
111
|
+
const counts = new Map();
|
|
112
|
+
const firstName = new Map();
|
|
113
|
+
for (const row of rows) {
|
|
114
|
+
if (row.body === undefined)
|
|
115
|
+
continue;
|
|
116
|
+
const text = String(row.body);
|
|
117
|
+
counts.set(text, (counts.get(text) ?? 0) + 1);
|
|
118
|
+
if (!firstName.has(text))
|
|
119
|
+
firstName.set(text, row.name);
|
|
120
|
+
}
|
|
121
|
+
const taken = new Set();
|
|
122
|
+
for (const [text, count] of counts) {
|
|
123
|
+
const known = wellKnownSchema(text);
|
|
124
|
+
if (known) {
|
|
125
|
+
plan.ref.set(text, known);
|
|
126
|
+
plan.imports.add(known);
|
|
127
|
+
}
|
|
128
|
+
else if (count > 1) {
|
|
129
|
+
const id = schemaIdFrom(firstName.get(text), taken);
|
|
130
|
+
plan.ref.set(text, str(id));
|
|
131
|
+
plan.declare.push([id, jsonLiteral(text)]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return plan;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A schema body as a JSON LITERAL rather than an escaped string.
|
|
138
|
+
*
|
|
139
|
+
* `schema: { type: "object", … }` reads; `schema: "{\"type\":\"object\",…}"` does
|
|
140
|
+
* not, and a real artifact carries thousands of characters of it. The emitters have
|
|
141
|
+
* always accepted either — `typeof x === 'string' ? x : JSON.stringify(x)` — so this
|
|
142
|
+
* only changes which one an IMPORT produces.
|
|
143
|
+
*
|
|
144
|
+
* Only when re-stringifying reproduces the source byte for byte. A body with
|
|
145
|
+
* whitespace, an unusual key order, or anything JSON.parse would not give back
|
|
146
|
+
* unchanged keeps its string form, because the round trip is held to byte identity
|
|
147
|
+
* and a prettier source is not worth breaking that.
|
|
148
|
+
*/
|
|
149
|
+
function jsonLiteral(body) {
|
|
150
|
+
const value = jsonValue(body);
|
|
151
|
+
return typeof value === 'string' ? str(value) : literal(value);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The same decision, as a VALUE rather than as source text — for a body that lands
|
|
155
|
+
* inside a record the emitter renders for itself, such as a node's `context`.
|
|
156
|
+
*
|
|
157
|
+
* The round-trip guard is the whole rule: a body is parsed only when
|
|
158
|
+
* re-stringifying it reproduces the source byte for byte, so anything with
|
|
159
|
+
* whitespace, an unusual key order, or a number JSON would not spell back the same
|
|
160
|
+
* way stays the string it was and the recompile still matches.
|
|
161
|
+
*/
|
|
162
|
+
function jsonValue(body) {
|
|
163
|
+
const text = String(body);
|
|
164
|
+
try {
|
|
165
|
+
const parsed = JSON.parse(text);
|
|
166
|
+
// A body of `null` stays the STRING it was. `null` and `undefined` are how a
|
|
167
|
+
// context record says "field not supplied" — `contextRows` skips both — so
|
|
168
|
+
// handing back a real null would delete the row on recompile rather than write
|
|
169
|
+
// it. `Intsvc.UnifiedHttpRequest` makes this concrete: its `body` field's
|
|
170
|
+
// registry default is the text `null`, and every HTTP node carries it.
|
|
171
|
+
if (parsed === null)
|
|
172
|
+
return text;
|
|
173
|
+
return JSON.stringify(parsed) === text ? parsed : text;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return text;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** An object key: a bare identifier when legal, otherwise a quoted string. */
|
|
180
|
+
function key(k) {
|
|
181
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k) ? k : str(k);
|
|
182
|
+
}
|
|
183
|
+
/** Render `{ a: 1, b: 2 }` from the entries that are present; `''` when none are. */
|
|
184
|
+
function opts(entries) {
|
|
185
|
+
const parts = entries.filter(([, v]) => v !== undefined).map(([k, v]) => `${key(k)}: ${v}`);
|
|
186
|
+
return parts.length ? `{ ${parts.join(', ')} }` : '';
|
|
187
|
+
}
|
|
188
|
+
/** A record literal, or `undefined` when the record is empty. */
|
|
189
|
+
function record(r) {
|
|
190
|
+
if (!r || Object.keys(r).length === 0)
|
|
191
|
+
return undefined;
|
|
192
|
+
return `{ ${Object.entries(r)
|
|
193
|
+
.map(([k, v]) => `${key(k)}: ${typeof v === 'string' ? str(v) : literal(v)}`)
|
|
194
|
+
.join(', ')} }`;
|
|
195
|
+
}
|
|
196
|
+
/** A collector for the fluent chain's lines. */
|
|
197
|
+
class Chain {
|
|
198
|
+
indent;
|
|
199
|
+
lines = [];
|
|
200
|
+
constructor(indent) {
|
|
201
|
+
this.indent = indent;
|
|
202
|
+
}
|
|
203
|
+
add(call) {
|
|
204
|
+
this.lines.push(`${this.indent}${call}`);
|
|
205
|
+
}
|
|
206
|
+
get all() {
|
|
207
|
+
return this.lines;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// ─── Reading the moddle tree ──────────────────────────────────────────────────
|
|
211
|
+
/** `bpmn:SendTask` → `sendTask`; the instance-tag spelling the builder speaks. */
|
|
212
|
+
function localName(type) {
|
|
213
|
+
const local = type.split(':')[1] ?? type;
|
|
214
|
+
return local.charAt(0).toLowerCase() + local.slice(1);
|
|
215
|
+
}
|
|
216
|
+
/** The `uipath:*` payload element on an element, if it has one. */
|
|
217
|
+
function payloadOf(el) {
|
|
218
|
+
for (const ext of el.extensionElements?.values ?? []) {
|
|
219
|
+
if (ext.$type === 'uipath:Activity' || ext.$type === 'uipath:Event' || ext.$type === 'uipath:Mapping')
|
|
220
|
+
return ext;
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
/** `uipath:tags` on an element as a plain record, or undefined when it has none. */
|
|
225
|
+
function tagsOf(el) {
|
|
226
|
+
const tags = (el.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Tags');
|
|
227
|
+
const rows = tags?.tags ?? [];
|
|
228
|
+
// A row with no `key` is the PROCESS-level shape (a plain value), which
|
|
229
|
+
// `.metadata({ tags })` owns; only the keyed per-node form belongs here.
|
|
230
|
+
const keyed = rows.filter((t) => t.key !== undefined);
|
|
231
|
+
if (!keyed.length)
|
|
232
|
+
return undefined;
|
|
233
|
+
return record(Object.fromEntries(keyed.map((t) => [t.key, String(t.value ?? '')])));
|
|
234
|
+
}
|
|
235
|
+
/** The `uipath:Retry` element on an element, if it has one. */
|
|
236
|
+
function retryOf(el) {
|
|
237
|
+
return (el.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Retry');
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Whether the context rows carry detail the `context` record cannot hold: a type
|
|
241
|
+
* the registry does not declare for that field, or an absent `value` attribute
|
|
242
|
+
* (which is not the same as `value=""`).
|
|
243
|
+
*/
|
|
244
|
+
function contextRowsNeedSpellingOut(payload, type) {
|
|
245
|
+
const rows = payload?.context?.input ?? [];
|
|
246
|
+
if (!rows.length || !type)
|
|
247
|
+
return false;
|
|
248
|
+
let declared;
|
|
249
|
+
try {
|
|
250
|
+
declared = new Map(registryType(type).context.map((f) => [f.name, f.type]));
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
return rows.some((r) => (r.value === undefined && r.body === undefined) ||
|
|
256
|
+
r.required !== undefined ||
|
|
257
|
+
r.target !== undefined ||
|
|
258
|
+
(r.type ?? 'string') !== (declared.get(r.name) ?? 'string'));
|
|
259
|
+
}
|
|
260
|
+
/** The context rows as `TypedContextRow` option source, in document order. */
|
|
261
|
+
function emitContextRows(payload) {
|
|
262
|
+
const rows = payload?.context?.input ?? [];
|
|
263
|
+
if (!rows.length)
|
|
264
|
+
return undefined;
|
|
265
|
+
return `[${rows
|
|
266
|
+
.map((r) => opts([
|
|
267
|
+
['name', str(r.name)],
|
|
268
|
+
['type', r.type !== undefined ? str(r.type) : undefined],
|
|
269
|
+
['required', r.required !== undefined ? 'true' : undefined],
|
|
270
|
+
['target', r.target !== undefined ? str(r.target) : undefined],
|
|
271
|
+
['value', r.value !== undefined ? str(r.value) : undefined],
|
|
272
|
+
['body', r.body !== undefined ? (r.type === 'json' ? jsonLiteral(r.body) : str(String(r.body))) : undefined],
|
|
273
|
+
]))
|
|
274
|
+
.join(', ')}]`;
|
|
275
|
+
}
|
|
276
|
+
/** `uipath:context` inputs as a plain record, json fields read from the body. */
|
|
277
|
+
function contextOf(payload) {
|
|
278
|
+
const out = {};
|
|
279
|
+
for (const input of payload?.context?.input ?? []) {
|
|
280
|
+
out[input.name] = input.type === 'json' ? jsonValue(input.body ?? '') : (input.value ?? '');
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
/** The extension type value, e.g. `Intsvc.UnifiedHttpRequest`. */
|
|
285
|
+
function extensionTypeOf(payload) {
|
|
286
|
+
return payload?.type?.value;
|
|
287
|
+
}
|
|
288
|
+
/** A retry policy back into `RetrySpec` option source. */
|
|
289
|
+
function emitRetry(retry) {
|
|
290
|
+
if (!retry)
|
|
291
|
+
return undefined;
|
|
292
|
+
const count = Number(retry.maxRetryCount ?? 0);
|
|
293
|
+
if (!Number.isFinite(count) || count < 1)
|
|
294
|
+
return undefined;
|
|
295
|
+
return opts([
|
|
296
|
+
['maxRetries', String(count)],
|
|
297
|
+
['backoff', retry.retryBackoff ? str(retry.retryBackoff) : undefined],
|
|
298
|
+
['backoffType', retry.retryBackoffType === 'Exponential' ? str('exponential') : undefined],
|
|
299
|
+
['exponentialBase', retry.exponentialBase !== undefined ? String(Number(retry.exponentialBase)) : undefined],
|
|
300
|
+
['allErrors', retry.retryAllErrors === 'true' || retry.retryAllErrors === true ? 'true' : undefined],
|
|
301
|
+
['maxDuration', retry.maxDuration ? str(retry.maxDuration) : undefined],
|
|
302
|
+
]);
|
|
303
|
+
}
|
|
304
|
+
/** The `uipath:ErrorMapping` element on an element, if it has one. */
|
|
305
|
+
function errorMappingOf(el) {
|
|
306
|
+
return (el.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:ErrorMapping');
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* `uipath:errorMapping` back into `ErrorMappingRow[]` option source.
|
|
310
|
+
*
|
|
311
|
+
* Every attribute the descriptor declares is carried, including `priority` and
|
|
312
|
+
* `detail`, which neither this SDK nor the local engine interprets — an imported
|
|
313
|
+
* artifact may hold them and dropping them would break the byte-identity round trip.
|
|
314
|
+
*
|
|
315
|
+
* `retryable` is tri-state on the wire and stays that way here: present-and-`"true"`,
|
|
316
|
+
* present-and-`"false"`, or ABSENT. Reading absent as `false` would emit an attribute
|
|
317
|
+
* the original did not have.
|
|
318
|
+
*/
|
|
319
|
+
function emitErrorMapping(em) {
|
|
320
|
+
const rows = em?.error ?? [];
|
|
321
|
+
if (!rows.length)
|
|
322
|
+
return undefined;
|
|
323
|
+
const parts = rows.map((r) => opts([
|
|
324
|
+
['code', str(r.errorRef ?? '')],
|
|
325
|
+
['retryable', r.retryable === undefined ? undefined : r.retryable === 'true' || r.retryable === true ? 'true' : 'false'],
|
|
326
|
+
['id', r.id !== undefined ? str(r.id) : undefined],
|
|
327
|
+
// A numeric-looking priority round-trips as a number; anything else stays a
|
|
328
|
+
// string, since the wire is a string either way and the author's form is unknown.
|
|
329
|
+
['priority', r.priority === undefined ? undefined : /^-?\d+$/.test(String(r.priority)) ? String(Number(r.priority)) : str(r.priority)],
|
|
330
|
+
['condition', r.condition !== undefined ? str(r.condition) : undefined],
|
|
331
|
+
['detail', r.detail !== undefined ? str(r.detail) : undefined],
|
|
332
|
+
]));
|
|
333
|
+
return `[${parts.join(', ')}]`;
|
|
334
|
+
}
|
|
335
|
+
/** Multi-instance loop characteristics back into `LoopSpec` option source. */
|
|
336
|
+
function emitLoop(el) {
|
|
337
|
+
const loop = el.loopCharacteristics;
|
|
338
|
+
if (!loop)
|
|
339
|
+
return undefined;
|
|
340
|
+
const uip = (loop.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:LoopCharacteristics');
|
|
341
|
+
if (!uip)
|
|
342
|
+
return undefined;
|
|
343
|
+
return opts([
|
|
344
|
+
['collection', str(uip.inputCollection ?? '')],
|
|
345
|
+
['itemVar', str(uip.inputElement ?? 'item')],
|
|
346
|
+
['sequential', loop.isSequential ? 'true' : undefined],
|
|
347
|
+
['completion', loop.completionCondition?.body ? str(loop.completionCondition.body) : undefined],
|
|
348
|
+
]);
|
|
349
|
+
}
|
|
350
|
+
/** An event definition back into the `{ message | timer | error | terminate }` option. */
|
|
351
|
+
function eventOptions(el) {
|
|
352
|
+
const def = (el.eventDefinitions ?? [])[0];
|
|
353
|
+
if (!def)
|
|
354
|
+
return [];
|
|
355
|
+
switch (def.$type) {
|
|
356
|
+
case 'bpmn:MessageEventDefinition':
|
|
357
|
+
return [['message', str(def.messageRef?.name ?? '')]];
|
|
358
|
+
case 'bpmn:TerminateEventDefinition':
|
|
359
|
+
return [['terminate', 'true']];
|
|
360
|
+
case 'bpmn:ErrorEventDefinition': {
|
|
361
|
+
const ref = def.errorRef;
|
|
362
|
+
// No `errorRef` is CATCH-ANY, not "no definition". Returning nothing here
|
|
363
|
+
// dropped the definition entirely, and the builder then refused the result —
|
|
364
|
+
// which is what made all 71 error boundaries in five real exports unimportable.
|
|
365
|
+
if (!ref)
|
|
366
|
+
return [['error', 'true']];
|
|
367
|
+
// The builder defaults an error's code to its name, so re-emitting the pair
|
|
368
|
+
// only matters when they differ — otherwise the shorter form round-trips.
|
|
369
|
+
return [
|
|
370
|
+
['error', ref.errorCode && ref.errorCode !== ref.name ? opts([['name', str(ref.name)], ['code', str(ref.errorCode)]]) : str(ref.name)],
|
|
371
|
+
];
|
|
372
|
+
}
|
|
373
|
+
case 'bpmn:TimerEventDefinition': {
|
|
374
|
+
const d = def.timeDuration?.body;
|
|
375
|
+
const date = def.timeDate?.body;
|
|
376
|
+
const cycle = def.timeCycle?.body;
|
|
377
|
+
// A bare duration is the shorthand the builder accepts directly.
|
|
378
|
+
if (d !== undefined && date === undefined && cycle === undefined)
|
|
379
|
+
return [['timer', str(d)]];
|
|
380
|
+
return [['timer', opts([['duration', d !== undefined ? str(d) : undefined], ['date', date !== undefined ? str(date) : undefined], ['cycle', cycle !== undefined ? str(cycle) : undefined]])]];
|
|
381
|
+
}
|
|
382
|
+
default:
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/** `uipath:output` rows split into the type's own row and the author-mapped extras. */
|
|
387
|
+
function outputsOf(payload, id, type) {
|
|
388
|
+
const rows = payload?.output ?? [];
|
|
389
|
+
const extras = {};
|
|
390
|
+
let outputVar;
|
|
391
|
+
const specName = type ? registryType(type).output?.name : undefined;
|
|
392
|
+
for (const row of rows) {
|
|
393
|
+
if (specName !== undefined && row.name === specName) {
|
|
394
|
+
// Only worth emitting when it differs from the default the builder derives.
|
|
395
|
+
outputVar = row.var;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (row.source !== undefined)
|
|
399
|
+
extras[row.var ?? row.name] = row.source;
|
|
400
|
+
}
|
|
401
|
+
return { outputVar, extras };
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Whether these rows can be reproduced by the compact `outputVar` + `outputs` pair,
|
|
405
|
+
* or need spelling out.
|
|
406
|
+
*
|
|
407
|
+
* A row with a BODY cannot: `outputs` is a variable→expression map, so the body — a
|
|
408
|
+
* connector's response schema, the biggest thing in the element — has nowhere to go.
|
|
409
|
+
* A row whose NAME differs from the variable it writes cannot either, for the same
|
|
410
|
+
* reason `add_output` needed the merge: `outputs` is keyed by the variable, so it
|
|
411
|
+
* always writes `name` and `var` the same.
|
|
412
|
+
*
|
|
413
|
+
* Deciding per artifact rather than per type is what keeps this honest. The compact
|
|
414
|
+
* form stays in use wherever it is exact, so the 30 other extension types decompile
|
|
415
|
+
* to exactly the source they did before, and only a row that genuinely needs the
|
|
416
|
+
* long form gets it.
|
|
417
|
+
*/
|
|
418
|
+
function outputRowsNeedSpellingOut(payload) {
|
|
419
|
+
const rows = payload?.output ?? [];
|
|
420
|
+
return rows.some((row) => row.body !== undefined ||
|
|
421
|
+
row.custom !== undefined ||
|
|
422
|
+
row.description !== undefined ||
|
|
423
|
+
row.target !== undefined ||
|
|
424
|
+
(row.var !== undefined && row.var !== row.name));
|
|
425
|
+
}
|
|
426
|
+
/** The output rows as `TypedOutputRow` option source, in document order. */
|
|
427
|
+
function emitOutputRows(payload) {
|
|
428
|
+
const rows = payload?.output ?? [];
|
|
429
|
+
if (!rows.length)
|
|
430
|
+
return undefined;
|
|
431
|
+
const rendered = rows.map((row) => opts([
|
|
432
|
+
['name', str(row.name)],
|
|
433
|
+
['type', row.type !== undefined ? str(row.type) : undefined],
|
|
434
|
+
['var', row.var !== undefined ? str(row.var) : undefined],
|
|
435
|
+
['source', row.source !== undefined ? str(row.source) : undefined],
|
|
436
|
+
['custom', row.custom ? 'true' : undefined],
|
|
437
|
+
['description', row.description !== undefined ? str(row.description) : undefined],
|
|
438
|
+
['target', row.target !== undefined ? str(row.target) : undefined],
|
|
439
|
+
// The body goes back as the STRING it was, not as a re-serialized object: a
|
|
440
|
+
// schema is carried, never interpreted, so nothing here can reorder its keys
|
|
441
|
+
// or drop a field this SDK does not know about.
|
|
442
|
+
['schema', row.body !== undefined ? jsonLiteral(row.body) : undefined],
|
|
443
|
+
]));
|
|
444
|
+
return `[${rendered.join(', ')}]`;
|
|
445
|
+
}
|
|
446
|
+
// ─── Element emitters ─────────────────────────────────────────────────────────
|
|
447
|
+
/**
|
|
448
|
+
* A registry-typed node.
|
|
449
|
+
*
|
|
450
|
+
* Emitted through the GENERIC `.activity()` rather than a typed method, and
|
|
451
|
+
* deliberately: `context` is reproduced verbatim, so the round-trip is exact for
|
|
452
|
+
* all 30 extension types without a per-type reverse mapping to keep in step with
|
|
453
|
+
* the registry. Typed methods are for authoring; fidelity is what an importer
|
|
454
|
+
* owes you.
|
|
455
|
+
*/
|
|
456
|
+
function emitTypedNode(chain, el, payload, type) {
|
|
457
|
+
const spec = registryType(type);
|
|
458
|
+
const id = el.id;
|
|
459
|
+
// Verbatim: reproduce every context field, in document order, and let the
|
|
460
|
+
// emitter inject nothing. Stripping the ones that match a registry default
|
|
461
|
+
// would be smaller source but a lossy round-trip — the artifact's own emitter
|
|
462
|
+
// may not have written the same set the registry would default.
|
|
463
|
+
const ctx = contextOf(payload);
|
|
464
|
+
const inputRows = payload.input ?? [];
|
|
465
|
+
let inputs;
|
|
466
|
+
if (spec.inputPattern === 'separateInputs') {
|
|
467
|
+
// A row's value is in its BODY or its `value` attribute, and which one is
|
|
468
|
+
// decided by the field's declared type — see `payloadInput` in typed-node.ts.
|
|
469
|
+
// Reading only the body silently produced `{}` for every scalar row, so a
|
|
470
|
+
// string correlation reference round-tripped to `value="[object Object]"`.
|
|
471
|
+
// Nothing before this authored a separateInputs type with a declared scalar
|
|
472
|
+
// field, so every row really was a body and the bug had nowhere to appear.
|
|
473
|
+
inputs = Object.fromEntries(inputRows.map((r) => [r.name, r.body !== undefined ? safeJson(r.body) : r.value]));
|
|
474
|
+
}
|
|
475
|
+
else if (inputRows.length) {
|
|
476
|
+
inputs = safeJson(inputRows[0].body);
|
|
477
|
+
}
|
|
478
|
+
const verbatimContext = contextRowsNeedSpellingOut(payload, type) ? emitContextRows(payload) : undefined;
|
|
479
|
+
const { outputVar, extras } = outputsOf(payload, id, type);
|
|
480
|
+
const defaultVar = spec.output ? `${id}_${identifierize(spec.output.name)}` : undefined;
|
|
481
|
+
const verbatimRows = outputRowsNeedSpellingOut(payload) ? emitOutputRows(payload) : undefined;
|
|
482
|
+
chain.add(`.activity(${str(id)}, ${str(type)}${optionalArg(opts([
|
|
483
|
+
['name', el.name !== undefined ? str(el.name) : undefined],
|
|
484
|
+
// Only when it differs from the registry's own template, so an
|
|
485
|
+
// SDK-authored node keeps the shorter source it had.
|
|
486
|
+
['typeVersion', payload.type?.version && payload.type.version !== 'v1' ? str(payload.type.version) : undefined],
|
|
487
|
+
['context', verbatimContext ? undefined : record(ctx)],
|
|
488
|
+
['contextRows', verbatimContext],
|
|
489
|
+
['inputSchema', payload.context?.inputSchema?.body !== undefined ? jsonLiteral(payload.context.inputSchema.body) : undefined],
|
|
490
|
+
['inputs', record(inputs)],
|
|
491
|
+
['outputRows', verbatimRows],
|
|
492
|
+
['outputVar', verbatimRows === undefined && outputVar !== undefined && outputVar !== defaultVar ? str(outputVar) : undefined],
|
|
493
|
+
['outputs', verbatimRows === undefined ? record(extras) : undefined],
|
|
494
|
+
['skipCondition', payload.skipCondition !== undefined ? str(payload.skipCondition) : undefined],
|
|
495
|
+
['contextVerbatim', Object.keys(ctx).length ? 'true' : undefined],
|
|
496
|
+
['tags', tagsOf(el)],
|
|
497
|
+
['retry', emitRetry(retryOf(el))],
|
|
498
|
+
['errorMapping', emitErrorMapping(errorMappingOf(el))],
|
|
499
|
+
['loop', emitLoop(el)],
|
|
500
|
+
]))})`);
|
|
501
|
+
}
|
|
502
|
+
/** Mirrors the emitter's own default-variable naming, so a default is not echoed. */
|
|
503
|
+
function identifierize(name) {
|
|
504
|
+
const words = name.split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
505
|
+
if (words.length === 0)
|
|
506
|
+
return 'response';
|
|
507
|
+
return words.map((w, i) => (i === 0 ? w.charAt(0).toLowerCase() + w.slice(1) : w.charAt(0).toUpperCase() + w.slice(1))).join('');
|
|
508
|
+
}
|
|
509
|
+
/** `, { … }` when there are options, `''` when there are none. */
|
|
510
|
+
function optionalArg(rendered) {
|
|
511
|
+
return rendered ? `, ${rendered}` : '';
|
|
512
|
+
}
|
|
513
|
+
/** Parse a CDATA body as JSON, falling back to the raw string. */
|
|
514
|
+
function safeJson(body) {
|
|
515
|
+
if (typeof body !== 'string')
|
|
516
|
+
return {};
|
|
517
|
+
try {
|
|
518
|
+
return JSON.parse(body);
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
return body;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/** A `uipath:mapping` script task. */
|
|
525
|
+
function emitScriptTask(chain, el, payload, type) {
|
|
526
|
+
const args = (payload?.input ?? []).find((r) => r.name === 'args');
|
|
527
|
+
const inputs = args ? safeJson(args.body) : undefined;
|
|
528
|
+
const outputs = {};
|
|
529
|
+
for (const row of payload?.output ?? [])
|
|
530
|
+
outputs[row.var ?? row.name] = row.source;
|
|
531
|
+
// The rows go back verbatim whenever `outputs` could not reproduce them —
|
|
532
|
+
// the product names a row after what the script RETURNS (`scriptResponse`,
|
|
533
|
+
// `Error`) while binding a differently-named variable, and the derived form
|
|
534
|
+
// names it after the variable.
|
|
535
|
+
const verbatim = outputRowsNeedSpellingOut(payload) ? emitOutputRows(payload) : undefined;
|
|
536
|
+
chain.add(`.scriptTask(${str(el.id)}, ${opts([
|
|
537
|
+
['name', el.name !== undefined ? str(el.name) : undefined],
|
|
538
|
+
['script', str(el.script ?? '')],
|
|
539
|
+
['scriptFormat', el.scriptFormat && el.scriptFormat !== 'JavaScript' ? str(el.scriptFormat) : undefined],
|
|
540
|
+
// Only when it is neither the contract's value nor the invalid one this SDK
|
|
541
|
+
// used to emit. `BPMN.ScriptTask` is NORMALISED AWAY rather than preserved:
|
|
542
|
+
// the runtime contract has no such type, so reproducing it would propagate a
|
|
543
|
+
// defect `uip maestro bpmn validate` does not catch. Same call the round trip
|
|
544
|
+
// already makes for a bare `<uipath:input name="args">`.
|
|
545
|
+
[
|
|
546
|
+
'type',
|
|
547
|
+
type !== undefined && type !== 'BPMN.Variables' && type !== 'BPMN.ScriptTask' ? str(type) : undefined,
|
|
548
|
+
],
|
|
549
|
+
['inputSchema', payload?.context?.inputSchema?.body !== undefined ? jsonLiteral(payload.context.inputSchema.body) : undefined],
|
|
550
|
+
['inputs', record(inputs)],
|
|
551
|
+
['outputs', verbatim ? undefined : record(outputs)],
|
|
552
|
+
['outputRows', verbatim],
|
|
553
|
+
['tags', tagsOf(el)],
|
|
554
|
+
['retry', emitRetry(retryOf(el))],
|
|
555
|
+
['errorMapping', emitErrorMapping(errorMappingOf(el))],
|
|
556
|
+
['loop', emitLoop(el)],
|
|
557
|
+
])})`);
|
|
558
|
+
}
|
|
559
|
+
/** A `uipath:mapping` variable task. */
|
|
560
|
+
function emitVariableTask(chain, el, payload) {
|
|
561
|
+
const set = {};
|
|
562
|
+
for (const row of payload.output ?? []) {
|
|
563
|
+
const source = String(row.source ?? '');
|
|
564
|
+
// The serializer wraps a literal as `=js:"…"`; unwrap it so the source reads
|
|
565
|
+
// the way it was authored rather than the way it was encoded.
|
|
566
|
+
const asLiteral = /^=js:(".*")$/.exec(source);
|
|
567
|
+
set[row.var ?? row.name] = asLiteral ? JSON.parse(asLiteral[1]) : source;
|
|
568
|
+
}
|
|
569
|
+
chain.add(`.task(${str(el.id)}, ${opts([
|
|
570
|
+
['name', el.name !== undefined ? str(el.name) : undefined],
|
|
571
|
+
['set', record(set)],
|
|
572
|
+
['tags', tagsOf(el)],
|
|
573
|
+
['retry', emitRetry(retryOf(el))],
|
|
574
|
+
['errorMapping', emitErrorMapping(errorMappingOf(el))],
|
|
575
|
+
['loop', emitLoop(el)],
|
|
576
|
+
])})`);
|
|
577
|
+
}
|
|
578
|
+
/** Every element of one scope, in document order, then its flows. */
|
|
579
|
+
function emitScope(chain, container, depth, indent) {
|
|
580
|
+
const flows = [];
|
|
581
|
+
for (const el of container.flowElements ?? []) {
|
|
582
|
+
if (el.$type === 'bpmn:SequenceFlow') {
|
|
583
|
+
flows.push(el);
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
emitElement(chain, el, depth, indent);
|
|
587
|
+
}
|
|
588
|
+
for (const f of flows) {
|
|
589
|
+
const auto = `Flow_${f.sourceRef?.id}_${f.targetRef?.id}`;
|
|
590
|
+
chain.add(`.sequenceFlow(${str(f.sourceRef?.id)}, ${str(f.targetRef?.id)}${optionalArg(opts([
|
|
591
|
+
['id', f.id !== auto ? str(f.id) : undefined],
|
|
592
|
+
['name', f.name !== undefined ? str(f.name) : undefined],
|
|
593
|
+
['condition', f.conditionExpression?.body !== undefined ? str(f.conditionExpression.body) : undefined],
|
|
594
|
+
]))})`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Whether a start event's event definition carries configuration a registry spec
|
|
599
|
+
* cannot supply — today, a timer's duration/date/cycle.
|
|
600
|
+
*
|
|
601
|
+
* A definition with no children is reproduced exactly by the spec-driven emitter
|
|
602
|
+
* (it emits the same bare element), so the generic `.activity()` path is lossless
|
|
603
|
+
* for it. One with a body is not.
|
|
604
|
+
*/
|
|
605
|
+
function startEventDefinitionCarriesConfig(el) {
|
|
606
|
+
for (const def of el.eventDefinitions ?? []) {
|
|
607
|
+
if (def.$type === 'bpmn:TimerEventDefinition')
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* A start event carrying a registry-backed trigger — `.startEvent({ timer, trigger })`.
|
|
614
|
+
*
|
|
615
|
+
* The schedule comes from the element's own event definition and the payload
|
|
616
|
+
* variable from the extension's output row, which is exactly the split the builder
|
|
617
|
+
* models: the definition schedules, the extension names the type and the variable.
|
|
618
|
+
*
|
|
619
|
+
* Only `Intsvc.TimerTrigger` has an authoring surface today. Another start-event
|
|
620
|
+
* type reaching here (`Intsvc.EventTrigger`, phase 3d) has no `trigger` spelling to
|
|
621
|
+
* emit, so it is refused rather than quietly written as an `.activity()` that would
|
|
622
|
+
* lose its event definition.
|
|
623
|
+
*/
|
|
624
|
+
function emitTriggerStartEvent(chain, el, payload, type) {
|
|
625
|
+
if (type !== 'Intsvc.TimerTrigger') {
|
|
626
|
+
throw new Error(`start event "${el.id}" carries extension type "${type}", whose event definition the generic ` +
|
|
627
|
+
`.activity() path cannot reproduce, so it is refused rather than silently emitted without it.`);
|
|
628
|
+
}
|
|
629
|
+
const spec = registryType(type);
|
|
630
|
+
const row = (payload.output ?? [])[0];
|
|
631
|
+
const defaultVar = spec.output ? `${el.id}_${identifierize(spec.output.name)}` : undefined;
|
|
632
|
+
const outputVar = row?.var;
|
|
633
|
+
const trigger = outputVar !== undefined && outputVar !== defaultVar ? opts([['outputVar', str(outputVar)]]) : 'true';
|
|
634
|
+
chain.add(`.startEvent(${str(el.id)}, ${opts([
|
|
635
|
+
['name', el.name !== undefined ? str(el.name) : undefined],
|
|
636
|
+
...eventOptions(el),
|
|
637
|
+
['trigger', trigger],
|
|
638
|
+
])})`);
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Kinds whose builder method OWNS the element it emits, so a payload that does not
|
|
642
|
+
* describe that element is a decoration rather than the node itself.
|
|
643
|
+
*
|
|
644
|
+
* An activity is deliberately absent: for a service task the extension type really
|
|
645
|
+
* is what the node is, and the generic `.activity()` reproduces it faithfully.
|
|
646
|
+
*/
|
|
647
|
+
const ELEMENT_OWNS = new Set([
|
|
648
|
+
'startEvent',
|
|
649
|
+
'endEvent',
|
|
650
|
+
'intermediateCatchEvent',
|
|
651
|
+
'intermediateThrowEvent',
|
|
652
|
+
'boundaryEvent',
|
|
653
|
+
'scriptTask',
|
|
654
|
+
'subProcess',
|
|
655
|
+
]);
|
|
656
|
+
/** The five element kinds the builder spells with an event method. */
|
|
657
|
+
const EVENT_KINDS = new Set([
|
|
658
|
+
'startEvent',
|
|
659
|
+
'endEvent',
|
|
660
|
+
'intermediateCatchEvent',
|
|
661
|
+
'intermediateThrowEvent',
|
|
662
|
+
'boundaryEvent',
|
|
663
|
+
]);
|
|
664
|
+
/**
|
|
665
|
+
* Whether an element is the kind its extension type says it is.
|
|
666
|
+
*
|
|
667
|
+
* The registry gives each type the element it serializes to. When the two agree,
|
|
668
|
+
* the type describes the whole element and the typed emitter reproduces it. When
|
|
669
|
+
* they disagree the payload is a DECORATION on an element the type knows nothing
|
|
670
|
+
* about — `BPMN.Variables` says `bpmn:Task`, but the product writes it on start
|
|
671
|
+
* events, end events and sub-processes too — and the element has to win.
|
|
672
|
+
*
|
|
673
|
+
* An unknown type answers `false` rather than throwing: the caller is choosing an
|
|
674
|
+
* emitter, and the typed path is the one that reports the unknown type properly.
|
|
675
|
+
*/
|
|
676
|
+
function typeDescribesElement(kind, type) {
|
|
677
|
+
if (!type)
|
|
678
|
+
return false;
|
|
679
|
+
let element;
|
|
680
|
+
try {
|
|
681
|
+
element = registryType(type).element;
|
|
682
|
+
}
|
|
683
|
+
catch {
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
return !!element && localName(element).toLowerCase() === kind.toLowerCase();
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* An event element, with whatever payload it carries as an option on it.
|
|
690
|
+
*
|
|
691
|
+
* One emitter for all five kinds because they differ only in which options are
|
|
692
|
+
* legal, and the boundary's extra two are additive.
|
|
693
|
+
*/
|
|
694
|
+
function emitEvent(chain, el, kind, payload, type) {
|
|
695
|
+
// `outputRows` is the only payload shape an event carries in the corpus. A
|
|
696
|
+
// `context` or `input` row on one would mean the registry describes the element
|
|
697
|
+
// after all, which is the branch this function is not on.
|
|
698
|
+
const eventPayload = payload && type
|
|
699
|
+
? opts([
|
|
700
|
+
['type', str(type)],
|
|
701
|
+
['outputRows', emitOutputRows(payload) ?? '[]'],
|
|
702
|
+
])
|
|
703
|
+
: undefined;
|
|
704
|
+
if (kind === 'boundaryEvent') {
|
|
705
|
+
chain.add(`.boundaryEvent(${str(el.id)}, ${opts([
|
|
706
|
+
['name', el.name !== undefined ? str(el.name) : undefined],
|
|
707
|
+
['attachedTo', str(el.attachedToRef?.id)],
|
|
708
|
+
// The builder defaults to interrupting; only the exception is worth saying.
|
|
709
|
+
['cancelActivity', el.cancelActivity === false ? 'false' : undefined],
|
|
710
|
+
...eventOptions(el),
|
|
711
|
+
['payload', eventPayload],
|
|
712
|
+
])})`);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
chain.add(`.${kind}(${str(el.id)}${optionalArg(opts([
|
|
716
|
+
['name', el.name !== undefined ? str(el.name) : undefined],
|
|
717
|
+
...eventOptions(el),
|
|
718
|
+
['payload', eventPayload],
|
|
719
|
+
]))})`);
|
|
720
|
+
}
|
|
721
|
+
function emitElement(chain, el, depth, indent) {
|
|
722
|
+
const kind = localName(el.$type);
|
|
723
|
+
const payload = payloadOf(el);
|
|
724
|
+
const type = extensionTypeOf(payload);
|
|
725
|
+
// THE ELEMENT DECIDES, whenever the payload does not describe it.
|
|
726
|
+
//
|
|
727
|
+
// The product decorates elements with payloads that say nothing about what the
|
|
728
|
+
// element is: `BPMN.Variables` on a start event (mapping process inputs onto
|
|
729
|
+
// variables), on an end event (variables onto process outputs), on a script task,
|
|
730
|
+
// on a sub-process — and `uipath:Activity` on an error boundary, to capture the
|
|
731
|
+
// error payload. `BPMN.Variables`' registry element is `bpmn:Task`, so dispatching
|
|
732
|
+
// on the extension type turned every one of them into a task. Measured over five
|
|
733
|
+
// real exports, that cost a process its only start event (recompile then failed
|
|
734
|
+
// `NO_START`), freed ten error boundaries into the main flow as service tasks,
|
|
735
|
+
// dropped 19 445 lines of script body, and flattened a sub-process holding 22
|
|
736
|
+
// elements into a single `.task()`. All of it validates.
|
|
737
|
+
//
|
|
738
|
+
// `typeDescribesElement` separates decoration from description, rather than a list
|
|
739
|
+
// of decorating types: `Intsvc.EventTrigger` and `Intsvc.TimerTrigger` genuinely
|
|
740
|
+
// ARE start events in the registry, so they keep the typed path and their context.
|
|
741
|
+
const elementWins = ELEMENT_OWNS.has(kind) && !typeDescribesElement(kind, type);
|
|
742
|
+
if (elementWins) {
|
|
743
|
+
if (EVENT_KINDS.has(kind))
|
|
744
|
+
return emitEvent(chain, el, kind, payload, type);
|
|
745
|
+
if (kind === 'scriptTask')
|
|
746
|
+
return emitScriptTask(chain, el, payload, type);
|
|
747
|
+
// `subProcess` falls through to the switch below, whose emitter already
|
|
748
|
+
// recurses into the nested scope — it was simply never reached.
|
|
749
|
+
}
|
|
750
|
+
// Registry-typed nodes: the extension type, not the element, decides.
|
|
751
|
+
if (payload && type && !elementWins) {
|
|
752
|
+
if (type === 'BPMN.ScriptTask')
|
|
753
|
+
return emitScriptTask(chain, el, payload, type);
|
|
754
|
+
if (type === 'BPMN.Variables')
|
|
755
|
+
return emitVariableTask(chain, el, payload);
|
|
756
|
+
// …except a start-event type whose event definition carries CONFIGURATION the
|
|
757
|
+
// generic `.activity()` path would drop.
|
|
758
|
+
//
|
|
759
|
+
// The distinction is the definition's CONTENT, not the element. A timer's
|
|
760
|
+
// definition holds the schedule, which the typed emitter takes from the spec —
|
|
761
|
+
// and the spec declares it empty, so a timer trigger came back as
|
|
762
|
+
// `<bpmn:timerEventDefinition />` with no schedule at all: the fires-never
|
|
763
|
+
// artifact the builder refuses to author, reachable by IMPORT instead. Both
|
|
764
|
+
// validators call that Valid, so only the byte-identity gate sees it.
|
|
765
|
+
//
|
|
766
|
+
// `Intsvc.EventTrigger` (phase 3d) is the opposite case: its
|
|
767
|
+
// `<bpmn:messageEventDefinition />` is BARE, and the spec emits exactly that, so
|
|
768
|
+
// the generic path reproduces it byte-for-byte and needs no special handling.
|
|
769
|
+
if (kind === 'startEvent' && startEventDefinitionCarriesConfig(el)) {
|
|
770
|
+
return emitTriggerStartEvent(chain, el, payload, type);
|
|
771
|
+
}
|
|
772
|
+
return emitTypedNode(chain, el, payload, type);
|
|
773
|
+
}
|
|
774
|
+
switch (kind) {
|
|
775
|
+
case 'exclusiveGateway':
|
|
776
|
+
case 'inclusiveGateway':
|
|
777
|
+
chain.add(`.${kind}(${str(el.id)}${optionalArg(opts([
|
|
778
|
+
['name', el.name !== undefined ? str(el.name) : undefined],
|
|
779
|
+
['default', el.default?.id !== undefined ? str(el.default.id) : undefined],
|
|
780
|
+
]))})`);
|
|
781
|
+
return;
|
|
782
|
+
case 'parallelGateway':
|
|
783
|
+
case 'eventBasedGateway':
|
|
784
|
+
chain.add(`.${kind}(${str(el.id)}${optionalArg(opts([['name', el.name !== undefined ? str(el.name) : undefined]]))})`);
|
|
785
|
+
return;
|
|
786
|
+
// A task element with no `uipath:*` payload — an abstract task. All seven kinds are
|
|
787
|
+
// legal bare and `validate` accepts every one, so they decompile through
|
|
788
|
+
// `.plainTask()`, which is the only surface that emits a payload-free element.
|
|
789
|
+
//
|
|
790
|
+
// `bpmn:task` used to come through `.task()` here, with a comment claiming it
|
|
791
|
+
// "round-trips as an empty one". It did not: `.task()` always emits a
|
|
792
|
+
// `BPMN.Variables` mapping, so the recompiled element GAINED a payload the original
|
|
793
|
+
// never had — a silent change, in a file whose header promises the opposite. The
|
|
794
|
+
// other six threw "has no builder equivalent", which was at least honest.
|
|
795
|
+
case 'task':
|
|
796
|
+
case 'userTask':
|
|
797
|
+
case 'serviceTask':
|
|
798
|
+
case 'sendTask':
|
|
799
|
+
case 'manualTask':
|
|
800
|
+
case 'businessRuleTask':
|
|
801
|
+
case 'receiveTask':
|
|
802
|
+
chain.add(`.plainTask(${str(el.id)}${optionalArg(opts([
|
|
803
|
+
['name', el.name !== undefined ? str(el.name) : undefined],
|
|
804
|
+
// Only when it is not the default, so the common case stays terse. Spelled
|
|
805
|
+
// the way the XML is, not the way moddle types it.
|
|
806
|
+
['element', kind === 'task' ? undefined : str(`bpmn:${kind}`)],
|
|
807
|
+
['tags', tagsOf(el)],
|
|
808
|
+
['retry', emitRetry(retryOf(el))],
|
|
809
|
+
['errorMapping', emitErrorMapping(errorMappingOf(el))],
|
|
810
|
+
['loop', emitLoop(el)],
|
|
811
|
+
]))})`);
|
|
812
|
+
return;
|
|
813
|
+
case 'subProcess': {
|
|
814
|
+
const inner = new Chain(indent.repeat(depth + 2));
|
|
815
|
+
emitScope(inner, el, depth + 1, indent);
|
|
816
|
+
emitVariablesInto(inner, el);
|
|
817
|
+
const body = inner.all.length
|
|
818
|
+
? `(sp) => {\n${inner.all.map((l, i) => (i === 0 ? `${l.replace(/^(\s*)\./, '$1sp.')}` : l)).join('\n')};\n${indent.repeat(depth + 1)}}`
|
|
819
|
+
: '(sp) => {}';
|
|
820
|
+
// The mapping a sub-process carries maps its results back out to the
|
|
821
|
+
// enclosing scope — distinct from the variables it DECLARES, which
|
|
822
|
+
// `emitVariablesInto` has already written into the body above.
|
|
823
|
+
const spPayload = payloadOf(el);
|
|
824
|
+
const spType = extensionTypeOf(spPayload);
|
|
825
|
+
chain.add(`.subProcess(${str(el.id)}, ${body}${optionalArg(opts([
|
|
826
|
+
['name', el.name !== undefined ? str(el.name) : undefined],
|
|
827
|
+
[
|
|
828
|
+
'payload',
|
|
829
|
+
spPayload && spType
|
|
830
|
+
? opts([
|
|
831
|
+
['type', str(spType)],
|
|
832
|
+
['outputRows', emitOutputRows(spPayload) ?? '[]'],
|
|
833
|
+
])
|
|
834
|
+
: undefined,
|
|
835
|
+
],
|
|
836
|
+
['triggeredByEvent', el.triggeredByEvent ? 'true' : undefined],
|
|
837
|
+
['loop', emitLoop(el)],
|
|
838
|
+
['tags', tagsOf(el)],
|
|
839
|
+
['retry', emitRetry(retryOf(el))],
|
|
840
|
+
['errorMapping', emitErrorMapping(errorMappingOf(el))],
|
|
841
|
+
]))})`);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
default:
|
|
845
|
+
throw new Error(`decompile: element <${el.$type}> (id "${el.id}") has no builder equivalent. ` +
|
|
846
|
+
'A registry-typed node needs its `uipath:activity`/`uipath:event`/`uipath:mapping` payload to be decompiled.');
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
/** The core `types` spellings the builder's `.var()` takes, from a wire value. */
|
|
850
|
+
function varType(wire) {
|
|
851
|
+
switch (wire) {
|
|
852
|
+
case 'json':
|
|
853
|
+
// `object` and `array` both serialize to `json`; `object` is the safe reading
|
|
854
|
+
// since the builder maps both the same way.
|
|
855
|
+
return 'object';
|
|
856
|
+
case 'bool':
|
|
857
|
+
return 'boolean';
|
|
858
|
+
default:
|
|
859
|
+
return wire ?? 'string';
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/** Emit `.var()` / `.input()` / `.output()` for one scope's declarations. */
|
|
863
|
+
function emitVariablesInto(chain, container, skip = new Set(),
|
|
864
|
+
/**
|
|
865
|
+
* Rewrite one `elementId` as another. Needed when the declarations are read off a
|
|
866
|
+
* COLLABORATION, whose id will not exist after a recompile — see `variablesHost`.
|
|
867
|
+
*/
|
|
868
|
+
rehome,
|
|
869
|
+
/** How each schema body should be written — see {@link planSchemas}. */
|
|
870
|
+
plan) {
|
|
871
|
+
const vars = (container.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Variables');
|
|
872
|
+
if (!vars)
|
|
873
|
+
return;
|
|
874
|
+
const groups = [
|
|
875
|
+
['input', 'input'],
|
|
876
|
+
['inputOutput', 'var'],
|
|
877
|
+
['output', 'output'],
|
|
878
|
+
];
|
|
879
|
+
for (const [prop, method] of groups) {
|
|
880
|
+
for (const v of vars[prop] ?? []) {
|
|
881
|
+
if (skip.has(v.id))
|
|
882
|
+
continue;
|
|
883
|
+
const type = varType(v.type);
|
|
884
|
+
const def = v.default === undefined ? undefined : decodeDefault(v.default, v.type);
|
|
885
|
+
chain.add(`.${method}(${str(v.id)}, ${str(type)}${optionalArg(opts([
|
|
886
|
+
['name', v.name !== undefined && v.name !== v.id ? str(v.name) : undefined],
|
|
887
|
+
['default', def],
|
|
888
|
+
['schema', v.body !== undefined ? (plan?.ref.get(String(v.body)) ?? jsonLiteral(v.body)) : undefined],
|
|
889
|
+
['required', v.required !== undefined ? 'true' : undefined],
|
|
890
|
+
[
|
|
891
|
+
'elementId',
|
|
892
|
+
v.elementId === undefined
|
|
893
|
+
? undefined
|
|
894
|
+
: str(rehome && v.elementId === rehome.from ? rehome.to : v.elementId),
|
|
895
|
+
],
|
|
896
|
+
]))})`);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
/** A variable `default` attribute back into the TS literal it came from. */
|
|
901
|
+
function decodeDefault(raw, wire) {
|
|
902
|
+
if (wire === 'json')
|
|
903
|
+
return raw; // already JSON — emit it verbatim
|
|
904
|
+
if (wire === 'number')
|
|
905
|
+
return Number.isFinite(Number(raw)) ? String(Number(raw)) : str(raw);
|
|
906
|
+
if (wire === 'boolean' || wire === 'bool')
|
|
907
|
+
return raw === 'true' ? 'true' : 'false';
|
|
908
|
+
return str(raw);
|
|
909
|
+
}
|
|
910
|
+
// ─── Entry point ──────────────────────────────────────────────────────────────
|
|
911
|
+
/**
|
|
912
|
+
* Turn `.bpmn` XML into `.bpmn.ts` builder source.
|
|
913
|
+
*
|
|
914
|
+
* @param xml - The `.bpmn` document.
|
|
915
|
+
* @param options - The import specifier for the emitted source.
|
|
916
|
+
* @returns TypeScript source whose default export re-serializes to the same XML.
|
|
917
|
+
*/
|
|
918
|
+
/**
|
|
919
|
+
* Where a document's `uipath:variables` and `uipath:bindings` actually live, and
|
|
920
|
+
* what it costs to read them from there.
|
|
921
|
+
*
|
|
922
|
+
* A pool-shaped export hangs both off the `bpmn:collaboration` rather than the
|
|
923
|
+
* `bpmn:process` — the process is reached through a `bpmn:participant` — and the
|
|
924
|
+
* SDK models a bare process. Reading only the process found nothing at all: one
|
|
925
|
+
* real customer file declares **all 112** of its variables at collaboration level,
|
|
926
|
+
* so the import produced a source whose every expression referenced something
|
|
927
|
+
* undeclared.
|
|
928
|
+
*
|
|
929
|
+
* Flattening is the honest trade — the variables are the process's, and the pool
|
|
930
|
+
* is presentation — but it is not free, so it is announced rather than performed
|
|
931
|
+
* silently: the emitted source carries a header saying what was dropped. Twenty-seven
|
|
932
|
+
* of those variables are scoped to the collaboration's own id, which will not exist
|
|
933
|
+
* after a recompile, so they are rehomed onto the process, which is a legal scope.
|
|
934
|
+
*/
|
|
935
|
+
function variablesHost(defs, process) {
|
|
936
|
+
const hasOwn = (process.extensionElements?.values ?? []).some((x) => x.$type === 'uipath:Variables' || x.$type === 'uipath:Bindings');
|
|
937
|
+
const collab = (defs.rootElements ?? []).find((r) => r.$type === 'bpmn:Collaboration');
|
|
938
|
+
if (hasOwn || !collab)
|
|
939
|
+
return { container: process, note: '' };
|
|
940
|
+
const lanes = (process.laneSets ?? []).flatMap((ls) => ls.lanes ?? []);
|
|
941
|
+
const dropped = [
|
|
942
|
+
`bpmn:collaboration ${collab.id}`,
|
|
943
|
+
...(collab.participants ?? []).map((p) => `bpmn:participant ${p.id}${p.name ? ` (${p.name})` : ''}`),
|
|
944
|
+
...lanes.map((l) => `bpmn:lane ${l.id}${l.name ? ` (${l.name})` : ''}`),
|
|
945
|
+
];
|
|
946
|
+
const note = `// FLATTENED FROM A POOL. The source document wrapped this process in a\n` +
|
|
947
|
+
`// collaboration, and the SDK models a bare bpmn:process — so recompiling this\n` +
|
|
948
|
+
`// file will NOT reproduce the pool or its lanes, and the canvas will look\n` +
|
|
949
|
+
`// different. Dropped:\n` +
|
|
950
|
+
dropped.map((d) => `// - ${d}\n`).join('') +
|
|
951
|
+
`// The variables below were declared on the collaboration and are carried over;\n` +
|
|
952
|
+
`// those scoped to it are rehomed onto the process, which is a legal scope.\n\n`;
|
|
953
|
+
return { container: collab, rehome: { from: collab.id, to: process.id }, note };
|
|
954
|
+
}
|
|
955
|
+
export async function decompile(xml, options = {}) {
|
|
956
|
+
const moddle = new BpmnModdle({ uipath: uipathDescriptor });
|
|
957
|
+
const { rootElement } = await moddle.fromXML(xml, { lax: true });
|
|
958
|
+
const defs = rootElement;
|
|
959
|
+
const process = (defs.rootElements ?? []).find((r) => r.$type === 'bpmn:Process');
|
|
960
|
+
if (!process)
|
|
961
|
+
throw new Error('decompile: no bpmn:Process in the document');
|
|
962
|
+
const indent = ' ';
|
|
963
|
+
const chain = new Chain(indent);
|
|
964
|
+
const host = variablesHost(defs, process);
|
|
965
|
+
// Process metadata, before anything else — it describes the document, and reading it
|
|
966
|
+
// first mirrors the order `serialize` writes it.
|
|
967
|
+
//
|
|
968
|
+
// Dropping it silently is a round-trip hole with teeth: every real artifact carries
|
|
969
|
+
// `uipath:migrationVersion` (the skills `edit/*` fixtures all have version="11.5"), and a
|
|
970
|
+
// grader that requires it fails a decompiled-then-recompiled process even though the
|
|
971
|
+
// author changed nothing. `.metadata()` only became expressible in #474, which is why
|
|
972
|
+
// this was not wired up when the importer landed.
|
|
973
|
+
const processExt = process.extensionElements?.values ?? [];
|
|
974
|
+
const findExt = (type) => processExt.find((x) => x.$type === type);
|
|
975
|
+
const migration = findExt('uipath:MigrationVersion');
|
|
976
|
+
const rootStart = (process.flowElements ?? []).find((x) => x.$type === 'bpmn:StartEvent');
|
|
977
|
+
const entryPoint = (rootStart?.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:EntryPointId') ?? findExt('uipath:EntryPointId');
|
|
978
|
+
const tagsExt = findExt('uipath:Tags');
|
|
979
|
+
const caseMgmt = findExt('uipath:CaseManagement');
|
|
980
|
+
const tagValues = (tagsExt?.tags ?? []).map((t) => t.value).filter((v) => typeof v === 'string');
|
|
981
|
+
// `isExecutable` is an ATTRIBUTE rather than an extension, and only worth emitting when
|
|
982
|
+
// it is false: the serializer defaults to true, and a document that omits the attribute
|
|
983
|
+
// is executable by BPMN's own default. Carrying it matters because the serializer used
|
|
984
|
+
// to hardcode true, so a non-executable process came back executable — a draft promoted
|
|
985
|
+
// to runnable, silently.
|
|
986
|
+
const nonExecutable = process.isExecutable === false;
|
|
987
|
+
// The schema generation gets its OWN call, under the name all three families
|
|
988
|
+
// use for a document's schema version — and it comes first, mirroring the
|
|
989
|
+
// order `serialize` writes the process's extension children. `.metadata({
|
|
990
|
+
// migrationVersion })` still sets the same field; this is the spelling the
|
|
991
|
+
// round-trip emits.
|
|
992
|
+
const schema = preserveSchemaVersion(BPMN_SCHEMA_VERSIONS, migration?.version);
|
|
993
|
+
if (schema.call)
|
|
994
|
+
chain.add(schema.call);
|
|
995
|
+
if (entryPoint !== undefined ||
|
|
996
|
+
tagValues.length > 0 ||
|
|
997
|
+
caseMgmt !== undefined ||
|
|
998
|
+
nonExecutable) {
|
|
999
|
+
chain.add(`.metadata(${opts([
|
|
1000
|
+
['executable', nonExecutable ? 'false' : undefined],
|
|
1001
|
+
['entryPointId', entryPoint?.value !== undefined ? str(entryPoint.value) : undefined],
|
|
1002
|
+
['tags', tagValues.length > 0 ? `[${tagValues.map((t) => str(t)).join(', ')}]` : undefined],
|
|
1003
|
+
[
|
|
1004
|
+
'caseManagement',
|
|
1005
|
+
caseMgmt?.version !== undefined
|
|
1006
|
+
? `{ version: ${str(caseMgmt.version)}${caseMgmt.value !== undefined ? `, value: ${str(caseMgmt.value)}` : ''} }`
|
|
1007
|
+
: undefined,
|
|
1008
|
+
],
|
|
1009
|
+
])})`);
|
|
1010
|
+
}
|
|
1011
|
+
// Bindings next: a typed node may reference one, and reading the declarations
|
|
1012
|
+
// before the nodes keeps the emitted source in dependency order.
|
|
1013
|
+
const bindings = (host.container.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Bindings');
|
|
1014
|
+
for (const b of bindings?.binding ?? []) {
|
|
1015
|
+
chain.add(`.binding(${str(b.id)}, ${opts([
|
|
1016
|
+
['name', b.name !== undefined && b.name !== b.id ? str(b.name) : undefined],
|
|
1017
|
+
['value', b.default !== undefined ? str(b.default) : undefined],
|
|
1018
|
+
['resource', b.resource !== undefined && b.resource !== 'custom' ? str(b.resource) : undefined],
|
|
1019
|
+
['resourceKey', b.resourceKey !== undefined && b.resourceKey !== b.default ? str(b.resourceKey) : undefined],
|
|
1020
|
+
['propertyAttribute', b.propertyAttribute !== undefined && b.propertyAttribute !== 'value' ? str(b.propertyAttribute) : undefined],
|
|
1021
|
+
['resourceSubType', b.resourceSubType !== undefined ? str(b.resourceSubType) : undefined],
|
|
1022
|
+
])})`);
|
|
1023
|
+
}
|
|
1024
|
+
// Variables the serializer DERIVES from a node's output are re-derived on
|
|
1025
|
+
// recompile, so echoing them would double-declare. Skip exactly those.
|
|
1026
|
+
const derived = derivedVariableIds(process);
|
|
1027
|
+
// Count the bodies first: whether one is written inline or shared depends on how
|
|
1028
|
+
// many variables carry it, which is only knowable across the whole document.
|
|
1029
|
+
const varsExt = (host.container.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Variables');
|
|
1030
|
+
const schemaPlan = planSchemas([
|
|
1031
|
+
...(varsExt?.input ?? []),
|
|
1032
|
+
...(varsExt?.inputOutput ?? []),
|
|
1033
|
+
...(varsExt?.output ?? []),
|
|
1034
|
+
]);
|
|
1035
|
+
for (const [id, body] of schemaPlan.declare)
|
|
1036
|
+
chain.add(`.schema(${str(id)}, ${body})`);
|
|
1037
|
+
emitVariablesInto(chain, host.container, derived, host.rehome, schemaPlan);
|
|
1038
|
+
emitScope(chain, process, 0, indent);
|
|
1039
|
+
const importFrom = options.importSpecifier ?? './bpmn-sdk.js';
|
|
1040
|
+
const imported = ['bpmn', ...[...schemaPlan.imports].sort()].join(', ');
|
|
1041
|
+
const head = `import { ${imported} } from ${str(importFrom)};\n\nexport default bpmn(${str(process.id)})`;
|
|
1042
|
+
const name = process.name !== undefined ? `\n${indent}.name(${str(process.name)})` : '';
|
|
1043
|
+
return `${host.note}${head}${name}\n${chain.all.join('\n')}\n${indent}.build();\n`;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Variable ids the serializer creates on its own from a node's output rows.
|
|
1047
|
+
*
|
|
1048
|
+
* These are not author declarations — `implicitNodeVars` adds them at serialize
|
|
1049
|
+
* time — so a decompiled file must NOT re-declare them, or the recompiled process
|
|
1050
|
+
* declares them twice and the XML stops matching.
|
|
1051
|
+
*
|
|
1052
|
+
* **This set has to equal serialize's derive set exactly.** Drift either way is a
|
|
1053
|
+
* silent round-trip bug: too narrow and a variable is declared twice (measured —
|
|
1054
|
+
* an author-mapped HITL output was re-declared, which reordered the block); too
|
|
1055
|
+
* wide and a genuinely authored variable disappears. So it mirrors
|
|
1056
|
+
* `implicitNodeVars`: EVERY output row of a typed node, its own and the
|
|
1057
|
+
* author-mapped extras alike, plus a connector's response/error pair — but only
|
|
1058
|
+
* where the declaration on disk actually LOOKS derived, per below.
|
|
1059
|
+
*
|
|
1060
|
+
* The second half of that comment was not hypothetical. `implicitNodeVars` claims a
|
|
1061
|
+
* row's variable only when the author has not declared it (`taken.has(id)` — "author-
|
|
1062
|
+
* declared names win"), so a row whose `var` is also an explicit `.var()` is NOT
|
|
1063
|
+
* derived, and echoing the row's id into the skip set deleted a real declaration.
|
|
1064
|
+
* Measured on a business rule mapping into a declared variable:
|
|
1065
|
+
* `.var('t', 'string', { default: 'none' })` vanished and came back as the implicit
|
|
1066
|
+
* `type="json"` with no default — a silently RETYPED variable, D1's hazard arriving by
|
|
1067
|
+
* a different road. Any `outputs: { <a declared var>: … }` on any typed node hit it;
|
|
1068
|
+
* the HITL case in the round-trip table missed it only because its mapped row writes
|
|
1069
|
+
* `decision` while its declared variable is `o`.
|
|
1070
|
+
*/
|
|
1071
|
+
function derivedVariableIds(process) {
|
|
1072
|
+
const candidates = new Set();
|
|
1073
|
+
const walk = (container) => {
|
|
1074
|
+
for (const el of container.flowElements ?? []) {
|
|
1075
|
+
const payload = payloadOf(el);
|
|
1076
|
+
const type = extensionTypeOf(payload);
|
|
1077
|
+
// A mapping node's rows write AUTHORED variables (a script task's outputs, a
|
|
1078
|
+
// variable task's assignments), so they are not derived.
|
|
1079
|
+
if (payload && type && type !== 'BPMN.Variables' && type !== 'BPMN.ScriptTask') {
|
|
1080
|
+
for (const row of payload.output ?? [])
|
|
1081
|
+
if (row.var)
|
|
1082
|
+
candidates.add(row.var);
|
|
1083
|
+
}
|
|
1084
|
+
if (el.$type === 'bpmn:SubProcess')
|
|
1085
|
+
walk(el);
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
walk(process);
|
|
1089
|
+
// Keep only the candidates whose declaration matches what `implicitNodeVars` would
|
|
1090
|
+
// write: `{ id, name: id, type: 'object', direction: 'inputOutput' }`, i.e. an
|
|
1091
|
+
// `inputOutput` row, `name` equal to `id`, wire type `json`, and no default or
|
|
1092
|
+
// element scope. A declaration that differs in ANY of those cannot have been
|
|
1093
|
+
// derived, so it is the author's and has to be echoed.
|
|
1094
|
+
const out = new Set();
|
|
1095
|
+
const vars = (process.extensionElements?.values ?? []).find((x) => x.$type === 'uipath:Variables');
|
|
1096
|
+
for (const v of vars?.inputOutput ?? []) {
|
|
1097
|
+
if (!candidates.has(v.id))
|
|
1098
|
+
continue;
|
|
1099
|
+
if (v.name !== undefined && v.name !== v.id)
|
|
1100
|
+
continue;
|
|
1101
|
+
if (v.type !== 'json')
|
|
1102
|
+
continue;
|
|
1103
|
+
if (v.default !== undefined || v.elementId !== undefined)
|
|
1104
|
+
continue;
|
|
1105
|
+
out.add(v.id);
|
|
1106
|
+
}
|
|
1107
|
+
return out;
|
|
1108
|
+
}
|