@uipath/maestro-builder-sdk 6.58.0 → 6.59.0

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/dist/merge.js CHANGED
@@ -95,119 +95,8 @@ import { placeAddedNodes } from './node-placement.js';
95
95
  import { mergeAgentSidecars } from './merge-sidecars.js';
96
96
  import { embeddedAgentFindings, isEmbeddedAgent, mergeEmbeddedAgentNode } from './merge-embedded-agent.js';
97
97
  import { mergeObject, mergeVariables, stableJson } from './merge-variables.js';
98
+ import { byId, isKept, nodeUnchanged, subflowsTakenFromEdited } from './edit-scope.js';
98
99
  export { MergeRefusedError } from './merge-roundtrip.js';
99
- function byId(nodes) {
100
- return new Map((nodes ?? []).map((n) => [n.id, n]));
101
- }
102
- /**
103
- * Whether two nodes are the SAME as far as the author is concerned — same type,
104
- * same inputs and same output mappings. Layout (`ui`), `display`, `typeVersion`,
105
- * and the derived parts of `outputs` (type, schema, description) are ignored,
106
- * because those are exactly the parts decompile does not carry through. An
107
- * output's `source` is authored, not derived: it is what an End returns, so an
108
- * edit to `.return({ ... })` changes nothing else on the node. Used only to
109
- * decide "did the user edit this node?" against the baseline.
110
- */
111
- function nodeUnchanged(a, b) {
112
- if (!a || !b)
113
- return false;
114
- if (a.type !== b.type)
115
- return false;
116
- return JSON.stringify(a.inputs ?? {}) === JSON.stringify(b.inputs ?? {})
117
- && JSON.stringify(outputSources(a)) === JSON.stringify(outputSources(b));
118
- }
119
- /** Each output's `source` by output name; outputs without one are left out. */
120
- function outputSources(n) {
121
- const sources = {};
122
- for (const [name, output] of Object.entries(n.outputs ?? {})) {
123
- if (output && typeof output === 'object' && 'source' in output)
124
- sources[name] = output.source;
125
- }
126
- return sources;
127
- }
128
- /**
129
- * The ids of the root nodes {@link mergeFlow} takes from `edited` rather than
130
- * restoring from `original`: every node that is new, or that differs from its
131
- * `baseline` counterpart. With no baseline, that is every node.
132
- *
133
- * For instance, with a baseline equal to `edited` and every node present in
134
- * `original`, the answer is `[]` — a no-edit merge keeps them all.
135
- */
136
- export function nodesTakenFromEdited(original, edited, baseline) {
137
- const origNodes = byId(original.nodes);
138
- const baseNodes = byId(baseline?.nodes);
139
- return (edited.nodes ?? []).filter((e) => !isKept(e, origNodes, baseNodes, baseline)).map((e) => e.id);
140
- }
141
- /**
142
- * The keys of the subflows {@link mergeFlow} takes from `edited` rather than
143
- * restoring from `original`: every subflow that is new, that the original does
144
- * not have, or that differs from its `baseline` counterpart in any way the
145
- * author can edit — a node's inputs, output sources or label, an edge, the
146
- * scope's `variables` or `layout` (`subflowUnchanged`, #1322). With no
147
- * baseline, that is every subflow. A kept subflow is the original's whole: its
148
- * nodes, edges, variables and layout.
149
- */
150
- export function subflowsTakenFromEdited(original, edited, baseline) {
151
- return Object.keys(edited.subflows ?? {}).filter((key) => {
152
- const orig = original.subflows?.[key];
153
- const base = baseline?.subflows?.[key];
154
- return !baseline || orig === undefined || base === undefined
155
- || !subflowUnchanged(edited.subflows[key], base);
156
- });
157
- }
158
- /** A node's canvas label, when it has one. */
159
- const labelOf = (n) => n?.display?.label;
160
- /**
161
- * Whether an edited subflow equals its baseline: the same node ids, each node
162
- * unchanged (`nodeUnchanged`) and under the same label, the same edges by
163
- * their endpoints and handles (ids are compile's own), and the same
164
- * `variables` and `layout` byte for byte. The scope's variables carry what the
165
- * nodes do not — a step's `{ updates }` row, a child `.input()` type, a `.var()`
166
- * default — and a kept subflow is the original's whole, so any of them changing
167
- * has to make the subflow a taken one.
168
- */
169
- function subflowUnchanged(e, b) {
170
- const eNodes = byId(e.nodes);
171
- const bNodes = byId(b.nodes);
172
- if (eNodes.size !== bNodes.size)
173
- return false;
174
- for (const [id, n] of eNodes) {
175
- const base = bNodes.get(id);
176
- if (!nodeUnchanged(n, base) || labelOf(n) !== labelOf(base))
177
- return false;
178
- }
179
- if (JSON.stringify(e.variables ?? null) !== JSON.stringify(b.variables ?? null))
180
- return false;
181
- // `layout.edges` is keyed by edge id, and ids come from one file-wide
182
- // counter: a step added before the call shifts every id in the subflow
183
- // without touching it. So routes are compared by the endpoints the id names
184
- // (as the edges themselves are), and `layout.nodes` as it is.
185
- if (JSON.stringify(layoutByEndpoints(e)) !== JSON.stringify(layoutByEndpoints(b)))
186
- return false;
187
- const eEdges = (e.edges ?? []).map(edgeKey).sort();
188
- const bEdges = (b.edges ?? []).map(edgeKey).sort();
189
- return eEdges.length === bEdges.length && eEdges.every((k, i) => k === bEdges[i]);
190
- }
191
- /** `<source>@<port>-><target>@<port>`: an edge's identity apart from its id. */
192
- const edgeKey = (x) => `${x.sourceNodeId}@${x.sourcePort}->${x.targetNodeId}@${x.targetPort}`;
193
- /** A scope's layout with its `edges` re-keyed by endpoints, so an id shift does not read as a change. */
194
- function layoutByEndpoints(sf) {
195
- const layout = sf.layout;
196
- if (!layout || typeof layout !== 'object')
197
- return layout ?? null;
198
- const byId = new Map((sf.edges ?? []).map((x) => [x.id, edgeKey(x)]));
199
- const { edges, ...rest } = layout;
200
- const routes = Object.entries(edges ?? {}).map(([id, route]) => [byId.get(id) ?? id, route]).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
201
- return { ...rest, edges: Object.fromEntries(routes) };
202
- }
203
- /**
204
- * Whether merge restores `e`'s ORIGINAL node instead of taking `e`: there is a
205
- * baseline, the original has the node, and `e` equals its baseline counterpart.
206
- * {@link mergeFlow}'s node loop and {@link nodesTakenFromEdited} both ask this.
207
- */
208
- function isKept(e, origNodes, baseNodes, baseline) {
209
- return !!baseline && origNodes.has(e.id) && nodeUnchanged(e, baseNodes.get(e.id));
210
- }
211
100
  /**
212
101
  * Give an edited node the original's position, by id.
213
102
  *
@@ -42,6 +42,9 @@ export const PROCESS_DIAGNOSTICS = {
42
42
  APIWF_READ_WITHOUT_RETURNS: { severity: 'error', summary: 'an API workflow step field read with no returns' },
43
43
  FUNCTION_EMPTY_RETURNS: { severity: 'warning', summary: 'a function step with returns: {}' },
44
44
  FUNCTION_KEY_NOT_KEY: { severity: 'error', summary: 'a function step whose key is not a GUID' },
45
+ FUNCTION_LOCAL_HAS_FOLDER: { severity: 'error', summary: 'a function step in-solution, with a folderPath' },
46
+ FUNCTION_LOCAL_NO_PROJECT_ID: { severity: 'error', summary: 'a function step in-solution, with no projectId' },
47
+ FUNCTION_LOCAL_PROJECT_ID_NOT_GUID: { severity: 'error', summary: 'a function step in-solution, with a projectId that is not a GUID' },
45
48
  FUNCTION_NAME_IS_PATH: { severity: 'error', summary: 'a function step whose name looks like a folder path' },
46
49
  FUNCTION_NO_FOLDER: { severity: 'error', summary: 'a function step with no folderPath' },
47
50
  FUNCTION_NO_KEY: { severity: 'error', summary: 'a function step with no key' },
@@ -220,10 +223,26 @@ export function checkPublishedFunction(inputs, ctx) {
220
223
  at('FUNCTION_NO_NAME', `Function step "${step}" has no \`name\`. The runtime resolves the function by name + folder, not by the `
221
224
  + `key in the node type, so without it the dispatch fails before it starts.`, `name: 'acme-echo'`);
222
225
  }
223
- if (blank(inputs?.folderPath)) {
226
+ const inSolution = inputs?.location === 'in-solution';
227
+ if (!inSolution && blank(inputs?.folderPath)) {
224
228
  at('FUNCTION_NO_FOLDER', `Function step "${step}" has no \`folderPath\`. The runtime resolves the function by name + folder — and a `
225
229
  + `function is often deployed into a folder of its OWN name, so read the folder from the tenant rather `
226
- + `than assuming 'Shared'.`, `folderPath: 'Shared/acme-echo'`);
230
+ + `than assuming 'Shared'. For a coded function in the SAME solution, set \`location: 'in-solution'\` `
231
+ + `and its \`projectId\` instead.`, `folderPath: 'Shared/acme-echo'`);
232
+ }
233
+ if (inSolution && blank(inputs?.projectId)) {
234
+ at('FUNCTION_LOCAL_NO_PROJECT_ID', `In-solution function step "${step}" has no \`projectId\`. Read \`resource.projectKey\` from `
235
+ + `\`resources/solution_folder/process/function/<project>.json\`.`, `projectId: '<resource.projectKey>'`);
236
+ }
237
+ else if (inSolution && !GUID_RE.test(String(inputs.projectId).trim())) {
238
+ at('FUNCTION_LOCAL_PROJECT_ID_NOT_GUID', `In-solution function step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
239
+ + `the sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
240
+ }
241
+ if (inSolution && !blank(inputs?.folderPath)) {
242
+ at('FUNCTION_LOCAL_HAS_FOLDER', `In-solution function step "${step}" also sets \`folderPath\` ${JSON.stringify(inputs.folderPath)}. An `
243
+ + `in-solution sibling has no folder until the solution is deployed — the platform's own manifest `
244
+ + `writes \`""\` — so the value would be dropped. Remove it, or drop \`location: 'in-solution'\` to `
245
+ + `call the published copy.`);
227
246
  }
228
247
  const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
229
248
  if (name.includes('/')) {
@@ -257,7 +257,12 @@ const FUNCTION_PREFIX = 'uipath.core.function.';
257
257
  * alongside `output` (gated on `errorHandlingEnabled`, as everywhere else).
258
258
  */
