@uipath/maestro-builder-sdk 6.4.0 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/registry.js +68 -12
- package/dist/registry/archive/entry-policy.d.ts +36 -0
- package/dist/registry/archive/entry-policy.js +60 -0
- package/dist/registry/archive/extract.d.ts +31 -0
- package/dist/registry/archive/extract.js +92 -0
- package/dist/registry/archive/zip-reader.d.ts +38 -0
- package/dist/registry/archive/zip-reader.js +256 -0
- package/dist/registry/codecs/pyjson.d.ts +62 -0
- package/dist/registry/codecs/pyjson.js +208 -0
- package/dist/registry/compat/compare.d.ts +18 -0
- package/dist/registry/compat/compare.js +32 -0
- package/dist/registry/compat/paths.d.ts +4 -0
- package/dist/registry/compat/paths.js +17 -0
- package/dist/registry/compat/pystr.d.ts +50 -0
- package/dist/registry/compat/pystr.js +221 -0
- package/dist/registry/concurrency.d.ts +10 -0
- package/dist/registry/concurrency.js +34 -0
- package/dist/registry/curated-overlay.d.ts +36 -4
- package/dist/registry/curated-overlay.js +87 -14
- package/dist/registry/errors.d.ts +26 -0
- package/dist/registry/errors.js +38 -0
- package/dist/registry/extractor.d.ts +9 -10
- package/dist/registry/extractor.js +5 -15
- package/dist/registry/files.d.ts +26 -0
- package/dist/registry/files.js +160 -0
- package/dist/registry/generators/descriptors.d.ts +81 -0
- package/dist/registry/generators/descriptors.js +405 -0
- package/dist/registry/generators/fields.d.ts +46 -0
- package/dist/registry/generators/fields.js +111 -0
- package/dist/registry/generators/markdown/common.d.ts +21 -0
- package/dist/registry/generators/markdown/common.js +55 -0
- package/dist/registry/generators/markdown/index.d.ts +33 -0
- package/dist/registry/generators/markdown/index.js +37 -0
- package/dist/registry/generators/markdown/page.d.ts +8 -0
- package/dist/registry/generators/markdown/page.js +52 -0
- package/dist/registry/generators/markdown/pyvalue.d.ts +34 -0
- package/dist/registry/generators/markdown/pyvalue.js +106 -0
- package/dist/registry/generators/markdown/sections/choosing-object.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/choosing-object.js +52 -0
- package/dist/registry/generators/markdown/sections/example.d.ts +22 -0
- package/dist/registry/generators/markdown/sections/example.js +295 -0
- package/dist/registry/generators/markdown/sections/filtering.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/filtering.js +76 -0
- package/dist/registry/generators/markdown/sections/header.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/header.js +37 -0
- package/dist/registry/generators/markdown/sections/inputs.d.ts +30 -0
- package/dist/registry/generators/markdown/sections/inputs.js +151 -0
- package/dist/registry/generators/markdown/sections/lookups.d.ts +12 -0
- package/dist/registry/generators/markdown/sections/lookups.js +150 -0
- package/dist/registry/generators/markdown/sections/outputs.d.ts +3 -0
- package/dist/registry/generators/markdown/sections/outputs.js +9 -0
- package/dist/registry/generators/markdown/sections/resolving-fields.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/resolving-fields.js +139 -0
- package/dist/registry/generators/markdown/sections/schema-dynamic.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/schema-dynamic.js +86 -0
- package/dist/registry/generators/materialize.d.ts +30 -0
- package/dist/registry/generators/materialize.js +132 -0
- package/dist/registry/generators/md-symbols.d.ts +22 -0
- package/dist/registry/generators/md-symbols.js +58 -0
- package/dist/registry/generators/triggers.d.ts +15 -0
- package/dist/registry/generators/triggers.js +85 -0
- package/dist/registry/generators/type-registry.d.ts +50 -0
- package/dist/registry/generators/type-registry.js +154 -0
- package/dist/registry/generators/type-tree.d.ts +65 -0
- package/dist/registry/generators/type-tree.js +179 -0
- package/dist/registry/library-build/build.d.ts +57 -0
- package/dist/registry/library-build/build.js +286 -0
- package/dist/registry/library-build/canonical.d.ts +119 -0
- package/dist/registry/library-build/canonical.js +512 -0
- package/dist/registry/library-build/cli.d.ts +45 -0
- package/dist/registry/library-build/cli.js +182 -0
- package/dist/registry/library-build/context.d.ts +107 -0
- package/dist/registry/library-build/context.js +267 -0
- package/dist/registry/library-build/enrich.d.ts +36 -0
- package/dist/registry/library-build/enrich.js +158 -0
- package/dist/registry/library-build/events.d.ts +108 -0
- package/dist/registry/library-build/events.js +476 -0
- package/dist/registry/library-build/generic-expand.d.ts +77 -0
- package/dist/registry/library-build/generic-expand.js +363 -0
- package/dist/registry/library-build/plan.d.ts +71 -0
- package/dist/registry/library-build/plan.js +270 -0
- package/dist/registry/library-build/py.d.ts +45 -0
- package/dist/registry/library-build/py.js +167 -0
- package/dist/registry/library-build/schema-action.d.ts +38 -0
- package/dist/registry/library-build/schema-action.js +107 -0
- package/dist/registry/library-build/write.d.ts +6 -0
- package/dist/registry/library-build/write.js +26 -0
- package/dist/registry/library-reader.d.ts +20 -0
- package/dist/registry/library-reader.js +50 -0
- package/dist/registry/lookup-specs.d.ts +55 -0
- package/dist/registry/lookup-specs.js +140 -0
- package/dist/registry/materialize-cli.js +28 -14
- package/dist/registry/materializer.d.ts +25 -3
- package/dist/registry/materializer.js +45 -29
- package/dist/registry/model/library.d.ts +254 -0
- package/dist/registry/model/library.js +23 -0
- package/dist/registry/naming.d.ts +54 -0
- package/dist/registry/naming.js +142 -0
- package/dist/registry/orphans.d.ts +4 -0
- package/dist/registry/orphans.js +86 -0
- package/dist/registry/prepare/actions.d.ts +26 -0
- package/dist/registry/prepare/actions.js +94 -0
- package/dist/registry/prepare/connections.d.ts +181 -0
- package/dist/registry/prepare/connections.js +399 -0
- package/dist/registry/prepare/event.d.ts +96 -0
- package/dist/registry/prepare/event.js +268 -0
- package/dist/registry/prepare/handoff.d.ts +21 -0
- package/dist/registry/prepare/handoff.js +91 -0
- package/dist/registry/prepare/ledger.d.ts +104 -0
- package/dist/registry/prepare/ledger.js +216 -0
- package/dist/registry/prepare/messages.d.ts +190 -0
- package/dist/registry/prepare/messages.js +472 -0
- package/dist/registry/prepare/objects.d.ts +130 -0
- package/dist/registry/prepare/objects.js +318 -0
- package/dist/registry/prepare/overlay.d.ts +149 -0
- package/dist/registry/prepare/overlay.js +487 -0
- package/dist/registry/prepare/parents.d.ts +41 -0
- package/dist/registry/prepare/parents.js +122 -0
- package/dist/registry/prepare/pipeline.d.ts +91 -0
- package/dist/registry/prepare/pipeline.js +376 -0
- package/dist/registry/prepare/regenerate.d.ts +9 -0
- package/dist/registry/prepare/regenerate.js +50 -0
- package/dist/registry/prepare/resolve.d.ts +199 -0
- package/dist/registry/prepare/resolve.js +470 -0
- package/dist/registry/prepare/retry.d.ts +83 -0
- package/dist/registry/prepare/retry.js +109 -0
- package/dist/registry/prepare/source-import.d.ts +54 -0
- package/dist/registry/prepare/source-import.js +192 -0
- package/dist/registry/prepare/support.d.ts +41 -0
- package/dist/registry/prepare/support.js +178 -0
- package/dist/registry/prepare-cli.d.ts +16 -5
- package/dist/registry/prepare-cli.js +60 -53
- package/dist/registry/pull-cli.js +8 -7
- package/dist/registry/reporter.d.ts +26 -0
- package/dist/registry/reporter.js +41 -0
- package/dist/registry/uip/client.d.ts +99 -0
- package/dist/registry/uip/client.js +226 -0
- package/dist/registry/uip/commands.d.ts +40 -0
- package/dist/registry/uip/commands.js +80 -0
- package/dist/registry/uip/envelope.d.ts +37 -0
- package/dist/registry/uip/envelope.js +84 -0
- package/dist/registry/uip/response-cache.d.ts +28 -0
- package/dist/registry/uip/response-cache.js +57 -0
- package/lib/README.md +10 -8
- package/package.json +3 -3
- package/dist/generators/_connections.py +0 -331
- package/dist/generators/_events.py +0 -355
- package/dist/generators/_lookups.py +0 -114
- package/dist/generators/_naming.py +0 -185
- package/dist/generators/_objects.py +0 -299
- package/dist/generators/_resolve.py +0 -319
- package/dist/generators/convert_library_to_md.py +0 -1441
- package/dist/generators/extract_library.py +0 -125
- package/dist/generators/generate_connectors_ts.py +0 -936
- package/dist/generators/generate_library_json.py +0 -2527
- package/dist/generators/generate_triggers_ts.py +0 -110
- package/dist/generators/materialize_registry.py +0 -155
- package/dist/generators/prepare_connector.py +0 -1860
- package/dist/registry/python.d.ts +0 -26
- package/dist/registry/python.js +0 -46
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { LibraryField } from "../model/library.js";
|
|
2
|
+
/** Registry field `type` → TypeScript type; anything else is `unknown`. */
|
|
3
|
+
export declare const TYPE_MAP: Readonly<Record<string, string>>;
|
|
4
|
+
/**
|
|
5
|
+
* Python truthiness for a JSON-shaped value. JavaScript agrees on `null`,
|
|
6
|
+
* `false`, `0` and `""` but calls an empty array or object truthy; the Python
|
|
7
|
+
* generator tested `field.get("required")`, `runtime.get("requiresConnection")`
|
|
8
|
+
* and the like with a bare `if`, so the emitted bytes depend on this rule.
|
|
9
|
+
*/
|
|
10
|
+
export declare function pyTruthy(value: unknown): boolean;
|
|
11
|
+
/** `TYPE_MAP.get(str(field["type"] or "").lower(), "unknown")`. */
|
|
12
|
+
export declare function tsType(field: {
|
|
13
|
+
type?: unknown;
|
|
14
|
+
}): string;
|
|
15
|
+
/**
|
|
16
|
+
* A field as the tree holds it: the library's declaration plus the `__lookup`
|
|
17
|
+
* flag `descriptors.ts` sets on a field whose type must also accept a token.
|
|
18
|
+
*/
|
|
19
|
+
export type TreeField = LibraryField & {
|
|
20
|
+
__lookup?: boolean;
|
|
21
|
+
};
|
|
22
|
+
/** `{ rootSegment → node }`: the roots of one operation's inputs or outputs. */
|
|
23
|
+
export type TypeTree = Map<string, TypeNode>;
|
|
24
|
+
/** One node in a tree reconstructed from flattened connector field paths. */
|
|
25
|
+
export declare class TypeNode {
|
|
26
|
+
/** Every cardinality this segment was declared with; `true` is `[*]`. */
|
|
27
|
+
readonly arrayModes: Set<boolean>;
|
|
28
|
+
/** Every TypeScript type a declaration of this segment mapped to. */
|
|
29
|
+
readonly declaredTypes: Set<string>;
|
|
30
|
+
/** The first field declared on this exact segment; its description is rendered. */
|
|
31
|
+
declared: TreeField | undefined;
|
|
32
|
+
opaqueObject: boolean;
|
|
33
|
+
opaqueArray: boolean;
|
|
34
|
+
required: boolean;
|
|
35
|
+
readonly children: TypeTree;
|
|
36
|
+
constructor(isArray?: boolean);
|
|
37
|
+
addDeclaration(field: TreeField): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Split `items[*].owner.id` into `[["items", true], ["owner", false], ["id", false]]`.
|
|
41
|
+
* A malformed name (an empty segment, as in `a..b` or `[*]`) is kept whole as
|
|
42
|
+
* one quoted property.
|
|
43
|
+
*/
|
|
44
|
+
export declare function splitFieldPath(name: string): Array<[segment: string, isArray: boolean]>;
|
|
45
|
+
/** The roots of the tree for one flat field list; a field without a name is skipped. */
|
|
46
|
+
export declare function buildTypeTree(fields: readonly TreeField[]): TypeTree;
|
|
47
|
+
/**
|
|
48
|
+
* Merge repeated occurrences into one reusable connector-level type. A child
|
|
49
|
+
* only the source has is adopted by reference, so the two trees share it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function mergeTypeNodes(target: TypeNode, source: TypeNode): void;
|
|
52
|
+
/**
|
|
53
|
+
* The interface name for a nested segment: singularised when the segment is an
|
|
54
|
+
* array, PascalCased, prefixed with `Nested` when it would start with a digit.
|
|
55
|
+
*/
|
|
56
|
+
export declare function nestedTypeName(fieldName: string, node: TypeNode, suffix?: string): string;
|
|
57
|
+
/** True when the node or any descendant is required. */
|
|
58
|
+
export declare function subtreeRequired(node: TypeNode): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Stable structural identity — the Python's nested tuple, as one canonical
|
|
61
|
+
* string. Input types reuse only exact shapes, so two nodes share an interface
|
|
62
|
+
* iff their fingerprints are equal. Children are ordered by name, as
|
|
63
|
+
* `sorted(node.children.items())` orders them.
|
|
64
|
+
*/
|
|
65
|
+
export declare function typeFingerprint(node: TypeNode): string;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type tree a descriptor's interfaces are rendered from.
|
|
3
|
+
*
|
|
4
|
+
* A connector declares its fields flat — `items[*].owner.id` — and the
|
|
5
|
+
* descriptor exposes them as natural nested objects and arrays. This module
|
|
6
|
+
* rebuilds that nesting: one {@link TypeNode} per path segment, carrying every
|
|
7
|
+
* declaration that landed on it (a segment can be declared both as a scalar and
|
|
8
|
+
* as an array, or by several fields with different types) so the renderer can
|
|
9
|
+
* union them. Ported from the `TypeNode` half of `generate_connectors_ts.py`;
|
|
10
|
+
* `type-registry.ts` names and de-duplicates the nodes and `descriptors.ts`
|
|
11
|
+
* renders them.
|
|
12
|
+
*/
|
|
13
|
+
import { sortedStrings } from "../compat/compare.js";
|
|
14
|
+
import { pyStr } from "../compat/pystr.js";
|
|
15
|
+
import { pascal, singular } from "../naming.js";
|
|
16
|
+
/** Registry field `type` → TypeScript type; anything else is `unknown`. */
|
|
17
|
+
export const TYPE_MAP = {
|
|
18
|
+
string: "string",
|
|
19
|
+
number: "number",
|
|
20
|
+
integer: "number",
|
|
21
|
+
float: "number",
|
|
22
|
+
double: "number",
|
|
23
|
+
long: "number",
|
|
24
|
+
boolean: "boolean",
|
|
25
|
+
bool: "boolean",
|
|
26
|
+
object: "Record<string, unknown>",
|
|
27
|
+
array: "unknown[]",
|
|
28
|
+
json: "unknown",
|
|
29
|
+
file: "string",
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Python truthiness for a JSON-shaped value. JavaScript agrees on `null`,
|
|
33
|
+
* `false`, `0` and `""` but calls an empty array or object truthy; the Python
|
|
34
|
+
* generator tested `field.get("required")`, `runtime.get("requiresConnection")`
|
|
35
|
+
* and the like with a bare `if`, so the emitted bytes depend on this rule.
|
|
36
|
+
*/
|
|
37
|
+
export function pyTruthy(value) {
|
|
38
|
+
if (value === null || value === undefined || value === false || value === "")
|
|
39
|
+
return false;
|
|
40
|
+
if (typeof value === "number")
|
|
41
|
+
return value !== 0; // NaN is truthy in Python
|
|
42
|
+
if (typeof value === "bigint")
|
|
43
|
+
return value !== 0n;
|
|
44
|
+
if (Array.isArray(value))
|
|
45
|
+
return value.length > 0;
|
|
46
|
+
if (value instanceof Map || value instanceof Set)
|
|
47
|
+
return value.size > 0;
|
|
48
|
+
if (typeof value === "object")
|
|
49
|
+
return Object.keys(value).length > 0;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
/** `str(value or "")`: the coercion the generator applied before comparing a type name. */
|
|
53
|
+
function strOrEmpty(value) {
|
|
54
|
+
return pyTruthy(value) ? pyStr(value) : "";
|
|
55
|
+
}
|
|
56
|
+
/** `TYPE_MAP.get(str(field["type"] or "").lower(), "unknown")`. */
|
|
57
|
+
export function tsType(field) {
|
|
58
|
+
return TYPE_MAP[strOrEmpty(field.type).toLowerCase()] ?? "unknown";
|
|
59
|
+
}
|
|
60
|
+
/** One node in a tree reconstructed from flattened connector field paths. */
|
|
61
|
+
export class TypeNode {
|
|
62
|
+
/** Every cardinality this segment was declared with; `true` is `[*]`. */
|
|
63
|
+
arrayModes = new Set();
|
|
64
|
+
/** Every TypeScript type a declaration of this segment mapped to. */
|
|
65
|
+
declaredTypes = new Set();
|
|
66
|
+
/** The first field declared on this exact segment; its description is rendered. */
|
|
67
|
+
declared = undefined;
|
|
68
|
+
opaqueObject = false;
|
|
69
|
+
opaqueArray = false;
|
|
70
|
+
required = false;
|
|
71
|
+
children = new Map();
|
|
72
|
+
constructor(isArray = false) {
|
|
73
|
+
this.arrayModes.add(isArray);
|
|
74
|
+
}
|
|
75
|
+
addDeclaration(field) {
|
|
76
|
+
if (this.declared === undefined)
|
|
77
|
+
this.declared = field;
|
|
78
|
+
const declaredType = strOrEmpty(field.type).toLowerCase();
|
|
79
|
+
this.declaredTypes.add(tsType(field));
|
|
80
|
+
this.opaqueObject = this.opaqueObject || declaredType === "object";
|
|
81
|
+
this.opaqueArray = this.opaqueArray || declaredType === "array";
|
|
82
|
+
this.required = this.required || pyTruthy(field.required);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Split `items[*].owner.id` into `[["items", true], ["owner", false], ["id", false]]`.
|
|
87
|
+
* A malformed name (an empty segment, as in `a..b` or `[*]`) is kept whole as
|
|
88
|
+
* one quoted property.
|
|
89
|
+
*/
|
|
90
|
+
export function splitFieldPath(name) {
|
|
91
|
+
const parts = [];
|
|
92
|
+
for (const raw of name.split(".")) {
|
|
93
|
+
const isArray = raw.endsWith("[*]");
|
|
94
|
+
const segment = isArray ? raw.slice(0, -3) : raw;
|
|
95
|
+
if (!segment)
|
|
96
|
+
return [[name, false]];
|
|
97
|
+
parts.push([segment, isArray]);
|
|
98
|
+
}
|
|
99
|
+
return parts;
|
|
100
|
+
}
|
|
101
|
+
/** The roots of the tree for one flat field list; a field without a name is skipped. */
|
|
102
|
+
export function buildTypeTree(fields) {
|
|
103
|
+
const roots = new Map();
|
|
104
|
+
for (const field of fields) {
|
|
105
|
+
const name = field.name;
|
|
106
|
+
if (!pyTruthy(name))
|
|
107
|
+
continue;
|
|
108
|
+
let cursor = roots;
|
|
109
|
+
let node;
|
|
110
|
+
for (const [segment, isArray] of splitFieldPath(pyStr(name))) {
|
|
111
|
+
let next = cursor.get(segment);
|
|
112
|
+
if (next === undefined) {
|
|
113
|
+
next = new TypeNode(isArray);
|
|
114
|
+
cursor.set(segment, next);
|
|
115
|
+
}
|
|
116
|
+
node = next;
|
|
117
|
+
node.arrayModes.add(isArray);
|
|
118
|
+
cursor = node.children;
|
|
119
|
+
}
|
|
120
|
+
if (node !== undefined)
|
|
121
|
+
node.addDeclaration(field);
|
|
122
|
+
}
|
|
123
|
+
return roots;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Merge repeated occurrences into one reusable connector-level type. A child
|
|
127
|
+
* only the source has is adopted by reference, so the two trees share it.
|
|
128
|
+
*/
|
|
129
|
+
export function mergeTypeNodes(target, source) {
|
|
130
|
+
for (const mode of source.arrayModes)
|
|
131
|
+
target.arrayModes.add(mode);
|
|
132
|
+
for (const type of source.declaredTypes)
|
|
133
|
+
target.declaredTypes.add(type);
|
|
134
|
+
target.opaqueObject = target.opaqueObject || source.opaqueObject;
|
|
135
|
+
target.opaqueArray = target.opaqueArray || source.opaqueArray;
|
|
136
|
+
target.required = target.required || source.required;
|
|
137
|
+
if (target.declared === undefined)
|
|
138
|
+
target.declared = source.declared;
|
|
139
|
+
for (const [childName, sourceChild] of source.children) {
|
|
140
|
+
const targetChild = target.children.get(childName);
|
|
141
|
+
if (targetChild === undefined)
|
|
142
|
+
target.children.set(childName, sourceChild);
|
|
143
|
+
else
|
|
144
|
+
mergeTypeNodes(targetChild, sourceChild);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The interface name for a nested segment: singularised when the segment is an
|
|
149
|
+
* array, PascalCased, prefixed with `Nested` when it would start with a digit.
|
|
150
|
+
*/
|
|
151
|
+
export function nestedTypeName(fieldName, node, suffix = "") {
|
|
152
|
+
const source = node.arrayModes.has(true) || node.opaqueArray ? singular(fieldName) : fieldName;
|
|
153
|
+
let name = pascal(source);
|
|
154
|
+
name = /^[A-Za-z_$]/.test(name) ? name : `Nested${name}`;
|
|
155
|
+
return `${name}${suffix}`;
|
|
156
|
+
}
|
|
157
|
+
/** True when the node or any descendant is required. */
|
|
158
|
+
export function subtreeRequired(node) {
|
|
159
|
+
if (node.required)
|
|
160
|
+
return true;
|
|
161
|
+
for (const child of node.children.values())
|
|
162
|
+
if (subtreeRequired(child))
|
|
163
|
+
return true;
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Stable structural identity — the Python's nested tuple, as one canonical
|
|
168
|
+
* string. Input types reuse only exact shapes, so two nodes share an interface
|
|
169
|
+
* iff their fingerprints are equal. Children are ordered by name, as
|
|
170
|
+
* `sorted(node.children.items())` orders them.
|
|
171
|
+
*/
|
|
172
|
+
export function typeFingerprint(node) {
|
|
173
|
+
const modes = [...node.arrayModes].sort((a, b) => Number(a) - Number(b));
|
|
174
|
+
const types = sortedStrings(node.declaredTypes);
|
|
175
|
+
const children = sortedStrings(node.children.keys()).map((name) => [name, typeFingerprint(node.children.get(name))]);
|
|
176
|
+
// An internal key, never emitted: JSON is unambiguous for these shapes, so
|
|
177
|
+
// equal strings mean equal tuples.
|
|
178
|
+
return JSON.stringify([modes, types, node.opaqueObject, node.opaqueArray, node.required, children]);
|
|
179
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { LibraryEntry } from "../model/library.js";
|
|
2
|
+
import { type BuildContext, type BuildPorts } from "./context.js";
|
|
3
|
+
import { type LibraryPlan } from "./plan.js";
|
|
4
|
+
export type CatalogScope = "all" | "tier1" | "tier2";
|
|
5
|
+
export interface BuildOptions {
|
|
6
|
+
/** `--output-dir`: removed and rewritten. */
|
|
7
|
+
outputDir: string;
|
|
8
|
+
/** `--triggers-output`; defaults to `<dirname(normpath(outputDir))>/registry-triggers.json`. */
|
|
9
|
+
triggersOutput?: string;
|
|
10
|
+
/**
|
|
11
|
+
* `--connectors a,b`, already split and stripped. `undefined` is a full
|
|
12
|
+
* build (the published catalog); a list — even an empty one — is a curated
|
|
13
|
+
* subset, as `if args.connectors:` on the raw string was.
|
|
14
|
+
*/
|
|
15
|
+
connectors?: readonly string[];
|
|
16
|
+
/** `--catalog-scope` (default `all`). */
|
|
17
|
+
catalogScope?: CatalogScope;
|
|
18
|
+
skipEnrichment?: boolean;
|
|
19
|
+
skipConnectorEnrichment?: boolean;
|
|
20
|
+
skipGenericExpansion?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface BuildResult {
|
|
23
|
+
/** The node types the build materialized, after scope filtering. */
|
|
24
|
+
nodeTypes: string[];
|
|
25
|
+
/** The entries as planned, in planning order. */
|
|
26
|
+
entries: LibraryEntry[];
|
|
27
|
+
plan: LibraryPlan;
|
|
28
|
+
/** Entry files written — `write_library`'s return value. */
|
|
29
|
+
written: number;
|
|
30
|
+
triggersOutput: string;
|
|
31
|
+
triggerCount: number;
|
|
32
|
+
}
|
|
33
|
+
/** The Python's default: beside the output directory. */
|
|
34
|
+
export declare function defaultTriggersOutput(outputDir: string): string;
|
|
35
|
+
/** `{c.strip() for c in text.split(",") if c.strip()}`, in first-seen order. */
|
|
36
|
+
export declare function parseConnectorsFlag(text: string): string[];
|
|
37
|
+
/**
|
|
38
|
+
* `registry_search`: the connector node types the registry lists, from the
|
|
39
|
+
* cached raw stdout of `flow registry search --filter tags:in=connector` —
|
|
40
|
+
* fetched once when the cache has no `_search_results.json`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function registrySearch(ctx: BuildContext): Promise<string[]>;
|
|
43
|
+
/**
|
|
44
|
+
* `fetch_all`: the registry response of every node type, from the cache or
|
|
45
|
+
* fetched in parallel. `nodeType → cache name`; a node type whose fetch failed
|
|
46
|
+
* is absent (and was reported by `registryGet`).
|
|
47
|
+
*/
|
|
48
|
+
export declare function fetchAll(ctx: BuildContext, nodeTypes: readonly string[]): Promise<Map<string, string>>;
|
|
49
|
+
/**
|
|
50
|
+
* `fetch_connector_records_for_keys`: one `is connectors metadata` per key,
|
|
51
|
+
* sorted and deduplicated; `key → record`, `null` where the lookup failed.
|
|
52
|
+
*/
|
|
53
|
+
export declare function fetchConnectorRecordsForKeys(ctx: BuildContext, keys: Iterable<string>): Promise<Map<string, unknown>>;
|
|
54
|
+
/** `fetch_connector_records`: one record per unique connector key in `entries`. */
|
|
55
|
+
export declare function fetchConnectorRecords(ctx: BuildContext, entries: readonly LibraryEntry[]): Promise<Map<string, unknown>>;
|
|
56
|
+
/** The whole generator, `main()` minus argument parsing and cache cleanup. */
|
|
57
|
+
export declare function buildLibrary(options: BuildOptions, ports: BuildPorts): Promise<BuildResult>;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `main()` of `generate_library_json.py`, as a function of its options and
|
|
3
|
+
* ports: search → scope → trigger side-file → fetch → extract → enrich →
|
|
4
|
+
* connector records → generic expansion → events → curated overrides → plan →
|
|
5
|
+
* write.
|
|
6
|
+
*
|
|
7
|
+
* Every remote call goes through the {@link BuildContext} built from the ports,
|
|
8
|
+
* so a build against a `--keep-temp` cache the Python left behind makes no call
|
|
9
|
+
* at all — which is how the port is proven byte-for-byte. The two `sys.exit(
|
|
10
|
+
* "<message>")` gates are thrown as {@link Refusal}s carrying the same text.
|
|
11
|
+
*/
|
|
12
|
+
import { dirname, join, normalize } from "node:path";
|
|
13
|
+
import { sortedStrings } from "../compat/compare.js";
|
|
14
|
+
import { pyStr } from "../compat/pystr.js";
|
|
15
|
+
import { mapConcurrent } from "../concurrency.js";
|
|
16
|
+
import { Refusal, messageOf } from "../errors.js";
|
|
17
|
+
import { registrySearchConnectors } from "../uip/commands.js";
|
|
18
|
+
import { applyCuratedOverrides, extractCanonical, filterNodeTypesByTier, mergeConnectorRecord, nodeOf, scopedConnectorKey, selectPublishedConnectors, } from "./canonical.js";
|
|
19
|
+
import { SEARCH_RESULTS_NAME, createBuildContext, isConnectorGet, registryCacheName, registryGet, } from "./context.js";
|
|
20
|
+
import { enrichAll } from "./enrich.js";
|
|
21
|
+
import { resolveConnectorEvents } from "./events.js";
|
|
22
|
+
import { expandAllGenerics, sortExpandedEntries } from "./generic-expand.js";
|
|
23
|
+
import { orphanCheck, planLibrary, triggerRows } from "./plan.js";
|
|
24
|
+
import { get, item, pyIter, pyOr, pyReprStrList, truthy } from "./py.js";
|
|
25
|
+
import { writePlan, writeTriggerSideFile } from "./write.js";
|
|
26
|
+
/** The Python's default: beside the output directory. */
|
|
27
|
+
export function defaultTriggersOutput(outputDir) {
|
|
28
|
+
return join(dirname(normalize(outputDir)) || ".", "registry-triggers.json");
|
|
29
|
+
}
|
|
30
|
+
/** `{c.strip() for c in text.split(",") if c.strip()}`, in first-seen order. */
|
|
31
|
+
export function parseConnectorsFlag(text) {
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
for (const part of text.split(",")) {
|
|
34
|
+
const key = part.trim();
|
|
35
|
+
if (key)
|
|
36
|
+
seen.add(key);
|
|
37
|
+
}
|
|
38
|
+
return [...seen];
|
|
39
|
+
}
|
|
40
|
+
/** `", ".join(missing[:10])` plus ` (+N more)` past ten. */
|
|
41
|
+
function missingPreview(missing) {
|
|
42
|
+
const preview = missing.slice(0, 10).join(", ");
|
|
43
|
+
const remainder = missing.length > 10 ? ` (+${missing.length - 10} more)` : "";
|
|
44
|
+
return `${preview}${remainder}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `registry_search`: the connector node types the registry lists, from the
|
|
48
|
+
* cached raw stdout of `flow registry search --filter tags:in=connector` —
|
|
49
|
+
* fetched once when the cache has no `_search_results.json`.
|
|
50
|
+
*/
|
|
51
|
+
export async function registrySearch(ctx) {
|
|
52
|
+
if (!ctx.caches.registry.has(SEARCH_RESULTS_NAME)) {
|
|
53
|
+
ctx.reporter.info("Running registry search...");
|
|
54
|
+
const result = await ctx.client.run(registrySearchConnectors(), ctx.timeoutMs === undefined ? undefined : { timeoutMs: ctx.timeoutMs });
|
|
55
|
+
if (result.code !== 0)
|
|
56
|
+
throw new Refusal(`Registry search failed: ${result.stderr}`, "REGISTRY_SEARCH_FAILED");
|
|
57
|
+
ctx.caches.registry.write(SEARCH_RESULTS_NAME, undefined, result.stdout);
|
|
58
|
+
}
|
|
59
|
+
const data = ctx.caches.registry.read(SEARCH_RESULTS_NAME);
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const entry of pyIter(get(data, "Data", []))) {
|
|
62
|
+
const nodeType = item(entry, "NodeType");
|
|
63
|
+
if (typeof nodeType === "string" && nodeType.startsWith("uipath.connector."))
|
|
64
|
+
out.push(nodeType);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* `fetch_all`: the registry response of every node type, from the cache or
|
|
70
|
+
* fetched in parallel. `nodeType → cache name`; a node type whose fetch failed
|
|
71
|
+
* is absent (and was reported by `registryGet`).
|
|
72
|
+
*/
|
|
73
|
+
export async function fetchAll(ctx, nodeTypes) {
|
|
74
|
+
const results = new Map();
|
|
75
|
+
const toFetch = [];
|
|
76
|
+
let alreadyCached = 0;
|
|
77
|
+
for (const nt of nodeTypes) {
|
|
78
|
+
const name = registryCacheName(nt);
|
|
79
|
+
if (ctx.caches.registry.has(name)) {
|
|
80
|
+
results.set(nt, name);
|
|
81
|
+
alreadyCached += 1;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
toFetch.push(nt);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (alreadyCached)
|
|
88
|
+
ctx.reporter.info(` ${alreadyCached} connectors already cached, ${toFetch.length} to fetch`);
|
|
89
|
+
if (toFetch.length > 0) {
|
|
90
|
+
let done = 0;
|
|
91
|
+
const names = await mapConcurrent(toFetch, ctx.concurrency, async (nt) => {
|
|
92
|
+
const name = await registryGet(ctx, nt);
|
|
93
|
+
done += 1;
|
|
94
|
+
if (done % 50 === 0 || done === toFetch.length)
|
|
95
|
+
ctx.reporter.info(` Fetched ${done}/${toFetch.length}`);
|
|
96
|
+
return name;
|
|
97
|
+
});
|
|
98
|
+
toFetch.forEach((nt, i) => {
|
|
99
|
+
const name = names[i];
|
|
100
|
+
if (name)
|
|
101
|
+
results.set(nt, name);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* `fetch_connector_records_for_keys`: one `is connectors metadata` per key,
|
|
108
|
+
* sorted and deduplicated; `key → record`, `null` where the lookup failed.
|
|
109
|
+
*/
|
|
110
|
+
export async function fetchConnectorRecordsForKeys(ctx, keys) {
|
|
111
|
+
const sorted = sortedStrings(new Set([...keys].filter((key) => truthy(key))));
|
|
112
|
+
ctx.reporter.info(` Looking up ${sorted.length} connector records...`);
|
|
113
|
+
let done = 0;
|
|
114
|
+
const records = await mapConcurrent(sorted, ctx.concurrency, async (key) => {
|
|
115
|
+
let record;
|
|
116
|
+
try {
|
|
117
|
+
record = await isConnectorGet(ctx, key);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
record = undefined;
|
|
121
|
+
}
|
|
122
|
+
done += 1;
|
|
123
|
+
if (done % 50 === 0 || done === sorted.length)
|
|
124
|
+
ctx.reporter.info(` Fetched ${done}/${sorted.length} connector records`);
|
|
125
|
+
return record ?? null;
|
|
126
|
+
});
|
|
127
|
+
return new Map(sorted.map((key, i) => [key, records[i]]));
|
|
128
|
+
}
|
|
129
|
+
/** `fetch_connector_records`: one record per unique connector key in `entries`. */
|
|
130
|
+
export function fetchConnectorRecords(ctx, entries) {
|
|
131
|
+
const keys = entries.map((e) => item(item(e, "connector"), "key")).filter((key) => truthy(key));
|
|
132
|
+
return fetchConnectorRecordsForKeys(ctx, keys);
|
|
133
|
+
}
|
|
134
|
+
/** The whole generator, `main()` minus argument parsing and cache cleanup. */
|
|
135
|
+
export async function buildLibrary(options, ports) {
|
|
136
|
+
const ctx = createBuildContext(ports);
|
|
137
|
+
const info = (line) => ctx.reporter.info(line);
|
|
138
|
+
const warn = (line) => ctx.reporter.warn(line);
|
|
139
|
+
const concurrency = ctx.concurrency;
|
|
140
|
+
let nodeTypes = await registrySearch(ctx);
|
|
141
|
+
info(`Found ${nodeTypes.length} connector node types`);
|
|
142
|
+
let connectorRecords;
|
|
143
|
+
if (options.connectors !== undefined) {
|
|
144
|
+
// Curated subset: the requested connectors' action AND connector-event
|
|
145
|
+
// node types. The START form stays out — `registry get` refuses it
|
|
146
|
+
// without a --connection-id.
|
|
147
|
+
const allow = new Set(options.connectors);
|
|
148
|
+
const before = nodeTypes.length;
|
|
149
|
+
nodeTypes = nodeTypes.filter((nt) => {
|
|
150
|
+
const key = scopedConnectorKey(nt);
|
|
151
|
+
return key !== null && allow.has(key);
|
|
152
|
+
});
|
|
153
|
+
const matched = new Set([...allow].filter((key) => nodeTypes.some((nt) => `${nt}.`.includes(`.${key}.`))));
|
|
154
|
+
info(`Curated subset: kept ${nodeTypes.length}/${before} node types for ${matched.size}/${allow.size} connectors ` +
|
|
155
|
+
pyReprStrList(sortedStrings(matched)));
|
|
156
|
+
const missing = [...allow].filter((key) => !matched.has(key));
|
|
157
|
+
if (missing.length > 0) {
|
|
158
|
+
warn(` WARN: no registry nodes matched these connector keys: ${pyReprStrList(sortedStrings(missing))}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// A full build ships the PUBLISHED catalog, not everything the build
|
|
163
|
+
// tenant happens to see.
|
|
164
|
+
const before = nodeTypes.length;
|
|
165
|
+
const [kept, dropped] = selectPublishedConnectors(nodeTypes);
|
|
166
|
+
nodeTypes = kept;
|
|
167
|
+
if (dropped.length > 0) {
|
|
168
|
+
info(`Published catalog: kept ${nodeTypes.length}/${before} node types, dropped ${dropped.length} ` +
|
|
169
|
+
`tenant-local connector(s) ${pyReprStrList(dropped)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const catalogScope = options.catalogScope ?? "all";
|
|
173
|
+
if (catalogScope === "tier1" || catalogScope === "tier2") {
|
|
174
|
+
const tierNumber = catalogScope.slice(-1);
|
|
175
|
+
const scopeKeys = new Set(nodeTypes.map((nt) => scopedConnectorKey(nt)).filter((key) => Boolean(key)));
|
|
176
|
+
info(`Resolving Tier ${tierNumber} scope from metadata for ${scopeKeys.size} connector keys...`);
|
|
177
|
+
connectorRecords = await fetchConnectorRecordsForKeys(ctx, scopeKeys);
|
|
178
|
+
const missingMetadata = sortedStrings([...connectorRecords].filter(([, record]) => record === null).map(([key]) => key));
|
|
179
|
+
if (missingMetadata.length > 0) {
|
|
180
|
+
throw new Refusal(`Tier ${tierNumber} filtering requires connector metadata for every candidate connector, but lookup ` +
|
|
181
|
+
`failed for: ${missingPreview(missingMetadata)}`, "TIER_METADATA_MISSING");
|
|
182
|
+
}
|
|
183
|
+
const before = nodeTypes.length;
|
|
184
|
+
const [kept, matchingKeys] = filterNodeTypesByTier(nodeTypes, connectorRecords, catalogScope);
|
|
185
|
+
nodeTypes = kept;
|
|
186
|
+
if (matchingKeys.size === 0) {
|
|
187
|
+
throw new Refusal(`Tier ${tierNumber} filtering selected no connectors. Expected the Tier field from ` +
|
|
188
|
+
`\`uip is connectors metadata\` to contain '${tierNumber}'.`, "TIER_EMPTY");
|
|
189
|
+
}
|
|
190
|
+
info(`Tier ${tierNumber} scope: kept ${nodeTypes.length}/${before} action node types for ${matchingKeys.size} connectors`);
|
|
191
|
+
}
|
|
192
|
+
// Side-file: every trigger nodeType the registry search exposed.
|
|
193
|
+
const triggersOutput = options.triggersOutput || defaultTriggersOutput(options.outputDir);
|
|
194
|
+
info(`Writing trigger side-file → ${triggersOutput}`);
|
|
195
|
+
let triggerCount = 0;
|
|
196
|
+
if (!ctx.caches.registry.has(SEARCH_RESULTS_NAME)) {
|
|
197
|
+
warn(` WARN: search cache missing at ${SEARCH_RESULTS_NAME}; can't write triggers side-file`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const rows = triggerRows(ctx.caches.registry.read(SEARCH_RESULTS_NAME));
|
|
201
|
+
triggerCount = writeTriggerSideFile(rows, triggersOutput);
|
|
202
|
+
info(` wrote ${triggerCount} triggers to ${triggersOutput}`);
|
|
203
|
+
}
|
|
204
|
+
info(`Fetching connector details (concurrency=${concurrency})...`);
|
|
205
|
+
const cacheFiles = await fetchAll(ctx, nodeTypes);
|
|
206
|
+
info(`Successfully fetched ${cacheFiles.size} connectors`);
|
|
207
|
+
info("Extracting connector entries (Curated + Generic)...");
|
|
208
|
+
let entries = [];
|
|
209
|
+
const v1defSources = new Map();
|
|
210
|
+
const byActivity = new Map();
|
|
211
|
+
for (const nt of sortedStrings(cacheFiles.keys())) {
|
|
212
|
+
const name = cacheFiles.get(nt);
|
|
213
|
+
try {
|
|
214
|
+
const entry = extractCanonical(nodeOf(ctx.caches.registry.read(name)));
|
|
215
|
+
if (truthy(entry)) {
|
|
216
|
+
entries.push(entry);
|
|
217
|
+
v1defSources.set(item(entry, "nodeType"), name);
|
|
218
|
+
// `entry["operation"].get("activityType", "") or "(empty)"`, printed
|
|
219
|
+
// with `str()`: a `True` stays `True`, not `true`, in the count line.
|
|
220
|
+
const at = pyStr(pyOr(get(item(entry, "operation"), "activityType", ""), "(empty)"));
|
|
221
|
+
byActivity.set(at, (byActivity.get(at) ?? 0) + 1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
warn(` WARN: error processing ${nt}: ${messageOf(e)}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
info(`Found ${entries.length} entries total: ` +
|
|
229
|
+
sortedStrings(byActivity.keys())
|
|
230
|
+
.map((k) => `${k}=${byActivity.get(k)}`)
|
|
231
|
+
.join(", "));
|
|
232
|
+
if (!options.skipEnrichment) {
|
|
233
|
+
info(`Enriching via \`is resources describe\` (concurrency=${concurrency})...`);
|
|
234
|
+
entries = await enrichAll(ctx, entries);
|
|
235
|
+
}
|
|
236
|
+
if (!options.skipConnectorEnrichment) {
|
|
237
|
+
info(`Enriching via \`is connectors metadata\` (concurrency=${concurrency})...`);
|
|
238
|
+
if (connectorRecords === undefined)
|
|
239
|
+
connectorRecords = await fetchConnectorRecords(ctx, entries);
|
|
240
|
+
const missingMetadata = sortedStrings([...connectorRecords].filter(([, record]) => record === null).map(([key]) => key));
|
|
241
|
+
if (missingMetadata.length > 0) {
|
|
242
|
+
throw new Refusal("Connector metadata is required to classify schema discovery, but `uip is connectors metadata` " +
|
|
243
|
+
`failed for: ${missingPreview(missingMetadata)}. Upgrade the UiPath CLI or rerun after checking authentication.`, "CONNECTOR_METADATA_MISSING");
|
|
244
|
+
}
|
|
245
|
+
let named = 0;
|
|
246
|
+
for (const e of entries) {
|
|
247
|
+
const key = item(item(e, "connector"), "key");
|
|
248
|
+
mergeConnectorRecord(e, connectorRecords.get(key) ?? null);
|
|
249
|
+
if (truthy(get(item(e, "connector"), "name")))
|
|
250
|
+
named += 1;
|
|
251
|
+
}
|
|
252
|
+
const schemaDynamic = entries.filter((e) => truthy(get(item(e, "runtime"), "requiresConnectionForSchema"))).length;
|
|
253
|
+
info(` Named ${named}/${entries.length} entries from connector records`);
|
|
254
|
+
info(` schema-dynamic from connector capabilities: ${schemaDynamic}/${entries.length}`);
|
|
255
|
+
}
|
|
256
|
+
if (!options.skipGenericExpansion) {
|
|
257
|
+
info(`Expanding Generic CRUD by (object, operation) (concurrency=${concurrency})...`);
|
|
258
|
+
const [kept, expanded] = await expandAllGenerics(ctx, entries);
|
|
259
|
+
// DELIBERATE FIX: the Python appended expansions in thread-completion
|
|
260
|
+
// order; sorted, a build is a function of its inputs (see
|
|
261
|
+
// `sortExpandedEntries`).
|
|
262
|
+
entries = [...kept, ...sortExpandedEntries(expanded)];
|
|
263
|
+
}
|
|
264
|
+
info(`Resolving connector events (concurrency=${concurrency})...`);
|
|
265
|
+
const [described, expandedEvents] = await resolveConnectorEvents(ctx, entries);
|
|
266
|
+
entries = [...described, ...sortExpandedEntries(expandedEvents)];
|
|
267
|
+
// Curated op remaps — applied last so they override anything enrichment or
|
|
268
|
+
// expansion produced.
|
|
269
|
+
const overridden = applyCuratedOverrides(entries);
|
|
270
|
+
if (overridden)
|
|
271
|
+
info(` Applied ${overridden} curated op override(s)`);
|
|
272
|
+
info("Writing canonical library...");
|
|
273
|
+
const loadNode = (nodeType) => {
|
|
274
|
+
const name = v1defSources.get(nodeType);
|
|
275
|
+
return name ? nodeOf(ctx.caches.registry.read(name)) : undefined;
|
|
276
|
+
};
|
|
277
|
+
const plan = planLibrary(entries, loadNode);
|
|
278
|
+
for (const line of plan.warnings)
|
|
279
|
+
warn(line);
|
|
280
|
+
const orphans = orphanCheck(plan.files, plan.index);
|
|
281
|
+
if (orphans.length > 0)
|
|
282
|
+
warn(` WARN: ${orphans.length} entry file(s) have no index row: ${orphans.slice(0, 5).join(", ")}`);
|
|
283
|
+
const written = writePlan(plan, options.outputDir);
|
|
284
|
+
info(`Wrote ${written} library entries + index.json`);
|
|
285
|
+
return { nodeTypes, entries, plan, written, triggersOutput, triggerCount };
|
|
286
|
+
}
|