@uipath/maestro-builder-sdk 6.50.0 → 6.50.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.
@@ -3,7 +3,8 @@
3
3
  * check-cli — fast static validation of <Name>.case.ts (no output file).
4
4
  *
5
5
  * Usage: node --experimental-strip-types check-cli.js <case.ts | BaseName> \
6
- * [--library <library-json dir>] [--connectors-local <overlay dir>] [--bindings <bindings.json>]
6
+ * [--library <library-json dir>] [--connectors-local <overlay dir>] [--bindings <bindings.json>] \
7
+ * [-o <the plan compile writes>]
7
8
  * Exits 0 when clean (warnings allowed), 1 on any error.
8
9
  *
9
10
  * The library is opened exactly as `compile-cli` opens it (`openCliLibrary`), so
@@ -14,19 +15,18 @@ import { resolveCaseFile, loadBuiltCase } from './load.js';
14
15
  import { check } from './check.js';
15
16
  import { caseCheckOpts } from './cli-library.js';
16
17
  import { openCliLibrary } from '../core/cli-library.js';
17
- import { Bindings } from '../core/bindings.js';
18
- import { defaultBindingsFile } from '../workdir.js';
19
- import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
18
+ import { caseCompileTarget } from './compile-cli.js';
19
+ import { runWhenInvokedDirectly } from '../cli-run.js';
20
20
  import { ensureTypeScriptRuntime } from '../node-runtime.js';
21
21
  /** Flags that take a value: their argument is never the source positional. */
22
- const VALUE_FLAGS = new Set(['--library', '--connectors-local', '--bindings']);
23
- const USAGE = 'usage: check <case.ts | BaseName> [--library <dir>] [--connectors-local <dir>] [--bindings <file>]';
22
+ const VALUE_FLAGS = new Set(['--library', '--connectors-local', '--bindings', '-o']);
23
+ const USAGE = 'usage: check <case.ts | BaseName> [--library <dir>] [--connectors-local <dir>] [--bindings <file>] [-o <plan>]';
24
24
  export async function run(argv) {
25
25
  if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
26
26
  console.error(USAGE);
27
27
  return 2;
28
28
  }
