@uipath/maestro-builder-sdk 6.3.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/api-index.md +6 -6
- 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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PrepareOptions, type PrepareReport } from "./prepare/pipeline.js";
|
|
1
2
|
/**
|
|
2
3
|
* Fetch one operation's connection-resolved schema into a local overlay.
|
|
3
4
|
*
|
|
@@ -14,7 +15,9 @@
|
|
|
14
15
|
*
|
|
15
16
|
* Needs the `uip` CLI on PATH and logged in. The tenant calls go through it —
|
|
16
17
|
* `uip maestro flow registry get`, `uip is resources list/describe` — because
|
|
17
|
-
* `uip` is the tenant client and this only orchestrates it.
|
|
18
|
+
* `uip` is the tenant client and this only orchestrates it. No interpreter is
|
|
19
|
+
* involved: this used to spawn `prepare_connector.py`, and the port of that
|
|
20
|
+
* script lives in `prepare/`.
|
|
18
21
|
*/
|
|
19
22
|
export interface PrepareConnectorOptions {
|
|
20
23
|
connectorKey: string;
|
|
@@ -46,13 +49,21 @@ export interface PrepareConnectorOptions {
|
|
|
46
49
|
source?: string;
|
|
47
50
|
/** Print the import line instead of editing the source. */
|
|
48
51
|
noSourceRewrite?: boolean;
|
|
52
|
+
/** @deprecated No interpreter is involved any more; accepted and ignored. */
|
|
49
53
|
pythonExecutable?: string;
|
|
50
|
-
/**
|
|
54
|
+
/** @deprecated The pipeline is part of the package; accepted and ignored. */
|
|
51
55
|
generatorPath?: string;
|
|
52
56
|
}
|
|
53
|
-
/**
|
|
54
|
-
export declare function
|
|
55
|
-
|
|
57
|
+
/** This module's options as the pipeline's, which name three things differently. */
|
|
58
|
+
export declare function pipelineOptions(options: PrepareConnectorOptions): PrepareOptions;
|
|
59
|
+
/**
|
|
60
|
+
* Prepare one operation, reporting to the console.
|
|
61
|
+
*
|
|
62
|
+
* Kept as a one-argument function because it is the API a caller outside this
|
|
63
|
+
* package holds; `prepare/pipeline.ts` is where the work is, and it takes its
|
|
64
|
+
* `uip` client and reporter injected so tests replay recordings.
|
|
65
|
+
*/
|
|
66
|
+
export declare function prepareConnector(options: PrepareConnectorOptions): Promise<PrepareReport>;
|
|
56
67
|
/**
|
|
57
68
|
* Options for this argv, or null when it does not name a connector, an action
|
|
58
69
|
* and a connection. Split out from `run` so the parsing can be tested without
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { packagedGenerator, runGenerator } from "./python.js";
|
|
2
1
|
import { PACKAGE_NAME } from "../package-name.js";
|
|
2
|
+
import { exitCodeOf } from "./errors.js";
|
|
3
|
+
import { exitText } from "./prepare/messages.js";
|
|
4
|
+
import { prepareConnector as runPipeline } from "./prepare/pipeline.js";
|
|
5
|
+
import { consoleReporter } from "./reporter.js";
|
|
6
|
+
import { defaultUipClient } from "./uip/client.js";
|
|
3
7
|
// This mirrors the generator's own flags one for one, so a `--version` here is
|
|
4
8
|
// the connector's version, not the package's. The umbrella CLI spells that
|
|
5
9
|
// `--connector-version`, because commander's program-level version option
|
|
@@ -14,58 +18,45 @@ const USAGE = [
|
|
|
14
18
|
" [--resolve FIELD:BY=VALUE ...] [--refresh-lookups]",
|
|
15
19
|
" [--out <dir>] [--version <v>] [--import <specifier>]",
|
|
16
20
|
" [--json-only] [--source <flow.ts>] [--no-source-rewrite]",
|
|
17
|
-
" [--python <exe>]",
|
|
21
|
+
" [--python <exe>] (accepted and ignored)",
|
|
18
22
|
].join("\n");
|
|
19
|
-
/**
|
|
20
|
-
export function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (options.outPath)
|
|
45
|
-
args.push("--out", options.outPath);
|
|
46
|
-
if (options.version)
|
|
47
|
-
args.push("--version", options.version);
|
|
48
|
-
// Always sent, because the generator's own default is `../sdk/flow-sdk.js` —
|
|
49
|
-
// a relative path that resolves in the eval image's checkout and nowhere else.
|
|
50
|
-
// The legacy `prepare-connector` shim hard-coded this; nothing else does, so
|
|
51
|
-
// omitting it emits a descriptor whose import line resolves to nothing.
|
|
52
|
-
// Matches materializer.ts, which defaults the same specifier.
|
|
53
|
-
args.push("--import", options.connectorImport ?? DEFAULT_CONNECTOR_IMPORT);
|
|
54
|
-
if (options.jsonOnly)
|
|
55
|
-
args.push("--json-only");
|
|
56
|
-
if (options.source)
|
|
57
|
-
args.push("--source", options.source);
|
|
58
|
-
if (options.noSourceRewrite)
|
|
59
|
-
args.push("--no-source-rewrite");
|
|
60
|
-
return args;
|
|
23
|
+
/** This module's options as the pipeline's, which name three things differently. */
|
|
24
|
+
export function pipelineOptions(options) {
|
|
25
|
+
return {
|
|
26
|
+
connectorKey: options.connectorKey,
|
|
27
|
+
action: options.action,
|
|
28
|
+
connectionId: options.connectionId,
|
|
29
|
+
connection: options.connection,
|
|
30
|
+
bindings: options.bindings,
|
|
31
|
+
bindConnection: options.bindConnection,
|
|
32
|
+
bindFolder: options.bindFolder,
|
|
33
|
+
noBindings: options.noBindings,
|
|
34
|
+
objectName: options.objectName,
|
|
35
|
+
allObjects: options.allObjects,
|
|
36
|
+
fields: options.fields,
|
|
37
|
+
resolve: options.resolve,
|
|
38
|
+
refreshLookups: options.refreshLookups,
|
|
39
|
+
out: options.outPath,
|
|
40
|
+
version: options.version,
|
|
41
|
+
// Always set, because a descriptor's import line has to resolve for whoever
|
|
42
|
+
// installed the package. Matches materializer.ts, which defaults the same.
|
|
43
|
+
importSpecifier: options.connectorImport ?? DEFAULT_CONNECTOR_IMPORT,
|
|
44
|
+
jsonOnly: options.jsonOnly,
|
|
45
|
+
source: options.source,
|
|
46
|
+
noSourceRewrite: options.noSourceRewrite,
|
|
47
|
+
};
|
|
61
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Prepare one operation, reporting to the console.
|
|
51
|
+
*
|
|
52
|
+
* Kept as a one-argument function because it is the API a caller outside this
|
|
53
|
+
* package holds; `prepare/pipeline.ts` is where the work is, and it takes its
|
|
54
|
+
* `uip` client and reporter injected so tests replay recordings.
|
|
55
|
+
*/
|
|
62
56
|
export async function prepareConnector(options) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
args,
|
|
67
|
-
pythonExecutable: options.pythonExecutable,
|
|
68
|
-
what: "Preparing the connector",
|
|
57
|
+
return runPipeline(pipelineOptions(options), {
|
|
58
|
+
client: defaultUipClient(),
|
|
59
|
+
reporter: consoleReporter,
|
|
69
60
|
});
|
|
70
61
|
}
|
|
71
62
|
function value(args, name) {
|
|
@@ -136,10 +127,19 @@ export function parseArgs(argv) {
|
|
|
136
127
|
consumed.add(index);
|
|
137
128
|
});
|
|
138
129
|
const positionals = argv.filter((_, index) => !consumed.has(index));
|
|
139
|
-
// `--connection-id` is no longer required: without one the
|
|
130
|
+
// `--connection-id` is no longer required: without one the pipeline
|
|
140
131
|
// discovers the connection itself and writes bindings.json.
|
|
141
132
|
if (positionals.length !== 2)
|
|
142
133
|
return null;
|
|
134
|
+
// argparse put these in a mutually exclusive group, so it exited 2 when both
|
|
135
|
+
// appeared. Accepting both is worse than it sounds: the named object is
|
|
136
|
+
// matched and recorded in the ledger, and then every object is expanded over
|
|
137
|
+
// the top of it, so the author gets a different result from the one they
|
|
138
|
+
// asked for and no error.
|
|
139
|
+
if (argv.includes("--object") && argv.includes("--all-objects")) {
|
|
140
|
+
console.error("argument --all-objects: not allowed with argument --object");
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
143
|
return {
|
|
144
144
|
connectorKey: positionals[0],
|
|
145
145
|
action: positionals[1],
|
|
@@ -174,12 +174,19 @@ export async function run(argv) {
|
|
|
174
174
|
console.error(USAGE);
|
|
175
175
|
return 2;
|
|
176
176
|
}
|
|
177
|
+
for (const flag of ["--python", "--generator"]) {
|
|
178
|
+
if (argv.includes(flag)) {
|
|
179
|
+
console.error(`registry prepare: ${flag} is ignored — the pipeline runs in-process now and the flag will be removed.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
177
182
|
try {
|
|
178
183
|
await prepareConnector(options);
|
|
179
184
|
return 0;
|
|
180
185
|
}
|
|
181
186
|
catch (error) {
|
|
182
|
-
|
|
183
|
-
|
|
187
|
+
// `exitText` reproduces the script's `sys.exit(f"prepare-connector: {e}")`,
|
|
188
|
+
// and the refusals it raises already carry that prefix.
|
|
189
|
+
console.error(exitText(error));
|
|
190
|
+
return exitCodeOf(error);
|
|
184
191
|
}
|
|
185
192
|
}
|
|
@@ -29,9 +29,10 @@ const USAGE = [
|
|
|
29
29
|
"Usage: flow-sdk registry pull [--connectors <dir>] [--connector <key>]...",
|
|
30
30
|
" [--library <dir>] [--library-md <dir>]",
|
|
31
31
|
" [--url <url>] [--archive <zip>] [--force]",
|
|
32
|
-
" [--import <specifier>] [--triggers <path>] [--
|
|
33
|
-
" [--root <prefix>] [--extractor <path>] [--generator <path>]",
|
|
32
|
+
" [--import <specifier>] [--triggers <path>] [--root <prefix>]",
|
|
34
33
|
].join("\n");
|
|
34
|
+
/** Flags from the Python era, accepted for one release and ignored. */
|
|
35
|
+
const IGNORED = ["--python", "--extractor", "--generator"];
|
|
35
36
|
function value(args, name) {
|
|
36
37
|
const index = args.indexOf(name);
|
|
37
38
|
return index >= 0 ? args[index + 1] : undefined;
|
|
@@ -191,7 +192,11 @@ export async function run(argv) {
|
|
|
191
192
|
}
|
|
192
193
|
const cacheDir = libraryCacheRoot();
|
|
193
194
|
const force = has(argv, "--force");
|
|
194
|
-
const
|
|
195
|
+
for (const flag of IGNORED) {
|
|
196
|
+
if (has(argv, flag)) {
|
|
197
|
+
console.error(`registry pull: ${flag} is ignored — the generators run in-process now and the flag will be removed.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
195
200
|
const connectorsPath = value(argv, "--connectors") ?? "connectors";
|
|
196
201
|
try {
|
|
197
202
|
const archive = await resolveArchive(argv, cacheDir, force);
|
|
@@ -216,8 +221,6 @@ export async function run(argv) {
|
|
|
216
221
|
archivePath: archive.path,
|
|
217
222
|
destinationPath: staging,
|
|
218
223
|
root: value(argv, "--root"),
|
|
219
|
-
pythonExecutable,
|
|
220
|
-
extractorPath: value(argv, "--extractor"),
|
|
221
224
|
});
|
|
222
225
|
await rm(libraryPath, { recursive: true, force: true });
|
|
223
226
|
await mkdir(join(libraryPath, ".."), { recursive: true });
|
|
@@ -244,9 +247,7 @@ export async function run(argv) {
|
|
|
244
247
|
connectorsPath: wantConnectors ? connectorsPath : undefined,
|
|
245
248
|
connectorSubset: referenced,
|
|
246
249
|
connectorImport: value(argv, "--import"),
|
|
247
|
-
pythonExecutable,
|
|
248
250
|
triggersPath: value(argv, "--triggers"),
|
|
249
|
-
generatorPath: value(argv, "--generator"),
|
|
250
251
|
});
|
|
251
252
|
}
|
|
252
253
|
if (!(await isFile(join(markdownPath, "index.json")))) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where progress and warnings go.
|
|
3
|
+
*
|
|
4
|
+
* The Python generators printed progress to stdout and `WARN:` lines to
|
|
5
|
+
* stderr, and prepare interleaved the two with explicit flushes so a note
|
|
6
|
+
* landed under the line it explained. A verb takes a {@link Reporter} so tests
|
|
7
|
+
* can run silent and so the CLI decides the streams once.
|
|
8
|
+
*/
|
|
9
|
+
export interface Reporter {
|
|
10
|
+
/** Progress a person watching the terminal wants to see. */
|
|
11
|
+
info(line: string): void;
|
|
12
|
+
/** A condition the run survived but the reader should know about. */
|
|
13
|
+
warn(line: string): void;
|
|
14
|
+
}
|
|
15
|
+
/** stdout for progress, stderr for warnings — the CLI's choice. */
|
|
16
|
+
export declare const consoleReporter: Reporter;
|
|
17
|
+
/** Everything to stderr, for verbs whose stdout carries a result. */
|
|
18
|
+
export declare const stderrReporter: Reporter;
|
|
19
|
+
export declare const silentReporter: Reporter;
|
|
20
|
+
/** A reporter that remembers, for tests. */
|
|
21
|
+
export declare class RecordingReporter implements Reporter {
|
|
22
|
+
readonly infos: string[];
|
|
23
|
+
readonly warnings: string[];
|
|
24
|
+
info(line: string): void;
|
|
25
|
+
warn(line: string): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where progress and warnings go.
|
|
3
|
+
*
|
|
4
|
+
* The Python generators printed progress to stdout and `WARN:` lines to
|
|
5
|
+
* stderr, and prepare interleaved the two with explicit flushes so a note
|
|
6
|
+
* landed under the line it explained. A verb takes a {@link Reporter} so tests
|
|
7
|
+
* can run silent and so the CLI decides the streams once.
|
|
8
|
+
*/
|
|
9
|
+
/** stdout for progress, stderr for warnings — the CLI's choice. */
|
|
10
|
+
export const consoleReporter = {
|
|
11
|
+
info(line) {
|
|
12
|
+
process.stdout.write(`${line}\n`);
|
|
13
|
+
},
|
|
14
|
+
warn(line) {
|
|
15
|
+
process.stderr.write(`${line}\n`);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
/** Everything to stderr, for verbs whose stdout carries a result. */
|
|
19
|
+
export const stderrReporter = {
|
|
20
|
+
info(line) {
|
|
21
|
+
process.stderr.write(`${line}\n`);
|
|
22
|
+
},
|
|
23
|
+
warn(line) {
|
|
24
|
+
process.stderr.write(`${line}\n`);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export const silentReporter = {
|
|
28
|
+
info() { },
|
|
29
|
+
warn() { },
|
|
30
|
+
};
|
|
31
|
+
/** A reporter that remembers, for tests. */
|
|
32
|
+
export class RecordingReporter {
|
|
33
|
+
infos = [];
|
|
34
|
+
warnings = [];
|
|
35
|
+
info(line) {
|
|
36
|
+
this.infos.push(line);
|
|
37
|
+
}
|
|
38
|
+
warn(line) {
|
|
39
|
+
this.warnings.push(line);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export interface UipRunOptions {
|
|
2
|
+
/** Working directory for the CLI; defaults to the process's. */
|
|
3
|
+
cwd?: string;
|
|
4
|
+
/** Kill the call after this many milliseconds. `undefined` means no limit, as before. */
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface UipResult {
|
|
8
|
+
/** The exit code, or `null` when the process was killed by a signal. */
|
|
9
|
+
code: number | null;
|
|
10
|
+
signal?: NodeJS.Signals | null;
|
|
11
|
+
stdout: string;
|
|
12
|
+
stderr: string;
|
|
13
|
+
/** True when `timeoutMs` elapsed and the process was killed. */
|
|
14
|
+
timedOut?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* True when no process ran because the client declined the call (see
|
|
17
|
+
* {@link OfflineUipClient}). A refusal reads like a failed run — non-zero
|
|
18
|
+
* exit, empty stdout — but it is not an answer from the tenant, so a caller
|
|
19
|
+
* that remembers failures in a cache must not remember this one.
|
|
20
|
+
*/
|
|
21
|
+
refused?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface UipClient {
|
|
24
|
+
/** Run `argv[0] argv.slice(1)` — the first element is the executable, normally `uip`. */
|
|
25
|
+
run(argv: readonly string[], options?: UipRunOptions): Promise<UipResult>;
|
|
26
|
+
}
|
|
27
|
+
/** The real thing: spawn the CLI, capture both streams, never throw on a non-zero exit. */
|
|
28
|
+
export declare class SpawnUipClient implements UipClient {
|
|
29
|
+
private readonly defaults;
|
|
30
|
+
constructor(defaults?: UipRunOptions);
|
|
31
|
+
run(argv: readonly string[], options?: UipRunOptions): Promise<UipResult>;
|
|
32
|
+
}
|
|
33
|
+
/** The file a recorded call lives in, from its argv. */
|
|
34
|
+
export declare function recordingName(argv: readonly string[]): string;
|
|
35
|
+
/**
|
|
36
|
+
* Serve recorded results; refuse anything unrecorded.
|
|
37
|
+
*
|
|
38
|
+
* The refusal is the point. A test that reaches the tenant by accident should
|
|
39
|
+
* fail with the argv it tried, not pass against whatever the network said.
|
|
40
|
+
*/
|
|
41
|
+
export declare class ReplayUipClient implements UipClient {
|
|
42
|
+
private readonly directory;
|
|
43
|
+
private readonly byName;
|
|
44
|
+
constructor(directory: string);
|
|
45
|
+
run(argv: readonly string[]): Promise<UipResult>;
|
|
46
|
+
}
|
|
47
|
+
/** Run through another client and write every result down for replay. */
|
|
48
|
+
export declare class RecordingUipClient implements UipClient {
|
|
49
|
+
private readonly inner;
|
|
50
|
+
private readonly directory;
|
|
51
|
+
constructor(inner: UipClient, directory: string);
|
|
52
|
+
run(argv: readonly string[], options?: UipRunOptions): Promise<UipResult>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A client that runs nothing and reports every call as refused.
|
|
56
|
+
*
|
|
57
|
+
* This is what `--offline` installs, and it exists because of how a build
|
|
58
|
+
* replays. The three response caches hold every reply a run received, so a
|
|
59
|
+
* second build from them normally reaches no tenant — but a call that FAILED
|
|
60
|
+
* was never cached (neither generator caches a failed `registry get`), so the
|
|
61
|
+
* replay tries it again. On the machine doing the diagnosis there is usually no
|
|
62
|
+
* `uip`, and the build then dies on a spawn error instead of reproducing the
|
|
63
|
+
* snapshot it was asked to reproduce. A full-catalog run had 427 such calls:
|
|
64
|
+
* namespace probes where a miss is the expected answer.
|
|
65
|
+
*
|
|
66
|
+
* Failing is therefore the faithful answer, not a fallback. Every fetcher
|
|
67
|
+
* already treats a non-zero exit as "no data for this one", which is exactly
|
|
68
|
+
* what happened on the original run. The result is also marked `refused`, for
|
|
69
|
+
* the one thing a fetcher must NOT do with it: the two trigger caches remember
|
|
70
|
+
* a failure as an empty answer, and remembering a refusal that way would hand
|
|
71
|
+
* a later ONLINE run over the same `--keep-temp` caches an empty event
|
|
72
|
+
* catalogue.
|
|
73
|
+
*/
|
|
74
|
+
export declare class OfflineUipClient implements UipClient {
|
|
75
|
+
run(argv: readonly string[]): Promise<UipResult>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The client a verb should use, honouring the recording and replay switches.
|
|
79
|
+
*
|
|
80
|
+
* Two environment variables change what a PRODUCTION build talks to, and
|
|
81
|
+
* nothing else announces them, so they are spelled out here:
|
|
82
|
+
*
|
|
83
|
+
* - `UIP_REPLAY_DIR=<dir>` — no `uip` is run at all; every call is answered
|
|
84
|
+
* from the recordings in `<dir>`. Set by accident, the build dies on its
|
|
85
|
+
* first tenant call with `ReplayUipClient: no recording for …`, naming the
|
|
86
|
+
* command and the file it looked for. Nothing is written to the tenant or
|
|
87
|
+
* the directory.
|
|
88
|
+
* - `UIP_RECORD_DIR=<dir>` — `uip` runs normally and every result is also
|
|
89
|
+
* written to `<dir>` as one JSON file per call. Set by accident, the build
|
|
90
|
+
* succeeds and looks ordinary, but leaves behind a directory of tenant
|
|
91
|
+
* responses (connection ids, schemas, object listings) that grows with
|
|
92
|
+
* every call; check for it if a build's working tree fills up.
|
|
93
|
+
*
|
|
94
|
+
* Both exist to capture a fixture from a real tenant and to replay one — that
|
|
95
|
+
* is how `fixtures/library-build/` was taken — and neither is meant to be set
|
|
96
|
+
* in a pipeline. They are read here and nowhere else, so this is the only place
|
|
97
|
+
* an operator can be told what they do.
|
|
98
|
+
*/
|
|
99
|
+
export declare function defaultUipClient(options?: UipRunOptions): UipClient;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running `uip`, and standing in for it.
|
|
3
|
+
*
|
|
4
|
+
* Everything in the registry that talks to the tenant goes through a
|
|
5
|
+
* {@link UipClient}. The real one spawns the CLI with a timeout and UTF-8
|
|
6
|
+
* decoding. The replay one serves envelopes captured earlier, keyed by the
|
|
7
|
+
* exact argv, and fails loudly on a call nobody recorded — which is how prepare
|
|
8
|
+
* and the library builder are tested without a tenant, and how the same
|
|
9
|
+
* recording drives a byte-for-byte comparison against the Python they replace.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here judges the response. Whether a non-zero exit is fatal, or a
|
|
12
|
+
* `Result: Failure` envelope should be cached, is the caller's rule — the
|
|
13
|
+
* Python callers disagreed with each other on purpose, and those rules stay
|
|
14
|
+
* beside the call sites that own them.
|
|
15
|
+
*/
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
/**
|
|
21
|
+
* Room for the largest `registry search` a tenant has answered, with margin.
|
|
22
|
+
* `execFile`'s 1 MiB default cut that stream off, so the port raised it; a
|
|
23
|
+
* child that exceeds even this is killed, which is what `execFile` did too.
|
|
24
|
+
*/
|
|
25
|
+
const MAX_OUTPUT_BYTES = 256 * 1024 * 1024;
|
|
26
|
+
/** The real thing: spawn the CLI, capture both streams, never throw on a non-zero exit. */
|
|
27
|
+
export class SpawnUipClient {
|
|
28
|
+
defaults;
|
|
29
|
+
constructor(defaults = {}) {
|
|
30
|
+
this.defaults = defaults;
|
|
31
|
+
}
|
|
32
|
+
run(argv, options = {}) {
|
|
33
|
+
const [executable, ...args] = argv;
|
|
34
|
+
const timeoutMs = options.timeoutMs ?? this.defaults.timeoutMs;
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
// stdin is `ignore`, not the default pipe. A pipe this process never
|
|
37
|
+
// writes to stays open, so a `uip` that decides to prompt — an expired
|
|
38
|
+
// login, a consent question — would block on it for as long as the
|
|
39
|
+
// build runs, and no timeout is set by default. On /dev/null the prompt
|
|
40
|
+
// reads EOF at once and the CLI fails like any other call, which is
|
|
41
|
+
// what Python's `subprocess.run` gave it: the parent's stdin, already at
|
|
42
|
+
// EOF on an agent.
|
|
43
|
+
const child = spawn(executable, args, {
|
|
44
|
+
cwd: options.cwd ?? this.defaults.cwd,
|
|
45
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
46
|
+
});
|
|
47
|
+
const stdout = [];
|
|
48
|
+
const stderr = [];
|
|
49
|
+
let bytes = 0;
|
|
50
|
+
let timedOut = false;
|
|
51
|
+
let settled = false;
|
|
52
|
+
const timer = timeoutMs !== undefined && timeoutMs > 0
|
|
53
|
+
? setTimeout(() => {
|
|
54
|
+
timedOut = true;
|
|
55
|
+
child.kill("SIGKILL");
|
|
56
|
+
}, timeoutMs)
|
|
57
|
+
: undefined;
|
|
58
|
+
const collect = (chunks) => (chunk) => {
|
|
59
|
+
bytes += chunk.length;
|
|
60
|
+
chunks.push(chunk);
|
|
61
|
+
if (bytes > MAX_OUTPUT_BYTES)
|
|
62
|
+
child.kill("SIGKILL");
|
|
63
|
+
};
|
|
64
|
+
child.stdout.on("data", collect(stdout));
|
|
65
|
+
child.stderr.on("data", collect(stderr));
|
|
66
|
+
child.on("error", (error) => {
|
|
67
|
+
if (settled)
|
|
68
|
+
return;
|
|
69
|
+
settled = true;
|
|
70
|
+
if (timer !== undefined)
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
if (error.code === "ENOENT") {
|
|
73
|
+
reject(new Error(`Could not start ${executable}: ${error.message}. The registry verbs call the ` +
|
|
74
|
+
"UiPath CLI; install it and log in, or put it on PATH."));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Every other spawn failure — EACCES on the binary, EAGAIN with no
|
|
78
|
+
// process slots, a `uip` that is a directory — rejects as well, and
|
|
79
|
+
// that is a CHANGE from how this port read at first, where anything
|
|
80
|
+
// but a missing binary resolved a failed result and the run carried on
|
|
81
|
+
// into warnings and a thin library. Python raised `OSError` from
|
|
82
|
+
// `subprocess.run` for all of them, so rejecting is both the faithful
|
|
83
|
+
// answer and the better one: a permission problem on the CLI is not a
|
|
84
|
+
// connector that has no events.
|
|
85
|
+
reject(error);
|
|
86
|
+
});
|
|
87
|
+
child.on("close", (code, signal) => {
|
|
88
|
+
if (settled)
|
|
89
|
+
return;
|
|
90
|
+
settled = true;
|
|
91
|
+
if (timer !== undefined)
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
// Decoded once, whole, so a multi-byte character split across two
|
|
94
|
+
// chunks comes out intact.
|
|
95
|
+
resolve({
|
|
96
|
+
code,
|
|
97
|
+
signal,
|
|
98
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
99
|
+
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
100
|
+
timedOut,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** The file a recorded call lives in, from its argv. */
|
|
107
|
+
export function recordingName(argv) {
|
|
108
|
+
// Joined on NUL, which no argument can contain, so the digest is a function
|
|
109
|
+
// of the argument BOUNDARIES as well as the characters: a space would make
|
|
110
|
+
// `["uip", "is", "a b"]` and `["uip", "is", "a", "b"]` the same recording,
|
|
111
|
+
// and `--connectors "a,b"` versus two flags is exactly the kind of pair the
|
|
112
|
+
// registry issues.
|
|
113
|
+
const digest = createHash("sha256").update(argv.join("\u0000")).digest("hex").slice(0, 16);
|
|
114
|
+
const slug = argv
|
|
115
|
+
.slice(1, 5)
|
|
116
|
+
.join("-")
|
|
117
|
+
.replace(/[^A-Za-z0-9.-]+/g, "_")
|
|
118
|
+
.slice(0, 60);
|
|
119
|
+
return `${slug}-${digest}.json`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Serve recorded results; refuse anything unrecorded.
|
|
123
|
+
*
|
|
124
|
+
* The refusal is the point. A test that reaches the tenant by accident should
|
|
125
|
+
* fail with the argv it tried, not pass against whatever the network said.
|
|
126
|
+
*/
|
|
127
|
+
export class ReplayUipClient {
|
|
128
|
+
directory;
|
|
129
|
+
byName = new Map();
|
|
130
|
+
constructor(directory) {
|
|
131
|
+
this.directory = directory;
|
|
132
|
+
}
|
|
133
|
+
async run(argv) {
|
|
134
|
+
const name = recordingName(argv);
|
|
135
|
+
let recording = this.byName.get(name);
|
|
136
|
+
if (!recording) {
|
|
137
|
+
const path = join(this.directory, name);
|
|
138
|
+
if (!existsSync(path)) {
|
|
139
|
+
throw new Error(`ReplayUipClient: no recording for\n ${argv.join(" ")}\n(expected ${path}). ` +
|
|
140
|
+
"Record it with RecordingUipClient, or the call is one the code under test should not make. " +
|
|
141
|
+
"A build replays only when UIP_REPLAY_DIR is set; if that was not intended, unset it.");
|
|
142
|
+
}
|
|
143
|
+
recording = JSON.parse(readFileSync(path, "utf8"));
|
|
144
|
+
this.byName.set(name, recording);
|
|
145
|
+
}
|
|
146
|
+
return { ...recording.result };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** Run through another client and write every result down for replay. */
|
|
150
|
+
export class RecordingUipClient {
|
|
151
|
+
inner;
|
|
152
|
+
directory;
|
|
153
|
+
constructor(inner, directory) {
|
|
154
|
+
this.inner = inner;
|
|
155
|
+
this.directory = directory;
|
|
156
|
+
mkdirSync(directory, { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
async run(argv, options) {
|
|
159
|
+
const result = await this.inner.run(argv, options);
|
|
160
|
+
const recording = { argv: [...argv], result };
|
|
161
|
+
writeFileSync(join(this.directory, recordingName(argv)), `${JSON.stringify(recording, null, 2)}\n`);
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A client that runs nothing and reports every call as refused.
|
|
167
|
+
*
|
|
168
|
+
* This is what `--offline` installs, and it exists because of how a build
|
|
169
|
+
* replays. The three response caches hold every reply a run received, so a
|
|
170
|
+
* second build from them normally reaches no tenant — but a call that FAILED
|
|
171
|
+
* was never cached (neither generator caches a failed `registry get`), so the
|
|
172
|
+
* replay tries it again. On the machine doing the diagnosis there is usually no
|
|
173
|
+
* `uip`, and the build then dies on a spawn error instead of reproducing the
|
|
174
|
+
* snapshot it was asked to reproduce. A full-catalog run had 427 such calls:
|
|
175
|
+
* namespace probes where a miss is the expected answer.
|
|
176
|
+
*
|
|
177
|
+
* Failing is therefore the faithful answer, not a fallback. Every fetcher
|
|
178
|
+
* already treats a non-zero exit as "no data for this one", which is exactly
|
|
179
|
+
* what happened on the original run. The result is also marked `refused`, for
|
|
180
|
+
* the one thing a fetcher must NOT do with it: the two trigger caches remember
|
|
181
|
+
* a failure as an empty answer, and remembering a refusal that way would hand
|
|
182
|
+
* a later ONLINE run over the same `--keep-temp` caches an empty event
|
|
183
|
+
* catalogue.
|
|
184
|
+
*/
|
|
185
|
+
export class OfflineUipClient {
|
|
186
|
+
async run(argv) {
|
|
187
|
+
return {
|
|
188
|
+
code: 1,
|
|
189
|
+
signal: null,
|
|
190
|
+
stdout: "",
|
|
191
|
+
stderr: `offline: refused to run \`${argv.join(" ")}\` — no cached response, and --offline forbids the call`,
|
|
192
|
+
timedOut: false,
|
|
193
|
+
refused: true,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The client a verb should use, honouring the recording and replay switches.
|
|
199
|
+
*
|
|
200
|
+
* Two environment variables change what a PRODUCTION build talks to, and
|
|
201
|
+
* nothing else announces them, so they are spelled out here:
|
|
202
|
+
*
|
|
203
|
+
* - `UIP_REPLAY_DIR=<dir>` — no `uip` is run at all; every call is answered
|
|
204
|
+
* from the recordings in `<dir>`. Set by accident, the build dies on its
|
|
205
|
+
* first tenant call with `ReplayUipClient: no recording for …`, naming the
|
|
206
|
+
* command and the file it looked for. Nothing is written to the tenant or
|
|
207
|
+
* the directory.
|
|
208
|
+
* - `UIP_RECORD_DIR=<dir>` — `uip` runs normally and every result is also
|
|
209
|
+
* written to `<dir>` as one JSON file per call. Set by accident, the build
|
|
210
|
+
* succeeds and looks ordinary, but leaves behind a directory of tenant
|
|
211
|
+
* responses (connection ids, schemas, object listings) that grows with
|
|
212
|
+
* every call; check for it if a build's working tree fills up.
|
|
213
|
+
*
|
|
214
|
+
* Both exist to capture a fixture from a real tenant and to replay one — that
|
|
215
|
+
* is how `fixtures/library-build/` was taken — and neither is meant to be set
|
|
216
|
+
* in a pipeline. They are read here and nowhere else, so this is the only place
|
|
217
|
+
* an operator can be told what they do.
|
|
218
|
+
*/
|
|
219
|
+
export function defaultUipClient(options = {}) {
|
|
220
|
+
const replay = process.env.UIP_REPLAY_DIR;
|
|
221
|
+
if (replay)
|
|
222
|
+
return new ReplayUipClient(replay);
|
|
223
|
+
const client = new SpawnUipClient(options);
|
|
224
|
+
const record = process.env.UIP_RECORD_DIR;
|
|
225
|
+
return record ? new RecordingUipClient(client, record) : client;
|
|
226
|
+
}
|