@uipath/maestro-builder-sdk 6.46.1 → 6.46.3
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 +25 -25
- package/dist/api-members.md +137 -137
- package/dist/case/case-sdk.d.ts +13 -7
- package/dist/case/case-sdk.js +6 -6
- package/dist/case/check.js +11 -0
- package/dist/case/compile-cli.d.ts +10 -0
- package/dist/case/compile-cli.js +158 -66
- package/dist/case/decompile.js +58 -10
- package/dist/case/resource-identity.d.ts +44 -0
- package/dist/case/resource-identity.js +45 -0
- package/dist/case/serialize.js +234 -23
- package/package.json +2 -2
package/dist/case/case-sdk.d.ts
CHANGED
|
@@ -432,7 +432,13 @@ export interface TaskRef {
|
|
|
432
432
|
* Options for {@link TaskBuilder.businessRule}.
|
|
433
433
|
*/
|
|
434
434
|
export interface BusinessRuleOptions {
|
|
435
|
-
/**
|
|
435
|
+
/**
|
|
436
|
+
* The Orchestrator folder the rule is deployed in, e.g. `Shared`. For a rule
|
|
437
|
+
* built in the same solution as the case, pass `'solution_folder'` (the folder
|
|
438
|
+
* `registry search --local` reports): the binding is keyed
|
|
439
|
+
* `solution_folder.<name>` and its folderPath is left empty, so the case runs
|
|
440
|
+
* the rule from its own deployed folder.
|
|
441
|
+
*/
|
|
436
442
|
folder: string;
|
|
437
443
|
/**
|
|
438
444
|
* The rule's whole result schema, as `uip maestro case tasks describe --type
|
|
@@ -759,7 +765,7 @@ declare class TaskBuilder {
|
|
|
759
765
|
* Reference a published Maestro process.
|
|
760
766
|
*
|
|
761
767
|
* @param name - The published process's name.
|
|
762
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
768
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
763
769
|
* @returns This builder, so calls chain.
|
|
764
770
|
*/
|
|
765
771
|
process(name: string, opts?: {
|
|
@@ -782,7 +788,7 @@ declare class TaskBuilder {
|
|
|
782
788
|
* Reference a published agent.
|
|
783
789
|
*
|
|
784
790
|
* @param name - The published agent's name.
|
|
785
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
791
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
786
792
|
* @returns This builder, so calls chain.
|
|
787
793
|
*/
|
|
788
794
|
agent(name: string, opts?: {
|
|
@@ -792,7 +798,7 @@ declare class TaskBuilder {
|
|
|
792
798
|
* Reference a published RPA process.
|
|
793
799
|
*
|
|
794
800
|
* @param name - The published RPA process's name.
|
|
795
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
801
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
796
802
|
* @returns This builder, so calls chain.
|
|
797
803
|
*/
|
|
798
804
|
rpa(name: string, opts?: {
|
|
@@ -802,7 +808,7 @@ declare class TaskBuilder {
|
|
|
802
808
|
* Reference a published API workflow.
|
|
803
809
|
*
|
|
804
810
|
* @param name - The published API workflow's name.
|
|
805
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
811
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
806
812
|
* @returns This builder, so calls chain.
|
|
807
813
|
*/
|
|
808
814
|
apiWorkflow(name: string, opts?: {
|
|
@@ -814,7 +820,7 @@ declare class TaskBuilder {
|
|
|
814
820
|
* io-binding as reference-mode tasks.
|
|
815
821
|
*
|
|
816
822
|
* @param name - The published child case's name.
|
|
817
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
823
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
818
824
|
* @returns This builder, so calls chain.
|
|
819
825
|
*/
|
|
820
826
|
caseManagement(name: string, opts?: {
|
|
@@ -824,7 +830,7 @@ declare class TaskBuilder {
|
|
|
824
830
|
* Reference a published Maestro Flow.
|
|
825
831
|
*
|
|
826
832
|
* @param name - The published Flow's name.
|
|
827
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
833
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
828
834
|
* @returns This builder, so calls chain.
|
|
829
835
|
*/
|
|
830
836
|
flowProcess(name: string, opts?: {
|
package/dist/case/case-sdk.js
CHANGED
|
@@ -190,7 +190,7 @@ class TaskBuilder {
|
|
|
190
190
|
* Reference a published Maestro process.
|
|
191
191
|
*
|
|
192
192
|
* @param name - The published process's name.
|
|
193
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
193
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
194
194
|
* @returns This builder, so calls chain.
|
|
195
195
|
*/
|
|
196
196
|
process(name, opts) {
|
|
@@ -216,7 +216,7 @@ class TaskBuilder {
|
|
|
216
216
|
* Reference a published agent.
|
|
217
217
|
*
|
|
218
218
|
* @param name - The published agent's name.
|
|
219
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
219
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
220
220
|
* @returns This builder, so calls chain.
|
|
221
221
|
*/
|
|
222
222
|
agent(name, opts) {
|
|
@@ -226,7 +226,7 @@ class TaskBuilder {
|
|
|
226
226
|
* Reference a published RPA process.
|
|
227
227
|
*
|
|
228
228
|
* @param name - The published RPA process's name.
|
|
229
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
229
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
230
230
|
* @returns This builder, so calls chain.
|
|
231
231
|
*/
|
|
232
232
|
rpa(name, opts) {
|
|
@@ -236,7 +236,7 @@ class TaskBuilder {
|
|
|
236
236
|
* Reference a published API workflow.
|
|
237
237
|
*
|
|
238
238
|
* @param name - The published API workflow's name.
|
|
239
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
239
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
240
240
|
* @returns This builder, so calls chain.
|
|
241
241
|
*/
|
|
242
242
|
apiWorkflow(name, opts) {
|
|
@@ -248,7 +248,7 @@ class TaskBuilder {
|
|
|
248
248
|
* io-binding as reference-mode tasks.
|
|
249
249
|
*
|
|
250
250
|
* @param name - The published child case's name.
|
|
251
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
251
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
252
252
|
* @returns This builder, so calls chain.
|
|
253
253
|
*/
|
|
254
254
|
caseManagement(name, opts) {
|
|
@@ -258,7 +258,7 @@ class TaskBuilder {
|
|
|
258
258
|
* Reference a published Maestro Flow.
|
|
259
259
|
*
|
|
260
260
|
* @param name - The published Flow's name.
|
|
261
|
-
* @param opts - `folder` — the Orchestrator folder it lives in.
|
|
261
|
+
* @param opts - `folder` — the Orchestrator folder it lives in, or `'solution_folder'` (the default when omitted) for one built in this solution. Compile binds the task through a name/folderPath pair of root bindings keyed `<folder>.<name>`.
|
|
262
262
|
* @returns This builder, so calls chain.
|
|
263
263
|
*/
|
|
264
264
|
flowProcess(name, opts) {
|
package/dist/case/check.js
CHANGED
|
@@ -483,6 +483,17 @@ function checkStage(stage, diags, refStage, refPlacement) {
|
|
|
483
483
|
message: `Task "${t.name}" binding "${'var' in invalidBinding ? invalidBinding.var : invalidBinding.name}" has unsupported type ${JSON.stringify(invalidBinding.type)}. Use a type name such as "string" or "jsonSchema"; jsonSchema(...) is only for declaring a structured case variable.`,
|
|
484
484
|
where: t.name,
|
|
485
485
|
});
|
|
486
|
+
// The builder binds a resource task itself: it mints the name/folderPath
|
|
487
|
+
// binding pair from the literal name and folder. A `=bindings.<id>` string
|
|
488
|
+
// passed as either becomes part of the binding key, which then names no
|
|
489
|
+
// resource (`=bindings.f.=bindings.n`) and dangles at deploy.
|
|
490
|
+
if (t.ref && [t.ref.name, t.ref.folderPath].some((v) => v.trim().startsWith('=bindings.')))
|
|
491
|
+
diags.push({
|
|
492
|
+
level: 'error',
|
|
493
|
+
code: 'RESOURCE_REF_IS_BINDING',
|
|
494
|
+
message: `Task "${t.name}" passes a "=bindings.<id>" reference as its resource name or folder. Pass the resource's own name and its folder, e.g. .agent('Contract Analysis', { folder: 'Shared/Legal' }); compile writes the name/folderPath binding pair keyed '<folder>.<name>' and points the task at it.`,
|
|
495
|
+
where: t.name,
|
|
496
|
+
});
|
|
486
497
|
if (t.kind === 'business-rule') {
|
|
487
498
|
if (!t.ref || !t.ref.name.trim() || !t.ref.folderPath.trim())
|
|
488
499
|
diags.push({
|
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type CliExitCode } from '../cli-run.js';
|
|
3
3
|
export declare function run(argv: string[]): Promise<CliExitCode>;
|
|
4
|
+
/**
|
|
5
|
+
* Write the entry point's id onto its trigger, as `data.inputs.entryPointId`.
|
|
6
|
+
*
|
|
7
|
+
* The Case Designer pairs a trigger with its entry point through this field.
|
|
8
|
+
* A trigger without it gets a fresh GUID on the first open, and the entry
|
|
9
|
+
* point's `uniqueId` is rewritten to match, so the entry point changes identity
|
|
10
|
+
* (MST-16422; `validate --strict` warns STRICT_TRIGGER_ENTRY_POINT_ID). The
|
|
11
|
+
* Designer writes it last among the inputs, so it is appended.
|
|
12
|
+
*/
|
|
13
|
+
export declare function stampEntryPointId(trigger: any, uniqueId: string): void;
|
package/dist/case/compile-cli.js
CHANGED
|
@@ -28,6 +28,7 @@ import { defaultBindingsFile } from '../workdir.js';
|
|
|
28
28
|
import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
|
|
29
29
|
import { ensureTypeScriptRuntime } from '../node-runtime.js';
|
|
30
30
|
import { CASE_FORMAT_PROFILE } from './format-profile.js';
|
|
31
|
+
import { isInSolutionKey, SOLUTION_FOLDER } from './resource-identity.js';
|
|
31
32
|
import { sha256Uuid } from '../core/stable-id.js';
|
|
32
33
|
import { isRecord } from '../core/node-envelope.js';
|
|
33
34
|
export async function run(argv) {
|
|
@@ -81,9 +82,11 @@ export async function run(argv) {
|
|
|
81
82
|
reportDiagnostics([...diags, thrownDiagnostic(error)]);
|
|
82
83
|
throw error;
|
|
83
84
|
}
|
|
85
|
+
// Entry points first: each trigger carries its entry point's `uniqueId` as
|
|
86
|
+
// `data.inputs.entryPointId`, so the plan is written after the ids are known.
|
|
87
|
+
const triggers = syncEntryPoints(caseplan, out);
|
|
84
88
|
writeJsonFile(out, caseplan);
|
|
85
89
|
const stages = caseplan.nodes.filter((n) => String(n.type).endsWith('Stage')).length;
|
|
86
|
-
const triggers = syncEntryPoints(caseplan, out);
|
|
87
90
|
const epNote = triggers >= 0 ? `, synced entry-points.json (${triggers} trigger(s))` : '';
|
|
88
91
|
const resources = syncBindingsV2(caseplan, out);
|
|
89
92
|
const bindingsNote = resources >= 0 ? `, synced bindings_v2.json (${resources} resource(s))` : '';
|
|
@@ -229,6 +232,25 @@ function describedCaseFileName(dir) {
|
|
|
229
232
|
function packagedCaseFileName(dir, outPath) {
|
|
230
233
|
return shippedCasePlanName(dir) ?? basename(outPath);
|
|
231
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Write the entry point's id onto its trigger, as `data.inputs.entryPointId`.
|
|
237
|
+
*
|
|
238
|
+
* The Case Designer pairs a trigger with its entry point through this field.
|
|
239
|
+
* A trigger without it gets a fresh GUID on the first open, and the entry
|
|
240
|
+
* point's `uniqueId` is rewritten to match, so the entry point changes identity
|
|
241
|
+
* (MST-16422; `validate --strict` warns STRICT_TRIGGER_ENTRY_POINT_ID). The
|
|
242
|
+
* Designer writes it last among the inputs, so it is appended.
|
|
243
|
+
*/
|
|
244
|
+
export function stampEntryPointId(trigger, uniqueId) {
|
|
245
|
+
if (typeof uniqueId !== 'string' || !uniqueId)
|
|
246
|
+
return;
|
|
247
|
+
trigger.data ??= {};
|
|
248
|
+
const inputs = trigger.data.inputs;
|
|
249
|
+
if (inputs !== undefined && (typeof inputs !== 'object' || inputs === null || Array.isArray(inputs)))
|
|
250
|
+
return;
|
|
251
|
+
const { entryPointId: _old, ...rest } = (inputs ?? {});
|
|
252
|
+
trigger.data.inputs = { ...rest, entryPointId: uniqueId };
|
|
253
|
+
}
|
|
232
254
|
/**
|
|
233
255
|
* Keep the sibling `entry-points.json` in step with the caseplan's trigger nodes:
|
|
234
256
|
* the runtime discovers triggers there, so a trigger node with no matching
|
|
@@ -251,9 +273,11 @@ function syncEntryPoints(caseplan, outPath) {
|
|
|
251
273
|
// Project the case's In/Out args into this trigger's input/output schemas
|
|
252
274
|
// (In-args scoped to this trigger by elementId; Out-args to every entry).
|
|
253
275
|
const { input, output } = entryPointIO(caseplan.variables, n.id);
|
|
276
|
+
const uniqueId = prev?.uniqueId ?? sha256Uuid(`entrypoint:${n.id}`);
|
|
277
|
+
stampEntryPointId(n, uniqueId);
|
|
254
278
|
return {
|
|
255
279
|
filePath: `/content/${base}.bpmn#${n.id}`,
|
|
256
|
-
uniqueId
|
|
280
|
+
uniqueId,
|
|
257
281
|
type: 'CaseManagement',
|
|
258
282
|
input,
|
|
259
283
|
output,
|
|
@@ -268,7 +292,8 @@ function syncEntryPoints(caseplan, outPath) {
|
|
|
268
292
|
* artifact. `resources refresh` reads bindings_v2.json, not caseplan.json, and
|
|
269
293
|
* succeeds with zero warnings when the sidecar is stale or empty.
|
|
270
294
|
*
|
|
271
|
-
* Non-connector resources come from root binding
|
|
295
|
+
* Non-connector resources come from the root binding rows only, as in
|
|
296
|
+
* `uip maestro case bindings sync`.
|
|
272
297
|
* A connector's connection and folder relationship comes from the context[]
|
|
273
298
|
* that references both binding ids.
|
|
274
299
|
* No-op when compiling outside a scaffolded project.
|
|
@@ -284,59 +309,29 @@ function syncBindingsV2(caseplan, outPath) {
|
|
|
284
309
|
.filter((binding) => typeof binding.id === 'string')
|
|
285
310
|
.map((binding) => [binding.id, binding]));
|
|
286
311
|
const resources = [];
|
|
287
|
-
|
|
312
|
+
// One entry per (resource, resourceKey) group of root binding rows, in the
|
|
313
|
+
// shape `uip maestro case bindings sync` derives (`deriveCaseBindingsV2` in
|
|
314
|
+
// cli case-bindings-service.ts). `validate --strict` compares the two
|
|
315
|
+
// entry by entry as serialized JSON, so field order matters as well as
|
|
316
|
+
// field values.
|
|
317
|
+
const groups = new Map();
|
|
288
318
|
for (const binding of bindings) {
|
|
289
319
|
const resource = stringValue(binding.resource);
|
|
290
320
|
const key = stringValue(binding.resourceKey);
|
|
291
|
-
if (!resource || !key ||
|
|
321
|
+
if (!resource || !key || typeof binding.propertyAttribute !== 'string')
|
|
322
|
+
continue;
|
|
323
|
+
if (resource.toLowerCase() === 'connection')
|
|
292
324
|
continue;
|
|
293
325
|
const groupKey = `${resource}\0${key}`;
|
|
294
|
-
|
|
295
|
-
if (
|
|
296
|
-
group
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const property = stringValue(binding.propertyAttribute) ?? stringValue(binding.name);
|
|
300
|
-
if (property)
|
|
301
|
-
group.value[property] = { defaultValue: binding.default ?? key };
|
|
302
|
-
const subType = stringValue(binding.resourceSubType);
|
|
303
|
-
if (subType)
|
|
304
|
-
group.subType = subType;
|
|
326
|
+
const group = groups.get(groupKey);
|
|
327
|
+
if (group)
|
|
328
|
+
group.push(binding);
|
|
329
|
+
else
|
|
330
|
+
groups.set(groupKey, [binding]);
|
|
305
331
|
}
|
|
306
|
-
const referenceTaskSubTypes = new Map([
|
|
307
|
-
['process', 'ProcessOrchestration'],
|
|
308
|
-
['agent', 'Agent'],
|
|
309
|
-
['rpa', undefined],
|
|
310
|
-
['api-workflow', 'Api'],
|
|
311
|
-
['case-management', 'CaseManagement'],
|
|
312
|
-
['flow-process', 'ProcessOrchestration'],
|
|
313
|
-
]);
|
|
314
332
|
const seenConnections = new Set();
|
|
315
333
|
const connectionResources = [];
|
|
316
334
|
visitRecords(caseplan, (record) => {
|
|
317
|
-
const type = stringValue(record.type);
|
|
318
|
-
const data = isRecord(record.data) ? record.data : undefined;
|
|
319
|
-
if (type && data && referenceTaskSubTypes.has(type)) {
|
|
320
|
-
const name = referencedBindingValue(data.name, byId);
|
|
321
|
-
const folderPath = referencedBindingValue(data.folderPath, byId);
|
|
322
|
-
if (name && folderPath) {
|
|
323
|
-
const key = referencedBindingResourceKey(data.name, byId)
|
|
324
|
-
?? referencedBindingResourceKey(data.folderPath, byId)
|
|
325
|
-
?? `${folderPath}.${name}`;
|
|
326
|
-
const groupKey = `process\0${key}`;
|
|
327
|
-
if (!grouped.has(groupKey)) {
|
|
328
|
-
grouped.set(groupKey, {
|
|
329
|
-
resource: 'process',
|
|
330
|
-
key,
|
|
331
|
-
value: {
|
|
332
|
-
name: { defaultValue: name },
|
|
333
|
-
folderPath: { defaultValue: folderPath },
|
|
334
|
-
},
|
|
335
|
-
subType: referenceTaskSubTypes.get(type),
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
335
|
if (!Array.isArray(record.context))
|
|
341
336
|
return;
|
|
342
337
|
const context = new Map(record.context
|
|
@@ -359,23 +354,126 @@ function syncBindingsV2(caseplan, outPath) {
|
|
|
359
354
|
metadata: { connector },
|
|
360
355
|
});
|
|
361
356
|
});
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
key: group.key,
|
|
366
|
-
value: group.value,
|
|
367
|
-
// A BusinessRule entry carries the bindings version deploy needs to
|
|
368
|
-
// override its folder — the metadata `uip maestro case bindings sync`
|
|
369
|
-
// writes for it (no subType).
|
|
370
|
-
metadata: group.subType
|
|
371
|
-
? { subType: group.subType }
|
|
372
|
-
: group.resource === 'BusinessRule' ? { BindingsVersion: '2.2' } : {},
|
|
373
|
-
});
|
|
374
|
-
}
|
|
357
|
+
const labels = bindingLabels(caseplan.nodes);
|
|
358
|
+
for (const group of groups.values())
|
|
359
|
+
resources.push(sidecarResourceEntry(group, labels));
|
|
375
360
|
resources.push(...connectionResources);
|
|
376
361
|
writeJsonFile(sidecarPath, { version: '2.0', resources });
|
|
377
362
|
return resources.length;
|
|
378
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* The Case Designer's label for each value slot of an app or process entry.
|
|
366
|
+
* These resources get the Designer shape (cli#5003, MST-16424): an `id`, each
|
|
367
|
+
* slot flagged and labelled, and the `BindingsVersion` / `SolutionsSupport`
|
|
368
|
+
* metadata without which deploy cannot override the entry.
|
|
369
|
+
*/
|
|
370
|
+
const DESIGNER_SLOT_NAMES = new Map([
|
|
371
|
+
['app', { name: 'App name', folderPath: 'Folder path', solutionsSupport: true }],
|
|
372
|
+
['process', { name: 'Process name', folderPath: 'Folder path', solutionsSupport: true }],
|
|
373
|
+
['ixpDeployment', { name: 'name', folderPath: 'Folder path', solutionsSupport: false }],
|
|
374
|
+
]);
|
|
375
|
+
/** The bindings version deploy needs to override an entry. */
|
|
376
|
+
const RESOURCE_BINDINGS_VERSION = '2.2';
|
|
377
|
+
/**
|
|
378
|
+
* One sidecar entry for a group of root binding rows that share a resource and
|
|
379
|
+
* a resource key. The subtype comes from the rows only: a Flow's rows carry
|
|
380
|
+
* none, so its entry has none either, as the Designer saves it.
|
|
381
|
+
*
|
|
382
|
+
* `metadata.ActivityName` labels a Designer-shaped entry with the first task
|
|
383
|
+
* that references one of its rows ({@link bindingLabels}).
|
|
384
|
+
*/
|
|
385
|
+
function sidecarResourceEntry(group, labels) {
|
|
386
|
+
const resource = group[0].resource;
|
|
387
|
+
const key = group[0].resourceKey;
|
|
388
|
+
const attributeDefault = (attribute) => {
|
|
389
|
+
const found = group.find((binding) => binding.propertyAttribute === attribute)?.default;
|
|
390
|
+
return typeof found === 'string' ? found : undefined;
|
|
391
|
+
};
|
|
392
|
+
// An in-solution resource binds an empty folderPath in the caseplan, while
|
|
393
|
+
// the sidecar names its folder by the sentinel the key carries.
|
|
394
|
+
const folderPath = isInSolutionKey(key) ? SOLUTION_FOLDER : attributeDefault('folderPath');
|
|
395
|
+
const name = attributeDefault('name');
|
|
396
|
+
const subType = group.map((binding) => binding.resourceSubType).find((found) => found !== undefined);
|
|
397
|
+
const designer = DESIGNER_SLOT_NAMES.get(resource);
|
|
398
|
+
if (designer) {
|
|
399
|
+
const value = {};
|
|
400
|
+
if (folderPath !== undefined) {
|
|
401
|
+
value.folderPath = { defaultValue: folderPath, isExpression: false, displayName: designer.folderPath };
|
|
402
|
+
}
|
|
403
|
+
if (name !== undefined) {
|
|
404
|
+
value.name = { defaultValue: name, isExpression: false, displayName: designer.name };
|
|
405
|
+
}
|
|
406
|
+
const activityName = group
|
|
407
|
+
.map((binding) => (typeof binding.id === 'string' ? labels.get(binding.id) : undefined))
|
|
408
|
+
.find((found) => found !== undefined);
|
|
409
|
+
return {
|
|
410
|
+
resource,
|
|
411
|
+
key,
|
|
412
|
+
id: `${resource}${key}`,
|
|
413
|
+
value,
|
|
414
|
+
metadata: {
|
|
415
|
+
...(activityName !== undefined && { ActivityName: activityName }),
|
|
416
|
+
BindingsVersion: RESOURCE_BINDINGS_VERSION,
|
|
417
|
+
DisplayLabel: 'name',
|
|
418
|
+
...(designer.solutionsSupport && { SolutionsSupport: 'true' }),
|
|
419
|
+
...(subType !== undefined && { subType }),
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const value = {};
|
|
424
|
+
if (name !== undefined)
|
|
425
|
+
value.name = { defaultValue: name };
|
|
426
|
+
if (folderPath !== undefined)
|
|
427
|
+
value.folderPath = { defaultValue: folderPath };
|
|
428
|
+
// A solution-resource picker (such as a Data Fabric entity) carries its folder key as a slot of its own.
|
|
429
|
+
const folderKey = attributeDefault('folderKey');
|
|
430
|
+
if (folderKey !== undefined)
|
|
431
|
+
value.folderKey = { defaultValue: folderKey };
|
|
432
|
+
return {
|
|
433
|
+
resource,
|
|
434
|
+
key,
|
|
435
|
+
value,
|
|
436
|
+
metadata: {
|
|
437
|
+
BindingsVersion: RESOURCE_BINDINGS_VERSION,
|
|
438
|
+
...(subType !== undefined && { subType }),
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The label of the first object that references each binding id
|
|
444
|
+
* (`=bindings.<id>`), in document order: the nearest enclosing `displayName`,
|
|
445
|
+
* `display.label` or `label`. The same walk as the CLI's
|
|
446
|
+
* `collectBindingLabels`, so both name an entry after the same task.
|
|
447
|
+
*/
|
|
448
|
+
function bindingLabels(nodes) {
|
|
449
|
+
const found = new Map();
|
|
450
|
+
const ownLabel = (node) => {
|
|
451
|
+
const display = isRecord(node.display) ? node.display.label : undefined;
|
|
452
|
+
return [node.displayName, display, node.label].map(stringValue).find((label) => label !== undefined);
|
|
453
|
+
};
|
|
454
|
+
const walk = (node, label) => {
|
|
455
|
+
if (typeof node === 'string') {
|
|
456
|
+
if (label !== undefined && node.startsWith('=bindings.')) {
|
|
457
|
+
const id = node.slice('=bindings.'.length);
|
|
458
|
+
if (!found.has(id))
|
|
459
|
+
found.set(id, label);
|
|
460
|
+
}
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (Array.isArray(node)) {
|
|
464
|
+
for (const item of node)
|
|
465
|
+
walk(item, label);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (!isRecord(node))
|
|
469
|
+
return;
|
|
470
|
+
const scoped = ownLabel(node) ?? label;
|
|
471
|
+
for (const value of Object.values(node))
|
|
472
|
+
walk(value, scoped);
|
|
473
|
+
};
|
|
474
|
+
walk(nodes ?? [], undefined);
|
|
475
|
+
return found;
|
|
476
|
+
}
|
|
379
477
|
function referencedBindingValue(value, bindings) {
|
|
380
478
|
const ref = stringValue(value);
|
|
381
479
|
if (!ref)
|
|
@@ -385,12 +483,6 @@ function referencedBindingValue(value, bindings) {
|
|
|
385
483
|
const binding = bindings.get(ref.slice('=bindings.'.length));
|
|
386
484
|
return binding && (stringValue(binding.default) ?? stringValue(binding.resourceKey));
|
|
387
485
|
}
|
|
388
|
-
function referencedBindingResourceKey(value, bindings) {
|
|
389
|
-
const ref = stringValue(value);
|
|
390
|
-
if (!ref?.startsWith('=bindings.'))
|
|
391
|
-
return undefined;
|
|
392
|
-
return stringValue(bindings.get(ref.slice('=bindings.'.length))?.resourceKey);
|
|
393
|
-
}
|
|
394
486
|
function visitRecords(value, visit) {
|
|
395
487
|
if (Array.isArray(value)) {
|
|
396
488
|
for (const item of value)
|
package/dist/case/decompile.js
CHANGED
|
@@ -4,6 +4,7 @@ import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
|
|
|
4
4
|
import { preserveSchemaVersion } from '../schema-version.js';
|
|
5
5
|
import { ownEntry } from '../core/own-entry.js';
|
|
6
6
|
import { isUnrestorableSlotId } from './preserve.js';
|
|
7
|
+
import { isInSolutionKey, SOLUTION_FOLDER } from './resource-identity.js';
|
|
7
8
|
const TASK_KIND_METHOD = {
|
|
8
9
|
process: 'process',
|
|
9
10
|
agent: 'agent',
|
|
@@ -133,6 +134,10 @@ function encodedSubscriptionConfig(context, owner) {
|
|
|
133
134
|
throw new Error(`${owner} has malformed subscription metadata configuration JSON.`);
|
|
134
135
|
}
|
|
135
136
|
}
|
|
137
|
+
/** A plain object with no keys — the Designer's spelling of an empty subscription body. */
|
|
138
|
+
function isEmptyObject(value) {
|
|
139
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
140
|
+
}
|
|
136
141
|
function subscriptionWhere(bag, owner) {
|
|
137
142
|
const inputs = bag.inputs;
|
|
138
143
|
if (!Array.isArray(inputs) || inputs.length !== 1)
|
|
@@ -142,6 +147,11 @@ function subscriptionWhere(bag, owner) {
|
|
|
142
147
|
throw new Error(`${owner} has an unsupported subscription input.`);
|
|
143
148
|
if (row.value === '' && row.body === undefined)
|
|
144
149
|
return undefined;
|
|
150
|
+
// The Case Designer saves a subscription with no parameters (an HTTP webhook,
|
|
151
|
+
// say) as `body: {}` with no `value`. That is the same "no where" the SDK
|
|
152
|
+
// writes as `value: ''`, not an unsupported body.
|
|
153
|
+
if (isEmptyObject(row.body) && (row.value === undefined || row.value === ''))
|
|
154
|
+
return undefined;
|
|
145
155
|
const queryParams = row.body?.queryParams;
|
|
146
156
|
if (!queryParams || typeof queryParams !== 'object' || Array.isArray(queryParams))
|
|
147
157
|
throw new Error(`${owner} has an unsupported subscription body.`);
|
|
@@ -334,11 +344,39 @@ function bindingName(value, refs) {
|
|
|
334
344
|
const id = value.slice('=bindings.'.length);
|
|
335
345
|
return refs.bindingById.get(id)?.id ?? id;
|
|
336
346
|
}
|
|
337
|
-
|
|
347
|
+
/**
|
|
348
|
+
* The literal a resource task's `data.name` / `data.folderPath` names: the
|
|
349
|
+
* value itself, or the `default` of the root binding row a `=bindings.<id>`
|
|
350
|
+
* reference points at. A reference the plan's root `bindings[]` cannot answer
|
|
351
|
+
* (no such row, or a row with no `default`) is refused: the plan names no
|
|
352
|
+
* resource there, and emitting the reference as the name would only fail
|
|
353
|
+
* again at compile.
|
|
354
|
+
*/
|
|
355
|
+
function bindingDefault(value, refs, owner, field) {
|
|
338
356
|
if (typeof value !== 'string' || !value.startsWith('=bindings.'))
|
|
339
357
|
return value;
|
|
340
358
|
const id = value.slice('=bindings.'.length);
|
|
341
|
-
|
|
359
|
+
const row = refs.bindingById.get(id);
|
|
360
|
+
if (typeof row?.default !== 'string')
|
|
361
|
+
throw new Error(`${owner} data.${field} is "${value}", but the plan's root bindings ${row ? `row "${id}" has no default` : `declare no row "${id}"`}, `
|
|
362
|
+
+ `so decompile cannot recover the resource ${field === 'name' ? 'name' : 'folder'}. `
|
|
363
|
+
+ `Give the task a name/folderPath binding pair whose rows carry the resource's name and folder as their defaults (what Studio Web writes), then decompile again.`);
|
|
364
|
+
return row.default;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* The folder a resource task's `folderPath` reference names, as the author
|
|
368
|
+
* writes it. An in-solution resource binds an empty folderPath under a
|
|
369
|
+
* `solution_folder.<name>` key; the builder spells that folder `solution_folder`
|
|
370
|
+
* (resource-identity.ts), so lift the sentinel back rather than the empty
|
|
371
|
+
* default, which would recompile to the key `.<name>`.
|
|
372
|
+
*/
|
|
373
|
+
function bindingFolder(value, refs, owner) {
|
|
374
|
+
if (typeof value === 'string' && value.startsWith('=bindings.')) {
|
|
375
|
+
const row = refs.bindingById.get(value.slice('=bindings.'.length));
|
|
376
|
+
if (typeof row?.resourceKey === 'string' && isInSolutionKey(row.resourceKey))
|
|
377
|
+
return SOLUTION_FOLDER;
|
|
378
|
+
}
|
|
379
|
+
return bindingDefault(value, refs, owner, 'folderPath');
|
|
342
380
|
}
|
|
343
381
|
function selectNextStageByExit(plan, refs) {
|
|
344
382
|
const raw = plan.metadata?.waitForHumanSelectNextStageDFConnector;
|
|
@@ -505,10 +543,17 @@ function externalCall(task, refs, library) {
|
|
|
505
543
|
throw new Error(`${owner} has unsupported serviceType "${String(data.serviceType ?? '')}".`);
|
|
506
544
|
if (!Array.isArray(data.context))
|
|
507
545
|
throw new Error(`${owner} requires a resolved context array.`);
|
|
546
|
+
// A trailing `activityConfigurationVersion: v2` row only picks the Designer
|
|
547
|
+
// panel; compile writes it back from the plan's `intsvcActivityConfig` flag.
|
|
548
|
+
const rows = data.context;
|
|
549
|
+
const last = rows[rows.length - 1];
|
|
550
|
+
const configRows = last?.name === 'activityConfigurationVersion' && last.value === 'v2' && last.type === 'string'
|
|
551
|
+
? rows.slice(0, -1)
|
|
552
|
+
: rows;
|
|
508
553
|
const expectedContext = ['activity', 'connectorKey', 'connection', 'folderKey', 'method', 'path'];
|
|
509
|
-
if (
|
|
510
|
-
throw new Error(`${owner} context must contain exactly ${expectedContext.join(', ')} in canonical order.`);
|
|
511
|
-
const context = contextMap(
|
|
554
|
+
if (configRows.map((row) => row.name).join('\0') !== expectedContext.join('\0'))
|
|
555
|
+
throw new Error(`${owner} context must contain exactly ${expectedContext.join(', ')} in canonical order, optionally followed by activityConfigurationVersion v2.`);
|
|
556
|
+
const context = contextMap({ context: configRows });
|
|
512
557
|
const stringValue = (name) => {
|
|
513
558
|
const row = context.get(name);
|
|
514
559
|
if (row?.type !== 'string' || typeof row.value !== 'string' || !row.value)
|
|
@@ -574,9 +619,12 @@ function emitTaskKind(task, refs, library) {
|
|
|
574
619
|
throw new Error(`unresolved ${kind} task "${task.displayName ?? task.id ?? '?'}" cannot carry input or output bindings.`);
|
|
575
620
|
return `.unresolved(${tsLiteral(kind)})`;
|
|
576
621
|
}
|
|
577
|
-
const
|
|
578
|
-
const
|
|
579
|
-
const
|
|
622
|
+
const owner = `${kind} task "${task.displayName ?? task.id ?? '?'}"`;
|
|
623
|
+
const name = bindingDefault(data.name, refs, owner, 'name');
|
|
624
|
+
const folderPath = bindingFolder(data.folderPath, refs, owner);
|
|
625
|
+
// No folder already means the case's own solution folder
|
|
626
|
+
// (resource-identity.ts), so an in-solution resource decompiles to none.
|
|
627
|
+
const folder = folderPath && folderPath !== SOLUTION_FOLDER ? `, ${opts([['folder', tsLiteral(folderPath)]])}` : '';
|
|
580
628
|
return `.${method}(${tsLiteral(name ?? '')}${folder})`;
|
|
581
629
|
}
|
|
582
630
|
if (isAuthorableBusinessRule(task))
|
|
@@ -642,8 +690,8 @@ function isBusinessRuleOwnOutput(row) {
|
|
|
642
690
|
function businessRuleCall(task, refs) {
|
|
643
691
|
const data = task.data ?? {};
|
|
644
692
|
const owner = `business-rule task "${task.displayName ?? task.id ?? '?'}"`;
|
|
645
|
-
const name = bindingDefault(data.name, refs);
|
|
646
|
-
const folderPath =
|
|
693
|
+
const name = bindingDefault(data.name, refs, owner, 'name');
|
|
694
|
+
const folderPath = bindingFolder(data.folderPath, refs, owner);
|
|
647
695
|
if (typeof name !== 'string' || !name || typeof folderPath !== 'string')
|
|
648
696
|
throw new Error(`${owner} must name its rule and folder (data.name / data.folderPath).`);
|
|
649
697
|
const outputRow = (Array.isArray(data.outputs) ? data.outputs : [])
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a Case resource task names the resource it runs: the `resourceKey` its
|
|
3
|
+
* root binding pair shares, and the `folderPath` default the runtime reads.
|
|
4
|
+
*
|
|
5
|
+
* For a resource deployed to a tenant folder the two agree:
|
|
6
|
+
*
|
|
7
|
+
* folder 'Shared/Finance', name 'CreditRisk'
|
|
8
|
+
* -> resourceKey 'Shared/Finance.CreditRisk', folderPath 'Shared/Finance'
|
|
9
|
+
*
|
|
10
|
+
* For a sibling built inside the same solution they do not. Its folder is the
|
|
11
|
+
* `solution_folder` sentinel (what `uip maestro case registry search --local`
|
|
12
|
+
* and `resources/solution_folder/...` report), and the platform splits it:
|
|
13
|
+
*
|
|
14
|
+
* folder 'solution_folder', name 'CreditRisk'
|
|
15
|
+
* -> resourceKey 'solution_folder.CreditRisk', folderPath ''
|
|
16
|
+
*
|
|
17
|
+
* The key keeps the sentinel because deploy and `resources refresh` match the
|
|
18
|
+
* resource by it. The `folderPath` default is empty because the running case
|
|
19
|
+
* starts the sibling in its own deployed folder; a literal `solution_folder`
|
|
20
|
+
* there passes `validate` and fails the task at runtime with "Folder does not
|
|
21
|
+
* exist". The CLI encodes the same split: `case-bindings-service.ts` writes the
|
|
22
|
+
* sentinel into `bindings_v2.json` for a `solution_folder.` key, and
|
|
23
|
+
* `case-validate-service.ts` exempts that prefix from its key/folder agreement
|
|
24
|
+
* check.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SOLUTION_FOLDER = "solution_folder";
|
|
27
|
+
export interface ResourceIdentity {
|
|
28
|
+
/** The `resourceKey` both rows of the name/folderPath binding pair carry. */
|
|
29
|
+
resourceKey: string;
|
|
30
|
+
/** The `default` of the `folderPath` binding row. */
|
|
31
|
+
folderPath: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The binding identity of resource `name` authored in `folder`. No folder
|
|
35
|
+
* means the case's own solution folder, the same as `solution_folder`:
|
|
36
|
+
*
|
|
37
|
+
* .process('notify') -> resourceKey 'solution_folder.notify', folderPath ''
|
|
38
|
+
*
|
|
39
|
+
* (A literal `.notify` key would name no folder, and deploy and `resources
|
|
40
|
+
* refresh` could not match it.)
|
|
41
|
+
*/
|
|
42
|
+
export declare function resourceIdentity(name: string, folder: string): ResourceIdentity;
|
|
43
|
+
/** True for the key of a resource built inside the same solution. */
|
|
44
|
+
export declare function isInSolutionKey(resourceKey: string): boolean;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a Case resource task names the resource it runs: the `resourceKey` its
|
|
3
|
+
* root binding pair shares, and the `folderPath` default the runtime reads.
|
|
4
|
+
*
|
|
5
|
+
* For a resource deployed to a tenant folder the two agree:
|
|
6
|
+
*
|
|
7
|
+
* folder 'Shared/Finance', name 'CreditRisk'
|
|
8
|
+
* -> resourceKey 'Shared/Finance.CreditRisk', folderPath 'Shared/Finance'
|
|
9
|
+
*
|
|
10
|
+
* For a sibling built inside the same solution they do not. Its folder is the
|
|
11
|
+
* `solution_folder` sentinel (what `uip maestro case registry search --local`
|
|
12
|
+
* and `resources/solution_folder/...` report), and the platform splits it:
|
|
13
|
+
*
|
|
14
|
+
* folder 'solution_folder', name 'CreditRisk'
|
|
15
|
+
* -> resourceKey 'solution_folder.CreditRisk', folderPath ''
|
|
16
|
+
*
|
|
17
|
+
* The key keeps the sentinel because deploy and `resources refresh` match the
|
|
18
|
+
* resource by it. The `folderPath` default is empty because the running case
|
|
19
|
+
* starts the sibling in its own deployed folder; a literal `solution_folder`
|
|
20
|
+
* there passes `validate` and fails the task at runtime with "Folder does not
|
|
21
|
+
* exist". The CLI encodes the same split: `case-bindings-service.ts` writes the
|
|
22
|
+
* sentinel into `bindings_v2.json` for a `solution_folder.` key, and
|
|
23
|
+
* `case-validate-service.ts` exempts that prefix from its key/folder agreement
|
|
24
|
+
* check.
|
|
25
|
+
*/
|
|
26
|
+
export const SOLUTION_FOLDER = 'solution_folder';
|
|
27
|
+
const SOLUTION_FOLDER_KEY_PREFIX = `${SOLUTION_FOLDER}.`;
|
|
28
|
+
/**
|
|
29
|
+
* The binding identity of resource `name` authored in `folder`. No folder
|
|
30
|
+
* means the case's own solution folder, the same as `solution_folder`:
|
|
31
|
+
*
|
|
32
|
+
* .process('notify') -> resourceKey 'solution_folder.notify', folderPath ''
|
|
33
|
+
*
|
|
34
|
+
* (A literal `.notify` key would name no folder, and deploy and `resources
|
|
35
|
+
* refresh` could not match it.)
|
|
36
|
+
*/
|
|
37
|
+
export function resourceIdentity(name, folder) {
|
|
38
|
+
if (folder === SOLUTION_FOLDER || !folder.trim())
|
|
39
|
+
return { resourceKey: `${SOLUTION_FOLDER_KEY_PREFIX}${name}`, folderPath: '' };
|
|
40
|
+
return { resourceKey: `${folder}.${name}`, folderPath: folder };
|
|
41
|
+
}
|
|
42
|
+
/** True for the key of a resource built inside the same solution. */
|
|
43
|
+
export function isInSolutionKey(resourceKey) {
|
|
44
|
+
return resourceKey.startsWith(SOLUTION_FOLDER_KEY_PREFIX);
|
|
45
|
+
}
|