@uipath/maestro-builder-sdk 6.53.2 → 6.54.1

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.
@@ -595,9 +595,16 @@ export interface TaskInputBinding {
595
595
  export interface TaskOutputBinding {
596
596
  /** The case-variable name the field feeds (read downstream as `=vars.<var>`). */
597
597
  var: string;
598
- /** The source field expression, e.g. `=response` or `=Error.Message`. */
598
+ /** The source field expression, e.g. `=response` or `=Error.Message`. For a `custom` output, the value assigned instead. */
599
599
  source: string;
600
600
  type: TypeDesc;
601
+ /**
602
+ * A custom assignment output, written `custom: true` with no `id`, the way
603
+ * Studio Web saves one: when the task completes, `var` is set to `source` (a
604
+ * literal, `=vars.<name>`, or a `=js:` expression), not to a field of the
605
+ * task's result. Set by `.outputs({ <var>: { assign } })`.
606
+ */
607
+ custom?: boolean;
601
608
  }
602
609
  export interface BuiltTask {
603
610
  /** Task key + display name (also the handle used by `selected-tasks-completed`). */
@@ -1131,12 +1138,38 @@ declare class TaskBuilder {
1131
1138
  * task's own (bare) output, with no root entry — until a second task writes
1132
1139
  * the same variable, which makes both reassignments.
1133
1140
  *
1134
- * @param shape - Case-variable names to the result field they take, or `{ source, type }`.
1141
+ * Pass `{ assign, type }` for a **custom** output: the SDD's `x = expr` row.
1142
+ * When the task completes, the case variable is set to `assign` — a literal,
1143
+ * `=vars.<name>`, or a `=js:` expression — instead of a result field. It is
1144
+ * written as Studio Web saves one: `custom: true`, `elementId: 'root'`, no
1145
+ * `id`. The SDD notation `=metadata.<field>` reads nothing outside `=js:`, so it
1146
+ * is written as `=js:metadata.<field>`. An SDD literal copied with its quotes
1147
+ * (`{ assign: '"literal-assigned"' }`) loses them, as `sdd convert` reads it.
1148
+ * A custom output sees only the outputs listed before it on the same task:
1149
+ * outputs are evaluated in order, so it cannot read its own variable or a
1150
+ * later output's (`check` reports `OUTPUT_SELF_REFERENCE` /
1151
+ * `OUTPUT_FORWARD_READ`).
1152
+ *
1153
+ * @example
1154
+ * **Extract a result field and assign two custom outputs**
1155
+ * ```ts
1156
+ * casePlan('echo').stage('Echo', (s) => s.task('Echo', (t) =>
1157
+ * t.apiWorkflow('Echo', { folder: 'Shared' }).outputs({
1158
+ * renamedResult: '=APIOutput1',
1159
+ * literalResult: { assign: 'literal-assigned' },
1160
+ * resultCopy: { assign: "=js:vars.renamedResult + '-copy'" },
1161
+ * })))
1162
+ * ```
1163
+ *
1164
+ * @param shape - Case-variable names to the result field they take, `{ source, type }`, or a custom `{ assign, type }`.
1135
1165
  * @returns This builder, so calls chain.
1136
1166
  */
1137
1167
  outputs(shape: Record<string, string | {
1138
1168
  source: string;
1139
1169
  type?: TypeDesc;
1170
+ } | {
1171
+ assign: string;
1172
+ type?: TypeDesc;
1140
1173
  }>): this;
1141
1174
  /**
1142
1175
  * Add a task-entry condition. Pass a nested array for the complete OR-of-AND grid.
@@ -489,15 +489,48 @@ class TaskBuilder {
489
489
  * task's own (bare) output, with no root entry — until a second task writes
490
490
  * the same variable, which makes both reassignments.
491
491
  *
492
- * @param shape - Case-variable names to the result field they take, or `{ source, type }`.
492
+ * Pass `{ assign, type }` for a **custom** output: the SDD's `x = expr` row.
493
+ * When the task completes, the case variable is set to `assign` — a literal,
494
+ * `=vars.<name>`, or a `=js:` expression — instead of a result field. It is
495
+ * written as Studio Web saves one: `custom: true`, `elementId: 'root'`, no
496
+ * `id`. The SDD notation `=metadata.<field>` reads nothing outside `=js:`, so it
497
+ * is written as `=js:metadata.<field>`. An SDD literal copied with its quotes
498
+ * (`{ assign: '"literal-assigned"' }`) loses them, as `sdd convert` reads it.
499
+ * A custom output sees only the outputs listed before it on the same task:
500
+ * outputs are evaluated in order, so it cannot read its own variable or a
501
+ * later output's (`check` reports `OUTPUT_SELF_REFERENCE` /
502
+ * `OUTPUT_FORWARD_READ`).
503
+ *
504
+ * @example
505
+ * **Extract a result field and assign two custom outputs**
506
+ * ```ts
507
+ * casePlan('echo').stage('Echo', (s) => s.task('Echo', (t) =>
508
+ * t.apiWorkflow('Echo', { folder: 'Shared' }).outputs({
509
+ * renamedResult: '=APIOutput1',
510
+ * literalResult: { assign: 'literal-assigned' },
511
+ * resultCopy: { assign: "=js:vars.renamedResult + '-copy'" },
512
+ * })))
513
+ * ```
514
+ *
515
+ * @param shape - Case-variable names to the result field they take, `{ source, type }`, or a custom `{ assign, type }`.
493
516
  * @returns This builder, so calls chain.
494
517
  */
495
518
  outputs(shape) {
496
519
  this._outputs ??= [];
497
520
  for (const [name, v] of Object.entries(shape)) {
498
- const source = typeof v === 'string' ? v : v.source;
499
- const type = (typeof v === 'string' ? 'string' : (v.type ?? 'string'));
500
- this._outputs.push({ var: name, source, type });
521
+ if (typeof v === 'string') {
522
+ this._outputs.push({ var: name, source: v, type: 'string' });
523
+ continue;
524
+ }
525
+ const type = (v.type ?? 'string');
526
+ if ('assign' in v) {
527
+ if ('source' in v)
528
+ throw new Error(`task "${this._name}": output "${name}" sets both source and assign — use source to extract a result field, assign for a custom output.`);
529
+ this._outputs.push({ var: name, source: customOutputValue(v.assign), type, custom: true });
530
+ }
531
+ else {
532
+ this._outputs.push({ var: name, source: v.source, type });
533
+ }
501
534
  }
502
535
  return this;
503
536
  }
@@ -543,6 +576,41 @@ class TaskBuilder {
543
576
  };
544
577
  }
545
578
  }
