@uipath/maestro-builder-sdk 6.70.4 → 6.70.6

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.
@@ -24,7 +24,7 @@
24
24
  */
25
25
  import { diagnosticLine, reportDiagnostics, reportResult, thrownDiagnostic } from './cli-result.js';
26
26
  import { writeJsonFile } from './core/json-file.js';
27
- import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
27
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync } from 'node:fs';
28
28
  import { basename, dirname, join, relative, resolve } from 'node:path';
29
29
  import { resolveFlowFile, loadBuiltFlow } from './load.js';
30
30
  import { serializeWithSidecars } from './serialize.js';
@@ -36,7 +36,7 @@ import { connectionResources, inlineAgentResources, mergeBindingsV2, refreshInli
36
36
  import { readBindingsV2, readInlineAgentFiles } from './bindings-v2-files.js';
37
37
  import { ensureTypeScriptRuntime } from './node-runtime.js';
38
38
  import { defaultBindingsFile } from './workdir.js';
39
- import { isWithin } from './path-within.js';
39
+ import { isWithin, physicalPath } from './path-within.js';
40
40
  import { flowSources, resourceSourcesOfOtherFlows } from './project-resource-users.js';
41
41
  import { fnv1aX4Uuid } from './core/stable-id.js';
42
42
  import { CompileRefusal } from './core/compile-refusal.js';
@@ -86,7 +86,20 @@ export async function run(argv) {
86
86
  // BYOA-only event on a connection `prepare` never checked
87
87
  // (EVENT_BYOA_UNVERIFIED): printed in the same `✗ [CODE]` shape `check` uses
88
88
  // for it. Nothing is written.
89
- const out = flagValue(argv, '-o') ?? `${built.name || basename(file).replace(/\.flow\.ts$|\.ts$/, '')}.flow`;
89
+ const explicitOut = flagValue(argv, '-o');
90
+ const out = explicitOut ?? `${built.name || basename(file).replace(/\.flow\.ts$|\.ts$/, '')}.flow`;
91
+ // The default is named after the flow, which decompile copies from the input: keep it inside cwd.
92
+ if (explicitOut === undefined) {
93
+ const target = physicalPath(resolve(out));
94
+ if (target === undefined || !isWithin(realpathSync.native(process.cwd()), target)) {
95
+ const message = `refusing to write ${out} outside the working directory: without -o the output is named after`
96
+ + ` the flow (${JSON.stringify(built.name)}). Pass -o <file>.flow to choose where it is written`;
97
+ console.error(`compile: ${message}; not emitting.`);
98
+ diagnostics.push({ level: 'error', code: 'FLOW_NAME_OUTSIDE_WORKING_DIR', message });
99
+ reportDiagnostics(diagnostics);
100
+ return 1;
101
+ }
102
+ }
90
103
  const outDir = dirname(resolve(out));
91
104
  // ONE FOLDER, ONE FLOW (#1184). An agent's `source` is the name of the folder
92
105
  // beside the `.flow` that holds its `agent.json` and `resources/`. Decompile
package/dist/decompile.js CHANGED
@@ -35,6 +35,7 @@ import { DEFAULT_TRIGGER_ID, Expr } from './core/expr.js';
35
35
  import { FLOW_NODE_TYPES } from './flow-node-registry.js';
36
36
  import { SUBFLOW_START_LABEL } from './core/subflow-start.js';
37
37
  import { commentText } from './core/comment-text.js';
38
+ import { escapeRegExp } from './core/escape-regexp.js';
38
39
  import { tsKey as key, tsLiteral, tsNumber, tsObject as objLit, tsOptions as opts, tsText } from './core/ts-literal.js';
39
40
  import { wireExprBody } from './core/wire-expr.js';
40
41
  import { hasExits } from './core/step-ports.js';
@@ -2864,7 +2865,7 @@ function emitChildFlow(name, childFile, ctx) {
2864
2865
  */
2865
2866
  function readChildStartAsStart(child, startId) {
2866
2867
  const renamed = renameVarRoot(child, startId, DEFAULT_TRIGGER_ID);
2867
- const path = new RegExp(`^(=?vars\\.)${startId.replace(/[$]/g, '\\$&')}(?![A-Za-z0-9_$])`);
2868
+ const path = new RegExp(`^(=?vars\\.)${escapeRegExp(startId)}(?![A-Za-z0-9_$])`);
2868
2869
  const walk = (x, key, parentKey) => {
2869
2870
  if (typeof x === 'string')
2870
2871
  return key === 'binding' || parentKey === 'appInputBindings' ? x.replace(path, `$1${DEFAULT_TRIGGER_ID}`) : x;
@@ -2954,8 +2955,9 @@ const RAW_JS_FIELDS = new Set(['script', 'expression', 'condition', 'completionC
2954
2955
  * script stays as written, the way `serialize` left it (#1092 review).
2955
2956
  */
2956
2957
  function renameVarRoot(v, from, to) {
2957
- const re = new RegExp(`\\$vars\\.${from.replace(/[$]/g, '\\$&')}(?![A-Za-z0-9_$])`, 'g');
2958
- const inCode = (js) => mapJsCode(js, (code) => code.replace(re, `$vars.${to}`));
2958
+ const re = new RegExp(`\\$vars\\.${escapeRegExp(from)}(?![A-Za-z0-9_$])`, 'g');
2959
+ // a function: `$&` in `to` is data
2960
+ const inCode = (js) => mapJsCode(js, (code) => code.replace(re, () => `$vars.${to}`));
2959
2961
  const walk = (x, key) => {
2960
2962
  if (typeof x === 'string') {
2961
2963
  if (x.startsWith('=js:'))
@@ -1,20 +1,3 @@
1
- /**
2
- * path-within — the one path-containment test for anything that writes files.
3
- *
4
- * "Is `child` inside `parent`?" looks like a string-prefix question and is not
5
- * one. `child.startsWith(parent + '/')` is correct only where the separator is
6
- * `/`: on Windows `path.resolve` returns `C:\proj\P\x`, the check refuses every
7
- * path, and compile refused every inline agent's `<source>/agent.json`
8
- * (flow-builder-sdk#946). A prefix test without a trailing separator has the
9
- * opposite bug: it accepts the sibling `C:\proj\P2\x` as inside `C:\proj\P`.
10
- *
11
- * So the test is `relative(parent, child)` instead, which gets both right on
12
- * every platform, and Windows' case-insensitive drive letters and
13
- * cross-drive paths (`relative` returns the absolute `D:\x` there) with them.
14
- *
15
- * The path implementation is a parameter so the Windows rules can be tested on
16
- * a Linux runner (`isWithin(a, b, path.win32)`). Callers leave it out.
17
- */
18
1
  import * as nodePath from 'node:path';
19
2
  /** The slice of `node:path` the test needs; `path.win32` and `path.posix` both fit. */
20
3
  export type PathApi = Pick<typeof nodePath, 'relative' | 'isAbsolute' | 'sep'>;
@@ -31,3 +14,18 @@ export type PathApi = Pick<typeof nodePath, 'relative' | 'isAbsolute' | 'sep'>;
31
14
  * isWithin('C:\\proj\\P', 'D:\\agent.json', path.win32) // false — other drive
32
15
  */
33
16
  export declare function isWithin(parent: string, child: string, path?: PathApi): boolean;
17
+ /**
18
+ * Where `dir` lands on disk: the real path of its deepest ancestor that
19
+ * exists, every link on the way followed, then the rest of `dir`, which does
20
+ * not exist yet and so holds no link. `undefined` when that ancestor's real
21
+ * path cannot be read: a link to nothing, or a loop of links. A caller treats
22
+ * `undefined` as outside.
23
+ *
24
+ * The agent SDK's CLI keeps a copy of this function for its default output
25
+ * folder, so both CLIs follow links the same way.
26
+ *
27
+ * @example
28
+ * // ./work/sw-basic/l is a link to /elsewhere, and X does not exist:
29
+ * physicalPath('/cwd/work/sw-basic/l/X') // '/elsewhere/X'
30
+ */
31
+ export declare function physicalPath(dir: string): string | undefined;
@@ -12,10 +12,16 @@
12
12
  * every platform, and Windows' case-insensitive drive letters and
13
13
  * cross-drive paths (`relative` returns the absolute `D:\x` there) with them.
14
14
  *
15
+ * `isWithin` reads only the spelling of its paths, so a link inside `parent`
16
+ * that points out of it passes. A check that guards a write compares
17
+ * `physicalPath`s instead.
18
+ *
15
19
  * The path implementation is a parameter so the Windows rules can be tested on
16
20
  * a Linux runner (`isWithin(a, b, path.win32)`). Callers leave it out.
17
21
  */
22
+ import { lstatSync, realpathSync } from 'node:fs';
18
23
  import * as nodePath from 'node:path';
24
+ import { basename, dirname, join } from 'node:path';
19
25
  /**
20
26
  * `child` is `parent` itself or somewhere beneath it.
21
27
  *
@@ -39,3 +45,41 @@ export function isWithin(parent, child, path = nodePath) {
39
45
  // Windows: a child on another drive has no relative spelling.
40
46
  return !path.isAbsolute(rel);
41
47
  }
48
+ /**
49
+ * Where `dir` lands on disk: the real path of its deepest ancestor that
50
+ * exists, every link on the way followed, then the rest of `dir`, which does
51
+ * not exist yet and so holds no link. `undefined` when that ancestor's real
52
+ * path cannot be read: a link to nothing, or a loop of links. A caller treats
53
+ * `undefined` as outside.
54
+ *
55
+ * The agent SDK's CLI keeps a copy of this function for its default output
56
+ * folder, so both CLIs follow links the same way.
57
+ *
58
+ * @example
59
+ * // ./work/sw-basic/l is a link to /elsewhere, and X does not exist:
60
+ * physicalPath('/cwd/work/sw-basic/l/X') // '/elsewhere/X'
61
+ */
62
+ export function physicalPath(dir) {
63
+ const rest = [];
64
+ let at = dir;
65
+ for (;;) {
66
+ try {
67
+ lstatSync(at);
68
+ break;
69
+ }
70
+ catch {
71
+ // Missing: its parent, then.
72
+ }
73
+ const parent = dirname(at);
74
+ if (parent === at)
75
+ break;
76
+ rest.unshift(basename(at));
77
+ at = parent;
78
+ }
79
+ try {
80
+ return join(realpathSync.native(at), ...rest);
81
+ }
82
+ catch {
83
+ return undefined;
84
+ }
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.70.4",
3
+ "version": "6.70.6",
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",
@@ -100,5 +100,5 @@
100
100
  "@types/node": "^22.7.0",
101
101
  "esbuild": "^0.28.1"
102
102
  },
103
- "gitref": "e9ad3d42f515a19663813c7befb4f6afdea2b87c"
103
+ "gitref": "acb0888df4770ee4db5ebb89ef2116cf400ee1b9"
104
104
  }