@uipath/maestro-builder-sdk 6.18.9 → 6.18.10

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.
@@ -714,6 +714,11 @@
714
714
  "name": "exponentialBase",
715
715
  "isAttr": true,
716
716
  "type": "String"
717
+ },
718
+ {
719
+ "name": "enforceLimits",
720
+ "isAttr": true,
721
+ "type": "String"
717
722
  }
718
723
  ]
719
724
  },
@@ -25,6 +25,7 @@ import { Library } from '../core/library.js';
25
25
  import { decompileWithDiagnostics } from './decompile.js';
26
26
  import { CASE_IMPORT } from '../package-name.js';
27
27
  import { commentText } from '../core/comment-text.js';
28
+ import { pipelineFrontDoor } from '../registry/uip/pipeline-front-door.js';
28
29
  /** Derive a PascalCase base name from the case name for the default output file. */
29
30
  function baseName(plan) {
30
31
  const src = plan.name ?? plan.id ?? 'Case';
@@ -90,8 +91,7 @@ function pipelineScript(originalPath, tsPath, pipelinePath, importSpecifier, bes
90
91
  const base = pipelinePath.replace(/\.pipeline\.mjs$/, '');
91
92
  return `#!/usr/bin/env node
92
93
  // Generated by case decompile — edit ${commentText(basename(tsPath))}, then run this file.
93
- import { execFileSync, spawnSync } from 'node:child_process';
94
- import { existsSync } from 'node:fs';
94
+ ${pipelineFrontDoor('case')}
95
95
 
96
96
  const ORIGINAL = ${JSON.stringify(originalPath)};
97
97
  const TS = ${JSON.stringify(tsPath)};
@@ -100,12 +100,6 @@ const IMPORT = ${JSON.stringify(importSpecifier)};
100
100
  const LIBRARY = ${JSON.stringify(libraryDir)};
101
101
  const BEST_EFFORT = ${JSON.stringify(bestEffort)};
102
102
 
103
- const uipProbe = spawnSync('uip', ['maestro', 'case', 'compile', '--help'], { stdio: 'ignore' });
104
- const frontDoor = !uipProbe.error && uipProbe.status === 0
105
- ? { file: 'uip', prefix: ['maestro', 'case'] }
106
- : { file: 'npx', prefix: ['--no-install', 'flow-sdk', 'case'] };
107
- const run = (verb, args) =>
108
- execFileSync(frontDoor.file, [...frontDoor.prefix, verb, ...args], { stdio: 'inherit' });
109
103
  const withLibrary = (args) => LIBRARY ? [...args, '--library', LIBRARY] : args;
110
104
 
111
105
  if (!existsSync(TS)) {
@@ -26,6 +26,7 @@ import { resolveFlowRefs } from './ref-resolve.js';
26
26
  import { flagValue, runWhenInvokedDirectly } from './cli-run.js';
27
27
  import { PACKAGE_NAME } from './package-name.js';
28
28
  import { commentText } from './core/comment-text.js';
29
+ import { pipelineFrontDoor } from './registry/uip/pipeline-front-door.js';
29
30
  /** Derive a PascalCase base name from the flow name/id for the default output file. */
30
31
  function baseName(flow) {
31
32
  const src = flow.name ?? flow.id ?? 'Flow';
@@ -103,8 +104,7 @@ function pipelineScript(originalPath, tsPath, pipelinePath) {
103
104
  return `#!/usr/bin/env node
104
105
  // Generated by flow-decompile — the brownfield read/modify/write pipeline for
105
106
  // ${commentText(basename(originalPath))}. Run it, edit ${commentText(basename(tsPath))}, run it again.
106
- import { execFileSync, spawnSync } from 'node:child_process';
107
- import { existsSync } from 'node:fs';
107
+ ${pipelineFrontDoor('flow')}
108
108
 
109
109
  const ORIGINAL = ${JSON.stringify(originalPath)};
110
110
  const TS = ${JSON.stringify(tsPath)};
@@ -112,13 +112,6 @@ const BASELINE = ${JSON.stringify(base + '.baseline.flow')};
112
112
  const EDITED = ${JSON.stringify(base + '.edited.flow')};
113
113
  const MERGED = ${JSON.stringify(base + '.merged.flow')};
114
114
 
115
- const uipProbe = spawnSync('uip', ['maestro', 'flow', 'compile', '--help'], { stdio: 'ignore' });
116
- const frontDoor = !uipProbe.error && uipProbe.status === 0
117
- ? { file: 'uip', prefix: ['maestro', 'flow'] }
118
- : { file: 'npx', prefix: ['--no-install', 'flow-sdk', 'flow'] };
119
- const run = (verb, args) =>
120
- execFileSync(frontDoor.file, [...frontDoor.prefix, verb, ...args], { stdio: 'inherit' });
121
-
122
115
  // Ensure the decompiled source exists (a fresh clone may have only the .flow).
123
116
  if (!existsSync(TS)) run('decompile', [ORIGINAL, '-o', TS, '--no-pipeline']);
124
117
 
@@ -1,3 +1,4 @@
1
+ import { win32 } from "node:path";
1
2
  export interface Launch {
2
3
  file: string;
3
4
  args: string[];
@@ -13,12 +14,21 @@ export interface LaunchHost {
13
14
  execPath: string;
14
15
  isFile(path: string): boolean;
15
16
  readFile(path: string): string;
17
+ /** Windows path operations (`node:path`'s `win32`), passed in so {@link resolveLaunch} imports nothing. */
18
+ win32: Pick<typeof win32, "join" | "dirname" | "extname">;
16
19
  }
20
+ /** The host this process runs on. */
21
+ export declare function ambientLaunchHost(): LaunchHost;
17
22
  /**
18
- * The file and arguments to spawn for `executable args`, without a shell.
23
+ * The file and arguments to spawn for `executable args` on `host`, without a shell.
24
+ *
25
+ * Self-contained on purpose (see the module comment): generated scripts embed
26
+ * `resolveLaunch.toString()`.
19
27
  *
20
28
  * @throws when, on Windows, the name resolves to a batch shim this cannot read
21
29
  * a script out of — naming the shim, so the remedy is not "put uip on PATH"
22
30
  * for a `uip` that is on PATH.
23
31
  */
32
+ export declare function resolveLaunch(executable: string, args: readonly string[], host: LaunchHost): Launch;
33
+ /** {@link resolveLaunch} on this process's host. */
24
34
  export declare function launchFor(executable: string, args: readonly string[], host?: LaunchHost): Launch;
@@ -22,6 +22,13 @@
22
22
  * A name that is not bare (a path, or one with an extension) is spawned as
23
23
  * given, and so is anything on another platform.
24
24
  *
25
+ * The rules live in ONE self-contained function, {@link resolveLaunch}, because
26
+ * a second copy runs outside this package: the brownfield pipeline scripts that
27
+ * `flow decompile` and `case decompile` write embed its source text
28
+ * (`pipeline-front-door.ts`, flow-builder-sdk#1120). So it may reference only
29
+ * its parameters and globals — no module constant, no import — and a test runs
30
+ * its source text in an empty context to hold that.
31
+ *
25
32
  * Example, on Windows with npm's global bin on PATH:
26
33
  * launchFor("uip", ["--version"])
27
34
  * // -> { file: "C:\\Program Files\\nodejs\\node.exe",
@@ -29,14 +36,8 @@
29
36
  */
30
37
  import { readFileSync, statSync } from "node:fs";
31
38
  import { win32 } from "node:path";
32
- const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
33
- /**
34
- * The script an npm (`cmd-shim`) or pnpm shim hands its arguments to: the last
35
- * quoted `%dp0%\…` / `%~dp0\…` path followed by `%*`. Only a JavaScript target
36
- * counts; a shim around something else is not one this module can run directly.
37
- */
38
- const SHIM_TARGET = /"%~?dp0%?\\([^"]+\.[cm]?js)"\s+%\*/g;
39
- function ambientHost() {
39
+ /** The host this process runs on. */
40
+ export function ambientLaunchHost() {
40
41
  return {
41
42
  platform: process.platform,
42
43
  path: process.env.PATH,
@@ -51,55 +52,61 @@ function ambientHost() {
51
52
  }
52
53
  },
53
54
  readFile: (path) => readFileSync(path, "utf8"),
55
+ win32,
54
56
  };
55
57
  }
56
- /** True for a name `PATH` lookup applies to: no directory part and no extension. */
57
- function isBareName(executable) {
58
- return executable !== "" && !/[\\/]/.test(executable) && win32.extname(executable) === "";
59
- }
60
- /** The first `<dir>\<executable><ext>` on PATH, in cmd.exe's order, or undefined. */
61
- function findOnPath(executable, host) {
62
- const extensions = (host.pathExt || DEFAULT_PATHEXT).split(";").filter(Boolean);
63
- for (const dir of (host.path ?? "").split(";")) {
64
- const trimmed = dir.trim().replace(/^"(.*)"$/, "$1");
65
- if (trimmed === "")
66
- continue;
67
- for (const ext of extensions) {
68
- const candidate = win32.join(trimmed, executable + ext.toLowerCase());
69
- if (host.isFile(candidate))
70
- return candidate;
71
- }
72
- }
73
- return undefined;
74
- }
75
- /** The script a `.cmd`/`.bat` shim runs, as an absolute path, or undefined. */
76
- function shimTarget(shim, host) {
77
- const matches = [...host.readFile(shim).matchAll(SHIM_TARGET)];
78
- const last = matches.at(-1);
79
- return last ? win32.join(win32.dirname(shim), last[1]) : undefined;
80
- }
81
58
  /**
82
- * The file and arguments to spawn for `executable args`, without a shell.
59
+ * The file and arguments to spawn for `executable args` on `host`, without a shell.
60
+ *
61
+ * Self-contained on purpose (see the module comment): generated scripts embed
62
+ * `resolveLaunch.toString()`.
83
63
  *
84
64
  * @throws when, on Windows, the name resolves to a batch shim this cannot read
85
65
  * a script out of — naming the shim, so the remedy is not "put uip on PATH"
86
66
  * for a `uip` that is on PATH.
87
67
  */
88
- export function launchFor(executable, args, host = ambientHost()) {
89
- if (host.platform !== "win32" || !isBareName(executable))
68
+ export function resolveLaunch(executable, args, host) {
69
+ const path = host.win32;
70
+ // Bare: no directory part and no extension. Only a bare name is looked up.
71
+ const bare = executable !== "" && !/[\\/]/.test(executable) && path.extname(executable) === "";
72
+ if (host.platform !== "win32" || !bare)
90
73
  return { file: executable, args: [...args] };
91
- const found = findOnPath(executable, host);
74
+ // The first `<dir>\<executable><ext>` on PATH, in cmd.exe's PATHEXT order.
75
+ const extensions = (host.pathExt || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
76
+ let found;
77
+ search: for (const dir of (host.path ?? "").split(";")) {
78
+ const trimmed = dir.trim().replace(/^"(.*)"$/, "$1");
79
+ if (trimmed === "")
80
+ continue;
81
+ for (const ext of extensions) {
82
+ const candidate = path.join(trimmed, executable + ext.toLowerCase());
83
+ if (host.isFile(candidate)) {
84
+ found = candidate;
85
+ break search;
86
+ }
87
+ }
88
+ }
92
89
  // Not on PATH: spawn the bare name and let the ENOENT say so.
93
- if (!found)
90
+ if (found === undefined)
94
91
  return { file: executable, args: [...args] };
95
- const ext = win32.extname(found).toLowerCase();
92
+ const ext = path.extname(found).toLowerCase();
96
93
  if (ext !== ".cmd" && ext !== ".bat")
97
94
  return { file: found, args: [...args] };
98
- const script = shimTarget(found, host);
99
- if (!script || !host.isFile(script)) {
95
+ // The script an npm (cmd-shim) or pnpm shim hands its arguments to: the last
96
+ // quoted `%dp0%\…` / `%~dp0\…` JavaScript path followed by `%*`.
97
+ const matches = [...host.readFile(found).matchAll(/"%~?dp0%?\\([^"]+\.[cm]?js)"\s+%\*/g)];
98
+ const last = matches[matches.length - 1];
99
+ const script = last ? path.join(path.dirname(found), last[1]) : undefined;
100
+ if (script === undefined || !host.isFile(script)) {
100
101
  throw new Error(`Could not start ${executable}: ${found} is a batch file, which Node cannot run without a shell, ` +
101
- (script ? `and the script it names (${script}) does not exist.` : "and it names no JavaScript entry point to run instead.") +
102
- " Reinstall the UiPath CLI with npm (`npm install -g @uipath/cli`).");
102
+ (script !== undefined
103
+ ? `and the script it names (${script}) does not exist.`
104
+ : "and it names no JavaScript entry point to run instead.") +
105
+ " Reinstall it with npm (for the UiPath CLI, `npm install -g @uipath/cli`).");
103
106
  }
104
107
  return { file: host.execPath, args: [script, ...args] };
105
108
  }
109
+ /** {@link resolveLaunch} on this process's host. */
110
+ export function launchFor(executable, args, host = ambientLaunchHost()) {
111
+ return resolveLaunch(executable, args, host);
112
+ }
@@ -0,0 +1,2 @@
1
+ /** The script text: imports, the embedded resolver, `frontDoor`, and `run(verb, args)`. */
2
+ export declare function pipelineFrontDoor(kind: 'flow' | 'case'): string;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The "front door" block of a generated brownfield pipeline script: how the
3
+ * script finds a CLI to run `compile`, `decompile` and `merge` with.
4
+ *
5
+ * `flow decompile` and `case decompile` each write a `<Name>.pipeline.mjs`
6
+ * beside the source. Both used to carry their own copy of this block, and both
7
+ * spawned the bare names `uip` and `npx` without a shell. On Windows both are
8
+ * `.cmd` shims, which Node cannot start that way, so the probe for `uip` failed,
9
+ * the `npx` fallback failed too, and the pipeline could not run at all
10
+ * (flow-builder-sdk#1120, the same defect as #940 in the registry).
11
+ *
12
+ * Now there is one block, and it starts programs through the same rules the
13
+ * registry uses: it embeds the source text of `resolveLaunch`
14
+ * (`registry/uip/launch.ts`), so a fix there reaches every pipeline written
15
+ * after it. The order is unchanged:
16
+ * 1. `uip maestro <kind>`, when `uip maestro <kind> compile --help` exits 0
17
+ * (a CLI that carries the authoring commands);
18
+ * 2. otherwise the SDK's own `flow-sdk` bin, found the way
19
+ * `npx --no-install flow-sdk` found it: `node_modules/.bin` in the script's
20
+ * folder and every folder above it, then `PATH`.
21
+ *
22
+ * The block brings its own imports, `existsSync` among them, and defines
23
+ * `run(verb, args)`; the pipeline body uses both. Nothing in it names a path on
24
+ * the machine that generated it, so a pipeline stays relocatable.
25
+ *
26
+ * Example, for `kind = 'flow'` on a machine whose `uip` has the commands:
27
+ * run('compile', ['A.flow.ts', '-o', 'A.edited.flow'])
28
+ * // POSIX: uip maestro flow compile A.flow.ts -o A.edited.flow
29
+ * // Windows: node.exe <npm>\node_modules\@uipath\cli\dist\index.js maestro flow compile …
30
+ */
31
+ import { resolveLaunch } from './launch.js';
32
+ /** The script text: imports, the embedded resolver, `frontDoor`, and `run(verb, args)`. */
33
+ export function pipelineFrontDoor(kind) {
34
+ return `import { execFileSync, spawnSync } from 'node:child_process';
35
+ import { existsSync, readFileSync, statSync } from 'node:fs';
36
+ import { dirname, join, win32 } from 'node:path';
37
+ import { fileURLToPath } from 'node:url';
38
+
39
+ const KIND = ${JSON.stringify(kind)};
40
+
41
+ // How to start a program by name without a shell, Windows .cmd shims included.
42
+ // Copied from the SDK's registry/uip/launch.ts when this file was generated.
43
+ ${resolveLaunch.toString()}
44
+
45
+ const launchHost = (path) => ({
46
+ platform: process.platform,
47
+ path,
48
+ pathExt: process.env.PATHEXT,
49
+ execPath: process.execPath,
50
+ isFile: (p) => { try { return statSync(p).isFile(); } catch { return false; } },
51
+ readFile: (p) => readFileSync(p, 'utf8'),
52
+ win32,
53
+ });
54
+
55
+ // 1. \`uip maestro <kind>\`, when this CLI carries the authoring commands.
56
+ function uipFrontDoor() {
57
+ let uip;
58
+ try {
59
+ uip = resolveLaunch('uip', [], launchHost(process.env.PATH));
60
+ } catch {
61
+ return undefined; // a uip shim that cannot be read: use the SDK instead
62
+ }
63
+ const probe = spawnSync(uip.file, [...uip.args, 'maestro', KIND, 'compile', '--help'], { stdio: 'ignore' });
64
+ return !probe.error && probe.status === 0 ? { file: uip.file, prefix: [...uip.args, 'maestro', KIND] } : undefined;
65
+ }
66
+
67
+ // 2. The SDK's flow-sdk bin, where \`npx --no-install flow-sdk\` looked for it.
68
+ function sdkFrontDoor() {
69
+ const bins = [];
70
+ for (let dir = dirname(fileURLToPath(import.meta.url)); ; dir = dirname(dir)) {
71
+ bins.push(join(dir, 'node_modules', '.bin'));
72
+ if (dirname(dir) === dir) break;
73
+ }
74
+ let sdk;
75
+ if (process.platform === 'win32') {
76
+ sdk = resolveLaunch('flow-sdk', [], launchHost([...bins, process.env.PATH ?? ''].join(';')));
77
+ } else {
78
+ const local = bins.map((bin) => join(bin, 'flow-sdk')).find((p) => existsSync(p));
79
+ sdk = { file: local ?? 'flow-sdk', args: [] };
80
+ }
81
+ return { file: sdk.file, prefix: [...sdk.args, KIND] };
82
+ }
83
+
84
+ const frontDoor = uipFrontDoor() ?? sdkFrontDoor();
85
+ const run = (verb, args) =>
86
+ execFileSync(frontDoor.file, [...frontDoor.prefix, verb, ...args], { stdio: 'inherit' });
87
+ `;
88
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.18.9",
3
+ "version": "6.18.10",
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",
@@ -92,5 +92,5 @@
92
92
  "@types/node": "^22.7.0",
93
93
  "esbuild": "^0.28.1"
94
94
  },
95
- "gitref": "6f8e3c88230704c5b5bc0e8983be45ebedd135dc"
95
+ "gitref": "0bbd0a5b9951750e0b3009b17b0188e485266cbe"
96
96
  }