@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, `console`,
8
- * and the runtime's `$vars`. From the Flow skill's `references/script.md` ("The
9
- * JavaScript runtime"), which also lists what reads `undefined` in a real run and
10
- * is therefore absent here: `Intl`, `URL`, `TextEncoder`, `Buffer`, `fetch`,
11
- * `require`, `structuredClone`. Change this when the runtime changes.
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
- * What a Flow script body can read: the language's ES2020 built-ins, `console`,
3
- * and the runtime's `$vars`. From the Flow skill's `references/script.md` ("The
4
- * JavaScript runtime"), which also lists what reads `undefined` in a real run and
5
- * is therefore absent here: `Intl`, `URL`, `TextEncoder`, `Buffer`, `fetch`,
6
- * `require`, `structuredClone`. Change this when the runtime changes.
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.59.1",
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": "b7b4631f9c98cab1479e0d5ec9495f43651e93f8"
103
+ "gitref": "d9059695caec0a3b1f3b215f5b830451dbdbdad9"
100
104
  }