259
259
  function publishedFunctionDef(spec) {
260
- const resourceKey = `${spec.folderPath}.${spec.name}`;
260
+ // The location slot the other process resources share: an in-solution
261
+ // sibling (VS Code 1.204.25's `uipath.core.function.<key>` against the
262
+ // solution's own coded function) is the bare key, folder `''`, no
263
+ // `section`, and `projectId`/`projectName`/`isInSolution` on the model.
264
+ const loc = processResourceLocation(spec);
265
+ const { resourceKey } = loc;
261
266
  const properties = {};
262
267
  for (const [k, v] of Object.entries(spec.inputs)) {
263
268
  properties[k] = { type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' };
@@ -266,8 +271,8 @@ function publishedFunctionDef(spec) {
266
271
  nodeType: `${FUNCTION_PREFIX}${spec.key}`,
267
272
  version: '1.0.0',
268
273
  category: 'function',
269
- description: `(${spec.folderPath})`,
270
- tags: [],
274
+ description: loc.description,
275
+ tags: loc.tags,
271
276
  sortOrder: resourceNodeSortOrder('function'),
272
277
  supportsErrorHandling: true,
273
278
  display: { label: spec.name, icon: 'function' },
@@ -285,11 +290,12 @@ function publishedFunctionDef(spec) {
285
290
  ],
286
291
  },
287
292
  ],
293
+ ...loc.definitionExtras,
288
294
  model: {
289
295
  type: 'bpmn:ServiceTask',
290
296
  serviceType: 'Orchestrator.ExecuteFunctionAsync',
291
297
  version: 'v2',
292
- section: 'Published',
298
+ ...(loc.inSolution ? {} : { section: 'Published' }),
293
299
  listingVersion: '1.0.0',
294
300
  bindings: {
295
301
  resource: 'process',
@@ -298,9 +304,10 @@ function publishedFunctionDef(spec) {
298
304
  orchestratorType: 'function',
299
305
  values: [
300
306
  { name: 'name', propertyAttribute: 'name', default: spec.name },
301
- { name: 'folderPath', propertyAttribute: 'folderPath', default: spec.folderPath },
307
+ { name: 'folderPath', propertyAttribute: 'folderPath', default: loc.folderPath },
302
308
  ],
303
309
  },
310
+ ...loc.modelExtras,
304
311
  context: [
305
312
  { name: 'name', type: 'string', value: '<bindings.name>' },
306
313
  { name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
@@ -598,7 +605,10 @@ export function processDefinition(spec) {
598
605
  const s = (spec.inputs ?? {});
599
606
  switch (spec.kind) {
600
607
  case 'publishedFunction':
601
- return publishedFunctionDef({ key: s.key, name: s.name, folderPath: s.folderPath, inputs: s.inputs ?? {}, returns: s.returns });
608
+ return publishedFunctionDef({
609
+ key: s.key, name: s.name, folderPath: s.folderPath, location: s.location, projectId: s.projectId,
610
+ inputs: s.inputs ?? {}, returns: s.returns,
611
+ });
602
612
  case 'apiWorkflow':
603
613
  return apiWorkflowDef({ key: s.key, name: s.name, folderPath: s.folderPath, inputs: s.inputs ?? {}, returns: s.returns });
604
614
  case 'agenticProcess':
@@ -111,17 +111,42 @@ export interface PublishedFunctionInputs {
111
111
  * `uipath.core.function.<key>`. Find it with
112
112
  * `uip maestro flow registry search "uipath.core.function"` (after
113
113
  * `registry pull --force`) or the Orchestrator process listing.
114
+ *
115
+ * For an in-solution sibling it is `resource.key` from
116
+ * `resources/solution_folder/process/function/<project>.json` — the suffix of
117
+ * the node type `uip maestro flow registry list --local` shows.
114
118
  */
115
119
  key: string;
116
120
  /** The function's name in Orchestrator, e.g. `'acme-echo'`. */
117
121
  name: string;
122
+ /**
123
+ * Where the function lives. `'published'` (the default) is a function
124
+ * deployed to `folderPath`. `'in-solution'` is a sibling coded-function
125
+ * project of the same solution: it needs `projectId` and no `folderPath`, and
126
+ * `key` is that sibling's `resource.key` from
127
+ * `resources/solution_folder/process/function/<project>.json`.
128
+ */
129
+ location?: ProcessLocation;
118
130
  /**
119
131
  * The Orchestrator folder holding it. A function is usually deployed into a
120
132
  * folder of its OWN name (e.g. `'Shared/acme-echo'`), and the binding's
121
133
  * `resourceKey` is `<folderPath>.<name>` — copy the folder from the tenant
122
- * rather than assuming `'Shared'`.
134
+ * rather than assuming `'Shared'`. Required for a published function; omit
135
+ * it for an in-solution sibling.
136
+ *
137
+ * @enforcedBy FUNCTION_NO_FOLDER A published function needs its folder.
138
+ * @enforcedBy FUNCTION_LOCAL_HAS_FOLDER An in-solution function has no folder.
123
139
  */
124
- folderPath: string;
140
+ folderPath?: string;
141
+ /**
142
+ * The sibling function project's `resource.projectKey`, read from
143
+ * `resources/solution_folder/process/function/<project>.json`. Required only
144
+ * when `location` is `'in-solution'`.
145
+ *
146
+ * @enforcedBy FUNCTION_LOCAL_NO_PROJECT_ID An in-solution function needs its project id.
147
+ * @enforcedBy FUNCTION_LOCAL_PROJECT_ID_NOT_GUID The project id is the sibling's GUID, not its name.
148
+ */
149
+ projectId?: string;
125
150
  /** The function's own input arguments, by its own names. */
126
151
  inputs?: Record<string, unknown>;
127
152
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.58.0",
3
+ "version": "6.59.0",
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": "d70ce10e11def3cbb2d1d42ba52c76686dcfae2f"
99
+ "gitref": "ddebc637dab91373bb326fd4da5590f6e7d3b9c2"
100
100
  }