@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,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The BPMN document-format profile.
|
|
3
|
+
*
|
|
4
|
+
* BPMN's schema version is `uipath:migrationVersion` — the Canvas schema
|
|
5
|
+
* generation the document targets, e.g. `11` or `11.5`. It is the odd one of
|
|
6
|
+
* the three families, and the policy below says so rather than pretending
|
|
7
|
+
* otherwise:
|
|
8
|
+
*
|
|
9
|
+
* - **There is no pin.** Flow and Case each write one version by default;
|
|
10
|
+
* BPMN writes NONE. An empty `uipath:migrationVersion` fails product
|
|
11
|
+
* validation outright, so absent beats blank, and a process that never
|
|
12
|
+
* declared one keeps declaring nothing.
|
|
13
|
+
* - **The SDK's emission does not vary with it.** The `.bpmn` this SDK writes
|
|
14
|
+
* is the same XML whatever generation is stamped on it, so unlike Flow's
|
|
15
|
+
* file format and Case's grammar, the value is a stamp carried through
|
|
16
|
+
* rather than a claim the serializer has to satisfy. That is what `mode:
|
|
17
|
+
* 'verbatim'` means: any non-empty stamp is accepted, because refusing an
|
|
18
|
+
* unfamiliar one would DROP what the platform wrote — the exact loss this is
|
|
19
|
+
* here to prevent (Studio Web stamps it on export and some graders require
|
|
20
|
+
* it).
|
|
21
|
+
*
|
|
22
|
+
* So `.schemaVersion()` here is the named surface for the same field
|
|
23
|
+
* `.metadata({ migrationVersion })` has always set, and both spellings remain
|
|
24
|
+
* valid on the same document.
|
|
25
|
+
*/
|
|
26
|
+
import type { SchemaVersionPolicy } from '../schema-version.js';
|
|
27
|
+
export declare const BPMN_SCHEMA_VERSIONS: SchemaVersionPolicy;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const BPMN_SCHEMA_VERSIONS = {
|
|
2
|
+
family: 'bpmn',
|
|
3
|
+
mode: 'verbatim',
|
|
4
|
+
method: '.schemaVersion',
|
|
5
|
+
// No default: the document declares nothing unless the author (or the file a
|
|
6
|
+
// decompile read) says otherwise.
|
|
7
|
+
default: undefined,
|
|
8
|
+
migration: 'A Canvas schema generation is stamped by the product on export; this SDK carries the stamp through '
|
|
9
|
+
+ 'and never rewrites it.',
|
|
10
|
+
writable: [],
|
|
11
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bpmn/intsvc — the Phase 3d Integration Service node families.
|
|
3
|
+
*
|
|
4
|
+
* Two families, four extension types, and **no bespoke emitter**: each resolves a
|
|
5
|
+
* connector descriptor from the library, turns it into the `context`/`inputs` the
|
|
6
|
+
* registry declares, and hands that to the spec-driven `createTypedNode`. That is
|
|
7
|
+
* the Phase 3a bet paying off — once `InputFields` reached the snapshot (D12), the
|
|
8
|
+
* `separateInputs` path already emitted these correctly, so 3d is resolution and
|
|
9
|
+
* wiring rather than emission. All four shapes were validated against
|
|
10
|
+
* `uip maestro bpmn validate` as plain `.activity()` calls before this module
|
|
11
|
+
* existed.
|
|
12
|
+
*
|
|
13
|
+
* - **Connector events** (`Intsvc.EventTrigger` / `Intsvc.WaitForEvent`) — one
|
|
14
|
+
* connector operation, two placements. `trigger` starts the process, `event`
|
|
15
|
+
* pauses one mid-flow; the platform ships both with the same operation and only
|
|
16
|
+
* the library namespace differs, exactly as Flow's `onEvent()` / `waitForEvent()`
|
|
17
|
+
* pair does.
|
|
18
|
+
* - **External agent / workflow** (`Intsvc.{Sync,Async}AgentExecution`,
|
|
19
|
+
* `Intsvc.{Sync,Async}WorkflowExecution`) — an invocation of something outside
|
|
20
|
+
* this process, picked by the descriptor's `operation.subType` and the author's
|
|
21
|
+
* `mode`. The same four types the Case SDK reaches through `.externalAgent()` /
|
|
22
|
+
* `.externalWorkflow()`; the serviceType mapping is deliberately identical to
|
|
23
|
+
* `case/format-profile.ts` so one artifact family cannot drift from the other.
|
|
24
|
+
*/
|
|
25
|
+
import type { ModdleElement } from 'bpmn-moddle';
|
|
26
|
+
import type { BpmnNode } from './bpmn-sdk.js';
|
|
27
|
+
import { type ConnectorCtx } from './connector.js';
|
|
28
|
+
type El = ModdleElement;
|
|
29
|
+
type EventNode = Extract<BpmnNode, {
|
|
30
|
+
kind: 'connectorEvent';
|
|
31
|
+
}>;
|
|
32
|
+
type ExternalNode = Extract<BpmnNode, {
|
|
33
|
+
kind: 'externalTask';
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* The extension type a Phase 3d node lowers to, WITHOUT consulting the library.
|
|
37
|
+
*
|
|
38
|
+
* The emitters below take a connector event's type from the library's
|
|
39
|
+
* `runtime.serviceType`, which is the authority. This is the same answer derived
|
|
40
|
+
* from the node alone, so the serializer can declare the node's output variable
|
|
41
|
+
* before it has resolved anything — and the emitter asserts the two agree, so the
|
|
42
|
+
* shortcut cannot silently diverge.
|
|
43
|
+
*/
|
|
44
|
+
export declare function intsvcServiceType(n: EventNode | ExternalNode): string;
|
|
45
|
+
/**
|
|
46
|
+
* The variable a Phase 3d node's own output row writes.
|
|
47
|
+
*
|
|
48
|
+
* Derived from the registry spec's OUTPUT NAME rather than assumed: the event
|
|
49
|
+
* family names its output `result` and the agent/workflow family names it
|
|
50
|
+
* `response`, so hardcoding either one declares a variable the row does not use.
|
|
51
|
+
* That mismatch is invisible to both validators and was caught only by the
|
|
52
|
+
* round-trip gate.
|
|
53
|
+
*/
|
|
54
|
+
export declare function intsvcOutputVar(n: EventNode | ExternalNode): string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* A connector event node — `Intsvc.EventTrigger` or `Intsvc.WaitForEvent`.
|
|
57
|
+
*
|
|
58
|
+
* The extension type comes from the LIBRARY (`runtime.serviceType`), not from the
|
|
59
|
+
* method the author called. That is deliberate: the descriptor is the authority on
|
|
60
|
+
* which placement it describes, so a `trigger` descriptor reached through
|
|
61
|
+
* `.waitForEvent()` is caught here rather than emitting a start-event type onto a
|
|
62
|
+
* receive task — a mismatch both offline validators accept.
|
|
63
|
+
*
|
|
64
|
+
* @param ctx - Serializer context: moddle, library, bindings, binding sink.
|
|
65
|
+
* @param n - The authored node.
|
|
66
|
+
* @returns The moddle element.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createConnectorEvent(ctx: ConnectorCtx, n: EventNode): El;
|
|
69
|
+
/**
|
|
70
|
+
* An external agent / workflow invocation.
|
|
71
|
+
*
|
|
72
|
+
* @param ctx - Serializer context: moddle, library, bindings, binding sink.
|
|
73
|
+
* @param n - The authored node.
|
|
74
|
+
* @returns The moddle element.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createExternalTask(ctx: ConnectorCtx, n: ExternalNode): El;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { createTypedNode, typedNodeOutputVar } from './typed-node.js';
|
|
2
|
+
import { connectorBindingRef } from './connector.js';
|
|
3
|
+
/**
|
|
4
|
+
* Which extension type an external invocation lowers to.
|
|
5
|
+
*
|
|
6
|
+
* Kept as a literal table rather than string-built so a typo is a compile error,
|
|
7
|
+
* and mirrored field-for-field from `CASE_FORMAT_PROFILE` — the Case SDK reaches
|
|
8
|
+
* the same four types, and the two must agree.
|
|
9
|
+
*/
|
|
10
|
+
const EXTERNAL_SERVICE_TYPE = {
|
|
11
|
+
agent: { sync: 'Intsvc.SyncAgentExecution', async: 'Intsvc.AsyncAgentExecution' },
|
|
12
|
+
workflow: { sync: 'Intsvc.SyncWorkflowExecution', async: 'Intsvc.AsyncWorkflowExecution' },
|
|
13
|
+
};
|
|
14
|
+
/** The descriptor `subType` each family requires, per `case/serialize.ts`. */
|
|
15
|
+
const EXTERNAL_SUBTYPE = { agent: 'AgentExecution', workflow: 'ProcessExecution' };
|
|
16
|
+
/** Which extension type a connector-event node lowers to, by placement. */
|
|
17
|
+
const EVENT_SERVICE_TYPE = { trigger: 'Intsvc.EventTrigger', event: 'Intsvc.WaitForEvent' };
|
|
18
|
+
/**
|
|
19
|
+
* The extension type a Phase 3d node lowers to, WITHOUT consulting the library.
|
|
20
|
+
*
|
|
21
|
+
* The emitters below take a connector event's type from the library's
|
|
22
|
+
* `runtime.serviceType`, which is the authority. This is the same answer derived
|
|
23
|
+
* from the node alone, so the serializer can declare the node's output variable
|
|
24
|
+
* before it has resolved anything — and the emitter asserts the two agree, so the
|
|
25
|
+
* shortcut cannot silently diverge.
|
|
26
|
+
*/
|
|
27
|
+
export function intsvcServiceType(n) {
|
|
28
|
+
return n.kind === 'connectorEvent'
|
|
29
|
+
? EVENT_SERVICE_TYPE[n.subscribe]
|
|
30
|
+
: EXTERNAL_SERVICE_TYPE[n.family][n.mode];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The variable a Phase 3d node's own output row writes.
|
|
34
|
+
*
|
|
35
|
+
* Derived from the registry spec's OUTPUT NAME rather than assumed: the event
|
|
36
|
+
* family names its output `result` and the agent/workflow family names it
|
|
37
|
+
* `response`, so hardcoding either one declares a variable the row does not use.
|
|
38
|
+
* That mismatch is invisible to both validators and was caught only by the
|
|
39
|
+
* round-trip gate.
|
|
40
|
+
*/
|
|
41
|
+
export function intsvcOutputVar(n) {
|
|
42
|
+
return typedNodeOutputVar(n.id, intsvcServiceType(n), n.outputVar);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A connector event node — `Intsvc.EventTrigger` or `Intsvc.WaitForEvent`.
|
|
46
|
+
*
|
|
47
|
+
* The extension type comes from the LIBRARY (`runtime.serviceType`), not from the
|
|
48
|
+
* method the author called. That is deliberate: the descriptor is the authority on
|
|
49
|
+
* which placement it describes, so a `trigger` descriptor reached through
|
|
50
|
+
* `.waitForEvent()` is caught here rather than emitting a start-event type onto a
|
|
51
|
+
* receive task — a mismatch both offline validators accept.
|
|
52
|
+
*
|
|
53
|
+
* @param ctx - Serializer context: moddle, library, bindings, binding sink.
|
|
54
|
+
* @param n - The authored node.
|
|
55
|
+
* @returns The moddle element.
|
|
56
|
+
*/
|
|
57
|
+
export function createConnectorEvent(ctx, n) {
|
|
58
|
+
if (!ctx.library)
|
|
59
|
+
throw new Error(`connector event "${n.id}" needs the connector library — pass { library } to serialize ` +
|
|
60
|
+
`or use uip maestro bpmn compile --library.`);
|
|
61
|
+
const rc = ctx.library.resolveEvent(n.subscribe, n.key, n.eventName, n.version);
|
|
62
|
+
const op = (rc.entry.operation ?? {});
|
|
63
|
+
const runtime = rc.entry.runtime ?? {};
|
|
64
|
+
const type = runtime.serviceType;
|
|
65
|
+
if (!type) {
|
|
66
|
+
throw new Error(`connector event ${n.key}/${n.eventName} has no runtime.serviceType in the library; ` +
|
|
67
|
+
`it cannot be authored as a BPMN node.`);
|
|
68
|
+
}
|
|
69
|
+
// The library is the authority, but the serializer has already declared this
|
|
70
|
+
// node's output variable from the placement alone (`intsvcServiceType`). If the
|
|
71
|
+
// two ever disagree, that variable is wrong — so say so rather than emit it.
|
|
72
|
+
const expected = intsvcServiceType(n);
|
|
73
|
+
if (type !== expected) {
|
|
74
|
+
throw new Error(`connector event "${n.id}" was authored as a ${n.subscribe} (${expected}), but the library ` +
|
|
75
|
+
`describes ${n.key}/${n.eventName} as ${type}. Use ` +
|
|
76
|
+
`${type === 'Intsvc.EventTrigger' ? '.eventTrigger()' : '.waitForEvent()'} instead.`);
|
|
77
|
+
}
|
|
78
|
+
const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
|
|
79
|
+
const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey');
|
|
80
|
+
if (!conn || !folder) {
|
|
81
|
+
throw new Error(`connector event "${n.id}" needs both a connection and a folder binding name — ` +
|
|
82
|
+
`the type declares connectionId, folderId, folderPath and folderKey all required.`);
|
|
83
|
+
}
|
|
84
|
+
// The registry marks `operation` and `objectName` required AND hidden, so an
|
|
85
|
+
// author never passes them; they come off the descriptor, the same way `.http()`
|
|
86
|
+
// takes `mode` from a registry default.
|
|
87
|
+
const inputs = {
|
|
88
|
+
connectionId: `=bindings.${conn.id}`,
|
|
89
|
+
connectorKey: rc.connectorKey,
|
|
90
|
+
folderId: `=bindings.${folder.id}`,
|
|
91
|
+
folderPath: n.folderPath ?? '/Shared',
|
|
92
|
+
folderKey: `=bindings.${folder.id}`,
|
|
93
|
+
operation: op.eventOperation ?? '',
|
|
94
|
+
objectName: op.objectName ?? '',
|
|
95
|
+
};
|
|
96
|
+
if (n.filter !== undefined)
|
|
97
|
+
inputs.filter = n.filter;
|
|
98
|
+
if (n.parameters !== undefined)
|
|
99
|
+
inputs.parameters = n.parameters;
|
|
100
|
+
return createTypedNode(ctx.m, n.id, type, {
|
|
101
|
+
name: n.name ?? rc.entry.display?.label ?? n.eventName,
|
|
102
|
+
inputs,
|
|
103
|
+
outputVar: n.outputVar,
|
|
104
|
+
outputs: n.outputs,
|
|
105
|
+
skipCondition: n.skipCondition,
|
|
106
|
+
}, (varId) => ctx.varTypeOf?.(varId));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* An external agent / workflow invocation.
|
|
110
|
+
*
|
|
111
|
+
* @param ctx - Serializer context: moddle, library, bindings, binding sink.
|
|
112
|
+
* @param n - The authored node.
|
|
113
|
+
* @returns The moddle element.
|
|
114
|
+
*/
|
|
115
|
+
export function createExternalTask(ctx, n) {
|
|
116
|
+
if (!ctx.library)
|
|
117
|
+
throw new Error(`external ${n.family} "${n.id}" needs the connector library — pass { library } to serialize ` +
|
|
118
|
+
`or use uip maestro bpmn compile --library.`);
|
|
119
|
+
const rc = ctx.library.resolve(n.key, n.action, n.version);
|
|
120
|
+
const op = (rc.entry.operation ?? {});
|
|
121
|
+
const wanted = EXTERNAL_SUBTYPE[n.family];
|
|
122
|
+
// The library is the authority on what a descriptor IS. Without this check an
|
|
123
|
+
// ordinary connector action authored as an external agent emits a node the
|
|
124
|
+
// validator accepts and the runtime dispatches to the wrong handler family —
|
|
125
|
+
// the Case SDK records the same trap in its Phase 4.4 notes.
|
|
126
|
+
if (op.subType !== wanted) {
|
|
127
|
+
throw new Error(`external ${n.family} "${n.id}" needs ${wanted === 'AgentExecution' ? 'an' : 'a'} ${wanted} descriptor, but ` +
|
|
128
|
+
`${n.key}/${n.action} is "${String(op.subType ?? 'untyped')}". ` +
|
|
129
|
+
`The shipped library has very few of these — ${wanted === 'AgentExecution' ? '2' : '1'} operation(s).`);
|
|
130
|
+
}
|
|
131
|
+
const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
|
|
132
|
+
const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey');
|
|
133
|
+
if (!conn || !folder) {
|
|
134
|
+
throw new Error(`external ${n.family} "${n.id}" needs both a connection and a folder binding name.`);
|
|
135
|
+
}
|
|
136
|
+
return createTypedNode(ctx.m, n.id, EXTERNAL_SERVICE_TYPE[n.family][n.mode], {
|
|
137
|
+
name: n.name ?? rc.entry.display?.label ?? n.action,
|
|
138
|
+
context: {
|
|
139
|
+
// `activity` is the ACTIVITY TYPE ID, not the operation name — the one place
|
|
140
|
+
// this family differs from `.connector()`, and what `case/serialize.ts` uses.
|
|
141
|
+
activity: rc.activityTypeId ?? '',
|
|
142
|
+
connectorKey: rc.connectorKey,
|
|
143
|
+
connection: `=bindings.${conn.id}`,
|
|
144
|
+
folderKey: `=bindings.${folder.id}`,
|
|
145
|
+
method: rc.httpMethod || 'POST',
|
|
146
|
+
path: rc.endpoint || '',
|
|
147
|
+
},
|
|
148
|
+
// One merged `body` row, as the type's own XmlTemplate shows — not the
|
|
149
|
+
// path/query/body split a connector activity uses.
|
|
150
|
+
inputs: { body: n.inputs },
|
|
151
|
+
outputVar: n.outputVar,
|
|
152
|
+
outputs: n.outputs,
|
|
153
|
+
skipCondition: n.skipCondition,
|
|
154
|
+
}, (varId) => ctx.varTypeOf?.(varId));
|
|
155
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Shared: dynamic-import an authored bpmn module and return its BuiltBpmn. */
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
/** Resolve a user argument (a `.ts` path or a BaseName) to a bpmn source file. */
|
|
6
|
+
export function resolveBpmnFile(arg) {
|
|
7
|
+
const candidates = [arg, `${arg}.bpmn.ts`, `${arg}.ts`];
|
|
8
|
+
for (const c of candidates)
|
|
9
|
+
if (existsSync(resolve(c)))
|
|
10
|
+
return resolve(c);
|
|
11
|
+
throw new Error(`bpmn source not found: tried ${candidates.join(', ')}`);
|
|
12
|
+
}
|
|
13
|
+
export async function loadBuiltBpmn(file) {
|
|
14
|
+
const mod = await import(pathToFileURL(file).href);
|
|
15
|
+
const exp = mod.default ?? mod.bpmn;
|
|
16
|
+
if (!exp)
|
|
17
|
+
throw new Error(`${file} has no default export. Add: export default bpmn('id')...build();`);
|
|
18
|
+
const built = typeof exp.build === 'function' ? exp.build() : exp;
|
|
19
|
+
if (!built || !Array.isArray(built.nodes))
|
|
20
|
+
throw new Error(`${file} default export is not a built bpmn. Did you call .build()?`);
|
|
21
|
+
return built;
|
|
22
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* merge-cli — merge an edited process back into the original `.bpmn` (bin `bpmn-merge`)
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* bpmn-merge <original.bpmn> <edited.bpmn> [-o merged.bpmn] [--baseline <baseline.bpmn>]
|
|
7
|
+
*
|
|
8
|
+
* `edited.bpmn` is compile(decompile(original)) after your edits. `--baseline` is
|
|
9
|
+
* compile(decompile(original)) BEFORE any edit — supplying it enables the delta merge, so
|
|
10
|
+
* every element you did not touch is emitted from the original byte for byte. Omit it for
|
|
11
|
+
* a two-way merge (take the edited process, keep the original diagram). See merge.ts.
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { basename } from 'node:path';
|
|
15
|
+
import { mergeBpmn } from './merge.js';
|
|
16
|
+
import { runWhenInvokedDirectly } from '../cli-run.js';
|
|
17
|
+
function opt(argv, name) {
|
|
18
|
+
const i = argv.indexOf(name);
|
|
19
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
20
|
+
}
|
|
21
|
+
export function run(argv) {
|
|
22
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
23
|
+
console.error('usage: bpmn-merge <original.bpmn> <edited.bpmn> [-o merged.bpmn] [--baseline <baseline.bpmn>]');
|
|
24
|
+
return 2;
|
|
25
|
+
}
|
|
26
|
+
const flags = new Set(['-o', '--baseline']);
|
|
27
|
+
const positionals = argv.filter((a, i) => !flags.has(a) && !flags.has(argv[i - 1]) && !a.startsWith('-'));
|
|
28
|
+
const [originalPath, editedPath] = positionals;
|
|
29
|
+
if (!originalPath || !editedPath) {
|
|
30
|
+
console.error('bpmn-merge: need <original.bpmn> and <edited.bpmn>');
|
|
31
|
+
return 2;
|
|
32
|
+
}
|
|
33
|
+
const baselinePath = opt(argv, '--baseline');
|
|
34
|
+
const result = mergeBpmn(readFileSync(originalPath, 'utf8'), readFileSync(editedPath, 'utf8'), {
|
|
35
|
+
...(baselinePath ? { baseline: readFileSync(baselinePath, 'utf8') } : {}),
|
|
36
|
+
});
|
|
37
|
+
const out = opt(argv, '-o') ?? 'merged.bpmn';
|
|
38
|
+
writeFileSync(out, result.xml);
|
|
39
|
+
// Say what was kept and what was rewritten: on a surgical edit the preserved count
|
|
40
|
+
// should dwarf the authored one, and a reader who sees otherwise has learned something.
|
|
41
|
+
console.log(`bpmn-merge: wrote ${basename(out)} (${result.preserved.length} preserved, ` +
|
|
42
|
+
`${result.authored.length} authored, ${result.removed.length} removed)`);
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
runWhenInvokedDirectly(import.meta.url, 'bpmn-merge', run);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface BpmnMergeOptions {
|
|
2
|
+
/**
|
|
3
|
+
* `compile(decompile(original))` captured BEFORE the author's edits — what a faithful
|
|
4
|
+
* round trip of the original produces. Enables the delta merge: an element identical
|
|
5
|
+
* to its baseline counterpart is treated as untouched and emitted from the original.
|
|
6
|
+
* Omit for a two-way merge (take `edited`, re-attach the original diagram).
|
|
7
|
+
*/
|
|
8
|
+
baseline?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface BpmnMergeResult {
|
|
11
|
+
xml: string;
|
|
12
|
+
/** Element ids emitted from the original verbatim (payload preserved). */
|
|
13
|
+
preserved: string[];
|
|
14
|
+
/** Element ids taken from the edited document (authored, or newly added). */
|
|
15
|
+
authored: string[];
|
|
16
|
+
/** Element ids present in the original but gone from the edited process. */
|
|
17
|
+
removed: string[];
|
|
18
|
+
}
|
|
19
|
+
export declare function mergeBpmn(originalXml: string, editedXml: string, options?: BpmnMergeOptions): BpmnMergeResult;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* merge — write a decompiled-then-edited process back into the ORIGINAL `.bpmn`,
|
|
3
|
+
* splicing in only what changed and leaving everything else byte for byte alone.
|
|
4
|
+
*
|
|
5
|
+
* The brownfield pipeline, the BPMN counterpart of `src/merge.ts`:
|
|
6
|
+
*
|
|
7
|
+
* original.bpmn --decompile--> X.bpmn.ts --(edit)--> --compile--> edited.bpmn
|
|
8
|
+
* baseline.bpmn = compile(decompile(original)) # BEFORE any edit
|
|
9
|
+
* mergeBpmn(original, edited, { baseline }) --> merged.bpmn
|
|
10
|
+
*
|
|
11
|
+
* Why not just keep `edited`? Because decompile is **semantically** lossless, not
|
|
12
|
+
* attribute-for-attribute: it discards the diagram, and it normalises payload detail the
|
|
13
|
+
* builder models by intent rather than by wire text (an output row keyed by a variable
|
|
14
|
+
* comes back named after that variable, losing a distinct row `name`). Taking `edited`
|
|
15
|
+
* wholesale rewrites every element in the file, including the ones the author never
|
|
16
|
+
* looked at — which is exactly what a brownfield edit must not do.
|
|
17
|
+
*
|
|
18
|
+
* The merge is therefore a **delta**, and it turns on one observation: those
|
|
19
|
+
* normalisations are UNIFORM, so they land on `baseline` and on `edited` identically. An
|
|
20
|
+
* element the author did not touch is byte-identical between the two — and when it is,
|
|
21
|
+
* the merge emits the element from the ORIGINAL. The original XML is the carrier for the
|
|
22
|
+
* wire detail the builder does not model, so the builder never has to model it.
|
|
23
|
+
*
|
|
24
|
+
* Without a `baseline` the merge is two-way: take the edited process, re-attach the
|
|
25
|
+
* original diagram by element id. Correct whenever decompile was faithful for every
|
|
26
|
+
* element, and still much better than a bare recompile, which drops layout entirely.
|
|
27
|
+
*
|
|
28
|
+
* ## Two things that differ from the flow merge
|
|
29
|
+
*
|
|
30
|
+
* 1. **Wiring lives inside the element.** A flow node's edges are separate objects, so
|
|
31
|
+
* `mergeFlow` can swap a whole node. A BPMN element carries `bpmn:incoming` /
|
|
32
|
+
* `bpmn:outgoing` as children, so a reorder changes an element whose payload is
|
|
33
|
+
* untouched. The comparison here therefore ignores wiring, and a preserved element
|
|
34
|
+
* adopts the EDITED wiring while keeping its ORIGINAL payload. Without that,
|
|
35
|
+
* reordering two tasks either loses their payloads or restores the old order.
|
|
36
|
+
*
|
|
37
|
+
* 2. **`bpmn:extensionElements` has no id**, so the process-level block is merged by
|
|
38
|
+
* role rather than by key: the author's `uipath:variables` wins (they may have
|
|
39
|
+
* declared one), and every other child — `migrationVersion`, `caseManagement`, and
|
|
40
|
+
* anything else a future platform version puts there — comes from the original
|
|
41
|
+
* untouched. Nothing in this file needs to know what those payloads mean.
|
|
42
|
+
*
|
|
43
|
+
* ## What it deliberately does NOT preserve
|
|
44
|
+
*
|
|
45
|
+
* A bare `<uipath:input name="args">`. `uip maestro bpmn validate` rejects it — the row
|
|
46
|
+
* must declare `type="json"` and target `"bodyField"` — so an original missing them is
|
|
47
|
+
* invalid, and the recompiled form is the correct one. Preserving that faithfully would
|
|
48
|
+
* mean preserving invalid markup. It is not a special case in the code: such a row only
|
|
49
|
+
* ever appears inside an element the author edited, which is taken from `edited` anyway.
|
|
50
|
+
* Recorded here because it is the one difference a reader will expect to see handled.
|
|
51
|
+
*/
|
|
52
|
+
import { findByLocal, parseXmlRanges } from './xml-ranges.js';
|
|
53
|
+
const WIRING = new Set(['incoming', 'outgoing']);
|
|
54
|
+
function processOf(xml, label) {
|
|
55
|
+
const process = findByLocal(parseXmlRanges(xml), 'process');
|
|
56
|
+
if (!process)
|
|
57
|
+
throw new Error(`merge: ${label} has no bpmn:process`);
|
|
58
|
+
return process;
|
|
59
|
+
}
|
|
60
|
+
/** The element's own source text. */
|
|
61
|
+
function textOf(xml, range) {
|
|
62
|
+
return xml.slice(range.start, range.end);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The element's text with its wiring children removed and whitespace flattened — the
|
|
66
|
+
* form two documents are compared in to answer "did the author touch this element?".
|
|
67
|
+
*
|
|
68
|
+
* A textual comparison is exact here, not an approximation: `baseline` and `edited` are
|
|
69
|
+
* both output of the same serializer from the same source shape, so identical intent
|
|
70
|
+
* produces identical bytes. Structural canonicalisation would be strictly more code for
|
|
71
|
+
* the same answer.
|
|
72
|
+
*/
|
|
73
|
+
function withoutWiring(xml, range) {
|
|
74
|
+
const cuts = range.children.filter((c) => WIRING.has(c.local));
|
|
75
|
+
let text = '';
|
|
76
|
+
let at = range.start;
|
|
77
|
+
for (const cut of cuts) {
|
|
78
|
+
text += xml.slice(at, cut.start);
|
|
79
|
+
at = cut.end;
|
|
80
|
+
}
|
|
81
|
+
text += xml.slice(at, range.end);
|
|
82
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Graft the edited element's wiring onto the original element's text.
|
|
86
|
+
*
|
|
87
|
+
* The edited wiring is written where the original's first wiring child sat, so the
|
|
88
|
+
* children stay in schema order (`extensionElements` before `incoming`/`outgoing`). An
|
|
89
|
+
* element with no wiring in either document is returned untouched.
|
|
90
|
+
*/
|
|
91
|
+
function withWiringFrom(originalXml, original, editedXml, edited) {
|
|
92
|
+
const originalWiring = original.children.filter((c) => WIRING.has(c.local));
|
|
93
|
+
const editedWiring = edited.children.filter((c) => WIRING.has(c.local));
|
|
94
|
+
// When the wiring is the same, the element is returned untouched — no reflowing. This
|
|
95
|
+
// is not an optimisation: splicing identical content back in still rewrites the
|
|
96
|
+
// element's whitespace, and an original that put `<bpmn:incoming/>` and
|
|
97
|
+
// `<bpmn:outgoing/>` on one line would come back on two. That is a diff on an element
|
|
98
|
+
// nobody edited, which is the one thing this function exists to avoid.
|
|
99
|
+
const wiringOf = (xml, w) => w.map((c) => `${c.local}=${xml.slice(c.innerStart, c.innerEnd).trim()}`).join(',');
|
|
100
|
+
if (wiringOf(originalXml, originalWiring) === wiringOf(editedXml, editedWiring)) {
|
|
101
|
+
return textOf(originalXml, original);
|
|
102
|
+
}
|
|
103
|
+
const replacement = editedWiring.map((w) => editedXml.slice(w.start, w.end));
|
|
104
|
+
// Indentation of the first wiring child, so the grafted lines sit where the old ones did.
|
|
105
|
+
const anchor = originalWiring[0];
|
|
106
|
+
const indent = anchor
|
|
107
|
+
? (/([ \t]*)$/.exec(originalXml.slice(original.innerStart, anchor.start))?.[1] ?? '')
|
|
108
|
+
: '';
|
|
109
|
+
if (originalWiring.length === 0) {
|
|
110
|
+
// Nothing to replace: insert before the close tag.
|
|
111
|
+
const head = originalXml.slice(original.start, original.innerEnd);
|
|
112
|
+
const tail = originalXml.slice(original.innerEnd, original.end);
|
|
113
|
+
const block = replacement.map((w) => `${indent} ${w}\n`).join('');
|
|
114
|
+
return `${head}${block}${indent}${tail}`;
|
|
115
|
+
}
|
|
116
|
+
let text = '';
|
|
117
|
+
let at = original.start;
|
|
118
|
+
let first = true;
|
|
119
|
+
for (const w of originalWiring) {
|
|
120
|
+
text += originalXml.slice(at, w.start);
|
|
121
|
+
if (first) {
|
|
122
|
+
text += replacement.join(`\n${indent}`);
|
|
123
|
+
first = false;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// A second or later wiring child: its own text is dropped, and so is the
|
|
127
|
+
// separator that preceded it, which the slice above already added.
|
|
128
|
+
text = text.replace(/\s+$/, '');
|
|
129
|
+
}
|
|
130
|
+
at = w.end;
|
|
131
|
+
}
|
|
132
|
+
text += originalXml.slice(at, original.end);
|
|
133
|
+
return text;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Children of the process-level `bpmn:extensionElements` that the BUILDER can author, and
|
|
137
|
+
* which therefore come from the EDITED document.
|
|
138
|
+
*
|
|
139
|
+
* This is the whole rule for that block, which has no id and so cannot be merged by key:
|
|
140
|
+
* anything the builder models is the author's to change, and anything it does not model is
|
|
141
|
+
* preserve-only and comes from the original untouched. Nothing here needs to know what
|
|
142
|
+
* these payloads mean — only who owns them.
|
|
143
|
+
*
|
|
144
|
+
* It started as `variables` alone, which was too narrow and wrong in a way worth
|
|
145
|
+
* recording: a task that sanitized a `uipath:binding` default — replacing a real
|
|
146
|
+
* connection id with a placeholder — produced a merged file with the private value still
|
|
147
|
+
* in it, because bindings were treated as preserve-only. The author had changed them and
|
|
148
|
+
* the merge quietly put them back. Metadata is on the list for the same reason: `.metadata()`
|
|
149
|
+
* has been authorable since #474, so a changed `migrationVersion` must land too.
|
|
150
|
+
*/
|
|
151
|
+
const AUTHORED_EXTENSIONS = new Set([
|
|
152
|
+
'variables',
|
|
153
|
+
'bindings',
|
|
154
|
+
'migrationVersion',
|
|
155
|
+
'entryPointId',
|
|
156
|
+
'tags',
|
|
157
|
+
'caseManagement',
|
|
158
|
+
]);
|
|
159
|
+
/**
|
|
160
|
+
* Merge the process-level `bpmn:extensionElements`.
|
|
161
|
+
*
|
|
162
|
+
* Same delta logic as the elements: an authorable child is taken from the edited document
|
|
163
|
+
* only when the author actually CHANGED it, judged against the baseline. Ownership alone
|
|
164
|
+
* is not enough — taking every authorable child from the edited document rewrote ones
|
|
165
|
+
* nobody touched, and `caseManagement`'s `<![CDATA[…]]>` came back as plain text. Same
|
|
166
|
+
* value, but a diff on a block the author never opened, which is the thing this file
|
|
167
|
+
* exists to avoid.
|
|
168
|
+
*
|
|
169
|
+
* With no baseline there is nothing to compare against, so an authorable child comes from
|
|
170
|
+
* the edited document — the same fallback the two-way element merge makes.
|
|
171
|
+
*/
|
|
172
|
+
function mergeExtensionElements(originalXml, original, editedXml, edited, baselineXml, baseline) {
|
|
173
|
+
if (!original)
|
|
174
|
+
return edited ? textOf(editedXml, edited) : undefined;
|
|
175
|
+
if (!edited)
|
|
176
|
+
return textOf(originalXml, original);
|
|
177
|
+
const baselineByLocal = new Map((baseline?.children ?? []).map((c) => [c.local, c]));
|
|
178
|
+
const flat = (xml, r) => xml.slice(r.start, r.end).replace(/\s+/g, ' ').trim();
|
|
179
|
+
/** Did the author change this child, relative to a faithful round trip of the original? */
|
|
180
|
+
const changed = (child) => {
|
|
181
|
+
if (!baselineXml)
|
|
182
|
+
return true;
|
|
183
|
+
const base = baselineByLocal.get(child.local);
|
|
184
|
+
if (!base)
|
|
185
|
+
return true;
|
|
186
|
+
return flat(editedXml, child) !== flat(baselineXml, base);
|
|
187
|
+
};
|
|
188
|
+
const editedByLocal = new Map(edited.children.filter((c) => changed(c)).map((c) => [c.local, c]));
|
|
189
|
+
const seen = new Set();
|
|
190
|
+
// Walk the ORIGINAL's children so its order and formatting lead, substituting each
|
|
191
|
+
// authored child with the edited document's version of it.
|
|
192
|
+
let body = '';
|
|
193
|
+
let at = original.innerStart;
|
|
194
|
+
for (const child of original.children) {
|
|
195
|
+
body += originalXml.slice(at, child.start);
|
|
196
|
+
const replacement = AUTHORED_EXTENSIONS.has(child.local)
|
|
197
|
+
? editedByLocal.get(child.local)
|
|
198
|
+
: undefined;
|
|
199
|
+
if (replacement) {
|
|
200
|
+
body += editedXml.slice(replacement.start, replacement.end);
|
|
201
|
+
seen.add(child.local);
|
|
202
|
+
}
|
|
203
|
+
else if (AUTHORED_EXTENSIONS.has(child.local) &&
|
|
204
|
+
baselineByLocal.has(child.local) &&
|
|
205
|
+
!edited.children.some((c) => c.local === child.local)) {
|
|
206
|
+
// The author DELETED it (e.g. dropped every binding): the baseline had it and the
|
|
207
|
+
// edit does not. Skip it, and the whitespace that preceded it, so no blank line is
|
|
208
|
+
// left behind. Requires a baseline — without one, absence is indistinguishable from
|
|
209
|
+
// a child the round trip never carried.
|
|
210
|
+
body = body.replace(/\s+$/, '');
|
|
211
|
+
seen.add(child.local);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
body += originalXml.slice(child.start, child.end);
|
|
215
|
+
}
|
|
216
|
+
at = child.end;
|
|
217
|
+
}
|
|
218
|
+
body += originalXml.slice(at, original.innerEnd);
|
|
219
|
+
// Authored children the original did not have at all — the author ADDED a block.
|
|
220
|
+
const indent = /([ \t]*)$/.exec(originalXml.slice(original.innerStart, original.children[0]?.start ?? original.innerEnd))?.[1] ?? '';
|
|
221
|
+
for (const child of edited.children) {
|
|
222
|
+
if (AUTHORED_EXTENSIONS.has(child.local) && !seen.has(child.local) && !original.children.some((c) => c.local === child.local)) {
|
|
223
|
+
body += `${indent}${editedXml.slice(child.start, child.end)}\n`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return (originalXml.slice(original.start, original.innerStart) +
|
|
227
|
+
body +
|
|
228
|
+
originalXml.slice(original.innerEnd, original.end));
|
|
229
|
+
}
|
|
230
|
+
export function mergeBpmn(originalXml, editedXml, options = {}) {
|
|
231
|
+
const originalProcess = processOf(originalXml, 'original');
|
|
232
|
+
const editedProcess = processOf(editedXml, 'edited');
|
|
233
|
+
const baselineProcess = options.baseline ? processOf(options.baseline, 'baseline') : undefined;
|
|
234
|
+
const byId = (p) => new Map(p.children.filter((c) => c.id !== undefined).map((c) => [c.id, c]));
|
|
235
|
+
const originalById = byId(originalProcess);
|
|
236
|
+
const baselineById = baselineProcess ? byId(baselineProcess) : new Map();
|
|
237
|
+
const preserved = [];
|
|
238
|
+
const authored = [];
|
|
239
|
+
const kept = new Set();
|
|
240
|
+
const pieces = [];
|
|
241
|
+
const extension = mergeExtensionElements(originalXml, originalProcess.children.find((c) => c.local === 'extensionElements'), editedXml, editedProcess.children.find((c) => c.local === 'extensionElements'), options.baseline, baselineProcess?.children.find((c) => c.local === 'extensionElements'));
|
|
242
|
+
if (extension)
|
|
243
|
+
pieces.push(extension);
|
|
244
|
+
// The EDITED process decides which elements exist and in what order; the ORIGINAL
|
|
245
|
+
// decides the content of every element the author did not touch.
|
|
246
|
+
for (const element of editedProcess.children) {
|
|
247
|
+
if (element.local === 'extensionElements')
|
|
248
|
+
continue;
|
|
249
|
+
const id = element.id;
|
|
250
|
+
const original = id !== undefined ? originalById.get(id) : undefined;
|
|
251
|
+
const baseline = id !== undefined ? baselineById.get(id) : undefined;
|
|
252
|
+
const untouched = original !== undefined &&
|
|
253
|
+
baseline !== undefined &&
|
|
254
|
+
withoutWiring(editedXml, element) === withoutWiring(options.baseline, baseline);
|
|
255
|
+
if (untouched) {
|
|
256
|
+
pieces.push(withWiringFrom(originalXml, original, editedXml, element));
|
|
257
|
+
preserved.push(id);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
pieces.push(textOf(editedXml, element));
|
|
261
|
+
if (id !== undefined)
|
|
262
|
+
authored.push(id);
|
|
263
|
+
}
|
|
264
|
+
if (id !== undefined)
|
|
265
|
+
kept.add(id);
|
|
266
|
+
}
|
|
267
|
+
const removed = [...originalById.keys()].filter((id) => !kept.has(id));
|
|
268
|
+
// Splice the rebuilt process body into the ORIGINAL document, so the prolog,
|
|
269
|
+
// `bpmn:definitions` attributes and namespace declarations are the original's.
|
|
270
|
+
const indent = /([ \t]*)$/.exec(originalXml.slice(originalProcess.innerStart, originalProcess.children[0]?.start ?? originalProcess.innerEnd))?.[1] ?? ' ';
|
|
271
|
+
const body = `\n${indent}${pieces.join(`\n${indent}`)}\n${indent.slice(0, -2)}`;
|
|
272
|
+
let merged = originalXml.slice(0, originalProcess.innerStart) +
|
|
273
|
+
body +
|
|
274
|
+
originalXml.slice(originalProcess.innerEnd);
|
|
275
|
+
// The diagram: the original's layout survives, which is strictly better than the
|
|
276
|
+
// re-layout a bare recompile forces. Shapes and edges for deleted elements go with
|
|
277
|
+
// them; anything newly added has no DI yet, which is what `format`/`tidy` is for.
|
|
278
|
+
if (removed.length > 0) {
|
|
279
|
+
const plane = findByLocal(parseXmlRanges(merged), 'BPMNPlane');
|
|
280
|
+
if (plane) {
|
|
281
|
+
const gone = plane.children.filter((di) => {
|
|
282
|
+
const ref = di.attributes.bpmnElement;
|
|
283
|
+
return ref !== undefined && !kept.has(ref);
|
|
284
|
+
});
|
|
285
|
+
for (const di of [...gone].reverse()) {
|
|
286
|
+
// Take the whitespace before it too, so no blank line is left behind.
|
|
287
|
+
const from = /\s*$/.exec(merged.slice(0, di.start))?.index ?? di.start;
|
|
288
|
+
merged = merged.slice(0, from) + merged.slice(di.end);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { xml: merged, preserved, authored, removed };
|
|
293
|
+
}
|