@uipath/maestro-builder-sdk 6.68.2 → 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/check.js +6 -1
- package/dist/case/compile-cli.js +7 -201
- package/dist/case/decompile.d.ts +1 -0
- package/dist/case/decompile.js +23 -0
- package/dist/case/preserve.d.ts +32 -0
- package/dist/case/preserve.js +41 -0
- package/dist/case/serialize.js +6 -4
- package/dist/core/bindings-v2-resource.d.ts +51 -0
- package/dist/core/bindings-v2-resource.js +50 -0
- package/dist/core/bindings.d.ts +12 -0
- package/dist/core/bindings.js +25 -3
- 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/check.js
CHANGED
|
@@ -3,7 +3,7 @@ import { ruleShapeProblems } from './rule-options.js';
|
|
|
3
3
|
import { types } from '../core/expr.js';
|
|
4
4
|
import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS, authoredCaseVersion, caseVersionMismatch } from './format-profile.js';
|
|
5
5
|
import { schemaVersionRefusal } from '../schema-version.js';
|
|
6
|
-
import { caseJsonPreservation, caseJsonPreservationErrors } from './preserve.js';
|
|
6
|
+
import { caseJsonPreservation, caseJsonPreservationErrors, preservedBindings } from './preserve.js';
|
|
7
7
|
import { EQUALS_EXPR_DIALECT, checkBindingLabels, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
|
|
8
8
|
import { checkEventResolution } from '../core/event-checks.js';
|
|
9
9
|
import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, EXTERNAL_TASK_HYBRID_UNSUPPORTED, externalHybridRefusal, isExternalExecutionMode } from './external.js';
|
|
@@ -25,6 +25,11 @@ const ILLEGAL = [];
|
|
|
25
25
|
*/
|
|
26
26
|
export function check(built, opts = {}) {
|
|
27
27
|
const diags = [];
|
|
28
|
+
// The same registry `serialize` resolves against, so the two agree on which
|
|
29
|
+
// labels a decompiled source's preserved binding rows answer.
|
|
30
|
+
const bindings = preservedBindings(opts.bindings, caseJsonPreservation(built));
|
|
31
|
+
if (bindings !== opts.bindings)
|
|
32
|
+
opts = { ...opts, bindings };
|
|
28
33
|
for (const message of caseJsonPreservationErrors(caseJsonPreservation(built)))
|
|
29
34
|
diags.push({
|
|
30
35
|
level: 'error',
|
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);
|
package/dist/case/decompile.d.ts
CHANGED
package/dist/case/decompile.js
CHANGED
|
@@ -1315,6 +1315,26 @@ function identityPreservation(plan) {
|
|
|
1315
1315
|
])),
|
|
1316
1316
|
};
|
|
1317
1317
|
}
|
|
1318
|
+
/**
|
|
1319
|
+
* The root connection/folder rows a recompile needs to resolve the labels
|
|
1320
|
+
* decompile names connectors by. `bindingLabel` writes such a label as the
|
|
1321
|
+
* row's `id` (`connection: "bOC1"`), and that id means nothing without the row.
|
|
1322
|
+
* Kept: `resource: "Connection"` rows with a key other than their own id. Not
|
|
1323
|
+
* kept: a row `serialize` minted offline (decompile writes its authored label,
|
|
1324
|
+
* and a recompile mints the same row again) and a row keyed by its own id
|
|
1325
|
+
* (a placeholder, which `Bindings` would ignore anyway).
|
|
1326
|
+
*/
|
|
1327
|
+
function preservedBindingRows(plan) {
|
|
1328
|
+
return (plan.bindings ?? []).filter((row) => {
|
|
1329
|
+
const r = row;
|
|
1330
|
+
if (typeof r.id !== 'string' || r.resource !== 'Connection')
|
|
1331
|
+
return false;
|
|
1332
|
+
if (offlineBindingLabel(r) !== undefined)
|
|
1333
|
+
return false;
|
|
1334
|
+
const key = r.resourceKey ?? r.default;
|
|
1335
|
+
return typeof key === 'string' && key !== '' && key !== r.id;
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1318
1338
|
/**
|
|
1319
1339
|
* Everything the ORIGINAL document owns and the typed builder does not model —
|
|
1320
1340
|
* ids, layout, platform metadata, annotation nodes, node order, designer
|
|
@@ -1436,6 +1456,9 @@ export function collectPreservation(plan) {
|
|
|
1436
1456
|
preservation.omitTaskElementIds = omitTaskElementIds;
|
|
1437
1457
|
if (omitStagePositions.length)
|
|
1438
1458
|
preservation.omitStagePositions = omitStagePositions;
|
|
1459
|
+
const bindingRows = preservedBindingRows(plan);
|
|
1460
|
+
if (bindingRows.length)
|
|
1461
|
+
preservation.bindings = bindingRows;
|
|
1439
1462
|
const variableKnown = new Set(['id', 'name', 'type', 'default', 'body', '_jsonSchema', 'elementId', 'var', 'custom']);
|
|
1440
1463
|
const variableFields = {};
|
|
1441
1464
|
const legacyVariableDefaults = {};
|
package/dist/case/preserve.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { BuiltCase } from './case-sdk.js';
|
|
2
|
+
import { Bindings } from '../core/bindings.js';
|
|
3
|
+
import type { BindingsView } from '../core/binding-messages.js';
|
|
2
4
|
export type PreservedVariableLane = 'inputs' | 'outputs' | 'inputOutputs';
|
|
3
5
|
/**
|
|
4
6
|
* A preserved `inputs[]` / `outputs[]` slot id copied verbatim from its name is
|
|
@@ -143,11 +145,41 @@ export interface CaseJsonPreservation {
|
|
|
143
145
|
omitTaskElementIds?: PreservedTaskRef[];
|
|
144
146
|
/** Opaque wire identities aligned to typed constructs by name and position. */
|
|
145
147
|
identities?: CaseIdentityPreservation;
|
|
148
|
+
/**
|
|
149
|
+
* The original plan's connection and folder rows from root `bindings[]`: the
|
|
150
|
+
* map from the binding id decompile names a connector by
|
|
151
|
+
* (`connection: "bOC1"`) to its resource key. Compile reads them only to
|
|
152
|
+
* resolve such a label when bindings.json does not answer it (see
|
|
153
|
+
* {@link preservedBindings}); it emits a row only when a task references it.
|
|
154
|
+
* Without them, compiling the source anywhere but over the original plan
|
|
155
|
+
* wrote `resourceKey: "bOC1"`, and nothing refused it (builder-sdk#1242).
|
|
156
|
+
*/
|
|
157
|
+
bindings?: Record<string, unknown>[];
|
|
146
158
|
}
|
|
147
159
|
/** Attach decompiler-owned, non-authoring wire data to a built case. */
|
|
148
160
|
export declare function preserveCaseJson(built: BuiltCase, payload: CaseJsonPreservation): BuiltCase;
|
|
149
161
|
/** @internal Read the preservation payload attached by `preserveCaseJson`. */
|
|
150
162
|
export declare function caseJsonPreservation(built: BuiltCase): CaseJsonPreservation | undefined;
|
|
163
|
+
/**
|
|
164
|
+
* The bindings registry `check` and `serialize` resolve a decompiled source's
|
|
165
|
+
* connection and folder labels against: `bindings` plus the rows the source
|
|
166
|
+
* preserved ({@link CaseJsonPreservation.bindings}) as fallback rows. A fallback
|
|
167
|
+
* row answers only a label equal to its id, never counts as declared, and loses
|
|
168
|
+
* to bindings.json and to the plan `-o` writes over (the same rules as
|
|
169
|
+
* `Bindings.withFallback`). A row `serialize` minted offline is a placeholder for
|
|
170
|
+
* its label, not a key, so it answers nothing.
|
|
171
|
+
*
|
|
172
|
+
* A `Bindings` comes back as a new `Bindings`. Any other {@link BindingsView}
|
|
173
|
+
* (an SDK caller's own, or a `Bindings` from a second copy of the package) is
|
|
174
|
+
* wrapped, so the preserved rows answer there too instead of being dropped.
|
|
175
|
+
*
|
|
176
|
+
* @param bindings - The registry the caller loaded, if any.
|
|
177
|
+
* @param payload - The source's preservation payload, if any.
|
|
178
|
+
* @returns A registry with the preserved rows, or `bindings` unchanged when
|
|
179
|
+
* there are none.
|
|
180
|
+
*/
|
|
181
|
+
export declare function preservedBindings(bindings: Bindings | undefined, payload: CaseJsonPreservation | undefined): Bindings | undefined;
|
|
182
|
+
export declare function preservedBindings(bindings: BindingsView | undefined, payload: CaseJsonPreservation | undefined): BindingsView | undefined;
|
|
151
183
|
/** @internal Validate that preservation cannot impersonate typed authoring. */
|
|
152
184
|
export declare function caseJsonPreservationErrors(payload: CaseJsonPreservation | undefined): string[];
|
|
153
185
|
export {};
|
package/dist/case/preserve.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { stableId } from '../core/stable-id.js';
|
|
2
|
+
import { Bindings } from '../core/bindings.js';
|
|
3
|
+
import { offlineBindingLabel } from './ids.js';
|
|
2
4
|
/**
|
|
3
5
|
* A preserved `inputs[]` / `outputs[]` slot id copied verbatim from its name is
|
|
4
6
|
* the STRICT_FORMAL_ARG_ID defect, not an identity to keep; the minted id stands.
|
|
@@ -36,6 +38,30 @@ export function preserveCaseJson(built, payload) {
|
|
|
36
38
|
export function caseJsonPreservation(built) {
|
|
37
39
|
return preserved.get(built);
|
|
38
40
|
}
|
|
41
|
+
export function preservedBindings(bindings, payload) {
|
|
42
|
+
const rows = payload?.bindings;
|
|
43
|
+
if (!Array.isArray(rows) || rows.length === 0)
|
|
44
|
+
return bindings;
|
|
45
|
+
const minted = (row) => offlineBindingLabel(row) !== undefined;
|
|
46
|
+
if (bindings === undefined || bindings instanceof Bindings)
|
|
47
|
+
return (bindings ?? new Bindings()).withFallbackRows(rows, minted);
|
|
48
|
+
return withPreservedRows(bindings, new Bindings().withFallbackRows(rows, minted));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `view` with `extra`'s fallback rows behind it: `view` answers first, and a
|
|
52
|
+
* label only `extra` answers is a fallback label (`fromFallback`), exactly as
|
|
53
|
+
* `Bindings` treats its own fallback rows.
|
|
54
|
+
*/
|
|
55
|
+
function withPreservedRows(view, extra) {
|
|
56
|
+
const primary = (name) => view.resolve(name) !== undefined || view.entry?.(name) !== undefined
|
|
57
|
+
|| (name !== undefined && (view.declared?.() ?? []).includes(name));
|
|
58
|
+
return {
|
|
59
|
+
resolve: (name) => view.resolve(name) ?? (primary(name) ? undefined : extra.resolve(name)),
|
|
60
|
+
...(view.declared ? { declared: () => view.declared() } : {}),
|
|
61
|
+
entry: (name) => view.entry?.(name) ?? (primary(name) ? undefined : extra.entry(name)),
|
|
62
|
+
fromFallback: (name) => view.fromFallback?.(name) === true || (!primary(name) && extra.fromFallback(name)),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
39
65
|
const TOP_LEVEL_KEYS = new Set([
|
|
40
66
|
'id', 'name', 'description', 'version', 'bindings', 'variables', 'metadata', 'nodes', 'edges', 'layout',
|
|
41
67
|
]);
|
|
@@ -123,6 +149,21 @@ export function caseJsonPreservationErrors(payload) {
|
|
|
123
149
|
else if (TASK_KEYS.has(field))
|
|
124
150
|
errors.push(`task "${entry.task}" field "${field}" is owned by the typed Case serializer`);
|
|
125
151
|
}
|
|
152
|
+
if (payload.bindings !== undefined) {
|
|
153
|
+
if (!Array.isArray(payload.bindings))
|
|
154
|
+
errors.push('preserved "bindings" must be an array of root binding rows');
|
|
155
|
+
else
|
|
156
|
+
payload.bindings.forEach((row, index) => {
|
|
157
|
+
const r = row;
|
|
158
|
+
const key = r && typeof r === 'object' ? r.resourceKey ?? r.default : undefined;
|
|
159
|
+
if (!r || typeof r !== 'object' || typeof r.id !== 'string' || r.id === '')
|
|
160
|
+
errors.push(`preserved binding row ${index + 1} has no string id`);
|
|
161
|
+
else if (r.resource !== 'Connection')
|
|
162
|
+
errors.push(`preserved binding row "${r.id}" is not a Connection row (only connection and folder rows are preserved)`);
|
|
163
|
+
else if (typeof key !== 'string' || key === '' || key === r.id)
|
|
164
|
+
errors.push(`preserved binding row "${r.id}" carries no resource key other than its own id`);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
126
167
|
for (const [index, fields] of (payload.triggerDataFields ?? []).entries())
|
|
127
168
|
for (const field of Object.keys(fields))
|
|
128
169
|
if (!PRESERVED_TRIGGER_DATA_KEYS.has(field))
|
package/dist/case/serialize.js
CHANGED
|
@@ -25,7 +25,7 @@ import { isResourceLabelRefusal, resolveResourceRef, resourceIdentity } from './
|
|
|
25
25
|
import { ownEntry } from '../core/own-entry.js';
|
|
26
26
|
import { stableId } from '../core/stable-id.js';
|
|
27
27
|
import { resolveSchemaVersion } from '../schema-version.js';
|
|
28
|
-
import { caseJsonPreservation, caseJsonPreservationErrors, } from './preserve.js';
|
|
28
|
+
import { caseJsonPreservation, caseJsonPreservationErrors, preservedBindings, } from './preserve.js';
|
|
29
29
|
import { businessEventProblems } from './business-event-shape.js';
|
|
30
30
|
import { readEventFilterLeaf, eventFilterTreeLeaf } from '../core/event-filters.js';
|
|
31
31
|
import { responseJsonSchema } from '../core/connector-response-schema.js';
|
|
@@ -101,7 +101,9 @@ export function serialize(built, opts = {}) {
|
|
|
101
101
|
stageIdByLabel: new Map(),
|
|
102
102
|
taskIdByKey: new Map(),
|
|
103
103
|
library: opts.library,
|
|
104
|
-
|
|
104
|
+
// A decompiled source carries its plan's connection rows: they answer the
|
|
105
|
+
// binding-id labels decompile wrote (`connection: "bOC1"`).
|
|
106
|
+
bindings: preservedBindings(opts.bindings, caseJsonPreservation(built)),
|
|
105
107
|
bindingSink: new Map(),
|
|
106
108
|
ids,
|
|
107
109
|
offlineBindingIds: new Map(),
|
|
@@ -1875,7 +1877,7 @@ function bindingRef(resolver, name, resource, propertyAttribute, displayName, si
|
|
|
1875
1877
|
const refused = bindingLabelRefusal(resolver.bindings, name, site.attribute, site.where, site.target);
|
|
1876
1878
|
if (refused)
|
|
1877
1879
|
throw refused;
|
|
1878
|
-
const entry = resolver.bindings?.entry(name);
|
|
1880
|
+
const entry = resolver.bindings?.entry?.(name);
|
|
1879
1881
|
const resourceKey = entry?.resourceKey ?? entry?.default ?? name;
|
|
1880
1882
|
// Only a label with no bindings.json entry gets a minted id: an entry's
|
|
1881
1883
|
// `resourceKey` is the real key, so its id (or its name) cannot clash with it.
|
|
@@ -1912,7 +1914,7 @@ function folderBindingRef(resolver, name, connection, site) {
|
|
|
1912
1914
|
const refused = bindingLabelRefusal(resolver.bindings, name, site.attribute, site.where, site.target);
|
|
1913
1915
|
if (refused)
|
|
1914
1916
|
throw refused;
|
|
1915
|
-
const entry = resolver.bindings?.entry(name);
|
|
1917
|
+
const entry = resolver.bindings?.entry?.(name);
|
|
1916
1918
|
const baseId = entry
|
|
1917
1919
|
? String(entry.id ?? name)
|
|
1918
1920
|
: offlineBindingId(resolver, offlineBindingSeed('Connection', 'folderKey', name, connection.resourceKey));
|
|
@@ -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/dist/core/bindings.d.ts
CHANGED
|
@@ -26,6 +26,18 @@ export declare class Bindings {
|
|
|
26
26
|
* @returns This registry, for chaining.
|
|
27
27
|
*/
|
|
28
28
|
withFallback(file?: string, isPlaceholder?: (row: Record<string, unknown>) => boolean): this;
|
|
29
|
+
/**
|
|
30
|
+
* A NEW registry: this one's entries and fallback rows, plus `rows` as more
|
|
31
|
+
* fallback rows (same rules as {@link withFallback}; a row whose `id` is
|
|
32
|
+
* already a fallback row is skipped, so earlier fallbacks win). This registry
|
|
33
|
+
* is not changed, so a caller's `Bindings` can be shared across compiles.
|
|
34
|
+
*
|
|
35
|
+
* @param rows - Root binding rows, e.g. the ones a decompiled Case source carries.
|
|
36
|
+
* @param isPlaceholder - As for {@link withFallback}.
|
|
37
|
+
* @returns The new registry.
|
|
38
|
+
*/
|
|
39
|
+
withFallbackRows(rows: readonly unknown[], isPlaceholder?: (row: Record<string, unknown>) => boolean): Bindings;
|
|
40
|
+
private addFallbackRows;
|
|
29
41
|
private primary;
|
|
30
42
|
/**
|
|
31
43
|
* The fallback row whose `id` is `name`. A row whose key is its own id
|
package/dist/core/bindings.js
CHANGED
|
@@ -44,11 +44,33 @@ export class Bindings {
|
|
|
44
44
|
catch {
|
|
45
45
|
return this; // a fallback is optional; an unreadable one adds nothing
|
|
46
46
|
}
|
|
47
|
+
this.addFallbackRows(normalizeBindings(parsed), isPlaceholder);
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A NEW registry: this one's entries and fallback rows, plus `rows` as more
|
|
52
|
+
* fallback rows (same rules as {@link withFallback}; a row whose `id` is
|
|
53
|
+
* already a fallback row is skipped, so earlier fallbacks win). This registry
|
|
54
|
+
* is not changed, so a caller's `Bindings` can be shared across compiles.
|
|
55
|
+
*
|
|
56
|
+
* @param rows - Root binding rows, e.g. the ones a decompiled Case source carries.
|
|
57
|
+
* @param isPlaceholder - As for {@link withFallback}.
|
|
58
|
+
* @returns The new registry.
|
|
59
|
+
*/
|
|
60
|
+
withFallbackRows(rows, isPlaceholder) {
|
|
61
|
+
const copy = new Bindings();
|
|
62
|
+
copy.list = this.list;
|
|
63
|
+
copy.fallback = [...this.fallback];
|
|
64
|
+
copy.addFallbackRows(rows, isPlaceholder);
|
|
65
|
+
return copy;
|
|
66
|
+
}
|
|
67
|
+
addFallbackRows(rows, isPlaceholder) {
|
|
47
68
|
const ids = new Set(this.fallback.map((x) => x?.id));
|
|
48
|
-
for (const entry of
|
|
49
|
-
if (typeof entry?.id === 'string' && !ids.has(entry.id) && !isPlaceholder?.(entry))
|
|
69
|
+
for (const entry of rows)
|
|
70
|
+
if (typeof entry?.id === 'string' && !ids.has(entry.id) && !isPlaceholder?.(entry)) {
|
|
50
71
|
this.fallback.push(entry);
|
|
51
|
-
|
|
72
|
+
ids.add(entry.id);
|
|
73
|
+
}
|
|
52
74
|
}
|
|
53
75
|
primary(name) {
|
|
54
76
|
return this.list.find((x) => x.name === name || x.id === name);
|
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
|
}
|