@uipath/maestro-builder-sdk 6.51.3 → 6.52.1
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/case/decompile.js +5 -1
- package/dist/case/serialize.js +24 -29
- package/dist/check.d.ts +8 -0
- package/dist/check.js +33 -2
- package/dist/cli/commands/flow.js +6 -0
- package/dist/cli-run.d.ts +2 -0
- package/dist/cli-run.js +8 -0
- package/dist/core/connector-configuration.d.ts +29 -2
- package/dist/core/connector-configuration.js +45 -3
- package/dist/core/connector-inputs.d.ts +10 -0
- package/dist/core/connector-inputs.js +16 -0
- package/dist/core/nodes/extensions.d.ts +23 -0
- package/dist/core/nodes/extensions.js +18 -0
- package/dist/core/nodes/family.d.ts +13 -0
- package/dist/core/nodes/node-type.d.ts +19 -1
- package/dist/core/nodes/package-definition.d.ts +82 -0
- package/dist/core/nodes/package-definition.js +162 -0
- package/dist/core/nodes/registry.d.ts +14 -2
- package/dist/core/nodes/registry.js +68 -25
- package/dist/decompile-cli.d.ts +1 -1
- package/dist/decompile-cli.js +14 -4
- package/dist/decompile.d.ts +14 -0
- package/dist/decompile.js +77 -9
- package/dist/extend.d.ts +81 -0
- package/dist/extend.js +56 -0
- package/dist/flow-node-registry.d.ts +7 -0
- package/dist/flow-node-registry.js +14 -0
- package/dist/merge-cli.js +12 -2
- package/dist/merge-compile.js +14 -1
- package/dist/node-packages.d.ts +68 -0
- package/dist/node-packages.js +279 -0
- package/dist/nodes/connector/flow/node-type.js +2 -6
- package/dist/serialize.d.ts +2 -1
- package/dist/serialize.js +10 -3
- package/package.json +6 -2
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The node definition a node-type package writes into a compiled `.flow`.
|
|
3
|
+
*
|
|
4
|
+
* A package type has no platform definition — neither the SDK's bundled snapshot
|
|
5
|
+
* nor the tenant has one — so the file has to carry everything a reader needs:
|
|
6
|
+
* the designer's display and handles, the input and output schemas, and the
|
|
7
|
+
* `model.expansion` template the `.flow` → BPMN converter expands.
|
|
8
|
+
*
|
|
9
|
+
* ## Why one-node expansion
|
|
10
|
+
*
|
|
11
|
+
* The product's converter (vendored in `local-engine`) resolves a node's
|
|
12
|
+
* definition as `manifestDef ?? fileDef` and reads a node's per-instance input
|
|
13
|
+
* VALUES only through a definition's `model.expansion`: a `context` entry's
|
|
14
|
+
* `source: 'node.data.inputs.<name>'` is read off each placed node while the
|
|
15
|
+
* template is expanded. So every package definition expands to exactly one
|
|
16
|
+
* `bpmn:ServiceTask` that REPLACES the node under its own id, carries
|
|
17
|
+
* `<uipath:type value="<serviceType>">`, its inputs as context, and its
|
|
18
|
+
* `=response` / `=Error` output rows — the element shape a runtime's dispatch
|
|
19
|
+
* lane reads for a connector. Expansion drops the node's original edges, so the
|
|
20
|
+
* template re-creates the `success` edge and, when the author wired `.onError`,
|
|
21
|
+
* an error boundary with the `error` edge.
|
|
22
|
+
*
|
|
23
|
+
* The input NAMES are the contract with the runtime half of the package: a
|
|
24
|
+
* handler reads them off `TaskContext.inputs`. A `string` context entry is
|
|
25
|
+
* emitted as `value` and a `json` one as a JSON `body`; the converter turns a
|
|
26
|
+
* falsy `string` into `""` and a falsy `json` into `{}`, so a package encodes
|
|
27
|
+
* booleans and numbers as `=js:` expressions rather than relying on either.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_PACKAGE_DEFINITION_VERSION = '1.0';
|
|
30
|
+
const ERROR_SCHEMA = {
|
|
31
|
+
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
32
|
+
type: 'object',
|
|
33
|
+
required: ['code', 'message', 'detail', 'category', 'status'],
|
|
34
|
+
properties: {
|
|
35
|
+
code: { type: 'string', description: 'Error code as a string' },
|
|
36
|
+
message: { type: 'string', description: 'High-level error message' },
|
|
37
|
+
detail: { type: 'string', description: 'Detailed error description' },
|
|
38
|
+
category: { type: 'string', description: 'Error category' },
|
|
39
|
+
status: { type: 'integer', description: 'HTTP status code' },
|
|
40
|
+
},
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
};
|
|
43
|
+
/** The context entry type the converter emits for a field: structured values ride as a JSON body. */
|
|
44
|
+
function contextType(field) {
|
|
45
|
+
return field.type === 'array' || field.type === 'object' ? 'json' : 'string';
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build a package node's definition from its shape: the complete manifest a
|
|
49
|
+
* compiled flow carries as a `snapshot` reference, with the one-node expansion
|
|
50
|
+
* described above. Provenance (`source.package`) is stamped at serialize time
|
|
51
|
+
* from the family's `package`, not here, so a shape stays free of the package's
|
|
52
|
+
* own name.
|
|
53
|
+
*/
|
|
54
|
+
export function defineNodeDefinition(shape) {
|
|
55
|
+
const version = shape.version ?? DEFAULT_PACKAGE_DEFINITION_VERSION;
|
|
56
|
+
const context = Object.entries(shape.inputs).map(([name, field]) => ({
|
|
57
|
+
name,
|
|
58
|
+
type: contextType(field),
|
|
59
|
+
source: `node.data.inputs.${name}`,
|
|
60
|
+
}));
|
|
61
|
+
const properties = Object.fromEntries(Object.entries(shape.inputs).map(([name, field]) => [name, {
|
|
62
|
+
type: field.type,
|
|
63
|
+
description: field.description,
|
|
64
|
+
...(field.enum ? { enum: [...field.enum] } : {}),
|
|
65
|
+
}]));
|
|
66
|
+
const required = Object.entries(shape.inputs).filter(([, f]) => f.required).map(([name]) => name);
|
|
67
|
+
const inputDefaults = Object.fromEntries(Object.entries(shape.inputs).filter(([, f]) => f.default !== undefined).map(([name, f]) => [name, f.default]));
|
|
68
|
+
return {
|
|
69
|
+
nodeType: shape.nodeType,
|
|
70
|
+
version,
|
|
71
|
+
category: shape.category ?? 'package',
|
|
72
|
+
description: shape.description,
|
|
73
|
+
tags: shape.tags ? [...shape.tags] : ['package', ...shape.nodeType.split('.').slice(1)],
|
|
74
|
+
sortOrder: 90,
|
|
75
|
+
supportsErrorHandling: true,
|
|
76
|
+
display: { label: shape.label, icon: shape.icon },
|
|
77
|
+
handleConfiguration: [
|
|
78
|
+
{ position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
|
|
79
|
+
{
|
|
80
|
+
position: 'right',
|
|
81
|
+
handles: [
|
|
82
|
+
{ id: 'success', type: 'source', handleType: 'output' },
|
|
83
|
+
{
|
|
84
|
+
id: 'error', label: 'Error', type: 'source', handleType: 'output',
|
|
85
|
+
visible: '{inputs.errorHandlingEnabled}', constraints: { maxConnections: 1 },
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
debug: { runtime: 'bpmnEngine' },
|
|
91
|
+
model: {
|
|
92
|
+
type: 'bpmn:ServiceTask',
|
|
93
|
+
serviceType: shape.serviceType,
|
|
94
|
+
version: 'v1',
|
|
95
|
+
expansion: {
|
|
96
|
+
nodes: [
|
|
97
|
+
{
|
|
98
|
+
id: '{nodeId}',
|
|
99
|
+
type: 'bpmn:ServiceTask',
|
|
100
|
+
replaceOriginal: true,
|
|
101
|
+
position: { x: 0, y: 0 },
|
|
102
|
+
data: {
|
|
103
|
+
label: '{node.data.label}',
|
|
104
|
+
model: {
|
|
105
|
+
serviceType: shape.serviceType,
|
|
106
|
+
version: 'v1',
|
|
107
|
+
context,
|
|
108
|
+
outputs: [
|
|
109
|
+
{ name: 'output', type: 'object', source: '=response', var: 'output' },
|
|
110
|
+
{ name: 'error', type: 'object', source: '=Error', var: 'error' },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
condition: "hasEdgeFromHandle('error')",
|
|
117
|
+
id: '_Implicit_BoundaryError_{nodeId}',
|
|
118
|
+
type: 'bpmn:BoundaryEvent',
|
|
119
|
+
parentId: null,
|
|
120
|
+
position: { x: 0, y: 0 },
|
|
121
|
+
data: {
|
|
122
|
+
label: 'Error',
|
|
123
|
+
attachedToId: '{nodeId}',
|
|
124
|
+
eventDefinition: { type: 'bpmn:ErrorEventDefinition', id: '_Implicit_BoundaryError_{nodeId}_Error' },
|
|
125
|
+
// The `=Error` envelope is written by the BOUNDARY's output row when it
|
|
126
|
+
// fires, not by the failed task's own row — without this, `.onError`
|
|
127
|
+
// handlers read `<step>.error` as undefined. `custom: true` keeps the var
|
|
128
|
+
// as written: the converter would otherwise prefix it with the boundary's id.
|
|
129
|
+
model: { outputs: [{ name: 'error', type: 'object', source: '=Error', var: '{nodeId}.error', custom: true }] },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
edges: [
|
|
134
|
+
{ matchOriginalEdgeByHandle: 'success', preserveEdgeId: true, source: '{nodeId}', type: 'bpmn:SequenceFlow' },
|
|
135
|
+
{
|
|
136
|
+
condition: "hasEdgeFromHandle('error')",
|
|
137
|
+
matchOriginalEdgeByHandle: 'error',
|
|
138
|
+
preserveEdgeId: true,
|
|
139
|
+
source: '_Implicit_BoundaryError_{nodeId}',
|
|
140
|
+
type: 'bpmn:SequenceFlow',
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
inputDefinition: { type: 'object', properties, required },
|
|
146
|
+
inputDefaults,
|
|
147
|
+
outputDefinition: {
|
|
148
|
+
output: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
description: `What ${shape.label} produced`,
|
|
151
|
+
source: '=response',
|
|
152
|
+
var: 'output',
|
|
153
|
+
schema: {
|
|
154
|
+
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: Object.fromEntries(Object.entries(shape.outputs).map(([k, v]) => [k, { type: v.type, description: v.description }])),
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
error: { type: 'object', description: 'Error information if the step fails', source: '=Error', var: 'error', schema: ERROR_SCHEMA },
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -35,13 +35,25 @@ export interface NodeTypeRegistryOptions {
|
|
|
35
35
|
export declare class NodeTypeRegistry<Ctx extends ArtifactContexts> {
|
|
36
36
|
#private;
|
|
37
37
|
readonly artifact: string;
|
|
38
|
-
readonly families: readonly NodeFamily<Ctx>[];
|
|
39
38
|
/**
|
|
40
39
|
* @param artifact - The artifact's name, for messages (`'flow'`).
|
|
41
|
-
* @param families - Every family this artifact supports, in no significant order.
|
|
40
|
+
* @param families - Every built-in family this artifact supports, in no significant order.
|
|
42
41
|
* @param options - What the artifact knows that the contract does not.
|
|
43
42
|
*/
|
|
44
43
|
constructor(artifact: string, families: readonly NodeFamily<Ctx>[], options: NodeTypeRegistryOptions);
|
|
44
|
+
/** Every family the registry knows: the built-in ones, then the registered packages' in registration order. */
|
|
45
|
+
get families(): readonly NodeFamily<Ctx>[];
|
|
46
|
+
/**
|
|
47
|
+
* Register a node-type package's family (`@uipath/maestro-builder-sdk/extend`).
|
|
48
|
+
*
|
|
49
|
+
* The whole registry is re-indexed with the new family last, under the same
|
|
50
|
+
* rules construction applies, so a package cannot take a kind, a type name, a
|
|
51
|
+
* prefix or a diagnostic code a built-in family owns, nor one another package
|
|
52
|
+
* registered first. A refusal names the package and leaves the live index as
|
|
53
|
+
* it was. Registering the same family object twice is a no-op: an ESM module
|
|
54
|
+
* runs once per process, but a test may import a package through two paths.
|
|
55
|
+
*/
|
|
56
|
+
register(family: NodeFamily<Ctx>): void;
|
|
45
57
|
/**
|
|
46
58
|
* The node type for a spec, by its kind, or undefined when the kind is not
|
|
47
59
|
* registered. When several types share the kind, the one whose `matches(spec)`
|
|
@@ -6,19 +6,57 @@ function owns(p, typeName) {
|
|
|
6
6
|
}
|
|
7
7
|
export class NodeTypeRegistry {
|
|
8
8
|
artifact;
|
|
9
|
-
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
#byPrefix = [];
|
|
9
|
+
#builtIn;
|
|
10
|
+
#extensions = [];
|
|
11
|
+
#index;
|
|
12
|
+
#options;
|
|
14
13
|
/**
|
|
15
14
|
* @param artifact - The artifact's name, for messages (`'flow'`).
|
|
16
|
-
* @param families - Every family this artifact supports, in no significant order.
|
|
15
|
+
* @param families - Every built-in family this artifact supports, in no significant order.
|
|
17
16
|
* @param options - What the artifact knows that the contract does not.
|
|
18
17
|
*/
|
|
19
18
|
constructor(artifact, families, options) {
|
|
20
19
|
this.artifact = artifact;
|
|
21
|
-
this
|
|
20
|
+
this.#builtIn = families;
|
|
21
|
+
this.#options = options;
|
|
22
|
+
const { index, problems } = this.#build(families);
|
|
23
|
+
if (problems.length > 0) {
|
|
24
|
+
throw new Error(`internal SDK error: the ${artifact} node-type registry is inconsistent:\n`
|
|
25
|
+
+ problems.map((p) => ` - ${p}`).join('\n'));
|
|
26
|
+
}
|
|
27
|
+
this.#index = index;
|
|
28
|
+
}
|
|
29
|
+
/** Every family the registry knows: the built-in ones, then the registered packages' in registration order. */
|
|
30
|
+
get families() {
|
|
31
|
+
return [...this.#builtIn, ...this.#extensions];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Register a node-type package's family (`@uipath/maestro-builder-sdk/extend`).
|
|
35
|
+
*
|
|
36
|
+
* The whole registry is re-indexed with the new family last, under the same
|
|
37
|
+
* rules construction applies, so a package cannot take a kind, a type name, a
|
|
38
|
+
* prefix or a diagnostic code a built-in family owns, nor one another package
|
|
39
|
+
* registered first. A refusal names the package and leaves the live index as
|
|
40
|
+
* it was. Registering the same family object twice is a no-op: an ESM module
|
|
41
|
+
* runs once per process, but a test may import a package through two paths.
|
|
42
|
+
*/
|
|
43
|
+
register(family) {
|
|
44
|
+
if (this.#extensions.includes(family))
|
|
45
|
+
return;
|
|
46
|
+
if (family.package === undefined) {
|
|
47
|
+
throw new Error(`cannot register node family "${family.id}": a family registered at run time must name its package (NodeFamily.package).`);
|
|
48
|
+
}
|
|
49
|
+
const { index, problems } = this.#build([...this.#builtIn, ...this.#extensions, family]);
|
|
50
|
+
if (problems.length > 0) {
|
|
51
|
+
throw new Error(`cannot register node family "${family.id}" from ${family.package.name}:\n`
|
|
52
|
+
+ problems.map((p) => ` - ${p}`).join('\n'));
|
|
53
|
+
}
|
|
54
|
+
this.#extensions = [...this.#extensions, family];
|
|
55
|
+
this.#index = index;
|
|
56
|
+
}
|
|
57
|
+
#build(families) {
|
|
58
|
+
const index = { byKind: new Map(), byTypeName: new Map(), byPrefix: [] };
|
|
59
|
+
const options = this.#options;
|
|
22
60
|
const problems = [];
|
|
23
61
|
const familyIds = new Set();
|
|
24
62
|
const codeOwner = new Map();
|
|
@@ -36,7 +74,7 @@ export class NodeTypeRegistry {
|
|
|
36
74
|
for (const listed of family.types) {
|
|
37
75
|
const type = asVersioned(listed);
|
|
38
76
|
const where = `${family.id}/${type.kind}`;
|
|
39
|
-
const sharing =
|
|
77
|
+
const sharing = index.byKind.get(type.kind);
|
|
40
78
|
if (sharing && (!type.matches || sharing.some((other) => !other.type.matches))) {
|
|
41
79
|
problems.push(`kind "${type.kind}" is registered twice (${[...sharing, { family, type }].map((e) => `${e.family.id}/${e.type.typeName}`).join(' and ')}); node types that share a kind must each declare matches(spec)`);
|
|
42
80
|
}
|
|
@@ -45,22 +83,22 @@ export class NodeTypeRegistry {
|
|
|
45
83
|
if (type.typeName !== `${prefix}*`) {
|
|
46
84
|
problems.push(`${where}: a type that owns the names under "${prefix}" is named "${prefix}*", not "${type.typeName}"`);
|
|
47
85
|
}
|
|
48
|
-
const overlap =
|
|
86
|
+
const overlap = index.byPrefix.find((p) => p.prefix.startsWith(prefix) || prefix.startsWith(p.prefix));
|
|
49
87
|
if (overlap)
|
|
50
88
|
problems.push(`${where}: prefix "${prefix}" overlaps "${overlap.prefix}" (${placeOf(overlap.entry)})`);
|
|
51
|
-
const shadowed = [...
|
|
89
|
+
const shadowed = [...index.byTypeName.keys()].find((n) => n.startsWith(prefix) && (type.typeNameMatches?.(n) ?? true));
|
|
52
90
|
if (shadowed)
|
|
53
91
|
problems.push(`${where}: prefix "${prefix}" would also own the registered type "${shadowed}"`);
|
|
54
92
|
}
|
|
55
93
|
else {
|
|
56
|
-
const owner =
|
|
94
|
+
const owner = index.byPrefix.find((p) => owns(p, type.typeName));
|
|
57
95
|
if (owner)
|
|
58
96
|
problems.push(`${where}: type name "${type.typeName}" falls under the prefix "${owner.prefix}" (${placeOf(owner.entry)})`);
|
|
59
97
|
}
|
|
60
98
|
if (type.typeNameMatches !== undefined && type.typeNamePrefix === undefined) {
|
|
61
99
|
problems.push(`${where}: typeNameMatches narrows a typeNamePrefix, and the type declares none`);
|
|
62
100
|
}
|
|
63
|
-
const typeOwner =
|
|
101
|
+
const typeOwner = index.byTypeName.get(type.typeName);
|
|
64
102
|
if (typeOwner) {
|
|
65
103
|
problems.push(`type name "${type.typeName}" is registered twice (${placeOf(typeOwner)} and ${where}); one VersionedNodeType owns every version of a type`);
|
|
66
104
|
}
|
|
@@ -72,19 +110,24 @@ export class NodeTypeRegistry {
|
|
|
72
110
|
}
|
|
73
111
|
for (const [key, impl] of Object.entries(type.versions)) {
|
|
74
112
|
problems.push(...implementationProblems(where, key, impl, type, options));
|
|
113
|
+
// A package's definition is the package's: the platform does not run it, so
|
|
114
|
+
// a built-in family may not declare one, and a package family declares nothing else.
|
|
115
|
+
if (impl.definition === 'package' && family.package === undefined) {
|
|
116
|
+
problems.push(`${where}@${key}: is a package definition, so it must come from a node-type package (NodeFamily.package), not a built-in family`);
|
|
117
|
+
}
|
|
118
|
+
if (impl.definition !== 'package' && family.package !== undefined) {
|
|
119
|
+
problems.push(`${where}@${key}: comes from package ${family.package.name}, so it must declare definition: 'package' (found '${impl.definition}')`);
|
|
120
|
+
}
|
|
75
121
|
}
|
|
76
122
|
const entry = { family, type };
|
|
77
|
-
|
|
78
|
-
|
|
123
|
+
index.byKind.set(type.kind, [...(sharing ?? []), entry]);
|
|
124
|
+
index.byTypeName.set(type.typeName, entry);
|
|
79
125
|
if (type.typeNamePrefix !== undefined) {
|
|
80
|
-
|
|
126
|
+
index.byPrefix.push({ prefix: type.typeNamePrefix, matches: type.typeNameMatches?.bind(type), entry });
|
|
81
127
|
}
|
|
82
128
|
}
|
|
83
129
|
}
|
|
84
|
-
|
|
85
|
-
throw new Error(`internal SDK error: the ${artifact} node-type registry is inconsistent:\n`
|
|
86
|
-
+ problems.map((p) => ` - ${p}`).join('\n'));
|
|
87
|
-
}
|
|
130
|
+
return { index, problems };
|
|
88
131
|
}
|
|
89
132
|
/**
|
|
90
133
|
* The node type for a spec, by its kind, or undefined when the kind is not
|
|
@@ -92,7 +135,7 @@ export class NodeTypeRegistry {
|
|
|
92
135
|
* holds — undefined when none does; two matching is an SDK bug and throws.
|
|
93
136
|
*/
|
|
94
137
|
byKind(kind, spec) {
|
|
95
|
-
const all = this.#byKind.get(kind);
|
|
138
|
+
const all = this.#index.byKind.get(kind);
|
|
96
139
|
if (!all)
|
|
97
140
|
return undefined;
|
|
98
141
|
if (all.length === 1)
|
|
@@ -105,7 +148,7 @@ export class NodeTypeRegistry {
|
|
|
105
148
|
}
|
|
106
149
|
/** Every node type registered for a kind (several when they share it), in registration order. */
|
|
107
150
|
typesOfKind(kind) {
|
|
108
|
-
return this.#byKind.get(kind) ?? [];
|
|
151
|
+
return this.#index.byKind.get(kind) ?? [];
|
|
109
152
|
}
|
|
110
153
|
/**
|
|
111
154
|
* The node type for a type name in the file — the exact name, else the type
|
|
@@ -115,14 +158,14 @@ export class NodeTypeRegistry {
|
|
|
115
158
|
* read it as its factory, so it still does.
|
|
116
159
|
*/
|
|
117
160
|
byTypeName(typeName) {
|
|
118
|
-
const exact = this.#byTypeName.get(typeName);
|
|
161
|
+
const exact = this.#index.byTypeName.get(typeName);
|
|
119
162
|
if (exact)
|
|
120
163
|
return exact;
|
|
121
|
-
return this.#byPrefix.find((p) => owns(p, typeName))?.entry;
|
|
164
|
+
return this.#index.byPrefix.find((p) => owns(p, typeName))?.entry;
|
|
122
165
|
}
|
|
123
166
|
/** Every registered kind, sorted. */
|
|
124
167
|
kinds() {
|
|
125
|
-
return [...this.#byKind.keys()].sort();
|
|
168
|
+
return [...this.#index.byKind.keys()].sort();
|
|
126
169
|
}
|
|
127
170
|
}
|
|
128
171
|
/** Where a registered type lives, as the registry's messages name it: `<family>/<kind>`. */
|
|
@@ -154,7 +197,7 @@ function implementationProblems(where, key, impl, type, options) {
|
|
|
154
197
|
}
|
|
155
198
|
}
|
|
156
199
|
else if (impl.errorPort === undefined) {
|
|
157
|
-
out.push(`${at}: is
|
|
200
|
+
out.push(`${at}: is ${impl.definition}, so it must declare errorPort — there is no bundled definition to ask`);
|
|
158
201
|
}
|
|
159
202
|
return out;
|
|
160
203
|
}
|
package/dist/decompile-cli.d.ts
CHANGED
package/dist/decompile-cli.js
CHANGED
|
@@ -23,9 +23,10 @@ import { basename, dirname, join, resolve } from 'node:path';
|
|
|
23
23
|
import { defaultSourceOut, workDirBeside, writeSource } from './workdir.js';
|
|
24
24
|
import { decompile } from './decompile.js';
|
|
25
25
|
import { resolveFlowRefs } from './ref-resolve.js';
|
|
26
|
-
import { flagValue, runWhenInvokedDirectly } from './cli-run.js';
|
|
26
|
+
import { flagValue, flagValues, runWhenInvokedDirectly } from './cli-run.js';
|
|
27
27
|
import { PACKAGE_NAME } from './package-name.js';
|
|
28
28
|
import { commentText } from './core/comment-text.js';
|
|
29
|
+
import { loadNodePackages } from './node-packages.js';
|
|
29
30
|
import { pipelineFrontDoor } from './registry/uip/pipeline-front-door.js';
|
|
30
31
|
/** Derive a PascalCase base name from the flow name/id for the default output file. */
|
|
31
32
|
function baseName(flow) {
|
|
@@ -37,12 +38,12 @@ function baseName(flow) {
|
|
|
37
38
|
.join('');
|
|
38
39
|
return pascal || 'Flow';
|
|
39
40
|
}
|
|
40
|
-
export function run(argv) {
|
|
41
|
+
export async function run(argv) {
|
|
41
42
|
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
42
|
-
console.error('usage: flow-decompile <original.flow> [-o Name.flow.ts] [--import <specifier>] [--strict]');
|
|
43
|
+
console.error('usage: flow-decompile <original.flow> [-o Name.flow.ts] [--import <specifier>] [--package <name>]... [--strict]');
|
|
43
44
|
return 2;
|
|
44
45
|
}
|
|
45
|
-
const flags = new Set(['-o', '--import']);
|
|
46
|
+
const flags = new Set(['-o', '--import', '--package']);
|
|
46
47
|
const pos = argv.find((a, i) => !flags.has(a) && !flags.has(argv[i - 1]) && !a.startsWith('--'));
|
|
47
48
|
if (!pos) {
|
|
48
49
|
console.error('flow-decompile: missing <original.flow>');
|
|
@@ -55,6 +56,15 @@ export function run(argv) {
|
|
|
55
56
|
baseUri: pos,
|
|
56
57
|
read: (path) => readFileSync(path, 'utf8'),
|
|
57
58
|
});
|
|
59
|
+
// Node-type packages the file needs: loaded only when the project trusts them
|
|
60
|
+
// (package.json nodePackages, or --package), resolved from the .flow's own
|
|
61
|
+
// directory (node-packages.ts). An untrusted or missing package is a warning,
|
|
62
|
+
// and its nodes print as rawNode(...).
|
|
63
|
+
const packages = await loadNodePackages(pos, flow, flagValues(argv, '--package'));
|
|
64
|
+
for (const message of packages.warnings)
|
|
65
|
+
console.error(`flow-decompile: warning: ${message}`);
|
|
66
|
+
if (packages.loaded.length > 0)
|
|
67
|
+
reportResult({ packages: packages.loaded.map((p) => p.package) });
|
|
58
68
|
const ts = decompile(flow, {
|
|
59
69
|
importSpecifier: flagValue(argv, '--import') ?? PACKAGE_NAME,
|
|
60
70
|
bestEffort: !argv.includes('--strict'),
|
package/dist/decompile.d.ts
CHANGED
|
@@ -152,5 +152,19 @@ export interface DecompileOptions {
|
|
|
152
152
|
*/
|
|
153
153
|
readSidecar?: (relativePath: string) => string | undefined;
|
|
154
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Tracks which identifiers the emitted source uses, per module, so each import
|
|
157
|
+
* is exact. A built-in factory lands in the SDK's import; a node-type package's
|
|
158
|
+
* factory (`FactoryCall.module`, the family's `package.name`) lands in that
|
|
159
|
+
* package's own import line, so the emitted source compiles against the same
|
|
160
|
+
* packages the original was written with (docs/EXTENSIBLE_NODE_TYPES_DESIGN.md §3.5).
|
|
161
|
+
*/
|
|
162
|
+
/**
|
|
163
|
+
* The SDK names the decompiler itself may import (its helpers), beside every
|
|
164
|
+
* built-in family's factory. A package factory with one of these names is
|
|
165
|
+
* aliased, whichever is requested first, so the SDK's own name is always bare.
|
|
166
|
+
* `decompile-imports.test.ts` checks this list against the `imp.use('…')` sites.
|
|
167
|
+
*/
|
|
168
|
+
export declare const SDK_HELPER_NAMES: readonly string[];
|
|
155
169
|
export declare function decompile(flow: FlowFile, options?: DecompileOptions): string;
|
|
156
170
|
export {};
|
package/dist/decompile.js
CHANGED
|
@@ -308,16 +308,76 @@ class Emit {
|
|
|
308
308
|
return this.lines.join('\n');
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
|
-
/**
|
|
311
|
+
/**
|
|
312
|
+
* Tracks which identifiers the emitted source uses, per module, so each import
|
|
313
|
+
* is exact. A built-in factory lands in the SDK's import; a node-type package's
|
|
314
|
+
* factory (`FactoryCall.module`, the family's `package.name`) lands in that
|
|
315
|
+
* package's own import line, so the emitted source compiles against the same
|
|
316
|
+
* packages the original was written with (docs/EXTENSIBLE_NODE_TYPES_DESIGN.md §3.5).
|
|
317
|
+
*/
|
|
318
|
+
/**
|
|
319
|
+
* The SDK names the decompiler itself may import (its helpers), beside every
|
|
320
|
+
* built-in family's factory. A package factory with one of these names is
|
|
321
|
+
* aliased, whichever is requested first, so the SDK's own name is always bare.
|
|
322
|
+
* `decompile-imports.test.ts` checks this list against the `imp.use('…')` sites.
|
|
323
|
+
*/
|
|
324
|
+
export const SDK_HELPER_NAMES = [
|
|
325
|
+
'connector', 'conversationTrigger', 'err', 'flow', 'formTrigger', 'input', 'ixpExtract', 'js', 'lit', 'manual',
|
|
326
|
+
'mock', 'onEvent', 'out', 'ran', 'rawNode', 'scheduled', 'subflow', 'types', 'v', 'voiceTrigger', 'waitForEvent',
|
|
327
|
+
];
|
|
328
|
+
/** `@acme/flow-nodes` → `acme_flow_nodes`: the alias suffix for a package's colliding factory. */
|
|
329
|
+
function moduleSlug(module) {
|
|
330
|
+
return module.replace(/^@/, '').replace(/[^A-Za-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
|
|
331
|
+
}
|
|
312
332
|
class Imports {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
333
|
+
/** `undefined` is the SDK itself; the specifier is only known when the lines are printed. */
|
|
334
|
+
used = new Map([[undefined, new Map([['flow', 'flow']])]]);
|
|
335
|
+
/** Every local identifier handed out, so no two modules bind the same name. */
|
|
336
|
+
locals = new Set(['flow']);
|
|
337
|
+
/** Names a package may not take bare: the SDK's helpers and every built-in factory. */
|
|
338
|
+
reserved;
|
|
339
|
+
constructor() {
|
|
340
|
+
this.reserved = new Set([
|
|
341
|
+
...SDK_HELPER_NAMES,
|
|
342
|
+
...FLOW_NODE_TYPES.families.filter((f) => f.package === undefined).flatMap((f) => f.types.map((t) => t.factory)),
|
|
343
|
+
]);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* The local identifier to print for `name` imported from `module` (`undefined`
|
|
347
|
+
* for the SDK). An SDK name is always its own; a package factory keeps its name
|
|
348
|
+
* unless the SDK owns it or another module already binds it, and is then
|
|
349
|
+
* imported `as <name>_<package slug>` — deterministic, so the emitted source is
|
|
350
|
+
* stable and `import { post as post_acme_flow_nodes }` reads as what it is.
|
|
351
|
+
*/
|
|
352
|
+
use(name, module) {
|
|
353
|
+
let names = this.used.get(module);
|
|
354
|
+
if (names === undefined) {
|
|
355
|
+
names = new Map();
|
|
356
|
+
this.used.set(module, names);
|
|
357
|
+
}
|
|
358
|
+
const existing = names.get(name);
|
|
359
|
+
if (existing !== undefined)
|
|
360
|
+
return existing;
|
|
361
|
+
let local = name;
|
|
362
|
+
if (module !== undefined && (this.reserved.has(name) || this.locals.has(name))) {
|
|
363
|
+
local = `${name}_${moduleSlug(module)}`;
|
|
364
|
+
for (let n = 2; this.locals.has(local); n++)
|
|
365
|
+
local = `${name}_${moduleSlug(module)}_${n}`;
|
|
366
|
+
}
|
|
367
|
+
names.set(name, local);
|
|
368
|
+
this.locals.add(local);
|
|
369
|
+
return local;
|
|
317
370
|
}
|
|
371
|
+
/** One import per module: the SDK first, then the packages by name. */
|
|
318
372
|
line(specifier) {
|
|
319
|
-
const
|
|
320
|
-
|
|
373
|
+
const lines = [];
|
|
374
|
+
for (const [module, names] of [...this.used].sort(([a], [b]) => (a === undefined ? -1 : b === undefined ? 1 : a.localeCompare(b)))) {
|
|
375
|
+
if (names.size === 0)
|
|
376
|
+
continue;
|
|
377
|
+
const bindings = [...names].sort(([a], [b]) => a.localeCompare(b)).map(([name, local]) => (local === name ? name : `${name} as ${local}`));
|
|
378
|
+
lines.push(`import { ${bindings.join(', ')} } from ${tsLiteral(module ?? specifier)};`);
|
|
379
|
+
}
|
|
380
|
+
return lines.join('\n');
|
|
321
381
|
}
|
|
322
382
|
}
|
|
323
383
|
// ─── Expression inversion ─────────────────────────────────────────────────────
|
|
@@ -774,7 +834,9 @@ function emitRegisteredSpec(node, imp, inputNames, graph) {
|
|
|
774
834
|
};
|
|
775
835
|
const options = opts((call.options ?? []).map(([k, v]) => [k, v]));
|
|
776
836
|
const args = [...(call.positional ?? []), ...(options ? [options] : [])];
|
|
777
|
-
|
|
837
|
+
// A package type's factory is imported from its package, not from the SDK.
|
|
838
|
+
const origin = FLOW_NODE_TYPES.byTypeName(node.type)?.family.package;
|
|
839
|
+
return `${imp.use(call.factory, call.module ?? origin?.name)}(${args.join(', ')})`
|
|
778
840
|
+ (call.comment !== undefined ? ` /* ${commentText(call.comment)} */` : '');
|
|
779
841
|
}
|
|
780
842
|
/**
|
|
@@ -937,12 +999,18 @@ function unsupportedSpec(node, imp, o) {
|
|
|
937
999
|
}
|
|
938
1000
|
const inputs = node.inputs ?? {};
|
|
939
1001
|
const inputsSrc = Object.keys(inputs).length > 0 ? JSON.stringify(inputs, null, 2) : undefined;
|
|
1002
|
+
// A definition a node-type package wrote names the package; the node prints
|
|
1003
|
+
// as rawNode because the project does not trust (or has not installed) it.
|
|
1004
|
+
const origin = def.source?.package;
|
|
1005
|
+
const hint = typeof origin === 'string'
|
|
1006
|
+
? ` /* from node-type package ${commentText(origin)}: load it with --package or package.json nodePackages to get its factory */`
|
|
1007
|
+
: '';
|
|
940
1008
|
return `${imp.use('rawNode')}(${opts([
|
|
941
1009
|
['nodeType', tsLiteral(node.type)],
|
|
942
1010
|
['version', tsText(node.typeVersion)],
|
|
943
1011
|
['manifest', constName],
|
|
944
1012
|
['inputs', inputsSrc],
|
|
945
|
-
])})`;
|
|
1013
|
+
])})${hint}`;
|
|
946
1014
|
}
|
|
947
1015
|
if (o.bestEffort === false) {
|
|
948
1016
|
throw new Error(`decompile: unsupported node type "${node.type}" (node "${node.id}") and the file carries no `
|
package/dist/extend.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The extension surface of the Flow SDK: what a NODE-TYPE PACKAGE imports.
|
|
3
|
+
*
|
|
4
|
+
* A node-type package carries a family of node types the SDK does not ship —
|
|
5
|
+
* the Local family (`@uipath/flow-local-nodes`) is the first — and makes them
|
|
6
|
+
* available to a `.flow.ts` through a normal import:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { flow, manual } from '@uipath/maestro-builder-sdk';
|
|
10
|
+
* import { runCommand } from '@uipath/flow-local-nodes';
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* The package registers its family as a side effect of being imported
|
|
14
|
+
* (`defineFlowNodeFamily`), so `compile`, `check` and `merge` need no
|
|
15
|
+
* configuration: `loadBuiltFlow` imports the author's module, the module imports
|
|
16
|
+
* the package, the package registers, and `build()` runs against a registry that
|
|
17
|
+
* knows the new kinds. Every type of a package family declares
|
|
18
|
+
* `definition: 'package'`, and the registry refuses a package type anywhere else —
|
|
19
|
+
* that is the rule that keeps the public SDK free of node types the UiPath
|
|
20
|
+
* platform cannot run.
|
|
21
|
+
*
|
|
22
|
+
* Everything else in `core/` is private. A package needs only what is exported
|
|
23
|
+
* here; see docs/EXTENSIBLE_NODE_TYPES_DESIGN.md.
|
|
24
|
+
*/
|
|
25
|
+
import type { NodeFamily, DiagnosticCatalog, PackageOrigin } from './core/nodes/family.js';
|
|
26
|
+
import type { FlowAction } from './core/node-classes.js';
|
|
27
|
+
import type { FlowContexts, FlowNodeFamily } from './flow-node-type.js';
|
|
28
|
+
export type { NodeType, FactoryCall, OutputContract, ExprSite, DefinitionSource, ArtifactContexts, FieldsRead, OutputRead, OutputPathRead, } from './core/nodes/node-type.js';
|
|
29
|
+
export type { VersionedNodeType } from './core/nodes/versioned.js';
|
|
30
|
+
export type { NodeFamily, Placement, DiagnosticCatalog, PackageOrigin } from './core/nodes/family.js';
|
|
31
|
+
export type { DefinitionReference } from './core/node-classes.js';
|
|
32
|
+
export type { FlowContexts, FlowNodeType, FlowVersionedNodeType, FlowNodeFamily, FlowSerializeContext, FlowDeserializeContext, FlowPrintContext, FlowCheckContext, FlowFamilyCheckContext, FlowNodeContribution, FlowRawNodeAdvice, } from './flow-node-type.js';
|
|
33
|
+
export type { FlowAction } from './core/node-classes.js';
|
|
34
|
+
export { defineNodeDefinition, DEFAULT_PACKAGE_DEFINITION_VERSION, } from './core/nodes/package-definition.js';
|
|
35
|
+
export type { PackageNodeShape, PackageInputField, PackageOutputField, PackageNodeDefinition } from './core/nodes/package-definition.js';
|
|
36
|
+
/**
|
|
37
|
+
* Wrap a package factory's spec literal as the `FlowAction` instance `.step()`
|
|
38
|
+
* accepts, keeping the literal's own type. A built-in factory extends the SDK's
|
|
39
|
+
* `FlowActionSpec` union by augmenting an internal interface; a package has no
|
|
40
|
+
* such access, and does not need it: `.step(name, spec)` takes any `FlowAction`,
|
|
41
|
+
* and this is the one way to make one. The returned value IS the spec (the
|
|
42
|
+
* serializer reads `kind` and `inputs` off it) and is `instanceof FlowAction`.
|
|
43
|
+
*
|
|
44
|
+
* @param spec - The spec literal the factory built, `{ kind, inputs }`.
|
|
45
|
+
* @returns The same spec, typed as itself and as a `FlowAction`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function packageActionSpec<S extends {
|
|
48
|
+
kind: string;
|
|
49
|
+
}>(spec: S): S & FlowAction;
|
|
50
|
+
/**
|
|
51
|
+
* The version of this extension contract. A package pins the version it was
|
|
52
|
+
* written against in `defineFlowNodeFamily({ contract })`; a mismatch is refused
|
|
53
|
+
* at import time, before any flow is built, with both numbers in the sentence.
|
|
54
|
+
* It moves only when a hook's signature changes.
|
|
55
|
+
*/
|
|
56
|
+
export declare const EXTENSION_CONTRACT_VERSION = 1;
|
|
57
|
+
/** What a package passes to `defineFlowNodeFamily`. */
|
|
58
|
+
export interface FlowNodeFamilyDefinition {
|
|
59
|
+
/** Stable id, e.g. `'local'`. Unique across the registry. */
|
|
60
|
+
readonly id: string;
|
|
61
|
+
readonly title: string;
|
|
62
|
+
/** The family's node types: single-version (`FlowNodeType`) or versioned (`FlowVersionedNodeType`), as a built-in family lists them. */
|
|
63
|
+
readonly types: FlowNodeFamily['types'];
|
|
64
|
+
readonly diagnostics: DiagnosticCatalog;
|
|
65
|
+
/** Rules over every placement of this family's types, after the per-node checks. */
|
|
66
|
+
readonly check?: NodeFamily<FlowContexts>['check'];
|
|
67
|
+
/** The package the family comes from; written as provenance into every definition. */
|
|
68
|
+
readonly package: PackageOrigin;
|
|
69
|
+
/** The `EXTENSION_CONTRACT_VERSION` the package was written against. */
|
|
70
|
+
readonly contract: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Define and register a node-type package's family with the Flow registry.
|
|
74
|
+
*
|
|
75
|
+
* Returns the family. Refuses, by throwing at import time, a contract version
|
|
76
|
+
* other than this SDK's and a family with no package name. The registry refuses,
|
|
77
|
+
* when it adopts the family (at compile time, before any node is serialized), a
|
|
78
|
+
* kind, type name, prefix or diagnostic code a built-in family or an earlier
|
|
79
|
+
* package owns, and a type that does not declare `definition: 'package'`.
|
|
80
|
+
*/
|
|
81
|
+
export declare function defineFlowNodeFamily(definition: FlowNodeFamilyDefinition): FlowNodeFamily;
|