@uipath/maestro-builder-sdk 6.45.2 → 6.46.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bpmn/uipath-moddle.v1.json +5 -0
- package/dist/case/case-expr-check.js +10 -1
- package/dist/compile-cli.js +12 -2
- package/dist/core/read-text.d.ts +4 -0
- package/dist/core/read-text.js +4 -0
- package/dist/registry/library-build/context.js +2 -2
- package/dist/registry/prepare/connections.js +2 -1
- package/dist/registry/prepare/handoff.js +1 -1
- package/dist/registry/prepare/messages.d.ts +9 -0
- package/dist/registry/prepare/messages.js +18 -0
- package/dist/registry/prepare/objects.js +1 -1
- package/dist/registry/prepare/overlay.d.ts +8 -2
- package/dist/registry/prepare/overlay.js +76 -22
- package/dist/registry/prepare/pipeline.d.ts +7 -4
- package/dist/registry/prepare/pipeline.js +19 -8
- package/dist/registry/prepare/regenerate.js +2 -2
- package/dist/registry/prepare/source-import.js +5 -3
- package/dist/registry/prepare/staged-files.d.ts +21 -0
- package/dist/registry/prepare/staged-files.js +110 -0
- package/dist/registry/uip/client.d.ts +18 -0
- package/dist/registry/uip/client.js +80 -0
- package/package.json +2 -2
|
@@ -39,9 +39,18 @@ export function checkCaseExpressions(built) {
|
|
|
39
39
|
// task output is not flagged as an undeclared reference.
|
|
40
40
|
for (const stage of built.stages)
|
|
41
41
|
for (const lane of stage.lanes)
|
|
42
|
-
for (const task of lane)
|
|
42
|
+
for (const task of lane) {
|
|
43
43
|
for (const o of task.outputs ?? [])
|
|
44
44
|
roots.add(o.var);
|
|
45
|
+
// A human task's form outputs declare one too: the field's `value` is the
|
|
46
|
+
// variable the assignee's answer lands in (`.action({ outputs: [{ name:
|
|
47
|
+
// 'Action', value: 'decision' }] })` → `vars.decision`). The serializer
|
|
48
|
+
// registers exactly that companion (`emitActionField`), so a gate on it
|
|
49
|
+
// is a declared read, not a typo.
|
|
50
|
+
for (const f of task.action?.outputs ?? [])
|
|
51
|
+
if (f.value)
|
|
52
|
+
roots.add(f.value);
|
|
53
|
+
}
|
|
45
54
|
// Event-trigger payload extractions declare readable case variables too.
|
|
46
55
|
for (const t of built.triggers)
|
|
47
56
|
for (const o of t.eventOutputs ?? [])
|
package/dist/compile-cli.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { reportDiagnostics, reportResult, thrownDiagnostic } from './cli-result.js';
|
|
21
21
|
import { writeJsonFile } from './core/json-file.js';
|
|
22
22
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
23
|
-
import { basename, dirname, join, resolve } from 'node:path';
|
|
23
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
24
24
|
import { resolveFlowFile, loadBuiltFlow } from './load.js';
|
|
25
25
|
import { serializeWithSidecars } from './serialize.js';
|
|
26
26
|
import { compileOwnsResource } from './core/agent-tool-resources.js';
|
|
@@ -272,8 +272,18 @@ export async function run(argv) {
|
|
|
272
272
|
}
|
|
273
273
|
if (!compileOwnsResource(body))
|
|
274
274
|
continue;
|
|
275
|
+
try {
|
|
276
|
+
rmSync(join(resourcesDir, entry), { recursive: true, force: true });
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
// Studio Web's project file system refuses deletes. Only a surviving file keeps its row: a later compile skips a folder with no resource.json.
|
|
280
|
+
if (existsSync(file)) {
|
|
281
|
+
console.error(`compile: could not remove stale ${relative(outDir, file)} (${error instanceof Error ? error.message : String(error)}); `
|
|
282
|
+
+ 'it is still declared as a resource of this agent until it is deleted.');
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
275
286
|
removedResources.push(body);
|
|
276
|
-
rmSync(join(resourcesDir, entry), { recursive: true, force: true });
|
|
277
287
|
console.log(`compile: removed stale ${join('resources', entry, 'resource.json')}`);
|
|
278
288
|
}
|
|
279
289
|
}
|
package/dist/core/read-text.d.ts
CHANGED
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
* `process.getBuiltinModule`, not an `import`, so the library loads no `node:` builtin
|
|
22
22
|
* (FB015 exempts this fallback), and `process` is read through `globalThis`, as in
|
|
23
23
|
* `core/typescript-module.ts`.
|
|
24
|
+
*
|
|
25
|
+
* The tenant calls (`registry prepare`'s `uip` runs) have a sibling slot,
|
|
26
|
+
* `globalThis[Symbol.for('uipath.maestro-builder-sdk.uip')]`, documented on
|
|
27
|
+
* `defaultUipClient` in `registry/uip/client.ts`.
|
|
24
28
|
*/
|
|
25
29
|
/** The file's text, or `undefined` when there is none. */
|
|
26
30
|
export type ReadText = (path: string) => string | undefined;
|
package/dist/core/read-text.js
CHANGED
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
* `process.getBuiltinModule`, not an `import`, so the library loads no `node:` builtin
|
|
22
22
|
* (FB015 exempts this fallback), and `process` is read through `globalThis`, as in
|
|
23
23
|
* `core/typescript-module.ts`.
|
|
24
|
+
*
|
|
25
|
+
* The tenant calls (`registry prepare`'s `uip` runs) have a sibling slot,
|
|
26
|
+
* `globalThis[Symbol.for('uipath.maestro-builder-sdk.uip')]`, documented on
|
|
27
|
+
* `defaultUipClient` in `registry/uip/client.ts`.
|
|
24
28
|
*/
|
|
25
29
|
const HOST_READ_TEXT = Symbol.for('uipath.maestro-builder-sdk.readText');
|
|
26
30
|
/** The reader a default read uses: the host's, when one is in the slot, else the disk. */
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* a `--keep-temp` cache the Python wrote drives this code to the same bytes,
|
|
14
14
|
* which is how the port is proven.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import { bytesToHex, sha256 } from "../../core/sha.js";
|
|
17
17
|
import { pyJsonStringify } from "../codecs/pyjson.js";
|
|
18
18
|
import { sortedByKeys } from "../compat/compare.js";
|
|
19
19
|
import { isActivitiesList as isActivitiesListArgv, isConnectorsMetadata, isResourcesDescribeObject, isResourcesDescribeOperation, isResourcesList as isResourcesListArgv, registryGet as registryGetArgv, } from "../uip/commands.js";
|
|
@@ -47,7 +47,7 @@ export function fieldScope(parentFields) {
|
|
|
47
47
|
return "";
|
|
48
48
|
const pairs = sortedByKeys([...parentFields.entries()], ([k]) => k, ([, v]) => v);
|
|
49
49
|
const flat = pairs.map(([k, v]) => `${k}=${v}`).join("\0");
|
|
50
|
-
return `f-${
|
|
50
|
+
return `f-${bytesToHex(sha256(flat)).slice(0, 12)}`;
|
|
51
51
|
}
|
|
52
52
|
/** Cache file for one `flow registry get`: the node type with `.` → `_`. */
|
|
53
53
|
export function registryCacheName(nodeType) {
|
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
* a {@link UipClient} and hand the rows to them. Every refusal's text is kept
|
|
22
22
|
* verbatim — agents read these messages and follow them.
|
|
23
23
|
*/
|
|
24
|
-
import {
|
|
24
|
+
import { readdirSync } from "node:fs";
|
|
25
25
|
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
26
26
|
import { pyJsonStringify } from "../codecs/pyjson.js";
|
|
27
27
|
import { Refusal, messageOf } from "../errors.js";
|
|
28
28
|
import { isConnectionsList } from "../uip/commands.js";
|
|
29
29
|
import { dataItems, failureReason, parseEnvelope } from "../uip/envelope.js";
|
|
30
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "./staged-files.js";
|
|
30
31
|
import { isPlainObject, notJsonReason, pyGet, pyOr, pyRepr, pyStrAny, pyTruthy } from "./support.js";
|
|
31
32
|
/** No usable connection, with the reason and the way forward (`_connections.ConnectionError_`). */
|
|
32
33
|
export class ConnectionRefusal extends Refusal {
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
* `_unwrap_value` in `prepare_connector.py`. The text lives in `messages.ts`;
|
|
14
14
|
* this module reads the generated module and parses it.
|
|
15
15
|
*/
|
|
16
|
-
import { readFileSync } from "node:fs";
|
|
17
16
|
import { join, resolve } from "node:path";
|
|
18
17
|
import { relativePosix, toPosix } from "../compat/paths.js";
|
|
19
18
|
import { pyStrip } from "../compat/pystr.js";
|
|
20
19
|
import { isRelativeSpecifier } from "./source-import.js";
|
|
21
20
|
import { descriptorUsageFallbackLines, descriptorUsageLines, descriptorUsageUnparsedLines } from "./messages.js";
|
|
21
|
+
import { readFileSync } from "./staged-files.js";
|
|
22
22
|
/**
|
|
23
23
|
* `ConnectorValue<string>` → `string`, for the handoff skeleton. The generated
|
|
24
24
|
* property type says what the field ACCEPTS — the literal or an expression —
|
|
@@ -30,6 +30,15 @@ export declare const PREPARE_COMMAND = "npx flow-sdk registry prepare";
|
|
|
30
30
|
export declare function prefixed(text: string): string;
|
|
31
31
|
/** `prepare-connector: next: <text>` — the last lines of a run, each one a call the reader can paste. */
|
|
32
32
|
export declare function nextLine(text: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* The run's last line: the `registry describe` that prints the page just
|
|
35
|
+
* prepared. `overlay` is `--out` when it is not the default describe reads.
|
|
36
|
+
*/
|
|
37
|
+
export declare function describeNextLine(cli: string, nodeType: string, objectName?: string, overlay?: string): string;
|
|
38
|
+
/** The line a refusal gains when the run had already staged files, which its progress lines may have named. */
|
|
39
|
+
export declare function discardedNote(count: number): string;
|
|
40
|
+
/** A write failed after the run succeeded, so the files written before it remain. */
|
|
41
|
+
export declare function partialWriteMessage(path: string, reason: string): string;
|
|
33
42
|
/**
|
|
34
43
|
* A refusal whose message is the COMPLETE text the Python passed to
|
|
35
44
|
* `sys.exit`, prefix included. Every other {@link Refusal} reaching the exit
|
|
@@ -36,6 +36,24 @@ export function prefixed(text) {
|
|
|
36
36
|
export function nextLine(text) {
|
|
37
37
|
return prefixed(`next: ${text}`);
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The run's last line: the `registry describe` that prints the page just
|
|
41
|
+
* prepared. `overlay` is `--out` when it is not the default describe reads.
|
|
42
|
+
*/
|
|
43
|
+
export function describeNextLine(cli, nodeType, objectName, overlay) {
|
|
44
|
+
const object = objectName ? ` --object ${objectName}` : "";
|
|
45
|
+
const local = overlay ? ` --connectors-local ${overlay}` : "";
|
|
46
|
+
return nextLine(`read the prepared page: ${cli} registry describe ${nodeType}${object}${local}`);
|
|
47
|
+
}
|
|
48
|
+
/** The line a refusal gains when the run had already staged files, which its progress lines may have named. */
|
|
49
|
+
export function discardedNote(count) {
|
|
50
|
+
return `Nothing was written: the ${count} file(s) this run staged, including any reported above, were discarded.`;
|
|
51
|
+
}
|
|
52
|
+
/** A write failed after the run succeeded, so the files written before it remain. */
|
|
53
|
+
export function partialWriteMessage(path, reason) {
|
|
54
|
+
return prefixed(`could not write ${path} (${reason}). The files written before it remain, so the overlay or bindings.json `
|
|
55
|
+
+ "may be partly updated; fix the cause and run the same prepare again.");
|
|
56
|
+
}
|
|
39
57
|
/**
|
|
40
58
|
* A refusal whose message is the COMPLETE text the Python passed to
|
|
41
59
|
* `sys.exit`, prefix included. Every other {@link Refusal} reaching the exit
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
*
|
|
32
32
|
* Ported from `integrations/scripts/_objects.py`; the refusal text is verbatim.
|
|
33
33
|
*/
|
|
34
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
35
34
|
import { dirname, join } from "node:path";
|
|
36
35
|
import { pyJsonStringify } from "../codecs/pyjson.js";
|
|
37
36
|
import { pyStrip } from "../compat/pystr.js";
|
|
38
37
|
import { Refusal, messageOf } from "../errors.js";
|
|
38
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "./staged-files.js";
|
|
39
39
|
import { isPlainObject, pyOr, pyRepr, pySplitWhitespace, pyStrAny, pyTruthy, pyTypeName } from "./support.js";
|
|
40
40
|
/**
|
|
41
41
|
* Enough rows to choose from without dumping a catalog into the error. The
|
|
@@ -10,8 +10,14 @@ export declare function variantKey(entry: Record<string, unknown>): string;
|
|
|
10
10
|
export declare function descriptorOverlayPath(path: string): string;
|
|
11
11
|
/**
|
|
12
12
|
* `_migrate_legacy_descriptor_roots`: move `<dst>/<key>/**` written by an
|
|
13
|
-
* earlier `prepare` under `<dst>/descriptors/<key>/**`,
|
|
14
|
-
*
|
|
13
|
+
* earlier `prepare` under `<dst>/descriptors/<key>/**`, leaving no empty
|
|
14
|
+
* directory behind. Only roots the index still names are touched.
|
|
15
|
+
*
|
|
16
|
+
* The files are copied with the run's other writes, and only once those are
|
|
17
|
+
* written is each old file renamed over its copy and its emptied folder
|
|
18
|
+
* removed, so a refused run moves nothing. Where the file system refuses
|
|
19
|
+
* renames or deletes (Studio Web), the old files stay beside their copies;
|
|
20
|
+
* once the index names `descriptors/`, nothing reads them.
|
|
15
21
|
*/
|
|
16
22
|
export declare function migrateLegacyDescriptorRoots(dst: string, entries: readonly unknown[]): void;
|
|
17
23
|
/**
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* `_keep_prepared_schema` and `_lookup_specs_from_overlay` in
|
|
21
21
|
* `prepare_connector.py`.
|
|
22
22
|
*/
|
|
23
|
-
import {
|
|
24
|
-
import { basename, dirname, join } from "node:path";
|
|
23
|
+
import { lstatSync, readdirSync, renameSync, rmdirSync } from "node:fs";
|
|
24
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
25
25
|
import { pyJsonDocument } from "../codecs/pyjson.js";
|
|
26
26
|
import { sortedByKeys, sortedStrings } from "../compat/compare.js";
|
|
27
27
|
import { toPosix } from "../compat/paths.js";
|
|
@@ -30,6 +30,7 @@ import { SCHEMA_VERSION } from "../library-build/canonical.js";
|
|
|
30
30
|
import { sidecarPath, unwrapV1Def } from "../library-reader.js";
|
|
31
31
|
import { catalogReferences, loadInvariantCollections, specsFromFields } from "../lookup-specs.js";
|
|
32
32
|
import { isEventNodeType } from "../model/library.js";
|
|
33
|
+
import { afterWrites, existsSync, mkdirSync, readFileSync, stagedReadText, writeFileSync } from "./staged-files.js";
|
|
33
34
|
import { isPlainObject, pyGet, pyOr, pyStrAny, pyTruthy, pyTypeName } from "./support.js";
|
|
34
35
|
export const INDEX_FILENAME = "index.json";
|
|
35
36
|
export const DESCRIPTORS_ROOT = "descriptors";
|
|
@@ -55,8 +56,14 @@ export function descriptorOverlayPath(path) {
|
|
|
55
56
|
}
|
|
56
57
|
/**
|
|
57
58
|
* `_migrate_legacy_descriptor_roots`: move `<dst>/<key>/**` written by an
|
|
58
|
-
* earlier `prepare` under `<dst>/descriptors/<key>/**`,
|
|
59
|
-
*
|
|
59
|
+
* earlier `prepare` under `<dst>/descriptors/<key>/**`, leaving no empty
|
|
60
|
+
* directory behind. Only roots the index still names are touched.
|
|
61
|
+
*
|
|
62
|
+
* The files are copied with the run's other writes, and only once those are
|
|
63
|
+
* written is each old file renamed over its copy and its emptied folder
|
|
64
|
+
* removed, so a refused run moves nothing. Where the file system refuses
|
|
65
|
+
* renames or deletes (Studio Web), the old files stay beside their copies;
|
|
66
|
+
* once the index names `descriptors/`, nothing reads them.
|
|
60
67
|
*/
|
|
61
68
|
export function migrateLegacyDescriptorRoots(dst, entries) {
|
|
62
69
|
const legacyRoots = new Set();
|
|
@@ -71,27 +78,74 @@ export function migrateLegacyDescriptorRoots(dst, entries) {
|
|
|
71
78
|
legacyRoots.add(parts[0]);
|
|
72
79
|
}
|
|
73
80
|
for (const legacyRoot of sortedStrings(legacyRoots)) {
|
|
74
|
-
const sourceRoot =
|
|
75
|
-
if (
|
|
81
|
+
const sourceRoot = legacyRootDir(dst, legacyRoot);
|
|
82
|
+
if (sourceRoot === undefined)
|
|
76
83
|
continue;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const subdirs = children.filter((c) => c.isDirectory()).map((c) => c.name);
|
|
81
|
-
// `os.walk(topdown=False)`: the children are handled before their parent.
|
|
82
|
-
for (const name of subdirs)
|
|
83
|
-
walk(join(dir, name));
|
|
84
|
-
const destination = join(destinationRoot, toPosix(dir.slice(sourceRoot.length)).replace(/^\//, ""));
|
|
84
|
+
const moved = [];
|
|
85
|
+
// Dirent types do not follow symlinks, so a linked file or folder is neither and is skipped.
|
|
86
|
+
const walk = (dir, destination) => {
|
|
85
87
|
mkdirSync(destination, { recursive: true });
|
|
86
|
-
for (const child of
|
|
87
|
-
if (child.
|
|
88
|
-
|
|
88
|
+
for (const child of readdirSync(dir, { withFileTypes: true })) {
|
|
89
|
+
if (child.isDirectory())
|
|
90
|
+
walk(join(dir, child.name), join(destination, child.name));
|
|
91
|
+
else if (child.isFile()) {
|
|
92
|
+
writeFileSync(join(destination, child.name), readFileSync(join(dir, child.name)));
|
|
93
|
+
moved.push([join(dir, child.name), join(destination, child.name)]);
|
|
94
|
+
}
|
|
89
95
|
}
|
|
90
|
-
for (const name of subdirs)
|
|
91
|
-
rmdirSync(join(dir, name));
|
|
92
96
|
};
|
|
93
|
-
walk(sourceRoot);
|
|
94
|
-
|
|
97
|
+
walk(sourceRoot, join(dst, DESCRIPTORS_ROOT, legacyRoot));
|
|
98
|
+
afterWrites(() => moveOver(sourceRoot, moved));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** A connector key as the legacy layout named its folder: one plain path segment. */
|
|
102
|
+
const LEGACY_ROOT = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
103
|
+
/**
|
|
104
|
+
* `<dst>/<legacyRoot>`, or `undefined` unless it is a real folder strictly
|
|
105
|
+
* inside `dst`. The name comes from `index.json`, a project file anyone with
|
|
106
|
+
* the project can edit, so `..` or a link must not reach the files beside the
|
|
107
|
+
* overlay.
|
|
108
|
+
*/
|
|
109
|
+
function legacyRootDir(dst, legacyRoot) {
|
|
110
|
+
if (!LEGACY_ROOT.test(legacyRoot))
|
|
111
|
+
return undefined;
|
|
112
|
+
const root = resolve(dst, legacyRoot);
|
|
113
|
+
const inside = relative(resolve(dst), root);
|
|
114
|
+
if (inside === "" || inside.startsWith("..") || isAbsolute(inside))
|
|
115
|
+
return undefined;
|
|
116
|
+
try {
|
|
117
|
+
const stat = lstatSync(root);
|
|
118
|
+
return stat.isDirectory() && !stat.isSymbolicLink() ? root : undefined;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Rename each copied file over its copy, then remove the folders between it
|
|
126
|
+
* and `root` once empty, deepest first. Only these files and folders: anything
|
|
127
|
+
* added to the old tree since the copy stays where it is.
|
|
128
|
+
*/
|
|
129
|
+
function moveOver(root, moved) {
|
|
130
|
+
try {
|
|
131
|
+
for (const [source, destination] of moved)
|
|
132
|
+
renameSync(source, destination);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return; // refused (Studio Web): the old files stay beside their copies
|
|
136
|
+
}
|
|
137
|
+
const folders = new Set();
|
|
138
|
+
for (const [source] of moved) {
|
|
139
|
+
for (let folder = dirname(source); folder.startsWith(root); folder = dirname(folder))
|
|
140
|
+
folders.add(folder);
|
|
141
|
+
}
|
|
142
|
+
for (const folder of [...folders].sort((a, b) => b.length - a.length)) {
|
|
143
|
+
try {
|
|
144
|
+
rmdirSync(folder);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// not empty, or refused: it stays
|
|
148
|
+
}
|
|
95
149
|
}
|
|
96
150
|
}
|
|
97
151
|
function sortKey(entry, key) {
|
|
@@ -463,7 +517,7 @@ export function lookupSpecsFromOverlay(out, nodeType, objectName) {
|
|
|
463
517
|
const entries = isPlainObject(index) ? pyGet(index, "entries", []) : [];
|
|
464
518
|
if (!Array.isArray(entries))
|
|
465
519
|
return new Map();
|
|
466
|
-
const invariant = loadInvariantCollections(out);
|
|
520
|
+
const invariant = loadInvariantCollections(out, stagedReadText());
|
|
467
521
|
for (const row of entries) {
|
|
468
522
|
if (!isPlainObject(row) || row.nodeType !== nodeType)
|
|
469
523
|
continue;
|
|
@@ -53,8 +53,8 @@ export interface PreparePorts {
|
|
|
53
53
|
/** The project root the Python read as `Path.cwd()`. Defaults to the process's. */
|
|
54
54
|
cwd?: string;
|
|
55
55
|
timeoutMs?: number;
|
|
56
|
-
/** How the
|
|
57
|
-
|
|
56
|
+
/** How the CLI is spelled in the pasteable commands; defaults to {@link cliSpelling}. */
|
|
57
|
+
cli?: string;
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
60
|
* Where prepare writes its overlay when `--out` is not given, as shown to the
|
|
@@ -102,10 +102,13 @@ export declare function matchCatalogObject(ctx: BuildContext, connectorKey: stri
|
|
|
102
102
|
* command PICKED and finds nothing, the other enabled connections for the
|
|
103
103
|
* connector are probed, and the run starts over pinned to the one that
|
|
104
104
|
* resolves everything (flow-builder-sdk#744). The Python re-exec'd itself for
|
|
105
|
-
* that; here the bindings snapshot is restored and
|
|
106
|
-
*
|
|
105
|
+
* that; here the bindings snapshot is restored and the run starts over, inside
|
|
106
|
+
* the same held writes, with `--connection-id <winner> --refresh-lookups`. The refresh is
|
|
107
107
|
* NOT optional: the ledger keys a resolution by `[nodeType, field, by, value]`
|
|
108
108
|
* and not by connection, so an id recorded against the connection just
|
|
109
109
|
* abandoned would come back as "already resolved" on the re-run.
|
|
110
|
+
*
|
|
111
|
+
* Nothing reaches the disk until the whole command, re-run included, has
|
|
112
|
+
* succeeded; a refusal writes nothing (`staged-files.ts`).
|
|
110
113
|
*/
|
|
111
114
|
export declare function prepareConnector(options: PrepareOptions, ports: PreparePorts): Promise<PrepareReport>;
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
* into the lines verbatim, as `args.out` and `args.bindings` did — while the
|
|
25
25
|
* file system is reached through paths resolved against `cwd`.
|
|
26
26
|
*/
|
|
27
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
28
27
|
import { relative, resolve } from "node:path";
|
|
29
28
|
import { GUID_RE } from "../../core/binding-messages.js";
|
|
29
|
+
import { cliSpelling } from "../../core/cli-spelling.js";
|
|
30
30
|
import { PACKAGE_NAME } from "../../package-name.js";
|
|
31
31
|
import { afterLast } from "../compat/pystr.js";
|
|
32
32
|
import { extractCanonical, isHttpPassthrough, nodeOf } from "../library-build/canonical.js";
|
|
@@ -41,7 +41,7 @@ import { prepareEvent } from "./event.js";
|
|
|
41
41
|
import { integrationServiceEntry } from "./integration-service.js";
|
|
42
42
|
import { descriptorUsage } from "./handoff.js";
|
|
43
43
|
import { ResolutionsExhausted, allParentsResolved, resolvedParentValues, writeResolutions } from "./ledger.js";
|
|
44
|
-
import {
|
|
44
|
+
import { PrepareRefusal, ambiguousLabelsMessage, connectionChoiceLine, bindingsLine, bpmnConnectorUsageLines, candidateLabel, connectionLine, describeNextLine, generatedModulesLine, genericActionNeedsObjectMessage, inputFieldNote, flagDiffersFromSourceLine, keptSchemaMessage, labelTakenMessage, lookupVocabularyLines, missingGenericSchemaMessage, missingParentsNote, nextLine, noCanonicalEntryMessage, objectFlagsOnlyForGenericMessage, objectNoteLine, objectRecordedLine, parentFieldNamesMismatchMessage, parentOnlyMessage, prefixed, preparedFromIntegrationServiceLine, actionCanonicalizedLine, catalogRefreshFailedLine, refreshingCatalogLine, preparedSummaryLine, rejectedParentFieldsMessage, rerunAgainstLine, resolvedParentsNote, registryGetFailedMessage, sharedLabelMessage, sourceLabelSuffix, tsNote, partlyUnreadableLabelLine, unreadableLabelLine, unresolvedOperationMessage, } from "./messages.js";
|
|
45
45
|
import { ObjectMatchRefusal, RESOLUTIONS_FILENAME, matchObject, recordObjectResolution } from "./objects.js";
|
|
46
46
|
import { countInputFields, keepPreparedSchema, lookupSpecsFromOverlay, mergeFilesInto, parentFields as overlayParentFields, schemaActionParents, } from "./overlay.js";
|
|
47
47
|
import { declaredParents, objectActionRequest, objectActionSpecs, parseFields } from "./parents.js";
|
|
@@ -50,6 +50,7 @@ import { retryOtherConnections } from "./retry.js";
|
|
|
50
50
|
import { repointSource } from "./source-import.js";
|
|
51
51
|
import { sourceBindingLabels } from "./source-labels.js";
|
|
52
52
|
import { isPlainObject, pyGet, pyOr, pyPathJoin, pyPathlibStr, pyStrAny, pyTruthy } from "./support.js";
|
|
53
|
+
import { existsSync, readFileSync, stagedWrites } from "./staged-files.js";
|
|
53
54
|
import { workPath } from "../../workdir.js";
|
|
54
55
|
/**
|
|
55
56
|
* Where prepare writes its overlay when `--out` is not given, as shown to the
|
|
@@ -119,7 +120,7 @@ export async function matchCatalogObject(ctx, connectorKey, wanted, verb) {
|
|
|
119
120
|
function chooseLabels(options, ports) {
|
|
120
121
|
const info = (line) => ports.reporter.info(line);
|
|
121
122
|
const warn = (line) => ports.reporter.warn(line);
|
|
122
|
-
const command = ports.
|
|
123
|
+
const command = `${ports.cli ?? cliSpelling()} registry prepare`;
|
|
123
124
|
const labels = sourceBindingLabels({
|
|
124
125
|
cwd: ports.cwd ?? process.cwd(),
|
|
125
126
|
out: options.out ?? defaultOverlayOut(ports.cwd ?? process.cwd()),
|
|
@@ -263,13 +264,19 @@ async function connect(options, ports) {
|
|
|
263
264
|
* command PICKED and finds nothing, the other enabled connections for the
|
|
264
265
|
* connector are probed, and the run starts over pinned to the one that
|
|
265
266
|
* resolves everything (flow-builder-sdk#744). The Python re-exec'd itself for
|
|
266
|
-
* that; here the bindings snapshot is restored and
|
|
267
|
-
*
|
|
267
|
+
* that; here the bindings snapshot is restored and the run starts over, inside
|
|
268
|
+
* the same held writes, with `--connection-id <winner> --refresh-lookups`. The refresh is
|
|
268
269
|
* NOT optional: the ledger keys a resolution by `[nodeType, field, by, value]`
|
|
269
270
|
* and not by connection, so an id recorded against the connection just
|
|
270
271
|
* abandoned would come back as "already resolved" on the re-run.
|
|
272
|
+
*
|
|
273
|
+
* Nothing reaches the disk until the whole command, re-run included, has
|
|
274
|
+
* succeeded; a refusal writes nothing (`staged-files.ts`).
|
|
271
275
|
*/
|
|
272
|
-
export
|
|
276
|
+
export function prepareConnector(options, ports) {
|
|
277
|
+
return stagedWrites(() => prepareOrRetry(options, ports));
|
|
278
|
+
}
|
|
279
|
+
async function prepareOrRetry(options, ports) {
|
|
273
280
|
const info = (line) => ports.reporter.info(line);
|
|
274
281
|
const warn = (line) => ports.reporter.warn(line);
|
|
275
282
|
// Whether the CALLER chose the connection, recorded before discovery answers
|
|
@@ -324,7 +331,7 @@ export async function prepareConnector(options, ports) {
|
|
|
324
331
|
// The labels this run chose ride along as flags, so the re-entry binds under
|
|
325
332
|
// the same names instead of deriving them again.
|
|
326
333
|
const labels = connected.bound ? { bindConnection: connected.bound.connection, bindFolder: connected.bound.folder } : {};
|
|
327
|
-
return
|
|
334
|
+
return prepareOrRetry({ ...options, ...labels, connectionId: chosenId, refreshLookups: true }, ports);
|
|
328
335
|
}
|
|
329
336
|
}
|
|
330
337
|
/**
|
|
@@ -336,7 +343,8 @@ async function prepareOnConnection(options, ports, connected, registryRefresh) {
|
|
|
336
343
|
const cwd = ports.cwd ?? process.cwd();
|
|
337
344
|
const info = (line) => ports.reporter.info(line);
|
|
338
345
|
const warn = (line) => ports.reporter.warn(line);
|
|
339
|
-
const
|
|
346
|
+
const cli = ports.cli ?? cliSpelling();
|
|
347
|
+
const command = `${cli} registry prepare`;
|
|
340
348
|
const { connectorKey } = options;
|
|
341
349
|
// Rebound to the activity's own action id when Integration Service supplies
|
|
342
350
|
// the entry, so the overlay path, the summary and the handoff all spell the
|
|
@@ -349,6 +357,7 @@ async function prepareOnConnection(options, ports, connected, registryRefresh) {
|
|
|
349
357
|
const importSpecifier = options.importSpecifier ?? PACKAGE_NAME;
|
|
350
358
|
const jsonOnly = options.jsonOnly ?? false;
|
|
351
359
|
const { connectionId, bound, rollback } = connected;
|
|
360
|
+
const describeOverlay = out === resolve(cwd, defaultOverlayOut(cwd)) ? undefined : outShown;
|
|
352
361
|
const ctx = createBuildContext({ client: ports.client, caches: ports.caches ?? memoryCaches(), reporter: ports.reporter, concurrency: 1, timeoutMs: ports.timeoutMs }, { connectionId, parentFields });
|
|
353
362
|
let nodeType = `uipath.connector.${connectorKey}.${action}`;
|
|
354
363
|
let fromIntegrationService;
|
|
@@ -376,6 +385,7 @@ async function prepareOnConnection(options, ports, connected, registryRefresh) {
|
|
|
376
385
|
prepareCommand: command,
|
|
377
386
|
});
|
|
378
387
|
if (!("missed" in event)) {
|
|
388
|
+
info(describeNextLine(cli, event.eventNodeType, event.objectName, describeOverlay));
|
|
379
389
|
return { kind: "event", nodeType: event.eventNodeType, connectionId, bound, objectName: event.objectName };
|
|
380
390
|
}
|
|
381
391
|
const probes = [actionProbe.miss, event.missed];
|
|
@@ -574,5 +584,6 @@ async function prepareOnConnection(options, ports, connected, registryRefresh) {
|
|
|
574
584
|
for (const line of usage)
|
|
575
585
|
info(nextLine(line));
|
|
576
586
|
}
|
|
587
|
+
info(describeNextLine(cli, nodeType, options.allObjects ? "<object>" : objectName, describeOverlay));
|
|
577
588
|
return { kind: "action", nodeType, connectionId, bound, variants, inputFields, kept, objectName };
|
|
578
589
|
}
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
* `generate_connectors_ts.py --library <out> --output <out>`; the generator is
|
|
19
19
|
* now `renderDescriptorModules`, called in-process.
|
|
20
20
|
*/
|
|
21
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
22
21
|
import { join } from "node:path";
|
|
23
22
|
import { Refusal } from "../errors.js";
|
|
24
23
|
import { formatRequiredInputGaps, renderDescriptorModules } from "../generators/descriptors.js";
|
|
25
24
|
import { loadLibrary } from "../library-reader.js";
|
|
26
25
|
import { overlayHasGenericExpanded } from "./overlay.js";
|
|
26
|
+
import { mkdirSync, stagedReadText, writeFileSync } from "./staged-files.js";
|
|
27
27
|
/**
|
|
28
28
|
* Write `<out>/<connectorKey>.ts` for every connector in the overlay and
|
|
29
29
|
* return the module names written; nothing at all under `--json-only`.
|
|
@@ -35,7 +35,7 @@ import { overlayHasGenericExpanded } from "./overlay.js";
|
|
|
35
35
|
export function regenerateDescriptors(out, importSpecifier, jsonOnly = false) {
|
|
36
36
|
if (jsonOnly)
|
|
37
37
|
return [];
|
|
38
|
-
const library = loadLibrary(out);
|
|
38
|
+
const library = loadLibrary(out, stagedReadText());
|
|
39
39
|
const output = renderDescriptorModules(library, {
|
|
40
40
|
importSpecifier,
|
|
41
41
|
includeGenericExpanded: overlayHasGenericExpanded(out),
|
|
@@ -19,13 +19,14 @@
|
|
|
19
19
|
* Ported from `_IMPORT_RE`, `_resolves_to`, `_sources_importing`,
|
|
20
20
|
* `_repoint_import` and `_repoint_source` in `prepare_connector.py`.
|
|
21
21
|
*/
|
|
22
|
-
import {
|
|
22
|
+
import { readdirSync, realpathSync } from "node:fs";
|
|
23
23
|
import { basename, dirname, join, resolve } from "node:path";
|
|
24
24
|
import { compareCodePoints } from "../compat/compare.js";
|
|
25
25
|
import { relativePosix } from "../compat/paths.js";
|
|
26
26
|
import { workPath } from "../../workdir.js";
|
|
27
27
|
import { isFile } from "../files.js";
|
|
28
28
|
import { repointedLine, severalSourcesLine, sourceNotAFileLine } from "./messages.js";
|
|
29
|
+
import { existsSync, readFileSync, writeFileSync } from "./staged-files.js";
|
|
29
30
|
/**
|
|
30
31
|
* `import … from "<specifier>"` — the specifier is group 3, quotes preserved.
|
|
31
32
|
* Anchored to a real import statement on purpose (see the module comment).
|
|
@@ -180,9 +181,10 @@ export function repointImport(source, baked, prepared) {
|
|
|
180
181
|
*/
|
|
181
182
|
export function repointSource(options) {
|
|
182
183
|
const cwd = options.cwd ?? process.cwd();
|
|
183
|
-
const
|
|
184
|
-
if (options.noSourceRewrite || !
|
|
184
|
+
const module = resolve(cwd, options.out, `${options.connectorKey}.ts`);
|
|
185
|
+
if (options.noSourceRewrite || !existsSync(module))
|
|
185
186
|
return [];
|
|
187
|
+
const prepared = canonicalPath(module);
|
|
186
188
|
const root = canonicalPath(cwd);
|
|
187
189
|
// Where `registry pull` put the module: `.flow-sdk/connectors/`, or a legacy `connectors/`.
|
|
188
190
|
const baked = canonicalPath(join(workPath("connectors", { cwd: root }), `${options.connectorKey}.ts`));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReadText } from "../../core/read-text.js";
|
|
2
|
+
/**
|
|
3
|
+
* Run `work` with its writes held, then write them all, or none when it throws.
|
|
4
|
+
* One run at a time per process: the held files are process-wide, so a second
|
|
5
|
+
* run started before the first ends is refused rather than mixed into it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function stagedWrites<T>(work: () => Promise<T>): Promise<T>;
|
|
8
|
+
/** Run `step` once the held files are written: at once outside a run, never for a run that throws. */
|
|
9
|
+
export declare function afterWrites(step: () => void): void;
|
|
10
|
+
export declare function writeFileSync(path: string, content: string | Uint8Array, encoding?: "utf8"): void;
|
|
11
|
+
/** A held file's parent is made when it is written, so there is nothing to make yet. */
|
|
12
|
+
export declare function mkdirSync(path: string, options: {
|
|
13
|
+
recursive: true;
|
|
14
|
+
}): void;
|
|
15
|
+
export declare function existsSync(path: string): boolean;
|
|
16
|
+
export declare function readFileSync(path: string): Uint8Array;
|
|
17
|
+
export declare function readFileSync(path: string, encoding: "utf8"): string;
|
|
18
|
+
/** Drops a held file; only outside a run does it reach the disk. */
|
|
19
|
+
export declare function rmSync(path: string): void;
|
|
20
|
+
/** The library reader `loadLibrary` takes, seeing held files first. */
|
|
21
|
+
export declare function stagedReadText(): ReadText;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `prepare`'s file writes, held in memory until the run has succeeded.
|
|
3
|
+
*
|
|
4
|
+
* Studio Web's project file system refuses deletes, and so renames, which
|
|
5
|
+
* rules out both ways a failed run used to be cleaned up: writing a scratch
|
|
6
|
+
* copy and swapping it in, and removing what a refusal left behind. Instead
|
|
7
|
+
* the prepare modules read and write through these functions. While
|
|
8
|
+
* {@link stagedWrites} runs, a write lands in memory and a later read in the
|
|
9
|
+
* same run sees it; when the run resolves, each file is written once, with a
|
|
10
|
+
* plain write. A run that throws writes nothing.
|
|
11
|
+
*
|
|
12
|
+
* Outside {@link stagedWrites} every function is the `node:fs` one, so a
|
|
13
|
+
* helper called on its own (the tests, `mergeInto`) writes at once as before.
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from "node:fs";
|
|
16
|
+
import { basename, dirname, resolve } from "node:path";
|
|
17
|
+
import { defaultReadText } from "../../core/read-text.js";
|
|
18
|
+
import { Refusal, messageOf } from "../errors.js";
|
|
19
|
+
import { PrepareRefusal, discardedNote, partialWriteMessage } from "./messages.js";
|
|
20
|
+
let pending;
|
|
21
|
+
let afterwards = [];
|
|
22
|
+
/**
|
|
23
|
+
* Run `work` with its writes held, then write them all, or none when it throws.
|
|
24
|
+
* One run at a time per process: the held files are process-wide, so a second
|
|
25
|
+
* run started before the first ends is refused rather than mixed into it.
|
|
26
|
+
*/
|
|
27
|
+
export async function stagedWrites(work) {
|
|
28
|
+
if (pending !== undefined) {
|
|
29
|
+
throw new Error("registry prepare is already running in this process; run one prepare at a time, and wait for it to end before the next.");
|
|
30
|
+
}
|
|
31
|
+
const files = new Map();
|
|
32
|
+
pending = files;
|
|
33
|
+
const then = [];
|
|
34
|
+
afterwards = then;
|
|
35
|
+
let result;
|
|
36
|
+
try {
|
|
37
|
+
result = await work();
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error instanceof Refusal && files.size > 0)
|
|
41
|
+
error.message += `\n${discardedNote(files.size)}`;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
pending = undefined;
|
|
46
|
+
}
|
|
47
|
+
// An index written before a failed write would name descriptors that are not there.
|
|
48
|
+
const indexLast = [...files].sort(([a], [b]) => Number(basename(a) === "index.json") - Number(basename(b) === "index.json"));
|
|
49
|
+
for (const [path, content] of indexLast) {
|
|
50
|
+
try {
|
|
51
|
+
fs.mkdirSync(dirname(path), { recursive: true });
|
|
52
|
+
fs.writeFileSync(path, content);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new PrepareRefusal(partialWriteMessage(path, messageOf(error)), "PREPARE_WRITE_FAILED");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const step of then)
|
|
59
|
+
step();
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/** Run `step` once the held files are written: at once outside a run, never for a run that throws. */
|
|
63
|
+
export function afterWrites(step) {
|
|
64
|
+
if (pending !== undefined)
|
|
65
|
+
afterwards.push(step);
|
|
66
|
+
else
|
|
67
|
+
step();
|
|
68
|
+
}
|
|
69
|
+
function held(path) {
|
|
70
|
+
return pending?.get(resolve(path));
|
|
71
|
+
}
|
|
72
|
+
export function writeFileSync(path, content, encoding) {
|
|
73
|
+
if (pending !== undefined)
|
|
74
|
+
pending.set(resolve(path), content);
|
|
75
|
+
else
|
|
76
|
+
fs.writeFileSync(path, content, encoding);
|
|
77
|
+
}
|
|
78
|
+
/** A held file's parent is made when it is written, so there is nothing to make yet. */
|
|
79
|
+
export function mkdirSync(path, options) {
|
|
80
|
+
if (pending === undefined)
|
|
81
|
+
fs.mkdirSync(path, options);
|
|
82
|
+
}
|
|
83
|
+
export function existsSync(path) {
|
|
84
|
+
return held(path) !== undefined || fs.existsSync(path);
|
|
85
|
+
}
|
|
86
|
+
export function readFileSync(path, encoding) {
|
|
87
|
+
const content = held(path);
|
|
88
|
+
if (content === undefined)
|
|
89
|
+
return encoding === undefined ? fs.readFileSync(path) : fs.readFileSync(path, encoding);
|
|
90
|
+
if (encoding === undefined)
|
|
91
|
+
return typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
92
|
+
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
93
|
+
}
|
|
94
|
+
/** Drops a held file; only outside a run does it reach the disk. */
|
|
95
|
+
export function rmSync(path) {
|
|
96
|
+
if (pending !== undefined)
|
|
97
|
+
pending.delete(resolve(path));
|
|
98
|
+
else
|
|
99
|
+
fs.rmSync(path);
|
|
100
|
+
}
|
|
101
|
+
/** The library reader `loadLibrary` takes, seeing held files first. */
|
|
102
|
+
export function stagedReadText() {
|
|
103
|
+
const read = defaultReadText();
|
|
104
|
+
return (path) => {
|
|
105
|
+
const content = held(path);
|
|
106
|
+
if (content === undefined)
|
|
107
|
+
return read(path);
|
|
108
|
+
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -109,6 +109,24 @@ export declare class OfflineUipClient implements UipClient {
|
|
|
109
109
|
* ask for with `host` (see `host.ts`): every call then spawns its own `uip`,
|
|
110
110
|
* which is how the registry ran before flow-builder-sdk#965 and the shape to
|
|
111
111
|
* compare against when a prepare misbehaves.
|
|
112
|
+
*
|
|
113
|
+
* A host that cannot start a process (Studio Web runs the SDK's CLI in a
|
|
114
|
+
* sandbox with no `child_process`), and so cannot pass a client in, hands one
|
|
115
|
+
* over before the run: `globalThis[Symbol.for('uipath.maestro-builder-sdk.uip')]
|
|
116
|
+
* = client`, an object with the {@link UipClient} shape. Its `run(argv, options)`
|
|
117
|
+
* gets the argv `uip` would (`argv[0]` is `"uip"`) and resolves a {@link UipResult}
|
|
118
|
+
* — `code` 0 and the JSON envelope on `stdout` for a success, as the CLI prints
|
|
119
|
+
* it. The argv carries the author's values (`-f`, `--resolve`), so a host
|
|
120
|
+
* hands it to the CLI as an array and never through a shell. A rejection or a
|
|
121
|
+
* malformed answer reads as a failed run (`code` 1, the reason on `stderr`),
|
|
122
|
+
* and a call past its `timeoutMs` as a timed-out one. It answers every call in
|
|
123
|
+
* place of a spawned `uip`, `host` and `FLOW_SDK_UIP_HOST` included, and no
|
|
124
|
+
* `node:child_process` is loaded. Calls can
|
|
125
|
+
* overlap: the library builder runs several at once. `registry prepare` calls
|
|
126
|
+
* `close()`, if it has one, when it ends, and nothing else does, so a client
|
|
127
|
+
* that outlives the command leaves `close` out. `UIP_REPLAY_DIR` still wins over it, so a replay
|
|
128
|
+
* never reaches the tenant, and `UIP_RECORD_DIR` records what it answers. This is
|
|
129
|
+
* the tenant's counterpart of the library's `readText` slot (`core/read-text.ts`).
|
|
112
130
|
*/
|
|
113
131
|
export declare function defaultUipClient(options?: UipRunOptions & {
|
|
114
132
|
host?: HostUipClientOptions | boolean;
|
|
@@ -210,6 +210,59 @@ export class OfflineUipClient {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
+
const HOST_UIP = Symbol.for("uipath.maestro-builder-sdk.uip");
|
|
214
|
+
/** A call the slot's client could not answer, read like a failed run. */
|
|
215
|
+
function slotFailure(reason) {
|
|
216
|
+
return { code: 1, signal: null, stdout: "", stderr: `uip slot: ${reason}`, timedOut: false };
|
|
217
|
+
}
|
|
218
|
+
/** Why `answer` is not a {@link UipResult}, or `undefined` when it is one. */
|
|
219
|
+
function notAResult(answer) {
|
|
220
|
+
if (answer === null || typeof answer !== "object")
|
|
221
|
+
return `answered ${answer === null ? "null" : typeof answer}, not a result object`;
|
|
222
|
+
const { code, stdout, stderr } = answer;
|
|
223
|
+
if (typeof code !== "number")
|
|
224
|
+
return `answered a result whose code is ${typeof code}, not a number`;
|
|
225
|
+
if (typeof stdout !== "string" || typeof stderr !== "string")
|
|
226
|
+
return "answered a result whose stdout or stderr is not a string";
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The host's client in the slot, held to the contract the spawning client
|
|
231
|
+
* keeps: a timeout is enforced, and a rejection or a malformed answer is a
|
|
232
|
+
* failed run, never a throw.
|
|
233
|
+
*/
|
|
234
|
+
class SlotUipClient {
|
|
235
|
+
hosted;
|
|
236
|
+
defaults;
|
|
237
|
+
constructor(hosted, defaults) {
|
|
238
|
+
this.hosted = hosted;
|
|
239
|
+
this.defaults = defaults;
|
|
240
|
+
}
|
|
241
|
+
async run(argv, options = {}) {
|
|
242
|
+
const answer = Promise.resolve()
|
|
243
|
+
.then(() => this.hosted.run(argv, options))
|
|
244
|
+
.then((result) => {
|
|
245
|
+
const reason = notAResult(result);
|
|
246
|
+
return reason === undefined ? result : slotFailure(reason);
|
|
247
|
+
}, (error) => slotFailure(`the call failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
248
|
+
const timeoutMs = options.timeoutMs ?? this.defaults.timeoutMs;
|
|
249
|
+
if (timeoutMs === undefined || timeoutMs <= 0)
|
|
250
|
+
return answer;
|
|
251
|
+
let timer;
|
|
252
|
+
const expired = new Promise((resolve) => {
|
|
253
|
+
timer = setTimeout(() => resolve({ code: null, signal: null, stdout: "", stderr: `uip slot: no answer within ${timeoutMs} ms`, timedOut: true }), timeoutMs);
|
|
254
|
+
});
|
|
255
|
+
try {
|
|
256
|
+
return await Promise.race([answer, expired]);
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
clearTimeout(timer);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
close() {
|
|
263
|
+
this.hosted.close?.();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
213
266
|
/**
|
|
214
267
|
* The client a verb should use, honouring the recording and replay switches.
|
|
215
268
|
*
|
|
@@ -237,12 +290,39 @@ export class OfflineUipClient {
|
|
|
237
290
|
* ask for with `host` (see `host.ts`): every call then spawns its own `uip`,
|
|
238
291
|
* which is how the registry ran before flow-builder-sdk#965 and the shape to
|
|
239
292
|
* compare against when a prepare misbehaves.
|
|
293
|
+
*
|
|
294
|
+
* A host that cannot start a process (Studio Web runs the SDK's CLI in a
|
|
295
|
+
* sandbox with no `child_process`), and so cannot pass a client in, hands one
|
|
296
|
+
* over before the run: `globalThis[Symbol.for('uipath.maestro-builder-sdk.uip')]
|
|
297
|
+
* = client`, an object with the {@link UipClient} shape. Its `run(argv, options)`
|
|
298
|
+
* gets the argv `uip` would (`argv[0]` is `"uip"`) and resolves a {@link UipResult}
|
|
299
|
+
* — `code` 0 and the JSON envelope on `stdout` for a success, as the CLI prints
|
|
300
|
+
* it. The argv carries the author's values (`-f`, `--resolve`), so a host
|
|
301
|
+
* hands it to the CLI as an array and never through a shell. A rejection or a
|
|
302
|
+
* malformed answer reads as a failed run (`code` 1, the reason on `stderr`),
|
|
303
|
+
* and a call past its `timeoutMs` as a timed-out one. It answers every call in
|
|
304
|
+
* place of a spawned `uip`, `host` and `FLOW_SDK_UIP_HOST` included, and no
|
|
305
|
+
* `node:child_process` is loaded. Calls can
|
|
306
|
+
* overlap: the library builder runs several at once. `registry prepare` calls
|
|
307
|
+
* `close()`, if it has one, when it ends, and nothing else does, so a client
|
|
308
|
+
* that outlives the command leaves `close` out. `UIP_REPLAY_DIR` still wins over it, so a replay
|
|
309
|
+
* never reaches the tenant, and `UIP_RECORD_DIR` records what it answers. This is
|
|
310
|
+
* the tenant's counterpart of the library's `readText` slot (`core/read-text.ts`).
|
|
240
311
|
*/
|
|
241
312
|
export function defaultUipClient(options = {}) {
|
|
242
313
|
const { replay, record } = uipSwitches();
|
|
243
314
|
if (replay)
|
|
244
315
|
return new ReplayUipClient(replay);
|
|
245
316
|
const { host, ...runOptions } = options;
|
|
317
|
+
const hosted = globalThis[HOST_UIP];
|
|
318
|
+
if (hosted !== undefined && hosted !== null) {
|
|
319
|
+
if (typeof hosted.run !== "function") {
|
|
320
|
+
throw new Error("globalThis[Symbol.for('uipath.maestro-builder-sdk.uip')] holds no uip client: it needs a " +
|
|
321
|
+
"run(argv, options) method that resolves { code, stdout, stderr }.");
|
|
322
|
+
}
|
|
323
|
+
const slot = new SlotUipClient(hosted, runOptions);
|
|
324
|
+
return record ? new RecordingUipClient(slot, record) : slot;
|
|
325
|
+
}
|
|
246
326
|
const spawning = new SpawnUipClient(runOptions);
|
|
247
327
|
// One `uip mcp serve` for the command's whole chain of calls, where the
|
|
248
328
|
// caller asked for it and the operator has not switched it off. Serial, so
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.46.1",
|
|
4
4
|
"description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docs.uipath.com/maestro",
|
|
@@ -93,5 +93,5 @@
|
|
|
93
93
|
"@types/node": "^22.7.0",
|
|
94
94
|
"esbuild": "^0.28.1"
|
|
95
95
|
},
|
|
96
|
-
"gitref": "
|
|
96
|
+
"gitref": "c6f5b341abc0adecb1e1dd3bffee2126aa0f01cc"
|
|
97
97
|
}
|