@uipath/maestro-builder-sdk 6.46.0 → 6.46.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.
@@ -39,9 +39,18 @@ export function checkCaseExpressions(built) {
39
39
  // task output is not flagged as an undeclared reference.
40
40
  for (const stage of built.stages)
41
41
  for (const lane of stage.lanes)
42
- for (const task of lane)
42
+ for (const task of lane) {
43
43
  for (const o of task.outputs ?? [])
44
44
  roots.add(o.var);
45
+ // A human task's form outputs declare one too: the field's `value` is the
46
+ // variable the assignee's answer lands in (`.action({ outputs: [{ name:
47
+ // 'Action', value: 'decision' }] })` → `vars.decision`). The serializer
48
+ // registers exactly that companion (`emitActionField`), so a gate on it
49
+ // is a declared read, not a typo.
50
+ for (const f of task.action?.outputs ?? [])
51
+ if (f.value)
52
+ roots.add(f.value);
53
+ }
45
54
  // Event-trigger payload extractions declare readable case variables too.
46
55
  for (const t of built.triggers)
47
56
  for (const o of t.eventOutputs ?? [])
@@ -432,7 +432,13 @@ export interface TaskRef {
432
432
  * Options for {@link TaskBuilder.businessRule}.
433
433
  */
434
434
  export interface BusinessRuleOptions {
435
- /** The Orchestrator folder the rule is deployed in, e.g. `Shared`. */
435
+ /**
436
+ * The Orchestrator folder the rule is deployed in, e.g. `Shared`. For a rule
437
+ * built in the same solution as the case, pass `'solution_folder'` (the folder
438
+ * `registry search --local` reports): the binding is keyed
439
+ * `solution_folder.<name>` and its folderPath is left empty, so the case runs
440
+ * the rule from its own deployed folder.
441
+ */
436
442
  folder: string;
437
443
  /**
438
444
  * The rule's whole result schema, as `uip maestro case tasks describe --type
@@ -1,3 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { type CliExitCode } from '../cli-run.js';
3
3
  export declare function run(argv: string[]): Promise<CliExitCode>;
4
+ /**
5
+ * Write the entry point's id onto its trigger, as `data.inputs.entryPointId`.
6
+ *
7
+ * The Case Designer pairs a trigger with its entry point through this field.
8
+ * A trigger without it gets a fresh GUID on the first open, and the entry
9
+ * point's `uniqueId` is rewritten to match, so the entry point changes identity
10
+ * (MST-16422; `validate --strict` warns STRICT_TRIGGER_ENTRY_POINT_ID). The
11
+ * Designer writes it last among the inputs, so it is appended.
12
+ */
13
+ export declare function stampEntryPointId(trigger: any, uniqueId: string): void;
@@ -28,6 +28,7 @@ import { defaultBindingsFile } from '../workdir.js';
28
28
  import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
29
29
  import { ensureTypeScriptRuntime } from '../node-runtime.js';
30
30
  import { CASE_FORMAT_PROFILE } from './format-profile.js';
31
+ import { isInSolutionKey, SOLUTION_FOLDER } from './resource-identity.js';
31
32
  import { sha256Uuid } from '../core/stable-id.js';
32
33
  import { isRecord } from '../core/node-envelope.js';
33
34
  export async function run(argv) {
@@ -81,9 +82,11 @@ export async function run(argv) {
81
82
  reportDiagnostics([...diags, thrownDiagnostic(error)]);
82
83
  throw error;
83
84
  }
85
+ // Entry points first: each trigger carries its entry point's `uniqueId` as
86
+ // `data.inputs.entryPointId`, so the plan is written after the ids are known.
87
+ const triggers = syncEntryPoints(caseplan, out);
84
88
  writeJsonFile(out, caseplan);
85
89
  const stages = caseplan.nodes.filter((n) => String(n.type).endsWith('Stage')).length;
86
- const triggers = syncEntryPoints(caseplan, out);
87
90
  const epNote = triggers >= 0 ? `, synced entry-points.json (${triggers} trigger(s))` : '';
88
91
  const resources = syncBindingsV2(caseplan, out);
89
92
  const bindingsNote = resources >= 0 ? `, synced bindings_v2.json (${resources} resource(s))` : '';
@@ -229,6 +232,25 @@ function describedCaseFileName(dir) {
229
232
  function packagedCaseFileName(dir, outPath) {
230
233
  return shippedCasePlanName(dir) ?? basename(outPath);
231
234
  }
235
+ /**
236
+ * Write the entry point's id onto its trigger, as `data.inputs.entryPointId`.
237
+ *
238
+ * The Case Designer pairs a trigger with its entry point through this field.
239
+ * A trigger without it gets a fresh GUID on the first open, and the entry
240
+ * point's `uniqueId` is rewritten to match, so the entry point changes identity
241
+ * (MST-16422; `validate --strict` warns STRICT_TRIGGER_ENTRY_POINT_ID). The
242
+ * Designer writes it last among the inputs, so it is appended.
243
+ */
244
+ export function stampEntryPointId(trigger, uniqueId) {
245
+ if (typeof uniqueId !== 'string' || !uniqueId)
246
+ return;
247
+ trigger.data ??= {};
248
+ const inputs = trigger.data.inputs;
249
+ if (inputs !== undefined && (typeof inputs !== 'object' || inputs === null || Array.isArray(inputs)))
250
+ return;
251
+ const { entryPointId: _old, ...rest } = (inputs ?? {});
252
+ trigger.data.inputs = { ...rest, entryPointId: uniqueId };
253
+ }
232
254
  /**
233
255
  * Keep the sibling `entry-points.json` in step with the caseplan's trigger nodes:
234
256
  * the runtime discovers triggers there, so a trigger node with no matching
@@ -251,9 +273,11 @@ function syncEntryPoints(caseplan, outPath) {
251
273
  // Project the case's In/Out args into this trigger's input/output schemas
252
274
  // (In-args scoped to this trigger by elementId; Out-args to every entry).
253
275
  const { input, output } = entryPointIO(caseplan.variables, n.id);
276
+ const uniqueId = prev?.uniqueId ?? sha256Uuid(`entrypoint:${n.id}`);
277
+ stampEntryPointId(n, uniqueId);
254
278
  return {
255
279
  filePath: `/content/${base}.bpmn#${n.id}`,
256
- uniqueId: prev?.uniqueId ?? sha256Uuid(`entrypoint:${n.id}`),
280
+ uniqueId,
257
281
  type: 'CaseManagement',
258
282
  input,
259
283
  output,
@@ -297,8 +321,14 @@ function syncBindingsV2(caseplan, outPath) {
297
321
  grouped.set(groupKey, group);
298
322
  }
299
323
  const property = stringValue(binding.propertyAttribute) ?? stringValue(binding.name);
324
+ // An in-solution resource binds an empty folderPath in the caseplan, while
325
+ // the sidecar names its folder by the sentinel the key carries — the split
326
+ // `uip maestro case bindings sync` writes (resource-identity.ts).
327
+ const defaultValue = property === 'folderPath' && isInSolutionKey(key)
328
+ ? SOLUTION_FOLDER
329
+ : binding.default ?? key;
300
330
  if (property)
301
- group.value[property] = { defaultValue: binding.default ?? key };
331
+ group.value[property] = { defaultValue };
302
332
  const subType = stringValue(binding.resourceSubType);
303
333
  if (subType)
304
334
  group.subType = subType;
@@ -4,6 +4,7 @@ import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
4
4
  import { preserveSchemaVersion } from '../schema-version.js';
5
5
  import { ownEntry } from '../core/own-entry.js';
6
6
  import { isUnrestorableSlotId } from './preserve.js';
7
+ import { isInSolutionKey, SOLUTION_FOLDER } from './resource-identity.js';
7
8
  const TASK_KIND_METHOD = {
8
9
  process: 'process',
9
10
  agent: 'agent',
@@ -133,6 +134,10 @@ function encodedSubscriptionConfig(context, owner) {
133
134
  throw new Error(`${owner} has malformed subscription metadata configuration JSON.`);
134
135
  }
135
136
  }
137
+ /** A plain object with no keys — the Designer's spelling of an empty subscription body. */
138
+ function isEmptyObject(value) {
139
+ return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
140
+ }
136
141
  function subscriptionWhere(bag, owner) {
137
142
  const inputs = bag.inputs;
138
143
  if (!Array.isArray(inputs) || inputs.length !== 1)
@@ -142,6 +147,11 @@ function subscriptionWhere(bag, owner) {
142
147
  throw new Error(`${owner} has an unsupported subscription input.`);
143
148
  if (row.value === '' && row.body === undefined)
144
149
  return undefined;
150
+ // The Case Designer saves a subscription with no parameters (an HTTP webhook,
151
+ // say) as `body: {}` with no `value`. That is the same "no where" the SDK
152
+ // writes as `value: ''`, not an unsupported body.
153
+ if (isEmptyObject(row.body) && (row.value === undefined || row.value === ''))
154
+ return undefined;
145
155
  const queryParams = row.body?.queryParams;
146
156
  if (!queryParams || typeof queryParams !== 'object' || Array.isArray(queryParams))
147
157
  throw new Error(`${owner} has an unsupported subscription body.`);
@@ -340,6 +350,21 @@ function bindingDefault(value, refs) {
340
350
  const id = value.slice('=bindings.'.length);
341
351
  return refs.bindingById.get(id)?.default ?? value;
342
352
  }
353
+ /**
354
+ * The folder a resource task's `folderPath` reference names, as the author
355
+ * writes it. An in-solution resource binds an empty folderPath under a
356
+ * `solution_folder.<name>` key; the builder spells that folder `solution_folder`
357
+ * (resource-identity.ts), so lift the sentinel back rather than the empty
358
+ * default, which would recompile to the key `.<name>`.
359
+ */
360
+ function bindingFolder(value, refs) {
361
+ if (typeof value === 'string' && value.startsWith('=bindings.')) {
362
+ const row = refs.bindingById.get(value.slice('=bindings.'.length));
363
+ if (typeof row?.resourceKey === 'string' && isInSolutionKey(row.resourceKey))
364
+ return SOLUTION_FOLDER;
365
+ }
366
+ return bindingDefault(value, refs);
367
+ }
343
368
  function selectNextStageByExit(plan, refs) {
344
369
  const raw = plan.metadata?.waitForHumanSelectNextStageDFConnector;
345
370
  if (raw === undefined)
@@ -505,10 +530,17 @@ function externalCall(task, refs, library) {
505
530
  throw new Error(`${owner} has unsupported serviceType "${String(data.serviceType ?? '')}".`);
506
531
  if (!Array.isArray(data.context))
507
532
  throw new Error(`${owner} requires a resolved context array.`);
533
+ // A trailing `activityConfigurationVersion: v2` row only picks the Designer
534
+ // panel; compile writes it back from the plan's `intsvcActivityConfig` flag.
535
+ const rows = data.context;
536
+ const last = rows[rows.length - 1];
537
+ const configRows = last?.name === 'activityConfigurationVersion' && last.value === 'v2' && last.type === 'string'
538
+ ? rows.slice(0, -1)
539
+ : rows;
508
540
  const expectedContext = ['activity', 'connectorKey', 'connection', 'folderKey', 'method', 'path'];
509
- if (data.context.map((row) => row.name).join('\0') !== expectedContext.join('\0'))
510
- throw new Error(`${owner} context must contain exactly ${expectedContext.join(', ')} in canonical order.`);
511
- const context = contextMap(data);
541
+ if (configRows.map((row) => row.name).join('\0') !== expectedContext.join('\0'))
542
+ throw new Error(`${owner} context must contain exactly ${expectedContext.join(', ')} in canonical order, optionally followed by activityConfigurationVersion v2.`);
543
+ const context = contextMap({ context: configRows });
512
544
  const stringValue = (name) => {
513
545
  const row = context.get(name);
514
546
  if (row?.type !== 'string' || typeof row.value !== 'string' || !row.value)
@@ -643,7 +675,7 @@ function businessRuleCall(task, refs) {
643
675
  const data = task.data ?? {};
644
676
  const owner = `business-rule task "${task.displayName ?? task.id ?? '?'}"`;
645
677
  const name = bindingDefault(data.name, refs);
646
- const folderPath = bindingDefault(data.folderPath, refs);
678
+ const folderPath = bindingFolder(data.folderPath, refs);
647
679
  if (typeof name !== 'string' || !name || typeof folderPath !== 'string')
648
680
  throw new Error(`${owner} must name its rule and folder (data.name / data.folderPath).`);
649
681
  const outputRow = (Array.isArray(data.outputs) ? data.outputs : [])
@@ -0,0 +1,36 @@
1
+ /**
2
+ * How a Case resource task names the resource it runs: the `resourceKey` its
3
+ * root binding pair shares, and the `folderPath` default the runtime reads.
4
+ *
5
+ * For a resource deployed to a tenant folder the two agree:
6
+ *
7
+ * folder 'Shared/Finance', name 'CreditRisk'
8
+ * -> resourceKey 'Shared/Finance.CreditRisk', folderPath 'Shared/Finance'
9
+ *
10
+ * For a sibling built inside the same solution they do not. Its folder is the
11
+ * `solution_folder` sentinel (what `uip maestro case registry search --local`
12
+ * and `resources/solution_folder/...` report), and the platform splits it:
13
+ *
14
+ * folder 'solution_folder', name 'CreditRisk'
15
+ * -> resourceKey 'solution_folder.CreditRisk', folderPath ''
16
+ *
17
+ * The key keeps the sentinel because deploy and `resources refresh` match the
18
+ * resource by it. The `folderPath` default is empty because the running case
19
+ * starts the sibling in its own deployed folder; a literal `solution_folder`
20
+ * there passes `validate` and fails the task at runtime with "Folder does not
21
+ * exist". The CLI encodes the same split: `case-bindings-service.ts` writes the
22
+ * sentinel into `bindings_v2.json` for a `solution_folder.` key, and
23
+ * `case-validate-service.ts` exempts that prefix from its key/folder agreement
24
+ * check.
25
+ */
26
+ export declare const SOLUTION_FOLDER = "solution_folder";
27
+ export interface ResourceIdentity {
28
+ /** The `resourceKey` both rows of the name/folderPath binding pair carry. */
29
+ resourceKey: string;
30
+ /** The `default` of the `folderPath` binding row. */
31
+ folderPath: string;
32
+ }
33
+ /** The binding identity of resource `name` authored in `folder`. */
34
+ export declare function resourceIdentity(name: string, folder: string): ResourceIdentity;
35
+ /** True for the key of a resource built inside the same solution. */
36
+ export declare function isInSolutionKey(resourceKey: string): boolean;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * How a Case resource task names the resource it runs: the `resourceKey` its
3
+ * root binding pair shares, and the `folderPath` default the runtime reads.
4
+ *
5
+ * For a resource deployed to a tenant folder the two agree:
6
+ *
7
+ * folder 'Shared/Finance', name 'CreditRisk'
8
+ * -> resourceKey 'Shared/Finance.CreditRisk', folderPath 'Shared/Finance'
9
+ *
10
+ * For a sibling built inside the same solution they do not. Its folder is the
11
+ * `solution_folder` sentinel (what `uip maestro case registry search --local`
12
+ * and `resources/solution_folder/...` report), and the platform splits it:
13
+ *
14
+ * folder 'solution_folder', name 'CreditRisk'
15
+ * -> resourceKey 'solution_folder.CreditRisk', folderPath ''
16
+ *
17
+ * The key keeps the sentinel because deploy and `resources refresh` match the
18
+ * resource by it. The `folderPath` default is empty because the running case
19
+ * starts the sibling in its own deployed folder; a literal `solution_folder`
20
+ * there passes `validate` and fails the task at runtime with "Folder does not
21
+ * exist". The CLI encodes the same split: `case-bindings-service.ts` writes the
22
+ * sentinel into `bindings_v2.json` for a `solution_folder.` key, and
23
+ * `case-validate-service.ts` exempts that prefix from its key/folder agreement
24
+ * check.
25
+ */
26
+ export const SOLUTION_FOLDER = 'solution_folder';
27
+ const SOLUTION_FOLDER_KEY_PREFIX = `${SOLUTION_FOLDER}.`;
28
+ /** The binding identity of resource `name` authored in `folder`. */
29
+ export function resourceIdentity(name, folder) {
30
+ if (folder === SOLUTION_FOLDER)
31
+ return { resourceKey: `${SOLUTION_FOLDER_KEY_PREFIX}${name}`, folderPath: '' };
32
+ return { resourceKey: `${folder}.${name}`, folderPath: folder };
33
+ }
34
+ /** True for the key of a resource built inside the same solution. */
35
+ export function isInSolutionKey(resourceKey) {
36
+ return resourceKey.startsWith(SOLUTION_FOLDER_KEY_PREFIX);
37
+ }
@@ -18,6 +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
22
  import { resolveSchemaVersion } from '../schema-version.js';
22
23
  import { caseJsonPreservation, caseJsonPreservationErrors, } from './preserve.js';
23
24
  import { readEventFilter, eventFilterProblem, eventFilterTreeLeaf } from '../core/event-filters.js';
@@ -248,7 +249,49 @@ export function serialize(built, opts = {}) {
248
249
  }
249
250
  : {}),
250
251
  };
251
- return applyAuthorLayout(applyPreservation(result, caseJsonPreservation(built)), built.layout);
252
+ return stampConnectorConfigVersion(applyAuthorLayout(applyPreservation(result, caseJsonPreservation(built)), built.layout));
253
+ }
254
+ /** The task types whose `data.context[]` carries an Integration Service activity configuration. */
255
+ const CONNECTOR_CONFIG_TASK_TYPES = new Set([
256
+ 'execute-connector-activity',
257
+ 'wait-for-connector',
258
+ 'external-agent',
259
+ 'external-workflow',
260
+ ]);
261
+ const V2_CONFIG_ROW = { name: 'activityConfigurationVersion', value: 'v2', type: 'string' };
262
+ /**
263
+ * Flagged-V2 plans: mark each connector task's and event trigger's context
264
+ * `activityConfigurationVersion: v2`, as the Designer and CLI do. Runs
265
+ * post-preservation because the flag is preserved metadata.
266
+ *
267
+ * Only a context bound to a connection is marked. A placeholder bag
268
+ * (`connectorKey` + `operation`, or a trigger's lone `objectName`) has no
269
+ * configuration panel to pick. A context that already carries the row keeps it.
270
+ */
271
+ function stampConnectorConfigVersion(result) {
272
+ const metadata = result.metadata;
273
+ if (metadata?.intsvcActivityConfig !== 'v2')
274
+ return result;
275
+ const stamp = (context) => {
276
+ if (!Array.isArray(context))
277
+ return;
278
+ if (!context.some((row) => row?.name === 'connection'))
279
+ return;
280
+ if (context.some((row) => row?.name === V2_CONFIG_ROW.name))
281
+ return;
282
+ context.push({ ...V2_CONFIG_ROW });
283
+ };
284
+ for (const node of result.nodes ?? []) {
285
+ if (node?.type === 'case-management:Trigger' || node?.type === 'uipath.case.trigger')
286
+ stamp(node.data?.[CASE_FORMAT_PROFILE.triggerBagField]?.context);
287
+ for (const lane of node?.data?.tasks ?? []) {
288
+ for (const task of Array.isArray(lane) ? lane : []) {
289
+ if (CONNECTOR_CONFIG_TASK_TYPES.has(task?.type))
290
+ stamp(task.data?.context);
291
+ }
292
+ }
293
+ }
294
+ return result;
252
295
  }
253
296
  const CASE_NODE_LAYOUT_FIELDS = ['position', 'style', 'measured', 'width', 'height', 'zIndex'];
254
297
  /**
@@ -1142,7 +1185,8 @@ function businessRuleBinding(resolver, ids, resourceKey, propertyAttribute, valu
1142
1185
  *
1143
1186
  * - `name` / `folderPath` are `=bindings.<id>` references to a `BusinessRule`
1144
1187
  * binding pair keyed `<folder>.<name>` (the runtime resolves the rule through
1145
- * them; `validate --strict` refuses literals);
1188
+ * them; `validate --strict` refuses literals). An in-solution rule is keyed
1189
+ * `solution_folder.<name>` with an empty folderPath default;
1146
1190
  * - `version: 'v3'`;
1147
1191
  * - `outputs` always starts with `output` (`source: '=result'`, the whole
1148
1192
  * decision-keyed result) and `Error` (`source: '=Error'`), followed by the
@@ -1151,9 +1195,11 @@ function businessRuleBinding(resolver, ids, resourceKey, propertyAttribute, valu
1151
1195
  */
1152
1196
  function businessRuleTaskData(task, resolver, elementId, ids) {
1153
1197
  const ref = task.ref;
1154
- const resourceKey = `${ref.folderPath}.${ref.name}`;
1198
+ // An in-solution rule (`folder: 'solution_folder'`) keeps the sentinel in its
1199
+ // key and binds an empty folderPath — see resource-identity.ts.
1200
+ const { resourceKey, folderPath } = resourceIdentity(ref.name, ref.folderPath);
1155
1201
  const nameId = businessRuleBinding(resolver, ids, resourceKey, 'name', ref.name);
1156
- const folderId = businessRuleBinding(resolver, ids, resourceKey, 'folderPath', ref.folderPath);
1202
+ const folderId = businessRuleBinding(resolver, ids, resourceKey, 'folderPath', folderPath);
1157
1203
  const ownRow = (name, source, seed, extra = {}) => {
1158
1204
  const slot = ids.make('v', 8, `${elementId}:${seed}`);
1159
1205
  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.0",
3
+ "version": "6.46.2",
4
4
  "description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.uipath.com/maestro",
@@ -93,5 +93,5 @@
93
93
  "@types/node": "^22.7.0",
94
94
  "esbuild": "^0.28.1"
95
95
  },
96
- "gitref": "b61f819d0271c38d3231a0183daeb2a5f880f9a4"
96
+ "gitref": "c3c40cdfae94553b2d63ec072fec2c1b1f45f805"
97
97
  }