@uipath/maestro-builder-sdk 6.4.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/cli/commands/registry.js +68 -12
  2. package/dist/registry/archive/entry-policy.d.ts +36 -0
  3. package/dist/registry/archive/entry-policy.js +60 -0
  4. package/dist/registry/archive/extract.d.ts +31 -0
  5. package/dist/registry/archive/extract.js +92 -0
  6. package/dist/registry/archive/zip-reader.d.ts +38 -0
  7. package/dist/registry/archive/zip-reader.js +256 -0
  8. package/dist/registry/codecs/pyjson.d.ts +62 -0
  9. package/dist/registry/codecs/pyjson.js +208 -0
  10. package/dist/registry/compat/compare.d.ts +18 -0
  11. package/dist/registry/compat/compare.js +32 -0
  12. package/dist/registry/compat/paths.d.ts +4 -0
  13. package/dist/registry/compat/paths.js +17 -0
  14. package/dist/registry/compat/pystr.d.ts +50 -0
  15. package/dist/registry/compat/pystr.js +221 -0
  16. package/dist/registry/concurrency.d.ts +10 -0
  17. package/dist/registry/concurrency.js +34 -0
  18. package/dist/registry/curated-overlay.d.ts +36 -4
  19. package/dist/registry/curated-overlay.js +87 -14
  20. package/dist/registry/errors.d.ts +26 -0
  21. package/dist/registry/errors.js +38 -0
  22. package/dist/registry/extractor.d.ts +9 -10
  23. package/dist/registry/extractor.js +5 -15
  24. package/dist/registry/files.d.ts +26 -0
  25. package/dist/registry/files.js +160 -0
  26. package/dist/registry/generators/descriptors.d.ts +81 -0
  27. package/dist/registry/generators/descriptors.js +405 -0
  28. package/dist/registry/generators/fields.d.ts +46 -0
  29. package/dist/registry/generators/fields.js +111 -0
  30. package/dist/registry/generators/markdown/common.d.ts +21 -0
  31. package/dist/registry/generators/markdown/common.js +55 -0
  32. package/dist/registry/generators/markdown/index.d.ts +33 -0
  33. package/dist/registry/generators/markdown/index.js +37 -0
  34. package/dist/registry/generators/markdown/page.d.ts +8 -0
  35. package/dist/registry/generators/markdown/page.js +52 -0
  36. package/dist/registry/generators/markdown/pyvalue.d.ts +34 -0
  37. package/dist/registry/generators/markdown/pyvalue.js +106 -0
  38. package/dist/registry/generators/markdown/sections/choosing-object.d.ts +2 -0
  39. package/dist/registry/generators/markdown/sections/choosing-object.js +52 -0
  40. package/dist/registry/generators/markdown/sections/example.d.ts +22 -0
  41. package/dist/registry/generators/markdown/sections/example.js +295 -0
  42. package/dist/registry/generators/markdown/sections/filtering.d.ts +2 -0
  43. package/dist/registry/generators/markdown/sections/filtering.js +76 -0
  44. package/dist/registry/generators/markdown/sections/header.d.ts +2 -0
  45. package/dist/registry/generators/markdown/sections/header.js +37 -0
  46. package/dist/registry/generators/markdown/sections/inputs.d.ts +30 -0
  47. package/dist/registry/generators/markdown/sections/inputs.js +151 -0
  48. package/dist/registry/generators/markdown/sections/lookups.d.ts +12 -0
  49. package/dist/registry/generators/markdown/sections/lookups.js +150 -0
  50. package/dist/registry/generators/markdown/sections/outputs.d.ts +3 -0
  51. package/dist/registry/generators/markdown/sections/outputs.js +9 -0
  52. package/dist/registry/generators/markdown/sections/resolving-fields.d.ts +2 -0
  53. package/dist/registry/generators/markdown/sections/resolving-fields.js +139 -0
  54. package/dist/registry/generators/markdown/sections/schema-dynamic.d.ts +2 -0
  55. package/dist/registry/generators/markdown/sections/schema-dynamic.js +86 -0
  56. package/dist/registry/generators/materialize.d.ts +30 -0
  57. package/dist/registry/generators/materialize.js +132 -0
  58. package/dist/registry/generators/md-symbols.d.ts +22 -0
  59. package/dist/registry/generators/md-symbols.js +58 -0
  60. package/dist/registry/generators/triggers.d.ts +15 -0
  61. package/dist/registry/generators/triggers.js +85 -0
  62. package/dist/registry/generators/type-registry.d.ts +50 -0
  63. package/dist/registry/generators/type-registry.js +154 -0
  64. package/dist/registry/generators/type-tree.d.ts +65 -0
  65. package/dist/registry/generators/type-tree.js +179 -0
  66. package/dist/registry/library-build/build.d.ts +57 -0
  67. package/dist/registry/library-build/build.js +286 -0
  68. package/dist/registry/library-build/canonical.d.ts +119 -0
  69. package/dist/registry/library-build/canonical.js +512 -0
  70. package/dist/registry/library-build/cli.d.ts +45 -0
  71. package/dist/registry/library-build/cli.js +182 -0
  72. package/dist/registry/library-build/context.d.ts +107 -0
  73. package/dist/registry/library-build/context.js +267 -0
  74. package/dist/registry/library-build/enrich.d.ts +36 -0
  75. package/dist/registry/library-build/enrich.js +158 -0
  76. package/dist/registry/library-build/events.d.ts +108 -0
  77. package/dist/registry/library-build/events.js +476 -0
  78. package/dist/registry/library-build/generic-expand.d.ts +77 -0
  79. package/dist/registry/library-build/generic-expand.js +363 -0
  80. package/dist/registry/library-build/plan.d.ts +71 -0
  81. package/dist/registry/library-build/plan.js +270 -0
  82. package/dist/registry/library-build/py.d.ts +45 -0
  83. package/dist/registry/library-build/py.js +167 -0
  84. package/dist/registry/library-build/schema-action.d.ts +38 -0
  85. package/dist/registry/library-build/schema-action.js +107 -0
  86. package/dist/registry/library-build/write.d.ts +6 -0
  87. package/dist/registry/library-build/write.js +26 -0
  88. package/dist/registry/library-reader.d.ts +20 -0
  89. package/dist/registry/library-reader.js +50 -0
  90. package/dist/registry/lookup-specs.d.ts +55 -0
  91. package/dist/registry/lookup-specs.js +140 -0
  92. package/dist/registry/materialize-cli.js +28 -14
  93. package/dist/registry/materializer.d.ts +25 -3
  94. package/dist/registry/materializer.js +45 -29
  95. package/dist/registry/model/library.d.ts +254 -0
  96. package/dist/registry/model/library.js +23 -0
  97. package/dist/registry/naming.d.ts +54 -0
  98. package/dist/registry/naming.js +142 -0
  99. package/dist/registry/orphans.d.ts +4 -0
  100. package/dist/registry/orphans.js +86 -0
  101. package/dist/registry/prepare/actions.d.ts +26 -0
  102. package/dist/registry/prepare/actions.js +94 -0
  103. package/dist/registry/prepare/connections.d.ts +181 -0
  104. package/dist/registry/prepare/connections.js +399 -0
  105. package/dist/registry/prepare/event.d.ts +96 -0
  106. package/dist/registry/prepare/event.js +268 -0
  107. package/dist/registry/prepare/handoff.d.ts +21 -0
  108. package/dist/registry/prepare/handoff.js +91 -0
  109. package/dist/registry/prepare/ledger.d.ts +104 -0
  110. package/dist/registry/prepare/ledger.js +216 -0
  111. package/dist/registry/prepare/messages.d.ts +190 -0
  112. package/dist/registry/prepare/messages.js +472 -0
  113. package/dist/registry/prepare/objects.d.ts +130 -0
  114. package/dist/registry/prepare/objects.js +318 -0
  115. package/dist/registry/prepare/overlay.d.ts +149 -0
  116. package/dist/registry/prepare/overlay.js +487 -0
  117. package/dist/registry/prepare/parents.d.ts +41 -0
  118. package/dist/registry/prepare/parents.js +122 -0
  119. package/dist/registry/prepare/pipeline.d.ts +91 -0
  120. package/dist/registry/prepare/pipeline.js +376 -0
  121. package/dist/registry/prepare/regenerate.d.ts +9 -0
  122. package/dist/registry/prepare/regenerate.js +50 -0
  123. package/dist/registry/prepare/resolve.d.ts +199 -0
  124. package/dist/registry/prepare/resolve.js +470 -0
  125. package/dist/registry/prepare/retry.d.ts +83 -0
  126. package/dist/registry/prepare/retry.js +109 -0
  127. package/dist/registry/prepare/source-import.d.ts +54 -0
  128. package/dist/registry/prepare/source-import.js +192 -0
  129. package/dist/registry/prepare/support.d.ts +41 -0
  130. package/dist/registry/prepare/support.js +178 -0
  131. package/dist/registry/prepare-cli.d.ts +16 -5
  132. package/dist/registry/prepare-cli.js +60 -53
  133. package/dist/registry/pull-cli.js +8 -7
  134. package/dist/registry/reporter.d.ts +26 -0
  135. package/dist/registry/reporter.js +41 -0
  136. package/dist/registry/uip/client.d.ts +99 -0
  137. package/dist/registry/uip/client.js +226 -0
  138. package/dist/registry/uip/commands.d.ts +40 -0
  139. package/dist/registry/uip/commands.js +80 -0
  140. package/dist/registry/uip/envelope.d.ts +37 -0
  141. package/dist/registry/uip/envelope.js +84 -0
  142. package/dist/registry/uip/response-cache.d.ts +28 -0
  143. package/dist/registry/uip/response-cache.js +57 -0
  144. package/lib/README.md +10 -8
  145. package/package.json +3 -3
  146. package/dist/generators/_connections.py +0 -331
  147. package/dist/generators/_events.py +0 -355
  148. package/dist/generators/_lookups.py +0 -114
  149. package/dist/generators/_naming.py +0 -185
  150. package/dist/generators/_objects.py +0 -299
  151. package/dist/generators/_resolve.py +0 -319
  152. package/dist/generators/convert_library_to_md.py +0 -1441
  153. package/dist/generators/extract_library.py +0 -125
  154. package/dist/generators/generate_connectors_ts.py +0 -936
  155. package/dist/generators/generate_library_json.py +0 -2527
  156. package/dist/generators/generate_triggers_ts.py +0 -110
  157. package/dist/generators/materialize_registry.py +0 -155
  158. package/dist/generators/prepare_connector.py +0 -1860
  159. package/dist/registry/python.d.ts +0 -26
  160. 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