579
+ /**
580
+ * A custom output's value as the runtime reads it. `=metadata.<field>` (the SDD
581
+ * notation) has no bare path prefix, so it resolves to nothing; only inside
582
+ * `=js:` is `metadata` in scope. The same narrow rewrite as `uip maestro case
583
+ * sdd convert` (`canonicalExpression`).
584
+ */
585
+ function customOutputValue(value) {
586
+ const unquoted = unquoteLiteral(value);
587
+ return unquoted.startsWith('=metadata.') ? `=js:${unquoted.slice(1)}` : unquoted;
588
+ }
589
+ /**
590
+ * An SDD cell's quoted literal without its quotes, as the CLI's `sdd convert`
591
+ * reads it (`unquoteLiteral` in `packages/case-tool/src/services/sdd-convert/io.ts`):
592
+ * `"literal-assigned"` → `literal-assigned`, with escaped quotes unescaped.
593
+ * A value not wholly wrapped in one pair of `"…"` or `'…'` (an unescaped quote
594
+ * inside, or an expression like `'a' + 'b'`) is returned unchanged.
595
+ */
596
+ function unquoteLiteral(cell) {
597
+ const text = cell.trim();
598
+ const quote = text[0];
599
+ if (quote !== '"' && quote !== "'")
600
+ return cell;
601
+ if (text.length < 2 || text[text.length - 1] !== quote)
602
+ return cell;
603
+ const inner = text.slice(1, -1);
604
+ for (let at = 0; at < inner.length; at += 1) {
605
+ if (inner[at] === '\\') {
606
+ at += 1;
607
+ continue;
608
+ }
609
+ if (inner[at] === quote)
610
+ return cell;
611
+ }
612
+ return inner.replaceAll(`\\${quote}`, quote);
613
+ }
546
614
  class StageBuilder {
547
615
  _label;
548
616
  _exception;
@@ -539,6 +539,32 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
539
539
  if (isResourceLabelRefusal(resolved))
540
540
  diags.push({ level: 'error', code: resolved.code, message: resolved.message, where: t.name });
541
541
  }
