@uipath/maestro-builder-sdk 6.33.1 → 6.34.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 +26 -24
- package/dist/api-members.md +141 -136
- package/dist/case/case-sdk.d.ts +52 -1
- package/dist/case/case-sdk.js +37 -2
- package/dist/case/check.js +30 -0
- package/dist/case/compile-cli.js +6 -1
- package/dist/case/decompile.js +50 -3
- package/dist/case/format-profile.d.ts +1 -1
- package/dist/case/format-profile.js +7 -3
- package/dist/case/serialize.js +93 -9
- package/dist/flow-variables.d.ts +2 -0
- package/dist/flow-variables.js +2 -1
- package/dist/merge-roundtrip.d.ts +1 -1
- package/dist/merge-roundtrip.js +32 -1
- package/dist/merge-variables.d.ts +19 -0
- package/dist/merge-variables.js +191 -0
- package/dist/merge.d.ts +4 -3
- package/dist/merge.js +18 -3
- package/dist/schema-version.js +6 -1
- package/package.json +2 -2
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* merge-variables — the delta rule for a flow's root `variables` (#1261).
|
|
3
|
+
*
|
|
4
|
+
* Nodes merge as a delta: one the author did not touch (edited equals
|
|
5
|
+
* baseline) is restored from the original. `variables` follow the same rule.
|
|
6
|
+
*
|
|
7
|
+
* The same rule, applied at three levels:
|
|
8
|
+
*
|
|
9
|
+
* - the whole section: edited equals baseline → the original's, verbatim;
|
|
10
|
+
* - a top-level key (`globals`, `nodes`, `variableUpdates`): edited equals
|
|
11
|
+
* baseline → the original's (absent stays absent);
|
|
12
|
+
* - an entry of an id-keyed list (`globals`, `nodes`) or a key of a map
|
|
13
|
+
* (`variableUpdates`): the same, and inside an entry each field the same,
|
|
14
|
+
* so an edited global's new `type` lands without the `name` compile adds.
|
|
15
|
+
*
|
|
16
|
+
* Presence follows the same logic. An entry the edited file lacks was removed
|
|
17
|
+
* by the author when the baseline has it, and lost by the round trip when it
|
|
18
|
+
* does not (kept). An entry the original lacks is the author's when the
|
|
19
|
+
* baseline lacks it or differs, and the round trip's when the baseline has it
|
|
20
|
+
* unchanged (dropped).
|
|
21
|
+
*
|
|
22
|
+
* Example, after the author changes `creditAmount`'s type and nothing else:
|
|
23
|
+
*
|
|
24
|
+
* original { id: 'creditAmount', direction: 'out', type: 'string' }
|
|
25
|
+
* baseline { id: 'creditAmount', name: 'creditAmount', direction: 'out', type: 'string' }
|
|
26
|
+
* edited { id: 'creditAmount', name: 'creditAmount', direction: 'out', type: 'number' }
|
|
27
|
+
* merged { id: 'creditAmount', direction: 'out', type: 'number' }
|
|
28
|
+
*
|
|
29
|
+
* An original with no `variables` section, or with no `globals` / `nodes` /
|
|
30
|
+
* `variableUpdates` key, is treated as empty there: what compile minted for
|
|
31
|
+
* the untouched part is still the round trip's, and only the author's
|
|
32
|
+
* additions land.
|
|
33
|
+
*
|
|
34
|
+
* Without a baseline there is nothing to tell an edit from the round trip, so
|
|
35
|
+
* the edited section is taken whole. So is a section whose original uses a key
|
|
36
|
+
* this file does not know (the older map spelling, `variables.city = {...}`)
|
|
37
|
+
* once the author changed anything: mixing two spellings in one section is
|
|
38
|
+
* worse than taking the compile's. The round-trip report still lists what that
|
|
39
|
+
* carries, but does not refuse it under `--strict`, because the author cannot
|
|
40
|
+
* avoid it short of making no edit ({@link isDeltaSpelling}).
|
|
41
|
+
*/
|
|
42
|
+
import { jsonClone } from './core/json-clone.js';
|
|
43
|
+
import { CONTAINER_KEYS } from './flow-variables.js';
|
|
44
|
+
/** `jsonClone`, which also passes `undefined` (an absent section or key) through. */
|
|
45
|
+
const copy = (v) => (v === undefined ? v : jsonClone(v));
|
|
46
|
+
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
47
|
+
/** JSON with object keys sorted, so key order is not a difference. */
|
|
48
|
+
export function stableJson(v) {
|
|
49
|
+
return JSON.stringify(v, (_k, val) => (isObj(val)
|
|
50
|
+
? Object.fromEntries(Object.keys(val).sort().map((k) => [k, val[k]]))
|
|
51
|
+
: val));
|
|
52
|
+
}
|
|
53
|
+
const same = (a, b) => stableJson(a) === stableJson(b);
|
|
54
|
+
/**
|
|
55
|
+
* Whether `variables` is spelled the way the delta reads it: absent, or an
|
|
56
|
+
* object whose keys are only the container keys (`globals`, `nodes`,
|
|
57
|
+
* `variableUpdates`). A map-spelled original is not, and is taken whole.
|
|
58
|
+
*/
|
|
59
|
+
export function isDeltaSpelling(variables) {
|
|
60
|
+
return variables === undefined || (isObj(variables) && Object.keys(variables).every((k) => CONTAINER_KEYS.has(k)));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The merged root `variables`. `goneNodeIds` are the original's nodes (root and
|
|
64
|
+
* subflows) the merged file no longer has: a node variable, or a
|
|
65
|
+
* `variableUpdates` key, kept from the original for one of them is dropped
|
|
66
|
+
* with the node.
|
|
67
|
+
*/
|
|
68
|
+
export function mergeVariables(original, edited, baseline, goneNodeIds) {
|
|
69
|
+
if (!baseline)
|
|
70
|
+
return copy(edited);
|
|
71
|
+
const base = baseline.variables;
|
|
72
|
+
if (same(edited, base))
|
|
73
|
+
return withoutGoneUpdates(copy(original), goneNodeIds);
|
|
74
|
+
if (!isDeltaSpelling(original) || !isObj(edited))
|
|
75
|
+
return copy(edited);
|
|
76
|
+
const orig = isObj(original) ? original : {};
|
|
77
|
+
const baseObj = isObj(base) ? base : {};
|
|
78
|
+
const out = {};
|
|
79
|
+
for (const key of keysInOrder(orig, edited)) {
|
|
80
|
+
const updates = key === 'variableUpdates';
|
|
81
|
+
const inner = updates
|
|
82
|
+
? (o, b, e) => (isObj(o) && isObj(e) ? mergeFields(o, isObj(b) ? b : undefined, e) : copy(e))
|
|
83
|
+
: (o, b, e) => mergeList(o, b, e, goneNodeIds);
|
|
84
|
+
// A section the original lacks but the round trip writes is merged as an
|
|
85
|
+
// empty one, so its minted entries stay out and only the author's land.
|
|
86
|
+
const absent = !(key in orig) && key in baseObj && key in edited && !same(edited[key], baseObj[key]);
|
|
87
|
+
const value = absent
|
|
88
|
+
? nonEmpty(inner(updates ? {} : [], baseObj[key], edited[key]))
|
|
89
|
+
: mergeKey(orig[key], baseObj[key], edited[key], key in orig, key in baseObj, key in edited, inner);
|
|
90
|
+
if (value !== undefined)
|
|
91
|
+
out[key] = value;
|
|
92
|
+
}
|
|
93
|
+
if (original === undefined && Object.keys(out).length === 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
return withoutGoneUpdates(out, goneNodeIds);
|
|
96
|
+
}
|
|
97
|
+
/** `undefined` for an empty list or map, so an absent section stays absent. */
|
|
98
|
+
function nonEmpty(v) {
|
|
99
|
+
if (Array.isArray(v))
|
|
100
|
+
return v.length ? v : undefined;
|
|
101
|
+
if (isObj(v))
|
|
102
|
+
return Object.keys(v).length ? v : undefined;
|
|
103
|
+
return v;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* `variables` without the `variableUpdates` keys (node ids) of nodes the merge
|
|
107
|
+
* dropped. A key the round trip lost is kept from the original, so without
|
|
108
|
+
* this it would outlive its node.
|
|
109
|
+
*/
|
|
110
|
+
function withoutGoneUpdates(variables, goneNodeIds) {
|
|
111
|
+
const updates = isObj(variables) ? variables.variableUpdates : undefined;
|
|
112
|
+
if (!isObj(updates) || !Object.keys(updates).some((k) => goneNodeIds.has(k)))
|
|
113
|
+
return variables;
|
|
114
|
+
return { ...variables, variableUpdates: Object.fromEntries(Object.entries(updates).filter(([k]) => !goneNodeIds.has(k))) };
|
|
115
|
+
}
|
|
116
|
+
/** Original's keys in its order, then the edited file's new ones. */
|
|
117
|
+
function keysInOrder(original, edited) {
|
|
118
|
+
return [...Object.keys(original), ...Object.keys(edited).filter((k) => !(k in original))];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* One key's value under the delta rule; `undefined` = leave the key out.
|
|
122
|
+
* `inner` merges a value both sides have and the author changed.
|
|
123
|
+
*/
|
|
124
|
+
function mergeKey(o, b, e, inOriginal, inBaseline, inEdited, inner) {
|
|
125
|
+
if (!inEdited)
|
|
126
|
+
return inBaseline || !inOriginal ? undefined : copy(o); // removed by the author / lost by the round trip
|
|
127
|
+
if (inBaseline && same(e, b))
|
|
128
|
+
return inOriginal ? copy(o) : undefined; // untouched
|
|
129
|
+
if (!inOriginal)
|
|
130
|
+
return copy(e); // the author's addition
|
|
131
|
+
return inner(o, b, e);
|
|
132
|
+
}
|
|
133
|
+
/** An id-keyed list (`globals`, `nodes`): per entry, then per field. */
|
|
134
|
+
function mergeList(o, b, e, goneNodeIds) {
|
|
135
|
+
if (!idList(o) || !idList(e))
|
|
136
|
+
return copy(e);
|
|
137
|
+
const base = new Map((idList(b) ? b : []).map((x) => [x.id, x]));
|
|
138
|
+
const edits = new Map(e.map((x) => [x.id, x]));
|
|
139
|
+
const out = [];
|
|
140
|
+
for (const entry of o) {
|
|
141
|
+
const id = entry.id;
|
|
142
|
+
if (!edits.has(id)) {
|
|
143
|
+
// The author removed it (baseline has it), or the round trip lost it; a
|
|
144
|
+
// lost node variable whose node is gone goes with the node.
|
|
145
|
+
if (!base.has(id) && !boundToGoneNode(entry, goneNodeIds))
|
|
146
|
+
out.push(copy(entry));
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const ed = edits.get(id);
|
|
150
|
+
out.push(base.has(id) && same(ed, base.get(id)) ? copy(entry) : mergeFields(entry, base.get(id), ed));
|
|
151
|
+
}
|
|
152
|
+
const originalIds = new Set(o.map((x) => x.id));
|
|
153
|
+
for (const [i, ed] of e.entries()) {
|
|
154
|
+
const id = ed.id;
|
|
155
|
+
if (originalIds.has(id) || (base.has(id) && same(ed, base.get(id))))
|
|
156
|
+
continue; // kept above / minted by the round trip
|
|
157
|
+
out.splice(insertAt(out, e, i), 0, copy(ed));
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
/** After the nearest earlier edited entry already in `out`, else first. */
|
|
162
|
+
function insertAt(out, edited, i) {
|
|
163
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
164
|
+
const at = out.findIndex((x) => x.id === edited[j].id);
|
|
165
|
+
if (at >= 0)
|
|
166
|
+
return at + 1;
|
|
167
|
+
}
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* An object the author changed — a list entry, or the `variableUpdates` map
|
|
172
|
+
* (keyed by node id): each key under the delta rule, each value taken whole,
|
|
173
|
+
* keys in the original's order.
|
|
174
|
+
*/
|
|
175
|
+
function mergeFields(o, b, e) {
|
|
176
|
+
const base = b ?? {};
|
|
177
|
+
const out = {};
|
|
178
|
+
for (const key of keysInOrder(o, e)) {
|
|
179
|
+
const v = mergeKey(o[key], base[key], e[key], key in o, key in base, key in e, (_o, _b, ev) => copy(ev));
|
|
180
|
+
if (v !== undefined)
|
|
181
|
+
out[key] = v;
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
function idList(v) {
|
|
186
|
+
return Array.isArray(v) && v.every((x) => isObj(x) && typeof x.id === 'string');
|
|
187
|
+
}
|
|
188
|
+
function boundToGoneNode(entry, goneNodeIds) {
|
|
189
|
+
const nodeId = isObj(entry.binding) ? entry.binding.nodeId : undefined;
|
|
190
|
+
return typeof nodeId === 'string' && goneNodeIds.has(nodeId);
|
|
191
|
+
}
|
package/dist/merge.d.ts
CHANGED
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
*
|
|
27
27
|
* Layout, `metadata`, `solutionId`, `projectId`, `runtime` and `layout` are
|
|
28
28
|
* preserved from the original; `subflows` and edges (with their ids) come from
|
|
29
|
-
* `edited`; `
|
|
29
|
+
* `edited`; root `variables` merge as a delta per entry, like nodes
|
|
30
|
+
* (merge-variables.ts); `definitions` and `bindings` are unioned
|
|
30
31
|
* (original wins on conflict, since the original's are the tenant-resolved
|
|
31
32
|
* authority). A binding row's identity is what it binds, not its row id, and a
|
|
32
33
|
* row the no-edit recompile minted only for nodes merge kept from the original
|
|
@@ -143,8 +144,8 @@ export interface MergeOptions {
|
|
|
143
144
|
* - with a `baseline`, each wire-identity difference between the baseline
|
|
144
145
|
* (the no-edit round trip) and the original — a node or edge added, dropped
|
|
145
146
|
* or rewired, a switch case id or human-task outcome id renamed, a binding
|
|
146
|
-
* row added or dropped
|
|
147
|
-
* author's edit did not undo it;
|
|
147
|
+
* row added or dropped, a root `variables` entry added or rewritten — that
|
|
148
|
+
* the merged file still carries, i.e. the author's edit did not undo it;
|
|
148
149
|
* - an edited node whose `typeVersion` differs from the original node with
|
|
149
150
|
* the same id, because the edit took the SDK's version;
|
|
150
151
|
* - a merged edge whose `sourcePort` is not a handle its source node declares,
|
package/dist/merge.js
CHANGED
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
*
|
|
27
27
|
* Layout, `metadata`, `solutionId`, `projectId`, `runtime` and `layout` are
|
|
28
28
|
* preserved from the original; `subflows` and edges (with their ids) come from
|
|
29
|
-
* `edited`; `
|
|
29
|
+
* `edited`; root `variables` merge as a delta per entry, like nodes
|
|
30
|
+
* (merge-variables.ts); `definitions` and `bindings` are unioned
|
|
30
31
|
* (original wins on conflict, since the original's are the tenant-resolved
|
|
31
32
|
* authority). A binding row's identity is what it binds, not its row id, and a
|
|
32
33
|
* row the no-edit recompile minted only for nodes merge kept from the original
|
|
@@ -89,6 +90,7 @@ import { MergeRefusedError, mergedFileFindings, roundTripFindings } from './merg
|
|
|
89
90
|
import { placeAddedNodes } from './node-placement.js';
|
|
90
91
|
import { mergeAgentSidecars } from './merge-sidecars.js';
|
|
91
92
|
import { embeddedAgentFindings, isEmbeddedAgent, mergeEmbeddedAgentNode } from './merge-embedded-agent.js';
|
|
93
|
+
import { mergeVariables } from './merge-variables.js';
|
|
92
94
|
export { MergeRefusedError } from './merge-roundtrip.js';
|
|
93
95
|
function byId(nodes) {
|
|
94
96
|
return new Map((nodes ?? []).map((n) => [n.id, n]));
|
|
@@ -256,8 +258,7 @@ function mergeFlowFindings(original, edited, options) {
|
|
|
256
258
|
result.nodes = finalNodes;
|
|
257
259
|
// Nodes present in the original but absent from `edited` were deleted by the
|
|
258
260
|
// user; they are simply not carried over.
|
|
259
|
-
// ──
|
|
260
|
-
result.variables = jsonClone(edited.variables);
|
|
261
|
+
// ── Subflows: the edited flow is authoritative (the user's graph). ──
|
|
261
262
|
result.subflows = jsonClone(edited.subflows ?? {});
|
|
262
263
|
// A subflow is taken from `edited` whole, so an embedded agent inside one
|
|
263
264
|
// would flip to the edited shell even with no edit. Keep it embedded too.
|
|
@@ -267,6 +268,20 @@ function mergeFlowFindings(original, edited, options) {
|
|
|
267
268
|
if (options.baseline)
|
|
268
269
|
keepOriginalSourcesInSubflows(original, result.subflows, options.baseline);
|
|
269
270
|
const subflowScopes = Object.values(result.subflows);
|
|
271
|
+
// ── Variables: the same delta as nodes, per entry — what the author did not
|
|
272
|
+
// change stays the original's, so compile's own spellings (an added
|
|
273
|
+
// `name`, a re-derived End output type) do not reach the file (#1261).
|
|
274
|
+
// See merge-variables.ts. ──
|
|
275
|
+
const mergedNodeIds = new Set([...finalNodes, ...subflowScopes.flatMap((sf) => sf?.nodes ?? [])].map((n) => n.id));
|
|
276
|
+
const goneNodeIds = new Set([
|
|
277
|
+
...(original.nodes ?? []),
|
|
278
|
+
...Object.values(original.subflows ?? {}).flatMap((sf) => sf?.nodes ?? []),
|
|
279
|
+
].map((n) => n.id).filter((id) => !mergedNodeIds.has(id)));
|
|
280
|
+
const variables = mergeVariables(original.variables, edited.variables, options.baseline, goneNodeIds);
|
|
281
|
+
if (variables === undefined)
|
|
282
|
+
delete result.variables;
|
|
283
|
+
else
|
|
284
|
+
result.variables = variables;
|
|
270
285
|
// ── Edges: the edited graph's edges define the wiring, under the edited
|
|
271
286
|
// file's own ids. compile draws every edge id — root AND subflows — from
|
|
272
287
|
// one file-wide counter, so they are already unique. Renumbering the root
|
package/dist/schema-version.js
CHANGED
|
@@ -106,11 +106,16 @@ export function schemaVersionRefusal(policy, requested) {
|
|
|
106
106
|
if (isWritableSchemaVersion(policy, requested))
|
|
107
107
|
return undefined;
|
|
108
108
|
const writable = writableSchemaVersions(policy);
|
|
109
|
+
const newestWritable = writable.at(-1);
|
|
109
110
|
const direction = policy.latestKnown !== undefined && schemaVersionAtLeast(requested, policy.latestKnown)
|
|
110
111
|
? compareSchemaVersions(requested, policy.latestKnown) > 0
|
|
111
112
|
? `The platform's newest known ${family} schema version is ${policy.latestKnown}, so ${requested} is newer than anything this SDK has read.`
|
|
112
113
|
: `${requested} is the platform's current ${family} schema version, but this SDK build has not been taught to write it.`
|
|
113
|
-
:
|
|
114
|
+
: newestWritable !== undefined && compareSchemaVersions(requested, newestWritable) > 0
|
|
115
|
+
? `${requested} is a platform ${family} schema version newer than this build writes`
|
|
116
|
+
+ (policy.latestKnown !== undefined ? ` (the platform's current is ${policy.latestKnown})` : '')
|
|
117
|
+
+ '; this SDK build has not been taught to write it.'
|
|
118
|
+
: `${requested} is older than the ${writable.length === 1 ? 'one version' : 'versions'} this build writes.`;
|
|
114
119
|
return (`${method}("${requested}"): this SDK build writes ${family} schema ${writable.join(' / ')}. ${direction} `
|
|
115
120
|
+ `A document's version claims that the WHOLE document matches that schema, so declaring one this build cannot emit `
|
|
116
121
|
+ `would write a false claim rather than a different document. ${policy.migration}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.34.0",
|
|
4
4
|
"description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docs.uipath.com/maestro",
|
|
@@ -93,5 +93,5 @@
|
|
|
93
93
|
"@types/node": "^22.7.0",
|
|
94
94
|
"esbuild": "^0.28.1"
|
|
95
95
|
},
|
|
96
|
-
"gitref": "
|
|
96
|
+
"gitref": "aa9dc9a7c6f6adc8edbf4c20d0681307073bb53b"
|
|
97
97
|
}
|