@uipath/maestro-builder-sdk 6.31.3 → 6.31.5

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/README.md CHANGED
@@ -372,7 +372,11 @@ read the same copy. The worked examples named by those guides travel with them.
372
372
 
373
373
  The connector library is not bundled: `uip maestro registry pull` downloads it
374
374
  and caches it per machine. The package does carry the curated overlay that pull
375
- applies on top — corrections and the definitions the raw registry dump omits.
375
+ applies on top. It adds the definitions the downloaded library lacks (connector
376
+ start triggers, for example) and never replaces one the library carries, except
377
+ for the patches listed in `lib/library-json.samples/overlay-corrections.json`,
378
+ which only add the fields they name. Pull prints one line saying what it added,
379
+ corrected and kept.
376
380
 
377
381
  ## License
378
382
 
@@ -117,6 +117,23 @@ const BASELINE = ${JSON.stringify(join(base + '.baseline', basename(base) + '.fl
117
117
  const EDITED = ${JSON.stringify(join(base + '.edited', basename(base) + '.flow'))};
118
118
  const MERGED = ${JSON.stringify(base + '.merged.flow')};
119
119
 
120
+ // Options. \`--strict\` goes to step 2's merge: refuse, writing nothing, when the
121
+ // no-edit round trip (BASELINE) differs from ORIGINAL anywhere, instead of
122
+ // warning and writing. An unknown option stops the run: silently ignoring one
123
+ // would run a merge the caller did not ask for.
124
+ const USAGE = 'usage: node ' + ${JSON.stringify(basename(pipelinePath))} + ' [--strict]';
125
+ const argv = process.argv.slice(2);
126
+ if (argv.includes('-h') || argv.includes('--help')) {
127
+ console.log(USAGE + '\\n --strict merge refuses (exit 1, nothing written) when the no-edit round trip differs from the original');
128
+ process.exit(0);
129
+ }
130
+ const unknown = argv.find((a) => a !== '--strict');
131
+ if (unknown !== undefined) {
132
+ console.error(${JSON.stringify(basename(pipelinePath))} + ': unknown option ' + JSON.stringify(unknown) + '\\n' + USAGE);
133
+ process.exit(2);
134
+ }
135
+ const STRICT = argv.includes('--strict');
136
+
120
137
  // Ensure the decompiled source exists (a fresh clone may have only the .flow).
121
138
  if (!existsSync(TS)) run('decompile', [ORIGINAL, '-o', TS, '--no-pipeline']);
122
139
 
@@ -128,14 +145,6 @@ if (!existsSync(BASELINE)) {
128
145
  console.log('\\n>>> Baseline captured. Now EDIT ' + ${JSON.stringify(basename(tsPath))} + ', then re-run this script to compile + merge.');
129
146
  } else {
130
147
  // ── Step 2: compile your edits, then merge them back into the original. ──
131
- mkdirSync(dirname(EDITED), { recursive: true });
132
- run('compile', [TS, '-o', EDITED, '--no-check']);
133
-
134
- // ── DIFF GATE (seam) ──────────────────────────────────────────────────────
135
- // A future step can diff ORIGINAL vs the about-to-be-written MERGED here and
136
- // ask for confirmation before the write. Left as a no-op for now.
137
- // ---------------------------------------------------------------------------
138
-
139
148
  // \`merge <edited> --original <original>\` is the grammar; the positional
140
149
  // \`merge <original> <edited>\` pair is deprecated. A \`uip\` from before
141
150
  // UiPath/cli#4526 knows only the pair, so ask its \`merge --help\` first.
@@ -144,10 +153,27 @@ if (!existsSync(BASELINE)) {
144
153
  const mergeHelp = frontDoor.via === 'uip'
145
154
  ? spawnSync(frontDoor.file, [...frontDoor.prefix, 'merge', '--help'], { encoding: 'utf8' })
146
155
  : undefined;
147
- const namedOriginal = mergeHelp === undefined
148
- || \`\${mergeHelp.stdout ?? ''}\${mergeHelp.stderr ?? ''}\`.includes('--original');
156
+ const mergeHelpText = mergeHelp === undefined ? undefined : \`\${mergeHelp.stdout ?? ''}\${mergeHelp.stderr ?? ''}\`;
157
+ const namedOriginal = mergeHelpText === undefined || mergeHelpText.includes('--original');
158
+ // A \`uip\` from before \`merge --strict\` existed would reject the flag with a
159
+ // bare "unknown option"; name the remedy instead, and never drop the flag.
160
+ // Both checks run before the compile, so a run that cannot merge does no work.
161
+ if (STRICT && mergeHelpText !== undefined && !mergeHelpText.includes('--strict')) {
162
+ console.error(${JSON.stringify(basename(pipelinePath))} + ': --strict was asked for, but this uip has no \`maestro flow merge --strict\`. '
163
+ + 'Update uip, or run without --strict.');
164
+ process.exit(2);
165
+ }
166
+ mkdirSync(dirname(EDITED), { recursive: true });
167
+ run('compile', [TS, '-o', EDITED, '--no-check']);
168
+
169
+ // ── DIFF GATE (seam) ──────────────────────────────────────────────────────
170
+ // A future step can diff ORIGINAL vs the about-to-be-written MERGED here and
171
+ // ask for confirmation before the write. Left as a no-op for now.
172
+ // ---------------------------------------------------------------------------
173
+
149
174
  const args = namedOriginal ? [EDITED, '--original', ORIGINAL, '-o', MERGED] : [ORIGINAL, EDITED, '-o', MERGED];
150
175
  if (existsSync(BASELINE)) args.push('--baseline', BASELINE);
176
+ if (STRICT) args.push('--strict');
151
177
  run('merge', args);
152
178
  console.log('\\n>>> Wrote ' + ${JSON.stringify(base + '.merged.flow')}
153
179
  + '. Replace the original with it, together with every <source>/ file merge reports writing beside it,'
package/dist/decompile.js CHANGED
@@ -2132,8 +2132,10 @@ function droppedEdgeMessage(e) {
2132
2132
  */
2133
2133
  function droppedEdgesSeg(node, ctx) {
2134
2134
  const dropped = ctx.graph.outEdges(node.id).filter((e) => !ctx.graph.accounted.has(e));
2135
- if (dropped.length === 0)
2136
- return undefined;
2135
+ return dropped.length === 0 ? undefined : dropEdges(dropped, ctx);
2136
+ }
2137
+ /** Account `dropped` as dropped: warn each once, remember it for strict mode, and return the site's `/* TODO *\/`. */
2138
+ function dropEdges(dropped, ctx) {
2137
2139
  for (const e of dropped) {
2138
2140
  ctx.graph.accounted.add(e);
2139
2141
  runDropped.push(e);
@@ -2200,11 +2202,12 @@ function commonPostDominator(targets, ipdom) {
2200
2202
  * decompiles to that `.parallel(...)`, then the region continues from the
2201
2203
  * merge's output (flow-builder-sdk#1155).
2202
2204
  *
2203
- * Every edge in `edges` is accounted (edge accounting, #1125): the fold
2204
- * recreates each one. A fork whose arms never meet at a merge node has no
2205
- * builder spelling. Like the same shape mid-flow (`region`), it throws under
2206
- * `bestEffort: false`, and otherwise keeps the first arm behind a
2207
- * `TODO: unstructured fan-out` marker, which is what reports the other edges.
2205
+ * When the arms meet at a merge node, every edge in `edges` is accounted
2206
+ * (edge accounting, #1125): the fold recreates each one. A fork whose arms
2207
+ * never meet at a merge node has no builder spelling. Like the same shape
2208
+ * mid-flow (`region`), it throws under `bestEffort: false`, and otherwise keeps
2209
+ * the first arm behind a `TODO: unstructured fan-out` marker. Only the kept
2210
+ * first edge is accounted; the others are reported as dropped at the marker.
2208
2211
  */
2209
2212
  function forkRegion(edges, stop, scope, ipdom, ctx, refInto) {
2210
2213
  if (edges.length <= 1)
@@ -2215,10 +2218,10 @@ function forkRegion(edges, stop, scope, ipdom, ctx, refInto) {
2215
2218
  if (join === undefined || join === stop || joinNode?.type !== T.merge) {
2216
2219
  if (ctx.o.bestEffort === false)
2217
2220
  throw new Error(`decompile: unstructured fan-out (${edges.length} edges).`);
2218
- for (const e of edges)
2219
- ctx.graph.accounted.add(e);
2221
+ ctx.graph.accounted.add(edges[0]);
2220
2222
  return [
2221
2223
  `/* TODO: unstructured fan-out (${commentText(edges.length)} edges) */`,
2224
+ dropEdges(edges.slice(1), ctx),
2222
2225
  ...region(targets[0], stop, scope, ipdom, ctx, refInto),
2223
2226
  ];
2224
2227
  }
@@ -2617,13 +2620,11 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2617
2620
  cursor = fork.next.cursor;
2618
2621
  continue;
2619
2622
  }
2620
- // Unstructured fan-out we can't fold — best-effort marker. Its successor
2621
- // edges are what the marker reports, so they are not reported twice.
2623
+ // Unstructured fan-out we can't fold — best-effort marker.
2624
+ // No successor is walked: leave the edges unaccounted so `flagDropped` names each.
2622
2625
  if (ctx.o.bestEffort === false)
2623
2626
  throw new Error(`decompile: unstructured fan-out at "${node.id}".`);
2624
2627
  segs.push(`/* TODO: unstructured fan-out at ${commentText(node.id)} (${commentText(succ.length)} successors) */`);
2625
- for (const t of succ)
2626
- follow(ctx.graph, node.id, t);
2627
2628
  flagDropped(node, ctx, segs);
2628
2629
  return segs;
2629
2630
  }
package/dist/merge.d.ts CHANGED
@@ -28,7 +28,9 @@
28
28
  * preserved from the original; `subflows` and edges (with their ids) come from
29
29
  * `edited`; `definitions` and `bindings` are unioned
30
30
  * (original wins on conflict, since the original's are the tenant-resolved
31
- * authority). A binding row's identity is what it binds, not its row id. Node ids are stable by construction (decompile names every step by
31
+ * authority). A binding row's identity is what it binds, not its row id, and a
32
+ * row the no-edit recompile minted only for nodes merge kept from the original
33
+ * is not carried (see withoutRowsMintedForKeptNodes). Node ids are stable by construction (decompile names every step by
32
34
  * its node id), which is what lets every re-attach key on id.
33
35
  *
34
36
  * ## Definitions are keyed by `nodeType@version`, not by `nodeType`
package/dist/merge.js CHANGED
@@ -28,7 +28,9 @@
28
28
  * preserved from the original; `subflows` and edges (with their ids) come from
29
29
  * `edited`; `definitions` and `bindings` are unioned
30
30
  * (original wins on conflict, since the original's are the tenant-resolved
31
- * authority). A binding row's identity is what it binds, not its row id. Node ids are stable by construction (decompile names every step by
31
+ * authority). A binding row's identity is what it binds, not its row id, and a
32
+ * row the no-edit recompile minted only for nodes merge kept from the original
33
+ * is not carried (see withoutRowsMintedForKeptNodes). Node ids are stable by construction (decompile names every step by
32
34
  * its node id), which is what lets every re-attach key on id.
33
35
  *
34
36
  * ## Definitions are keyed by `nodeType@version`, not by `nodeType`
@@ -212,12 +214,17 @@ function mergeFlowFindings(original, edited, options) {
212
214
  // ── Nodes: the edited graph decides which nodes exist and in what order; the
213
215
  // original decides their look (and, for untouched nodes, their content). ──
214
216
  const finalNodes = [];
217
+ // The edited nodes the merge replaced with the original's: their bindings
218
+ // references are the original's now, not the recompile's
219
+ // (see withoutRowsMintedForKeptNodes).
220
+ const keptFromOriginal = new Set();
215
221
  for (const e of edited.nodes ?? []) {
216
222
  const orig = origNodes.get(e.id);
217
223
  if (isKept(e, origNodes, baseNodes, options.baseline) && orig) {
218
224
  // Untouched by the user (equal to the faithful baseline) → keep the
219
225
  // original node exactly, placeholder-safe.
220
226
  finalNodes.push(jsonClone(orig));
227
+ keptFromOriginal.add(e.id);
221
228
  }
222
229
  else {
223
230
  // Added or edited → take the edited node, re-attaching original layout.
@@ -266,9 +273,11 @@ function mergeFlowFindings(original, edited, options) {
266
273
  ];
267
274
  result.definitions = mergeDefinitions(original.definitions, edited.definitions, allNodes, originalNodes, options.warn);
268
275
  // ── Bindings: union by what each row binds (original wins), so resources for
269
- // newly-added nodes come from `edited` while the original's stay intact. ──
276
+ // newly-added nodes come from `edited` while the original's stay intact.
277
+ // A row the no-edit recompile minted only for nodes the merge kept from the
278
+ // original is not carried (#1133). ──
270
279
  result.bindings = mergeBindings(original.bindings, edited.bindings);
271
- result.bindings = withoutRowsKeyedOnlyByKeptNodes(result.bindings, original, edited, options.baseline);
280
+ result.bindings = withoutRowsMintedForKeptNodes(result.bindings, original, edited, options.baseline, keptFromOriginal);
272
281
  // metadata / solutionId / projectId / layout / runtime were carried by the
273
282
  // initial clone of `original` and are intentionally left untouched.
274
283
  //
@@ -379,12 +388,14 @@ function mergeDefinitions(orig, edited, nodes, originalNodes, warn) {
379
388
  * holds it, in which case it gets a fresh one — ids are unreferenced labels, but
380
389
  * two rows with one id are not a valid file.
381
390
  *
382
- * Rows no surviving node uses are NOT pruned. The file has no uniform
391
+ * Rows of the ORIGINAL are never pruned. The canvas file has no uniform
383
392
  * node-to-row reference to decide "unused" by: an in-solution Flow is named by
384
393
  * the node TYPE's suffix, a connection by an id in the node's inputs, a published
385
394
  * process by a folder-qualified key that no node field spells. A wrong prune
386
395
  * drops a row a kept node needs, and the run fails to resolve its resource; an
387
- * orphan row is accepted by `validate` and ignored at run time.
396
+ * orphan row is accepted by `validate` and ignored at run time. Added rows the
397
+ * round trip minted only for kept nodes are dropped afterwards, by
398
+ * {@link withoutRowsMintedForKeptNodes}.
388
399
  */
389
400
  function mergeBindings(orig, edited) {
390
401
  const out = (orig ?? []).map((b) => jsonClone(b));
@@ -409,41 +420,70 @@ function mergeBindings(orig, edited) {
409
420
  return out;
410
421
  }
411
422
  /**
412
- * Drop each added row that the no-edit recompile minted for a resource only
413
- * nodes kept from the original spell. A row qualifies when the original lacks
414
- * it, the baseline has it, and its `resourceKey` appears in at least one edited
415
- * root node, every one of which the merge replaced with the original's: the kept
416
- * node is the original's, and it reaches the resource without that row.
423
+ * Drop each added row that the no-edit recompile minted only for nodes the merge
424
+ * kept from the original (#1133). A row qualifies when the original lacks it, the
425
+ * baseline has it, and at least one edited node references it, every one of
426
+ * which is in `keptFromOriginal`: the kept node is the original's, which reaches
427
+ * its resource without that row.
417
428
  *
418
- * Example: a classic-CLI IxP extract carries its model inline and has no
419
- * binding rows. It decompiles to `ixpExtract(...)`, whose compile mints
420
- * `ixpDeployment` `name` / `folderPath` rows keyed by the model id, and an
421
- * untouched node would carry them into the merged file.
429
+ * Examples:
430
+ * - A classic-CLI IxP extract carries its model inline and has no rows. It
431
+ * decompiles to `ixpExtract(...)`, whose compile mints `ixpDeployment` rows
432
+ * keyed by the model id.
433
+ * - A classic-CLI `core.action.http.v2` with no connection decompiles to
434
+ * `http({ managed: true })`, whose compile mints `""`-keyed rows for
435
+ * `<bindings.uipath-uipath-http connection>` and `<bindings.FolderKey>`.
422
436
  *
423
- * A row whose key no edited node spells is kept, as are rows a subflow node or
424
- * an edited node spells.
437
+ * "References" is read off the EDITED file, which the SDK wrote. A node
438
+ * references a row when it spells the row's `resourceKey`, or when it spells the
439
+ * row's `<bindings.NAME>` placeholder (in the node, or in its type's definition)
440
+ * and points at that row's resource. Names are not unique: a Slack step on
441
+ * connection X and a connection-less http step both use `<bindings.FolderKey>`,
442
+ * each with its own `FolderKey` row. So a placeholder counts for a keyed row
443
+ * only when no other row has that name, and for a `""`-keyed row only when the
444
+ * node spells none of the other same-named rows' keys.
445
+ *
446
+ * Kept: a row no edited node references (one a sidecar file uses, say), a row
447
+ * a subflow node or an edited node references, and every row without a baseline.
425
448
  */
426
- function withoutRowsKeyedOnlyByKeptNodes(rows, original, edited, baseline) {
427
- if (!baseline || !rows)
449
+ function withoutRowsMintedForKeptNodes(rows, original, edited, baseline, keptFromOriginal) {
450
+ if (!baseline || !rows || keptFromOriginal.size === 0)
428
451
  return rows;
429
- const origNodes = byId(original.nodes);
430
- const baseNodes = byId(baseline.nodes);
452
+ const contextByType = new Map();
453
+ for (const d of (edited.definitions ?? [])) {
454
+ if (typeof d?.nodeType !== 'string' || d.model?.context === undefined)
455
+ continue;
456
+ contextByType.set(d.nodeType, (contextByType.get(d.nodeType) ?? '') + JSON.stringify(d.model.context));
457
+ }
458
+ const describe = (n, kept) => ({ kept, text: JSON.stringify(n) + (contextByType.get(n.type) ?? '') });
431
459
  const nodes = [
432
- ...(edited.nodes ?? []).map((n) => ({ kept: origNodes.has(n.id) && nodeUnchanged(n, baseNodes.get(n.id)), text: JSON.stringify(n) })),
433
- ...Object.values(edited.subflows ?? {}).flatMap((sf) => (sf?.nodes ?? []).map((n) => ({ kept: false, text: JSON.stringify(n) }))),
460
+ ...(edited.nodes ?? []).map((n) => describe(n, keptFromOriginal.has(n.id))),
461
+ ...Object.values(edited.subflows ?? {}).flatMap((sf) => (sf?.nodes ?? []).map((n) => describe(n, false))),
434
462
  ];
435
- if (!nodes.some((n) => n.kept))
436
- return rows;
463
+ const spelled = (s) => JSON.stringify(s).slice(1, -1);
464
+ const keyOf = (b) => (typeof b.resourceKey === 'string' ? b.resourceKey : '');
465
+ const all = (edited.bindings ?? []);
437
466
  const fromOriginal = new Set((original.bindings ?? []).map(bindingIdentity));
438
467
  const inBaseline = new Set((baseline.bindings ?? []).map(bindingIdentity));
439
468
  return rows.filter((b) => {
440
469
  const identity = bindingIdentity(b);
441
470
  if (fromOriginal.has(identity) || !inBaseline.has(identity))
442
471
  return true;
443
- if (typeof b.resourceKey !== 'string' || b.resourceKey === '')
444
- return true;
445
- const spelled = JSON.stringify(b.resourceKey).slice(1, -1);
446
- const spellers = nodes.filter((n) => n.text.includes(spelled));
447
- return !(spellers.length > 0 && spellers.every((n) => n.kept));
472
+ const key = keyOf(b);
473
+ const others = typeof b.name === 'string'
474
+ ? all.filter((o) => o.name === b.name && keyOf(o) !== key)
475
+ : [];
476
+ const placeholder = typeof b.name === 'string' ? spelled(`<bindings.${b.name}>`) : undefined;
477
+ const references = (text) => {
478
+ if (key !== '' && text.includes(spelled(key)))
479
+ return true;
480
+ if (placeholder === undefined || !text.includes(placeholder))
481
+ return false;
482
+ return key === ''
483
+ ? !others.some((o) => text.includes(spelled(keyOf(o))))
484
+ : others.length === 0;
485
+ };
486
+ const referencers = nodes.filter((n) => references(n.text));
487
+ return !(referencers.length > 0 && referencers.every((n) => n.kept));
448
488
  });
449
489
  }
@@ -3,46 +3,81 @@ import { type Reporter } from "./reporter.js";
3
3
  export declare function bundledOverlayPath(): string;
4
4
  /** Stable identity of the curated data, so a changed overlay gets a new cache entry. */
5
5
  export declare function curatedOverlayIdentity(overlayPath?: string): Promise<string>;
6
+ /** The ledger of deliberate patches; read here, never copied into a library. */
7
+ export declare const CORRECTIONS_FILE = "overlay-corrections.json";
8
+ /** One row of {@link CORRECTIONS_FILE}. */
9
+ export interface OverlayCorrection {
10
+ path: string;
11
+ since: string;
12
+ reason: string;
13
+ /** Added to the entry's `inputSchema.fields` and the sidecar's `inputDefinition.fields` when absent. */
14
+ inputFields?: string[];
15
+ /** Top-level sidecar keys added when absent. */
16
+ sidecarKeys?: string[];
17
+ }
6
18
  export interface CuratedOverlayResult {
7
- /** Overlay files copied into the library. */
19
+ /** Files written into the library: definitions added, corrections patched, side-tables. */
8
20
  files: number;
9
21
  /** Index rows added for identities the library did not already carry. */
10
22
  entries: number;
11
23
  /**
12
24
  * Overlay entry files NOT copied, because the library resolves their
13
- * identity to a different path. POSIX-relative to the overlay, sorted. Each
14
- * one is also reported through the reporter — see {@link applyCuratedOverlay}.
25
+ * identity to a different path. POSIX-relative to the overlay, sorted.
15
26
  */
16
27
  skipped: string[];
28
+ /** Overlay entries added because the library lacked their identity. Sorted. */
29
+ added: string[];
30
+ /** Corrections patched into the library's own definition. Sorted. */
31
+ corrected: string[];
32
+ /**
33
+ * Overlay entries the library already carries at the same path, so its
34
+ * published definition was kept. Sorted. Not a problem: these are the
35
+ * directory's test fixtures, which the overlay must never copy over a
36
+ * published file (#1148).
37
+ */
38
+ kept: string[];
17
39
  }
18
40
  /**
19
- * Apply the SDK's curated corrections after extracting the raw registry dump.
41
+ * The bundled corrections, checked against the overlay they ship in. A row
42
+ * that names no indexed overlay entry, or a field that entry does not define,
43
+ * is a fault in the SDK's own data, so it throws rather than patching nothing.
44
+ */
45
+ export declare function readOverlayCorrections(overlayPath: string): Promise<OverlayCorrection[]>;
46
+ /**
47
+ * Apply the SDK's curated overlay after extracting the raw registry dump.
20
48
  *
21
- * Files replace the dump's copy, while index rows are a union where the dump
22
- * wins collisions. This is the same contract as the former unpack script: a
23
- * correction changes the referenced file, and a definition absent from the
24
- * dump (notably connector triggers) is made resolvable by its added index row.
49
+ * THE RULE. The overlay ADDS what the library lacks and never replaces what it
50
+ * carries, apart from the deliberate patches listed in {@link CORRECTIONS_FILE}:
25
51
  *
26
- * WHEN THE LIBRARY ALREADY RESOLVES THE IDENTITY ELSEWHERE, the file is not
27
- * copied at all and the skip is reported. That case is not hypothetical: four
28
- * of the bundled `event-*@1.0.0.json` files carry a (nodeType, version) the
29
- * raw dump lacks TODAY but a refreshed catalog serves at its own path — the
30
- * per-object event variants land under `<connector>/events/<object>/`, keeping
31
- * the template's nodeType. Since the union keeps the library's row, copying
32
- * anyway left a file no index row pointed at, which `--check-orphans` (the
33
- * image build passes it) rejects. The three ways out are not equal:
52
+ * - an overlay entry whose (nodeType, version) the library does not index is
53
+ * copied with its sidecar, and its index row is added. This is what makes
54
+ * the connector start triggers resolvable, which the raw dump lacks;
55
+ * - an entry the library indexes at the SAME path is kept as published. The
56
+ * overlay's copy is a snapshot, and the published library moves on: until
57
+ * #1148 every such file was copied over the download while the download's
58
+ * index row stayed, so nine 2026-07 snapshots replaced newer definitions and
59
+ * Outlook `send-email` read `send-mail` (no `saveAsDraft`, no multipart
60
+ * parts) under an index row that said `send-mail-v2`. The entry files carry
61
+ * no date or build number to compare, so "is the published one newer?" is
62
+ * not answerable per file; the answer is that the overlay never decides it;
63
+ * - a listed correction is PATCHED into the library's own definition: the
64
+ * named input fields and sidecar keys are added when absent, and nothing
65
+ * else is touched. It cannot change the operation's object or endpoint, so
66
+ * index row and entry file cannot be made to disagree. When the published
67
+ * definition already has everything, the correction is reported as no
68
+ * longer needed so its row can be removed;
69
+ * - an entry the library indexes at a DIFFERENT path is not copied: the copy
70
+ * would be a file no index row points at, which `--check-orphans` (the
71
+ * image build passes it) rejects, and stamping the flat curated file over
72
+ * each per-object variant the library serves would destroy their schemas.
73
+ * A correction in that position is warned about, since it no longer takes
74
+ * effect;
75
+ * - the SDK's side-tables (`list-envelopes.json`, `invariant-collections.json`)
76
+ * are the SDK's own data and are always copied.
34
77
  *
35
- * - copying and teaching the orphan check to tolerate the file would keep
36
- * the build green and hide the real loss, which is that the hand-verified
37
- * CORRECTION never takes effect once the index points somewhere else;
38
- * - redirecting the correction onto the indexed path cannot be done safely
39
- * here, because ONE event identity maps to MANY per-object paths in a
40
- * refreshed library, and stamping the flat curated definition over each
41
- * per-object variant would destroy the per-object schema the library just
42
- * served — a worse regression than the one being fixed;
43
- * - so the copy is skipped and said out loud. The library keeps the
44
- * definition it resolves, the tree gains no unreachable file, and the
45
- * operator is told which overlay row has been overtaken and needs
46
- * re-verifying. The image build is then green before AND after a refresh.
78
+ * What was done is reported in one line, and recorded in `.curated-overlay.json`
79
+ * beside the index, so a second application recognises its own patches: the
80
+ * record keeps a sha256 of each patched entry and sidecar, and a correction
81
+ * whose files no longer match it is patched again.
47
82
  */
48
83
  export declare function applyCuratedOverlay(libraryPath: string, overlayPath?: string, reporter?: Reporter): Promise<CuratedOverlayResult>;
@@ -1,8 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
- import { cp, mkdir, readdir, readFile } from "node:fs/promises";
2
+ import { access, cp, mkdir, readdir, readFile } from "node:fs/promises";
3
3
  import { dirname, join, relative, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { sortedStrings } from "./compat/compare.js";
6
+ import { jsonClone } from "../core/json-clone.js";
6
7
  import { writeJsonFile } from "../core/json-file.js";
7
8
  import { consoleReporter } from "./reporter.js";
8
9
  /** The corrections and definitions that the raw registry dump does not carry. */
@@ -33,39 +34,182 @@ export async function curatedOverlayIdentity(overlayPath = bundledOverlayPath())
33
34
  function entryIdentity(entry) {
34
35
  return `${String(entry.nodeType)}\0${String(entry.version)}`;
35
36
  }
37
+ /** The ledger of deliberate patches; read here, never copied into a library. */
38
+ export const CORRECTIONS_FILE = "overlay-corrections.json";
39
+ /**
40
+ * What the overlay did to one library, written beside it as a dotfile so a
41
+ * second application (`unpack-library.mjs` applies it twice, once per verb)
42
+ * does not mistake its own patch for "the published library caught up".
43
+ */
44
+ const RECORD_FILE = ".curated-overlay.json";
45
+ /** SDK-owned side-tables the published library does not carry; always copied. */
46
+ const SIDE_TABLES = new Set(["list-envelopes.json", "invariant-collections.json"]);
36
47
  /** The sidecar that travels with an entry file, and has no index row of its own. */
37
48
  function sidecarOf(rel) {
38
49
  return `${rel.slice(0, -".json".length)}.v1def.json`;
39
50
  }
51
+ function isDict(value) {
52
+ return typeof value === "object" && value !== null && !Array.isArray(value);
53
+ }
54
+ /** A sidecar is either the definition itself or a one-element array of it. */
55
+ function sidecarBody(sidecar) {
56
+ const body = Array.isArray(sidecar) ? sidecar[0] : sidecar;
57
+ return isDict(body) ? body : undefined;
58
+ }
59
+ /** `holder[key].fields`, created when absent, so a patch has an array to add to. */
60
+ function fieldList(holder, key) {
61
+ const section = isDict(holder[key]) ? holder[key] : (holder[key] = {});
62
+ if (!Array.isArray(section.fields))
63
+ section.fields = [];
64
+ return section.fields;
65
+ }
66
+ function fieldNames(fields) {
67
+ return new Set((Array.isArray(fields) ? fields : [])
68
+ .filter(isDict)
69
+ .map((field) => String(field.name)));
70
+ }
71
+ async function readJson(path) {
72
+ return JSON.parse(await readFile(path, "utf8"));
73
+ }
74
+ async function exists(path) {
75
+ try {
76
+ await access(path);
77
+ return true;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ /**
84
+ * The bundled corrections, checked against the overlay they ship in. A row
85
+ * that names no indexed overlay entry, or a field that entry does not define,
86
+ * is a fault in the SDK's own data, so it throws rather than patching nothing.
87
+ */
88
+ export async function readOverlayCorrections(overlayPath) {
89
+ const file = join(overlayPath, CORRECTIONS_FILE);
90
+ if (!(await exists(file)))
91
+ return [];
92
+ const ledger = (await readJson(file));
93
+ const overlayIndex = (await readJson(join(overlayPath, "index.json")));
94
+ const indexed = new Set((overlayIndex.entries ?? []).map((row) => row.path));
95
+ const corrections = ledger.corrections ?? [];
96
+ for (const correction of corrections) {
97
+ const where = `${CORRECTIONS_FILE}: correction ${JSON.stringify(correction.path)}`;
98
+ if (!indexed.has(correction.path)) {
99
+ throw new Error(`${where} names no entry in the overlay's index.json.`);
100
+ }
101
+ if (!correction.inputFields?.length && !correction.sidecarKeys?.length) {
102
+ throw new Error(`${where} patches nothing: give it inputFields or sidecarKeys.`);
103
+ }
104
+ const entry = (await readJson(join(overlayPath, correction.path)));
105
+ const sidecar = sidecarBody(await readJson(join(overlayPath, sidecarOf(correction.path)))) ?? {};
106
+ const entryFields = fieldNames(entry.inputSchema?.fields);
107
+ const sidecarFields = fieldNames(sidecar.inputDefinition?.fields);
108
+ for (const name of correction.inputFields ?? []) {
109
+ if (!entryFields.has(name) || !sidecarFields.has(name)) {
110
+ throw new Error(`${where}: the overlay's copy does not define input field ${JSON.stringify(name)} in both files.`);
111
+ }
112
+ }
113
+ for (const key of correction.sidecarKeys ?? []) {
114
+ if (!(key in sidecar)) {
115
+ throw new Error(`${where}: the overlay's sidecar has no ${JSON.stringify(key)}.`);
116
+ }
117
+ }
118
+ }
119
+ return corrections;
120
+ }
121
+ /**
122
+ * sha256 of a correction's entry file and sidecar as they are on disk now (a
123
+ * missing sidecar hashes as absent). The record keeps this for each patch it
124
+ * made, so a later application trusts its own patch only while both files are
125
+ * still the ones it wrote.
126
+ */
127
+ async function correctionDigest(libraryPath, path) {
128
+ const hash = createHash("sha256");
129
+ hash.update(await readFile(join(libraryPath, path)));
130
+ const sidecarPath = join(libraryPath, sidecarOf(path));
131
+ hash.update("\0");
132
+ hash.update((await exists(sidecarPath)) ? await readFile(sidecarPath) : "<no sidecar>");
133
+ return hash.digest("hex");
134
+ }
40
135
  /**
41
- * Apply the SDK's curated corrections after extracting the raw registry dump.
136
+ * Add what `correction` names to the library's own entry and sidecar, from the
137
+ * overlay's copy. Returns the names it added (empty means the published
138
+ * definition already carries every one of them) and how many files it wrote.
139
+ * A library with no sidecar for the entry gets none: only the entry is patched.
140
+ */
141
+ async function patchCorrection(libraryPath, overlayPath, correction) {
142
+ const sourceEntry = (await readJson(join(overlayPath, correction.path)));
143
+ const sourceSidecar = sidecarBody(await readJson(join(overlayPath, sidecarOf(correction.path)))) ?? {};
144
+ const entryPath = join(libraryPath, correction.path);
145
+ const sidecarPath = join(libraryPath, sidecarOf(correction.path));
146
+ const entry = (await readJson(entryPath));
147
+ const sidecarRaw = (await exists(sidecarPath)) ? await readJson(sidecarPath) : undefined;
148
+ const sidecar = sidecarRaw === undefined ? undefined : sidecarBody(sidecarRaw);
149
+ const added = [];
150
+ const addFields = (target, source, label) => {
151
+ const have = fieldNames(target);
152
+ const byName = new Map((Array.isArray(source) ? source : []).filter(isDict).map((field) => [String(field.name), field]));
153
+ for (const name of correction.inputFields ?? []) {
154
+ if (have.has(name))
155
+ continue;
156
+ target.push(jsonClone(byName.get(name)));
157
+ added.push(`${label}:${name}`);
158
+ }
159
+ };
160
+ addFields(fieldList(entry, "inputSchema"), sourceEntry.inputSchema?.fields, "inputSchema");
161
+ if (sidecar) {
162
+ addFields(fieldList(sidecar, "inputDefinition"), sourceSidecar.inputDefinition?.fields, "inputDefinition");
163
+ for (const key of correction.sidecarKeys ?? []) {
164
+ if (key in sidecar)
165
+ continue;
166
+ sidecar[key] = jsonClone(sourceSidecar[key]);
167
+ added.push(`sidecar:${key}`);
168
+ }
169
+ }
170
+ if (added.length === 0)
171
+ return { added, files: 0 };
172
+ writeJsonFile(entryPath, entry);
173
+ if (sidecar)
174
+ writeJsonFile(sidecarPath, sidecarRaw);
175
+ return { added, files: sidecar ? 2 : 1 };
176
+ }
177
+ /**
178
+ * Apply the SDK's curated overlay after extracting the raw registry dump.
42
179
  *
43
- * Files replace the dump's copy, while index rows are a union where the dump
44
- * wins collisions. This is the same contract as the former unpack script: a
45
- * correction changes the referenced file, and a definition absent from the
46
- * dump (notably connector triggers) is made resolvable by its added index row.
180
+ * THE RULE. The overlay ADDS what the library lacks and never replaces what it
181
+ * carries, apart from the deliberate patches listed in {@link CORRECTIONS_FILE}:
47
182
  *
48
- * WHEN THE LIBRARY ALREADY RESOLVES THE IDENTITY ELSEWHERE, the file is not
49
- * copied at all and the skip is reported. That case is not hypothetical: four
50
- * of the bundled `event-*@1.0.0.json` files carry a (nodeType, version) the
51
- * raw dump lacks TODAY but a refreshed catalog serves at its own path — the
52
- * per-object event variants land under `<connector>/events/<object>/`, keeping
53
- * the template's nodeType. Since the union keeps the library's row, copying
54
- * anyway left a file no index row pointed at, which `--check-orphans` (the
55
- * image build passes it) rejects. The three ways out are not equal:
183
+ * - an overlay entry whose (nodeType, version) the library does not index is
184
+ * copied with its sidecar, and its index row is added. This is what makes
185
+ * the connector start triggers resolvable, which the raw dump lacks;
186
+ * - an entry the library indexes at the SAME path is kept as published. The
187
+ * overlay's copy is a snapshot, and the published library moves on: until
188
+ * #1148 every such file was copied over the download while the download's
189
+ * index row stayed, so nine 2026-07 snapshots replaced newer definitions and
190
+ * Outlook `send-email` read `send-mail` (no `saveAsDraft`, no multipart
191
+ * parts) under an index row that said `send-mail-v2`. The entry files carry
192
+ * no date or build number to compare, so "is the published one newer?" is
193
+ * not answerable per file; the answer is that the overlay never decides it;
194
+ * - a listed correction is PATCHED into the library's own definition: the
195
+ * named input fields and sidecar keys are added when absent, and nothing
196
+ * else is touched. It cannot change the operation's object or endpoint, so
197
+ * index row and entry file cannot be made to disagree. When the published
198
+ * definition already has everything, the correction is reported as no
199
+ * longer needed so its row can be removed;
200
+ * - an entry the library indexes at a DIFFERENT path is not copied: the copy
201
+ * would be a file no index row points at, which `--check-orphans` (the
202
+ * image build passes it) rejects, and stamping the flat curated file over
203
+ * each per-object variant the library serves would destroy their schemas.
204
+ * A correction in that position is warned about, since it no longer takes
205
+ * effect;
206
+ * - the SDK's side-tables (`list-envelopes.json`, `invariant-collections.json`)
207
+ * are the SDK's own data and are always copied.
56
208
  *
57
- * - copying and teaching the orphan check to tolerate the file would keep
58
- * the build green and hide the real loss, which is that the hand-verified
59
- * CORRECTION never takes effect once the index points somewhere else;
60
- * - redirecting the correction onto the indexed path cannot be done safely
61
- * here, because ONE event identity maps to MANY per-object paths in a
62
- * refreshed library, and stamping the flat curated definition over each
63
- * per-object variant would destroy the per-object schema the library just
64
- * served — a worse regression than the one being fixed;
65
- * - so the copy is skipped and said out loud. The library keeps the
66
- * definition it resolves, the tree gains no unreachable file, and the
67
- * operator is told which overlay row has been overtaken and needs
68
- * re-verifying. The image build is then green before AND after a refresh.
209
+ * What was done is reported in one line, and recorded in `.curated-overlay.json`
210
+ * beside the index, so a second application recognises its own patches: the
211
+ * record keeps a sha256 of each patched entry and sidecar, and a correction
212
+ * whose files no longer match it is patched again.
69
213
  */
70
214
  export async function applyCuratedOverlay(libraryPath, overlayPath = bundledOverlayPath(),
71
215
  // The public materializer API does not forward one, and a warning nobody
@@ -74,6 +218,14 @@ reporter = consoleReporter) {
74
218
  const indexPath = join(libraryPath, "index.json");
75
219
  const destination = JSON.parse(await readFile(indexPath, "utf8"));
76
220
  const overlay = JSON.parse(await readFile(join(overlayPath, "index.json"), "utf8"));
221
+ const corrections = new Map((await readOverlayCorrections(overlayPath)).map((correction) => [correction.path, correction]));
222
+ const recordPath = join(libraryPath, RECORD_FILE);
223
+ const previous = (await exists(recordPath))
224
+ ? (await readJson(recordPath))
225
+ : {};
226
+ const previouslyAdded = new Set(previous.added ?? []);
227
+ const previousDigests = previous.correctedSha256 ?? {};
228
+ const digests = {};
77
229
  const entries = destination.entries ?? [];
78
230
  // Every path the library resolves each identity to. A `GenericExpanded` or
79
231
  // per-object entry shares its nodeType with its siblings, so an identity
@@ -91,46 +243,110 @@ reporter = consoleReporter) {
91
243
  }
92
244
  const additions = [];
93
245
  const skipped = new Set();
246
+ const added = [];
247
+ const corrected = [];
248
+ const kept = [];
249
+ const toCopy = new Set();
250
+ // Every entry file and sidecar the overlay's index accounts for; anything
251
+ // else in the directory is a side-table or the corrections ledger.
252
+ const rowFiles = new Set();
253
+ let written = 0;
94
254
  for (const row of overlay.entries ?? []) {
255
+ if (typeof row.path !== "string")
256
+ continue;
257
+ rowFiles.add(row.path);
258
+ rowFiles.add(sidecarOf(row.path));
95
259
  const identity = entryIdentity(row);
96
- if (!resolvedPaths.has(identity)) {
260
+ const correction = corrections.get(row.path);
261
+ const paths = resolvedPaths.get(identity);
262
+ if (paths === undefined) {
97
263
  additions.push(row);
264
+ added.push(row.path);
265
+ toCopy.add(row.path);
266
+ toCopy.add(sidecarOf(row.path));
98
267
  continue;
99
268
  }
100
- const paths = resolvedPaths.get(identity);
101
- if (typeof row.path !== "string" || paths.includes(row.path))
269
+ if (!paths.includes(row.path)) {
270
+ skipped.add(row.path);
271
+ if (correction) {
272
+ reporter.warn([
273
+ ` WARN: curated overlay correction NOT applied: ${row.path} (${correction.since})`,
274
+ ` ${String(row.nodeType)}@${String(row.version)} is in the library's index at`,
275
+ ...paths.map((path) => ` ${path}`),
276
+ " instead, so the library's own definition stands without this correction.",
277
+ ` Re-verify it there and update ${CORRECTIONS_FILE}.`,
278
+ ].join("\n"));
279
+ }
102
280
  continue;
103
- skipped.add(row.path);
104
- reporter.warn([
105
- ` WARN: curated overlay SKIPPED ${row.path}`,
106
- ` ${String(row.nodeType)}@${String(row.version)} is already in the library's index, at`,
107
- ...paths.map((path) => ` ${path}`),
108
- " so the copied file would resolve for nobody. The library's own definition stands and",
109
- " this correction does NOT take effect. Re-verify it against the current library and drop",
110
- " its row from typescript/sdk/lib/library-json.samples/index.json once the library carries it.",
111
- ].join("\n"));
112
- }
113
- // A skipped entry's sidecar goes with it: it is only ever read beside the
114
- // entry file, so leaving it behind would state a correction nothing reads.
115
- const notCopied = new Set(["index.json"]);
116
- for (const rel of skipped) {
117
- notCopied.add(rel);
118
- notCopied.add(sidecarOf(rel));
281
+ }
282
+ if (!correction) {
283
+ // A row this overlay added on an earlier application is still its own.
284
+ (previouslyAdded.has(row.path) ? added : kept).push(row.path);
285
+ continue;
286
+ }
287
+ // Our own earlier patch, as long as neither file changed since: a refreshed
288
+ // entry (an `unzip -o` over the same directory, say) is patched again.
289
+ const digest = await correctionDigest(libraryPath, row.path);
290
+ if (previousDigests[row.path] === digest) {
291
+ corrected.push(row.path);
292
+ digests[row.path] = digest;
293
+ continue;
294
+ }
295
+ const patched = await patchCorrection(libraryPath, overlayPath, correction);
296
+ if (patched.added.length === 0) {
297
+ kept.push(row.path);
298
+ reporter.warn([
299
+ ` WARN: curated overlay correction no longer needed: ${row.path} (${correction.since})`,
300
+ " The published definition already carries every field and key it adds.",
301
+ ` Remove its row from typescript/sdk/lib/library-json.samples/${CORRECTIONS_FILE}.`,
302
+ ].join("\n"));
303
+ continue;
304
+ }
305
+ corrected.push(row.path);
306
+ digests[row.path] = await correctionDigest(libraryPath, row.path);
307
+ written += patched.files;
119
308
  }
120
309
  const overlayFiles = await jsonFiles(overlayPath);
121
- let copied = 0;
122
310
  for (const source of overlayFiles) {
123
311
  const rel = relative(overlayPath, source).split(sep).join("/");
124
- if (notCopied.has(rel))
312
+ if (rel === "index.json" || rel === CORRECTIONS_FILE)
313
+ continue;
314
+ const accounted = rowFiles.has(rel);
315
+ // An entry or sidecar goes only where its row decided; a side-table always;
316
+ // any other file only into a library that lacks it, so a stray file here
317
+ // can never replace a published one either.
318
+ if (accounted && !toCopy.has(rel))
319
+ continue;
320
+ if (!accounted && !SIDE_TABLES.has(rel) && (await exists(join(libraryPath, rel))))
125
321
  continue;
126
322
  const target = join(libraryPath, rel);
127
323
  await mkdir(dirname(target), { recursive: true });
128
324
  await cp(source, target);
129
- copied += 1;
325
+ written += 1;
130
326
  }
131
327
  if (additions.length > 0) {
132
328
  destination.entries = [...entries, ...additions];
133
329
  writeJsonFile(indexPath, destination);
134
330
  }
135
- return { files: copied, entries: additions.length, skipped: sortedStrings([...skipped]) };
331
+ const result = {
332
+ files: written,
333
+ entries: additions.length,
334
+ skipped: sortedStrings([...skipped]),
335
+ added: sortedStrings(added),
336
+ corrected: sortedStrings(corrected),
337
+ kept: sortedStrings(kept),
338
+ };
339
+ writeJsonFile(recordPath, {
340
+ $comment: "What the SDK's curated overlay did to this library (registry pull). See curated-overlay.ts.",
341
+ added: result.added,
342
+ corrected: result.corrected,
343
+ correctedSha256: digests,
344
+ kept: result.kept,
345
+ skipped: result.skipped,
346
+ });
347
+ reporter.info(`curated overlay: added ${result.added.length} definition(s) the library lacks, ` +
348
+ `corrected ${result.corrected.length}` +
349
+ (result.corrected.length ? ` (${result.corrected.join(", ")})` : "") +
350
+ `, kept ${result.kept.length + result.skipped.length} published definition(s) unchanged.`);
351
+ return result;
136
352
  }
@@ -35,10 +35,12 @@ import { sortedStrings } from "./compat/compare.js";
35
35
  * `list-envelopes.json` is the LIST-ENVELOPE LEDGER (a side-table keyed by
36
36
  * nodeType, flow-builder-sdk#156); `invariant-collections.json` is the second
37
37
  * such side-table, keyed by a reference's `objectName` instead
38
- * (CONNECTOR_LOOKUP_DESIGN.md §5.3). Asking either for an index row is the
39
- * wrong question. Dotfiles are excluded separately — see {@link isDotfile}.
38
+ * (CONNECTOR_LOOKUP_DESIGN.md §5.3). `overlay-corrections.json` is the
39
+ * curated overlay's ledger of deliberate patches (#1148); the overlay never
40
+ * copies it, but a test library copied from the samples carries it. Asking any
41
+ * of them for an index row is the wrong question. Dotfiles are excluded separately — see {@link isDotfile}.
40
42
  */
41
- const SIDE_TABLES = new Set(["index.json", "list-envelopes.json", "invariant-collections.json"]);
43
+ const SIDE_TABLES = new Set(["index.json", "list-envelopes.json", "invariant-collections.json", "overlay-corrections.json"]);
42
44
  /**
43
45
  * A dotfile is never an operation entry, so it can never have an index row.
44
46
  *
@@ -133,7 +133,7 @@
133
133
  "connectorKey": "uipath-microsoft-outlook365",
134
134
  "label": "Send Email",
135
135
  "activityType": "Curated",
136
- "objectName": "send-mail",
136
+ "objectName": "send-mail-v2",
137
137
  "path": "uipath-microsoft-outlook365/send-email@1.0.0.json"
138
138
  },
139
139
  {
@@ -0,0 +1,43 @@
1
+ {
2
+ "schemaVersion": "1",
3
+ "$comment": [
4
+ "THE CORRECTIONS LEDGER: which files in this directory deliberately patch a published definition.",
5
+ "",
6
+ "This directory has two jobs. It is the small connector library the tests and the examples read,",
7
+ "and it is the curated overlay `registry pull` applies to a freshly downloaded library. Only the",
8
+ "second job can damage a user's library, and only through the rows listed here.",
9
+ "",
10
+ "THE RULE (`src/registry/curated-overlay.ts`). An overlay entry whose (nodeType, version) the",
11
+ "downloaded library does NOT carry is added, with its index row. An entry the library DOES carry is",
12
+ "left alone: the published definition wins, because a copy here is a snapshot that goes stale the",
13
+ "day the library is republished (builder-sdk#1148: nine 2026-07 snapshots were replacing newer",
14
+ "published definitions, Outlook `send-email` among them). The one exception is a row in",
15
+ "`corrections` below, and a correction is a PATCH, never a replacement: it adds the named input",
16
+ "fields and sidecar keys the published definition lacks, taking them from this directory's copy,",
17
+ "and touches nothing else. It cannot change `operation.objectName` or the endpoint, so the index",
18
+ "row and the entry file keep agreeing.",
19
+ "",
20
+ "A correction the published library has caught up with (it already has every field and key) is",
21
+ "reported as no longer needed. Remove its row here once a published build carries it.",
22
+ "",
23
+ "FIELDS. `path`: the entry file, as in index.json. `since`: the change that made it. `reason`:",
24
+ "why the published definition is wrong. `inputFields`: names added to `inputSchema.fields` (entry)",
25
+ "and `inputDefinition.fields` (sidecar) when absent. `sidecarKeys`: top-level sidecar keys added",
26
+ "when absent."
27
+ ],
28
+ "corrections": [
29
+ {
30
+ "path": "uipath-atlassian-jira/create-issue@1.0.0.json",
31
+ "since": "builder-sdk#108",
32
+ "reason": "Jira refuses a create without fields.summary (400). The published definition leaves the body fields to design-time schema generation, so a flow authored without `registry prepare` could not set summary or description, and nothing typed the created issue's key.",
33
+ "inputFields": [
34
+ "fields.summary",
35
+ "fields.description",
36
+ "key"
37
+ ],
38
+ "sidecarKeys": [
39
+ "outputResponseDefinition"
40
+ ]
41
+ }
42
+ ]
43
+ }
@@ -10,23 +10,66 @@
10
10
  ],
11
11
  "connector": {
12
12
  "key": "uipath-microsoft-outlook365",
13
- "name": "Microsoft Outlook 365"
13
+ "name": "Microsoft Outlook 365",
14
+ "tier": "1",
15
+ "discovery": {
16
+ "type": "static",
17
+ "hasObjectDiscovery": false,
18
+ "hasFieldDiscovery": false,
19
+ "hasCustomObjectDiscovery": false,
20
+ "hasCustomFieldDiscovery": false
21
+ }
14
22
  },
15
23
  "operation": {
16
24
  "name": "Create",
17
- "objectName": "send-mail",
25
+ "objectName": "send-mail-v2",
18
26
  "objectDisplayName": "Send Email",
19
27
  "httpMethod": "POST",
20
- "path": "/hubs/productivity/send-mail",
28
+ "path": "/hubs/productivity/send-mail-v2",
21
29
  "subType": "standard",
22
30
  "supportsStreaming": false,
23
31
  "activityType": "Curated",
24
- "pathTemplate": "/hubs/productivity/send-mail",
25
- "parameters": []
32
+ "pathTemplate": "/hubs/productivity/send-mail-v2",
33
+ "parameters": [
34
+ {
35
+ "name": "saveAsDraft",
36
+ "type": "query",
37
+ "dataType": "boolean",
38
+ "required": false,
39
+ "displayName": "Save as draft",
40
+ "description": "Send an email message. By default, the email will be saved as draft.",
41
+ "defaultValue": true
42
+ },
43
+ {
44
+ "name": "includeMessageDetails",
45
+ "type": "query",
46
+ "dataType": "boolean",
47
+ "required": false,
48
+ "displayName": "Include message details",
49
+ "description": "Returns the sent message's identifiers (message ID, conversation ID, internet message ID, status) when set to true. If set to false, response only contains status.",
50
+ "defaultValue": false
51
+ },
52
+ {
53
+ "name": "file",
54
+ "type": "multipart",
55
+ "dataType": "file",
56
+ "required": false,
57
+ "displayName": "Attachment",
58
+ "description": "The file to attach to the email"
59
+ },
60
+ {
61
+ "name": "body",
62
+ "type": "multipart",
63
+ "dataType": "string",
64
+ "required": false,
65
+ "displayName": "Body",
66
+ "description": "The object body"
67
+ }
68
+ ]
26
69
  },
27
70
  "display": {
28
71
  "label": "Send Email",
29
- "description": "(Microsoft Outlook 365) Send an email message based on the input parameters",
72
+ "description": "Send an email message based on the input parameters",
30
73
  "icon": "https://alpha.uipath.com/311c6049-4966-4ef2-a6f3-8ceccaf38d54/studio_/typecache/icons/160499b52f7cea190b530fe6f40da7ee03455a8d3fb72dd7f4f68d61e7865a3a.svg",
31
74
  "iconBackground": "",
32
75
  "iconBackgroundDark": "",
@@ -7,13 +7,14 @@
7
7
  "activity",
8
8
  "API.File.Upload.Optional"
9
9
  ],
10
- "sortOrder": 515,
10
+ "sortOrder": 520,
11
11
  "supportsErrorHandling": true,
12
- "description": "(Microsoft Outlook 365) Send an email message based on the input parameters",
12
+ "description": "Send an email message based on the input parameters",
13
13
  "display": {
14
14
  "label": "Send Email",
15
- "description": "(Microsoft Outlook 365) Send an email message based on the input parameters",
16
- "icon": "https://alpha.uipath.com/311c6049-4966-4ef2-a6f3-8ceccaf38d54/studio_/typecache/icons/160499b52f7cea190b530fe6f40da7ee03455a8d3fb72dd7f4f68d61e7865a3a.svg"
15
+ "description": "Send an email message based on the input parameters",
16
+ "icon": "https://alpha.uipath.com/311c6049-4966-4ef2-a6f3-8ceccaf38d54/studio_/typecache/icons/160499b52f7cea190b530fe6f40da7ee03455a8d3fb72dd7f4f68d61e7865a3a.svg",
17
+ "sourceDisplayName": "Microsoft Outlook 365"
17
18
  },
18
19
  "handleConfiguration": [
19
20
  {
@@ -78,7 +79,7 @@
78
79
  },
79
80
  {
80
81
  "name": "objectName",
81
- "value": "send-mail",
82
+ "value": "send-mail-v2",
82
83
  "type": "string"
83
84
  },
84
85
  {
@@ -138,11 +139,11 @@
138
139
  "packageId": "196656",
139
140
  "svgIconUrl": "icons/160499b52f7cea190b530fe6f40da7ee03455a8d3fb72dd7f4f68d61e7865a3a.svg",
140
141
  "displayName": "Send Email",
141
- "assemblyQualifiedName": "UiPath.IntegrationService.Activities.Runtime.Activities.ConnectorActivity, UiPath.IntegrationService.Activities.Runtime, Version=1.30.0.0, Culture=neutral, PublicKeyToken=null",
142
+ "assemblyQualifiedName": "UiPath.IntegrationService.Activities.Runtime.Activities.ConnectorActivity, UiPath.IntegrationService.Activities.Runtime, Version=1.34.0.0, Culture=neutral, PublicKeyToken=null",
142
143
  "description": "Send an email message based on the input parameters",
143
144
  "category": "",
144
145
  "activityColor": "#E56D5C",
145
- "configuration": "{\"connectorKey\":\"uipath-microsoft-outlook365\",\"objectName\":\"send-mail\",\"httpMethod\":\"POST\",\"activityType\":\"Curated\",\"version\":\"1.0.0\",\"supportsStreaming\":false,\"subType\":\"standard\"}",
146
+ "configuration": "{\"connectorKey\":\"uipath-microsoft-outlook365\",\"objectName\":\"send-mail-v2\",\"httpMethod\":\"POST\",\"activityType\":\"Curated\",\"version\":\"1.0.0\",\"supportsStreaming\":false,\"subType\":\"standard\"}",
146
147
  "uiPathActivityTypeId": "c7ce0a96-2091-3d94-b16f-706ebb1eb351",
147
148
  "isExperimental": false,
148
149
  "helpUrlTemplate": "https://docs.uipath.com/{0}/activities/other/latest/integration-service/uipath-microsoft-outlook365-Send-Email",
@@ -165,89 +166,71 @@
165
166
  "fields": [
166
167
  {
167
168
  "name": "message.importance",
168
- "displayName": "Importance",
169
169
  "type": "string",
170
- "dataType": "string",
171
- "required": false,
170
+ "displayName": "Importance",
172
171
  "description": "The importance of the email",
173
172
  "enum": [
174
- {
175
- "name": "High",
176
- "value": "high"
177
- },
178
- {
179
- "name": "Normal",
180
- "value": "normal"
181
- },
182
- {
183
- "name": "Low",
184
- "value": "low"
185
- }
186
- ]
173
+ "high",
174
+ "normal",
175
+ "low"
176
+ ],
177
+ "required": false
187
178
  },
188
179
  {
189
180
  "name": "message.ccRecipients",
190
- "displayName": "CC",
191
181
  "type": "string",
192
- "dataType": "string",
193
- "required": false,
194
- "description": "The secondary recipients of the email, separated by comma (,)"
182
+ "displayName": "CC",
183
+ "description": "The secondary recipients of the email, separated by comma (,)",
184
+ "required": false
195
185
  },
196
186
  {
197
187
  "name": "message.body.contentType",
198
- "displayName": "Message body content type",
199
188
  "type": "string",
200
- "dataType": "string",
201
- "required": false,
202
- "description": "Specifies the format of the email body, such as plain text or HTML."
189
+ "displayName": "Message body content type",
190
+ "description": "Specifies the format of the email body, such as plain text or HTML.",
191
+ "required": false
203
192
  },
204
193
  {
205
194
  "name": "message.bccRecipients",
206
- "displayName": "BCC",
207
195
  "type": "string",
208
- "dataType": "string",
209
- "required": false,
210
- "description": "The hidden recipients of the email, separated by comma (,)"
196
+ "displayName": "BCC",
197
+ "description": "The hidden recipients of the email, separated by comma (,)",
198
+ "required": false
211
199
  },
212
200
  {
213
201
  "name": "message.body.content",
214
- "displayName": "Body",
215
202
  "type": "string",
216
- "dataType": "string",
217
- "required": false,
218
- "description": "The body of the email"
203
+ "displayName": "Body",
204
+ "description": "The body of the email",
205
+ "required": false
219
206
  },
220
207
  {
221
208
  "name": "saveToSentItems",
222
- "displayName": "Save to sent items",
223
209
  "type": "boolean",
224
- "dataType": "boolean",
225
- "required": false,
226
- "description": "Indicates if the email should be saved in the sent items folder."
210
+ "displayName": "Save to sent items",
211
+ "description": "Indicates if the email should be saved in the sent items folder.",
212
+ "required": false
227
213
  },
228
214
  {
229
215
  "name": "message.toRecipients",
230
- "displayName": "To",
231
216
  "type": "string",
232
- "dataType": "string",
233
- "required": true,
234
- "description": "The main recipients of the email, separated by comma(,)"
217
+ "displayName": "To",
218
+ "description": "The main recipients of the email, separated by comma(,)",
219
+ "required": true
235
220
  },
236
221
  {
237
222
  "name": "message.subject",
238
- "displayName": "Subject",
239
223
  "type": "string",
240
- "dataType": "string",
241
- "required": false,
242
- "description": "The subject of the email"
224
+ "displayName": "Subject",
225
+ "description": "The subject of the email",
226
+ "required": false
243
227
  },
244
228
  {
245
229
  "name": "message.replyTo",
246
- "displayName": "Reply to",
247
230
  "type": "string",
248
- "dataType": "string",
249
- "required": false,
250
- "description": "The email addresses to use when replying, separated by comma (,)"
231
+ "displayName": "Reply to",
232
+ "description": "The email addresses to use when replying, separated by comma (,)",
233
+ "required": false
251
234
  }
252
235
  ]
253
236
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.31.3",
3
+ "version": "6.31.5",
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": "312d113c6d6ed072f209ca7faab413317d551880"
96
+ "gitref": "19700f105da9eae840ec05cd3796c28a96f1a4ed"
97
97
  }