@uipath/maestro-builder-sdk 6.56.0 → 6.58.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 (75) hide show
  1. package/dist/api-index.md +6 -2
  2. package/dist/api-members.md +1 -1
  3. package/dist/case/subscription.d.ts +4 -2
  4. package/dist/case/subscription.js +5 -2
  5. package/dist/cli/commands/flow.js +2 -0
  6. package/dist/core/actions.js +2 -2
  7. package/dist/core/binding-messages.d.ts +2 -1
  8. package/dist/core/binding-messages.js +3 -4
  9. package/dist/core/byoa.d.ts +3 -2
  10. package/dist/core/byoa.js +3 -3
  11. package/dist/core/carried-definitions.d.ts +0 -18
  12. package/dist/core/carried-definitions.js +3 -2
  13. package/dist/core/ceql.d.ts +2 -1
  14. package/dist/core/ceql.js +3 -2
  15. package/dist/core/compile-refusal.d.ts +21 -0
  16. package/dist/core/compile-refusal.js +26 -0
  17. package/dist/core/connector-inputs.js +2 -1
  18. package/dist/core/connector-resolve-error.d.ts +2 -1
  19. package/dist/core/connector-resolve-error.js +3 -4
  20. package/dist/core/connectors.js +2 -1
  21. package/dist/core/event-filters.js +2 -1
  22. package/dist/core/hitl-routing.d.ts +7 -82
  23. package/dist/core/hitl-routing.js +95 -6
  24. package/dist/core/library.js +4 -3
  25. package/dist/core/lookups.js +6 -5
  26. package/dist/core/nodes/registry.js +3 -2
  27. package/dist/core/nodes/versioned.js +3 -2
  28. package/dist/core/process-resource-sort-order.js +1 -1
  29. package/dist/core/read-text.d.ts +0 -28
  30. package/dist/core/read-text.js +3 -2
  31. package/dist/core/refusals.d.ts +465 -0
  32. package/dist/core/refusals.js +504 -0
  33. package/dist/core/script-function.d.ts +126 -0
  34. package/dist/core/script-function.js +511 -0
  35. package/dist/core/typescript-module.js +2 -1
  36. package/dist/decompile-cli.js +11 -3
  37. package/dist/decompile.d.ts +9 -0
  38. package/dist/decompile.js +76 -3
  39. package/dist/flow-expr-check.d.ts +2 -1
  40. package/dist/flow-expr-check.js +4 -2
  41. package/dist/flow-node-type.d.ts +21 -0
  42. package/dist/flow-sdk.js +17 -16
  43. package/dist/flow-step-tree.js +1 -1
  44. package/dist/flow-variables.js +3 -2
  45. package/dist/load.js +19 -5
  46. package/dist/nodes/agent/flow/inline-agent.js +7 -6
  47. package/dist/nodes/connector/flow/node-type.js +2 -1
  48. package/dist/nodes/data-fabric/flow/factories.js +2 -1
  49. package/dist/nodes/data-fabric/flow/verbs.js +2 -1
  50. package/dist/nodes/delay/flow/factories.js +2 -1
  51. package/dist/nodes/document/flow/ixp.js +5 -1
  52. package/dist/nodes/hitl/flow/check.js +4 -2
  53. package/dist/nodes/hitl/flow/node-type.js +6 -5
  54. package/dist/nodes/http/flow/factories.js +2 -1
  55. package/dist/nodes/http/flow/node-type.js +6 -5
  56. package/dist/nodes/process/flow/factories.js +2 -1
  57. package/dist/nodes/queue/flow/node-type.js +3 -2
  58. package/dist/nodes/script/flow/factories.d.ts +42 -1
  59. package/dist/nodes/script/flow/factories.js +8 -9
  60. package/dist/nodes/script/flow/node-type.js +71 -0
  61. package/dist/nodes/script/flow/runtime.d.ts +20 -0
  62. package/dist/nodes/script/flow/runtime.js +29 -0
  63. package/dist/nodes/script/flow/spec.d.ts +32 -1
  64. package/dist/nodes/transform/flow/node-type.js +2 -1
  65. package/dist/raw-node.js +3 -2
  66. package/dist/ref-resolve.d.ts +0 -36
  67. package/dist/ref-resolve.js +9 -8
  68. package/dist/schema-version.d.ts +0 -38
  69. package/dist/schema-version.js +3 -2
  70. package/dist/script-function-scope.d.ts +57 -0
  71. package/dist/script-function-scope.js +203 -0
  72. package/dist/sdk-resolve.js +2 -1
  73. package/dist/serialize.d.ts +5 -2
  74. package/dist/serialize.js +115 -83
  75. package/package.json +2 -2
