@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,91 @@
|
|
|
1
|
+
import { type BuildCaches, type BuildContext } from "../library-build/context.js";
|
|
2
|
+
import type { Reporter } from "../reporter.js";
|
|
3
|
+
import type { UipClient } from "../uip/client.js";
|
|
4
|
+
import { type CatalogueRow } from "./objects.js";
|
|
5
|
+
/** The command line, as `argparse` parsed it. Defaults are the Python's. */
|
|
6
|
+
export interface PrepareOptions {
|
|
7
|
+
connectorKey: string;
|
|
8
|
+
action: string;
|
|
9
|
+
/** Omit to discover the connection from `uip is connections list`. */
|
|
10
|
+
connectionId?: string;
|
|
11
|
+
/** `--connection <name>`: pick the connection by name when several match. */
|
|
12
|
+
connection?: string;
|
|
13
|
+
/** `--bindings` (default `./bindings.json`). */
|
|
14
|
+
bindings?: string;
|
|
15
|
+
/** `--bind-connection` (default: the connector's last dash-segment). */
|
|
16
|
+
bindConnection?: string;
|
|
17
|
+
/** `--bind-folder` (default `shared`). */
|
|
18
|
+
bindFolder?: string;
|
|
19
|
+
noBindings?: boolean;
|
|
20
|
+
/** `--object`; mutually exclusive with `allObjects`. */
|
|
21
|
+
objectName?: string;
|
|
22
|
+
allObjects?: boolean;
|
|
23
|
+
/** `-f NAME=VALUE`, repeatable, in the order given. */
|
|
24
|
+
fields?: readonly string[];
|
|
25
|
+
/** `--out` (default `./connectors-local`). */
|
|
26
|
+
out?: string;
|
|
27
|
+
/** `--source`: the `.flow.ts` whose import is repointed. */
|
|
28
|
+
source?: string;
|
|
29
|
+
noSourceRewrite?: boolean;
|
|
30
|
+
/** `--version`: pin an op version. */
|
|
31
|
+
version?: string;
|
|
32
|
+
/** `--import`: the module specifier the emitted `.ts` imports from (default {@link PACKAGE_NAME}). */
|
|
33
|
+
importSpecifier?: string;
|
|
34
|
+
/** `--resolve FIELD:BY=VALUE`, repeatable. */
|
|
35
|
+
resolve?: readonly string[];
|
|
36
|
+
refreshLookups?: boolean;
|
|
37
|
+
jsonOnly?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface PreparePorts {
|
|
40
|
+
client: UipClient;
|
|
41
|
+
reporter: Reporter;
|
|
42
|
+
/** Response caches for this run; in-memory ones by default (the Python used a temp dir). */
|
|
43
|
+
caches?: BuildCaches;
|
|
44
|
+
/** The project root the Python read as `Path.cwd()`. Defaults to the process's. */
|
|
45
|
+
cwd?: string;
|
|
46
|
+
timeoutMs?: number;
|
|
47
|
+
/** How the pasteable `registry prepare` is spelled in the notes. */
|
|
48
|
+
prepareCommand?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface PrepareReport {
|
|
51
|
+
kind: "action" | "event";
|
|
52
|
+
/** The node type the overlay entry is stored under (the WAIT form for an event). */
|
|
53
|
+
nodeType: string;
|
|
54
|
+
connectionId: string;
|
|
55
|
+
/** The symbolic names written to `bindings.json`, when bindings were touched. */
|
|
56
|
+
bound?: {
|
|
57
|
+
connection: string;
|
|
58
|
+
folder: string;
|
|
59
|
+
};
|
|
60
|
+
/** Action path only. */
|
|
61
|
+
variants?: number;
|
|
62
|
+
inputFields?: number;
|
|
63
|
+
kept?: [kept: number, discarded: number];
|
|
64
|
+
objectName?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* `_match_catalog_object`: the catalog row `--object` names, or `undefined`
|
|
68
|
+
* when the catalog itself is unavailable — that failure belongs to expansion,
|
|
69
|
+
* whose message already covers it. Every refusal past the name rungs is
|
|
70
|
+
* retried once against a refreshed catalog before it is believed: the
|
|
71
|
+
* `uip is resources list` cache never invalidates itself (measured: 18
|
|
72
|
+
* objects stale vs 864 fresh on uipath-salesforce-sfdc). Ambiguity WITHIN a
|
|
73
|
+
* name rung is not retriable — the refusal's call, not this loop's.
|
|
74
|
+
*/
|
|
75
|
+
export declare function matchCatalogObject(ctx: BuildContext, connectorKey: string, wanted: string, verb: string | undefined): Promise<CatalogueRow | undefined>;
|
|
76
|
+
/**
|
|
77
|
+
* The whole command. Throws a {@link Refusal} for every path the Python
|
|
78
|
+
* `sys.exit`ed on; the report is what a caller wanting more than the printed
|
|
79
|
+
* lines can read.
|
|
80
|
+
*
|
|
81
|
+
* When a `--resolve` reads a collection to the end on the connection this
|
|
82
|
+
* command PICKED and finds nothing, the other enabled connections for the
|
|
83
|
+
* connector are probed, and the run starts over pinned to the one that
|
|
84
|
+
* resolves everything (flow-builder-sdk#744). The Python re-exec'd itself for
|
|
85
|
+
* that; here the bindings snapshot is restored and `prepareConnector` is
|
|
86
|
+
* re-entered with `--connection-id <winner> --refresh-lookups`. The refresh is
|
|
87
|
+
* NOT optional: the ledger keys a resolution by `[nodeType, field, by, value]`
|
|
88
|
+
* and not by connection, so an id recorded against the connection just
|
|
89
|
+
* abandoned would come back as "already resolved" on the re-run.
|
|
90
|
+
*/
|
|
91
|
+
export declare function prepareConnector(options: PrepareOptions, ports: PreparePorts): Promise<PrepareReport>;
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `main()` of `prepare_connector.py`, as a function of its options and ports.
|
|
3
|
+
*
|
|
4
|
+
* Fetch ONE connector op's FULL, connection-resolved schema into the
|
|
5
|
+
* `./connectors-local/` overlay plus a typed `.ts`. The baked library is
|
|
6
|
+
* tenant-agnostic, so schema-dynamic ops ship only their connection-independent
|
|
7
|
+
* fields; this closes that at author time with a live connection, writing the
|
|
8
|
+
* same artifacts the bulk generator produces into an overlay the compile CLIs
|
|
9
|
+
* auto-detect. Calls ACCUMULATE — each prepared op merges into `--out`.
|
|
10
|
+
*
|
|
11
|
+
* The stages, in the Python's order: the connection (discovered or checked)
|
|
12
|
+
* and `bindings.json`; the registry definition, or the event twin; the schema
|
|
13
|
+
* (Generic expansion or a describe); the overlay merge, guarded by the
|
|
14
|
+
* keep-prepared-schema rule; the object decision and the lookup resolutions
|
|
15
|
+
* in the ledger; the descriptors; then the summary and every note, and finally
|
|
16
|
+
* the repointed import and the handoff.
|
|
17
|
+
*
|
|
18
|
+
* Every `uip` call goes through the {@link BuildContext} built from the ports,
|
|
19
|
+
* so a test drives the whole pipeline from a fake client; every `sys.exit` is a
|
|
20
|
+
* thrown {@link Refusal} carrying the same text (see `messages.ts`'s
|
|
21
|
+
* `exitText` for how the boundary spells them).
|
|
22
|
+
*
|
|
23
|
+
* Every path is printed as the author typed it — `--out` and `--bindings` go
|
|
24
|
+
* into the lines verbatim, as `args.out` and `args.bindings` did — while the
|
|
25
|
+
* file system is reached through paths resolved against `cwd`.
|
|
26
|
+
*/
|
|
27
|
+
import { resolve } from "node:path";
|
|
28
|
+
import { PACKAGE_NAME } from "../../package-name.js";
|
|
29
|
+
import { afterLast } from "../compat/pystr.js";
|
|
30
|
+
import { extractCanonical, isHttpPassthrough, nodeOf } from "../library-build/canonical.js";
|
|
31
|
+
import { createBuildContext, isResourcesList, registryGet } from "../library-build/context.js";
|
|
32
|
+
import { enrichEntry } from "../library-build/enrich.js";
|
|
33
|
+
import { expandGenericForConnector, genericTemplatesByOp } from "../library-build/generic-expand.js";
|
|
34
|
+
import { planLibrary } from "../library-build/plan.js";
|
|
35
|
+
import { MemoryResponseCache } from "../uip/response-cache.js";
|
|
36
|
+
import { registryActions } from "./actions.js";
|
|
37
|
+
import { discoverConnection, lookupConnection, mergeBindings, restoreBindings, snapshotBindings, } from "./connections.js";
|
|
38
|
+
import { prepareEvent } from "./event.js";
|
|
39
|
+
import { descriptorUsage } from "./handoff.js";
|
|
40
|
+
import { ResolutionsExhausted, allParentsResolved, resolvedParentValues, writeResolutions } from "./ledger.js";
|
|
41
|
+
import { PREPARE_COMMAND, PrepareRefusal, bindingsLine, candidateLabel, connectionLine, generatedModulesLine, genericActionNeedsObjectMessage, inputFieldNote, keptSchemaMessage, lookupVocabularyLines, missingGenericSchemaMessage, missingParentsNote, nextLine, noCanonicalEntryMessage, objectFlagsOnlyForGenericMessage, objectNoteLine, objectRecordedLine, parentFieldNamesMismatchMessage, parentOnlyMessage, prefixed, preparedSummaryLine, rejectedParentFieldsMessage, rerunAgainstLine, resolvedParentsNote, tsNote, unresolvedActionMessage, } from "./messages.js";
|
|
42
|
+
import { ObjectMatchRefusal, RESOLUTIONS_FILENAME, matchObject, recordObjectResolution } from "./objects.js";
|
|
43
|
+
import { countInputFields, keepPreparedSchema, lookupSpecsFromOverlay, mergeFilesInto, parentFields as overlayParentFields, schemaActionParents, } from "./overlay.js";
|
|
44
|
+
import { declaredParents, objectActionRequest, objectActionSpecs, parseFields } from "./parents.js";
|
|
45
|
+
import { regenerateDescriptors } from "./regenerate.js";
|
|
46
|
+
import { retryOtherConnections } from "./retry.js";
|
|
47
|
+
import { repointSource } from "./source-import.js";
|
|
48
|
+
import { isPlainObject, pyGet, pyOr, pyPathJoin, pyPathlibStr, pyStrAny, pyTruthy } from "./support.js";
|
|
49
|
+
function memoryCaches() {
|
|
50
|
+
return { registry: new MemoryResponseCache(), is: new MemoryResponseCache(), connectors: new MemoryResponseCache() };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* `_match_catalog_object`: the catalog row `--object` names, or `undefined`
|
|
54
|
+
* when the catalog itself is unavailable — that failure belongs to expansion,
|
|
55
|
+
* whose message already covers it. Every refusal past the name rungs is
|
|
56
|
+
* retried once against a refreshed catalog before it is believed: the
|
|
57
|
+
* `uip is resources list` cache never invalidates itself (measured: 18
|
|
58
|
+
* objects stale vs 864 fresh on uipath-salesforce-sfdc). Ambiguity WITHIN a
|
|
59
|
+
* name rung is not retriable — the refusal's call, not this loop's.
|
|
60
|
+
*/
|
|
61
|
+
export async function matchCatalogObject(ctx, connectorKey, wanted, verb) {
|
|
62
|
+
let rows = ((await isResourcesList(ctx, connectorKey, { operation: verb })) ?? []).filter(isPlainObject);
|
|
63
|
+
if (rows.length === 0)
|
|
64
|
+
return undefined;
|
|
65
|
+
for (const attempt of ["cached", "refreshed"]) {
|
|
66
|
+
try {
|
|
67
|
+
const { row, note } = matchObject(rows, wanted, verb);
|
|
68
|
+
if (note)
|
|
69
|
+
ctx.reporter.info(objectNoteLine(note));
|
|
70
|
+
return row;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (!(error instanceof ObjectMatchRefusal))
|
|
74
|
+
throw error;
|
|
75
|
+
if (attempt === "cached" && error.retriable) {
|
|
76
|
+
const refreshed = ((await isResourcesList(ctx, connectorKey, { operation: verb, refresh: true })) ?? []).filter(isPlainObject);
|
|
77
|
+
if (refreshed.length > 0)
|
|
78
|
+
rows = refreshed;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/** Stage 0: the connection, discovered or checked, and its row in `bindings.json`. */
|
|
87
|
+
async function connect(options, ports) {
|
|
88
|
+
const info = (line) => ports.reporter.info(line);
|
|
89
|
+
let row;
|
|
90
|
+
let connectionId;
|
|
91
|
+
if (!options.connectionId) {
|
|
92
|
+
// Discover the connection when the caller did not name one, and record it
|
|
93
|
+
// in bindings.json so the author never has to look up either key — the
|
|
94
|
+
// step that removes 4-7 tool calls per task.
|
|
95
|
+
row = await discoverConnection(ports.client, options.connectorKey, options.connection);
|
|
96
|
+
connectionId = pyStrAny(row.Id);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// An explicit id gets the same gate discovery applies: a `State: Failed`
|
|
100
|
+
// connection answers field discovery with an EMPTY schema.
|
|
101
|
+
connectionId = options.connectionId;
|
|
102
|
+
row = await lookupConnection(ports.client, options.connectorKey, connectionId);
|
|
103
|
+
}
|
|
104
|
+
info(connectionLine(row, connectionId));
|
|
105
|
+
if (options.noBindings)
|
|
106
|
+
return { connectionId, row };
|
|
107
|
+
// Recorded on BOTH routes. Until 3.30.5 only discovery wrote it, and the
|
|
108
|
+
// explicit-id route left the author hand-writing the id and folder key.
|
|
109
|
+
const bindingsShown = options.bindings ?? "./bindings.json";
|
|
110
|
+
const bindingsPath = resolve(ports.cwd ?? process.cwd(), bindingsShown);
|
|
111
|
+
// Taken before the first write so a later refusal can undo exactly this
|
|
112
|
+
// command's changes, and only them.
|
|
113
|
+
const rollback = { path: bindingsPath, snapshot: snapshotBindings(bindingsPath), shown: bindingsShown };
|
|
114
|
+
// `args.bind_connection or args.key.rsplit("-", 1)[-1]`: an empty
|
|
115
|
+
// --bind-connection falls back, as Python's `or` did. --bind-folder is an
|
|
116
|
+
// argparse default, so an empty one STANDS, as it did there.
|
|
117
|
+
const symbolic = pyOr(options.bindConnection, afterLast(options.connectorKey, "-"));
|
|
118
|
+
const folder = options.bindFolder ?? "shared";
|
|
119
|
+
const [wroteConnection, wroteFolder] = mergeBindings(bindingsPath, symbolic, folder, row);
|
|
120
|
+
if (wroteConnection)
|
|
121
|
+
info(bindingsLine(symbolic, bindingsShown));
|
|
122
|
+
if (wroteFolder)
|
|
123
|
+
info(bindingsLine(folder, bindingsShown));
|
|
124
|
+
return { connectionId, row, bound: { connection: symbolic, folder }, rollback };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The whole command. Throws a {@link Refusal} for every path the Python
|
|
128
|
+
* `sys.exit`ed on; the report is what a caller wanting more than the printed
|
|
129
|
+
* lines can read.
|
|
130
|
+
*
|
|
131
|
+
* When a `--resolve` reads a collection to the end on the connection this
|
|
132
|
+
* command PICKED and finds nothing, the other enabled connections for the
|
|
133
|
+
* connector are probed, and the run starts over pinned to the one that
|
|
134
|
+
* resolves everything (flow-builder-sdk#744). The Python re-exec'd itself for
|
|
135
|
+
* that; here the bindings snapshot is restored and `prepareConnector` is
|
|
136
|
+
* re-entered with `--connection-id <winner> --refresh-lookups`. The refresh is
|
|
137
|
+
* NOT optional: the ledger keys a resolution by `[nodeType, field, by, value]`
|
|
138
|
+
* and not by connection, so an id recorded against the connection just
|
|
139
|
+
* abandoned would come back as "already resolved" on the re-run.
|
|
140
|
+
*/
|
|
141
|
+
export async function prepareConnector(options, ports) {
|
|
142
|
+
const info = (line) => ports.reporter.info(line);
|
|
143
|
+
const warn = (line) => ports.reporter.warn(line);
|
|
144
|
+
// Whether the CALLER chose the connection, recorded before discovery answers
|
|
145
|
+
// with its own. An explicit choice is never second-guessed: the retry below
|
|
146
|
+
// is for a connection this command picked, not for one the author named.
|
|
147
|
+
const pinned = pyTruthy(options.connectionId) || pyTruthy(options.connection);
|
|
148
|
+
const connected = await connect(options, ports);
|
|
149
|
+
try {
|
|
150
|
+
return await prepareOnConnection(options, ports, connected);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (!(error instanceof ResolutionsExhausted))
|
|
154
|
+
throw error;
|
|
155
|
+
// The collection was read to the END on this connection and the value is
|
|
156
|
+
// not in it. That is evidence about the CONNECTION, not only about the
|
|
157
|
+
// value — so before failing, try the other enabled connections. A
|
|
158
|
+
// transport failure is the parent refusal and never reaches here.
|
|
159
|
+
const chosen = await retryOtherConnections({
|
|
160
|
+
client: ports.client,
|
|
161
|
+
reporter: ports.reporter,
|
|
162
|
+
connectorKey: options.connectorKey,
|
|
163
|
+
connectionId: connected.connectionId,
|
|
164
|
+
connection: options.connection,
|
|
165
|
+
pinned,
|
|
166
|
+
}, error.plan, error.first);
|
|
167
|
+
// Undo the bindings this run wrote for the connection that cannot serve
|
|
168
|
+
// the lookups, then start over pinned to the one that can. Re-running
|
|
169
|
+
// rather than switching in place is deliberate: the overlay and its
|
|
170
|
+
// lookup specs are described THROUGH a connection
|
|
171
|
+
// (`lookupSpecsFromOverlay`), so adopting a different connection after the
|
|
172
|
+
// describe would leave the overlay describing one connection while
|
|
173
|
+
// bindings.json names another.
|
|
174
|
+
if (connected.rollback)
|
|
175
|
+
restoreBindings(connected.rollback.path, connected.rollback.snapshot, warn, connected.rollback.shown);
|
|
176
|
+
const chosenId = pyStrAny(pyGet(chosen, "Id", null));
|
|
177
|
+
info(rerunAgainstLine(candidateLabel(chosen), error.field, chosenId));
|
|
178
|
+
// THE LOOP GUARD, and it is this line rather than a counter: the re-entry
|
|
179
|
+
// carries `connectionId`, so its own `pinned` is true and a second
|
|
180
|
+
// exhaustion takes the pinned branch — a refusal naming the alternatives —
|
|
181
|
+
// instead of probing again. The Python got the same guard from the same
|
|
182
|
+
// place, since the re-exec'd process recomputed
|
|
183
|
+
// `args._connection_pinned = bool(args.connection_id or args.connection)`
|
|
184
|
+
// from the `--connection-id` it had just been handed. One probing round
|
|
185
|
+
// per command is the whole contract: the probe has already proved this
|
|
186
|
+
// connection resolves every lookup, so a re-run that still cannot is a
|
|
187
|
+
// changed tenant, not another connection to go looking through.
|
|
188
|
+
return prepareConnector({ ...options, connectionId: chosenId, refreshLookups: true }, ports);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** Everything after stage 0, against one settled connection. */
|
|
192
|
+
async function prepareOnConnection(options, ports, connected) {
|
|
193
|
+
const cwd = ports.cwd ?? process.cwd();
|
|
194
|
+
const info = (line) => ports.reporter.info(line);
|
|
195
|
+
const warn = (line) => ports.reporter.warn(line);
|
|
196
|
+
const command = ports.prepareCommand ?? PREPARE_COMMAND;
|
|
197
|
+
const { connectorKey, action } = options;
|
|
198
|
+
const fieldPairs = options.fields ?? [];
|
|
199
|
+
const parentFields = parseFields(fieldPairs);
|
|
200
|
+
const outShown = options.out ?? "./connectors-local";
|
|
201
|
+
const out = resolve(cwd, outShown);
|
|
202
|
+
const importSpecifier = options.importSpecifier ?? PACKAGE_NAME;
|
|
203
|
+
const jsonOnly = options.jsonOnly ?? false;
|
|
204
|
+
const { connectionId, bound, rollback } = connected;
|
|
205
|
+
const ctx = createBuildContext({ client: ports.client, caches: ports.caches ?? memoryCaches(), reporter: ports.reporter, concurrency: 1, timeoutMs: ports.timeoutMs }, { connectionId, parentFields });
|
|
206
|
+
const nodeType = `uipath.connector.${connectorKey}.${action}`;
|
|
207
|
+
const cacheName = await registryGet(ctx, nodeType, true);
|
|
208
|
+
if (cacheName === undefined) {
|
|
209
|
+
// Not an activity. Connector EVENTS live one namespace over, and their
|
|
210
|
+
// registry definition only answers with a connection, which this command
|
|
211
|
+
// has by now. Same loop, different twin pair.
|
|
212
|
+
const event = await prepareEvent(ctx, {
|
|
213
|
+
connectorKey,
|
|
214
|
+
action,
|
|
215
|
+
connectionId,
|
|
216
|
+
objectName: options.objectName,
|
|
217
|
+
allObjects: options.allObjects,
|
|
218
|
+
fields: fieldPairs,
|
|
219
|
+
resolve: options.resolve,
|
|
220
|
+
refreshLookups: options.refreshLookups,
|
|
221
|
+
out,
|
|
222
|
+
outShown,
|
|
223
|
+
importSpecifier,
|
|
224
|
+
jsonOnly,
|
|
225
|
+
boundConnection: bound?.connection,
|
|
226
|
+
boundFolder: bound?.folder,
|
|
227
|
+
bindings: rollback,
|
|
228
|
+
prepareCommand: command,
|
|
229
|
+
});
|
|
230
|
+
if (event !== undefined) {
|
|
231
|
+
return { kind: "event", nodeType: event.eventNodeType, connectionId, bound, objectName: event.objectName };
|
|
232
|
+
}
|
|
233
|
+
const actions = await registryActions(ports.client, connectorKey, ports.timeoutMs);
|
|
234
|
+
throw new PrepareRefusal(unresolvedActionMessage(connectorKey, action, nodeType, actions), "ACTION_UNKNOWN");
|
|
235
|
+
}
|
|
236
|
+
const payload = ctx.caches.registry.read(cacheName);
|
|
237
|
+
// The registry's own declaration of parent inputs, read from the same reply.
|
|
238
|
+
const declared = declaredParents(payload);
|
|
239
|
+
let entry = extractCanonical(nodeOf(payload));
|
|
240
|
+
if (entry === undefined)
|
|
241
|
+
throw new PrepareRefusal(noCanonicalEntryMessage(nodeType), "NO_CANONICAL_ENTRY");
|
|
242
|
+
if (options.version)
|
|
243
|
+
entry.version = options.version;
|
|
244
|
+
if (parentFields.size > 0) {
|
|
245
|
+
const request = objectActionRequest(payload, parentFields);
|
|
246
|
+
if (request === undefined) {
|
|
247
|
+
throw new PrepareRefusal(parentFieldNamesMismatchMessage([...parentFields.keys()], objectActionSpecs(payload)), "PARENT_FIELDS_MISMATCH");
|
|
248
|
+
}
|
|
249
|
+
entry.operation.customFieldsRequestDetails = request;
|
|
250
|
+
}
|
|
251
|
+
let objectName = options.objectName;
|
|
252
|
+
let objectDecision;
|
|
253
|
+
let preparedEntries;
|
|
254
|
+
const isExpandableGeneric = entry.operation.activityType === "Generic" && !isHttpPassthrough(entry);
|
|
255
|
+
if (isExpandableGeneric) {
|
|
256
|
+
if (!objectName && !options.allObjects)
|
|
257
|
+
throw new PrepareRefusal(genericActionNeedsObjectMessage(), "OBJECT_REQUIRED");
|
|
258
|
+
const templates = genericTemplatesByOp([entry]).get(connectorKey) ?? new Map();
|
|
259
|
+
if (objectName) {
|
|
260
|
+
// The author's name, not necessarily the API's: matched deterministically
|
|
261
|
+
// BEFORE expansion — expansion re-reads the same cached catalog, so the
|
|
262
|
+
// match costs no extra network.
|
|
263
|
+
const verb = templates.size === 1 ? [...templates.keys()][0] : undefined;
|
|
264
|
+
const requested = objectName;
|
|
265
|
+
const row = await matchCatalogObject(ctx, connectorKey, objectName, verb);
|
|
266
|
+
if (row !== undefined) {
|
|
267
|
+
objectName = pyStrAny(pyOr(pyGet(row, "Name", null), objectName));
|
|
268
|
+
objectDecision = { requested, row, verb };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const selected = options.allObjects ? undefined : [objectName];
|
|
272
|
+
preparedEntries = await expandGenericForConnector(ctx, connectorKey, templates, selected);
|
|
273
|
+
if (preparedEntries.length === 0) {
|
|
274
|
+
const operation = pyStrAny(pyOr(pyGet(entry.operation, "name", null), action));
|
|
275
|
+
throw new PrepareRefusal(missingGenericSchemaMessage(operation, options.allObjects ? undefined : objectName), "GENERIC_SCHEMA_MISSING");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
if (objectName || options.allObjects)
|
|
280
|
+
throw new PrepareRefusal(objectFlagsOnlyForGenericMessage(), "OBJECT_NOT_GENERIC");
|
|
281
|
+
entry = await enrichEntry(ctx, entry); // full schema via --connection-id
|
|
282
|
+
preparedEntries = [entry];
|
|
283
|
+
}
|
|
284
|
+
const entryNodeType = entry.nodeType;
|
|
285
|
+
const plan = planLibrary(preparedEntries, (requestedNodeType) => (requestedNodeType === entryNodeType ? nodeOf(payload) : undefined));
|
|
286
|
+
for (const line of plan.warnings)
|
|
287
|
+
warn(line);
|
|
288
|
+
const kept = keepPreparedSchema(out, entryNodeType, plan.files, parentFields, objectName);
|
|
289
|
+
if (kept === undefined)
|
|
290
|
+
mergeFilesInto(plan.files, out);
|
|
291
|
+
// The object decision is the FIRST resolution — recorded before any lookup
|
|
292
|
+
// rows, because it is their prerequisite.
|
|
293
|
+
if (objectDecision !== undefined) {
|
|
294
|
+
recordObjectResolution(out, nodeType, objectDecision.requested, objectDecision.row, objectDecision.verb);
|
|
295
|
+
// Named as the author spelled `--out` (`os.path.join(args.out, …)`).
|
|
296
|
+
info(objectRecordedLine(objectDecision.requested, objectName, pyPathJoin(outShown, RESOLUTIONS_FILENAME)));
|
|
297
|
+
}
|
|
298
|
+
if (pyTruthy(options.resolve)) {
|
|
299
|
+
// A `ResolutionsExhausted` propagates to `prepareConnector`, which owns
|
|
300
|
+
// the retry: re-running pinned means starting the whole command over.
|
|
301
|
+
await writeResolutions({
|
|
302
|
+
client: ports.client,
|
|
303
|
+
connectorKey,
|
|
304
|
+
connectionId,
|
|
305
|
+
out,
|
|
306
|
+
outShown,
|
|
307
|
+
nodeType,
|
|
308
|
+
objectName,
|
|
309
|
+
resolve: options.resolve,
|
|
310
|
+
refreshLookups: options.refreshLookups,
|
|
311
|
+
reporter: ports.reporter,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
const modules = regenerateDescriptors(out, importSpecifier, jsonOnly);
|
|
315
|
+
// The generator's own last line, which the Python spawned and so printed
|
|
316
|
+
// between the resolution lines and the summary; under --json-only it never
|
|
317
|
+
// ran and said nothing.
|
|
318
|
+
if (!jsonOnly)
|
|
319
|
+
info(generatedModulesLine(modules.length, pyPathlibStr(outShown)));
|
|
320
|
+
const inputFields = countInputFields(out, entryNodeType, objectName);
|
|
321
|
+
const variants = preparedEntries.length;
|
|
322
|
+
// The describe is the evidence, not the finished field count: a rejected
|
|
323
|
+
// parent set leaves the registry-DECLARED fields in the overlay, so surface
|
|
324
|
+
// the CLI's own message instead of that quiet success.
|
|
325
|
+
const failures = ctx.failedFieldDescribes;
|
|
326
|
+
if (fieldPairs.length > 0 && (failures.length > 0 || inputFields === 0)) {
|
|
327
|
+
throw new PrepareRefusal(rejectedParentFieldsMessage(fieldPairs, failures), "PARENT_FIELDS_REJECTED");
|
|
328
|
+
}
|
|
329
|
+
info(preparedSummaryLine(nodeType, outShown, tsNote(jsonOnly, connectorKey), variants, inputFieldNote(inputFields)));
|
|
330
|
+
if (kept !== undefined)
|
|
331
|
+
info(keptSchemaMessage(action, kept[0], kept[1]));
|
|
332
|
+
for (const line of lookupVocabularyLines(connectorKey, action, objectName, lookupSpecsFromOverlay(out, nodeType, objectName), command)) {
|
|
333
|
+
info(line);
|
|
334
|
+
}
|
|
335
|
+
// Whether this operation has parent fields is answered by what was just
|
|
336
|
+
// prepared, not by a catalog flag.
|
|
337
|
+
const [parents, parentOnly] = overlayParentFields(out, entryNodeType, objectName);
|
|
338
|
+
if (parentOnly && fieldPairs.length > 0) {
|
|
339
|
+
throw new PrepareRefusal(parentOnlyMessage(parents, fieldPairs), "PARENT_VALUES_UNRESOLVED");
|
|
340
|
+
}
|
|
341
|
+
// Whether the schema action RAN is decided by its inputs, not by its output:
|
|
342
|
+
// every parent it names must have arrived as `-f`. Union of both declaration
|
|
343
|
+
// sites so an overlay that omits `schemaAction` still gets the registry's.
|
|
344
|
+
const actionParents = schemaActionParents(out, entryNodeType, objectName);
|
|
345
|
+
for (const name of declared)
|
|
346
|
+
if (!actionParents.includes(name))
|
|
347
|
+
actionParents.push(name);
|
|
348
|
+
const missing = actionParents.filter((name) => !parentFields.has(name));
|
|
349
|
+
// Silent when the schema was KEPT: the note's premise is that the overlay
|
|
350
|
+
// carries only the unresolved entry, and it does not.
|
|
351
|
+
if (missing.length > 0 && kept === undefined) {
|
|
352
|
+
const resolvedParents = resolvedParentValues(out, nodeType, actionParents);
|
|
353
|
+
if (allParentsResolved(actionParents, resolvedParents)) {
|
|
354
|
+
warn(resolvedParentsNote(connectorKey, action, actionParents, resolvedParents, command));
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
warn(missingParentsNote(connectorKey, action, actionParents, missing, parentFields, command));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (!jsonOnly) {
|
|
361
|
+
// Print what was bound, so the last line of output is a call the reader
|
|
362
|
+
// can paste rather than `<binding-name>` placeholders. `or`, not `??`: an
|
|
363
|
+
// empty symbolic name is a placeholder in Python's eyes too.
|
|
364
|
+
let usage = descriptorUsage(outShown, connectorKey, pyOr(bound?.connection, "<binding-name>"), pyOr(bound?.folder, "<folder-binding-name>"), cwd);
|
|
365
|
+
const repointed = repointSource({ out: outShown, connectorKey, cwd, source: options.source, noSourceRewrite: options.noSourceRewrite });
|
|
366
|
+
if (repointed.length > 0 && repointed[0].startsWith("repointed ")) {
|
|
367
|
+
// The edit is DONE, so telling the author to make it is now false.
|
|
368
|
+
usage = usage.filter((line) => !line.startsWith("import {"));
|
|
369
|
+
}
|
|
370
|
+
for (const line of repointed)
|
|
371
|
+
info(prefixed(line));
|
|
372
|
+
for (const line of usage)
|
|
373
|
+
info(nextLine(line));
|
|
374
|
+
}
|
|
375
|
+
return { kind: "action", nodeType, connectionId, bound, variants, inputFields, kept, objectName };
|
|
376
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write `<out>/<connectorKey>.ts` for every connector in the overlay and
|
|
3
|
+
* return the module names written; nothing at all under `--json-only`.
|
|
4
|
+
*
|
|
5
|
+
* The generator wrote every module and only then exited 1 on a required-input
|
|
6
|
+
* gap; the files are on disk here too, and the failure is as loud and as late
|
|
7
|
+
* (a {@link Refusal} carrying the generator's own text).
|
|
8
|
+
*/
|
|
9
|
+
export declare function regenerateDescriptors(out: string, importSpecifier: string, jsonOnly?: boolean): string[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite the overlay's typed descriptors from the overlay's own JSON.
|
|
3
|
+
*
|
|
4
|
+
* Both prepare paths end here. The EVENT path used not to: it wrote the JSON
|
|
5
|
+
* overlay and returned, so a project whose only prepare was an event got no
|
|
6
|
+
* `.ts` at all, and one that had prepared an action earlier kept that older
|
|
7
|
+
* run's module — generated before the event's parameters were in the overlay,
|
|
8
|
+
* so `where` was `Record<string, string>` and the trigger carried no
|
|
9
|
+
* `lookups`. That is the module `prepare` tells the author to import, and
|
|
10
|
+
* `--refresh-lookups` could not repair it.
|
|
11
|
+
*
|
|
12
|
+
* `includeGenericExpanded` is decided from the WHOLE overlay index rather than
|
|
13
|
+
* the entries of this run: the generator rewrites every module under the
|
|
14
|
+
* output, so reading one run would drop the descriptors an earlier
|
|
15
|
+
* `GenericExpanded` prepare had written.
|
|
16
|
+
*
|
|
17
|
+
* Ported from `_regenerate_descriptors`, which spawned
|
|
18
|
+
* `generate_connectors_ts.py --library <out> --output <out>`; the generator is
|
|
19
|
+
* now `renderDescriptorModules`, called in-process.
|
|
20
|
+
*/
|
|
21
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { Refusal } from "../errors.js";
|
|
24
|
+
import { formatRequiredInputGaps, renderDescriptorModules } from "../generators/descriptors.js";
|
|
25
|
+
import { loadLibrary } from "../library-reader.js";
|
|
26
|
+
import { overlayHasGenericExpanded } from "./overlay.js";
|
|
27
|
+
/**
|
|
28
|
+
* Write `<out>/<connectorKey>.ts` for every connector in the overlay and
|
|
29
|
+
* return the module names written; nothing at all under `--json-only`.
|
|
30
|
+
*
|
|
31
|
+
* The generator wrote every module and only then exited 1 on a required-input
|
|
32
|
+
* gap; the files are on disk here too, and the failure is as loud and as late
|
|
33
|
+
* (a {@link Refusal} carrying the generator's own text).
|
|
34
|
+
*/
|
|
35
|
+
export function regenerateDescriptors(out, importSpecifier, jsonOnly = false) {
|
|
36
|
+
if (jsonOnly)
|
|
37
|
+
return [];
|
|
38
|
+
const library = loadLibrary(out);
|
|
39
|
+
const output = renderDescriptorModules(library, {
|
|
40
|
+
importSpecifier,
|
|
41
|
+
includeGenericExpanded: overlayHasGenericExpanded(out),
|
|
42
|
+
});
|
|
43
|
+
mkdirSync(out, { recursive: true });
|
|
44
|
+
for (const [name, text] of output.modules)
|
|
45
|
+
writeFileSync(join(out, name), text, "utf8");
|
|
46
|
+
if (output.requiredInputGaps.length > 0) {
|
|
47
|
+
throw new Refusal(formatRequiredInputGaps(output.requiredInputGaps), "REQUIRED_INPUT_GAP");
|
|
48
|
+
}
|
|
49
|
+
return [...output.modules.keys()];
|
|
50
|
+
}
|