@uipath/maestro-builder-sdk 6.41.0 → 6.41.2

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.
@@ -55,7 +55,7 @@ type SpecLike = {
55
55
  * The author-declared exits of `spec`, or a `NoExits` explaining why there are
56
56
  * none to route.
57
57
  */
58
- export declare function declaredExits(spec: SpecLike | undefined, _pinnedVersion?: string): DeclaredExit[] | NoExits;
58
+ export declare function declaredExits(spec: SpecLike | undefined): DeclaredExit[] | NoExits;
59
59
  /** Narrowing helper — `declaredExits` returns one or the other. */
60
60
  export declare function hasExits(r: DeclaredExit[] | NoExits): r is DeclaredExit[];
61
61
  export {};
@@ -26,7 +26,7 @@
26
26
  * The author-declared exits of `spec`, or a `NoExits` explaining why there are
27
27
  * none to route.
28
28
  */
29
- export function declaredExits(spec, _pinnedVersion) {
29
+ export function declaredExits(spec) {
30
30
  const kind = spec && 'kind' in spec ? spec.kind : undefined;
31
31
  return {
32
32
  reason: `a ${kind ?? 'custom'} step declares no author-named exits`,
@@ -23,7 +23,7 @@ export function stepExits(spec, pinnedVersion) {
23
23
  return raw;
24
24
  const entry = typeof spec?.kind === 'string' ? FLOW_NODE_TYPES.byKind(spec.kind, spec) : undefined;
25
25
  if (!entry)
26
- return declaredExits(spec, pinnedVersion);
26
+ return declaredExits(spec);
27
27
  let impl;
28
28
  try {
29
29
  impl = resolveVersion(entry.type, spec, pinnedVersion, 'exits');
@@ -31,5 +31,5 @@ export function stepExits(spec, pinnedVersion) {
31
31
  catch {
32
32
  impl = resolveVersion(entry.type, spec, undefined, 'exits');
33
33
  }
34
- return impl.exits?.(spec, pinnedVersion) ?? declaredExits(spec, pinnedVersion);
34
+ return impl.exits?.(spec, pinnedVersion) ?? declaredExits(spec);
35
35
  }
@@ -1,4 +1,5 @@
1
1
  type Json = unknown;
2
+ type Obj = Record<string, Json>;
2
3
  /** JSON with object keys sorted, so key order is not a difference. */
3
4
  export declare function stableJson(v: Json): string;
4
5
  /**
@@ -16,4 +17,10 @@ export declare function isDeltaSpelling(variables: Json): boolean;
16
17
  export declare function mergeVariables(original: Json, edited: Json, baseline: {
17
18
  variables?: Json;
18
19
  } | undefined, goneNodeIds: Set<string>): Json;
20
+ /**
21
+ * `o` with the author's changes in `e` applied key by key under the delta rule,
22
+ * keys in the original's order; `inner` merges a value all three have and the
23
+ * author changed. Built from entries, so an own `__proto__` key stays a key.
24
+ */
25
+ export declare function mergeObject(o: Obj, b: Obj, e: Obj, inner: (o: Json, b: Json, e: Json) => Json): Obj;
19
26
  export {};
@@ -75,7 +75,7 @@ export function mergeVariables(original, edited, baseline, goneNodeIds) {
75
75
  return copy(edited);
76
76
  const orig = isObj(original) ? original : {};
77
77
  const baseObj = isObj(base) ? base : {};
78
- const out = {};
78
+ const entries = [];
79
79
  for (const key of keysInOrder(orig, edited)) {
80
80
  const updates = key === 'variableUpdates';
81
81
  const inner = updates
@@ -83,16 +83,16 @@ export function mergeVariables(original, edited, baseline, goneNodeIds) {
83
83
  : (o, b, e) => mergeList(o, b, e, goneNodeIds);
84
84
  // A section the original lacks but the round trip writes is merged as an
85
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]);
86
+ const absent = !has(orig, key) && has(baseObj, key) && has(edited, key) && !same(edited[key], baseObj[key]);
87
87
  const value = absent
88
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);
89
+ : mergeKey(own(orig, key), own(baseObj, key), own(edited, key), has(orig, key), has(baseObj, key), has(edited, key), inner);
90
90
  if (value !== undefined)
91
- out[key] = value;
91
+ entries.push([key, value]);
92
92
  }
93
- if (original === undefined && Object.keys(out).length === 0)
93
+ if (original === undefined && entries.length === 0)
94
94
  return undefined;
95
- return withoutGoneUpdates(out, goneNodeIds);
95
+ return withoutGoneUpdates(Object.fromEntries(entries), goneNodeIds);
96
96
  }
97
97
  /** `undefined` for an empty list or map, so an absent section stays absent. */
98
98
  function nonEmpty(v) {
@@ -113,9 +113,29 @@ function withoutGoneUpdates(variables, goneNodeIds) {
113
113
  return variables;
114
114
  return { ...variables, variableUpdates: Object.fromEntries(Object.entries(updates).filter(([k]) => !goneNodeIds.has(k))) };
115
115
  }
116
+ /**
117
+ * Own keys only, so a key named like an `Object.prototype` member
118
+ * (`constructor`, `toString`) is not taken as present everywhere.
119
+ */
120
+ const has = (v, k) => Object.hasOwn(v, k);
121
+ const own = (v, k) => (has(v, k) ? v[k] : undefined);
116
122
  /** Original's keys in its order, then the edited file's new ones. */
117
123
  function keysInOrder(original, edited) {
118
- return [...Object.keys(original), ...Object.keys(edited).filter((k) => !(k in original))];
124
+ return [...Object.keys(original), ...Object.keys(edited).filter((k) => !has(original, k))];
125
+ }
126
+ /**
127
+ * `o` with the author's changes in `e` applied key by key under the delta rule,
128
+ * keys in the original's order; `inner` merges a value all three have and the
129
+ * author changed. Built from entries, so an own `__proto__` key stays a key.
130
+ */
131
+ export function mergeObject(o, b, e, inner) {
132
+ const entries = [];
133
+ for (const key of keysInOrder(o, e)) {
134
+ const v = mergeKey(own(o, key), own(b, key), own(e, key), has(o, key), has(b, key), has(e, key), inner);
135
+ if (v !== undefined)
136
+ entries.push([key, v]);
137
+ }
138
+ return Object.fromEntries(entries);
119
139
  }
120
140
  /**
121
141
  * One key's value under the delta rule; `undefined` = leave the key out.
@@ -173,14 +193,7 @@ function insertAt(out, edited, i) {
173
193
  * keys in the original's order.
174
194
  */
175
195
  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;
196
+ return mergeObject(o, b ?? {}, e, (_o, _b, ev) => copy(ev));
184
197
  }
185
198
  function idList(v) {
186
199
  return Array.isArray(v) && v.every((x) => isObj(x) && typeof x.id === 'string');
package/dist/merge.d.ts CHANGED
@@ -19,7 +19,8 @@
19
19
  * `edited` is byte-equal to its `baseline` counterpart, the user did NOT
20
20
  * touch it, so the merge keeps the ORIGINAL node verbatim (its real type,
21
21
  * inputs, layout — even for a placeholder). Only nodes the user actually
22
- * changed (or added) are taken from `edited`.
22
+ * changed (or added) are taken from `edited`, and a changed node takes only
23
+ * the fields that differ from the baseline (see withEdits).
23
24
  * - Without a `baseline`, the merge is two-way: it takes `edited`'s graph and
24
25
  * re-attaches the original layout by node-id match. This is correct as long
25
26
  * as decompile was faithful for every node (the common case).
package/dist/merge.js CHANGED
@@ -19,7 +19,8 @@
19
19
  * `edited` is byte-equal to its `baseline` counterpart, the user did NOT
20
20
  * touch it, so the merge keeps the ORIGINAL node verbatim (its real type,
21
21
  * inputs, layout — even for a placeholder). Only nodes the user actually
22
- * changed (or added) are taken from `edited`.
22
+ * changed (or added) are taken from `edited`, and a changed node takes only
23
+ * the fields that differ from the baseline (see withEdits).
23
24
  * - Without a `baseline`, the merge is two-way: it takes `edited`'s graph and
24
25
  * re-attaches the original layout by node-id match. This is correct as long
25
26
  * as decompile was faithful for every node (the common case).
@@ -85,12 +86,13 @@
85
86
  */
86
87
  import { bindingIdentity } from './core/binding-identity.js';
87
88
  import { jsonClone } from './core/json-clone.js';
89
+ import { isRecord } from './core/node-envelope.js';
88
90
  import { DEFINITIONLESS_NODE_TYPES } from './core/definitionless-node-types.js';
89
91
  import { MergeRefusedError, mergedFileFindings, roundTripFindings } from './merge-roundtrip.js';
90
92
  import { placeAddedNodes } from './node-placement.js';
91
93
  import { mergeAgentSidecars } from './merge-sidecars.js';
92
94
  import { embeddedAgentFindings, isEmbeddedAgent, mergeEmbeddedAgentNode } from './merge-embedded-agent.js';
93
- import { mergeVariables } from './merge-variables.js';
95
+ import { mergeObject, mergeVariables, stableJson } from './merge-variables.js';
94
96
  export { MergeRefusedError } from './merge-roundtrip.js';
95
97
  function byId(nodes) {
96
98
  return new Map((nodes ?? []).map((n) => [n.id, n]));
@@ -163,6 +165,65 @@ function withOriginalLayout(edited, orig, originalLayout) {
163
165
  }
164
166
  return edited;
165
167
  }
168
+ /**
169
+ * Give `node` the original's `display` with the author's display edits applied,
170
+ * key by key: a key whose edited value differs from the baseline's is the
171
+ * author's (a relabel); every other key stays as the original has it. Compile
172
+ * writes only a label and the type's default icon, so the edited display taken
173
+ * whole drops a canvas file's `subLabel`, `iconBackground` and custom icon, and
174
+ * the original's taken whole drops the relabel.
175
+ */
176
+ function withDisplayEdits(node, orig, e, base) {
177
+ const o = isRecord(orig.display) ? orig.display : {};
178
+ const ed = isRecord(e.display) ? e.display : {};
179
+ const b = isRecord(base.display) ? base.display : {};
180
+ const display = {};
181
+ for (const k of new Set([...Object.keys(o), ...Object.keys(ed)])) {
182
+ const v = sortedJson(ed[k]) === sortedJson(b[k]) ? o[k] : ed[k];
183
+ if (v !== undefined)
184
+ display[k] = jsonClone(v);
185
+ }
186
+ const next = sortedJson(display) === sortedJson(orig.display ?? {}) ? orig.display : display;
187
+ if (JSON.stringify(next) === JSON.stringify(node.display))
188
+ return node;
189
+ // In place, so the node keeps its key order.
190
+ const out = { ...node, display: undefined };
191
+ if (next === undefined)
192
+ delete out.display;
193
+ else
194
+ out.display = jsonClone(next);
195
+ return out;
196
+ }
197
+ /**
198
+ * The original with the author's edits applied, by the delta rule of
199
+ * merge-variables.ts: where `e` equals the baseline `b` the original's value
200
+ * stays (absent stays absent), and inside objects all three have, the rule
201
+ * recurses key by key. Lists are taken whole, and so is a changed expression
202
+ * value, whose `type` says how to read its `expression`: decompile turns a
203
+ * legacy `{ type: 'literal', expression: '{{ $vars.x }}' }` agent input into a
204
+ * `jsExpression`, so an edited reference merged key by key would keep
205
+ * `literal` and hand the agent the text. Applied to an edited node, a
206
+ * one-input edit to a connector keeps the rest of its `detail` as written, and
207
+ * compile's `model`, derived `outputs` and other spellings stay out of it.
208
+ */
209
+ function withEdits(o, b, e) {
210
+ if (stableJson(e) === stableJson(b))
211
+ return o === undefined ? o : jsonClone(o);
212
+ if (!isRecord(o) || !isRecord(b) || !isRecord(e) || isExpressionValue(o) || isExpressionValue(e)) {
213
+ return e === undefined ? e : jsonClone(e);
214
+ }
215
+ return mergeObject(o, b, e, withEdits);
216
+ }
217
+ const isExpressionValue = (v) => typeof v.type === 'string' && Object.hasOwn(v, 'expression');
218
+ /**
219
+ * Whether an edited node can merge field by field onto its original: all three
220
+ * have one type and one `typeVersion`. A node whose version the edit or the
221
+ * round trip moved is taken from `edited` whole, so it never mixes two
222
+ * versions' inputs.
223
+ */
224
+ function mergesByField(orig, base, e) {
225
+ return orig.type === e.type && base.type === e.type && orig.typeVersion === e.typeVersion && base.typeVersion === e.typeVersion;
226
+ }
166
227
  /**
167
228
  * Give an edited node the original's `inputs.source` when only the round trip
168
229
  * changed it (the baseline's equals the edited one's).
@@ -243,18 +304,23 @@ function mergeFlowFindings(original, edited, options) {
243
304
  const editedIds = new Set();
244
305
  for (const e of edited.nodes ?? []) {
245
306
  const orig = origNodes.get(e.id);
307
+ const base = baseNodes.get(e.id);
246
308
  if (isKept(e, origNodes, baseNodes, options.baseline) && orig) {
247
309
  // Untouched by the user (equal to the faithful baseline) → keep the
248
- // original node exactly, placeholder-safe.
249
- finalNodes.push(jsonClone(orig));
310
+ // original node exactly, placeholder-safe, plus any relabel.
311
+ finalNodes.push(base ? withDisplayEdits(jsonClone(orig), orig, e, base) : jsonClone(orig));
250
312
  keptFromOriginal.add(e.id);
251
313
  }
252
314
  else {
253
- // Added or edited → take the edited node, re-attaching original layout.
315
+ // Added or edited → the original plus the author's changes, field by
316
+ // field (withEdits), else the edited node; then re-attach original layout.
254
317
  // An EMBEDDED agent stays embedded: the original plus the author's change
255
318
  // to the node, never the edited shell whole (#1220, merge-embedded-agent.ts).
256
- const node = keepsEmbeddedAgent(orig, e) ? mergeEmbeddedAgentNode(orig, e, baseNodes.get(e.id)) : jsonClone(e);
257
- const placed = withOriginalSource(withOriginalLayout(node, orig, layoutNodesOf(original)), orig, baseNodes.get(e.id));
319
+ const taken = keepsEmbeddedAgent(orig, e) ? mergeEmbeddedAgentNode(orig, e, base)
320
+ : orig && base && mergesByField(orig, base, e) ? withEdits(orig, base, e)
321
+ : jsonClone(e);
322
+ const node = orig && base ? withDisplayEdits(taken, orig, e, base) : taken;
323
+ const placed = withOriginalSource(withOriginalLayout(node, orig, layoutNodesOf(original)), orig, base);
258
324
  finalNodes.push(placed);
259
325
  // Without a baseline every node lands here; count one as edited only
260
326
  // when the merged node differs from the original.
@@ -289,6 +355,10 @@ function mergeFlowFindings(original, edited, options) {
289
355
  ...(original.nodes ?? []),
290
356
  ...Object.values(original.subflows ?? {}).flatMap((sf) => sf?.nodes ?? []),
291
357
  ].map((n) => n.id).filter((id) => !mergedNodeIds.has(id)));
358
+ const layoutNodes = layoutNodesOf(result);
359
+ if (layoutNodes)
360
+ for (const id of goneNodeIds)
361
+ delete layoutNodes[id];
292
362
  const variables = mergeVariables(original.variables, edited.variables, options.baseline, goneNodeIds);
293
363
  if (variables === undefined)
294
364
  delete result.variables;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.41.0",
3
+ "version": "6.41.2",
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": "98a669a3825cc478f61ec1f337f5efb835236c3e"
96
+ "gitref": "cac59a549d2de4ef1ed882e7d21c7ed68598cd52"
97
97
  }