@@ -0,0 +1,511 @@
1
+ import { typescript } from './typescript-module.js';
2
+ import { CompileRefusal } from './compile-refusal.js';
3
+ /**
4
+ * The body a script node carries for `fn`, or a thrown error naming every
5
+ * reason it cannot be one.
6
+ *
7
+ * @param fn - The function, or its source text (what `fn.toString()` returns).
8
+ * @param runtime - The runtime the body will run in.
9
+ * @param label - How errors name the call, e.g. `script(parseMessage)`.
10
+ */
11
+ export function lowerScriptFunction(fn, runtime, label) {
12
+ const source = typeof fn === 'string' ? fn : Function.prototype.toString.call(fn);
13
+ const result = tryLowerScriptFunction(source, runtime, { scopeReadsOnly: true });
14
+ if (result.ok) {
15
+ lowered.push(source);
16
+ return result.code;
17
+ }
18
+ const name = label ?? `script(${functionName(fn) ?? 'function'})`;
19
+ throw new CompileRefusal('SCRIPT_FUNCTION_UNLOWERABLE', `${name} cannot be a script: ${result.problems.join(' ')}`);
20
+ }
21
+ const lowered = [];
22
+ /**
23
+ * The source of every function {@link lowerScriptFunction} has lowered in this
24
+ * process, in order, one entry per call.
25
+ *
26
+ * The CLIs that load a `.flow.ts` take the entries added while it imported,
27
+ * twice over: to decide whether the module-scope pass
28
+ * (`script-function-scope.ts`) has anything to look at, so a flow with no
29
+ * function scripts pays nothing for it; and to fail closed, by matching each
30
+ * lowered text to a declaration that pass followed, so a function it could not
31
+ * see is refused. Taken per load because one process may load two sources
32
+ * (`merge --baseline-from`).
33
+ */
34
+ export function scriptFunctionLowerings() {
35
+ return lowered;
36
+ }
37
+ /**
38
+ * {@link lowerScriptFunction} without the throw, for a caller that falls back
39
+ * when the source cannot be lowered (decompile's function-form gate).
40
+ */
41
+ export function tryLowerScriptFunction(source, runtime, options = {}) {
42
+ if (/\{\s*\[native code\]\s*\}\s*$/.test(source)) {
43
+ return { ok: false, problems: ['it is a native or bound function, which has no source to lower.'], free: [] };
44
+ }
45
+ const ts = typescript();
46
+ const text = `(${source}\n)`;
47
+ const sf = ts.createSourceFile('script-function.ts', text, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
48
+ const syntax = sf.parseDiagnostics ?? [];
49
+ const fn = functionOf(ts, sf);
50
+ if (syntax.length > 0 || !fn) {
51
+ const first = syntax[0];
52
+ const why = first ? `: ${ts.flattenDiagnosticMessageText(first.messageText, '\n')}` : '';
53
+ return {
54
+ ok: false,
55
+ problems: [`its source does not parse as a function expression or arrow function${why}. `
56
+ + 'A method (`{ m(vars) {…} }`) or a class is not one; pass a function declaration, expression or arrow.'],
57
+ free: [],
58
+ syntax: true,
59
+ };
60
+ }
61
+ return new Lowering(ts, sf, fn, runtime, options).run();
62
+ }
63
+ /**
64
+ * The first syntax error in a script BODY (the text a script node carries), as
65
+ * `line N: message`, or undefined when it parses. Lines are the body's own.
66
+ */
67
+ export function scriptBodySyntaxError(code) {
68
+ const ts = typescript();
69
+ const sf = ts.createSourceFile('script-body.ts', `function __body() {\n${code}\n}`, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
70
+ const first = (sf.parseDiagnostics ?? [])[0];
71
+ if (!first)
72
+ return undefined;
73
+ // The wrapper's first line is the `{`, so a 0-based line in it is the body's
74
+ // 1-based line; an error found at the closing brace is reported on the last.
75
+ const line = Math.min(Math.max(1, sf.getLineAndCharacterOfPosition(first.start ?? 0).line), code.split('\n').length);
76
+ return `line ${line}: ${ts.flattenDiagnosticMessageText(first.messageText, '\n').replace(/\.$/, '')}`;
77
+ }
78
+ /**
79
+ * The JavaScript engine's early error for a body run as
80
+ * `function (<scopeGlobal>) { "use strict"; <code> }`, or undefined when it has
81
+ * none.
82
+ *
83
+ * TypeScript's parser does not report early errors (a duplicate `let`, a
84
+ * `const $vars` that clashes with the parameter, an octal literal in strict
85
+ * code), and the printed function lives in an ES module, which is strict. A body
86
+ * with one would make the whole module unloadable, so decompile asks the engine.
87
+ * The body is compiled, never called. A host without the `Function`
88
+ * constructor (a strict content-security policy) cannot answer, which is
89
+ * reported as an error too, so the caller keeps the string form.
90
+ */
91
+ export function scriptBodyEarlyError(code, runtime) {
92
+ // Compiled for their early errors only; neither is invoked. Module code also
93
+ // reserves `await`, which no synchronous constructor reports: an async
94
+ // function reserves it the same way, so a body must compile as both. (A
95
+ // body that uses `await` as an operator fails the first, as it would fail in
96
+ // the module's plain function.)
97
+ const AsyncFunction = (async () => { }).constructor;
98
+ try {
99
+ new Function(runtime.scopeGlobal, `"use strict";\n${code}`);
100
+ new AsyncFunction(runtime.scopeGlobal, `"use strict";\n${code}`);
101
+ return undefined;
102
+ }
103
+ catch (e) {
104
+ return e instanceof Error ? e.message : String(e);
105
+ }
106
+ }
107
+ /**
108
+ * The first piece of TypeScript-only syntax in a body, described, or undefined.
109
+ *
110
+ * A string body is JavaScript, but the printed function is read by a TypeScript
111
+ * loader, which erases type syntax before the runtime ever sees it: a body with
112
+ * `const n: number = 1` would come back from compile as `const n = 1`,
113
+ * and `f<T>(x)`, which JavaScript reads as two comparisons, would come back as a
114
+ * call. Either silently changes the stored script, so such a body keeps the
115
+ * string form.
116
+ */
117
+ export function scriptBodyTypeScriptSyntax(code) {
118
+ const ts = typescript();
119
+ const sf = ts.createSourceFile('script-body.ts', `function __body() {\n${code}\n}`, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
120
+ const TS_MODIFIERS = new Set([ts.SyntaxKind.PublicKeyword, ts.SyntaxKind.PrivateKeyword, ts.SyntaxKind.ProtectedKeyword,
121
+ ts.SyntaxKind.ReadonlyKeyword, ts.SyntaxKind.AbstractKeyword, ts.SyntaxKind.DeclareKeyword, ts.SyntaxKind.OverrideKeyword]);
122
+ let found;
123
+ const visit = (n) => {
124
+ if (found)
125
+ return;
126
+ const extendsClause = ts.isExpressionWithTypeArguments(n) && !n.typeArguments
127
+ && ts.isHeritageClause(n.parent) && n.parent.token === ts.SyntaxKind.ExtendsKeyword;
128
+ if (ts.isTypeNode(n) && !extendsClause)
129
+ found = 'a type annotation or type argument';
130
+ else if (ts.isTypeParameterDeclaration(n))
131
+ found = 'a type parameter';
132
+ else if (ts.isAsExpression(n) || ts.isSatisfiesExpression(n) || ts.isTypeAssertionExpression(n))
133
+ found = 'a type assertion';
134
+ else if (ts.isNonNullExpression(n))
135
+ found = 'a non-null assertion';
136
+ else if (ts.isInterfaceDeclaration(n) || ts.isTypeAliasDeclaration(n) || ts.isEnumDeclaration(n)
137
+ || ts.isModuleDeclaration(n) || ts.isImportEqualsDeclaration(n) || ts.isIndexSignatureDeclaration(n))
138
+ found = 'a TypeScript declaration';
139
+ else if (ts.isHeritageClause(n) && n.token === ts.SyntaxKind.ImplementsKeyword)
140
+ found = 'an `implements` clause';
141
+ else if ((ts.getModifiers(n) ?? []).some((m) => TS_MODIFIERS.has(m.kind)))
142
+ found = 'a TypeScript modifier';
143
+ else if ((ts.isParameter(n) || ts.isPropertyDeclaration(n) || ts.isMethodDeclaration(n)) && n.questionToken)
144
+ found = 'an optional marker';
145
+ else if ((ts.isVariableDeclaration(n) || ts.isPropertyDeclaration(n)) && n.exclamationToken)
146
+ found = 'a definite-assignment assertion';
147
+ else if ((ts.isFunctionDeclaration(n) || ts.isMethodDeclaration(n)) && !n.body)
148
+ found = 'an overload signature';
149
+ else
150
+ ts.forEachChild(n, visit);
151
+ };
152
+ ts.forEachChild(sf, (n) => (ts.isFunctionDeclaration(n) && n.body ? ts.forEachChild(n.body, visit) : visit(n)));
153
+ return found;
154
+ }
155
+ /** The one function expression or arrow `(<source>)` parses to, or undefined. */
156
+ function functionOf(ts, sf) {
157
+ if (sf.statements.length !== 1)
158
+ return undefined;
159
+ const stmt = sf.statements[0];
160
+ if (!ts.isExpressionStatement(stmt) || !ts.isParenthesizedExpression(stmt.expression))
161
+ return undefined;
162
+ const inner = stmt.expression.expression;
163
+ return ts.isFunctionExpression(inner) || ts.isArrowFunction(inner) ? inner : undefined;
164
+ }
165
+ function functionName(fn) {
166
+ if (typeof fn !== 'string')
167
+ return fn.name || undefined;
168
+ return /^\s*(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/.exec(fn)?.[1];
169
+ }
170
+ class Lowering {
171
+ ts;
172
+ sf;
173
+ fn;
174
+ runtime;
175
+ options;
176
+ checker;
177
+ problems = [];
178
+ edits = [];
179
+ free = new Set();
180
+ aliased = new Set();
181
+ param;
182
+ paramSymbol;
183
+ ownNameSymbol;
184
+ constructor(ts, sf, fn, runtime, options) {
185
+ this.ts = ts;
186
+ this.sf = sf;
187
+ this.fn = fn;
188
+ this.runtime = runtime;
189
+ this.options = options;
190
+ this.checker = oneFileChecker(ts, sf);
191
+ }
192
+ run() {
193
+ const { ts, fn } = this;
194
+ this.checkShape();
195
+ if (this.problems.length > 0)
196
+ return { ok: false, problems: this.problems, free: [] };
197
+ if (ts.isFunctionExpression(fn) && fn.name)
198
+ this.ownNameSymbol = this.checker.getSymbolAtLocation(fn.name);
199
+ const renaming = this.param !== undefined && this.param.text !== this.runtime.scopeGlobal;
200
+ if (renaming) {
201
+ this.checkNoCapture();
202
+ this.checkNoRedeclaredParameter();
203
+ }
204
+ this.visit(fn.body, 0);
205
+ if (this.aliased.size > 0) {
206
+ const name = [...this.aliased][0];
207
+ this.problems.push(`it uses its parameter \`${name}\` other than as a dotted read (\`${name}.start.output.x\`): an alias, a computed or optional read, `
208
+ + `or passing \`${name}\` on hides the step it reads from the step renaming and reference checks, which see only \`${this.runtime.scopeGlobal}.<step>\`. `
209
+ + 'Read each step with dots.');
210
+ }
211
+ const free = [...this.free].sort();
212
+ if (free.length > 0) {
213
+ this.problems.push(`it reads ${free.join(', ')}, which ${this.runtime.name} does not have. `
214
+ + 'A script runs alone: move the value inside the function, or declare it with `.var()` and read it from the scope.');
215
+ }
216
+ if (this.problems.length > 0)
217
+ return { ok: false, problems: this.problems, free };
218
+ return { ok: true, code: this.body() };
219
+ }
220
+ /** The function forms that cannot become a body at all. */
221
+ checkShape() {
222
+ const { ts, fn } = this;
223
+ const mods = ts.getModifiers(fn) ?? [];
224
+ if (mods.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword))
225
+ this.problems.push('it is `async`; a script body runs synchronously.');
226
+ if (ts.isFunctionExpression(fn) && fn.asteriskToken)
227
+ this.problems.push('it is a generator; a script body returns one value.');
228
+ if (fn.parameters.length > 1) {
229
+ this.problems.push(`it takes ${fn.parameters.length} parameters; a script function takes one, the runtime's variable scope (${this.runtime.scopeGlobal}), or none.`);
230
+ return;
231
+ }
232
+ const p = fn.parameters[0];
233
+ if (!p)
234
+ return;
235
+ if (p.dotDotDotToken)
236
+ this.problems.push('its parameter is a rest parameter; take the variable scope as one plain parameter.');
237
+ else if (p.initializer)
238
+ this.problems.push('its parameter has a default value; the runtime always supplies the variable scope.');
239
+ else if (!ts.isIdentifier(p.name)) {
240
+ this.problems.push('its parameter is destructured. Take the variable scope as one plain parameter and read it with dots '
241
+ + `(\`vars.start.output.x\`): a destructured read is invisible to the step renaming and reference checks that read \`${this.runtime.scopeGlobal}.<step>\`.`);
242
+ }
243
+ else {
244
+ this.param = p.name;
245
+ this.paramSymbol = this.checker.getSymbolAtLocation(p.name);
246
+ }
247
+ }
248
+ /**
249
+ * Refuse a binding named like the scope global anywhere in the function: once
250
+ * the parameter's references are rewritten to that name, the binding would
251
+ * capture them (`const $vars = {…}; return vars.x` would read the local).
252
+ */
253
+ checkNoCapture() {
254
+ const { ts } = this;
255
+ const target = this.runtime.scopeGlobal;
256
+ let found = false;
257
+ const visit = (n) => {
258
+ if (found)
259
+ return;
260
+ if (ts.isIdentifier(n) && n.text === target && isDeclarationName(ts, n))
261
+ found = true;
262
+ else
263
+ ts.forEachChild(n, visit);
264
+ };
265
+ visit(this.fn.body);
266
+ if (found) {
267
+ this.problems.push(`it declares its own \`${target}\`, which would capture the renamed parameter. `
268
+ + `Rename the inner binding, or name the parameter \`${target}\` so nothing is renamed.`);
269
+ }
270
+ }
271
+ /**
272
+ * Refuse a `var vars` or `function vars() {}` in the body: either binds the
273
+ * parameter's own name in the function's scope, so the rename would rewrite the
274
+ * reads but not the declaration, and `var vars = {x: 1}; return vars.x` would
275
+ * read the runtime's scope instead of the local. Judged by scope, not by
276
+ * symbol: the checker gives a function declaration a symbol of its own while
277
+ * still resolving the reads to the parameter. A nested function (arrows
278
+ * included) has its own `var` scope, so its declarations are its own.
279
+ */
280
+ checkNoRedeclaredParameter() {
281
+ const { ts } = this;
282
+ const name = this.param.text;
283
+ let found = false;
284
+ const visit = (n) => {
285
+ if (found)
286
+ return;
287
+ if (ts.isFunctionDeclaration(n) && n.name?.text === name) {
288
+ found = true;
289
+ return;
290
+ }
291
+ // A `var` list, with every name its pattern binds (`var { vars } = …`,
292
+ // `var [vars]`, `for (var { vars } of …)`); `let`/`const` and a catch binding
293
+ // are block-scoped and their own.
294
+ if (ts.isVariableDeclaration(n) && ts.isVariableDeclarationList(n.parent)
295
+ && (n.parent.flags & ts.NodeFlags.BlockScoped) === 0 && bindsName(ts, n.name, name)) {
296
+ found = true;
297
+ return;
298
+ }
299
+ if (ts.isFunctionLike(n) || ts.isClassLike(n))
300
+ return;
301
+ ts.forEachChild(n, visit);
302
+ };
303
+ visit(this.fn.body);
304
+ if (found) {
305
+ this.problems.push(`it redeclares its parameter \`${name}\` (a \`var\` or a function declaration), which the rename to \`${this.runtime.scopeGlobal}\` would split from its reads. `
306
+ + 'Give the local a name of its own.');
307
+ }
308
+ }
309
+ /**
310
+ * Walk the body: queue the renames and `__name` removals, and collect the
311
+ * names the runtime would not have. `fnDepth` counts the non-arrow functions
312
+ * entered, inside which `this`, `arguments` and `new.target` are their own.
313
+ */
314
+ visit(n, fnDepth) {
315
+ const { ts } = this;
316
+ if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === '__name' && !this.resolves(n.expression)) {
317
+ this.removeKeepNames(n, fnDepth);
318
+ return;
319
+ }
320
+ if (n.kind === ts.SyntaxKind.ThisKeyword && fnDepth === 0)
321
+ this.problems.push('it reads `this`, which a script body does not have.');
322
+ if (ts.isMetaProperty(n)) {
323
+ if (n.keywordToken === ts.SyntaxKind.ImportKeyword)
324
+ this.problems.push('it reads `import.meta`, which a script body does not have.');
325
+ else if (fnDepth === 0)
326
+ this.problems.push('it reads `new.target`, which a script body does not have.');
327
+ }
328
+ if (ts.isCallExpression(n) && n.expression.kind === ts.SyntaxKind.ImportKeyword)
329
+ this.problems.push('it calls `import()`; a script cannot load modules.');
330
+ if (n.kind === ts.SyntaxKind.SuperKeyword && !hasHomeObject(ts, n, this.fn))
331
+ this.problems.push('it reads `super`, which a script body does not have.');
332
+ if (ts.isIdentifier(n))
333
+ this.identifier(n, fnDepth);
334
+ // A scope with its own `this` and `arguments`. Its name, when computed, is
335
+ // evaluated in the ENCLOSING scope (`{ [arguments.length]() {} }` reads the
336
+ // outer function's), so the name is walked at the outer depth.
337
+ if (n !== this.fn && ownsThis(ts, n)) {
338
+ const name = n.name;
339
+ ts.forEachChild(n, (c) => this.visit(c, c === name ? fnDepth : fnDepth + 1));
340
+ return;
341
+ }
342
+ ts.forEachChild(n, (c) => this.visit(c, fnDepth));
343
+ }
344
+ identifier(id, fnDepth) {
345
+ const { ts } = this;
346
+ if (!isReference(ts, id))
347
+ return;
348
+ const parent = id.parent;
349
+ const shorthand = ts.isShorthandPropertyAssignment(parent) && parent.name === id;
350
+ const symbol = shorthand ? this.checker.getShorthandAssignmentValueSymbol(parent) : this.checker.getSymbolAtLocation(id);
351
+ if (symbol && symbol === this.paramSymbol) {
352
+ if (this.options.scopeReadsOnly && !(ts.isPropertyAccessExpression(parent) && parent.expression === id && !parent.questionDotToken)) {
353
+ this.aliased.add(id.text);
354
+ }
355
+ if (this.param.text !== this.runtime.scopeGlobal) {
356
+ this.edits.push(shorthand
357
+ ? [id.getStart(), id.end, `${id.text}: ${this.runtime.scopeGlobal}`]
358
+ : [id.getStart(), id.end, this.runtime.scopeGlobal]);
359
+ }
360
+ return;
361
+ }
362
+ if (symbol && symbol === this.ownNameSymbol) {
363
+ // The name is bound by the function itself, and only the body reaches the
364
+ // artifact: the runtime has no such name, so it is as free as any other.
365
+ this.free.add(id.text);
366
+ return;
367
+ }
368
+ // The checker binds a synthetic `arguments` in every function, so it resolves
369
+ // whether or not a body would have one: only a nested non-arrow function does.
370
+ if (id.text === 'arguments' && (symbol?.declarations?.length ?? 0) === 0) {
371
+ if (fnDepth === 0)
372
+ this.problems.push('it reads `arguments`, which a script body does not have.');
373
+ return;
374
+ }
375
+ if (symbol && !isUndefinedSymbol(symbol))
376
+ return;
377
+ if (id.text === this.runtime.scopeGlobal || this.runtime.globals.has(id.text))
378
+ return;
379
+ // `typeof x` of a bare name does not throw when `x` is absent, so it is a
380
+ // legitimate probe. A compound operand (`typeof x.y`, `typeof f()`) is
381
+ // evaluated first and does throw, so its names are checked as usual.
382
+ if (ts.isTypeOfExpression(parent) && parent.expression === id)
383
+ return;
384
+ this.free.add(id.text);
385
+ }
386
+ /** Whether an identifier resolves to a declaration in the function (as opposed to an ambient global). */
387
+ resolves(id) {
388
+ const s = this.checker.getSymbolAtLocation(id);
389
+ return s !== undefined && !isUndefinedSymbol(s);
390
+ }
391
+ /**
392
+ * esbuild `keepNames` output: `__name(x, "x")` around an expression becomes
393
+ * `x`, and a `__name(inner, "inner");` statement is dropped.
394
+ */
395
+ removeKeepNames(call, fnDepth) {
396
+ const { ts } = this;
397
+ const [target, nameArg] = call.arguments;
398
+ if (!target || !nameArg || call.arguments.length !== 2 || !ts.isStringLiteral(nameArg)) {
399
+ this.free.add('__name');
400
+ return;
401
+ }
402
+ if (ts.isExpressionStatement(call.parent)) {
403
+ this.edits.push([call.parent.getStart(), call.parent.end, '']);
404
+ return;
405
+ }
406
+ this.edits.push([call.getStart(), target.getStart(), '']);
407
+ this.edits.push([target.end, call.end, '']);
408
+ this.visit(target, fnDepth);
409
+ }
410
+ /** The body text with the queued edits applied. */
411
+ body() {
412
+ const { ts, fn, sf } = this;
413
+ const text = sf.text;
414
+ const block = ts.isBlock(fn.body);
415
+ // Between the braces, exactly; an expression body becomes `return <expr>;`.
416
+ const start = block ? fn.body.getStart() + 1 : fn.body.getStart();
417
+ const end = block ? fn.body.end - 1 : fn.body.end;
418
+ let out = text.slice(start, end);
419
+ for (const [s, e, t] of [...this.edits].sort((a, b) => b[0] - a[0])) {
420
+ out = out.slice(0, s - start) + t + out.slice(e - start);
421
+ }
422
+ return block ? out : `return ${out};`;
423
+ }
424
+ }
425
+ /** The checker of a one-file program over `sf`, with no default library: globals resolve to nothing. */
426
+ function oneFileChecker(ts, sf) {
427
+ const options = { noLib: true, noResolve: true, types: [], target: ts.ScriptTarget.ES2020 };
428
+ const host = {
429
+ getSourceFile: (name) => (name === sf.fileName ? sf : undefined),
430
+ getDefaultLibFileName: () => 'lib.d.ts',
431
+ writeFile: () => { },
432
+ getCurrentDirectory: () => '',
433
+ getCanonicalFileName: (f) => f,
434
+ useCaseSensitiveFileNames: () => true,
435
+ getNewLine: () => '\n',
436
+ fileExists: (name) => name === sf.fileName,
437
+ readFile: (name) => (name === sf.fileName ? sf.text : undefined),
438
+ };
439
+ return ts.createProgram({ rootNames: [sf.fileName], options, host }).getTypeChecker();
440
+ }
441
+ /** The checker's own symbol for `undefined`, which a no-lib program still binds. */
442
+ function isUndefinedSymbol(s) {
443
+ return s.escapedName === 'undefined' && (s.declarations?.length ?? 0) === 0;
444
+ }
445
+ /** Whether `id` is read as a value, not a property name, label, or type. */
446
+ export function isReference(ts, id) {
447
+ const p = id.parent;
448
+ if (ts.isPropertyAccessExpression(p) && p.name === id)
449
+ return false;
450
+ if (ts.isQualifiedName(p))
451
+ return false;
452
+ if ((ts.isPropertyAssignment(p) || ts.isMethodDeclaration(p) || ts.isPropertyDeclaration(p)
453
+ || ts.isGetAccessor(p) || ts.isSetAccessor(p) || ts.isPropertySignature(p) || ts.isEnumMember(p)) && p.name === id)
454
+ return false;
455
+ if (ts.isBindingElement(p) && p.propertyName === id)
456
+ return false;
457
+ if (ts.isLabeledStatement(p) || ts.isBreakOrContinueStatement(p))
458
+ return false;
459
+ if (ts.isMetaProperty(p))
460
+ return false;
461
+ if (isDeclarationName(ts, id))
462
+ return false;
463
+ // Inside a type annotation (a source that kept its types): not a runtime read.
464
+ for (let n = p; n && !ts.isSourceFile(n); n = n.parent) {
465
+ if (ts.isTypeNode(n) && !ts.isExpressionWithTypeArguments(n))
466
+ return false;
467
+ }
468
+ return true;
469
+ }
470
+ /** Whether a binding name or pattern binds `name` anywhere in it. */
471
+ function bindsName(ts, n, name) {
472
+ if (ts.isIdentifier(n))
473
+ return n.text === name;
474
+ return n.elements.some((e) => !ts.isOmittedExpression(e) && bindsName(ts, e.name, name));
475
+ }
476
+ /** Whether `id` is the name a declaration binds. */
477
+ function isDeclarationName(ts, id) {
478
+ const p = id.parent;
479
+ return (ts.isVariableDeclaration(p) || ts.isParameter(p) || ts.isFunctionDeclaration(p) || ts.isFunctionExpression(p)
480
+ || ts.isClassDeclaration(p) || ts.isClassExpression(p) || ts.isBindingElement(p)) && p.name === id;
481
+ }
482
+ /**
483
+ * Whether a node starts a scope with its own `this` and `arguments`: a non-arrow
484
+ * function, a method, accessor or constructor (of a class or an object literal),
485
+ * a class field's initializer, or a static block.
486
+ */
487
+ function ownsThis(ts, n) {
488
+ return ts.isFunctionDeclaration(n) || ts.isFunctionExpression(n) || ts.isMethodDeclaration(n)
489
+ || ts.isConstructorDeclaration(n) || ts.isGetAccessor(n) || ts.isSetAccessor(n)
490
+ || ts.isPropertyDeclaration(n) || ts.isClassStaticBlockDeclaration(n);
491
+ }
492
+ /**
493
+ * Whether `super` at `n` has a home object inside `fn`: the nearest enclosing
494
+ * method, accessor, constructor, class field or static block, of a class or of an
495
+ * object literal, with arrows transparent between. A computed member name is
496
+ * evaluated outside its member, so it does not count; a plain function does not
497
+ * give `super` one at all.
498
+ */
499
+ function hasHomeObject(ts, n, fn) {
500
+ let prev = n;
501
+ for (let p = n.parent; p && p !== fn; prev = p, p = p.parent) {
502
+ if (ts.isFunctionDeclaration(p) || ts.isFunctionExpression(p))
503
+ return false;
504
+ if (ownsThis(ts, p)) {
505
+ if (p.name === prev)
506
+ continue;
507
+ return true;
508
+ }
509
+ }
510
+ return false;
511
+ }
@@ -1,3 +1,4 @@
1
+ import { CompileRefusal } from './compile-refusal.js';
1
2
  const HOST_TYPESCRIPT = Symbol.for('uipath.maestro-builder-sdk.typescript');
2
3
  let typescriptModule;
3
4
  export function typescript() {
@@ -6,7 +7,7 @@ export function typescript() {
6
7
  function fromNode() {
7
8
  const getBuiltinModule = globalThis.process?.getBuiltinModule;
8
9
  if (getBuiltinModule === undefined) {
9
- throw new Error("no TypeScript compiler: outside Node, set globalThis[Symbol.for('uipath.maestro-builder-sdk.typescript')] to the 'typescript' module before checking a flow");
10
+ throw new CompileRefusal('TYPESCRIPT_UNAVAILABLE', "no TypeScript compiler: outside Node, set globalThis[Symbol.for('uipath.maestro-builder-sdk.typescript')] to the 'typescript' module before checking a flow");
10
11
  }
11
12
  const { createRequire } = getBuiltinModule('node:module');
12
13
  return createRequire(import.meta.url)('typescript');
@@ -3,7 +3,7 @@
3
3
  * decompile-cli — <original>.flow → <Name>.flow.ts (bin `flow-decompile`)
4
4
  *
5
5
  * Usage:
6
- * flow-decompile <original.flow> [-o Name.flow.ts] [--import <specifier>] [--strict]
6
+ * flow-decompile <original.flow> [-o Name.flow.ts] [--import <specifier>] [--strict] [--scripts function|string]
7
7
  *
8
8
  * The reverse of compile-cli: reads a `.flow` JSON and emits Flow SDK builder
9
9
  * source whose default export re-serializes to the same graph (same node ids,
@@ -11,6 +11,8 @@
11
11
  * `import { flow, … }` line (default the published package) so the file resolves
12
12
  * wherever you place it. `--strict` turns off best-effort mode, so an
13
13
  * unsupported construct is a hard error instead of a `// TODO` marker.
14
+ * `--scripts string` prints every script step as `script({ code })` instead of
15
+ * as a TypeScript function (the default, where the body allows it).
14
16
  *
15
17
  * Besides the `.flow.ts`, this also emits a per-flow pipeline wrapper
16
18
  * (`<Name>.pipeline.mjs`) that chains decompile → [edit] → compile → merge, so an
@@ -40,15 +42,20 @@ function baseName(flow) {
40
42
  }
41
43
  export async function run(argv) {
42
44
  if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
43
- console.error('usage: flow-decompile <original.flow> [-o Name.flow.ts] [--import <specifier>] [--package <name>]... [--strict]');
45
+ console.error('usage: flow-decompile <original.flow> [-o Name.flow.ts] [--import <specifier>] [--package <name>]... [--strict] [--scripts function|string]');
44
46
  return 2;
45
47
  }
46
- const flags = new Set(['-o', '--import', '--package']);
48
+ const flags = new Set(['-o', '--import', '--package', '--scripts']);
47
49
  const pos = argv.find((a, i) => !flags.has(a) && !flags.has(argv[i - 1]) && !a.startsWith('--'));
48
50
  if (!pos) {
49
51
  console.error('flow-decompile: missing <original.flow>');
50
52
  return 2;
51
53
  }
54
+ const scripts = flagValue(argv, '--scripts') ?? 'function';
55
+ if (scripts !== 'function' && scripts !== 'string') {
56
+ console.error(`flow-decompile: --scripts takes "function" or "string", got ${JSON.stringify(scripts)}`);
57
+ return 2;
58
+ }
52
59
  // A `.flow` may be SPLIT across sibling files with JSON Reference — resolve
53
60
  // those into one object model before decompiling (design §5; the format's own
54
61
  // read path does the same). A single-file flow walks through untouched.
@@ -68,6 +75,7 @@ export async function run(argv) {
68
75
  const ts = decompile(flow, {
69
76
  importSpecifier: flagValue(argv, '--import') ?? PACKAGE_NAME,
70
77
  bestEffort: !argv.includes('--strict'),
78
+ scripts,
71
79
  // Findings the source cannot carry on its own (a node no trigger reaches, a
72
80
  // pinned core version this SDK lacks, a file-format version outside the
73
81
  // writable band) go to stderr, where the agent running the brownfield loop
@@ -151,6 +151,15 @@ export interface DecompileOptions {
151
151
  * reported through `warn` (an error when `bestEffort` is false).
152
152
  */
153
153
  readSidecar?: (relativePath: string) => string | undefined;
154
+ /**
155
+ * How a script step prints. `function` prints it as a TypeScript function,
156
+ * `script(script_<step>, …)` with `function script_<step>($vars: FlowVars)
157
+ * {…}` after the flow, whenever its body passes decompile's gate; any other
158
+ * body keeps the string form with a warning. `string` always prints
159
+ * `script({ code })`. The default is `function` under Node and `string`
160
+ * elsewhere, where the loader's `toString()` has not been measured.
161
+ */
162
+ scripts?: 'function' | 'string';
154
163
  }
155
164
  /**
156
165
  * Tracks which identifiers the emitted source uses, per module, so each import