542
+ // A custom row (`{ assign }`) is evaluated in array order, after the rows
543
+ // before it on the same task: it cannot read itself or a later row's
544
+ // variable (it would see the old value). `uip maestro case validate
545
+ // --strict` reports these as STRICT_OUTPUT_SELF_REFERENCE /
546
+ // STRICT_OUTPUT_FORWARD_READ; they need only this task, so check says so.
547
+ (t.outputs ?? []).forEach((o, i) => {
548
+ if (!o.custom || !o.source.startsWith('='))
549
+ return;
550
+ const later = new Set((t.outputs ?? []).slice(i + 1).map((row) => row.var));
551
+ for (const name of varsRead(o.source)) {
552
+ if (name === o.var)
553
+ diags.push({
554
+ level: 'error',
555
+ code: 'OUTPUT_SELF_REFERENCE',
556
+ message: `Task "${t.name}" custom output "${o.var}" reads vars.${name}, its own variable. A custom output is set once when the task completes; read another variable, or compute the value from the task's result.`,
557
+ where: t.name,
558
+ });
559
+ else if (later.has(name))
560
+ diags.push({
561
+ level: 'error',
562
+ code: 'OUTPUT_FORWARD_READ',
563
+ message: `Task "${t.name}" custom output "${o.var}" reads vars.${name}, which a later output on the same task writes. Outputs are evaluated in the order listed, so it would read the old value. List "${name}" before "${o.var}".`,
564
+ where: t.name,
565
+ });
566
+ }
567
+ });
542
568
  if (t.kind === 'business-rule') {
543
569
  if (!t.ref || !t.ref.name.trim() || !t.ref.folderPath.trim())
544
570
  diags.push({
@@ -548,6 +574,8 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
548
574
  where: t.name,
549
575
  });
550
576
  for (const o of t.outputs ?? []) {
577
+ if (o.custom)
578
+ continue; // an assigned value, not a result field
551
579
  const wrapped = /^=\s*(result|output)\./.exec(o.source);
552
580
  if (wrapped)
553
581
  diags.push({
@@ -666,3 +694,10 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
666
694
  });
667
695
  }
668
696
  }
