@uipath/maestro-builder-sdk 6.55.0 → 6.57.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.
@@ -0,0 +1,203 @@
1
+ import { relative } from 'node:path';
2
+ import { typescript } from './core/typescript-module.js';
3
+ import { isReference } from './core/script-function.js';
4
+ /**
5
+ * Every name a `script(fn)` function in `file` (or a file it imports) reads from
6
+ * outside itself, and every call whose function it could not follow.
7
+ *
8
+ * It follows a bare identifier (through `const`, a function declaration and
9
+ * imports between the author's files) and an inline function. Any other
10
+ * function-typed argument (`script(h.check)`, `script(fns.check)`, a wrapper's
11
+ * parameter) is `unfollowed`, and the caller refuses it rather than let a capture
12
+ * through unchecked. An argument that is not a function (`script({ code })`, or
13
+ * options passed by name) is the string form and is not a script function.
14
+ */
15
+ export function scriptFunctionScope(file) {
16
+ const ts = typescript();
17
+ const options = {
18
+ noLib: true, types: [], noEmit: true, skipLibCheck: true, allowImportingTsExtensions: true,
19
+ target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.NodeNext, moduleResolution: ts.ModuleResolutionKind.NodeNext,
20
+ };
21
+ const program = ts.createProgram([file], options);
22
+ const checker = program.getTypeChecker();
23
+ const cwd = process.cwd();
24
+ const where = (n) => {
25
+ const sf = n.getSourceFile();
26
+ return `${relative(cwd, sf.fileName) || sf.fileName}:${sf.getLineAndCharacterOfPosition(n.getStart()).line + 1}`;
27
+ };
28
+ const out = [];
29
+ const unfollowed = [];
30
+ const untyped = [];
31
+ const seen = new Set();
32
+ const followedTexts = new Set();
33
+ const strip = stripTypes();
34
+ for (const sf of program.getSourceFiles()) {
35
+ if (sf.isDeclarationFile || sf.fileName.includes('/node_modules/'))
36
+ continue;
37
+ const visit = (n) => {
38
+ if (ts.isCallExpression(n) && isScriptCall(ts, checker, n) && n.arguments.length > 0) {
39
+ const arg = n.arguments[0];
40
+ const fn = functionOf(ts, checker, arg);
41
+ if (!fn) {
42
+ const type = checker.getTypeAtLocation(arg);
43
+ if (type.getCallSignatures().length > 0)
44
+ unfollowed.push(where(arg));
45
+ else if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown))
46
+ untyped.push(where(arg));
47
+ }
48
+ if (fn && !seen.has(fn)) {
49
+ seen.add(fn);
50
+ const text = strip?.(fnText(ts, fn));
51
+ if (text !== undefined)
52
+ followedTexts.add(text);
53
+ const call = `script(${ts.isIdentifier(n.arguments[0]) ? n.arguments[0].text : 'function'})`;
54
+ for (const c of capturesIn(ts, checker, fn))
55
+ out.push({ call, name: c.id.text, declaredAt: where(c.decl), readAt: where(c.id) });
56
+ }
57
+ }
58
+ ts.forEachChild(n, visit);
59
+ };
60
+ visit(sf);
61
+ }
62
+ return { captures: out, unfollowed, untyped, followed: seen.size, followedTexts };
63
+ }
64
+ /** A whitespace-free key for comparing function texts. */
65
+ export function functionTextKey(text) {
66
+ return text.replace(/\s+/g, '');
67
+ }
68
+ /**
69
+ * Node's own type stripping (`module.stripTypeScriptTypes`, Node 22.13+), as a
70
+ * function from a function's source text to its key; undefined when this Node
71
+ * does not have it, or the text does not strip on its own.
72
+ */
73
+ function stripTypes() {
74
+ const getBuiltinModule = process.getBuiltinModule;
75
+ const mod = getBuiltinModule?.('node:module');
76
+ const stripTs = mod?.stripTypeScriptTypes;
77
+ if (!stripTs)
78
+ return undefined;
79
+ return (text) => {
80
+ try {
81
+ // Parenthesised, so an anonymous function expression is a valid statement.
82
+ const stripped = stripTs(`(${text})`, { mode: 'strip' });
83
+ return functionTextKey(stripped.slice(1, -1));
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ };
89
+ }
90
+ /** A function's source as `toString()` would start it: without `export` / `default`. */
91
+ function fnText(ts, fn) {
92
+ const sf = fn.getSourceFile();
93
+ const mods = ts.canHaveModifiers(fn) ? ts.getModifiers(fn) ?? [] : [];
94
+ const lead = mods.filter((m) => m.kind === ts.SyntaxKind.ExportKeyword || m.kind === ts.SyntaxKind.DefaultKeyword);
95
+ const start = lead.length > 0 ? lead[lead.length - 1].end : fn.getStart();
96
+ return sf.text.slice(start, fn.end).trim();
97
+ }
98
+ /**
99
+ * Why a loaded flow cannot be trusted with its script functions, or undefined.
100
+ *
101
+ * Fails closed: a function the pass could not follow, or fewer functions found in
102
+ * the source than `script(fn)` lowered (a call made through `const sc = script`,
103
+ * a re-exporting module, or a function from a file outside the program), is
104
+ * refused like a capture, because what it reads was not checked.
105
+ */
106
+ export function scriptFunctionScopeProblem(file, lowered) {
107
+ const scope = scriptFunctionScope(file);
108
+ const parts = [];
109
+ if (scope.captures.length > 0)
110
+ parts.push(scriptFunctionCaptureMessage(scope.captures));
111
+ // Which lowered functions the pass accounts for. Matched by text when it can
112
+ // be: under Node's type stripping a lowered function's text IS its
113
+ // declaration's, minus types, so each one must match a followed declaration,
114
+ // and a `script()` call that never ran accounts for nothing. When nothing
115
+ // matches at all, either the loader reprints (tsx) or nothing lowered was
116
+ // followed; then an argument it could not type keeps it closed, and the
117
+ // count is the backstop.
118
+ const keys = [...new Set(lowered.map(functionTextKey))];
119
+ const matched = keys.filter((k) => scope.followedTexts.has(k)).length;
120
+ const unaccounted = matched > 0
121
+ ? keys.length - matched
122
+ : (scope.untyped.length > 0 ? Math.max(scope.untyped.length, 1) : Math.max(0, keys.length - scope.followed));
123
+ const where = [...scope.unfollowed, ...(matched > 0 ? [] : scope.untyped)];
124
+ if (scope.unfollowed.length > 0 || unaccounted > 0) {
125
+ const at = where.length > 0 ? ` (${where.join(', ')})` : '';
126
+ parts.push(`${Math.max(scope.unfollowed.length + unaccounted, 1)} script function(s) could not be checked for names taken from the module${at}: `
127
+ + 'pass each function to `script()` by name, or inline (`script(fn)`, `script((vars) => …)`), from a `.ts` file, with `script` imported by that name.');
128
+ }
129
+ return parts.length > 0 ? parts.join('\n') : undefined;
130
+ }
131
+ /** The message a load refuses with, one line per capture. */
132
+ export function scriptFunctionCaptureMessage(captures) {
133
+ const lines = captures.map((c) => ` ${c.call} reads ${c.name} (${c.readAt}), which resolves to ${c.declaredAt} in the author's code, `
134
+ + `but the lowered script runs alone, so ${c.name} would be the runtime's own global of that name, or nothing.`);
135
+ return `${captures.length} script function read(s) a name from outside the function:\n${lines.join('\n')}\n`
136
+ + 'Move the helper\'s code inside the function, or give it a name the runtime does not have.';
137
+ }
138
+ /**
139
+ * Whether a call is `script(…)` from an import: the callee is an identifier
140
+ * imported under the name `script`, or `<namespace import>.script`.
141
+ */
142
+ function isScriptCall(ts, checker, call) {
143
+ const callee = call.expression;
144
+ if (ts.isIdentifier(callee)) {
145
+ const decl = checker.getSymbolAtLocation(callee)?.declarations?.[0];
146
+ return decl !== undefined && ts.isImportSpecifier(decl) && (decl.propertyName ?? decl.name).text === 'script';
147
+ }
148
+ if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'script' && ts.isIdentifier(callee.expression)) {
149
+ const decl = checker.getSymbolAtLocation(callee.expression)?.declarations?.[0];
150
+ return decl !== undefined && ts.isNamespaceImport(decl);
151
+ }
152
+ return false;
153
+ }
154
+ /** The function an argument is, or names: inline, a declaration, or a `const` bound to one, through imports. */
155
+ function functionOf(ts, checker, arg) {
156
+ const fnLike = (n) => {
157
+ while (n && (ts.isParenthesizedExpression(n) || ts.isAsExpression(n) || ts.isSatisfiesExpression(n)))
158
+ n = n.expression;
159
+ return n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) ? n : undefined;
160
+ };
161
+ const inline = fnLike(arg);
162
+ if (inline)
163
+ return inline;
164
+ if (!ts.isIdentifier(arg))
165
+ return undefined;
166
+ let symbol = checker.getSymbolAtLocation(arg);
167
+ if (symbol && symbol.flags & ts.SymbolFlags.Alias)
168
+ symbol = checker.getAliasedSymbol(symbol);
169
+ for (const d of symbol?.declarations ?? []) {
170
+ const f = ts.isVariableDeclaration(d) ? fnLike(d.initializer) : fnLike(d);
171
+ if (f)
172
+ return f;
173
+ }
174
+ return undefined;
175
+ }
176
+ /** Each read in `fn` of a binding declared outside it, with that declaration. */
177
+ function capturesIn(ts, checker, fn) {
178
+ const found = [];
179
+ const names = new Set();
180
+ const inside = (d) => d.getSourceFile() === fn.getSourceFile() && d.pos >= fn.pos && d.end <= fn.end;
181
+ const visit = (n) => {
182
+ if (ts.isIdentifier(n) && isReference(ts, n) && !names.has(n.text)) {
183
+ const p = n.parent;
184
+ const symbol = ts.isShorthandPropertyAssignment(p) && p.name === n
185
+ ? checker.getShorthandAssignmentValueSymbol(p)
186
+ : checker.getSymbolAtLocation(n);
187
+ // An import, or any binding the author's own files declare outside the
188
+ // function. A name that resolves to nothing, or only to a declaration file
189
+ // (`console` once `@types/node` is in the program), is a global, and the
190
+ // lowering has judged it already. An import still lands on its specifier
191
+ // in the author's file.
192
+ const decl = symbol?.declarations?.find((d) => !inside(d) && !d.getSourceFile().isDeclarationFile);
193
+ if (decl && !symbol.declarations.some(inside)) {
194
+ names.add(n.text);
195
+ found.push({ id: n, decl });
196
+ }
197
+ }
198
+ ts.forEachChild(n, visit);
199
+ };
200
+ if (fn.body)
201
+ visit(fn.body);
202
+ return found;
203
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.55.0",
3
+ "version": "6.57.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",
@@ -96,5 +96,5 @@
96
96
  "@types/node": "^22.7.0",
97
97
  "esbuild": "^0.28.1"
98
98
  },
99
- "gitref": "64e3a8da320ffa0947e1a7edddcbabbdb9377e41"
99
+ "gitref": "16b32dbe18fb8635231d86b377d0595c64e95e51"
100
100
  }