29
- const input = argv.find((a, i) => !a.startsWith('--') && !VALUE_FLAGS.has(argv[i - 1]));
29
+ const input = argv.find((a, i) => !a.startsWith('-') && !VALUE_FLAGS.has(argv[i - 1]));
30
30
  if (input === undefined) {
31
31
  console.error(USAGE);
32
32
  return 2;
@@ -34,8 +34,9 @@ export async function run(argv) {
34
34
  const file = resolveCaseFile(input);
35
35
  ensureTypeScriptRuntime();
36
36
  const built = await loadBuiltCase(file);
37
- // bindings.json as `case compile` reads it, for the binding-label rules (#1034).
38
- const bindings = new Bindings(flagValue(argv, '--bindings') ?? defaultBindingsFile());
37
+ // The bindings `case compile` reads, plan fallback included, for the
38
+ // binding-label rules (#1034): `-o` names the plan as it does for compile.
39
+ const { bindings } = caseCompileTarget(argv);
39
40
  const diags = check(built, caseCheckOpts(openCliLibrary(argv), bindings));
40
41
  reportDiagnostics(diags);
41
42
  for (const d of diags)
@@ -1,6 +1,27 @@
1
1
  #!/usr/bin/env node
2
+ import { Bindings } from '../core/bindings.js';
2
3
  import { type CliExitCode } from '../cli-run.js';
3
4
  export declare function run(argv: string[]): Promise<CliExitCode>;
5
+ /**
6
+ * The plan file `case compile` writes, and the bindings it resolves connection
7
+ * and folder labels against. `case check` reads the same pair, so the two agree
8
+ * on which labels are declared.
9
+ *
10
+ * The plan is `-o`, else the plan file the project in the working directory
11
+ * ships, else `caseplan.json`. The bindings are `--bindings` (default: the
12
+ * project's bindings.json), with that plan's own root bindings as a fallback
13
+ * when it exists. Recompiling into an existing plan (the brownfield loop:
14
+ * decompile, edit, compile back over the same file) then resolves the binding
15
+ * ids decompile names a connector's connection and folder by
16
+ * (`connection: "bOC1"`).
17
+ *
18
+ * @param argv - The command's arguments.
19
+ * @returns The output path and the bindings registry.
20
+ */
21
+ export declare function caseCompileTarget(argv: string[]): {
22
+ out: string;
23
+ bindings: Bindings;
24
+ };
4
25
  /**
5
26
  * Write the entry point's id onto its trigger, as `data.inputs.entryPointId`.
6
27
  *
@@ -23,6 +23,7 @@ import { check } from './check.js';
23
23
  import { caseCheckOpts } from './cli-library.js';
24
24
  import { entryPointIO } from './entry-points.js';
25
25
  import { Bindings } from '../core/bindings.js';
26
+ import { offlineBindingLabel } from './ids.js';
26
27
  import { openCliLibrary } from '../core/cli-library.js';
27
28
  import { defaultBindingsFile } from '../workdir.js';
28
29
  import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
@@ -54,8 +55,7 @@ export async function run(argv) {
54
55
  // which is what the three copies of these lines used to each decide for itself.
55
56
  // Opened BEFORE the check pass, which reads the same library (#1014).
56
57
  const library = openCliLibrary(argv);
57
- const bindFile = flagValue(argv, '--bindings') ?? defaultBindingsFile();
58
- const bindings = new Bindings(bindFile);
58
+ const { out, bindings } = caseCompileTarget(argv);
59
59
  const diags = check(built, caseCheckOpts(library, bindings));
60
60
  for (const d of diags)
61
61
  console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}`);
@@ -66,7 +66,6 @@ export async function run(argv) {
66
66
  console.error(`compile: ${errors.length} error(s); not emitting.`);
67
67
  return 1;
68
68
  }
69
- const out = flagValue(argv, '-o') ?? shippedCasePlanName('.') ?? 'caseplan.json';
70
69
  const shadow = shadowedOutputDiagnostic(out);
71
70
  if (shadow) {
72
71
  console.error(` ✗ [${shadow.code}] ${shadow.message} Fix: ${shadow.suggestion}.`);
@@ -94,6 +93,30 @@ export async function run(argv) {
94
93
  reportResult({ path: resolve(out), stages, ...(triggers >= 0 ? { triggers } : {}), ...(resources >= 0 ? { resources } : {}) });
95
94
  return 0;
96
95
  }
96
+ /**
97
+ * The plan file `case compile` writes, and the bindings it resolves connection
98
+ * and folder labels against. `case check` reads the same pair, so the two agree
99
+ * on which labels are declared.
100
+ *
101
+ * The plan is `-o`, else the plan file the project in the working directory
102
+ * ships, else `caseplan.json`. The bindings are `--bindings` (default: the
103
+ * project's bindings.json), with that plan's own root bindings as a fallback
104
+ * when it exists. Recompiling into an existing plan (the brownfield loop:
105
+ * decompile, edit, compile back over the same file) then resolves the binding
106
+ * ids decompile names a connector's connection and folder by
107
+ * (`connection: "bOC1"`).
108
+ *
109
+ * @param argv - The command's arguments.
110
+ * @returns The output path and the bindings registry.
111
+ */
112
+ export function caseCompileTarget(argv) {
113
+ const out = flagValue(argv, '-o') ?? shippedCasePlanName('.') ?? 'caseplan.json';
114
+ const bindFile = flagValue(argv, '--bindings') ?? defaultBindingsFile();
115
+ // A row serialize minted offline (`{ id: 'b…', resourceKey: 'drive' }`) is a
116
+ // placeholder for the label `drive`, not a binding: it must not answer.
117
+ const minted = (row) => offlineBindingLabel(row) !== undefined;
118
+ return { out, bindings: new Bindings(bindFile).withFallback(existsSync(out) ? out : undefined, minted) };
119
+ }
97
120
  /**
98
121
  * The file names a Case project's plan can have, in the order the packager
99
122
  * (`@uipath/packager-tool-case` `CasePlanFileNames`) picks the first one present.
@@ -914,7 +914,10 @@ function producedVariableNames(plan) {
914
914
  continue;
915
915
  for (const lane of node.data?.tasks ?? [])
916
916
  for (const task of lane) {
917
- if (ownEntry(TASK_KIND_METHOD, task.type)) {
917
+ if (ownEntry(TASK_KIND_METHOD, task.type) || task.type === 'action') {
918
+ // An action form output (`{ name, value }`) produces its variable as a
919
+ // reference-task output does, so an older plan's root companion for it
920
+ // is not re-declared as a `.var()` (the recompile writes none).
918
921
  for (const row of task.data?.outputs ?? [])
919
922
  if (row.var)
920
923
  produced.add(row.var);
@@ -86,6 +86,7 @@ export function serialize(built, opts = {}) {
86
86
  ids,
87
87
  offlineBindingIds: new Map(),
88
88
  varCompanions: new Map(),
89
+ ownNameRows: new Map(),
89
90
  outputIds: new Set(),
90
91
  ownOutputVars: ownOutputVars(built),
91
92
  slaId: new Map(),
@@ -230,14 +231,26 @@ export function serialize(built, opts = {}) {
230
231
  throw new Error(problem.message);
231
232
  metadata.businessEvents = built.businessEvents.map(emitBusinessEvent);
232
233
  }
233
- // Variables: declared vars + the readable companions that task outputs bound
234
- // (collected during stage emission above). A declared var wins on a name clash.
234
+ // Variables: declared vars + the readable companions that REASSIGNED task
235
+ // outputs bound (collected during stage emission above). A declared var wins
236
+ // on a name clash. An own-name output (`id == var`) writes no root entry: like
237
+ // the Designer, the output row is itself the variable `=vars.<id>` reads.
235
238
  const variables = emitVariables(built.vars, ids, triggerNodeByRef);
236
239
  const inputOutputs = variables.inputOutputs;
237
240
  const declared = new Set(inputOutputs.map((e) => e.id));
238
241
  for (const [name, companion] of resolver.varCompanions)
239
242
  if (!declared.has(name))
240
243
  inputOutputs.push(companion);
244
+ // An own-name row that writes a variable which DOES have a root entry (a
245
+ // declared `.var()`, or a companion another, reassigned, writer needed) is the
246
+ // v1 skill's "controlled alias": `originalVar == id` tells the Designer the row
247
+ // feeds that existing variable, so it keeps the root entry instead of minting
248
+ // a second variable under the same name.
249
+ const rootIds = new Set(inputOutputs.map((e) => e.id));
250
+ for (const [name, rows] of resolver.ownNameRows)
251
+ if (rootIds.has(name))
252
+ for (const row of rows)
253
+ row.originalVar = row.id;
241
254
  const result = {
242
255
  id: caseId,
243
256
  name: built.name,
@@ -1164,7 +1177,11 @@ function emitActionField(f, outputElementId, resolver) {
1164
1177
  row.source = `=${f.name}`;
1165
1178
  row.target = `=${id}`;
1166
1179
  row.elementId = outputElementId;
1167
- if (resolver && !resolver.varCompanions.has(f.value)) {
1180
+ // `id == value`: the row is the variable (the Designer writes no root entry
1181
+ // for it). Only a suffixed, reassigned row needs the readable companion.
1182
+ if (resolver && id === f.value)
1183
+ addOwnNameRow(resolver, f.value, row);
1184
+ else if (resolver && !resolver.varCompanions.has(f.value)) {
1168
1185
  resolver.varCompanions.set(f.value, {
1169
1186
  id: f.value,
1170
1187
  name: f.value,
@@ -1209,18 +1226,24 @@ const OWN_NAME_OUTPUT_ROWS = new WeakSet();
1209
1226
  function emitTaskOutput(o, elementId, ids, seed, resolver) {
1210
1227
  const path = o.source.replace(/^=/, '');
1211
1228
  const field = path.split('.').pop() || o.source;
1212
- // Register the readable companion once (declared vars take precedence in the merge).
1213
- if (!resolver.varCompanions.has(o.var))
1214
- resolver.varCompanions.set(o.var, { id: o.var, name: o.var, type: o.type, elementId: 'root', custom: true });
1229
+ // A reassigned row registers the readable companion once (declared vars take
1230
+ // precedence in the merge); an own-name row is the variable and registers none.
1231
+ const companion = () => {
1232
+ if (!resolver.varCompanions.has(o.var))
1233
+ resolver.varCompanions.set(o.var, { id: o.var, name: o.var, type: o.type, elementId: 'root', custom: true });
1234
+ };
1215
1235
  if (isOwnNameOutput(o)) {
1216
1236
  const id = claimOutputId(resolver, o.var);
1217
1237
  if (id === o.var) {
1218
1238
  const row = { name: field, type: o.type, id, var: o.var, value: o.var, source: o.source, target: `=${id}`, elementId };
1219
1239
  OWN_NAME_OUTPUT_ROWS.add(row);
1240
+ addOwnNameRow(resolver, o.var, row);
1220
1241
  return row;
1221
1242
  }
1243
+ companion();
1222
1244
  return { name: field, type: o.type, id, var: o.var, originalVar: id, value: o.var, source: o.source, target: `=${id}`, elementId };
1223
1245
  }
1246
+ companion();
1224
1247
  const slot = claimOutputId(resolver, ids.make('o', 8, seed));
1225
1248
  return {
1226
1249
  name: field,
@@ -1600,6 +1623,14 @@ function connectorMetadata(rc, op, executionType) {
1600
1623
  function connectorOutputVar(resolver, base) {
1601
1624
  return claimOutputId(resolver, base);
1602
1625
  }
1626
+ /** Record an own-name row (`id == var`), the variable itself; see the variables merge in `serialize`. */
1627
+ function addOwnNameRow(resolver, name, row) {
1628
+ const rows = resolver.ownNameRows.get(name);
1629
+ if (rows)
1630
+ rows.push(row);
1631
+ else
1632
+ resolver.ownNameRows.set(name, [row]);
1633
+ }
1603
1634
  /**
1604
1635
  * Claim a task-output row id from the document-wide pool, the Designer's
1605
1636
  * uniqueness rule: the first claimant of `base` gets `base`, later ones get
@@ -27,6 +27,8 @@ export interface BindingsView {
27
27
  resolve(name?: string): string | undefined;
28
28
  declared?(): string[];
29
29
  entry?(name?: string): unknown;
30
+ /** True when only a recompile fallback row (the plan's own bindings) answers `name`. */
31
+ fromFallback?(name?: string): boolean;
30
32
  }
31
33
  export declare function bindingSelfNameMessage(where: string, name: string, attribute: 'connection' | 'folder', target?: PrepareTarget): string;
32
34
  /**
@@ -81,6 +81,11 @@ export function undeclaredBinding(bindings, name) {
81
81
  export function bindingLabelRefusal(bindings, name, attribute, where, target) {
82
82
  if (!bindings)
83
83
  return undefined;
84
+ // A label that is exactly a row id of the plan being recompiled (what
85
+ // decompile emits) reuses that row. Those rows are not an author's
86
+ // bindings.json, so neither rule applies to them.
87
+ if (bindings.fromFallback?.(name))
88
+ return undefined;
84
89
  const resolved = bindings.resolve(name);
85
90
  if (resolved === name)
86
91
  return new BindingRefusal('BINDING_SELF_NAME', bindingSelfNameMessage(where, name, attribute, target));
@@ -1,11 +1,50 @@
1
1
  export declare class Bindings {
2
2
  private list;
3
+ /**
4
+ * Rows from {@link withFallback}: the root `bindings[]` of the plan being
5
+ * recompiled. Kept apart from `list` on purpose. They are not an author's
6
+ * bindings.json, so they never count as declared names and never feed the
7
+ * self-name / undeclared label rules. An offline plan's own rows are
8
+ * `{ id: 'slack', resourceKey: 'slack' }`; counted as declared they made the
9
+ * second offline compile refuse the connector's own label (BINDING_SELF_NAME).
10
+ */
11
+ private fallback;
3
12
  constructor(file?: string);
4
- /** Resolve by binding `name` or `id`; returns its resourceKey (or default). */
13
+ /**
14
+ * Add the root bindings of another document (the plan being recompiled) as a
15
+ * fallback. A fallback row answers only a label that is exactly its `id`
16
+ * (what decompile emits), whose key is not that same id (a placeholder; see
17
+ * `fallbackRow`), and that the primary registry does not answer. It
18
+ * is never part of {@link declared}, and {@link fromFallback} lets the label
19
+ * rules skip it.
20
+ *
21
+ * @param file - A bindings.json, or any document with a root `bindings[]`
22
+ * (a caseplan); a missing or unreadable file adds nothing.
23
+ * @param isPlaceholder - Recognises a row the document's own compiler minted
24
+ * offline (a label with no real key behind it, under a minted id). Such a
25
+ * row answers nothing, like a row whose key is its own id.
26
+ * @returns This registry, for chaining.
27
+ */
28
+ withFallback(file?: string, isPlaceholder?: (row: Record<string, unknown>) => boolean): this;
29
+ private primary;
30
+ /**
31
+ * The fallback row whose `id` is `name`. A row whose key is its own id
32
+ * (`{ id: 'drive', resourceKey: 'drive' }`, what an offline compile writes) is
33
+ * a placeholder, not an answer, so it is skipped: the label rules then judge
34
+ * `drive` against the author's bindings.json as they would without a fallback.
35
+ */
36
+ private fallbackRow;
37
+ /**
38
+ * True when only a fallback row answers `name`: the primary registry does not,
39
+ * and a fallback row's `id` is exactly `name`. The label rules
40
+ * (`bindingLabelRefusal`) accept such a label as is.
41
+ */
42
+ fromFallback(name?: string): boolean;
43
+ /** Resolve by binding `name` or `id` (fallback rows: `id` only); returns its resourceKey (or default). */
5
44
  resolve(name?: string): string | undefined;
6
- /** Every symbolic name the file declares — what an author CAN write in `connection:` / `folder:`. */
45
+ /** Every symbolic name the bindings.json declares — what an author CAN write in `connection:` / `folder:`. Fallback rows are not included. */
7
46
  declared(): string[];
8
- /** The full binding entry by `name` or `id` — for consumers that emit a binding
47
+ /** The full binding entry by `name` or `id` (fallback rows: `id` only) — for consumers that emit a binding
9
48
  * registry + `=bindings.<id>` refs (the Case caseplan) rather than inlining the
10
49
  * resourceKey (the Flow node). */
11
50
  entry(name?: string): Record<string, unknown> | undefined;
@@ -5,21 +5,83 @@
5
5
  import { readFileSync, existsSync } from 'node:fs';
6
6
  export class Bindings {
7
7
  list = [];
8
+ /**
9
+ * Rows from {@link withFallback}: the root `bindings[]` of the plan being
10
+ * recompiled. Kept apart from `list` on purpose. They are not an author's
11
+ * bindings.json, so they never count as declared names and never feed the
12
+ * self-name / undeclared label rules. An offline plan's own rows are
13
+ * `{ id: 'slack', resourceKey: 'slack' }`; counted as declared they made the
14
+ * second offline compile refuse the connector's own label (BINDING_SELF_NAME).
15
+ */
16
+ fallback = [];
8
17
  constructor(file) {
9
18
  if (file && existsSync(file)) {
10
19
  this.list = normalizeBindings(JSON.parse(readFileSync(file, 'utf8')));
11
20
  }
12
21
  }
13
- /** Resolve by binding `name` or `id`; returns its resourceKey (or default). */
22
+ /**
23
+ * Add the root bindings of another document (the plan being recompiled) as a
24
+ * fallback. A fallback row answers only a label that is exactly its `id`
25
+ * (what decompile emits), whose key is not that same id (a placeholder; see
26
+ * `fallbackRow`), and that the primary registry does not answer. It
27
+ * is never part of {@link declared}, and {@link fromFallback} lets the label
28
+ * rules skip it.
29
+ *
30
+ * @param file - A bindings.json, or any document with a root `bindings[]`
31
+ * (a caseplan); a missing or unreadable file adds nothing.
32
+ * @param isPlaceholder - Recognises a row the document's own compiler minted
33
+ * offline (a label with no real key behind it, under a minted id). Such a
34
+ * row answers nothing, like a row whose key is its own id.
35
+ * @returns This registry, for chaining.
36
+ */
37
+ withFallback(file, isPlaceholder) {
38
+ if (!file || !existsSync(file))
39
+ return this;
40
+ let parsed;
41
+ try {
42
+ parsed = JSON.parse(readFileSync(file, 'utf8'));
43
+ }
44
+ catch {
45
+ return this; // a fallback is optional; an unreadable one adds nothing
46
+ }
47
+ const ids = new Set(this.fallback.map((x) => x?.id));
48
+ for (const entry of normalizeBindings(parsed))
49
+ if (typeof entry?.id === 'string' && !ids.has(entry.id) && !isPlaceholder?.(entry))
50
+ this.fallback.push(entry);
51
+ return this;
52
+ }
53
+ primary(name) {
54
+ return this.list.find((x) => x.name === name || x.id === name);
55
+ }
56
+ /**
57
+ * The fallback row whose `id` is `name`. A row whose key is its own id
58
+ * (`{ id: 'drive', resourceKey: 'drive' }`, what an offline compile writes) is
59
+ * a placeholder, not an answer, so it is skipped: the label rules then judge
60
+ * `drive` against the author's bindings.json as they would without a fallback.
61
+ */
62
+ fallbackRow(name) {
63
+ return this.fallback.find((x) => x.id === name && (x.resourceKey ?? x.default) !== name);
64
+ }
65
+ /**
66
+ * True when only a fallback row answers `name`: the primary registry does not,
67
+ * and a fallback row's `id` is exactly `name`. The label rules
68
+ * (`bindingLabelRefusal`) accept such a label as is.
69
+ */
70
+ fromFallback(name) {
71
+ if (!name)
72
+ return false;
73
+ return this.primary(name) === undefined && this.fallbackRow(name) !== undefined;
74
+ }
75
+ /** Resolve by binding `name` or `id` (fallback rows: `id` only); returns its resourceKey (or default). */
14
76
  resolve(name) {
15
77
  if (!name)
16
78
  return undefined;
17
- const b = this.list.find((x) => x.name === name || x.id === name);
79
+ const b = this.primary(name) ?? this.fallbackRow(name);
18
80
  if (!b)
19
81
  return undefined;
20
82
  return (b.resourceKey ?? b.default);
21
83
  }
22
- /** Every symbolic name the file declares — what an author CAN write in `connection:` / `folder:`. */
84
+ /** Every symbolic name the bindings.json declares — what an author CAN write in `connection:` / `folder:`. Fallback rows are not included. */
23
85
  declared() {
24
86
  const names = [];
25
87
  for (const b of this.list) {
@@ -29,13 +91,13 @@ export class Bindings {
29
91
  }
30
92
  return names;
31
93
  }
32
- /** The full binding entry by `name` or `id` — for consumers that emit a binding
94
+ /** The full binding entry by `name` or `id` (fallback rows: `id` only) — for consumers that emit a binding
33
95
  * registry + `=bindings.<id>` refs (the Case caseplan) rather than inlining the
34
96
  * resourceKey (the Flow node). */
35
97
  entry(name) {
36
98
  if (!name)
37
99
  return undefined;
38
- return this.list.find((x) => x.name === name || x.id === name);
100
+ return (this.primary(name) ?? this.fallbackRow(name));
39
101
  }
40
102
  }
41
103
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.50.0",
3
+ "version": "6.50.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",
@@ -92,5 +92,5 @@
92
92
  "@types/node": "^22.7.0",
93
93
  "esbuild": "^0.28.1"
94
94
  },
95
- "gitref": "d674fc1a9ed1b8aa61dcc0b30a95a434503eb61b"
95
+ "gitref": "a0ed06f0dddc98064867948988de27e22e280501"
96
96
  }