@uipath/maestro-builder-sdk 6.59.1 → 6.60.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.
|
@@ -33,6 +33,18 @@ export interface ScriptRuntime {
|
|
|
33
33
|
scopeGlobal: string;
|
|
34
34
|
/** Every other global name the runtime provides: the language built-ins it has, `console`. */
|
|
35
35
|
globals: ReadonlySet<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Globals the runtime declares with `const` before the body runs. The body
|
|
38
|
+
* runs as a script, so declaring one at its top level is an early error
|
|
39
|
+
* ("Identifier '…' has already been declared"), and assigning one is a
|
|
40
|
+
* runtime error ("Assignment to constant variable.").
|
|
41
|
+
*/
|
|
42
|
+
constGlobals?: ReadonlySet<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Globals that are objects with a fixed set of members: any other member reads
|
|
45
|
+
* `undefined` in the runtime, so reading one is refused.
|
|
46
|
+
*/
|
|
47
|
+
globalMembers?: ReadonlyMap<string, ReadonlySet<string>>;
|
|
36
48
|
/** How errors name the runtime: `the Flow script runtime`. */
|
|
37
49
|
name: string;
|
|
38
50
|
}
|
|
@@ -201,6 +201,7 @@ class Lowering {
|
|
|
201
201
|
this.checkNoCapture();
|
|
202
202
|
this.checkNoRedeclaredParameter();
|
|
203
203
|
}
|
|
204
|
+
this.checkConstGlobals();
|
|
204
205
|
this.visit(fn.body, 0);
|
|
205
206
|
if (this.aliased.size > 0) {
|
|
206
207
|
const name = [...this.aliased][0];
|
|
@@ -306,6 +307,102 @@ class Lowering {
|
|
|
306
307
|
+ 'Give the local a name of its own.');
|
|
307
308
|
}
|
|
308
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Refuse what the runtime's `const` globals make fail: a declaration of one at
|
|
312
|
+
* the body's top level, and an assignment to one where it is still the global.
|
|
313
|
+
* The body runs as a script, so its top level is the global scope: a `let`,
|
|
314
|
+
* `const`, `function` or `class` statement directly in it, and a `var` anywhere
|
|
315
|
+
* outside a nested function (it hoists), collides with the runtime's `const`.
|
|
316
|
+
*/
|
|
317
|
+
checkConstGlobals() {
|
|
318
|
+
const { ts } = this;
|
|
319
|
+
const reserved = this.runtime.constGlobals;
|
|
320
|
+
if (!reserved || reserved.size === 0)
|
|
321
|
+
return;
|
|
322
|
+
const declared = new Set();
|
|
323
|
+
const assigned = new Set();
|
|
324
|
+
const body = this.fn.body;
|
|
325
|
+
const names = (n, into) => {
|
|
326
|
+
if (ts.isIdentifier(n)) {
|
|
327
|
+
if (reserved.has(n.text))
|
|
328
|
+
into.add(n.text);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
for (const e of n.elements)
|
|
332
|
+
if (!ts.isOmittedExpression(e))
|
|
333
|
+
names(e.name, into);
|
|
334
|
+
};
|
|
335
|
+
// Declarations need a block body; an expression-bodied arrow has none, but
|
|
336
|
+
// its expression can still assign one (below).
|
|
337
|
+
if (ts.isBlock(body)) {
|
|
338
|
+
for (let stmt of body.statements) {
|
|
339
|
+
// A label does not scope what it labels: `l: function console() {}` collides too (measured).
|
|
340
|
+
while (ts.isLabeledStatement(stmt))
|
|
341
|
+
stmt = stmt.statement;
|
|
342
|
+
if (ts.isVariableStatement(stmt))
|
|
343
|
+
for (const d of stmt.declarationList.declarations)
|
|
344
|
+
names(d.name, declared);
|
|
345
|
+
if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && stmt.name && reserved.has(stmt.name.text))
|
|
346
|
+
declared.add(stmt.name.text);
|
|
347
|
+
}
|
|
348
|
+
// A `var` in a nested block hoists to the top level too.
|
|
349
|
+
const vars = (n) => {
|
|
350
|
+
if (ts.isFunctionLike(n) || ts.isClassLike(n))
|
|
351
|
+
return;
|
|
352
|
+
if (ts.isVariableDeclarationList(n) && (n.flags & ts.NodeFlags.BlockScoped) === 0)
|
|
353
|
+
for (const d of n.declarations)
|
|
354
|
+
names(d.name, declared);
|
|
355
|
+
ts.forEachChild(n, vars);
|
|
356
|
+
};
|
|
357
|
+
ts.forEachChild(body, vars);
|
|
358
|
+
}
|
|
359
|
+
// An assignment to the global itself: an unresolved target with a reserved name.
|
|
360
|
+
const targets = (e) => {
|
|
361
|
+
if (ts.isIdentifier(e)) {
|
|
362
|
+
if (reserved.has(e.text) && !this.resolves(e))
|
|
363
|
+
assigned.add(e.text);
|
|
364
|
+
}
|
|
365
|
+
else if (ts.isParenthesizedExpression(e))
|
|
366
|
+
targets(e.expression);
|
|
367
|
+
else if (ts.isArrayLiteralExpression(e))
|
|
368
|
+
e.elements.forEach((x) => targets(ts.isSpreadElement(x) ? x.expression : x));
|
|
369
|
+
else if (ts.isObjectLiteralExpression(e)) {
|
|
370
|
+
for (const p of e.properties) {
|
|
371
|
+
if (ts.isShorthandPropertyAssignment(p)) {
|
|
372
|
+
// The name resolves to the property; the value assigned is the variable's own symbol.
|
|
373
|
+
const sym = this.checker.getShorthandAssignmentValueSymbol(p);
|
|
374
|
+
if (reserved.has(p.name.text) && (!sym || isUndefinedSymbol(sym)))
|
|
375
|
+
assigned.add(p.name.text);
|
|
376
|
+
}
|
|
377
|
+
else if (ts.isPropertyAssignment(p))
|
|
378
|
+
targets(p.initializer);
|
|
379
|
+
else if (ts.isSpreadAssignment(p))
|
|
380
|
+
targets(p.expression);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
else if (ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.EqualsToken)
|
|
384
|
+
targets(e.left);
|
|
385
|
+
};
|
|
386
|
+
const walk = (n) => {
|
|
387
|
+
if (ts.isBinaryExpression(n) && n.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && n.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
|
|
388
|
+
targets(n.left);
|
|
389
|
+
if ((ts.isPrefixUnaryExpression(n) || ts.isPostfixUnaryExpression(n))
|
|
390
|
+
&& (n.operator === ts.SyntaxKind.PlusPlusToken || n.operator === ts.SyntaxKind.MinusMinusToken))
|
|
391
|
+
targets(n.operand);
|
|
392
|
+
if ((ts.isForOfStatement(n) || ts.isForInStatement(n)) && !ts.isVariableDeclarationList(n.initializer))
|
|
393
|
+
targets(n.initializer);
|
|
394
|
+
ts.forEachChild(n, walk);
|
|
395
|
+
};
|
|
396
|
+
walk(body);
|
|
397
|
+
for (const name of declared) {
|
|
398
|
+
this.problems.push(`it declares \`${name}\` at its top level, which ${this.runtime.name} has already declared with \`const\`: `
|
|
399
|
+
+ `the script would fail with "Identifier '${name}' has already been declared". Rename it, or declare it inside a nested block or function.`);
|
|
400
|
+
}
|
|
401
|
+
for (const name of assigned) {
|
|
402
|
+
this.problems.push(`it assigns to \`${name}\`, which ${this.runtime.name} declares with \`const\`: the script would fail with "Assignment to constant variable.". `
|
|
403
|
+
+ 'Use a local of another name.');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
309
406
|
/**
|
|
310
407
|
* Walk the body: queue the renames and `__name` removals, and collect the
|
|
311
408
|
* names the runtime would not have. `fnDepth` counts the non-arrow functions
|
|
@@ -374,8 +471,10 @@ class Lowering {
|
|
|
374
471
|
}
|
|
375
472
|
if (symbol && !isUndefinedSymbol(symbol))
|
|
376
473
|
return;
|
|
377
|
-
if (id.text === this.runtime.scopeGlobal || this.runtime.globals.has(id.text))
|
|
474
|
+
if (id.text === this.runtime.scopeGlobal || this.runtime.globals.has(id.text)) {
|
|
475
|
+
this.checkGlobalMember(id);
|
|
378
476
|
return;
|
|
477
|
+
}
|
|
379
478
|
// `typeof x` of a bare name does not throw when `x` is absent, so it is a
|
|
380
479
|
// legitimate probe. A compound operand (`typeof x.y`, `typeof f()`) is
|
|
381
480
|
// evaluated first and does throw, so its names are checked as usual.
|
|
@@ -383,6 +482,25 @@ class Lowering {
|
|
|
383
482
|
return;
|
|
384
483
|
this.free.add(id.text);
|
|
385
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* A member the runtime's object does not have (`crypto.getRandomValues`,
|
|
487
|
+
* `console.table`): read by name (`.x`, `['x']`) from the global itself.
|
|
488
|
+
*/
|
|
489
|
+
checkGlobalMember(id) {
|
|
490
|
+
const { ts } = this;
|
|
491
|
+
const members = this.runtime.globalMembers?.get(id.text);
|
|
492
|
+
if (!members)
|
|
493
|
+
return;
|
|
494
|
+
const p = id.parent;
|
|
495
|
+
const name = ts.isPropertyAccessExpression(p) && p.expression === id ? p.name.text
|
|
496
|
+
: ts.isElementAccessExpression(p) && p.expression === id && ts.isStringLiteralLike(p.argumentExpression) ? p.argumentExpression.text
|
|
497
|
+
: undefined;
|
|
498
|
+
if (name === undefined || members.has(name))
|
|
499
|
+
return;
|
|
500
|
+
const problem = `it reads \`${id.text}.${name}\`, which ${this.runtime.name}'s \`${id.text}\` does not have: it has only ${[...members].map((m) => `\`${m}\``).join(', ')}.`;
|
|
501
|
+
if (!this.problems.includes(problem))
|
|
502
|
+
this.problems.push(problem);
|
|
503
|
+
}
|
|
386
504
|
/** Whether an identifier resolves to a declaration in the function (as opposed to an ambient global). */
|
|
387
505
|
resolves(id) {
|
|
388
506
|
const s = this.checker.getSymbolAtLocation(id);
|
|
@@ -4,11 +4,23 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { ScriptRuntime } from '../../../core/script-function.js';
|
|
6
6
|
/**
|
|
7
|
-
* What a Flow script body can read: the language's ES2020 built-ins,
|
|
8
|
-
* and the runtime's `$vars`.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* What a Flow script body can read: the language's ES2020 built-ins, the
|
|
8
|
+
* globals the engine registers, `console`, and the runtime's `$vars`.
|
|
9
|
+
*
|
|
10
|
+
* Measured against UiPath/PO.BpmnEngine, which runs a script body with
|
|
11
|
+
* Jint 4.4.0 (`ScriptTaskService.InvokeScriptAsync`,
|
|
12
|
+
* `src/BpmnEngine/UiPath.PO.Worker/Services/ElementExecution/Script/ScriptTaskService.cs`):
|
|
13
|
+
* the body goes to `Engine.Evaluate` as a script, after `btoa`/`atob` (UTF-8),
|
|
14
|
+
* `randomUUID` and `crypto` (holding only `randomUUID`) are set
|
|
15
|
+
* (`src/BpmnEngine/UiPath.PO.BpmnEngine/Expressions/JavaScriptEvaluator.cs`) and
|
|
16
|
+
* two preludes declare {@link FLOW_SCRIPT_CONST_GLOBALS} with `const`. `Intl`,
|
|
17
|
+
* `URL`, `TextEncoder`, `Buffer`, `fetch`, `require` and `structuredClone` read
|
|
18
|
+
* `undefined` there, so they are absent here. Change this when the engine does.
|
|
19
|
+
*
|
|
20
|
+
* `aggregate`, `_aggregate`, `_pipe` and `uipath` exist but are the engine's
|
|
21
|
+
* undocumented helpers, so a script function may not read them; it may not
|
|
22
|
+
* redeclare or assign them either (`constGlobals`). Of `crypto` and `console`
|
|
23
|
+
* it may read only the members the engine's have (`globalMembers`).
|
|
12
24
|
*
|
|
13
25
|
* Annex B's `escape`/`unescape` are left off on purpose: they URL-encode, and
|
|
14
26
|
* `escape` is the likeliest name for an author's own HTML-escape helper, which
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* The names the engine's preludes declare with `const` before the script runs
|
|
3
|
+
* (`ScriptTaskService.cs`, `WithBuiltInFunctions` and `WithConsole`). At the
|
|
4
|
+
* body's top level, a `const`, `let`, `var`, `function` or `class` of one fails
|
|
5
|
+
* with "Identifier '…' has already been declared", and an assignment to one with
|
|
6
|
+
* "Assignment to constant variable." (both measured on Jint 4.4.0). Inside a
|
|
7
|
+
* nested block or function the name is the author's own and is fine.
|
|
8
|
+
*/
|
|
9
|
+
const FLOW_SCRIPT_CONST_GLOBALS = new Set(['console', 'uipath', 'aggregate', '_aggregate', '_pipe']);
|
|
10
|
+
/**
|
|
11
|
+
* The members the engine's own objects have; any other member reads
|
|
12
|
+
* `undefined`, so calling it throws "Property '…' of object is not a function"
|
|
13
|
+
* (measured on Jint 4.4.0). `crypto` holds only `randomUUID`
|
|
14
|
+
* (`JavaScriptEvaluator.cs`), and `console` only the five no-ops `WithConsole`
|
|
15
|
+
* declares (`ScriptTaskService.cs`).
|
|
16
|
+
*/
|
|
17
|
+
const FLOW_SCRIPT_GLOBAL_MEMBERS = new Map([
|
|
18
|
+
['crypto', new Set(['randomUUID'])],
|
|
19
|
+
['console', new Set(['debug', 'error', 'info', 'log', 'warn'])],
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* What a Flow script body can read: the language's ES2020 built-ins, the
|
|
23
|
+
* globals the engine registers, `console`, and the runtime's `$vars`.
|
|
24
|
+
*
|
|
25
|
+
* Measured against UiPath/PO.BpmnEngine, which runs a script body with
|
|
26
|
+
* Jint 4.4.0 (`ScriptTaskService.InvokeScriptAsync`,
|
|
27
|
+
* `src/BpmnEngine/UiPath.PO.Worker/Services/ElementExecution/Script/ScriptTaskService.cs`):
|
|
28
|
+
* the body goes to `Engine.Evaluate` as a script, after `btoa`/`atob` (UTF-8),
|
|
29
|
+
* `randomUUID` and `crypto` (holding only `randomUUID`) are set
|
|
30
|
+
* (`src/BpmnEngine/UiPath.PO.BpmnEngine/Expressions/JavaScriptEvaluator.cs`) and
|
|
31
|
+
* two preludes declare {@link FLOW_SCRIPT_CONST_GLOBALS} with `const`. `Intl`,
|
|
32
|
+
* `URL`, `TextEncoder`, `Buffer`, `fetch`, `require` and `structuredClone` read
|
|
33
|
+
* `undefined` there, so they are absent here. Change this when the engine does.
|
|
34
|
+
*
|
|
35
|
+
* `aggregate`, `_aggregate`, `_pipe` and `uipath` exist but are the engine's
|
|
36
|
+
* undocumented helpers, so a script function may not read them; it may not
|
|
37
|
+
* redeclare or assign them either (`constGlobals`). Of `crypto` and `console`
|
|
38
|
+
* it may read only the members the engine's have (`globalMembers`).
|
|
7
39
|
*
|
|
8
40
|
* Annex B's `escape`/`unescape` are left off on purpose: they URL-encode, and
|
|
9
41
|
* `escape` is the likeliest name for an author's own HTML-escape helper, which
|
|
@@ -15,8 +47,11 @@
|
|
|
15
47
|
export const FLOW_SCRIPT_RUNTIME = {
|
|
16
48
|
scopeGlobal: '$vars',
|
|
17
49
|
name: 'the Flow script runtime',
|
|
50
|
+
constGlobals: FLOW_SCRIPT_CONST_GLOBALS,
|
|
51
|
+
globalMembers: FLOW_SCRIPT_GLOBAL_MEMBERS,
|
|
18
52
|
globals: new Set([
|
|
19
53
|
'undefined', 'NaN', 'Infinity', 'globalThis', 'console',
|
|
54
|
+
'btoa', 'atob', 'randomUUID', 'crypto',
|
|
20
55
|
'Object', 'Function', 'Array', 'Number', 'Boolean', 'String', 'Symbol', 'BigInt', 'Date', 'RegExp', 'Math', 'JSON',
|
|
21
56
|
'Promise', 'Proxy', 'Reflect', 'Map', 'Set', 'WeakMap', 'WeakSet',
|
|
22
57
|
'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError',
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The globals the Flow script runtime gives a script body beyond the language,
|
|
3
|
+
* as types, for a `.flow.ts` that type-checks its script functions.
|
|
4
|
+
*
|
|
5
|
+
* Opt in from the file that uses them:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* /// <reference types="@uipath/maestro-builder-sdk/script-globals" />
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Types only. A script function is never called in Node; its body runs on the
|
|
12
|
+
* engine, which provides these names (UiPath/PO.BpmnEngine,
|
|
13
|
+
* `src/BpmnEngine/UiPath.PO.BpmnEngine/Expressions/JavaScriptEvaluator.cs`).
|
|
14
|
+
* Importing an implementation instead would not work: `compile` refuses a script
|
|
15
|
+
* function that reads a name from its module.
|
|
16
|
+
*
|
|
17
|
+
* Opt-in rather than part of the package entry, so importing the SDK does not
|
|
18
|
+
* add globals to every program that does.
|
|
19
|
+
*
|
|
20
|
+
* `btoa` and `atob` repeat the signatures `@types/node` and the DOM library
|
|
21
|
+
* declare, so they merge with either; they are here for a program that has
|
|
22
|
+
* neither. Note the engine's are UTF-8, where Node's and the browser's are
|
|
23
|
+
* Latin-1 and throw on a character above U+00FF. `crypto` is not declared:
|
|
24
|
+
* the engine's holds only `randomUUID`, and both of those libraries already
|
|
25
|
+
* type `crypto.randomUUID()`.
|
|
26
|
+
*/
|
|
27
|
+
export {};
|
|
28
|
+
declare global {
|
|
29
|
+
/** A fresh lowercase, hyphenated v4 UUID. The engine also exposes it as `crypto.randomUUID`. */
|
|
30
|
+
function randomUUID(): string;
|
|
31
|
+
/** Base64 of a string's UTF-8 bytes, in the engine (Node's and the browser's take Latin-1). */
|
|
32
|
+
function btoa(data: string): string;
|
|
33
|
+
/** The string whose UTF-8 bytes a Base64 string encodes, in the engine. */
|
|
34
|
+
function atob(data: string): string;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.60.0",
|
|
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",
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"types": "./dist/cli/index.d.ts",
|
|
38
38
|
"import": "./dist/cli/index.js"
|
|
39
39
|
},
|
|
40
|
+
"./script-globals": {
|
|
41
|
+
"types": "./dist/script-globals.d.ts",
|
|
42
|
+
"import": "./dist/script-globals.js"
|
|
43
|
+
},
|
|
40
44
|
"./registry-materializer": {
|
|
41
45
|
"types": "./dist/registry/materializer.d.ts",
|
|
42
46
|
"import": "./dist/registry/materializer.js"
|
|
@@ -96,5 +100,5 @@
|
|
|
96
100
|
"@types/node": "^22.7.0",
|
|
97
101
|
"esbuild": "^0.28.1"
|
|
98
102
|
},
|
|
99
|
-
"gitref": "
|
|
103
|
+
"gitref": "d9059695caec0a3b1f3b215f5b830451dbdbdad9"
|
|
100
104
|
}
|