@uipath/maestro-builder-sdk 6.36.1 → 6.37.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/api-index.md +12 -11
- package/dist/api-members.md +5 -5
- package/dist/bpmn/check.js +64 -0
- package/dist/bpmn/connector.js +3 -0
- package/dist/bpmn/declared-vars.js +8 -1
- package/dist/bpmn/decompile.js +14 -6
- package/dist/bpmn/intsvc.d.ts +9 -10
- package/dist/bpmn/intsvc.js +31 -18
- package/dist/bpmn/serialize.js +22 -24
- package/dist/cli-library.js +3 -1
- package/dist/core/cli-library.d.ts +8 -2
- package/dist/core/cli-library.js +6 -4
- package/dist/core/connector-resolve-error.d.ts +7 -1
- package/dist/core/event-checks.js +2 -2
- package/dist/core/lookups.d.ts +22 -0
- package/dist/core/lookups.js +34 -1
- package/dist/core/synthesized-definition.d.ts +15 -0
- package/dist/core/synthesized-definition.js +22 -0
- package/dist/registry/library-build/context.d.ts +16 -0
- package/dist/registry/library-build/context.js +32 -5
- package/dist/registry/prepare/actions.d.ts +15 -0
- package/dist/registry/prepare/actions.js +12 -1
- package/dist/registry/prepare/integration-service.d.ts +105 -0
- package/dist/registry/prepare/integration-service.js +218 -0
- package/dist/registry/prepare/messages.d.ts +41 -1
- package/dist/registry/prepare/messages.js +82 -1
- package/dist/registry/prepare/pipeline.js +73 -15
- package/dist/registry/uip/commands.d.ts +8 -0
- package/dist/registry/uip/commands.js +12 -0
- package/dist/serialize.js +11 -0
- package/package.json +2 -2
package/dist/core/lookups.js
CHANGED
|
@@ -166,6 +166,39 @@ export function resolvedValue(token, resolutions) {
|
|
|
166
166
|
const { nodeType, field, by, value } = token.__lookup;
|
|
167
167
|
return resolutions[lookupKey(nodeType, field, by, value)]?.value;
|
|
168
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* The event form of a node type — `uipath.connector.event.<key>.<op>` — for one
|
|
171
|
+
* written without a role segment, else `undefined`. A role-less type is what the
|
|
172
|
+
* string form `lookup(key, action, field)` keys a token under.
|
|
173
|
+
*/
|
|
174
|
+
function eventFormOf(nodeType) {
|
|
175
|
+
const m = /^uipath\.connector\.(?!event\.|trigger\.)(.+)$/.exec(nodeType);
|
|
176
|
+
return m ? `uipath.connector.event.${m[1]}` : undefined;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* {@link resolvedValue} for a token on an event's `where`.
|
|
180
|
+
*
|
|
181
|
+
* `prepare` records an event's resolutions under the EVENT form of the node type,
|
|
182
|
+
* which the descriptor form `lookup({ connector, event }, field)` keys a token
|
|
183
|
+
* under too. The string form `lookup(key, action, field)` cannot know it is being
|
|
184
|
+
* used on an event and keys the token under the activity form, so a recorded
|
|
185
|
+
* resolution was never found for it: on the 2026-10-16 run an author whose
|
|
186
|
+
* `prepare --resolve parentFolderId:displayName=Inbox` had succeeded still saw
|
|
187
|
+
* `LOOKUP_UNRESOLVED` from `check` and a refusal from `compile`, and pasted the
|
|
188
|
+
* id in by hand. The token's own key is tried first; the event form second.
|
|
189
|
+
*
|
|
190
|
+
* @param token - The unresolved lookup, from either `lookup()` form.
|
|
191
|
+
* @param resolutions - Everything `prepare --resolve` has recorded.
|
|
192
|
+
* @returns The connector-facing id, when either key was resolved.
|
|
193
|
+
*/
|
|
194
|
+
export function resolvedEventValue(token, resolutions) {
|
|
195
|
+
const own = resolvedValue(token, resolutions);
|
|
196
|
+
if (own !== undefined)
|
|
197
|
+
return own;
|
|
198
|
+
const { nodeType, field, by, value } = token.__lookup;
|
|
199
|
+
const eventForm = eventFormOf(nodeType);
|
|
200
|
+
return eventForm === undefined ? undefined : resolutions[lookupKey(eventForm, field, by, value)]?.value;
|
|
201
|
+
}
|
|
169
202
|
/**
|
|
170
203
|
* The message for a lookup nothing has resolved — the command that fixes it.
|
|
171
204
|
*
|
|
@@ -282,7 +315,7 @@ export function resolveEventWhere(where, resolutions, site) {
|
|
|
282
315
|
out[key] = value;
|
|
283
316
|
continue;
|
|
284
317
|
}
|
|
285
|
-
const resolved =
|
|
318
|
+
const resolved = resolvedEventValue(value, resolutions);
|
|
286
319
|
if (resolved === undefined)
|
|
287
320
|
throw new Error(`${site}: \`where.${key}\` ` + unresolvedLookupMessage(value));
|
|
288
321
|
out[key] = resolved;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one refusal Flow adds on top of `Library.resolve`: an operation whose
|
|
3
|
+
* sidecar `registry prepare` synthesized from Integration Service
|
|
4
|
+
* (`prepare/integration-service.ts`) has no registry node behind it. A BPMN
|
|
5
|
+
* `sendTask` carries its own identity and rows, so BPMN compiles it; a Flow
|
|
6
|
+
* node embeds the registry's whole definition — form, handles, model — which
|
|
7
|
+
* no describe supplies, so Flow refuses it here, in `check` and `compile`
|
|
8
|
+
* alike, instead of emitting a node the designer cannot open.
|
|
9
|
+
*/
|
|
10
|
+
import { ConnectorResolveError } from './connector-resolve-error.js';
|
|
11
|
+
import type { ResolvedConnector } from './library.js';
|
|
12
|
+
/** The marker `prepare` writes on a sidecar it built from Integration Service. */
|
|
13
|
+
export declare const SYNTHESIZED_FROM_INTEGRATION_SERVICE = "integration-service";
|
|
14
|
+
/** Flow's refusal for a resolved operation with no registry definition, or `undefined` when it has one. */
|
|
15
|
+
export declare function synthesizedDefinitionRefusal(rc: Pick<ResolvedConnector, 'v1def' | 'connectorKey' | 'nodeType'>): ConnectorResolveError | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one refusal Flow adds on top of `Library.resolve`: an operation whose
|
|
3
|
+
* sidecar `registry prepare` synthesized from Integration Service
|
|
4
|
+
* (`prepare/integration-service.ts`) has no registry node behind it. A BPMN
|
|
5
|
+
* `sendTask` carries its own identity and rows, so BPMN compiles it; a Flow
|
|
6
|
+
* node embeds the registry's whole definition — form, handles, model — which
|
|
7
|
+
* no describe supplies, so Flow refuses it here, in `check` and `compile`
|
|
8
|
+
* alike, instead of emitting a node the designer cannot open.
|
|
9
|
+
*/
|
|
10
|
+
import { ConnectorResolveError } from './connector-resolve-error.js';
|
|
11
|
+
/** The marker `prepare` writes on a sidecar it built from Integration Service. */
|
|
12
|
+
export const SYNTHESIZED_FROM_INTEGRATION_SERVICE = 'integration-service';
|
|
13
|
+
/** Flow's refusal for a resolved operation with no registry definition, or `undefined` when it has one. */
|
|
14
|
+
export function synthesizedDefinitionRefusal(rc) {
|
|
15
|
+
const origin = rc.v1def?.synthesizedFrom;
|
|
16
|
+
if (origin !== SYNTHESIZED_FROM_INTEGRATION_SERVICE)
|
|
17
|
+
return undefined;
|
|
18
|
+
const action = String(rc.nodeType).replace(/^uipath\.connector\.[^.]+\./, '');
|
|
19
|
+
return new ConnectorResolveError('CONNECTOR_NO_FLOW_DEFINITION', `connector ${rc.connectorKey} ${action} was prepared from Integration Service because the Flow node catalog serves no node for ${rc.connectorKey}; `
|
|
20
|
+
+ 'a Flow step or inline agent tool needs that node (its form, handles and model), so this operation compiles in BPMN only. '
|
|
21
|
+
+ `Author it as a BPMN .connector(), or check \`uip maestro flow registry search ${rc.connectorKey}\` after \`registry pull --force\` in case the catalog has since gained it.`);
|
|
22
|
+
}
|
|
@@ -130,4 +130,20 @@ export declare function isResourcesList(ctx: BuildContext, connectorKey: string,
|
|
|
130
130
|
* files are deliberately not reused: that output omitted the discovery
|
|
131
131
|
* capabilities and would silently mark every connector static.
|
|
132
132
|
*/
|
|
133
|
+
/** `uip is activities list`'s answer: its rows, or why it could not be read. */
|
|
134
|
+
export type IsActivitiesCatalog = {
|
|
135
|
+
rows: unknown[];
|
|
136
|
+
} | {
|
|
137
|
+
failure: string;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Cached `uip is activities list <key>`: the connector's activity catalog
|
|
141
|
+
* (every row, curated or not — the caller filters on `IsCurated`), or the
|
|
142
|
+
* failure in the CLI's own words ({@link failureReason}): a refusal, output
|
|
143
|
+
* that is not JSON, or a `Result: Failure` envelope (an expired token, a 5xx,
|
|
144
|
+
* a connector the tenant has not enabled). Cached beside the `is resources`
|
|
145
|
+
* payloads — it is the same catalog service, read without a connection — and
|
|
146
|
+
* a cached failure keeps its reason.
|
|
147
|
+
*/
|
|
148
|
+
export declare function isActivitiesCatalog(ctx: BuildContext, connectorKey: string): Promise<IsActivitiesCatalog>;
|
|
133
149
|
export declare function isConnectorGet(ctx: BuildContext, connectorKey: string): Promise<unknown>;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
17
|
import { pyJsonStringify } from "../codecs/pyjson.js";
|
|
18
18
|
import { sortedByKeys } from "../compat/compare.js";
|
|
19
|
-
import { isConnectorsMetadata, isResourcesDescribeObject, isResourcesDescribeOperation, isResourcesList as isResourcesListArgv, registryGet as registryGetArgv, } from "../uip/commands.js";
|
|
19
|
+
import { isActivitiesList as isActivitiesListArgv, isConnectorsMetadata, isResourcesDescribeObject, isResourcesDescribeOperation, isResourcesList as isResourcesListArgv, registryGet as registryGetArgv, } from "../uip/commands.js";
|
|
20
20
|
import { CACHED_FAILURE, isCachedFailure, safeCacheName } from "../uip/response-cache.js";
|
|
21
21
|
import { failureReason, isNodeNotFound } from "../uip/envelope.js";
|
|
22
22
|
import { get, isPyDict, pyOr, pyTruthy } from "./py.js";
|
|
@@ -251,11 +251,38 @@ export async function isResourcesList(ctx, connectorKey, options = {}) {
|
|
|
251
251
|
return Array.isArray(payload.Data) ? payload.Data : undefined;
|
|
252
252
|
}
|
|
253
253
|
/**
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
254
|
+
* Cached `uip is activities list <key>`: the connector's activity catalog
|
|
255
|
+
* (every row, curated or not — the caller filters on `IsCurated`), or the
|
|
256
|
+
* failure in the CLI's own words ({@link failureReason}): a refusal, output
|
|
257
|
+
* that is not JSON, or a `Result: Failure` envelope (an expired token, a 5xx,
|
|
258
|
+
* a connector the tenant has not enabled). Cached beside the `is resources`
|
|
259
|
+
* payloads — it is the same catalog service, read without a connection — and
|
|
260
|
+
* a cached failure keeps its reason.
|
|
258
261
|
*/
|
|
262
|
+
export async function isActivitiesCatalog(ctx, connectorKey) {
|
|
263
|
+
const name = `${safeCacheName(connectorKey, "_activities_list")}.json`;
|
|
264
|
+
if (ctx.caches.is.has(name)) {
|
|
265
|
+
const cached = ctx.caches.is.read(name);
|
|
266
|
+
const data = get(cached, "Data");
|
|
267
|
+
if (Array.isArray(data))
|
|
268
|
+
return { rows: data };
|
|
269
|
+
return { failure: String(pyOr(get(cached, "Message"), "the cached answer carries no rows")) };
|
|
270
|
+
}
|
|
271
|
+
const result = await runUip(ctx, isActivitiesListArgv(connectorKey));
|
|
272
|
+
if (refused(result))
|
|
273
|
+
return { failure: failureReason(result) };
|
|
274
|
+
const parsed = parseStdout(result.stdout);
|
|
275
|
+
if (parsed === undefined)
|
|
276
|
+
return { failure: failureReason(result) };
|
|
277
|
+
const payload = envelope(parsed);
|
|
278
|
+
if (get(payload, "Result") !== "Success") {
|
|
279
|
+
const failure = failureReason(result);
|
|
280
|
+
writeCache(ctx.caches.is, name, { Result: "Failure", Message: failure, [CACHED_FAILURE]: true });
|
|
281
|
+
return { failure };
|
|
282
|
+
}
|
|
283
|
+
writeCache(ctx.caches.is, name, payload);
|
|
284
|
+
return Array.isArray(payload.Data) ? { rows: payload.Data } : { failure: "the answer carries no Data rows" };
|
|
285
|
+
}
|
|
259
286
|
export async function isConnectorGet(ctx, connectorKey) {
|
|
260
287
|
const name = `${safeCacheName(connectorKey, "_connector_metadata")}.json`;
|
|
261
288
|
const firstRow = (payload) => {
|
|
@@ -56,6 +56,21 @@ export interface RegistryCatalog {
|
|
|
56
56
|
*/
|
|
57
57
|
failure?: string;
|
|
58
58
|
}
|
|
59
|
+
/** Whether `registry pull --force` refreshed the local catalog copy, or why not. */
|
|
60
|
+
export type RegistryRefresh = {
|
|
61
|
+
ok: true;
|
|
62
|
+
} | {
|
|
63
|
+
ok: false;
|
|
64
|
+
reason: string;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* `uip maestro flow registry pull --force`: refresh the local catalog copy the
|
|
68
|
+
* probes and the search answer from. It never refreshes itself, so before
|
|
69
|
+
* `prepare` concludes that the catalog serves nothing for a connector it pulls
|
|
70
|
+
* once and looks again; a stale copy would otherwise send a connector the
|
|
71
|
+
* catalog does serve down the BPMN-only path, and state it as fact.
|
|
72
|
+
*/
|
|
73
|
+
export declare function refreshRegistryCatalog(client: UipClient, timeoutMs?: number): Promise<RegistryRefresh>;
|
|
59
74
|
/**
|
|
60
75
|
* `registry search <key>`, read for both catalogs. This runs only on the
|
|
61
76
|
* failure path, to tell the author what the catalog does serve.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { compareCodePoints, sortedStrings } from "../compat/compare.js";
|
|
20
20
|
import { editDistance } from "../../core/edit-distance.js";
|
|
21
|
-
import { registrySearch } from "../uip/commands.js";
|
|
21
|
+
import { registryPull, registrySearch } from "../uip/commands.js";
|
|
22
22
|
import { failureReason, findJsonDocument, tail } from "../uip/envelope.js";
|
|
23
23
|
import { isPlainObject, pyOr } from "./support.js";
|
|
24
24
|
/**
|
|
@@ -141,6 +141,17 @@ export function registryEventIds(payload, connectorKey) {
|
|
|
141
141
|
forms: ["trigger", "event"].filter((form) => forms.get(id).has(form)),
|
|
142
142
|
}));
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* `uip maestro flow registry pull --force`: refresh the local catalog copy the
|
|
146
|
+
* probes and the search answer from. It never refreshes itself, so before
|
|
147
|
+
* `prepare` concludes that the catalog serves nothing for a connector it pulls
|
|
148
|
+
* once and looks again; a stale copy would otherwise send a connector the
|
|
149
|
+
* catalog does serve down the BPMN-only path, and state it as fact.
|
|
150
|
+
*/
|
|
151
|
+
export async function refreshRegistryCatalog(client, timeoutMs) {
|
|
152
|
+
const result = await client.run(registryPull(), timeoutMs === undefined ? undefined : { timeoutMs });
|
|
153
|
+
return result.code === 0 ? { ok: true } : { ok: false, reason: failureReason(result) };
|
|
154
|
+
}
|
|
144
155
|
/**
|
|
145
156
|
* `registry search <key>`, read for both catalogs. This runs only on the
|
|
146
157
|
* failure path, to tell the author what the catalog does serve.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { SYNTHESIZED_FROM_INTEGRATION_SERVICE } from "../../core/synthesized-definition.js";
|
|
2
|
+
import { type BuildContext } from "../library-build/context.js";
|
|
3
|
+
import type { LibraryEntry } from "../model/library.js";
|
|
4
|
+
/** The marker a synthesized sidecar carries; Flow refuses a definition that has it. One owner, `core/`, so the writer and the refuser cannot drift. */
|
|
5
|
+
export { SYNTHESIZED_FROM_INTEGRATION_SERVICE };
|
|
6
|
+
/** One row of `uip is activities list`, the fields this module reads. */
|
|
7
|
+
export interface IsActivity {
|
|
8
|
+
/** The activity's own name, PascalCase: `CreateTestSet`. */
|
|
9
|
+
name: string;
|
|
10
|
+
displayName: string;
|
|
11
|
+
description: string;
|
|
12
|
+
/** The IS object the activity addresses: `TestSet`. */
|
|
13
|
+
objectName: string;
|
|
14
|
+
/** The HTTP verb as the catalog spells it: `POST`, `GETBYID`, `PATCH`, … */
|
|
15
|
+
method: string;
|
|
16
|
+
}
|
|
17
|
+
/** Why the Integration Service path could not prepare the action. */
|
|
18
|
+
export type IntegrationServiceMiss =
|
|
19
|
+
/** The catalog could not be listed (`reason`: the CLI's own words), or lists no curated activity (no `reason`). */
|
|
20
|
+
{
|
|
21
|
+
kind: "no-catalog";
|
|
22
|
+
reason?: string;
|
|
23
|
+
}
|
|
24
|
+
/** The catalog lists curated activities, none of which is the action; their ids, for the refusal. */
|
|
25
|
+
| {
|
|
26
|
+
kind: "no-such-activity";
|
|
27
|
+
activities: string[];
|
|
28
|
+
}
|
|
29
|
+
/** The activity's HTTP method maps to no `is resources` verb, so there is no describe to read. */
|
|
30
|
+
| {
|
|
31
|
+
kind: "no-operation";
|
|
32
|
+
activity: IsActivity;
|
|
33
|
+
}
|
|
34
|
+
/** The activity exists but `is resources describe` returned no schema for it. */
|
|
35
|
+
| {
|
|
36
|
+
kind: "no-schema";
|
|
37
|
+
activity: IsActivity;
|
|
38
|
+
operation: string;
|
|
39
|
+
};
|
|
40
|
+
export interface IntegrationServicePrepared {
|
|
41
|
+
entry: LibraryEntry;
|
|
42
|
+
/**
|
|
43
|
+
* The action id the entry goes by: the activity's own name in kebab case,
|
|
44
|
+
* whatever spelling the author gave (`--action "Create Test Set"` prepares
|
|
45
|
+
* `create-test-set`). The overlay path and the handoff use it.
|
|
46
|
+
*/
|
|
47
|
+
action: string;
|
|
48
|
+
/** The registry-shaped node `planLibrary` builds the sidecar from. */
|
|
49
|
+
node: Record<string, unknown>;
|
|
50
|
+
activity: IsActivity;
|
|
51
|
+
/** The `is resources` verb the describe was read under (`Create`, `Retrieve`, …). */
|
|
52
|
+
operation: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The action id an activity goes by: its PascalCase name in kebab case —
|
|
56
|
+
* `CreateTestSet` → `create-test-set`, `AssignTestCasesToReq` →
|
|
57
|
+
* `assign-test-cases-to-req`, `CreateV2Attachment` → `create-v2-attachment`.
|
|
58
|
+
* A run of capitals stays one word (`GetPDFReport` → `get-pdf-report`).
|
|
59
|
+
*/
|
|
60
|
+
export declare function activityActionId(name: string): string;
|
|
61
|
+
/** The curated rows of an activities listing, in the catalog's order. */
|
|
62
|
+
export declare function curatedActivities(rows: readonly unknown[]): IsActivity[];
|
|
63
|
+
/**
|
|
64
|
+
* The curated activity an author's action names: by action id first
|
|
65
|
+
* (`create-test-set`), then by the activity's own name or display name,
|
|
66
|
+
* case-insensitively — `CreateTestSet`, `"Create Test Set"` and `create test set`
|
|
67
|
+
* all reach it. The spellings are compared both as given and in kebab case, so a
|
|
68
|
+
* display name with spaces matches whether or not the author kebab-cased it.
|
|
69
|
+
*/
|
|
70
|
+
export declare function matchActivity(activities: readonly IsActivity[], action: string): IsActivity | undefined;
|
|
71
|
+
/** The `is resources` verb an activity's HTTP method reads under; `undefined` for a verb the catalog has no describe for. */
|
|
72
|
+
export declare function describeOperationFor(method: string): string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Prepare one action from Integration Service: the entry and the node the
|
|
75
|
+
* sidecar is built from, or why not.
|
|
76
|
+
*/
|
|
77
|
+
export declare function integrationServiceEntry(ctx: BuildContext, input: {
|
|
78
|
+
connectorKey: string;
|
|
79
|
+
action: string;
|
|
80
|
+
}): Promise<IntegrationServicePrepared | {
|
|
81
|
+
miss: IntegrationServiceMiss;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* The library entry for one described activity — the shape `extractCanonical`
|
|
85
|
+
* writes from a registry node, filled from the describe instead: the IS
|
|
86
|
+
* object and verb, the path and its parameters, the request fields as the
|
|
87
|
+
* input schema. `httpMethod` keeps the catalog's spelling (`GETBYID` for a
|
|
88
|
+
* get-by-id, as registry entries do); the wire maps it. The entry goes by the
|
|
89
|
+
* activity's own action id, never the author's spelling: a display name would
|
|
90
|
+
* put spaces (or a `/`) in the overlay path and a second entry next to the id.
|
|
91
|
+
*/
|
|
92
|
+
export declare function synthesizeEntry(input: {
|
|
93
|
+
connectorKey: string;
|
|
94
|
+
activity: IsActivity;
|
|
95
|
+
operation: string;
|
|
96
|
+
described: Record<string, unknown>;
|
|
97
|
+
connector: unknown;
|
|
98
|
+
}): LibraryEntry;
|
|
99
|
+
/**
|
|
100
|
+
* The node `planLibrary` turns into the `.v1def.json` sidecar — the little a
|
|
101
|
+
* BPMN compile reads of one (`inputDefinition.fields` for the body, the model's
|
|
102
|
+
* type and service type) and the marker Flow refuses on. `buildV1Definition`
|
|
103
|
+
* keeps every key here.
|
|
104
|
+
*/
|
|
105
|
+
export declare function synthesizeNode(entry: LibraryEntry, described: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepare a connector operation the Flow node catalog does not serve, from
|
|
3
|
+
* Integration Service itself.
|
|
4
|
+
*
|
|
5
|
+
* `prepare` names an operation through `uip maestro flow registry get
|
|
6
|
+
* uipath.connector.<key>.<action>`: the registry node is the library entry's
|
|
7
|
+
* source and its `.v1def.json` sidecar. Some connectors have no node there at
|
|
8
|
+
* all, yet Integration Service serves them in full: `uip is activities list
|
|
9
|
+
* <key>` lists the curated activities with their object and HTTP method, and
|
|
10
|
+
* `uip is resources describe <key> <object> --operation <verb>` returns the
|
|
11
|
+
* path, the path/query parameters and the request and response fields. Told
|
|
12
|
+
* "op not in library", an author hand-fills `.activity('Intsvc.ActivityExecution')`
|
|
13
|
+
* tasks and gets the identity rows wrong.
|
|
14
|
+
*
|
|
15
|
+
* A BPMN connector task needs no registry node: the `sendTask` carries its own
|
|
16
|
+
* identity (`objectName`, `method`, `path`, `connectorKey`) and rows, which is
|
|
17
|
+
* exactly what the describe returns. So when the catalog serves NOTHING for the
|
|
18
|
+
* connector — not a misspelt action on a connector it does serve — this builds
|
|
19
|
+
* the library entry and a sidecar from the activity catalog and the describe,
|
|
20
|
+
* and the ordinary overlay write, descriptor regeneration and handoff follow.
|
|
21
|
+
* The sidecar is marked `synthesizedFrom: "integration-service"`: a Flow node
|
|
22
|
+
* embeds the registry's whole definition (form, handles, model), which no
|
|
23
|
+
* describe can supply, so Flow's compile and check refuse the entry with
|
|
24
|
+
* CONNECTOR_NO_FLOW_DEFINITION rather than emit a node the designer cannot open.
|
|
25
|
+
*
|
|
26
|
+
* Every `uip` call goes through the {@link BuildContext}; everything else is
|
|
27
|
+
* pure over the parsed replies.
|
|
28
|
+
*/
|
|
29
|
+
import { SCHEMA_VERSION, normalizeFieldKeys, pick } from "../library-build/canonical.js";
|
|
30
|
+
import { SYNTHESIZED_FROM_INTEGRATION_SERVICE } from "../../core/synthesized-definition.js";
|
|
31
|
+
import { isActivitiesCatalog, isConnectorGet, isDescribeOperation } from "../library-build/context.js";
|
|
32
|
+
import { HTTP_METHOD_TO_OPERATION } from "../library-build/enrich.js";
|
|
33
|
+
import { get, pyOr } from "../library-build/py.js";
|
|
34
|
+
import { isPlainObject } from "./support.js";
|
|
35
|
+
/** The marker a synthesized sidecar carries; Flow refuses a definition that has it. One owner, `core/`, so the writer and the refuser cannot drift. */
|
|
36
|
+
export { SYNTHESIZED_FROM_INTEGRATION_SERVICE };
|
|
37
|
+
/**
|
|
38
|
+
* The action id an activity goes by: its PascalCase name in kebab case —
|
|
39
|
+
* `CreateTestSet` → `create-test-set`, `AssignTestCasesToReq` →
|
|
40
|
+
* `assign-test-cases-to-req`, `CreateV2Attachment` → `create-v2-attachment`.
|
|
41
|
+
* A run of capitals stays one word (`GetPDFReport` → `get-pdf-report`).
|
|
42
|
+
*/
|
|
43
|
+
export function activityActionId(name) {
|
|
44
|
+
return name
|
|
45
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
46
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
|
|
47
|
+
.replace(/[\s_]+/g, "-")
|
|
48
|
+
.toLowerCase();
|
|
49
|
+
}
|
|
50
|
+
/** The curated rows of an activities listing, in the catalog's order. */
|
|
51
|
+
export function curatedActivities(rows) {
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const row of rows) {
|
|
54
|
+
if (!isPlainObject(row))
|
|
55
|
+
continue;
|
|
56
|
+
if (String(pyOr(get(row, "IsCurated"), "")).toLowerCase() !== "yes")
|
|
57
|
+
continue;
|
|
58
|
+
const name = String(pyOr(get(row, "Name"), "")).trim();
|
|
59
|
+
const objectName = String(pyOr(get(row, "ObjectName"), "")).trim();
|
|
60
|
+
const method = String(pyOr(get(row, "MethodName"), "")).trim().toUpperCase();
|
|
61
|
+
if (!name || !objectName || objectName === "N/A" || !method || method === "N/A")
|
|
62
|
+
continue;
|
|
63
|
+
out.push({
|
|
64
|
+
name,
|
|
65
|
+
displayName: String(pyOr(get(row, "DisplayName"), name)),
|
|
66
|
+
description: String(pyOr(get(row, "Description"), "")),
|
|
67
|
+
objectName,
|
|
68
|
+
method,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The curated activity an author's action names: by action id first
|
|
75
|
+
* (`create-test-set`), then by the activity's own name or display name,
|
|
76
|
+
* case-insensitively — `CreateTestSet`, `"Create Test Set"` and `create test set`
|
|
77
|
+
* all reach it. The spellings are compared both as given and in kebab case, so a
|
|
78
|
+
* display name with spaces matches whether or not the author kebab-cased it.
|
|
79
|
+
*/
|
|
80
|
+
export function matchActivity(activities, action) {
|
|
81
|
+
const wanted = action.trim().toLowerCase();
|
|
82
|
+
const wantedId = activityActionId(action.trim());
|
|
83
|
+
return (activities.find((a) => activityActionId(a.name) === wanted)
|
|
84
|
+
?? activities.find((a) => a.name.toLowerCase() === wanted)
|
|
85
|
+
?? activities.find((a) => a.displayName.trim().toLowerCase() === wanted)
|
|
86
|
+
?? activities.find((a) => activityActionId(a.displayName) === wantedId));
|
|
87
|
+
}
|
|
88
|
+
/** The `is resources` verb an activity's HTTP method reads under; `undefined` for a verb the catalog has no describe for. */
|
|
89
|
+
export function describeOperationFor(method) {
|
|
90
|
+
return HTTP_METHOD_TO_OPERATION.get(method.toUpperCase());
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Prepare one action from Integration Service: the entry and the node the
|
|
94
|
+
* sidecar is built from, or why not.
|
|
95
|
+
*/
|
|
96
|
+
export async function integrationServiceEntry(ctx, input) {
|
|
97
|
+
const { connectorKey, action } = input;
|
|
98
|
+
const catalog = await isActivitiesCatalog(ctx, connectorKey);
|
|
99
|
+
if ("failure" in catalog)
|
|
100
|
+
return { miss: { kind: "no-catalog", reason: catalog.failure } };
|
|
101
|
+
const activities = curatedActivities(catalog.rows);
|
|
102
|
+
if (activities.length === 0)
|
|
103
|
+
return { miss: { kind: "no-catalog" } };
|
|
104
|
+
const activity = matchActivity(activities, action);
|
|
105
|
+
if (!activity)
|
|
106
|
+
return { miss: { kind: "no-such-activity", activities: activities.map((a) => activityActionId(a.name)) } };
|
|
107
|
+
// No guess for a method the table does not list: a describe read under the
|
|
108
|
+
// wrong verb returns another operation's path, and the entry would target it.
|
|
109
|
+
const operation = describeOperationFor(activity.method);
|
|
110
|
+
if (operation === undefined)
|
|
111
|
+
return { miss: { kind: "no-operation", activity } };
|
|
112
|
+
const described = await isDescribeOperation(ctx, connectorKey, activity.objectName, operation);
|
|
113
|
+
if (!isPlainObject(described))
|
|
114
|
+
return { miss: { kind: "no-schema", activity, operation } };
|
|
115
|
+
const connector = await isConnectorGet(ctx, connectorKey);
|
|
116
|
+
const entry = synthesizeEntry({ connectorKey, activity, operation, described, connector });
|
|
117
|
+
return { entry, action: activityActionId(activity.name), node: synthesizeNode(entry, described), activity, operation };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The library entry for one described activity — the shape `extractCanonical`
|
|
121
|
+
* writes from a registry node, filled from the describe instead: the IS
|
|
122
|
+
* object and verb, the path and its parameters, the request fields as the
|
|
123
|
+
* input schema. `httpMethod` keeps the catalog's spelling (`GETBYID` for a
|
|
124
|
+
* get-by-id, as registry entries do); the wire maps it. The entry goes by the
|
|
125
|
+
* activity's own action id, never the author's spelling: a display name would
|
|
126
|
+
* put spaces (or a `/`) in the overlay path and a second entry next to the id.
|
|
127
|
+
*/
|
|
128
|
+
export function synthesizeEntry(input) {
|
|
129
|
+
const { connectorKey, activity, operation, described, connector } = input;
|
|
130
|
+
const op = pyOr(pick(described, "Operation", "operation"), {});
|
|
131
|
+
const path = String(pyOr(pick(op, "Path", "path"), ""));
|
|
132
|
+
const method = String(pyOr(pick(op, "Method", "method"), activity.method)).toUpperCase();
|
|
133
|
+
const label = String(pyOr(pick(op, "Curated", "curated"), activity.displayName));
|
|
134
|
+
const parameters = normalizeFieldKeys(pyOr(pick(described, "Parameters", "parameters"), []));
|
|
135
|
+
const requestFields = normalizeFieldKeys(pyOr(pick(described, "RequestFields", "requestFields"), []));
|
|
136
|
+
const responseFields = normalizeFieldKeys(pyOr(pick(described, "ResponseFields", "responseFields"), []));
|
|
137
|
+
const connectorName = String(pyOr(get(connector, "Name"), connectorKey));
|
|
138
|
+
const icon = String(pyOr(get(connector, "Image"), ""));
|
|
139
|
+
const entry = {
|
|
140
|
+
schemaVersion: SCHEMA_VERSION,
|
|
141
|
+
nodeType: `uipath.connector.${connectorKey}.${activityActionId(activity.name)}`,
|
|
142
|
+
version: "1.0.0",
|
|
143
|
+
category: "connector",
|
|
144
|
+
tags: ["connector", "activity"],
|
|
145
|
+
connector: { key: connectorKey, name: connectorName },
|
|
146
|
+
operation: {
|
|
147
|
+
name: operation,
|
|
148
|
+
objectName: activity.objectName,
|
|
149
|
+
objectDisplayName: String(pyOr(pick(described, "DisplayName", "displayName"), activity.objectName)),
|
|
150
|
+
httpMethod: method,
|
|
151
|
+
path,
|
|
152
|
+
pathTemplate: path,
|
|
153
|
+
subType: "standard",
|
|
154
|
+
supportsStreaming: false,
|
|
155
|
+
activityType: "Curated",
|
|
156
|
+
parameters,
|
|
157
|
+
},
|
|
158
|
+
display: {
|
|
159
|
+
label,
|
|
160
|
+
description: activity.description || `(${connectorName}) ${label}`,
|
|
161
|
+
icon,
|
|
162
|
+
iconBackground: "",
|
|
163
|
+
iconBackgroundDark: "",
|
|
164
|
+
operationLabel: label,
|
|
165
|
+
},
|
|
166
|
+
runtime: {
|
|
167
|
+
bpmnType: "bpmn:SendTask",
|
|
168
|
+
serviceType: "Intsvc.ActivityExecution",
|
|
169
|
+
activityConfigurationVersion: "v1",
|
|
170
|
+
requiresConnection: true,
|
|
171
|
+
requiresFolderKey: true,
|
|
172
|
+
requiresConnectionForSchema: true,
|
|
173
|
+
},
|
|
174
|
+
inputSchema: { fields: requestFields },
|
|
175
|
+
outputSchema: { fields: responseFields },
|
|
176
|
+
provenance: {
|
|
177
|
+
source: SYNTHESIZED_FROM_INTEGRATION_SERVICE,
|
|
178
|
+
activity: activity.name,
|
|
179
|
+
method: activity.method,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
return entry;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The node `planLibrary` turns into the `.v1def.json` sidecar — the little a
|
|
186
|
+
* BPMN compile reads of one (`inputDefinition.fields` for the body, the model's
|
|
187
|
+
* type and service type) and the marker Flow refuses on. `buildV1Definition`
|
|
188
|
+
* keeps every key here.
|
|
189
|
+
*/
|
|
190
|
+
export function synthesizeNode(entry, described) {
|
|
191
|
+
const e = entry;
|
|
192
|
+
const outputFields = normalizeFieldKeys(pyOr(pick(described, "ResponseFields", "responseFields"), []));
|
|
193
|
+
return {
|
|
194
|
+
nodeType: e.nodeType,
|
|
195
|
+
version: e.version,
|
|
196
|
+
category: e.category,
|
|
197
|
+
tags: e.tags,
|
|
198
|
+
display: { label: e.display.label, description: e.display.description, icon: e.display.icon },
|
|
199
|
+
model: {
|
|
200
|
+
type: "bpmn:SendTask",
|
|
201
|
+
serviceType: "Intsvc.ActivityExecution",
|
|
202
|
+
bindings: { resource: "connection", propertyAttribute: "ConnectionId" },
|
|
203
|
+
context: [
|
|
204
|
+
{ name: "connectorKey", value: e.connector.key },
|
|
205
|
+
{ name: "objectName", value: e.operation.objectName },
|
|
206
|
+
{ name: "operation", value: e.operation.name },
|
|
207
|
+
{ name: "method", value: e.operation.httpMethod },
|
|
208
|
+
{ name: "path", value: e.operation.path },
|
|
209
|
+
{ name: "connection", value: "" },
|
|
210
|
+
{ name: "folderKey", value: "" },
|
|
211
|
+
{ name: "activityConfigurationVersion", value: "v1" },
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
inputDefinition: { fields: e.inputSchema.fields },
|
|
215
|
+
outputDefinition: { output: { fields: outputFields }, error: {} },
|
|
216
|
+
synthesizedFrom: SYNTHESIZED_FROM_INTEGRATION_SERVICE,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
* `core/cli-spelling.ts`) without this module reading the environment.
|
|
22
22
|
*/
|
|
23
23
|
import { Refusal } from "../errors.js";
|
|
24
|
-
import { type RegistryCatalog } from "./actions.js";
|
|
24
|
+
import { type RegistryCatalog, type RegistryRefresh } from "./actions.js";
|
|
25
|
+
import type { IntegrationServiceMiss } from "./integration-service.js";
|
|
25
26
|
export declare const PREFIX = "prepare-connector: ";
|
|
26
27
|
/** The spelling the Python hard-coded into every pasteable re-run. */
|
|
27
28
|
export declare const PREPARE_COMMAND = "npx flow-sdk registry prepare";
|
|
@@ -101,6 +102,10 @@ export declare function unresolvedOperationMessage(input: {
|
|
|
101
102
|
name: string;
|
|
102
103
|
probes: readonly ProbeMiss[];
|
|
103
104
|
catalog: RegistryCatalog;
|
|
105
|
+
/** What Integration Service said, when the catalog served nothing for the connector and IS was asked instead. */
|
|
106
|
+
integrationService?: IntegrationServiceMiss;
|
|
107
|
+
/** Whether the catalog was refreshed before that conclusion. */
|
|
108
|
+
registryRefresh?: RegistryRefresh;
|
|
104
109
|
}): string;
|
|
105
110
|
/**
|
|
106
111
|
* A `registry get` probe failed for a reason other than "Node not found" — an
|
|
@@ -133,6 +138,41 @@ export declare function inputFieldNote(count: number | undefined | null): string
|
|
|
133
138
|
export declare function tsNote(jsonOnly: boolean, connectorKey: string): string;
|
|
134
139
|
/** The action path's success line. */
|
|
135
140
|
export declare function preparedSummaryLine(nodeType: string, out: string, ts: string, variants: number, fieldNote: string): string;
|
|
141
|
+
/**
|
|
142
|
+
* "The catalog serves no node for <key>", worded by what was measured: a fact
|
|
143
|
+
* after a forced pull, the local copy's answer when the pull failed or did not
|
|
144
|
+
* run (`registry get` and `registry search` answer from a copy that never
|
|
145
|
+
* refreshes itself).
|
|
146
|
+
*/
|
|
147
|
+
export declare function catalogServesNothingPhrase(key: string, refresh: RegistryRefresh | undefined): string;
|
|
148
|
+
/** The progress line before the one `registry pull --force` the fallback runs. */
|
|
149
|
+
export declare function refreshingCatalogLine(key: string): string;
|
|
150
|
+
/** The warning when that pull failed; the fallback continues against the local copy. */
|
|
151
|
+
export declare function catalogRefreshFailedLine(reason: string): string;
|
|
152
|
+
/** The note when the author's `--action` spelling was not the entry's id. */
|
|
153
|
+
export declare function actionCanonicalizedLine(given: string, actionId: string): string;
|
|
154
|
+
/**
|
|
155
|
+
* What Integration Service serves when the Flow catalog serves nothing for a
|
|
156
|
+
* connector: the curated activities' action ids, so the author re-runs with
|
|
157
|
+
* one of them, or why the one they named could not be read.
|
|
158
|
+
*/
|
|
159
|
+
export declare function integrationServiceLines(key: string, name: string, miss: IntegrationServiceMiss, refresh?: RegistryRefresh): string[];
|
|
160
|
+
/** The action path's source line when Integration Service supplied the entry. */
|
|
161
|
+
export declare function preparedFromIntegrationServiceLine(key: string, activity: {
|
|
162
|
+
name: string;
|
|
163
|
+
objectName: string;
|
|
164
|
+
method: string;
|
|
165
|
+
}, refresh?: RegistryRefresh): string;
|
|
166
|
+
/**
|
|
167
|
+
* The pasteable BPMN call for an operation prepared from Integration Service —
|
|
168
|
+
* the only family that compiles it, so the Flow import handoff is not printed
|
|
169
|
+
* for it. Required inputs are named with their type; the rest are left to the
|
|
170
|
+
* descriptor the overlay carries.
|
|
171
|
+
*/
|
|
172
|
+
export declare function bpmnConnectorUsageLines(connectorKey: string, action: string, required: ReadonlyArray<{
|
|
173
|
+
name: string;
|
|
174
|
+
type: string;
|
|
175
|
+
}>, boundConnection?: string, boundFolder?: string): string[];
|
|
136
176
|
/** Why this run did not overwrite the overlay (`_keep_prepared_schema`). */
|
|
137
177
|
export declare function keptSchemaMessage(action: string, kept: number, discarded: number): string;
|
|
138
178
|
/** The set was SHAPED right but resolved to nothing new: a VALUE is wrong for this connection. */
|