@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.
- package/dist/api-index.md +6 -2
- package/dist/api-members.md +1 -1
- package/dist/case/subscription.d.ts +4 -2
- package/dist/case/subscription.js +5 -2
- package/dist/cli/commands/flow.js +2 -0
- package/dist/core/actions.js +2 -2
- package/dist/core/binding-messages.d.ts +2 -1
- package/dist/core/binding-messages.js +3 -4
- package/dist/core/byoa.d.ts +3 -2
- package/dist/core/byoa.js +3 -3
- package/dist/core/carried-definitions.d.ts +0 -18
- package/dist/core/carried-definitions.js +3 -2
- package/dist/core/ceql.d.ts +2 -1
- package/dist/core/ceql.js +3 -2
- package/dist/core/compile-refusal.d.ts +21 -0
- package/dist/core/compile-refusal.js +26 -0
- package/dist/core/connector-inputs.js +2 -1
- package/dist/core/connector-resolve-error.d.ts +2 -1
- package/dist/core/connector-resolve-error.js +3 -4
- package/dist/core/connectors.js +2 -1
- package/dist/core/event-filters.js +2 -1
- package/dist/core/hitl-routing.d.ts +7 -82
- package/dist/core/hitl-routing.js +95 -6
- package/dist/core/library.js +4 -3
- package/dist/core/lookups.js +6 -5
- package/dist/core/nodes/registry.js +3 -2
- package/dist/core/nodes/versioned.js +3 -2
- package/dist/core/process-resource-sort-order.js +1 -1
- package/dist/core/read-text.d.ts +0 -28
- package/dist/core/read-text.js +3 -2
- package/dist/core/refusals.d.ts +465 -0
- package/dist/core/refusals.js +504 -0
- package/dist/core/script-function.d.ts +126 -0
- package/dist/core/script-function.js +511 -0
- package/dist/core/typescript-module.js +2 -1
- package/dist/decompile-cli.js +11 -3
- package/dist/decompile.d.ts +9 -0
- package/dist/decompile.js +76 -3
- package/dist/flow-expr-check.d.ts +2 -1
- package/dist/flow-expr-check.js +4 -2
- package/dist/flow-node-type.d.ts +21 -0
- package/dist/flow-sdk.js +17 -16
- package/dist/flow-step-tree.js +1 -1
- package/dist/flow-variables.js +3 -2
- package/dist/load.js +19 -5
- package/dist/nodes/agent/flow/inline-agent.js +7 -6
- package/dist/nodes/connector/flow/node-type.js +2 -1
- package/dist/nodes/data-fabric/flow/factories.js +2 -1
- package/dist/nodes/data-fabric/flow/verbs.js +2 -1
- package/dist/nodes/delay/flow/factories.js +2 -1
- package/dist/nodes/document/flow/ixp.js +5 -1
- package/dist/nodes/hitl/flow/check.js +4 -2
- package/dist/nodes/hitl/flow/node-type.js +6 -5
- package/dist/nodes/http/flow/factories.js +2 -1
- package/dist/nodes/http/flow/node-type.js +6 -5
- package/dist/nodes/process/flow/factories.js +2 -1
- package/dist/nodes/queue/flow/node-type.js +3 -2
- package/dist/nodes/script/flow/factories.d.ts +42 -1
- package/dist/nodes/script/flow/factories.js +8 -9
- package/dist/nodes/script/flow/node-type.js +71 -0
- package/dist/nodes/script/flow/runtime.d.ts +20 -0
- package/dist/nodes/script/flow/runtime.js +29 -0
- package/dist/nodes/script/flow/spec.d.ts +32 -1
- package/dist/nodes/transform/flow/node-type.js +2 -1
- package/dist/raw-node.js +3 -2
- package/dist/ref-resolve.d.ts +0 -36
- package/dist/ref-resolve.js +9 -8
- package/dist/schema-version.d.ts +0 -38
- package/dist/schema-version.js +3 -2
- package/dist/script-function-scope.d.ts +57 -0
- package/dist/script-function-scope.js +203 -0
- package/dist/sdk-resolve.js +2 -1
- package/dist/serialize.d.ts +5 -2
- package/dist/serialize.js +115 -83
- 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
|
|
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');
|
package/dist/decompile-cli.js
CHANGED
|
@@ -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
|
package/dist/decompile.d.ts
CHANGED
|
@@ -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
|