- /** Override used by tests and source checkouts. */
54
+ /** @deprecated The pipeline is part of the package; accepted and ignored. */
51
55
  generatorPath?: string;
52
56
  }
53
- /** The generator's argv for these options. Exported to be tested directly. */
54
- export declare function generatorArgs(options: PrepareConnectorOptions): string[];
55
- export declare function prepareConnector(options: PrepareConnectorOptions): Promise<void>;
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
- /** The generator's argv for these options. Exported to be tested directly. */
20
- export function generatorArgs(options) {
21
- const args = [options.connectorKey, options.action];
22
- if (options.connectionId)
23
- args.push("--connection-id", options.connectionId);
24
- if (options.connection)
25
- args.push("--connection", options.connection);
26
- if (options.bindings)
27
- args.push("--bindings", options.bindings);
28
- if (options.bindConnection)
29
- args.push("--bind-connection", options.bindConnection);
30
- if (options.bindFolder)
31
- args.push("--bind-folder", options.bindFolder);
32
- if (options.noBindings)
33
- args.push("--no-bindings");
34
- if (options.objectName)
35
- args.push("--object", options.objectName);
36
- if (options.allObjects)
37
- args.push("--all-objects");
38
- for (const field of options.fields ?? [])
39
- args.push("-f", field);
40
- for (const one of options.resolve ?? [])
41
- args.push("--resolve", one);
42
- if (options.refreshLookups)
43
- args.push("--refresh-lookups");
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
- const args = generatorArgs(options);
64
- await runGenerator({
65
- script: options.generatorPath ?? packagedGenerator("prepare_connector.py"),
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 generator
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
- console.error(error instanceof Error ? error.message : String(error));
183
- return 1;
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>] [--python <exe>]",
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 pythonExecutable = value(argv, "--python");
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
+ }