@uipath/maestro-builder-sdk 6.68.3 → 6.68.4
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/bindings-v2.d.ts +2 -9
- package/dist/bindings-v2.js +10 -20
- package/dist/case/bindings-v2.d.ts +3 -0
- package/dist/case/bindings-v2.js +281 -0
- package/dist/case/compile-cli.js +7 -201
- package/dist/core/bindings-v2-resource.d.ts +51 -0
- package/dist/core/bindings-v2-resource.js +50 -0
- package/package.json +2 -2
package/dist/bindings-v2.d.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import type { ConnectionUse } from './core/compile-refusal.js';
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
resource: string;
|
|
5
|
-
key: string;
|
|
6
|
-
id?: string;
|
|
7
|
-
value: Record<string, unknown>;
|
|
8
|
-
metadata: Record<string, unknown>;
|
|
9
|
-
[k: string]: unknown;
|
|
10
|
-
}
|
|
2
|
+
import { type BindingsV2Resource } from './core/bindings-v2-resource.js';
|
|
3
|
+
export type { BindingsV2Resource };
|
|
11
4
|
/** The `bindings_v2.json` document. */
|
|
12
5
|
export interface BindingsV2Document {
|
|
13
6
|
version: string;
|
package/dist/bindings-v2.js
CHANGED
|
@@ -55,10 +55,11 @@
|
|
|
55
55
|
*/
|
|
56
56
|
import { BindingRefusal, connectionConnectorConflictMessage } from './core/binding-messages.js';
|
|
57
57
|
import { isRecord } from './core/node-envelope.js';
|
|
58
|
+
import { BINDINGS_V2_DOCUMENT_VERSION, connectionResource, RESOURCE_BINDINGS_VERSION, } from './core/bindings-v2-resource.js';
|
|
58
59
|
/** The document version pack emits. */
|
|
59
|
-
const DOCUMENT_VERSION =
|
|
60
|
+
const DOCUMENT_VERSION = BINDINGS_V2_DOCUMENT_VERSION;
|
|
60
61
|
/** The per-resource metadata version pack stamps. */
|
|
61
|
-
const BINDINGS_VERSION =
|
|
62
|
+
const BINDINGS_VERSION = RESOURCE_BINDINGS_VERSION;
|
|
62
63
|
function nonEmptyString(value) {
|
|
63
64
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
64
65
|
}
|
|
@@ -235,26 +236,15 @@ export function connectionResources(flow) {
|
|
|
235
236
|
const binding = candidates[0];
|
|
236
237
|
const displayLabel = nonEmptyString(binding.name) ?? key;
|
|
237
238
|
const label = isRecord(node.display) ? nonEmptyString(node.display.label) : undefined;
|
|
238
|
-
|
|
239
|
+
// The same row Case writes (core/bindings-v2-resource.ts); pack writes no folderKey slot.
|
|
240
|
+
resources.push(connectionResource({
|
|
239
241
|
resource: 'connection',
|
|
240
242
|
key,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
isExpression: false,
|
|
247
|
-
displayName: displayLabel,
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
metadata: {
|
|
251
|
-
ActivityName: label ?? nonEmptyString(node.id) ?? key,
|
|
252
|
-
BindingsVersion: BINDINGS_VERSION,
|
|
253
|
-
DisplayLabel: displayLabel,
|
|
254
|
-
UseConnectionService: 'true',
|
|
255
|
-
Connector: connector,
|
|
256
|
-
},
|
|
257
|
-
});
|
|
243
|
+
connectionId: nonEmptyString(binding.default) ?? key,
|
|
244
|
+
displayLabel,
|
|
245
|
+
activityName: label ?? nonEmptyString(node.id) ?? key,
|
|
246
|
+
connector,
|
|
247
|
+
}));
|
|
258
248
|
}
|
|
259
249
|
return resources;
|
|
260
250
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type BindingsV2Resource } from '../core/bindings-v2-resource.js';
|
|
2
|
+
/** Every resource the caseplan's root bindings declare, in the order their rows first appear. */
|
|
3
|
+
export declare function deriveCaseBindingsV2(caseplan: Record<string, unknown>): BindingsV2Resource[];
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `bindings_v2.json` resources a caseplan's root `bindings[]` rows declare:
|
|
3
|
+
* the root-row pass of `uip maestro case bindings sync` (`deriveCaseBindingsV2`
|
|
4
|
+
* in cli `packages/case-tool/src/services/case-bindings-service.ts`). It is
|
|
5
|
+
* NOT the whole CLI function; see "Not ported" below.
|
|
6
|
+
*
|
|
7
|
+
* `uip maestro case validate --strict` re-derives the sidecar with that
|
|
8
|
+
* function and compares it to the file on disk entry by entry, as serialized
|
|
9
|
+
* JSON (`STRICT_BINDINGS_SIDECAR_STALE`), so field order matters as well as
|
|
10
|
+
* field values. The CLI is a restricted package the SDK cannot depend on, so
|
|
11
|
+
* this is a copy; `tests/case-bindings-v2-parity.test.ts` holds it to the
|
|
12
|
+
* CLI's own output, one resource kind at a time, from a fixture the real CLI
|
|
13
|
+
* function derived. Re-record that fixture with
|
|
14
|
+
* `bun typescript/tests/fixtures/case-bindings-v2/record.ts <uipath-cli checkout>`.
|
|
15
|
+
*
|
|
16
|
+
* Every entry comes from the root `bindings[]` rows, grouped by
|
|
17
|
+
* `(resource, resourceKey)`:
|
|
18
|
+
*
|
|
19
|
+
* - `Connection` → the connection row Flow writes too
|
|
20
|
+
* ({@link connectionResource}); its connector and activity label come from
|
|
21
|
+
* the connector context that references the row's binding id.
|
|
22
|
+
* - `app` / `process` / `ixpDeployment` → the Case Designer shape.
|
|
23
|
+
* - any other resource → the plain shape.
|
|
24
|
+
*
|
|
25
|
+
* Not ported — the CLI function's other passes:
|
|
26
|
+
*
|
|
27
|
+
* - `EventTrigger` / `Property` entries for a connector start trigger
|
|
28
|
+
* (`deriveEventTriggerResources`). The SDK writes these triggers, so this
|
|
29
|
+
* is a live gap: builder-sdk#1557, a known-gap row in the parity test.
|
|
30
|
+
* - The stored-binding overlay: entries a connector task keeps in its
|
|
31
|
+
* `metadata` context's `body.bindings`, merged into the derived entry's
|
|
32
|
+
* metadata or appended. The serializer's connector `metadata` body has no
|
|
33
|
+
* `bindings` key, so there is nothing to overlay today.
|
|
34
|
+
* - `Role` entries from `metadata.caseRoles`. The SDK neither authors nor
|
|
35
|
+
* preserves `caseRoles` (it is not in preserve.ts's metadata keys).
|
|
36
|
+
*
|
|
37
|
+
* If the serializer starts writing either of the last two, port the pass and
|
|
38
|
+
* add a task for it to the parity fixture (`fixtures/case-bindings-v2/plan.ts`).
|
|
39
|
+
*/
|
|
40
|
+
import { isRecord } from '../core/node-envelope.js';
|
|
41
|
+
import { connectionResource, RESOURCE_BINDINGS_VERSION, } from '../core/bindings-v2-resource.js';
|
|
42
|
+
import { isInSolutionKey, SOLUTION_FOLDER } from './resource-identity.js';
|
|
43
|
+
const CONNECTION_RESOURCE = 'Connection';
|
|
44
|
+
/** The placeholder a manual-mode http-request task binds; nothing declares it. */
|
|
45
|
+
const IMPLICIT_CONNECTION = 'ImplicitConnection';
|
|
46
|
+
const CONNECTION_ID_ATTRIBUTE = 'ConnectionId';
|
|
47
|
+
const FOLDER_KEY_ATTRIBUTE = 'folderKey';
|
|
48
|
+
const BINDING_REFERENCE_PREFIX = '=bindings.';
|
|
49
|
+
function asBindingRow(value) {
|
|
50
|
+
if (!isRecord(value))
|
|
51
|
+
return undefined;
|
|
52
|
+
return typeof value.id === 'string'
|
|
53
|
+
&& typeof value.resource === 'string'
|
|
54
|
+
&& typeof value.resourceKey === 'string'
|
|
55
|
+
&& value.resourceKey !== ''
|
|
56
|
+
&& typeof value.propertyAttribute === 'string'
|
|
57
|
+
&& (value.default === undefined || typeof value.default === 'string')
|
|
58
|
+
? value
|
|
59
|
+
: undefined;
|
|
60
|
+
}
|
|
61
|
+
/** Every resource the caseplan's root bindings declare, in the order their rows first appear. */
|
|
62
|
+
export function deriveCaseBindingsV2(caseplan) {
|
|
63
|
+
const groups = new Map();
|
|
64
|
+
for (const entry of Array.isArray(caseplan.bindings) ? caseplan.bindings : []) {
|
|
65
|
+
const binding = asBindingRow(entry);
|
|
66
|
+
if (!binding)
|
|
67
|
+
continue;
|
|
68
|
+
if (binding.resource === CONNECTION_RESOURCE && binding.resourceKey === IMPLICIT_CONNECTION)
|
|
69
|
+
continue;
|
|
70
|
+
const groupKey = `${binding.resource}:${binding.resourceKey}`;
|
|
71
|
+
const group = groups.get(groupKey);
|
|
72
|
+
if (group)
|
|
73
|
+
group.push(binding);
|
|
74
|
+
else
|
|
75
|
+
groups.set(groupKey, [binding]);
|
|
76
|
+
}
|
|
77
|
+
const connectorUses = collectConnectorUses(caseplan);
|
|
78
|
+
const labels = bindingLabels(caseplan.nodes);
|
|
79
|
+
return [...groups.values()].map((group) => group[0].resource === CONNECTION_RESOURCE
|
|
80
|
+
? connectionEntry(group, connectorUses)
|
|
81
|
+
: resourceEntry(group, labels));
|
|
82
|
+
}
|
|
83
|
+
function attributeDefault(group, attribute) {
|
|
84
|
+
return group.find((binding) => binding.propertyAttribute === attribute)?.default;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A connection: its `ConnectionId` row labels it, its `folderKey` row (keyed
|
|
88
|
+
* by the connection, not the folder) gives the folder slot.
|
|
89
|
+
*/
|
|
90
|
+
function connectionEntry(group, uses) {
|
|
91
|
+
const key = group[0].resourceKey;
|
|
92
|
+
const use = group.map((binding) => uses.get(binding.id)).find((found) => found !== undefined);
|
|
93
|
+
const label = (group.find((binding) => binding.propertyAttribute === CONNECTION_ID_ATTRIBUTE) ?? group[0]).name;
|
|
94
|
+
return connectionResource({
|
|
95
|
+
resource: CONNECTION_RESOURCE,
|
|
96
|
+
key,
|
|
97
|
+
connectionId: attributeDefault(group, CONNECTION_ID_ATTRIBUTE) ?? key,
|
|
98
|
+
...(typeof label === 'string' && label !== '' && { displayLabel: label }),
|
|
99
|
+
folderKey: attributeDefault(group, FOLDER_KEY_ATTRIBUTE),
|
|
100
|
+
activityName: use?.activityName,
|
|
101
|
+
connector: use?.connectorKey,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The Case Designer's label for each value slot of an app or process entry.
|
|
106
|
+
* These resources get the Designer shape (cli#5003, MST-16424): an `id`, each
|
|
107
|
+
* slot flagged and labelled, and the `BindingsVersion` / `SolutionsSupport`
|
|
108
|
+
* metadata without which deploy cannot override the entry.
|
|
109
|
+
*/
|
|
110
|
+
const DESIGNER_SLOT_NAMES = new Map([
|
|
111
|
+
['app', { name: 'App name', folderPath: 'Folder path', solutionsSupport: true }],
|
|
112
|
+
['process', { name: 'Process name', folderPath: 'Folder path', solutionsSupport: true }],
|
|
113
|
+
['ixpDeployment', { name: 'name', folderPath: 'Folder path', solutionsSupport: false }],
|
|
114
|
+
]);
|
|
115
|
+
/**
|
|
116
|
+
* One sidecar entry for a group of root binding rows that share a resource and
|
|
117
|
+
* a resource key. The subtype comes from the rows only: a Flow's rows carry
|
|
118
|
+
* none, so its entry has none either, as the Designer saves it.
|
|
119
|
+
*
|
|
120
|
+
* `metadata.ActivityName` labels a Designer-shaped entry with the first task
|
|
121
|
+
* that references one of its rows ({@link bindingLabels}).
|
|
122
|
+
*/
|
|
123
|
+
function resourceEntry(group, labels) {
|
|
124
|
+
const resource = group[0].resource;
|
|
125
|
+
const key = group[0].resourceKey;
|
|
126
|
+
// An in-solution resource binds an empty folderPath in the caseplan, while
|
|
127
|
+
// the sidecar names its folder by the sentinel the key carries.
|
|
128
|
+
const folderPath = isInSolutionKey(key) ? SOLUTION_FOLDER : attributeDefault(group, 'folderPath');
|
|
129
|
+
const name = attributeDefault(group, 'name');
|
|
130
|
+
const subType = group.map((binding) => binding.resourceSubType).find((found) => found !== undefined);
|
|
131
|
+
const designer = DESIGNER_SLOT_NAMES.get(resource);
|
|
132
|
+
if (designer) {
|
|
133
|
+
const value = {};
|
|
134
|
+
if (folderPath !== undefined) {
|
|
135
|
+
value.folderPath = { defaultValue: folderPath, isExpression: false, displayName: designer.folderPath };
|
|
136
|
+
}
|
|
137
|
+
if (name !== undefined) {
|
|
138
|
+
value.name = { defaultValue: name, isExpression: false, displayName: designer.name };
|
|
139
|
+
}
|
|
140
|
+
const activityName = group.map((binding) => labels.get(binding.id)).find((found) => found !== undefined);
|
|
141
|
+
return {
|
|
142
|
+
resource,
|
|
143
|
+
key,
|
|
144
|
+
id: `${resource}${key}`,
|
|
145
|
+
value,
|
|
146
|
+
metadata: {
|
|
147
|
+
...(activityName !== undefined && { ActivityName: activityName }),
|
|
148
|
+
BindingsVersion: RESOURCE_BINDINGS_VERSION,
|
|
149
|
+
DisplayLabel: 'name',
|
|
150
|
+
...(designer.solutionsSupport && { SolutionsSupport: 'true' }),
|
|
151
|
+
...(subType !== undefined && { subType }),
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const value = {};
|
|
156
|
+
if (name !== undefined)
|
|
157
|
+
value.name = { defaultValue: name };
|
|
158
|
+
if (folderPath !== undefined)
|
|
159
|
+
value.folderPath = { defaultValue: folderPath };
|
|
160
|
+
// A solution-resource picker (such as a Data Fabric entity) carries its folder key as a slot of its own.
|
|
161
|
+
const folderKey = attributeDefault(group, FOLDER_KEY_ATTRIBUTE);
|
|
162
|
+
if (folderKey !== undefined)
|
|
163
|
+
value.folderKey = { defaultValue: folderKey };
|
|
164
|
+
return {
|
|
165
|
+
resource,
|
|
166
|
+
key,
|
|
167
|
+
value,
|
|
168
|
+
metadata: {
|
|
169
|
+
BindingsVersion: RESOURCE_BINDINGS_VERSION,
|
|
170
|
+
...(subType !== undefined && { subType }),
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** A task's or condition's `displayName`, a trigger's `display.label`, or a legacy node's `label`. */
|
|
175
|
+
function ownLabel(node) {
|
|
176
|
+
const display = isRecord(node.display) ? node.display.label : undefined;
|
|
177
|
+
return [node.displayName, display, node.label].find((label) => typeof label === 'string' && label !== '');
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Every value under `root`, each with the label of the nearest object above it
|
|
181
|
+
* that names itself ({@link ownLabel}) — the CLI's `walkLabelled`.
|
|
182
|
+
*/
|
|
183
|
+
function walkLabelled(root, visit) {
|
|
184
|
+
const walk = (node, label) => {
|
|
185
|
+
visit(node, label);
|
|
186
|
+
if (Array.isArray(node)) {
|
|
187
|
+
for (const item of node)
|
|
188
|
+
walk(item, label);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (!isRecord(node))
|
|
192
|
+
return;
|
|
193
|
+
const scoped = ownLabel(node) ?? label;
|
|
194
|
+
for (const value of Object.values(node))
|
|
195
|
+
walk(value, scoped);
|
|
196
|
+
};
|
|
197
|
+
walk(root, undefined);
|
|
198
|
+
}
|
|
199
|
+
/** A `{ name, value }` context array that names a connector, by entry name; else undefined. */
|
|
200
|
+
function connectorContext(value) {
|
|
201
|
+
if (!Array.isArray(value))
|
|
202
|
+
return undefined;
|
|
203
|
+
const byName = new Map();
|
|
204
|
+
for (const entry of value) {
|
|
205
|
+
if (!isRecord(entry) || typeof entry.name !== 'string')
|
|
206
|
+
return undefined;
|
|
207
|
+
if (typeof entry.value === 'string')
|
|
208
|
+
byName.set(entry.name, entry.value);
|
|
209
|
+
}
|
|
210
|
+
return byName.has('connectorKey') ? byName : undefined;
|
|
211
|
+
}
|
|
212
|
+
function referencedBindingId(context) {
|
|
213
|
+
const connection = context?.get('connection');
|
|
214
|
+
return connection?.startsWith(BINDING_REFERENCE_PREFIX)
|
|
215
|
+
? connection.slice(BINDING_REFERENCE_PREFIX.length)
|
|
216
|
+
: undefined;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The connector whose authentication an http-request task reuses: its `body`
|
|
220
|
+
* input's `targetConnector`. Its context names the generic HTTP connector
|
|
221
|
+
* whatever the request calls, while the connection belongs to the target.
|
|
222
|
+
*/
|
|
223
|
+
function httpTargetConnector(node) {
|
|
224
|
+
if (!isRecord(node) || node.type !== 'http-request' || !isRecord(node.data) || !Array.isArray(node.data.inputs)) {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
for (const input of node.data.inputs) {
|
|
228
|
+
if (!isRecord(input) || input.name !== 'body' || !isRecord(input.body))
|
|
229
|
+
continue;
|
|
230
|
+
const target = input.body.targetConnector;
|
|
231
|
+
if (typeof target === 'string' && target !== '')
|
|
232
|
+
return target;
|
|
233
|
+
}
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The connector of each connection binding, keyed by binding id, from the
|
|
238
|
+
* connector contexts that reference it (`connection: "=bindings.<id>"`). The
|
|
239
|
+
* first use wins, as the CLI's `collectConnectorKeys` decides it.
|
|
240
|
+
*/
|
|
241
|
+
function collectConnectorUses(caseplan) {
|
|
242
|
+
const byBindingId = new Map();
|
|
243
|
+
walkLabelled(caseplan, (node, label) => {
|
|
244
|
+
// Before the context array beneath it, which would otherwise claim the
|
|
245
|
+
// binding for the generic HTTP connector.
|
|
246
|
+
const target = httpTargetConnector(node);
|
|
247
|
+
if (target !== undefined && isRecord(node)) {
|
|
248
|
+
const data = node.data;
|
|
249
|
+
const bindingId = referencedBindingId(connectorContext(data.context));
|
|
250
|
+
if (bindingId !== undefined && !byBindingId.has(bindingId)) {
|
|
251
|
+
byBindingId.set(bindingId, { connectorKey: target, activityName: ownLabel(node) ?? label });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!Array.isArray(node))
|
|
255
|
+
return;
|
|
256
|
+
const context = connectorContext(node);
|
|
257
|
+
const connectorKey = context?.get('connectorKey');
|
|
258
|
+
const bindingId = referencedBindingId(context);
|
|
259
|
+
if (connectorKey !== undefined && bindingId !== undefined && !byBindingId.has(bindingId)) {
|
|
260
|
+
byBindingId.set(bindingId, { connectorKey, activityName: label });
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return byBindingId;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The label of the first object that references each binding id
|
|
267
|
+
* (`=bindings.<id>`), in document order: the nearest enclosing `displayName`,
|
|
268
|
+
* `display.label` or `label`. The same walk as the CLI's
|
|
269
|
+
* `collectBindingLabels`, so both name an entry after the same task.
|
|
270
|
+
*/
|
|
271
|
+
function bindingLabels(nodes) {
|
|
272
|
+
const found = new Map();
|
|
273
|
+
walkLabelled(nodes ?? [], (node, label) => {
|
|
274
|
+
if (typeof node !== 'string' || label === undefined || !node.startsWith(BINDING_REFERENCE_PREFIX))
|
|
275
|
+
return;
|
|
276
|
+
const id = node.slice(BINDING_REFERENCE_PREFIX.length);
|
|
277
|
+
if (!found.has(id))
|
|
278
|
+
found.set(id, label);
|
|
279
|
+
});
|
|
280
|
+
return found;
|
|
281
|
+
}
|
package/dist/case/compile-cli.js
CHANGED
|
@@ -29,9 +29,9 @@ import { defaultBindingsFile } from '../workdir.js';
|
|
|
29
29
|
import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
|
|
30
30
|
import { ensureTypeScriptRuntime } from '../node-runtime.js';
|
|
31
31
|
import { CASE_FORMAT_PROFILE } from './format-profile.js';
|
|
32
|
-
import {
|
|
32
|
+
import { deriveCaseBindingsV2 } from './bindings-v2.js';
|
|
33
|
+
import { BINDINGS_V2_DOCUMENT_VERSION } from '../core/bindings-v2-resource.js';
|
|
33
34
|
import { sha256Uuid } from '../core/stable-id.js';
|
|
34
|
-
import { isRecord } from '../core/node-envelope.js';
|
|
35
35
|
export async function run(argv) {
|
|
36
36
|
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
37
37
|
console.error('usage: compile <case.ts | BaseName> [-o <project>/caseplan.case]');
|
|
@@ -315,210 +315,16 @@ function syncEntryPoints(caseplan, outPath) {
|
|
|
315
315
|
* artifact. `resources refresh` reads bindings_v2.json, not caseplan.json, and
|
|
316
316
|
* succeeds with zero warnings when the sidecar is stale or empty.
|
|
317
317
|
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
* that references both binding ids.
|
|
322
|
-
* No-op when compiling outside a scaffolded project.
|
|
318
|
+
* The entries are what `uip maestro case bindings sync` derives
|
|
319
|
+
* ({@link deriveCaseBindingsV2}). No-op when compiling outside a scaffolded
|
|
320
|
+
* project.
|
|
323
321
|
*/
|
|
324
322
|
function syncBindingsV2(caseplan, outPath) {
|
|
325
323
|
const sidecarPath = join(dirname(outPath), 'bindings_v2.json');
|
|
326
324
|
if (!existsSync(sidecarPath))
|
|
327
325
|
return -1;
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
: [];
|
|
331
|
-
const byId = new Map(bindings
|
|
332
|
-
.filter((binding) => typeof binding.id === 'string')
|
|
333
|
-
.map((binding) => [binding.id, binding]));
|
|
334
|
-
const resources = [];
|
|
335
|
-
// One entry per (resource, resourceKey) group of root binding rows, in the
|
|
336
|
-
// shape `uip maestro case bindings sync` derives (`deriveCaseBindingsV2` in
|
|
337
|
-
// cli case-bindings-service.ts). `validate --strict` compares the two
|
|
338
|
-
// entry by entry as serialized JSON, so field order matters as well as
|
|
339
|
-
// field values.
|
|
340
|
-
const groups = new Map();
|
|
341
|
-
for (const binding of bindings) {
|
|
342
|
-
const resource = stringValue(binding.resource);
|
|
343
|
-
const key = stringValue(binding.resourceKey);
|
|
344
|
-
if (!resource || !key || typeof binding.propertyAttribute !== 'string')
|
|
345
|
-
continue;
|
|
346
|
-
if (resource.toLowerCase() === 'connection')
|
|
347
|
-
continue;
|
|
348
|
-
const groupKey = `${resource}\0${key}`;
|
|
349
|
-
const group = groups.get(groupKey);
|
|
350
|
-
if (group)
|
|
351
|
-
group.push(binding);
|
|
352
|
-
else
|
|
353
|
-
groups.set(groupKey, [binding]);
|
|
354
|
-
}
|
|
355
|
-
const seenConnections = new Set();
|
|
356
|
-
const connectionResources = [];
|
|
357
|
-
visitRecords(caseplan, (record) => {
|
|
358
|
-
if (!Array.isArray(record.context))
|
|
359
|
-
return;
|
|
360
|
-
const context = new Map(record.context
|
|
361
|
-
.filter(isRecord)
|
|
362
|
-
.filter((row) => typeof row.name === 'string')
|
|
363
|
-
.map((row) => [row.name, row.value]));
|
|
364
|
-
const connector = stringValue(context.get('connectorKey'));
|
|
365
|
-
const connectionId = referencedBindingValue(context.get('connection'), byId);
|
|
366
|
-
const folderKey = referencedBindingValue(context.get('folderKey'), byId);
|
|
367
|
-
if (!connector || !connectionId || !folderKey || seenConnections.has(connectionId))
|
|
368
|
-
return;
|
|
369
|
-
seenConnections.add(connectionId);
|
|
370
|
-
connectionResources.push({
|
|
371
|
-
resource: 'Connection',
|
|
372
|
-
key: connectionId,
|
|
373
|
-
value: {
|
|
374
|
-
connectionId: { defaultValue: connectionId },
|
|
375
|
-
folderKey: { defaultValue: folderKey },
|
|
376
|
-
},
|
|
377
|
-
metadata: { connector },
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
const labels = bindingLabels(caseplan.nodes);
|
|
381
|
-
for (const group of groups.values())
|
|
382
|
-
resources.push(sidecarResourceEntry(group, labels));
|
|
383
|
-
resources.push(...connectionResources);
|
|
384
|
-
writeJsonFile(sidecarPath, { version: '2.0', resources });
|
|
326
|
+
const resources = deriveCaseBindingsV2(caseplan);
|
|
327
|
+
writeJsonFile(sidecarPath, { version: BINDINGS_V2_DOCUMENT_VERSION, resources });
|
|
385
328
|
return resources.length;
|
|
386
329
|
}
|
|
387
|
-
/**
|
|
388
|
-
* The Case Designer's label for each value slot of an app or process entry.
|
|
389
|
-
* These resources get the Designer shape (cli#5003, MST-16424): an `id`, each
|
|
390
|
-
* slot flagged and labelled, and the `BindingsVersion` / `SolutionsSupport`
|
|
391
|
-
* metadata without which deploy cannot override the entry.
|
|
392
|
-
*/
|
|
393
|
-
const DESIGNER_SLOT_NAMES = new Map([
|
|
394
|
-
['app', { name: 'App name', folderPath: 'Folder path', solutionsSupport: true }],
|
|
395
|
-
['process', { name: 'Process name', folderPath: 'Folder path', solutionsSupport: true }],
|
|
396
|
-
['ixpDeployment', { name: 'name', folderPath: 'Folder path', solutionsSupport: false }],
|
|
397
|
-
]);
|
|
398
|
-
/** The bindings version deploy needs to override an entry. */
|
|
399
|
-
const RESOURCE_BINDINGS_VERSION = '2.2';
|
|
400
|
-
/**
|
|
401
|
-
* One sidecar entry for a group of root binding rows that share a resource and
|
|
402
|
-
* a resource key. The subtype comes from the rows only: a Flow's rows carry
|
|
403
|
-
* none, so its entry has none either, as the Designer saves it.
|
|
404
|
-
*
|
|
405
|
-
* `metadata.ActivityName` labels a Designer-shaped entry with the first task
|
|
406
|
-
* that references one of its rows ({@link bindingLabels}).
|
|
407
|
-
*/
|
|
408
|
-
function sidecarResourceEntry(group, labels) {
|
|
409
|
-
const resource = group[0].resource;
|
|
410
|
-
const key = group[0].resourceKey;
|
|
411
|
-
const attributeDefault = (attribute) => {
|
|
412
|
-
const found = group.find((binding) => binding.propertyAttribute === attribute)?.default;
|
|
413
|
-
return typeof found === 'string' ? found : undefined;
|
|
414
|
-
};
|
|
415
|
-
// An in-solution resource binds an empty folderPath in the caseplan, while
|
|
416
|
-
// the sidecar names its folder by the sentinel the key carries.
|
|
417
|
-
const folderPath = isInSolutionKey(key) ? SOLUTION_FOLDER : attributeDefault('folderPath');
|
|
418
|
-
const name = attributeDefault('name');
|
|
419
|
-
const subType = group.map((binding) => binding.resourceSubType).find((found) => found !== undefined);
|
|
420
|
-
const designer = DESIGNER_SLOT_NAMES.get(resource);
|
|
421
|
-
if (designer) {
|
|
422
|
-
const value = {};
|
|
423
|
-
if (folderPath !== undefined) {
|
|
424
|
-
value.folderPath = { defaultValue: folderPath, isExpression: false, displayName: designer.folderPath };
|
|
425
|
-
}
|
|
426
|
-
if (name !== undefined) {
|
|
427
|
-
value.name = { defaultValue: name, isExpression: false, displayName: designer.name };
|
|
428
|
-
}
|
|
429
|
-
const activityName = group
|
|
430
|
-
.map((binding) => (typeof binding.id === 'string' ? labels.get(binding.id) : undefined))
|
|
431
|
-
.find((found) => found !== undefined);
|
|
432
|
-
return {
|
|
433
|
-
resource,
|
|
434
|
-
key,
|
|
435
|
-
id: `${resource}${key}`,
|
|
436
|
-
value,
|
|
437
|
-
metadata: {
|
|
438
|
-
...(activityName !== undefined && { ActivityName: activityName }),
|
|
439
|
-
BindingsVersion: RESOURCE_BINDINGS_VERSION,
|
|
440
|
-
DisplayLabel: 'name',
|
|
441
|
-
...(designer.solutionsSupport && { SolutionsSupport: 'true' }),
|
|
442
|
-
...(subType !== undefined && { subType }),
|
|
443
|
-
},
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
const value = {};
|
|
447
|
-
if (name !== undefined)
|
|
448
|
-
value.name = { defaultValue: name };
|
|
449
|
-
if (folderPath !== undefined)
|
|
450
|
-
value.folderPath = { defaultValue: folderPath };
|
|
451
|
-
// A solution-resource picker (such as a Data Fabric entity) carries its folder key as a slot of its own.
|
|
452
|
-
const folderKey = attributeDefault('folderKey');
|
|
453
|
-
if (folderKey !== undefined)
|
|
454
|
-
value.folderKey = { defaultValue: folderKey };
|
|
455
|
-
return {
|
|
456
|
-
resource,
|
|
457
|
-
key,
|
|
458
|
-
value,
|
|
459
|
-
metadata: {
|
|
460
|
-
BindingsVersion: RESOURCE_BINDINGS_VERSION,
|
|
461
|
-
...(subType !== undefined && { subType }),
|
|
462
|
-
},
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* The label of the first object that references each binding id
|
|
467
|
-
* (`=bindings.<id>`), in document order: the nearest enclosing `displayName`,
|
|
468
|
-
* `display.label` or `label`. The same walk as the CLI's
|
|
469
|
-
* `collectBindingLabels`, so both name an entry after the same task.
|
|
470
|
-
*/
|
|
471
|
-
function bindingLabels(nodes) {
|
|
472
|
-
const found = new Map();
|
|
473
|
-
const ownLabel = (node) => {
|
|
474
|
-
const display = isRecord(node.display) ? node.display.label : undefined;
|
|
475
|
-
return [node.displayName, display, node.label].map(stringValue).find((label) => label !== undefined);
|
|
476
|
-
};
|
|
477
|
-
const walk = (node, label) => {
|
|
478
|
-
if (typeof node === 'string') {
|
|
479
|
-
if (label !== undefined && node.startsWith('=bindings.')) {
|
|
480
|
-
const id = node.slice('=bindings.'.length);
|
|
481
|
-
if (!found.has(id))
|
|
482
|
-
found.set(id, label);
|
|
483
|
-
}
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
if (Array.isArray(node)) {
|
|
487
|
-
for (const item of node)
|
|
488
|
-
walk(item, label);
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
if (!isRecord(node))
|
|
492
|
-
return;
|
|
493
|
-
const scoped = ownLabel(node) ?? label;
|
|
494
|
-
for (const value of Object.values(node))
|
|
495
|
-
walk(value, scoped);
|
|
496
|
-
};
|
|
497
|
-
walk(nodes ?? [], undefined);
|
|
498
|
-
return found;
|
|
499
|
-
}
|
|
500
|
-
function referencedBindingValue(value, bindings) {
|
|
501
|
-
const ref = stringValue(value);
|
|
502
|
-
if (!ref)
|
|
503
|
-
return undefined;
|
|
504
|
-
if (!ref.startsWith('=bindings.'))
|
|
505
|
-
return ref;
|
|
506
|
-
const binding = bindings.get(ref.slice('=bindings.'.length));
|
|
507
|
-
return binding && (stringValue(binding.default) ?? stringValue(binding.resourceKey));
|
|
508
|
-
}
|
|
509
|
-
function visitRecords(value, visit) {
|
|
510
|
-
if (Array.isArray(value)) {
|
|
511
|
-
for (const item of value)
|
|
512
|
-
visitRecords(item, visit);
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
if (!isRecord(value))
|
|
516
|
-
return;
|
|
517
|
-
visit(value);
|
|
518
|
-
for (const child of Object.values(value))
|
|
519
|
-
visitRecords(child, visit);
|
|
520
|
-
}
|
|
521
|
-
function stringValue(value) {
|
|
522
|
-
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
523
|
-
}
|
|
524
330
|
runWhenInvokedDirectly(import.meta.url, 'compile', run);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One `bindings_v2.json` resource row, and the connection row every artifact
|
|
3
|
+
* writes.
|
|
4
|
+
*
|
|
5
|
+
* Flow (`bindings-v2.ts`) and Case (`case/bindings-v2.ts`) both declare a
|
|
6
|
+
* connector's connection in the project sidecar, and the CLI checks each
|
|
7
|
+
* against its own generator: `uip maestro flow pack` for Flow, `uip maestro
|
|
8
|
+
* case bindings sync` (`connectionEntry` in cli
|
|
9
|
+
* `packages/case-tool/src/services/case-bindings-service.ts`) for Case. Both
|
|
10
|
+
* generators write the same row — the Designer shape, `id` + flagged
|
|
11
|
+
* `ConnectionId` slot + `BindingsVersion` / `UseConnectionService` metadata.
|
|
12
|
+
* The CLI compares rows as serialized JSON, so the key order below is part of
|
|
13
|
+
* the contract.
|
|
14
|
+
*
|
|
15
|
+
* Case used to write its own older row (`connectionId` slot, a lower-case
|
|
16
|
+
* `connector` metadata key) and `validate --strict` reported it stale
|
|
17
|
+
* (builder-sdk#1382). One builder for both artifacts is what keeps the two
|
|
18
|
+
* from drifting apart again.
|
|
19
|
+
*/
|
|
20
|
+
/** One declared solution resource. */
|
|
21
|
+
export interface BindingsV2Resource {
|
|
22
|
+
resource: string;
|
|
23
|
+
key: string;
|
|
24
|
+
id?: string;
|
|
25
|
+
value: Record<string, unknown>;
|
|
26
|
+
metadata: Record<string, unknown>;
|
|
27
|
+
[k: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/** The `bindings_v2.json` document version every generator writes. */
|
|
30
|
+
export declare const BINDINGS_V2_DOCUMENT_VERSION = "2.0";
|
|
31
|
+
/** The per-resource `metadata.BindingsVersion` deploy needs to override an entry. */
|
|
32
|
+
export declare const RESOURCE_BINDINGS_VERSION = "2.2";
|
|
33
|
+
/** What one connection row is made of. Optional parts are left out of the row when absent. */
|
|
34
|
+
export interface ConnectionResourceParts {
|
|
35
|
+
/** `connection` in a `.flow`, `Connection` in a caseplan: each artifact keeps its own spelling. */
|
|
36
|
+
resource: string;
|
|
37
|
+
/** The connection's resource key. */
|
|
38
|
+
key: string;
|
|
39
|
+
/** The `ConnectionId` slot's value; the key when the binding row has no default. */
|
|
40
|
+
connectionId: string;
|
|
41
|
+
/** The connection binding's name: the slot's `displayName` and `metadata.DisplayLabel`. */
|
|
42
|
+
displayLabel?: string;
|
|
43
|
+
/** The folder key slot. Case writes it; Flow's pack does not. */
|
|
44
|
+
folderKey?: string;
|
|
45
|
+
/** The label of the first step or task that uses the connection. */
|
|
46
|
+
activityName?: string;
|
|
47
|
+
/** The connector the connection belongs to. */
|
|
48
|
+
connector?: string;
|
|
49
|
+
}
|
|
50
|
+
/** The connection row, in the Designer shape both CLI generators derive. */
|
|
51
|
+
export declare function connectionResource(parts: ConnectionResourceParts): BindingsV2Resource;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One `bindings_v2.json` resource row, and the connection row every artifact
|
|
3
|
+
* writes.
|
|
4
|
+
*
|
|
5
|
+
* Flow (`bindings-v2.ts`) and Case (`case/bindings-v2.ts`) both declare a
|
|
6
|
+
* connector's connection in the project sidecar, and the CLI checks each
|
|
7
|
+
* against its own generator: `uip maestro flow pack` for Flow, `uip maestro
|
|
8
|
+
* case bindings sync` (`connectionEntry` in cli
|
|
9
|
+
* `packages/case-tool/src/services/case-bindings-service.ts`) for Case. Both
|
|
10
|
+
* generators write the same row — the Designer shape, `id` + flagged
|
|
11
|
+
* `ConnectionId` slot + `BindingsVersion` / `UseConnectionService` metadata.
|
|
12
|
+
* The CLI compares rows as serialized JSON, so the key order below is part of
|
|
13
|
+
* the contract.
|
|
14
|
+
*
|
|
15
|
+
* Case used to write its own older row (`connectionId` slot, a lower-case
|
|
16
|
+
* `connector` metadata key) and `validate --strict` reported it stale
|
|
17
|
+
* (builder-sdk#1382). One builder for both artifacts is what keeps the two
|
|
18
|
+
* from drifting apart again.
|
|
19
|
+
*/
|
|
20
|
+
/** The `bindings_v2.json` document version every generator writes. */
|
|
21
|
+
export const BINDINGS_V2_DOCUMENT_VERSION = '2.0';
|
|
22
|
+
/** The per-resource `metadata.BindingsVersion` deploy needs to override an entry. */
|
|
23
|
+
export const RESOURCE_BINDINGS_VERSION = '2.2';
|
|
24
|
+
/** The connection row, in the Designer shape both CLI generators derive. */
|
|
25
|
+
export function connectionResource(parts) {
|
|
26
|
+
const { resource, key, connectionId, displayLabel, folderKey, activityName, connector } = parts;
|
|
27
|
+
const value = {
|
|
28
|
+
ConnectionId: {
|
|
29
|
+
defaultValue: connectionId,
|
|
30
|
+
isExpression: false,
|
|
31
|
+
...(displayLabel !== undefined && { displayName: displayLabel }),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
if (folderKey !== undefined)
|
|
35
|
+
value.folderKey = { defaultValue: folderKey };
|
|
36
|
+
return {
|
|
37
|
+
resource,
|
|
38
|
+
key,
|
|
39
|
+
// The generators concatenate resource and key with no separator.
|
|
40
|
+
id: `${resource}${key}`,
|
|
41
|
+
value,
|
|
42
|
+
metadata: {
|
|
43
|
+
...(activityName !== undefined && { ActivityName: activityName }),
|
|
44
|
+
BindingsVersion: RESOURCE_BINDINGS_VERSION,
|
|
45
|
+
...(displayLabel !== undefined && { DisplayLabel: displayLabel }),
|
|
46
|
+
UseConnectionService: 'true',
|
|
47
|
+
...(connector !== undefined && { Connector: connector }),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.68.
|
|
3
|
+
"version": "6.68.4",
|
|
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",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"@types/node": "^22.7.0",
|
|
101
101
|
"esbuild": "^0.28.1"
|
|
102
102
|
},
|
|
103
|
-
"gitref": "
|
|
103
|
+
"gitref": "f879560df189ceab42f899123d36f0add5b0a562"
|
|
104
104
|
}
|