@uipath/maestro-builder-sdk 6.11.5 → 6.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-index.md +83 -56
- package/dist/api-members.md +362 -261
- package/dist/bpmn/bpmn-expr-check.js +8 -1
- package/dist/bpmn/bpmn-sdk.d.ts +1026 -27
- package/dist/bpmn/bpmn-sdk.js +1328 -83
- package/dist/bpmn/check-cli.js +19 -4
- package/dist/bpmn/check.d.ts +1 -1
- package/dist/bpmn/check.js +529 -13
- package/dist/bpmn/connector.d.ts +2 -2
- package/dist/bpmn/connector.js +74 -37
- package/dist/bpmn/data-service.d.ts +124 -0
- package/dist/bpmn/data-service.js +98 -0
- package/dist/bpmn/decompile-cli.js +70 -8
- package/dist/bpmn/decompile.d.ts +71 -0
- package/dist/bpmn/decompile.js +845 -31
- package/dist/bpmn/graph.d.ts +69 -0
- package/dist/bpmn/graph.js +98 -0
- package/dist/bpmn/intsvc.js +2 -2
- package/dist/bpmn/known-methods.d.ts +15 -0
- package/dist/bpmn/known-methods.js +271 -0
- package/dist/bpmn/lift-verify.d.ts +40 -0
- package/dist/bpmn/lift-verify.js +133 -0
- package/dist/bpmn/serialize.d.ts +65 -1
- package/dist/bpmn/serialize.js +169 -22
- package/dist/bpmn/typed-node.d.ts +37 -6
- package/dist/bpmn/typed-node.js +81 -19
- package/dist/check-cli.js +2 -1
- package/dist/check.d.ts +13 -0
- package/dist/check.js +11 -0
- package/dist/cli/commands/bpmn.js +7 -1
- package/dist/cli-library.d.ts +7 -0
- package/dist/cli-library.js +19 -0
- package/dist/compile-cli.js +8 -5
- package/dist/core/byoa.d.ts +69 -0
- package/dist/core/byoa.js +98 -0
- package/dist/core/library.d.ts +23 -0
- package/dist/core/library.js +47 -1
- package/dist/core/lookup-store.d.ts +12 -0
- package/dist/core/lookup-store.js +25 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -0
- package/dist/registry/generators/descriptors.d.ts +1 -1
- package/dist/registry/generators/descriptors.js +20 -2
- package/dist/registry/generators/markdown/page.js +3 -1
- package/dist/registry/generators/markdown/sections/event-facts.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/event-facts.js +45 -0
- package/dist/registry/library-build/events.d.ts +32 -3
- package/dist/registry/library-build/events.js +48 -19
- package/dist/registry/prepare/byoa-ledger.d.ts +12 -0
- package/dist/registry/prepare/byoa-ledger.js +26 -0
- package/dist/registry/prepare/event.d.ts +5 -1
- package/dist/registry/prepare/event.js +22 -8
- package/dist/registry/prepare/messages.d.ts +2 -0
- package/dist/registry/prepare/messages.js +4 -0
- package/dist/serialize.js +24 -1
- package/package.json +2 -2
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bpmn/graph — the resolved wiring of a built process, printed for review.
|
|
3
|
+
*
|
|
4
|
+
* `bpmn check --graph` exists because two things now write flows the author never
|
|
5
|
+
* typed: the nesting constructs (an arm's chain, a handler's path, a synthesized
|
|
6
|
+
* join) and `flowMode('sequence')` (consecutive siblings). Both are deterministic,
|
|
7
|
+
* but a reviewer reading the source cannot see the edge that inserting a line
|
|
8
|
+
* added or moved. This prints every element in placement order with the flows
|
|
9
|
+
* leaving it, an implied edge marked `~>` and an explicit one `->`, so the diff of
|
|
10
|
+
* the printed graph is the diff of what the builder actually inferred.
|
|
11
|
+
*
|
|
12
|
+
* Read-only over the built IR; nothing here changes what `serialize` emits.
|
|
13
|
+
*/
|
|
14
|
+
import { type BuiltBpmn } from './bpmn-sdk.js';
|
|
15
|
+
/** One element of the printed graph, with the flows leaving it. */
|
|
16
|
+
export interface GraphNode {
|
|
17
|
+
/** The element's id. */
|
|
18
|
+
id: string;
|
|
19
|
+
/** The builder's node kind, e.g. `scriptTask` or `exclusiveGateway`. */
|
|
20
|
+
kind: string;
|
|
21
|
+
/** The element's display name, when it has one. */
|
|
22
|
+
name?: string;
|
|
23
|
+
/** Flows leaving this element, in flow order. */
|
|
24
|
+
outgoing: GraphEdge[];
|
|
25
|
+
/** How many flows arrive. */
|
|
26
|
+
incoming: number;
|
|
27
|
+
/** Set when the element is an event sub-process, a boundary event, or a start: nothing is expected to lead into it. */
|
|
28
|
+
entersOnItsOwn?: boolean;
|
|
29
|
+
/** A nested scope's own graph. */
|
|
30
|
+
children?: GraphScope;
|
|
31
|
+
}
|
|
32
|
+
/** One flow leaving an element, as the graph prints it. */
|
|
33
|
+
export interface GraphEdge {
|
|
34
|
+
/** The element the flow arrives at. */
|
|
35
|
+
to: string;
|
|
36
|
+
/** The flow's id. */
|
|
37
|
+
flow: string;
|
|
38
|
+
/** Whether the builder wrote the flow by inference rather than from an explicit `.sequenceFlow()`. */
|
|
39
|
+
implied: boolean;
|
|
40
|
+
/** The flow's condition expression, when it has one. */
|
|
41
|
+
condition?: string;
|
|
42
|
+
/** Whether this flow is its gateway's default. */
|
|
43
|
+
default?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** A scope's graph: the process root or one sub-process body. */
|
|
46
|
+
export interface GraphScope {
|
|
47
|
+
/** The scope, e.g. `process "orders"` or `subProcess "batch"`. */
|
|
48
|
+
scope: string;
|
|
49
|
+
/** The scope's elements, in placement order. */
|
|
50
|
+
nodes: GraphNode[];
|
|
51
|
+
/** Ids of elements nothing leads into, that are not entry points of their own. */
|
|
52
|
+
unreachable: string[];
|
|
53
|
+
/** Ids of elements nothing leaves, that are not end events. */
|
|
54
|
+
dangling: string[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The wiring of a built process as data — what `--json` carries.
|
|
58
|
+
*
|
|
59
|
+
* @param built - The process, as `.build()` returned it.
|
|
60
|
+
* @returns The root scope's graph, sub-processes nested inside their element.
|
|
61
|
+
*/
|
|
62
|
+
export declare function graphModel(built: BuiltBpmn): GraphScope;
|
|
63
|
+
/**
|
|
64
|
+
* The wiring of a built process as text, one element per line, indented by scope.
|
|
65
|
+
*
|
|
66
|
+
* @param built - The process, as `.build()` returned it.
|
|
67
|
+
* @returns The printed graph, ending in a newline.
|
|
68
|
+
*/
|
|
69
|
+
export declare function renderGraph(built: BuiltBpmn): string;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bpmn/graph — the resolved wiring of a built process, printed for review.
|
|
3
|
+
*
|
|
4
|
+
* `bpmn check --graph` exists because two things now write flows the author never
|
|
5
|
+
* typed: the nesting constructs (an arm's chain, a handler's path, a synthesized
|
|
6
|
+
* join) and `flowMode('sequence')` (consecutive siblings). Both are deterministic,
|
|
7
|
+
* but a reviewer reading the source cannot see the edge that inserting a line
|
|
8
|
+
* added or moved. This prints every element in placement order with the flows
|
|
9
|
+
* leaving it, an implied edge marked `~>` and an explicit one `->`, so the diff of
|
|
10
|
+
* the printed graph is the diff of what the builder actually inferred.
|
|
11
|
+
*
|
|
12
|
+
* Read-only over the built IR; nothing here changes what `serialize` emits.
|
|
13
|
+
*/
|
|
14
|
+
import { isStartLike } from './bpmn-sdk.js';
|
|
15
|
+
const GATEWAYS = new Set(['exclusiveGateway', 'parallelGateway', 'inclusiveGateway', 'eventBasedGateway']);
|
|
16
|
+
function entersOnItsOwn(n) {
|
|
17
|
+
return isStartLike(n) || n.kind === 'boundaryEvent' || (n.kind === 'subProcess' && n.triggeredByEvent === true);
|
|
18
|
+
}
|
|
19
|
+
function scopeGraph(scope, nodes, flows) {
|
|
20
|
+
const defaults = new Map();
|
|
21
|
+
for (const n of nodes)
|
|
22
|
+
if ((n.kind === 'exclusiveGateway' || n.kind === 'inclusiveGateway') && n.default)
|
|
23
|
+
defaults.set(n.default, n.id);
|
|
24
|
+
const incoming = new Map();
|
|
25
|
+
for (const f of flows)
|
|
26
|
+
incoming.set(f.target, (incoming.get(f.target) ?? 0) + 1);
|
|
27
|
+
const out = [];
|
|
28
|
+
const unreachable = [];
|
|
29
|
+
const dangling = [];
|
|
30
|
+
for (const n of nodes) {
|
|
31
|
+
const outgoing = flows
|
|
32
|
+
.filter((f) => f.source === n.id)
|
|
33
|
+
.map((f) => ({
|
|
34
|
+
to: f.target,
|
|
35
|
+
flow: f.id,
|
|
36
|
+
implied: f.implied === true,
|
|
37
|
+
...(f.condition !== undefined ? { condition: f.condition } : {}),
|
|
38
|
+
...(defaults.get(f.id) === n.id ? { default: true } : {}),
|
|
39
|
+
}));
|
|
40
|
+
const ins = incoming.get(n.id) ?? 0;
|
|
41
|
+
const own = entersOnItsOwn(n);
|
|
42
|
+
if (!own && ins === 0)
|
|
43
|
+
unreachable.push(n.id);
|
|
44
|
+
if (n.kind !== 'endEvent' && !(n.kind === 'subProcess' && n.triggeredByEvent) && outgoing.length === 0)
|
|
45
|
+
dangling.push(n.id);
|
|
46
|
+
out.push({
|
|
47
|
+
id: n.id,
|
|
48
|
+
kind: n.kind,
|
|
49
|
+
...(n.name !== undefined ? { name: n.name } : {}),
|
|
50
|
+
outgoing,
|
|
51
|
+
incoming: ins,
|
|
52
|
+
...(own ? { entersOnItsOwn: true } : {}),
|
|
53
|
+
...(n.kind === 'subProcess' ? { children: scopeGraph(`subProcess "${n.id}"`, n.nodes, n.flows) } : {}),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { scope, nodes: out, unreachable, dangling };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The wiring of a built process as data — what `--json` carries.
|
|
60
|
+
*
|
|
61
|
+
* @param built - The process, as `.build()` returned it.
|
|
62
|
+
* @returns The root scope's graph, sub-processes nested inside their element.
|
|
63
|
+
*/
|
|
64
|
+
export function graphModel(built) {
|
|
65
|
+
return scopeGraph(`process "${built.id}"`, built.nodes, built.flows);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The wiring of a built process as text, one element per line, indented by scope.
|
|
69
|
+
*
|
|
70
|
+
* @param built - The process, as `.build()` returned it.
|
|
71
|
+
* @returns The printed graph, ending in a newline.
|
|
72
|
+
*/
|
|
73
|
+
export function renderGraph(built) {
|
|
74
|
+
const lines = ['# -> explicit .sequenceFlow() ~> implied by nesting or flowMode(\'sequence\')'];
|
|
75
|
+
const walk = (g, depth) => {
|
|
76
|
+
const pad = ' '.repeat(depth);
|
|
77
|
+
lines.push(`${pad}${g.scope}`);
|
|
78
|
+
for (const n of g.nodes) {
|
|
79
|
+
const label = `${pad} ${n.kind.padEnd(22)} ${n.id}${n.name !== undefined ? ` "${n.name}"` : ''}`;
|
|
80
|
+
const notes = [];
|
|
81
|
+
if (g.unreachable.includes(n.id))
|
|
82
|
+
notes.push('NO INCOMING');
|
|
83
|
+
if (g.dangling.includes(n.id))
|
|
84
|
+
notes.push('NO OUTGOING');
|
|
85
|
+
if (n.incoming > 1 && !GATEWAYS.has(n.kind))
|
|
86
|
+
notes.push(`${n.incoming} incoming`);
|
|
87
|
+
lines.push(notes.length ? `${label} [${notes.join(', ')}]` : label);
|
|
88
|
+
for (const o of n.outgoing) {
|
|
89
|
+
const tags = [o.default ? 'default' : '', o.condition !== undefined ? `when ${o.condition}` : ''].filter(Boolean);
|
|
90
|
+
lines.push(`${pad} ${o.implied ? '~>' : '->'} ${o.to} (${o.flow})${tags.length ? ` ${tags.join(', ')}` : ''}`);
|
|
91
|
+
}
|
|
92
|
+
if (n.children)
|
|
93
|
+
walk(n.children, depth + 2);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
walk(graphModel(built), 0);
|
|
97
|
+
return lines.join('\n') + '\n';
|
|
98
|
+
}
|
package/dist/bpmn/intsvc.js
CHANGED
|
@@ -76,7 +76,7 @@ export function createConnectorEvent(ctx, n) {
|
|
|
76
76
|
`${type === 'Intsvc.EventTrigger' ? '.eventTrigger()' : '.waitForEvent()'} instead.`);
|
|
77
77
|
}
|
|
78
78
|
const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
|
|
79
|
-
const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey');
|
|
79
|
+
const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey', conn?.resourceKey);
|
|
80
80
|
if (!conn || !folder) {
|
|
81
81
|
throw new Error(`connector event "${n.id}" needs both a connection and a folder binding name — ` +
|
|
82
82
|
`the type declares connectionId, folderId, folderPath and folderKey all required.`);
|
|
@@ -129,7 +129,7 @@ export function createExternalTask(ctx, n) {
|
|
|
129
129
|
`The shipped library has very few of these — ${wanted === 'AgentExecution' ? '2' : '1'} operation(s).`);
|
|
130
130
|
}
|
|
131
131
|
const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
|
|
132
|
-
const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey');
|
|
132
|
+
const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey', conn?.resourceKey);
|
|
133
133
|
if (!conn || !folder) {
|
|
134
134
|
throw new Error(`external ${n.family} "${n.id}" needs both a connection and a folder binding name.`);
|
|
135
135
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Method names the platform expects to be CALLED, not merely named.
|
|
3
|
+
*
|
|
4
|
+
* Generated — do not edit by hand. Run `node scripts/gen-bpmn-known-methods.mjs`
|
|
5
|
+
* to refresh from the installed `@uipath/maestro-sdk`.
|
|
6
|
+
*
|
|
7
|
+
* The canvas validator's `METHOD_MISSING_PARENTHESES` rule reads this list: an
|
|
8
|
+
* expression containing `.<name>` not followed by `(` is an ERROR when `<name>` is
|
|
9
|
+
* one of these. It is the platform's own vocabulary of .NET/Jint methods reachable
|
|
10
|
+
* from an expression, copied verbatim so the SDK can report the same thing a rung
|
|
11
|
+
* earlier. `tests/bpmn-check-platform-rules.test.ts` fails if the copy drifts.
|
|
12
|
+
*/
|
|
13
|
+
export declare const KNOWN_METHODS: readonly string[];
|
|
14
|
+
/** The same list as a set, for the check's inner loop. */
|
|
15
|
+
export declare const KNOWN_METHOD_SET: ReadonlySet<string>;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Method names the platform expects to be CALLED, not merely named.
|
|
3
|
+
*
|
|
4
|
+
* Generated — do not edit by hand. Run `node scripts/gen-bpmn-known-methods.mjs`
|
|
5
|
+
* to refresh from the installed `@uipath/maestro-sdk`.
|
|
6
|
+
*
|
|
7
|
+
* The canvas validator's `METHOD_MISSING_PARENTHESES` rule reads this list: an
|
|
8
|
+
* expression containing `.<name>` not followed by `(` is an ERROR when `<name>` is
|
|
9
|
+
* one of these. It is the platform's own vocabulary of .NET/Jint methods reachable
|
|
10
|
+
* from an expression, copied verbatim so the SDK can report the same thing a rung
|
|
11
|
+
* earlier. `tests/bpmn-check-platform-rules.test.ts` fails if the copy drifts.
|
|
12
|
+
*/
|
|
13
|
+
export const KNOWN_METHODS = [
|
|
14
|
+
'Abs',
|
|
15
|
+
'Acos',
|
|
16
|
+
'AcosPi',
|
|
17
|
+
'Acosh',
|
|
18
|
+
'Add',
|
|
19
|
+
'AddDays',
|
|
20
|
+
'AddHours',
|
|
21
|
+
'AddMicroseconds',
|
|
22
|
+
'AddMilliseconds',
|
|
23
|
+
'AddMinutes',
|
|
24
|
+
'AddMonths',
|
|
25
|
+
'AddSeconds',
|
|
26
|
+
'AddTicks',
|
|
27
|
+
'AddYears',
|
|
28
|
+
'Asin',
|
|
29
|
+
'AsinPi',
|
|
30
|
+
'Asinh',
|
|
31
|
+
'Atan',
|
|
32
|
+
'Atan2',
|
|
33
|
+
'Atan2Pi',
|
|
34
|
+
'AtanPi',
|
|
35
|
+
'Atanh',
|
|
36
|
+
'BigMul',
|
|
37
|
+
'BitDecrement',
|
|
38
|
+
'BitIncrement',
|
|
39
|
+
'Cbrt',
|
|
40
|
+
'Ceiling',
|
|
41
|
+
'ChangeType',
|
|
42
|
+
'Clamp',
|
|
43
|
+
'Clone',
|
|
44
|
+
'Compare',
|
|
45
|
+
'CompareOrdinal',
|
|
46
|
+
'CompareTo',
|
|
47
|
+
'Concat',
|
|
48
|
+
'Contains',
|
|
49
|
+
'ConvertFromUtf32',
|
|
50
|
+
'ConvertToInteger',
|
|
51
|
+
'ConvertToIntegerNative',
|
|
52
|
+
'Copy',
|
|
53
|
+
'CopySign',
|
|
54
|
+
'CopyTo',
|
|
55
|
+
'Cos',
|
|
56
|
+
'CosPi',
|
|
57
|
+
'Cosh',
|
|
58
|
+
'Create',
|
|
59
|
+
'CreateChecked',
|
|
60
|
+
'CreateSaturating',
|
|
61
|
+
'CreateTruncating',
|
|
62
|
+
'CreateVersion7',
|
|
63
|
+
'DaysInMonth',
|
|
64
|
+
'Deconstruct',
|
|
65
|
+
'DegreesToRadians',
|
|
66
|
+
'DivRem',
|
|
67
|
+
'Divide',
|
|
68
|
+
'Duration',
|
|
69
|
+
'EndsWith',
|
|
70
|
+
'Equals',
|
|
71
|
+
'Exp',
|
|
72
|
+
'Exp10',
|
|
73
|
+
'Exp10M1',
|
|
74
|
+
'Exp2',
|
|
75
|
+
'Exp2M1',
|
|
76
|
+
'ExpM1',
|
|
77
|
+
'Finalize',
|
|
78
|
+
'Floor',
|
|
79
|
+
'Format',
|
|
80
|
+
'FromBase64CharArray',
|
|
81
|
+
'FromBase64String',
|
|
82
|
+
'FromBinary',
|
|
83
|
+
'FromDays',
|
|
84
|
+
'FromFileTime',
|
|
85
|
+
'FromFileTimeUtc',
|
|
86
|
+
'FromHexString',
|
|
87
|
+
'FromHours',
|
|
88
|
+
'FromMicroseconds',
|
|
89
|
+
'FromMilliseconds',
|
|
90
|
+
'FromMinutes',
|
|
91
|
+
'FromOACurrency',
|
|
92
|
+
'FromOADate',
|
|
93
|
+
'FromSeconds',
|
|
94
|
+
'FromTicks',
|
|
95
|
+
'FusedMultiplyAdd',
|
|
96
|
+
'GetBits',
|
|
97
|
+
'GetDateTimeFormats',
|
|
98
|
+
'GetEnumerator',
|
|
99
|
+
'GetHashCode',
|
|
100
|
+
'GetNumericValue',
|
|
101
|
+
'GetPinnableReference',
|
|
102
|
+
'GetType',
|
|
103
|
+
'GetTypeCode',
|
|
104
|
+
'GetUnicodeCategory',
|
|
105
|
+
'Hypot',
|
|
106
|
+
'IEEERemainder',
|
|
107
|
+
'ILogB',
|
|
108
|
+
'Ieee754Remainder',
|
|
109
|
+
'IndexOf',
|
|
110
|
+
'IndexOfAny',
|
|
111
|
+
'Insert',
|
|
112
|
+
'Intern',
|
|
113
|
+
'IsAscii',
|
|
114
|
+
'IsAsciiDigit',
|
|
115
|
+
'IsAsciiHexDigit',
|
|
116
|
+
'IsAsciiHexDigitLower',
|
|
117
|
+
'IsAsciiHexDigitUpper',
|
|
118
|
+
'IsAsciiLetter',
|
|
119
|
+
'IsAsciiLetterLower',
|
|
120
|
+
'IsAsciiLetterOrDigit',
|
|
121
|
+
'IsAsciiLetterUpper',
|
|
122
|
+
'IsBetween',
|
|
123
|
+
'IsCanonical',
|
|
124
|
+
'IsControl',
|
|
125
|
+
'IsDBNull',
|
|
126
|
+
'IsDaylightSavingTime',
|
|
127
|
+
'IsDigit',
|
|
128
|
+
'IsEvenInteger',
|
|
129
|
+
'IsFinite',
|
|
130
|
+
'IsHighSurrogate',
|
|
131
|
+
'IsInfinity',
|
|
132
|
+
'IsInteger',
|
|
133
|
+
'IsInterned',
|
|
134
|
+
'IsLeapYear',
|
|
135
|
+
'IsLetter',
|
|
136
|
+
'IsLetterOrDigit',
|
|
137
|
+
'IsLowSurrogate',
|
|
138
|
+
'IsLower',
|
|
139
|
+
'IsNaN',
|
|
140
|
+
'IsNegative',
|
|
141
|
+
'IsNegativeInfinity',
|
|
142
|
+
'IsNormal',
|
|
143
|
+
'IsNormalized',
|
|
144
|
+
'IsNullOrEmpty',
|
|
145
|
+
'IsNullOrWhiteSpace',
|
|
146
|
+
'IsNumber',
|
|
147
|
+
'IsOddInteger',
|
|
148
|
+
'IsPositive',
|
|
149
|
+
'IsPositiveInfinity',
|
|
150
|
+
'IsPow2',
|
|
151
|
+
'IsPunctuation',
|
|
152
|
+
'IsRealNumber',
|
|
153
|
+
'IsSeparator',
|
|
154
|
+
'IsSubnormal',
|
|
155
|
+
'IsSurrogate',
|
|
156
|
+
'IsSurrogatePair',
|
|
157
|
+
'IsSymbol',
|
|
158
|
+
'IsUpper',
|
|
159
|
+
'IsWhiteSpace',
|
|
160
|
+
'Join',
|
|
161
|
+
'LastIndexOf',
|
|
162
|
+
'LastIndexOfAny',
|
|
163
|
+
'LeadingZeroCount',
|
|
164
|
+
'Lerp',
|
|
165
|
+
'Log',
|
|
166
|
+
'Log10',
|
|
167
|
+
'Log10P1',
|
|
168
|
+
'Log2',
|
|
169
|
+
'Log2P1',
|
|
170
|
+
'LogP1',
|
|
171
|
+
'Max',
|
|
172
|
+
'MaxMagnitude',
|
|
173
|
+
'MaxMagnitudeNumber',
|
|
174
|
+
'MaxNumber',
|
|
175
|
+
'MemberwiseClone',
|
|
176
|
+
'Min',
|
|
177
|
+
'MinMagnitude',
|
|
178
|
+
'MinMagnitudeNumber',
|
|
179
|
+
'MinNumber',
|
|
180
|
+
'Multiply',
|
|
181
|
+
'MultiplyAddEstimate',
|
|
182
|
+
'Negate',
|
|
183
|
+
'NewGuid',
|
|
184
|
+
'Normalize',
|
|
185
|
+
'PadLeft',
|
|
186
|
+
'PadRight',
|
|
187
|
+
'Parse',
|
|
188
|
+
'ParseExact',
|
|
189
|
+
'PopCount',
|
|
190
|
+
'Pow',
|
|
191
|
+
'RadiansToDegrees',
|
|
192
|
+
'ReciprocalEstimate',
|
|
193
|
+
'ReciprocalSqrtEstimate',
|
|
194
|
+
'ReferenceEquals',
|
|
195
|
+
'Remainder',
|
|
196
|
+
'Remove',
|
|
197
|
+
'Replace',
|
|
198
|
+
'ReplaceLineEndings',
|
|
199
|
+
'RootN',
|
|
200
|
+
'RotateLeft',
|
|
201
|
+
'RotateRight',
|
|
202
|
+
'Round',
|
|
203
|
+
'Scale',
|
|
204
|
+
'ScaleB',
|
|
205
|
+
'Sign',
|
|
206
|
+
'Sin',
|
|
207
|
+
'SinCos',
|
|
208
|
+
'SinCosPi',
|
|
209
|
+
'SinPi',
|
|
210
|
+
'Sinh',
|
|
211
|
+
'SpecifyKind',
|
|
212
|
+
'Split',
|
|
213
|
+
'Sqrt',
|
|
214
|
+
'StartsWith',
|
|
215
|
+
'Substring',
|
|
216
|
+
'Subtract',
|
|
217
|
+
'Tan',
|
|
218
|
+
'TanPi',
|
|
219
|
+
'Tanh',
|
|
220
|
+
'ToBase64CharArray',
|
|
221
|
+
'ToBase64String',
|
|
222
|
+
'ToBinary',
|
|
223
|
+
'ToBoolean',
|
|
224
|
+
'ToByte',
|
|
225
|
+
'ToByteArray',
|
|
226
|
+
'ToChar',
|
|
227
|
+
'ToCharArray',
|
|
228
|
+
'ToDateTime',
|
|
229
|
+
'ToDecimal',
|
|
230
|
+
'ToDouble',
|
|
231
|
+
'ToFileTime',
|
|
232
|
+
'ToFileTimeUtc',
|
|
233
|
+
'ToInt16',
|
|
234
|
+
'ToInt32',
|
|
235
|
+
'ToInt64',
|
|
236
|
+
'ToLocalTime',
|
|
237
|
+
'ToLongDateString',
|
|
238
|
+
'ToLongTimeString',
|
|
239
|
+
'ToLower',
|
|
240
|
+
'ToLowerInvariant',
|
|
241
|
+
'ToOACurrency',
|
|
242
|
+
'ToOADate',
|
|
243
|
+
'ToSByte',
|
|
244
|
+
'ToShortDateString',
|
|
245
|
+
'ToShortTimeString',
|
|
246
|
+
'ToSingle',
|
|
247
|
+
'ToString',
|
|
248
|
+
'ToUInt16',
|
|
249
|
+
'ToUInt32',
|
|
250
|
+
'ToUInt64',
|
|
251
|
+
'ToUniversalTime',
|
|
252
|
+
'ToUpper',
|
|
253
|
+
'ToUpperInvariant',
|
|
254
|
+
'TrailingZeroCount',
|
|
255
|
+
'Trim',
|
|
256
|
+
'TrimEnd',
|
|
257
|
+
'TrimStart',
|
|
258
|
+
'Truncate',
|
|
259
|
+
'TryFormat',
|
|
260
|
+
'TryFromBase64Chars',
|
|
261
|
+
'TryFromBase64String',
|
|
262
|
+
'TryGetBits',
|
|
263
|
+
'TryParse',
|
|
264
|
+
'TryParseExact',
|
|
265
|
+
'TryToBase64Chars',
|
|
266
|
+
'TryToHexString',
|
|
267
|
+
'TryToHexStringLower',
|
|
268
|
+
'TryWriteBytes',
|
|
269
|
+
];
|
|
270
|
+
/** The same list as a set, for the check's inner loop. */
|
|
271
|
+
export const KNOWN_METHOD_SET = new Set(KNOWN_METHODS);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { BuiltBpmn } from './bpmn-sdk.js';
|
|
2
|
+
/** The result of building two sources and comparing what they built. */
|
|
3
|
+
export interface GraphComparison {
|
|
4
|
+
/** Whether the two sources build the same graph. */
|
|
5
|
+
same: boolean;
|
|
6
|
+
/** When they do not: which source failed to build, or what differed. */
|
|
7
|
+
reason?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The part of a built process that is the process: ordered by id, `implied`
|
|
11
|
+
* provenance dropped, and `undefined` fields gone the way JSON drops them.
|
|
12
|
+
*
|
|
13
|
+
* @param built - A built process.
|
|
14
|
+
* @returns A string two builds can be compared by.
|
|
15
|
+
*/
|
|
16
|
+
export declare function canonicalGraph(built: BuiltBpmn, renamedFlows?: ReadonlyMap<string, string>): string;
|
|
17
|
+
/**
|
|
18
|
+
* Build a decompiled source and return its graph.
|
|
19
|
+
*
|
|
20
|
+
* The source's `import { bpmn } from "<specifier>"` line is rewritten to `sdkEntry`
|
|
21
|
+
* so the module resolves from a temp directory, whatever specifier the caller asked
|
|
22
|
+
* the emitted file to carry.
|
|
23
|
+
*
|
|
24
|
+
* @param source - A `.bpmn.ts` source.
|
|
25
|
+
* @param sdkEntry - Absolute path of the `bpmn-sdk.js` to build against.
|
|
26
|
+
* @returns The built process.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildSource(source: string, sdkEntry: string): Promise<BuiltBpmn>;
|
|
29
|
+
/**
|
|
30
|
+
* Whether two sources build the same graph.
|
|
31
|
+
*
|
|
32
|
+
* @param flat - The flat source.
|
|
33
|
+
* @param nested - The lifted source.
|
|
34
|
+
* @param sdkEntry - Absolute path of the `bpmn-sdk.js` to build both against.
|
|
35
|
+
* @param renamedFlows - Flow ids the lift renamed, applied to the flat build before comparing.
|
|
36
|
+
* @returns `same: true`, or the reason they differ.
|
|
37
|
+
*/
|
|
38
|
+
export declare function sameGraph(flat: string, nested: string, sdkEntry: string,
|
|
39
|
+
/** Flow ids the lift renamed (`from` → `to`), applied to the FLAT build before comparing — see `LiftSummary.renamedFlows`. */
|
|
40
|
+
renamedFlows?: ReadonlyMap<string, string>): Promise<GraphComparison>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lift-verify — the check that makes `decompile --style nested` safe to write.
|
|
3
|
+
*
|
|
4
|
+
* The nested style is a lift of flat regions into the builder's constructs, and
|
|
5
|
+
* every construct lowers back to plain nodes and flows. So the two sources are
|
|
6
|
+
* interchangeable exactly when they BUILD THE SAME GRAPH: same elements, same
|
|
7
|
+
* flows, same variables, ids and payloads included. This builds both and compares
|
|
8
|
+
* them, and the CLI writes the nested source only when they agree — otherwise the
|
|
9
|
+
* flat one, with the reason. The lift's own rules are conservative, but a rule is
|
|
10
|
+
* an argument and this is the measurement.
|
|
11
|
+
*/
|
|
12
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { tmpdir } from 'node:os';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { pathToFileURL } from 'node:url';
|
|
16
|
+
function sortById(xs) {
|
|
17
|
+
return [...xs].sort((a, b) => a.id.localeCompare(b.id));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* JSON with every object's keys sorted, so two builds that set the same fields in a
|
|
21
|
+
* different order — a start event's `payload` before or after its `event` — read as
|
|
22
|
+
* the same. `Map` values are not expected here; `canonicalGraph` flattens the one it has.
|
|
23
|
+
*/
|
|
24
|
+
function stableJson(value) {
|
|
25
|
+
return JSON.stringify(value, (_key, v) => v && typeof v === 'object' && !Array.isArray(v)
|
|
26
|
+
? Object.fromEntries(Object.keys(v).sort().map((k) => [k, v[k]]))
|
|
27
|
+
: v);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The part of a built process that is the process: ordered by id, `implied`
|
|
31
|
+
* provenance dropped, and `undefined` fields gone the way JSON drops them.
|
|
32
|
+
*
|
|
33
|
+
* @param built - A built process.
|
|
34
|
+
* @returns A string two builds can be compared by.
|
|
35
|
+
*/
|
|
36
|
+
export function canonicalGraph(built, renamedFlows = new Map()) {
|
|
37
|
+
// A flow the lift renamed is compared under its new id — on the flow itself and on
|
|
38
|
+
// the gateway `default` that names it. Everything else about it must still agree.
|
|
39
|
+
const flowId = (id) => renamedFlows.get(id) ?? id;
|
|
40
|
+
const nodes = (ns) => sortById(ns).map((n) => n.kind === 'subProcess'
|
|
41
|
+
? { ...n, nodes: nodes(n.nodes), flows: flows(n.flows), variables: sortById(n.variables) }
|
|
42
|
+
: 'default' in n && typeof n.default === 'string'
|
|
43
|
+
? { ...n, default: flowId(n.default) }
|
|
44
|
+
: n);
|
|
45
|
+
// A relabelled edge also sheds an EMPTY label: `name=""` is designer residue an
|
|
46
|
+
// implied edge has nowhere to carry, and the lift refuses a real one.
|
|
47
|
+
const flows = (fs) => sortById(fs.map((f) => (renamedFlows.has(f.id) && f.name === '' ? { ...f, id: flowId(f.id), name: undefined } : { ...f, id: flowId(f.id) }))).map(({ implied: _implied, ...f }) => f);
|
|
48
|
+
return stableJson({
|
|
49
|
+
id: built.id,
|
|
50
|
+
name: built.name,
|
|
51
|
+
metadata: built.metadata,
|
|
52
|
+
variables: sortById(built.variables),
|
|
53
|
+
messages: sortById(built.messages),
|
|
54
|
+
errors: sortById(built.errors),
|
|
55
|
+
bindings: sortById(built.bindings),
|
|
56
|
+
schemas: [...built.schemas.entries()].sort(([a], [b]) => a.localeCompare(b)),
|
|
57
|
+
nodes: nodes(built.nodes),
|
|
58
|
+
flows: flows(built.flows),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Build a decompiled source and return its graph.
|
|
63
|
+
*
|
|
64
|
+
* The source's `import { bpmn } from "<specifier>"` line is rewritten to `sdkEntry`
|
|
65
|
+
* so the module resolves from a temp directory, whatever specifier the caller asked
|
|
66
|
+
* the emitted file to carry.
|
|
67
|
+
*
|
|
68
|
+
* @param source - A `.bpmn.ts` source.
|
|
69
|
+
* @param sdkEntry - Absolute path of the `bpmn-sdk.js` to build against.
|
|
70
|
+
* @returns The built process.
|
|
71
|
+
*/
|
|
72
|
+
export async function buildSource(source, sdkEntry) {
|
|
73
|
+
const dir = mkdtempSync(join(tmpdir(), 'bpmn-lift-'));
|
|
74
|
+
// `finally`, not a call after the await: the throw below and any error inside the
|
|
75
|
+
// imported module both escape, and `sameGraph` catches them — which is the path the
|
|
76
|
+
// tests take most. Cleaning up only on success leaked a directory per failed lift,
|
|
77
|
+
// two per verify and up to four per `decompile --style nested`.
|
|
78
|
+
//
|
|
79
|
+
// Safe to delete once the import has resolved: the module is fully evaluated, the SDK
|
|
80
|
+
// specifier was rewritten to an absolute path OUTSIDE this directory, and the file
|
|
81
|
+
// name is `mkdtemp` plus a random suffix, so nothing re-resolves it.
|
|
82
|
+
try {
|
|
83
|
+
writeFileSync(join(dir, 'package.json'), '{"type":"module"}\n');
|
|
84
|
+
const file = join(dir, `Lift-${Math.random().toString(36).slice(2)}.bpmn.ts`);
|
|
85
|
+
const rewritten = source.replace(/^(import\s*\{[^}]*\}\s*from\s*)["'][^"']+["'];/m, `$1${JSON.stringify(sdkEntry)};`);
|
|
86
|
+
writeFileSync(file, rewritten);
|
|
87
|
+
const mod = await import(pathToFileURL(file).href);
|
|
88
|
+
if (!mod.default || !Array.isArray(mod.default.nodes))
|
|
89
|
+
throw new Error('the source has no built default export');
|
|
90
|
+
return mod.default;
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
rmSync(dir, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Whether two sources build the same graph.
|
|
98
|
+
*
|
|
99
|
+
* @param flat - The flat source.
|
|
100
|
+
* @param nested - The lifted source.
|
|
101
|
+
* @param sdkEntry - Absolute path of the `bpmn-sdk.js` to build both against.
|
|
102
|
+
* @param renamedFlows - Flow ids the lift renamed, applied to the flat build before comparing.
|
|
103
|
+
* @returns `same: true`, or the reason they differ.
|
|
104
|
+
*/
|
|
105
|
+
export async function sameGraph(flat, nested, sdkEntry,
|
|
106
|
+
/** Flow ids the lift renamed (`from` → `to`), applied to the FLAT build before comparing — see `LiftSummary.renamedFlows`. */
|
|
107
|
+
renamedFlows = new Map()) {
|
|
108
|
+
let a;
|
|
109
|
+
let b;
|
|
110
|
+
try {
|
|
111
|
+
a = canonicalGraph(await buildSource(flat, sdkEntry), renamedFlows);
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
return { same: false, reason: `the flat source did not build: ${e instanceof Error ? e.message : String(e)}` };
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
b = canonicalGraph(await buildSource(nested, sdkEntry));
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
return { same: false, reason: `the nested source did not build: ${e instanceof Error ? e.message : String(e)}` };
|
|
121
|
+
}
|
|
122
|
+
if (a === b)
|
|
123
|
+
return { same: true };
|
|
124
|
+
return { same: false, reason: firstDifference(a, b) };
|
|
125
|
+
}
|
|
126
|
+
/** A short pointer at where two canonical graphs part ways. */
|
|
127
|
+
function firstDifference(a, b) {
|
|
128
|
+
let i = 0;
|
|
129
|
+
while (i < a.length && i < b.length && a[i] === b[i])
|
|
130
|
+
i++;
|
|
131
|
+
const from = Math.max(0, i - 60);
|
|
132
|
+
return `the graphs differ near …${a.slice(from, i + 80)}… (flat) vs …${b.slice(from, i + 80)}… (nested)`;
|
|
133
|
+
}
|