@uipath/maestro-builder-sdk 6.70.6 → 6.70.8

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.
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Reading another task's output in place: the SDD's `vars.$xref('<stage>',
3
+ * '<task>', '<output>')` marker, and `taskOutput()`, the typed way to write it
4
+ * (builder-sdk#1241).
5
+ *
6
+ * A task's own output is a variable of its own: the Case Designer saves it as
7
+ * `{ name: 'AgentExpenseRequest', id: 'agentExpenseRequest', var:
8
+ * 'agentExpenseRequest' }`, with no root `variables` entry, and a later task
9
+ * reads it as `=vars.agentExpenseRequest`. The id is the plan's, not the
10
+ * author's: it is unique across the plan, so a second task's `Action` output
11
+ * is `action2`. An SDD therefore names an output by where it is produced, and
12
+ * `uip maestro case sdd convert` lowers each marker to `vars.<output id>`
13
+ * (`sdd-convert/xrefs.ts`). Without the lowering here, an author had to copy
14
+ * the value into a case variable the SDD does not declare, and `validate
15
+ * --strict --sdd` refuses each one (`STRICT_SDD_VARIABLE_EXTRA`).
16
+ *
17
+ * The rule lives here so `check` (at `build()`) and `serialize` read a marker
18
+ * the same way. `build()` cannot see the ids, which `serialize` mints, so it
19
+ * resolves the stage and the task, and the output where the task's rows are
20
+ * all authored (a reference-mode task). `serialize` resolves every marker
21
+ * against the rows it wrote and refuses one that names no output.
22
+ */
23
+ /**
24
+ * One argument, read by its own delimiter: `'…'`, `"…"`, `` `…` `` or the curly
25
+ * pair, the four `sdd convert` accepts. A curly pair is two code points, so it
26
+ * gets its own branch instead of a backreference.
27
+ */
28
+ const QUOTED = String.raw `(?:'((?:\\.|[^'\\])*)'|"((?:\\.|[^"\\])*)"|` +
29
+ '`((?:\\\\.|[^`\\\\])*)`' +
30
+ String.raw `|\u201c([^\u201d]*)\u201d)`;
31
+ /** A `vars.$xref(stage, task, output)` marker. Global: use with `replace`/`matchAll` only. */
32
+ const XREF_CALL = new RegExp(String.raw `vars\.\$xref\(\s*${QUOTED}\s*,\s*${QUOTED}\s*,\s*${QUOTED}\s*\)`, 'g');
33
+ /**
34
+ * A property read glued to the marker (`vars.$xref(…).unusualClauses`). The
35
+ * producer may not have run when the reader does, so inside a `=js:`
36
+ * expression the lowering writes the optional chain
37
+ * `vars.<id>?.unusualClauses`, as `sdd convert` does. Only there: `?.` is
38
+ * JavaScript, and nothing shows that the platform's non-js `=vars.<path>`
39
+ * binding reads it, so any other value keeps the plain `.`.
40
+ */
41
+ const DOTTED_READ = /^\.[A-Za-z_$]/;
42
+ const unescape = (value) => value.replace(/\\(.)/g, '$1');
43
+ /** Each marker in `text`, in order, with where it sits. */
44
+ export function taskOutputRefs(text) {
45
+ if (!text.includes('$xref'))
46
+ return [];
47
+ return [...text.matchAll(XREF_CALL)].map((m) => {
48
+ const arg = (from) => unescape(m.slice(from, from + 4).find((v) => v !== undefined) ?? '');
49
+ return { stage: arg(1), task: arg(5), output: arg(9), whole: m[0], index: m.index ?? 0 };
50
+ });
51
+ }
52
+ /**
53
+ * Replace every marker in `text` with what `lower` returns for it. In a `=js:`
54
+ * expression, a marker followed by a property read gets the optional chain
55
+ * (`vars.<id>?.field`); any other value keeps the plain read (`vars.<id>.field`).
56
+ *
57
+ * @param text - The expression.
58
+ * @param lower - The variable a marker reads (`agentExpenseRequest`), or `undefined` to keep the marker.
59
+ */
60
+ export function lowerTaskOutputRefs(text, lower) {
61
+ if (!text.includes('$xref'))
62
+ return text;
63
+ const js = /^\s*=js:/.test(text);
64
+ let out = '';
65
+ let at = 0;
66
+ for (const ref of taskOutputRefs(text)) {
67
+ const after = text.slice(ref.index + ref.whole.length);
68
+ const variable = lower(ref);
69
+ // `undefined` keeps the marker as written (a reference to a stand-in; see `resolveInPlan`).
70
+ out += variable === undefined ? text.slice(at, ref.index) + ref.whole : `${text.slice(at, ref.index)}vars.${variable}${js && DOTTED_READ.test(after) ? '?' : ''}`;
71
+ at = ref.index + ref.whole.length;
72
+ }
73
+ return out + text.slice(at);
74
+ }
75
+ /**
76
+ * The one candidate `name` picks out: an exact match first, then a
77
+ * case-insensitive one (an SDD addresses a stage or an output in any case).
78
+ * `undefined` when none does; `'ambiguous'` when more than one does.
79
+ */
80
+ export function pickByName(items, name, names) {
81
+ for (const same of [(a) => a === name, (a) => a.toLowerCase() === name.toLowerCase()]) {
82
+ const hits = [...new Set(items.filter((item) => names(item).some((n) => n !== undefined && same(n))))];
83
+ if (hits.length === 1)
84
+ return hits[0];
85
+ if (hits.length > 1)
86
+ return 'ambiguous';
87
+ }
88
+ return undefined;
89
+ }
90
+ /**
91
+ * The row an output reference names among one task's rows: by the field it
92
+ * reads (`name`) or by the variable it writes (`var`).
93
+ */
94
+ export function pickOutputRow(rows, output) {
95
+ return pickByName(rows, output, (row) => [row.name, row.var]);
96
+ }
97
+ /** The refusal message for a marker that resolves to nothing. */
98
+ export function unresolvedTaskOutputRef(ref, why) {
99
+ return `taskOutput(${JSON.stringify(ref.stage)}, ${JSON.stringify(ref.task)}, ${JSON.stringify(ref.output)}) ${why}. ` +
100
+ 'A task-output reference names the stage label, the task name and the output (the result field it reads, or the key it has in `.outputs()`).';
101
+ }
102
+ /**
103
+ * The marker for a reference: `vars.$xref('<stage>','<task>','<output>')`,
104
+ * each argument single-quoted with its quotes and backslashes escaped. What
105
+ * `taskOutput()` returns, and what decompile writes back.
106
+ */
107
+ export function taskOutputMarker(stage, task, output) {
108
+ const quote = (value) => `'${value.replace(/[\\']/g, '\\$&')}'`;
109
+ return `vars.$xref(${quote(stage)},${quote(task)},${quote(output)})`;
110
+ }
111
+ /** Every stage task's output rows in a written caseplan, in document order. */
112
+ export function planTaskOutputs(plan) {
113
+ const found = [];
114
+ const nodes = plan?.nodes;
115
+ for (const node of Array.isArray(nodes) ? nodes : []) {
116
+ const data = node?.data;
117
+ if (typeof data?.label !== 'string' || !Array.isArray(data.tasks))
118
+ continue;
119
+ for (const lane of data.tasks)
120
+ for (const task of Array.isArray(lane) ? lane : []) {
121
+ if (typeof task?.displayName !== 'string')
122
+ continue;
123
+ const rows = Array.isArray(task.data?.outputs) ? task.data.outputs : [];
124
+ found.push({
125
+ stage: data.label,
126
+ task: task.displayName,
127
+ rows: rows.filter((row) => row && typeof row === 'object'),
128
+ ...(task.data?.standIn === true ? { standIn: true } : {}),
129
+ });
130
+ }
131
+ }
132
+ return found;
133
+ }
134
+ /**
135
+ * What a reference reads in a written plan: the variable of the row it names
136
+ * (`vars.<id>`, or a custom row's `var`, which has no id), or why it names none.
137
+ */
138
+ export function resolveInPlan(tasks, ref) {
139
+ const stage = pickByName([...new Set(tasks.map((t) => t.stage))], ref.stage, (label) => [label]);
140
+ if (stage === undefined)
141
+ return { why: `names no stage labelled ${JSON.stringify(ref.stage)}` };
142
+ if (stage === 'ambiguous')
143
+ return { why: `names a stage label that more than one stage answers to` };
144
+ const task = pickByName(tasks.filter((t) => t.stage === stage), ref.task, (t) => [t.task]);
145
+ if (task === undefined)
146
+ return { why: `names no task ${JSON.stringify(ref.task)} in stage ${JSON.stringify(stage)}` };
147
+ if (task === 'ambiguous')
148
+ return { why: `names a task that more than one task in stage ${JSON.stringify(stage)} answers to` };
149
+ // The named task was refused and stood in (collect mode). Its refusal is the
150
+ // finding; its outputs were never written, so there is nothing to judge the
151
+ // reference against here (`build()` already judged it against the authored ones).
152
+ if (task.standIn)
153
+ return { standIn: true };
154
+ const row = pickOutputRow(task.rows, ref.output);
155
+ const offered = [...new Set(task.rows.map((r) => r.name).filter((n) => typeof n === 'string'))];
156
+ if (row === undefined)
157
+ return { why: `names no output of task ${JSON.stringify(task.task)}, whose outputs are ${offered.length ? offered.map((n) => JSON.stringify(n)).join(', ') : 'none'}` };
158
+ if (row === 'ambiguous')
159
+ return { why: `names more than one output of task ${JSON.stringify(task.task)}; name it by the variable it writes instead` };
160
+ const variable = typeof row.id === 'string' && row.id ? row.id : row.var;
161
+ if (typeof variable !== 'string' || !variable)
162
+ return { why: `names an output of task ${JSON.stringify(task.task)} that writes no variable` };
163
+ return { variable };
164
+ }
165
+ /**
166
+ * Turn each `vars.<output id>` read in `text` that only a task-output reference
167
+ * can say back into `vars.$xref(…)`, for decompile. `byId` maps such an id to
168
+ * the reference that names it.
169
+ */
170
+ export function raiseTaskOutputReads(text, byId) {
171
+ if (!byId.size || !text.includes('vars.'))
172
+ return text;
173
+ return text.replace(/(?<![\w$.])vars\.([A-Za-z_$][\w$]*)(?![\w$])/g, (whole, id) => {
174
+ const ref = byId.get(id);
175
+ return ref ? taskOutputMarker(ref.stage, ref.task, ref.output) : whole;
176
+ });
177
+ }
178
+ /**
179
+ * The output ids a decompiled source can only read back by reference: a row
180
+ * whose id is not the variable it writes (a reassigned extract, a Designer row
181
+ * such as `{ id: 'oA1b2C3d4', var: 'AgentExpenseRequest' }`). Recompiling mints
182
+ * that id afresh, and no declaration makes `vars.<id>` readable, so a
183
+ * `vars.<id>` read in the plan goes back to the source as the reference that
184
+ * resolves to the same row. A read of an id that is also a variable (an
185
+ * own-name row, a root variable) is left as written. A row the reference
186
+ * could not name uniquely is left as written too.
187
+ */
188
+ export function referableOutputIds(plan, rootNames) {
189
+ const tasks = planTaskOutputs(plan);
190
+ const vars = new Set(rootNames);
191
+ for (const task of tasks)
192
+ for (const row of task.rows)
193
+ if (typeof row.var === 'string')
194
+ vars.add(row.var);
195
+ const byId = new Map();
196
+ for (const task of tasks)
197
+ for (const row of task.rows) {
198
+ if (typeof row.id !== 'string' || !row.id || row.id === row.var || vars.has(row.id))
199
+ continue;
200
+ for (const output of [row.name, row.var]) {
201
+ if (typeof output !== 'string' || !output)
202
+ continue;
203
+ const ref = { stage: task.stage, task: task.task, output };
204
+ const resolved = resolveInPlan(tasks, ref);
205
+ if ('variable' in resolved && resolved.variable === row.id) {
206
+ byId.set(row.id, ref);
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ return byId;
212
+ }
@@ -5,7 +5,7 @@
5
5
  * from the inner loop as from compile. Before this, `check` printed `0 error(s)`
6
6
  * for a source `compile` then refused (teams-decision, 2026-09-01, turn 22 → 23).
7
7
  */
8
- import { prepareCommand } from './cli-spelling.js';
8
+ import { placeholder, prepareCommand } from './cli-spelling.js';
9
9
  import { CompileRefusal } from './compile-refusal.js';
10
10
  /** A tenant resource key — an Integration Service connection id or an Orchestrator folder key — is a GUID. */
11
11
  export const GUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -96,7 +96,7 @@ export function bindingLabelRefusal(bindings, name, attribute, where, target) {
96
96
  return undefined;
97
97
  }
98
98
  function prepareFor(target) {
99
- return prepareCommand(target?.key ?? '<connector-key>', target?.action ?? '<action>');
99
+ return prepareCommand(target?.key ?? placeholder('<connector-key>'), target?.action ?? placeholder('<action>'));
100
100
  }
101
101
  /**
102
102
  * Why compile refuses `name`, and how to bind it.
@@ -143,7 +143,7 @@ export function connectionConnectorConflictMessage(connection, users) {
143
143
  return `connection "${connection}" is used by steps of different connectors (${who}). An Integration Service `
144
144
  + `connection belongs to exactly one connector, so at most one of these steps can run on it, and the solution `
145
145
  + `resource would be declared as whichever came first. Give each connector its own connection label in `
146
- + `bindings.json (\`${prepareCommand('<connector-key>', '<action>')}\` binds one per connector). A managed `
146
+ + `bindings.json (\`${prepareCommand(placeholder('<connector-key>'), placeholder('<action>'))}\` binds one per connector). A managed `
147
147
  + `\`http({ managed: true, … })\` step on another connector's connection must name that connector in `
148
148
  + `\`targetConnector\`.`;
149
149
  }
package/dist/core/byoa.js CHANGED
@@ -88,11 +88,11 @@ export function byoaUnverified(where, sub, facts, ledger, bindings) {
88
88
  if (byoaRecordFor(ledger, byoaLedgerKey(sub.connector, sub.event), id))
89
89
  return undefined;
90
90
  const named = label === id ? `connection ${id}` : `connection \`${label}\` (${id})`;
91
- const object = sub.object ? ` --object ${sub.object}` : '';
91
+ const object = sub.object ? ['--object', sub.object] : [];
92
92
  return `${where}: \`${sub.connector}\` \`${sub.event}\` requires a BYOA (bring-your-own-app) connection — `
93
93
  + `one created with your own OAuth app — and ${named} was never checked against that requirement. `
94
94
  + `A standard connection deploys and the trigger never fires. Prepare the event on this connection, `
95
95
  + `which refuses a standard connection and records a BYOA one:\n`
96
- + ` ${prepareCommand(sub.connector, sub.event)}${object} --connection-id ${id}\n`
96
+ + ` ${prepareCommand(sub.connector, sub.event, ...object, '--connection-id', id)}\n`
97
97
  + `BYOA connections (rows for \`${sub.connector}\`): uip is connections list --byoa --all-folders`;
98
98
  }
@@ -23,12 +23,33 @@ export interface CliSpellingEnv {
23
23
  * @returns `uip maestro` when the caller said so, else `npx flow-sdk`.
24
24
  */
25
25
  export declare function cliSpelling(env?: CliSpellingEnv): string;
26
+ declare const PLACEHOLDER_TEXT: unique symbol;
27
+ /** A `<name>` placeholder the SDK itself writes into a printed command; printed bare. */
28
+ export interface Placeholder {
29
+ readonly [PLACEHOLDER_TEXT]: string;
30
+ }
31
+ /**
32
+ * Mark `<name>` as a placeholder for {@link prepareCommand}. Only a marked
33
+ * placeholder is printed bare: a plain string shaped like `<x>` can come from a
34
+ * `.flow`, and bare it is a shell redirection.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * prepareCommand(key, event, '--connection-id', placeholder('<id>'));
39
+ * ```
40
+ */
41
+ export declare function placeholder(name: string): Placeholder;
26
42
  /**
27
43
  * A `registry prepare` command line, spelled for whoever is driving.
28
44
  *
29
- * @param connectorKey - The connector key, or omitted for the bare command.
30
- * @param action - The operation, printed as `--action <action>`.
31
- * @param rest - Further arguments, e.g. `--object`, `<name>`.
45
+ * The key, action and object often come from a `.flow` file, so every argument
46
+ * goes through {@link shellArg}; only a {@link placeholder} is kept as is. A value
47
+ * {@link shellArg} cannot quote prints as its placeholder, and the line ends
48
+ * with the value once, JSON-escaped: `… --object <object> (where <object> is "a';x")`.
49
+ *
50
+ * @param connectorKey - The connector key, or omitted (or empty) for the bare command.
51
+ * @param action - The operation, printed as `--action <action>`; omitted when empty.
52
+ * @param rest - Further arguments, e.g. `--object`, `Lead`; an empty one prints as `''`.
32
53
  * @returns The full command, no trailing newline.
33
54
  *
34
55
  * @example
@@ -36,9 +57,25 @@ export declare function cliSpelling(env?: CliSpellingEnv): string;
36
57
  * prepareCommand('uipath-salesforce-slack', 'send-message-to-user');
37
58
  * // -> "npx flow-sdk registry prepare uipath-salesforce-slack --action send-message-to-user"
38
59
  * // -> "uip maestro registry prepare …" when FLOW_SDK_INVOKED_AS="uip maestro"
60
+ * prepareCommand('uipath-x', 'created', '--object', 'My Object');
61
+ * // -> "npx flow-sdk registry prepare uipath-x --action created --object 'My Object'"
62
+ * ```
63
+ */
64
+ export declare function prepareCommand(connectorKey?: string | Placeholder, action?: string | Placeholder, ...rest: (string | Placeholder)[]): string;
65
+ /**
66
+ * One argument of a printed command, safe to paste into a POSIX shell or PowerShell.
67
+ *
68
+ * @param arg - The argument; `''` is returned for the empty string.
69
+ * @returns The argument as is, single-quoted, or `undefined` when no quoting is safe in both shells.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * shellArg('Lead'); // -> "Lead"
74
+ * shellArg('Lead;curl x|sh'); // -> "'Lead;curl x|sh'"
75
+ * shellArg("a';echo X;#"); // -> undefined
39
76
  * ```
40
77
  */
41
- export declare function prepareCommand(connectorKey?: string, action?: string, ...rest: string[]): string;
78
+ export declare function shellArg(arg: string): string | undefined;
42
79
  /**
43
80
  * The argument of one `--resolve <field>:<by>=<value>`, single-quoted when the
44
81
  * shell would otherwise mangle it.
@@ -58,3 +95,4 @@ export declare function resolveFlagArg(field: string, by: string, value: string)
58
95
  * moved to `uip`. Name it here once.
59
96
  */
60
97
  export declare const PULL_COMMAND = "uip maestro registry pull";
98
+ export {};
@@ -44,12 +44,39 @@ export function cliSpelling(env = ambientEnv()) {
44
44
  const hint = (env.FLOW_SDK_INVOKED_AS ?? '').trim();
45
45
  return SPELLING.test(hint) ? hint : DEFAULT_CLI_SPELLING;
46
46
  }
47
+ /** A `<name>` placeholder a caller prints for the reader to fill in, e.g. `<id>`, `<action>`. */
48
+ const PLACEHOLDER = /^<[a-z][a-z0-9-]*>$/;
49
+ const PLACEHOLDER_TEXT = Symbol('placeholder');
50
+ /**
51
+ * Mark `<name>` as a placeholder for {@link prepareCommand}. Only a marked
52
+ * placeholder is printed bare: a plain string shaped like `<x>` can come from a
53
+ * `.flow`, and bare it is a shell redirection.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * prepareCommand(key, event, '--connection-id', placeholder('<id>'));
58
+ * ```
59
+ */
60
+ export function placeholder(name) {
61
+ if (!PLACEHOLDER.test(name))
62
+ throw new Error(`internal SDK error: ${JSON.stringify(name)} is not a <name> placeholder`);
63
+ return { [PLACEHOLDER_TEXT]: name };
64
+ }
65
+ /** The placeholder printed in place of the value after `flag`: `<object>` after `--object`, else `<arg>`. */
66
+ function placeholderAfter(flag) {
67
+ return flag !== undefined && /^--[a-z][a-z0-9-]*$/.test(flag) ? `<${flag.slice(2)}>` : '<arg>';
68
+ }
47
69
  /**
48
70
  * A `registry prepare` command line, spelled for whoever is driving.
49
71
  *
50
- * @param connectorKey - The connector key, or omitted for the bare command.
51
- * @param action - The operation, printed as `--action <action>`.
52
- * @param rest - Further arguments, e.g. `--object`, `<name>`.
72
+ * The key, action and object often come from a `.flow` file, so every argument
73
+ * goes through {@link shellArg}; only a {@link placeholder} is kept as is. A value
74
+ * {@link shellArg} cannot quote prints as its placeholder, and the line ends
75
+ * with the value once, JSON-escaped: `… --object <object> (where <object> is "a';x")`.
76
+ *
77
+ * @param connectorKey - The connector key, or omitted (or empty) for the bare command.
78
+ * @param action - The operation, printed as `--action <action>`; omitted when empty.
79
+ * @param rest - Further arguments, e.g. `--object`, `Lead`; an empty one prints as `''`.
53
80
  * @returns The full command, no trailing newline.
54
81
  *
55
82
  * @example
@@ -57,22 +84,64 @@ export function cliSpelling(env = ambientEnv()) {
57
84
  * prepareCommand('uipath-salesforce-slack', 'send-message-to-user');
58
85
  * // -> "npx flow-sdk registry prepare uipath-salesforce-slack --action send-message-to-user"
59
86
  * // -> "uip maestro registry prepare …" when FLOW_SDK_INVOKED_AS="uip maestro"
87
+ * prepareCommand('uipath-x', 'created', '--object', 'My Object');
88
+ * // -> "npx flow-sdk registry prepare uipath-x --action created --object 'My Object'"
60
89
  * ```
61
90
  */
62
91
  export function prepareCommand(connectorKey, action, ...rest) {
63
- return [cliSpelling(), 'registry', 'prepare', connectorKey, ...(action ? ['--action', action] : []), ...rest]
64
- .filter(Boolean)
65
- .join(' ');
92
+ const held = [];
93
+ const arg = (value, stand) => {
94
+ if (typeof value !== 'string')
95
+ return value[PLACEHOLDER_TEXT];
96
+ const quoted = shellArg(value);
97
+ if (quoted !== undefined)
98
+ return quoted;
99
+ held.push(`${stand} is ${JSON.stringify(value)}`);
100
+ return stand;
101
+ };
102
+ const words = [cliSpelling(), 'registry', 'prepare'];
103
+ if (connectorKey)
104
+ words.push(arg(connectorKey, '<connector-key>'));
105
+ if (action)
106
+ words.push('--action', arg(action, '<action>'));
107
+ rest.forEach((value, i) => {
108
+ const prev = rest[i - 1];
109
+ words.push(arg(value, placeholderAfter(typeof prev === 'string' ? prev : undefined)));
110
+ });
111
+ const line = words.join(' ');
112
+ return held.length ? `${line} (where ${held.join(', ')})` : line;
113
+ }
114
+ /** Characters a POSIX shell or PowerShell reads specially in an unquoted argument (globs, whitespace, quotes incl. “ ” „, `$`, operators). */
115
+ const SHELL_UNSAFE = /[\s'"`$\\[\]*?;&|(){}!#~<>\u201C-\u201E]/;
116
+ /**
117
+ * Characters no single quoting keeps literal in both a POSIX shell and
118
+ * PowerShell: POSIX writes `'` as `'\''`, which PowerShell reads as a break-out;
119
+ * PowerShell also ends `'…'` at U+2018–U+201B; a control character can end the
120
+ * line. Every other character is literal inside `'…'` in both.
121
+ */
122
+ const UNQUOTABLE = /['\u2018-\u201B\p{Cc}]/u;
123
+ /** `arg` in POSIX shell single quotes, each embedded `'` written as `'\''`. */
124
+ function singleQuote(arg) {
125
+ return `'${arg.replaceAll("'", `'\\''`)}'`;
66
126
  }
67
127
  /**
68
- * Characters that make a `--resolve` argument unsafe to paste unquoted. `[*]`
69
- * is a shell glob (every multiselect wire name, `users[*]`), whitespace splits
70
- * a real name (`Coder Evals`) into two arguments, and quotes, `$`, backslash
71
- * and the control operators change what the shell hands over. The `<value>`
72
- * placeholder a template prints is stripped before the test, so a template
73
- * with nothing else unsafe keeps its bytes.
128
+ * One argument of a printed command, safe to paste into a POSIX shell or PowerShell.
129
+ *
130
+ * @param arg - The argument; `''` is returned for the empty string.
131
+ * @returns The argument as is, single-quoted, or `undefined` when no quoting is safe in both shells.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * shellArg('Lead'); // -> "Lead"
136
+ * shellArg('Lead;curl x|sh'); // -> "'Lead;curl x|sh'"
137
+ * shellArg("a';echo X;#"); // -> undefined
138
+ * ```
74
139
  */
75
- const SHELL_UNSAFE = /[\s'"`$\\[\]*?;&|(){}!#~<>]/;
140
+ export function shellArg(arg) {
141
+ if (UNQUOTABLE.test(arg))
142
+ return undefined;
143
+ return arg === '' || SHELL_UNSAFE.test(arg) ? `'${arg}'` : arg;
144
+ }
76
145
  /**
77
146
  * The argument of one `--resolve <field>:<by>=<value>`, single-quoted when the
78
147
  * shell would otherwise mangle it.
@@ -86,7 +155,7 @@ export function resolveFlagArg(field, by, value) {
86
155
  const arg = `${field}:${by}=${value}`;
87
156
  if (!SHELL_UNSAFE.test(arg.replaceAll('<value>', '')))
88
157
  return arg;
89
- return `'${arg.replaceAll("'", `'\\''`)}'`;
158
+ return singleQuote(arg);
90
159
  }
91
160
  /**
92
161
  * The command a message tells a person or agent to run when the cache is
@@ -121,7 +121,7 @@ compileRefusals = true) {
121
121
  warn('OBJECT_UNPREPARED', `${where}: object \`${object}\` has no prepared schema for `
122
122
  + `\`${sub.connector}\` \`${sub.event}\`, and this connector's objects come from `
123
123
  + `the connection, so the library cannot carry one. Materialize it:\n`
124
- + ` ${prepareCommand(connectorKey, eventName)} --object ${object}\n`
124
+ + ` ${prepareCommand(connectorKey, eventName, '--object', object)}\n`
125
125
  + `Until then its event parameters and filterable fields are unknown and `
126
126
  + `nothing here can check them.`);
127
127
  }
@@ -171,8 +171,7 @@ compileRefusals = true) {
171
171
  + `connector discovers CUSTOM FIELDS per connection, so the baked list is a `
172
172
  + `subset by design and this name may be a real field of yours. Prepare the `
173
173
  + `event against your connection to check it for real:\n`
174
- + ` ${prepareCommand(connectorKey, eventName)}`
175
- + `${object ? ` --object ${object}` : ''}\n`
174
+ + ` ${prepareCommand(connectorKey, eventName, ...(object ? ['--object', object] : []))}\n`
176
175
  + `Baked fields: ${names.slice(0, 12).join(', ')}${names.length > 12 ? ', …' : ''}.`);
177
176
  return;
178
177
  }
@@ -640,6 +640,7 @@ export declare const REFUSALS: {
640
640
  readonly TASK_OUTPUTS_NO_RESPONSE: "lowering";
641
641
  readonly TASK_OUTPUTS_PLACEHOLDER_EVENT: "lowering";
642
642
  readonly TASK_OUTPUTS_UNSUPPORTED: "lowering";
643
+ readonly TASK_OUTPUT_REF_UNRESOLVED: "lowering";
643
644
  readonly TASK_OUTPUT_SOURCE_AND_ASSIGN: "lowering";
644
645
  readonly TASK_REENTRY_IGNORED: "lint";
645
646
  readonly TASK_TIMER_OUT_OF_RANGE: "lint";
@@ -692,6 +692,7 @@ export const REFUSALS = {
692
692
  TASK_OUTPUTS_NO_RESPONSE: 'lowering',
693
693
  TASK_OUTPUTS_PLACEHOLDER_EVENT: 'lowering',
694
694
  TASK_OUTPUTS_UNSUPPORTED: 'lowering',
695
+ TASK_OUTPUT_REF_UNRESOLVED: 'lowering',
695
696
  TASK_OUTPUT_SOURCE_AND_ASSIGN: 'lowering',
696
697
  TASK_REENTRY_IGNORED: 'lint',
697
698
  TASK_TIMER_OUT_OF_RANGE: 'lint',
package/dist/decompile.js CHANGED
@@ -45,7 +45,7 @@ import { outputBackingGlobal, outputBackingName } from './flow-output-backing.js
45
45
  import { mapJsCode } from './core/js-code-spans.js';
46
46
  import { VARS_READ_RE } from './core/vars-read.js';
47
47
  import { missingBindingLabels } from './core/binding-messages.js';
48
- import { prepareCommand } from './core/cli-spelling.js';
48
+ import { placeholder, prepareCommand } from './core/cli-spelling.js';
49
49
  import { OUTCOME_HANDLE_NODE_TYPES } from './core/hitl-routing.js';
50
50
  import { contributionPort, forwardFanOut } from './core/definition-handles.js';
51
51
  import { STICKY_NOTE_TYPE } from './core/definitionless-node-types.js';
@@ -982,7 +982,7 @@ function unboundMarker(node, labels, key, action) {
982
982
  + `The source leaves ${missing.length > 1 ? 'them' : 'it'} out${isCanvasDraft(node)
983
983
  ? ' and marks the step `{ draft: true }`: compile writes it as a draft, and check refuses it'
984
984
  : ', and compile and check refuse it'} (BINDING_MISSING) until it names both labels: `
985
- + `run \`${prepareCommand(key, action ?? '<action>')}\`, then pass `
985
+ + `run \`${prepareCommand(key, action ?? placeholder('<action>'))}\`, then pass `
986
986
  + `\`connection\` and \`folder\`.`);
987
987
  return ` /* TODO: bind ${commentText(names)}; check refuses this node until it has both labels (BINDING_MISSING) */`;
988
988
  }
@@ -18,7 +18,7 @@
18
18
  * event's per-object variants are skipped because they share their template's
19
19
  * node type and so would emit the same const twice.
20
20
  */
21
- import { prepareCommand } from "../../core/cli-spelling.js";
21
+ import { placeholder, prepareCommand } from "../../core/cli-spelling.js";
22
22
  import { commentText, docComment } from "../../core/comment-text.js";
23
23
  import { PACKAGE_NAME } from "../../package-name.js";
24
24
  import { pyJsonString, pyJsonStringify } from "../codecs/pyjson.js";
@@ -207,7 +207,7 @@ export function emitEvent(entry, inFields, inputTree, outputTree, name, inputTyp
207
207
  lines.push(" * Requires a BYOA (bring-your-own-app) connection — on a standard one the");
208
208
  lines.push(" * trigger deploys and never fires. Bind it with");
209
209
  // Spelled for whoever ran the generator, like every `check` remedy.
210
- lines.push(` * \`${commentText(prepareCommand(pyStr(connectorKey), eventId, "--connection-id", "<id>"))}\`,`);
210
+ lines.push(` * \`${commentText(prepareCommand(pyStr(connectorKey), eventId, "--connection-id", placeholder("<id>")))}\`,`);
211
211
  lines.push(" * which refuses a standard connection and records the check `compile` requires;");
212
212
  lines.push(" * `uip is connections list --byoa --all-folders` lists the BYOA connections.");
213
213
  }
@@ -5,7 +5,7 @@
5
5
  * event page is unchanged. The facts are the sidecar's, which the library build
6
6
  * stamps from the event's `triggers objects` row (#848).
7
7
  */
8
- import { prepareCommand } from "../../../../core/cli-spelling.js";
8
+ import { placeholder, prepareCommand } from "../../../../core/cli-spelling.js";
9
9
  import { isEventNodeType } from "../../../model/library.js";
10
10
  import { dictOf, getStr } from "../pyvalue.js";
11
11
  export function eventFactsLines(j, v1def) {
@@ -29,7 +29,7 @@ export function eventFactsLines(j, v1def) {
29
29
  "connection that was never checked (`EVENT_BYOA_UNVERIFIED`).");
30
30
  out.push("");
31
31
  out.push("```bash");
32
- out.push(prepareCommand(key, event, "--connection-id", "<id>"));
32
+ out.push(prepareCommand(key, event, "--connection-id", placeholder("<id>")));
33
33
  out.push("uip is connections list --byoa --all-folders # the BYOA connections");
34
34
  out.push("```");
35
35
  out.push("");
package/dist/serialize.js CHANGED
@@ -57,7 +57,7 @@ import { planFlowGlobals, planSharedInputs, sharedInputRoots } from './flow-vari
57
57
  import { outputBackingGlobal, planOutputBacking } from './flow-output-backing.js';
58
58
  import { mapJsCode } from './core/js-code-spans.js';
59
59
  import { contributionPort } from './core/definition-handles.js';
60
- import { prepareCommand } from './core/cli-spelling.js';
60
+ import { placeholder, prepareCommand } from './core/cli-spelling.js';
61
61
  import { processResourceBindings } from './core/process-resource.js';
62
62
  import { types } from './core/expr.js';
63
63
  import { SUBFLOW_START_LABEL } from './core/subflow-start.js';
@@ -612,7 +612,7 @@ function resolveBinding(opts, name, attribute, where, target) {
612
612
  const fault = STUB_GUID_RE.test(value)
613
613
  ? `"401 Invalid Organization or User secret, or invalid Element token provided" — no such connection exists`
614
614
  : `"'Connection' has an invalid GUID value: '${value}'"`;
615
- const remedy = `Bind the real one: \`${prepareCommand('<connector-key>', '<action>')}\` finds the connection `
615
+ const remedy = `Bind the real one: \`${prepareCommand(placeholder('<connector-key>'), placeholder('<action>'))}\` finds the connection `
616
616
  + `and writes both entries into bindings.json (several candidates: pass \`--connection-id <id>\` from the list it `
617
617
  + `prints); by hand, read Id and FolderKey from \`uip is connections list --all-folders --output json\`.`;
618
618
  const message = resolved === undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.70.6",
3
+ "version": "6.70.8",
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": "acb0888df4770ee4db5ebb89ef2116cf400ee1b9"
103
+ "gitref": "17641bf2850dc69f377b91277d0e6dfc756b1280"
104
104
  }