@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/api-index.md +7 -7
- package/dist/api-members.md +30 -28
- package/dist/core/refusals.d.ts +3 -0
- package/dist/core/refusals.js +3 -0
- package/dist/edit-scope.d.ts +180 -0
- package/dist/edit-scope.js +251 -0
- package/dist/merge-cli.js +4 -2
- package/dist/merge-compile.d.ts +5 -88
- package/dist/merge-compile.js +8 -138
- package/dist/merge.d.ts +1 -61
- package/dist/merge.js +1 -112
- package/dist/nodes/process/flow/check.js +21 -2
- package/dist/nodes/process/flow/node-type.js +16 -6
- package/dist/nodes/process/flow/spec.d.ts +27 -2
- package/package.json +2 -2
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
|
-
|
|
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'
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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({
|
|
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
|
|
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.
|
|
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": "
|
|
99
|
+
"gitref": "ddebc637dab91373bb326fd4da5590f6e7d3b9c2"
|
|
100
100
|
}
|