697
+ /** The case variables an expression reads as `vars.<name>` or `vars["<name>"]`. */
698
+ function varsRead(expression) {
699
+ const names = new Set();
700
+ for (const m of expression.matchAll(/\bvars\.([A-Za-z_$][\w$]*)|\bvars\[\s*(['"])(.+?)\2\s*\]/g))
701
+ names.add(m[1] ?? m[3]);
702
+ return names;
703
+ }
@@ -806,9 +806,14 @@ function ioArg(rows, kind) {
806
806
  const entries = rows.map((row) => {
807
807
  const name = kind === 'inputs' ? row.name : row.var;
808
808
  const value = tsLiteral(row[valueField] ?? '');
809
- const cell = row.type && row.type !== 'string'
810
- ? `{ ${valueField}: ${value}, type: ${tsLiteral(row.type)} }`
811
- : value;
809
+ // A custom output (`custom: true`) assigns its value; it reads no result field.
810
+ const custom = kind === 'outputs' && row.custom === true;
811
+ const typed = row.type && row.type !== 'string' ? `, type: ${tsLiteral(row.type)}` : '';
812
+ const cell = custom
813
+ ? `{ assign: ${value}${typed} }`
814
+ : typed
815
+ ? `{ ${valueField}: ${value}${typed} }`
816
+ : value;
812
817
  return `${key(String(name ?? ''))}: ${cell}`;
813
818
  });
814
819
  return `{ ${entries.join(', ')} }`;
@@ -1258,6 +1258,8 @@ const OWN_NAME_OUTPUT_ROWS = new WeakSet();
1258
1258
  * `var` still names the shared case variable.
1259
1259
  */
1260
1260
  function emitTaskOutput(o, elementId, ids, seed, resolver) {
1261
+ if (o.custom)
1262
+ return emitCustomOutput(o, resolver);
1261
1263
  const path = o.source.replace(/^=/, '');
1262
1264
  const field = path.split('.').pop() || o.source;
1263
1265
  // A reassigned row registers the readable companion once (declared vars take
@@ -1291,6 +1293,20 @@ function emitTaskOutput(o, elementId, ids, seed, resolver) {
1291
1293
  elementId,
1292
1294
  };
1293
1295
  }
1296
+ /**
1297
+ * A custom assignment output (`.outputs({ x: { assign } })`), as Studio Web and
1298
+ * `uip maestro case sdd convert` (`customOutputFor`) write one: no `id` and no
1299
+ * `originalVar` (it reads no result field), `value` and `source` both the
1300
+ * assigned value, an empty `target` and `body`, and `elementId: 'root'`:
1301
+ * { name: 'literalResult', type: 'string', custom: true, var: 'literalResult',
1302
+ * value: 'literal-assigned', source: 'literal-assigned', target: '', body: '', elementId: 'root' }
1303
+ * Like an extract, it registers the root companion so `=vars.<var>` resolves.
1304
+ */
1305
+ function emitCustomOutput(o, resolver) {
1306
+ if (!resolver.varCompanions.has(o.var))
1307
+ resolver.varCompanions.set(o.var, { id: o.var, name: o.var, type: o.type, elementId: 'root', custom: true });
1308
+ return { name: o.var, type: o.type, custom: true, var: o.var, value: o.source, source: o.source, target: '', body: '', elementId: 'root' };
1309
+ }
1294
1310
  // ─── Business-rule task ───────────────────────────────────────────────────────
1295
1311
  /** The version a business-rule task carries: below v3 the engine cannot run the
1296
1312
  * `.uirule` package a BusinessRules project deploys (the case converter falls
@@ -1707,6 +1723,8 @@ function claimOutputId(resolver, base) {
1707
1723
  }
1708
1724
  /** Whether a task output is written in the own-name shape (see `emitTaskOutput`). */
1709
1725
  function isOwnNameOutput(o) {
1726
+ if (o.custom)
1727
+ return false;
1710
1728
  const path = o.source.replace(/^=/, '');
1711
1729
  return !path.includes('.') && keepsOwnName(o.var, path);
1712
1730
  }
@@ -78,6 +78,30 @@ export declare function readOverlayCorrections(overlayPath: string): Promise<Ove
78
78
  * What was done is reported in one line, and recorded in `.curated-overlay.json`
79
79
  * beside the index, so a second application recognises its own patches: the
80
80
  * record keeps a sha256 of each patched entry and sidecar, and a correction
81
- * whose files no longer match it is patched again.
81
+ * whose files no longer match it is patched again. The record also names the
82
+ * overlay that wrote it ({@link curatedOverlayIdentity}). Under a different
83
+ * overlay, such as a newer SDK pulling a published archive, every correction
84
+ * is patched again, so fields a newer correction lists are added.
82
85
  */
83
86
  export declare function applyCuratedOverlay(libraryPath: string, overlayPath?: string, reporter?: Reporter): Promise<CuratedOverlayResult>;
87
+ /** What {@link checkCuratedOverlay} found, one line per item. */
88
+ export interface CuratedOverlayCheck {
89
+ /** Parts the overlay adds that are absent: the overlay was not applied, or broke. */
90
+ missing: string[];
91
+ /**
92
+ * Corrections whose target the library no longer serves at the correction's
93
+ * path, or serves without the fields the patch adds: the operation moved,
94
+ * was renamed or re-versioned. `registry pull` only warns about these too.
95
+ */
96
+ staleCorrections: string[];
97
+ }
98
+ /**
99
+ * Check `libraryPath` against the curated overlay. Both lists are empty once
100
+ * {@link applyCuratedOverlay} has run on it, unless a correction went stale.
101
+ * Every overlay identity that is not a correction must be indexed at a file
102
+ * that exists, and each side-table must equal the bundled copy.
103
+ *
104
+ * The connector library pipeline runs this on the archive it publishes,
105
+ * because readers that load that archive directly apply no overlay.
106
+ */
107
+ export declare function checkCuratedOverlay(libraryPath: string, overlayPath?: string): Promise<CuratedOverlayCheck>;
@@ -34,6 +34,25 @@ export async function curatedOverlayIdentity(overlayPath = bundledOverlayPath())
34
34
  function entryIdentity(entry) {
35
35
  return `${String(entry.nodeType)}\0${String(entry.version)}`;
36
36
  }
37
+ /**
38
+ * Every path the library resolves each identity to. A `GenericExpanded` or
39
+ * per-object entry shares its nodeType with its siblings, so an identity
40
+ * legitimately has more than one.
41
+ */
42
+ function pathsByIdentity(entries) {
43
+ const resolvedPaths = new Map();
44
+ for (const entry of entries) {
45
+ if (typeof entry.path !== "string")
46
+ continue;
47
+ const identity = entryIdentity(entry);
48
+ const known = resolvedPaths.get(identity);
49
+ if (known === undefined)
50
+ resolvedPaths.set(identity, [entry.path]);
51
+ else
52
+ known.push(entry.path);
53
+ }
54
+ return resolvedPaths;
55
+ }
37
56
  /** The ledger of deliberate patches; read here, never copied into a library. */
38
57
  export const CORRECTIONS_FILE = "overlay-corrections.json";
39
58
  /**
@@ -209,7 +228,10 @@ async function patchCorrection(libraryPath, overlayPath, correction) {
209
228
  * What was done is reported in one line, and recorded in `.curated-overlay.json`
210
229
  * beside the index, so a second application recognises its own patches: the
211
230
  * record keeps a sha256 of each patched entry and sidecar, and a correction
212
- * whose files no longer match it is patched again.
231
+ * whose files no longer match it is patched again. The record also names the
232
+ * overlay that wrote it ({@link curatedOverlayIdentity}). Under a different
233
+ * overlay, such as a newer SDK pulling a published archive, every correction
234
+ * is patched again, so fields a newer correction lists are added.
213
235
  */
214
236
  export async function applyCuratedOverlay(libraryPath, overlayPath = bundledOverlayPath(),
215
237
  // The public materializer API does not forward one, and a warning nobody
@@ -223,24 +245,13 @@ reporter = consoleReporter) {
223
245
  const previous = (await exists(recordPath))
224
246
  ? (await readJson(recordPath))
225
247
  : {};
248
+ const overlayIdentity = await curatedOverlayIdentity(overlayPath);
249
+ const sameOverlay = previous.overlayIdentity === overlayIdentity;
226
250
  const previouslyAdded = new Set(previous.added ?? []);
227
251
  const previousDigests = previous.correctedSha256 ?? {};
228
252
  const digests = {};
229
253
  const entries = destination.entries ?? [];
230
- // Every path the library resolves each identity to. A `GenericExpanded` or
231
- // per-object entry shares its nodeType with its siblings, so an identity
232
- // legitimately has more than one.
233
- const resolvedPaths = new Map();
234
- for (const entry of entries) {
235
- if (typeof entry.path !== "string")
236
- continue;
237
- const identity = entryIdentity(entry);
238
- const known = resolvedPaths.get(identity);
239
- if (known === undefined)
240
- resolvedPaths.set(identity, [entry.path]);
241
- else
242
- known.push(entry.path);
243
- }
254
+ const resolvedPaths = pathsByIdentity(entries);
244
255
  const additions = [];
245
256
  const skipped = new Set();
246
257
  const added = [];
@@ -285,15 +296,18 @@ reporter = consoleReporter) {
285
296
  continue;
286
297
  }
287
298
  // 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.
299
+ // entry (an `unzip -o` over the same directory, say) is patched again. So
300
+ // is our patch under another overlay, whose correction may list more; when
301
+ // it lists nothing new, the patch is still ours, not upstream catching up.
289
302
  const digest = await correctionDigest(libraryPath, row.path);
290
- if (previousDigests[row.path] === digest) {
303
+ const ownPatch = previousDigests[row.path] === digest;
304
+ if (ownPatch && sameOverlay) {
291
305
  corrected.push(row.path);
292
306
  digests[row.path] = digest;
293
307
  continue;
294
308
  }
295
309
  const patched = await patchCorrection(libraryPath, overlayPath, correction);
296
- if (patched.added.length === 0) {
310
+ if (patched.added.length === 0 && !ownPatch) {
297
311
  kept.push(row.path);
298
312
  reporter.warn([
299
313
  ` WARN: curated overlay correction no longer needed: ${row.path} (${correction.since})`,
@@ -338,6 +352,7 @@ reporter = consoleReporter) {
338
352
  };
339
353
  writeJsonFile(recordPath, {
340
354
  $comment: "What the SDK's curated overlay did to this library (registry pull). See curated-overlay.ts.",
355
+ overlayIdentity,
341
356
  added: result.added,
342
357
  corrected: result.corrected,
343
358
  correctedSha256: digests,
@@ -350,3 +365,82 @@ reporter = consoleReporter) {
350
365
  `, kept ${result.kept.length + result.skipped.length} published definition(s) unchanged.`);
351
366
  return result;
352
367
  }
368
+ /**
369
+ * Check `libraryPath` against the curated overlay. Both lists are empty once
370
+ * {@link applyCuratedOverlay} has run on it, unless a correction went stale.
371
+ * Every overlay identity that is not a correction must be indexed at a file
372
+ * that exists, and each side-table must equal the bundled copy.
373
+ *
374
+ * The connector library pipeline runs this on the archive it publishes,
375
+ * because readers that load that archive directly apply no overlay.
376
+ */
377
+ export async function checkCuratedOverlay(libraryPath, overlayPath = bundledOverlayPath()) {
378
+ const library = (await readJson(join(libraryPath, "index.json")));
379
+ const overlay = (await readJson(join(overlayPath, "index.json")));
380
+ const corrections = await readOverlayCorrections(overlayPath);
381
+ const correctionPaths = new Set(corrections.map((correction) => correction.path));
382
+ const resolvedPaths = pathsByIdentity(library.entries ?? []);
383
+ const overlayRows = new Map();
384
+ const missing = [];
385
+ const staleCorrections = [];
386
+ for (const row of overlay.entries ?? []) {
387
+ if (typeof row.path !== "string")
388
+ continue;
389
+ overlayRows.set(row.path, row);
390
+ if (correctionPaths.has(row.path))
391
+ continue;
392
+ const name = `${String(row.nodeType)}@${String(row.version)}`;
393
+ const paths = resolvedPaths.get(entryIdentity(row));
394
+ if (paths === undefined) {
395
+ missing.push(`definition ${name}: no index row`);
396
+ continue;
397
+ }
398
+ for (const path of paths) {
399
+ if (!(await exists(join(libraryPath, path))))
400
+ missing.push(`definition ${name}: indexed at ${path}, which is absent`);
401
+ }
402
+ }
403
+ for (const correction of corrections) {
404
+ const where = `correction ${correction.path} (${correction.since})`;
405
+ const paths = resolvedPaths.get(entryIdentity(overlayRows.get(correction.path))) ?? [];
406
+ if (!paths.includes(correction.path)) {
407
+ staleCorrections.push(`${where}: the library has no definition at that path${paths.length ? ` (it indexes ${paths.join(", ")})` : ""}`);
408
+ continue;
409
+ }
410
+ if (!(await exists(join(libraryPath, correction.path)))) {
411
+ staleCorrections.push(`${where}: the library indexes that path, but the file is absent`);
412
+ continue;
413
+ }
414
+ const lacks = [];
415
+ const entry = (await readJson(join(libraryPath, correction.path)));
416
+ const entryFields = fieldNames(entry.inputSchema?.fields);
417
+ for (const field of correction.inputFields ?? []) {
418
+ if (!entryFields.has(field))
419
+ lacks.push(`inputSchema field ${field}`);
420
+ }
421
+ const sidecarPath = join(libraryPath, sidecarOf(correction.path));
422
+ if (await exists(sidecarPath)) {
423
+ const sidecar = sidecarBody(await readJson(sidecarPath)) ?? {};
424
+ const sidecarFields = fieldNames(sidecar.inputDefinition?.fields);
425
+ for (const field of correction.inputFields ?? []) {
426
+ if (!sidecarFields.has(field))
427
+ lacks.push(`sidecar inputDefinition field ${field}`);
428
+ }
429
+ for (const key of correction.sidecarKeys ?? []) {
430
+ if (!(key in sidecar))
431
+ lacks.push(`sidecar key ${key}`);
432
+ }
433
+ }
434
+ if (lacks.length > 0)
435
+ staleCorrections.push(`${where}: the definition lacks ${lacks.join(", ")}`);
436
+ }
437
+ for (const table of SIDE_TABLES) {
438
+ const target = join(libraryPath, table);
439
+ if (!(await exists(target)))
440
+ missing.push(`side-table ${table}: absent`);
441
+ else if (!(await readFile(target)).equals(await readFile(join(overlayPath, table)))) {
442
+ missing.push(`side-table ${table}: differs from the SDK's copy`);
443
+ }
444
+ }
445
+ return { missing, staleCorrections };
446
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.53.2",
3
+ "version": "6.54.1",
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",
@@ -96,5 +96,5 @@
96
96
  "@types/node": "^22.7.0",
97
97
  "esbuild": "^0.28.1"
98
98
  },
99
- "gitref": "7836e4e1acb3db20b1eef709958d4c02219f3a78"
99
+ "gitref": "5dfee12e621c69cfa2cf1338fc7df299768dbe16"
100
100
  }