@uipath/maestro-builder-sdk 6.46.4 → 6.46.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.
@@ -5,6 +5,7 @@ import { caseJsonPreservation, caseJsonPreservationErrors } from './preserve.js'
5
5
  import { EQUALS_EXPR_DIALECT, checkBindingLabels, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
6
6
  import { checkEventResolution } from '../core/event-checks.js';
7
7
  import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT } from './external.js';
8
+ import { isResourceLabelRefusal, resolveResourceRef } from './resource-identity.js';
8
9
  const TYPE_NAMES = new Set(Object.values(types));
9
10
  // audit-case-coverage: rule-slot table start
10
11
  // This runtime table must equal the committed matrix's rejected cells. The audit
@@ -257,7 +258,7 @@ export function check(built, opts = {}) {
257
258
  // positive that blocks emit (this gate runs before serialization), so it must be
258
259
  // re-derived from the serializer rather than from remembered probe results.
259
260
  for (const stage of built.stages) {
260
- checkStage(stage, diags, refStage, refPlacement);
261
+ checkStage(stage, diags, refStage, refPlacement, opts.bindings);
261
262
  stage.slaRules.forEach((s, i) => checkSla(s, `stage "${stage.label}" SLA #${i + 1}`, diags));
262
263
  }
263
264
  built.slaRules.forEach((s, i) => checkSla(s, `case SLA #${i + 1}`, diags));
@@ -394,7 +395,7 @@ function isNilOrEmpty(value) {
394
395
  return Object.keys(value).length === 0;
395
396
  return false;
396
397
  }
397
- function checkStage(stage, diags, refStage, refPlacement) {
398
+ function checkStage(stage, diags, refStage, refPlacement, bindings) {
398
399
  const where = `stage "${stage.label}"`;
399
400
  if (stage.exception && stage.required !== false)
400
401
  diags.push({
@@ -484,16 +485,15 @@ function checkStage(stage, diags, refStage, refPlacement) {
484
485
  where: t.name,
485
486
  });
486
487
  // The builder binds a resource task itself: it mints the name/folderPath
487
- // binding pair from the literal name and folder. A `=bindings.<id>` string
488
- // passed as either becomes part of the binding key, which then names no
489
- // resource (`=bindings.f.=bindings.n`) and dangles at deploy.
490
- if (t.ref && [t.ref.name, t.ref.folderPath].some((v) => v.trim().startsWith('=bindings.')))
491
- diags.push({
492
- level: 'error',
493
- code: 'RESOURCE_REF_IS_BINDING',
494
- message: `Task "${t.name}" passes a "=bindings.<id>" reference as its resource name or folder. Pass the resource's own name and its folder, e.g. .agent('Contract Analysis', { folder: 'Shared/Legal' }); compile writes the name/folderPath binding pair keyed '<folder>.<name>' and points the task at it.`,
495
- where: t.name,
496
- });
488
+ // binding pair from the literal name and folder. A `=bindings.<label>` name
489
+ // or folder is read through bindings.json first, the way a connector's
490
+ // `connection:` label is; one bindings.json cannot answer is refused with the
491
+ // same text serialize throws (`resolveResourceRef`).
492
+ if (t.ref) {
493
+ const resolved = resolveResourceRef(t.ref, bindings, `Task "${t.name}"`);
494
+ if (isResourceLabelRefusal(resolved))
495
+ diags.push({ level: 'error', code: resolved.code, message: resolved.message, where: t.name });
496
+ }
497
497
  if (t.kind === 'business-rule') {
498
498
  if (!t.ref || !t.ref.name.trim() || !t.ref.folderPath.trim())
499
499
  diags.push({
@@ -23,6 +23,7 @@
23
23
  * `case-validate-service.ts` exempts that prefix from its key/folder agreement
24
24
  * check.
25
25
  */
26
+ import { type BindingsView } from '../core/binding-messages.js';
26
27
  export declare const SOLUTION_FOLDER = "solution_folder";
27
28
  export interface ResourceIdentity {
28
29
  /** The `resourceKey` both rows of the name/folderPath binding pair carry. */
@@ -42,3 +43,47 @@ export interface ResourceIdentity {
42
43
  export declare function resourceIdentity(name: string, folder: string): ResourceIdentity;
43
44
  /** True for the key of a resource built inside the same solution. */
44
45
  export declare function isInSolutionKey(resourceKey: string): boolean;
46
+ /**
47
+ * Why a resource task's `=bindings.<label>` name or folder cannot be resolved.
48
+ *
49
+ * - `RESOURCE_REF_IS_BINDING`: there is no bindings.json (or it declares nothing),
50
+ * so the label names nothing compile can read.
51
+ * - `RESOURCE_BINDING_UNDECLARED`: bindings.json does not declare the label, or
52
+ * declares it with no value.
53
+ */
54
+ export interface ResourceLabelRefusal {
55
+ code: 'RESOURCE_REF_IS_BINDING' | 'RESOURCE_BINDING_UNDECLARED';
56
+ message: string;
57
+ }
58
+ /**
59
+ * The literal name and folder of a resource task, with any `=bindings.<label>`
60
+ * resolved through the project's bindings.json, the way a connector task's
61
+ * `connection:` label is.
62
+ *
63
+ * bindings.json { "name": "agentName", "default": "Agent" }
64
+ * { "name": "agentFolder", "default": "Shared/Golden" }
65
+ * .agent('=bindings.agentName', { folder: '=bindings.agentFolder' })
66
+ * -> { name: 'Agent', folderPath: 'Shared/Golden' }
67
+ *
68
+ * The task then compiles exactly as `.agent('Agent', { folder: 'Shared/Golden' })`
69
+ * does: compile mints the name/folderPath binding pair keyed `<folder>.<name>`.
70
+ * Passing the label straight through instead keyed the pair
71
+ * `=bindings.f.=bindings.n`, which names no resource and dangles at deploy.
72
+ *
73
+ * @param ref - The authored name and folder.
74
+ * @param bindings - The project's bindings.json, if any.
75
+ * @param where - The task, as a diagnostic names it.
76
+ * @returns The literal pair, or the refusal.
77
+ */
78
+ export declare function resolveResourceRef(ref: {
79
+ name: string;
80
+ folderPath: string;
81
+ }, bindings: BindingsView | undefined, where: string): {
82
+ name: string;
83
+ folderPath: string;
84
+ } | ResourceLabelRefusal;
85
+ /** True when {@link resolveResourceRef} refused. */
86
+ export declare function isResourceLabelRefusal(r: {
87
+ name: string;
88
+ folderPath: string;
89
+ } | ResourceLabelRefusal): r is ResourceLabelRefusal;
@@ -23,6 +23,7 @@
23
23
  * `case-validate-service.ts` exempts that prefix from its key/folder agreement
24
24
  * check.
25
25
  */
26
+ import { GUID_RE } from '../core/binding-messages.js';
26
27
  export const SOLUTION_FOLDER = 'solution_folder';
27
28
  const SOLUTION_FOLDER_KEY_PREFIX = `${SOLUTION_FOLDER}.`;
28
29
  /**
@@ -43,3 +44,72 @@ export function resourceIdentity(name, folder) {
43
44
  export function isInSolutionKey(resourceKey) {
44
45
  return resourceKey.startsWith(SOLUTION_FOLDER_KEY_PREFIX);
45
46
  }
47
+ /** How a resource task's name or folder spells a bindings.json label. */
48
+ const BINDING_LABEL_PREFIX = '=bindings.';
49
+ /** The literal value bindings.json declares for `label`, or `undefined`. */
50
+ function declaredValue(bindings, label) {
51
+ const entry = bindings?.entry?.(label);
52
+ // `default` is the value a person reads (the resource's name, the folder
53
+ // path); `resourceKey` can be the resource's GUID. A map-form bindings.json
54
+ // puts the value in both. A GUID-shaped `resourceKey` is never a name or a
55
+ // folder: taking it would key the pair `<folder>.<guid>`, which dangles at
56
+ // deploy, so the label falls through to RESOURCE_BINDING_UNDECLARED.
57
+ const fallback = typeof entry?.resourceKey === 'string' && GUID_RE.test(entry.resourceKey.trim()) ? undefined : entry?.resourceKey;
58
+ const value = entry ? (entry.default ?? fallback) : bindings?.resolve(label);
59
+ return typeof value === 'string' && value.trim() && !value.trim().startsWith(BINDING_LABEL_PREFIX)
60
+ ? value
61
+ : undefined;
62
+ }
63
+ /**
64
+ * The literal name and folder of a resource task, with any `=bindings.<label>`
65
+ * resolved through the project's bindings.json, the way a connector task's
66
+ * `connection:` label is.
67
+ *
68
+ * bindings.json { "name": "agentName", "default": "Agent" }
69
+ * { "name": "agentFolder", "default": "Shared/Golden" }
70
+ * .agent('=bindings.agentName', { folder: '=bindings.agentFolder' })
71
+ * -> { name: 'Agent', folderPath: 'Shared/Golden' }
72
+ *
73
+ * The task then compiles exactly as `.agent('Agent', { folder: 'Shared/Golden' })`
74
+ * does: compile mints the name/folderPath binding pair keyed `<folder>.<name>`.
75
+ * Passing the label straight through instead keyed the pair
76
+ * `=bindings.f.=bindings.n`, which names no resource and dangles at deploy.
77
+ *
78
+ * @param ref - The authored name and folder.
79
+ * @param bindings - The project's bindings.json, if any.
80
+ * @param where - The task, as a diagnostic names it.
81
+ * @returns The literal pair, or the refusal.
82
+ */
83
+ export function resolveResourceRef(ref, bindings, where) {
84
+ const out = { name: ref.name, folderPath: ref.folderPath };
85
+ for (const attribute of ['name', 'folderPath']) {
86
+ const authored = ref[attribute].trim();
87
+ if (!authored.startsWith(BINDING_LABEL_PREFIX))
88
+ continue;
89
+ const label = authored.slice(BINDING_LABEL_PREFIX.length);
90
+ const what = attribute === 'name' ? 'resource name' : 'folder';
91
+ const literal = `e.g. .agent('Contract Analysis', { folder: 'Shared/Legal' })`;
92
+ const declared = bindings?.declared?.() ?? [];
93
+ if (declared.length === 0)
94
+ return {
95
+ code: 'RESOURCE_REF_IS_BINDING',
96
+ message: `${where} passes "${authored}" as its ${what}, and there is no bindings.json declaring "${label}". `
97
+ + `Declare it in bindings.json ({ "name": "${label}", "default": "<the ${what}>" }), or pass the ${what} itself, ${literal}; `
98
+ + `compile writes the name/folderPath binding pair keyed '<folder>.<name>' and points the task at it.`,
99
+ };
100
+ const value = declaredValue(bindings, label);
101
+ if (value === undefined)
102
+ return {
103
+ code: 'RESOURCE_BINDING_UNDECLARED',
104
+ message: `${where} passes "${authored}" as its ${what}, but bindings.json does not declare "${label}" with a value `
105
+ + `(it declares: ${declared.join(', ')}). Use one of those labels, add "${label}" with its ${what} as "default", `
106
+ + `or pass the ${what} itself, ${literal}.`,
107
+ };
108
+ out[attribute] = value;
109
+ }
110
+ return out;
111
+ }
112
+ /** True when {@link resolveResourceRef} refused. */
113
+ export function isResourceLabelRefusal(r) {
114
+ return 'code' in r;
115
+ }
@@ -18,7 +18,7 @@ import { resolveEventWhere } from '../core/lookups.js';
18
18
  import { resolveCaseSubscription } from './subscription.js';
19
19
  import { externalDescriptorRefusal } from './external.js';
20
20
  import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
21
- import { resourceIdentity } from './resource-identity.js';
21
+ import { isResourceLabelRefusal, resolveResourceRef, resourceIdentity } from './resource-identity.js';
22
22
  import { ownEntry } from '../core/own-entry.js';
23
23
  import { resolveSchemaVersion } from '../schema-version.js';
24
24
  import { caseJsonPreservation, caseJsonPreservationErrors, } from './preserve.js';
@@ -914,7 +914,7 @@ function taskData(task, resolver, taskId, stageId, ids) {
914
914
  // never literals: `validate` refuses a literal (CASE_MGMT_RESOURCE_TASK_
915
915
  // LITERAL_BINDING), and deploy re-targets a resource only through its
916
916
  // binding (#1240).
917
- const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids);
917
+ const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids, task.name);
918
918
  const data = { name: nameRef, folderPath: folderRef };
919
919
  const elementId = `${stageId}-${taskId}`;
920
920
  if (task.inputs?.length)
@@ -940,7 +940,7 @@ function taskData(task, resolver, taskId, stageId, ids) {
940
940
  // The Action App the task opens, bound like any resource: an `app`
941
941
  // name/folderPath pair keyed `<deployment folder>.<app>` (#1240).
942
942
  if (task.ref) {
943
- const { nameRef, folderRef } = referenceBindings('action', task.ref, resolver, ids);
943
+ const { nameRef, folderRef } = referenceBindings('action', task.ref, resolver, ids, task.name);
944
944
  data.name = nameRef;
945
945
  data.folderPath = folderRef;
946
946
  }
@@ -1278,7 +1278,12 @@ function resourceBinding(resolver, ids, binding, resourceKey, propertyAttribute,
1278
1278
  /** The binding `resource` an action task's Action App is bound by (no subtype). */
1279
1279
  const ACTION_APP_RESOURCE = 'app';
1280
1280
  /** The `=bindings.<id>` name/folderPath references of a published-resource task. */
1281
- function referenceBindings(kind, ref, resolver, ids) {
1281
+ function referenceBindings(kind, authored, resolver, ids, taskName) {
1282
+ // A `=bindings.<label>` name or folder reads through bindings.json to its
1283
+ // literal value, so the task binds exactly as the literal form does.
1284
+ const ref = resolveResourceRef(authored, resolver.bindings, `Task "${taskName}"`);
1285
+ if (isResourceLabelRefusal(ref))
1286
+ throw new Error(`[${ref.code}] ${ref.message}`);
1282
1287
  const binding = kind === 'business-rule'
1283
1288
  ? { resource: BUSINESS_RULE_RESOURCE }
1284
1289
  : kind === 'action'
@@ -1308,7 +1313,7 @@ function referenceBindings(kind, ref, resolver, ids) {
1308
1313
  * (`=<Decision>.<column>`).
1309
1314
  */
1310
1315
  function businessRuleTaskData(task, resolver, elementId, ids) {
1311
- const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids);
1316
+ const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids, task.name);
1312
1317
  const ownRow = (name, source, seed, extra = {}) => {
1313
1318
  const slot = ids.make('v', 8, `${elementId}:${seed}`);
1314
1319
  return { name, type: 'jsonSchema', id: slot, var: slot, value: slot, source, target: `=${slot}`, elementId, ...extra };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.46.4",
3
+ "version": "6.46.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": "d2cb008a329ef32af20be81f73a2ec726e22a13c"
96
+ "gitref": "0edb50d70d9787a64a6c66ce37b5e1eed45d2a0d"
97
97
  }