@uipath/maestro-builder-sdk 6.51.3 → 6.52.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,68 @@
1
+ /** The `package.json` key the trust list lives under. */
2
+ export declare const NODE_PACKAGES_MANIFEST_KEY = "maestroBuilderSdk";
3
+ export declare const NODE_PACKAGES_LIST_KEY = "nodePackages";
4
+ export declare function isPackageName(spec: string): boolean;
5
+ export interface TrustedPackages {
6
+ readonly names: ReadonlySet<string>;
7
+ /** Where each trusted name came from: the manifest path, or `--package`. */
8
+ readonly sources: ReadonlyMap<string, string>;
9
+ /** The `package.json` consulted, when one was found above the file. */
10
+ readonly manifest?: string;
11
+ }
12
+ /** The nearest `package.json` at or above `dir`, or undefined. */
13
+ export declare function nearestPackageJson(dir: string): string | undefined;
14
+ /**
15
+ * The packages this run may load: the project's `nodePackages` list plus the
16
+ * `--package` names. An entry that is not a bare package name is refused with
17
+ * the file it came from, so a manifest cannot smuggle a path or a URL in.
18
+ */
19
+ export declare function trustedNodePackages(flowPath: string, flagged?: readonly string[]): TrustedPackages;
20
+ /**
21
+ * The packages a flow's definitions name for node types the registry does not
22
+ * know yet, keyed by package, with the node types each one would explain.
23
+ */
24
+ export declare function provenancePackages(flow: {
25
+ definitions?: unknown;
26
+ }): Map<string, string[]>;
27
+ /**
28
+ * Resolve `pkg` (and an optional `subpath`, `'./runtime'`) the way an ESM
29
+ * `import` from a file in `fromDir` would: walk up `node_modules` directories
30
+ * from there, and read the package's `exports` under import conditions — not
31
+ * `require.resolve`, whose CommonJS conditions refuse an import-only package and
32
+ * can pick a different entry of a dual one. Falls back to `main`, then
33
+ * `index.js`, for a package with no `exports`. Throws a sentence when nothing resolves.
34
+ */
35
+ export declare function resolvePackageEntry(pkg: string, fromDir: string, subpath?: string): string;
36
+ export interface LoadedNodePackages {
37
+ /** Packages imported, with where their trust came from. */
38
+ readonly loaded: ReadonlyArray<{
39
+ readonly package: string;
40
+ readonly trustedBy: string;
41
+ readonly resolved: string;
42
+ }>;
43
+ /** Packages the file names that the project does not trust; their nodes print as `rawNode`. */
44
+ readonly untrusted: ReadonlyArray<{
45
+ readonly package: string;
46
+ readonly nodeTypes: readonly string[];
47
+ }>;
48
+ /** Trusted packages that do not resolve from the file's directory. */
49
+ readonly missing: ReadonlyArray<{
50
+ readonly package: string;
51
+ readonly nodeTypes: readonly string[];
52
+ readonly error: string;
53
+ }>;
54
+ /** The warnings a tool prints, one sentence each. */
55
+ readonly warnings: readonly string[];
56
+ }
57
+ /** How to trust a package, as the warnings say it. */
58
+ export declare function trustHint(pkg: string): string;
59
+ /**
60
+ * Load every trusted package the flow needs (and every `--package`), resolved
61
+ * from the flow's directory. Importing a package registers its family through
62
+ * `/extend`; the registry adopts it at once, so a `decompile` that follows sees
63
+ * the types. Nothing is thrown for an untrusted or missing package: those are
64
+ * reported, and the caller's decompile falls back to `rawNode` for their nodes.
65
+ */
66
+ export declare function loadNodePackages(flowPath: string, flow: {
67
+ definitions?: unknown;
68
+ }, flagged?: readonly string[]): Promise<LoadedNodePackages>;
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Loading NODE-TYPE PACKAGES for a tool that has no author's module to import.
3
+ *
4
+ * `compile` needs none of this: the author's `.flow.ts` imports the package, the
5
+ * package registers its family (`@uipath/maestro-builder-sdk/extend`), and the
6
+ * build runs against a registry that knows the kinds. `decompile` and
7
+ * `merge --baseline-from` read a data-only `.flow`, so they have to learn which
8
+ * packages the file needs and load them — and loading a package runs its code.
9
+ *
10
+ * ## Provenance is discovery, not authorization
11
+ *
12
+ * A package type's definition in `definitions[]` carries `source.package`, the
13
+ * package that wrote it. That names what a node NEEDS; it never, on its own,
14
+ * causes an import. A package is loaded only when the project chose it:
15
+ *
16
+ * - listed under `"maestroBuilderSdk": { "nodePackages": [...] }` in the nearest
17
+ * `package.json` above the `.flow`, or
18
+ * - named with `--package <name>` on the command line.
19
+ *
20
+ * The trust boundary is the one the project already has — `npm install` plus its
21
+ * own manifest — and nothing in a `.flow` can widen it. An untrusted package's
22
+ * nodes are printed as `rawNode(...)`, and the warning names the package and the
23
+ * two ways to trust it. Specifiers are validated as bare package names before
24
+ * anything is resolved: no paths, no URLs.
25
+ *
26
+ * ## Resolution is relative to the workflow, not the tool
27
+ *
28
+ * A globally installed SDK has its own `node_modules`, where the project's
29
+ * packages are not. Every package is resolved from the `.flow`'s own directory
30
+ * (`createRequire` anchored at the file) and the resolved URL is imported. The
31
+ * package's own `@uipath/maestro-builder-sdk/extend` import is redirected to the
32
+ * running SDK (`sdk-resolve-hook.ts`), so its family lands in this registry even
33
+ * when the project has a different copy of the SDK, or none.
34
+ *
35
+ * See docs/EXTENSIBLE_NODE_TYPES_DESIGN.md §3.5.
36
+ */
37
+ import { existsSync, readFileSync } from 'node:fs';
38
+ import { dirname, join, resolve } from 'node:path';
39
+ import { pathToFileURL } from 'node:url';
40
+ import { FLOW_NODE_TYPES } from './flow-node-registry.js';
41
+ import { useThisSdkForAuthoredSource } from './sdk-resolve.js';
42
+ /** The `package.json` key the trust list lives under. */
43
+ export const NODE_PACKAGES_MANIFEST_KEY = 'maestroBuilderSdk';
44
+ export const NODE_PACKAGES_LIST_KEY = 'nodePackages';
45
+ /** A bare npm package name: `name` or `@scope/name`; no path, no URL, no version. */
46
+ const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
47
+ export function isPackageName(spec) {
48
+ return PACKAGE_NAME.test(spec) && spec.length <= 214;
49
+ }
50
+ /** The nearest `package.json` at or above `dir`, or undefined. */
51
+ export function nearestPackageJson(dir) {
52
+ let current = resolve(dir);
53
+ for (;;) {
54
+ const candidate = join(current, 'package.json');
55
+ if (existsSync(candidate))
56
+ return candidate;
57
+ const parent = dirname(current);
58
+ if (parent === current)
59
+ return undefined;
60
+ current = parent;
61
+ }
62
+ }
63
+ /**
64
+ * The packages this run may load: the project's `nodePackages` list plus the
65
+ * `--package` names. An entry that is not a bare package name is refused with
66
+ * the file it came from, so a manifest cannot smuggle a path or a URL in.
67
+ */
68
+ export function trustedNodePackages(flowPath, flagged = []) {
69
+ const names = new Set();
70
+ const sources = new Map();
71
+ for (const name of flagged) {
72
+ if (!isPackageName(name))
73
+ throw new Error(`--package ${JSON.stringify(name)} is not a bare npm package name (expected name or @scope/name).`);
74
+ names.add(name);
75
+ sources.set(name, '--package');
76
+ }
77
+ const manifest = nearestPackageJson(dirname(resolve(flowPath)));
78
+ if (manifest !== undefined) {
79
+ let parsed;
80
+ try {
81
+ parsed = JSON.parse(readFileSync(manifest, 'utf8'));
82
+ }
83
+ catch (error) {
84
+ throw new Error(`${manifest}: cannot read the nodePackages list: ${error.message}`);
85
+ }
86
+ const section = parsed?.[NODE_PACKAGES_MANIFEST_KEY];
87
+ const list = section?.[NODE_PACKAGES_LIST_KEY];
88
+ if (list !== undefined) {
89
+ if (!Array.isArray(list) || !list.every((entry) => typeof entry === 'string')) {
90
+ throw new Error(`${manifest}: "${NODE_PACKAGES_MANIFEST_KEY}"."${NODE_PACKAGES_LIST_KEY}" must be an array of package names.`);
91
+ }
92
+ for (const name of list) {
93
+ if (!isPackageName(name)) {
94
+ throw new Error(`${manifest}: nodePackages entry ${JSON.stringify(name)} is not a bare npm package name (expected name or @scope/name).`);
95
+ }
96
+ if (!names.has(name)) {
97
+ names.add(name);
98
+ sources.set(name, manifest);
99
+ }
100
+ }
101
+ }
102
+ }
103
+ return { names, sources, manifest };
104
+ }
105
+ /**
106
+ * The packages a flow's definitions name for node types the registry does not
107
+ * know yet, keyed by package, with the node types each one would explain.
108
+ */
109
+ export function provenancePackages(flow) {
110
+ const out = new Map();
111
+ const definitions = Array.isArray(flow.definitions) ? flow.definitions : [];
112
+ for (const def of definitions) {
113
+ const nodeType = typeof def?.nodeType === 'string' ? def.nodeType : undefined;
114
+ const source = def?.source;
115
+ const pkg = typeof source?.package === 'string' ? source.package : undefined;
116
+ if (nodeType === undefined || pkg === undefined)
117
+ continue;
118
+ if (FLOW_NODE_TYPES.byTypeName(nodeType) !== undefined)
119
+ continue;
120
+ const list = out.get(pkg) ?? [];
121
+ if (!list.includes(nodeType))
122
+ list.push(nodeType);
123
+ out.set(pkg, list);
124
+ }
125
+ return out;
126
+ }
127
+ /** The conditions an ESM `import` resolves `exports` with, in Node's order. */
128
+ const IMPORT_CONDITIONS = ['node', 'import', 'default'];
129
+ /** `exports` target for `subpath` under import conditions; undefined when the package does not export it. */
130
+ function exportsTarget(exportsField, subpath) {
131
+ const pick = (value) => {
132
+ if (typeof value === 'string')
133
+ return value;
134
+ if (Array.isArray(value)) {
135
+ for (const v of value) {
136
+ const hit = pick(v);
137
+ if (hit !== undefined)
138
+ return hit;
139
+ }
140
+ return undefined;
141
+ }
142
+ if (value !== null && typeof value === 'object') {
143
+ for (const [key, v] of Object.entries(value)) {
144
+ if (IMPORT_CONDITIONS.includes(key)) {
145
+ const hit = pick(v);
146
+ if (hit !== undefined)
147
+ return hit;
148
+ }
149
+ }
150
+ }
151
+ return undefined;
152
+ };
153
+ if (typeof exportsField === 'string' || Array.isArray(exportsField))
154
+ return subpath === '.' ? pick(exportsField) : undefined;
155
+ if (exportsField === null || typeof exportsField !== 'object')
156
+ return undefined;
157
+ const map = exportsField;
158
+ // Either a subpath map ({ ".": …, "./runtime": … }) or bare conditions for ".".
159
+ if (!Object.keys(map).some((k) => k.startsWith('.')))
160
+ return subpath === '.' ? pick(map) : undefined;
161
+ if (Object.hasOwn(map, subpath))
162
+ return pick(map[subpath]);
163
+ // Subpath patterns, as Node resolves them: a key with one `*` matches when the
164
+ // subpath has its prefix and suffix; the longest prefix wins, and the matched
165
+ // part replaces every `*` in the target ("./*": "./dist/*.js" → ./dist/runtime.js).
166
+ let best;
167
+ for (const key of Object.keys(map)) {
168
+ const star = key.indexOf('*');
169
+ if (star < 0 || key.indexOf('*', star + 1) >= 0)
170
+ continue;
171
+ const prefix = key.slice(0, star);
172
+ const suffix = key.slice(star + 1);
173
+ if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix) || subpath.length < prefix.length + suffix.length)
174
+ continue;
175
+ if (best === undefined || prefix.length > best.prefix.length)
176
+ best = { key, prefix, match: subpath.slice(prefix.length, subpath.length - suffix.length) };
177
+ }
178
+ if (best === undefined)
179
+ return undefined;
180
+ const target = pick(map[best.key]);
181
+ return target === undefined ? undefined : target.split('*').join(best.match);
182
+ }
183
+ /**
184
+ * Resolve `pkg` (and an optional `subpath`, `'./runtime'`) the way an ESM
185
+ * `import` from a file in `fromDir` would: walk up `node_modules` directories
186
+ * from there, and read the package's `exports` under import conditions — not
187
+ * `require.resolve`, whose CommonJS conditions refuse an import-only package and
188
+ * can pick a different entry of a dual one. Falls back to `main`, then
189
+ * `index.js`, for a package with no `exports`. Throws a sentence when nothing resolves.
190
+ */
191
+ export function resolvePackageEntry(pkg, fromDir, subpath = '.') {
192
+ let current = resolve(fromDir);
193
+ for (;;) {
194
+ const root = join(current, 'node_modules', ...pkg.split('/'));
195
+ const manifestPath = join(root, 'package.json');
196
+ if (existsSync(manifestPath)) {
197
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
198
+ let target;
199
+ if (manifest.exports !== undefined) {
200
+ target = exportsTarget(manifest.exports, subpath);
201
+ if (target === undefined)
202
+ throw new Error(`${pkg} does not export "${subpath}" for import (${manifestPath}).`);
203
+ }
204
+ else if (subpath === '.') {
205
+ target = typeof manifest.main === 'string' ? manifest.main : 'index.js';
206
+ }
207
+ else {
208
+ target = subpath;
209
+ }
210
+ const file = resolve(root, target);
211
+ if (!existsSync(file))
212
+ throw new Error(`${pkg}'s entry ${target} is missing (${file}).`);
213
+ return file;
214
+ }
215
+ const parent = dirname(current);
216
+ if (parent === current)
217
+ throw new Error(`Cannot find package '${pkg}' in any node_modules above ${fromDir}.`);
218
+ current = parent;
219
+ }
220
+ }
221
+ /** How to trust a package, as the warnings say it. */
222
+ export function trustHint(pkg) {
223
+ return `add it to package.json ("${NODE_PACKAGES_MANIFEST_KEY}": { "${NODE_PACKAGES_LIST_KEY}": [${JSON.stringify(pkg)}] }) or pass --package ${pkg}`;
224
+ }
225
+ /**
226
+ * Load every trusted package the flow needs (and every `--package`), resolved
227
+ * from the flow's directory. Importing a package registers its family through
228
+ * `/extend`; the registry adopts it at once, so a `decompile` that follows sees
229
+ * the types. Nothing is thrown for an untrusted or missing package: those are
230
+ * reported, and the caller's decompile falls back to `rawNode` for their nodes.
231
+ */
232
+ export async function loadNodePackages(flowPath, flow, flagged = []) {
233
+ const trusted = trustedNodePackages(flowPath, flagged);
234
+ const needed = provenancePackages(flow);
235
+ // Every package the project trusts is a candidate, not only the ones provenance
236
+ // names: a hand-written or older .flow whose definition carries no
237
+ // `source.package` is served by listing the package (or passing --package).
238
+ for (const name of trusted.names)
239
+ if (!needed.has(name))
240
+ needed.set(name, []);
241
+ const loaded = [];
242
+ const untrusted = [];
243
+ const missing = [];
244
+ const warnings = [];
245
+ const fromDir = dirname(resolve(flowPath));
246
+ for (const [pkg, nodeTypes] of needed) {
247
+ if (!trusted.names.has(pkg)) {
248
+ untrusted.push({ package: pkg, nodeTypes });
249
+ warnings.push(`node type${nodeTypes.length === 1 ? '' : 's'} ${nodeTypes.map((t) => `"${t}"`).join(', ')} come${nodeTypes.length === 1 ? 's' : ''} from package ${pkg} `
250
+ + `(definitions[].source.package), which this project does not trust; ${trustHint(pkg)}. Its nodes are printed as rawNode(...).`);
251
+ continue;
252
+ }
253
+ if (!isPackageName(pkg)) {
254
+ // A trusted list never holds one of these (trustedNodePackages refuses), so this is provenance lying.
255
+ untrusted.push({ package: pkg, nodeTypes });
256
+ warnings.push(`definitions[].source.package ${JSON.stringify(pkg)} is not a bare npm package name; its nodes are printed as rawNode(...).`);
257
+ continue;
258
+ }
259
+ let resolved;
260
+ try {
261
+ resolved = resolvePackageEntry(pkg, fromDir);
262
+ }
263
+ catch (error) {
264
+ missing.push({ package: pkg, nodeTypes, error: error.message });
265
+ warnings.push(`package ${pkg} is trusted (${trusted.sources.get(pkg)}) but does not resolve from ${fromDir}: ${error.message} Install it beside the flow (npm install ${pkg}); its nodes are printed as rawNode(...).`);
266
+ continue;
267
+ }
268
+ // A package imports `@uipath/maestro-builder-sdk/extend`. Point that at THIS
269
+ // SDK, as compile does for authored source: a second copy (the project's own,
270
+ // under a globally installed tool) would register the family into a registry
271
+ // this run never reads. Installed only now, right before a real import, so a
272
+ // run that falls back to rawNode for every package never needs the hook —
273
+ // a bundled CLI has none (tests/cli-bundle.test.ts).
274
+ useThisSdkForAuthoredSource();
275
+ await import(pathToFileURL(resolved).href);
276
+ loaded.push({ package: pkg, trustedBy: trusted.sources.get(pkg), resolved });
277
+ }
278
+ return { loaded, untrusted, missing, warnings };
279
+ }
@@ -30,7 +30,8 @@ export type DraftCode = BindingRefusalCode | typeof CONNECTOR_INPUT_MISSING | 'C
30
30
  export interface DraftFinding {
31
31
  /** The emitted node's id. */
32
32
  nodeId: string;
33
- code: DraftCode;
33
+ /** A {@link DraftCode}, or a node-type package's own code reported through `ctx.draft`. */
34
+ code: DraftCode | string;
34
35
  /** The sentence the strict compile refuses with. */
35
36
  message: string;
36
37
  }
package/dist/serialize.js CHANGED
@@ -2399,8 +2399,15 @@ export function serialize(built, opts = {}) {
2399
2399
  // makeNode, with the message every definition's mismatch gets.
2400
2400
  def = resolveContributionDefinition(contribution.definition, entry.type.versionedByDefinition ? undefined : version, where);
2401
2401
  }
2402
- else if (impl.definition === 'per-tenant' && resolved.carried === undefined) {
2403
- throw new Error(`internal SDK error: ${where}: per-tenant node type ${impl.typeName}@${version} returned no definition.`);
2402
+ else if (impl.definition !== 'bundled' && resolved.carried === undefined) {
2403
+ throw new Error(`internal SDK error: ${where}: ${impl.definition} node type ${impl.typeName}@${version} returned no definition.`);
2404
+ }
2405
+ // A package definition carries its provenance: which package wrote it, at
2406
+ // which version. Decompile loads the package's authoring half from it, and
2407
+ // the local engine the package's runtime half (docs/EXTENSIBLE_NODE_TYPES_DESIGN.md).
2408
+ if (impl.definition === 'package' && def !== undefined) {
2409
+ const origin = entry.family.package;
2410
+ def = { ...def, source: { package: origin.name, ...(origin.version !== undefined ? { version: origin.version } : {}) } };
2404
2411
  }
2405
2412
  // A type that owns a family of names (`typeNamePrefix`) is placed under the
2406
2413
  // name its definition carries — `uipath.core.function.<key>`, not the prefix.
@@ -2413,7 +2420,7 @@ export function serialize(built, opts = {}) {
2413
2420
  throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${version} supplied a definition for `
2414
2421
  + `${JSON.stringify(nodeType)}, which is not a name it owns under "${prefix}".`);
2415
2422
  }
2416
- if (impl.definition === 'per-tenant') {
2423
+ if (impl.definition !== 'bundled') {
2417
2424
  // At a version the flow carries, the carried definition answers: the
2418
2425
  // type's declaration speaks for the versions it registers, and an older
2419
2426
  // tenant definition (an inline agent at 1.1) may have no error handle.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.51.3",
3
+ "version": "6.52.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",
@@ -29,6 +29,10 @@
29
29
  "types": "./dist/bpmn/bpmn-sdk.d.ts",
30
30
  "import": "./dist/bpmn/bpmn-sdk.js"
31
31
  },
32
+ "./extend": {
33
+ "types": "./dist/extend.d.ts",
34
+ "import": "./dist/extend.js"
35
+ },
32
36
  "./cli": {
33
37
  "types": "./dist/cli/index.d.ts",
34
38
  "import": "./dist/cli/index.js"
@@ -92,5 +96,5 @@
92
96
  "@types/node": "^22.7.0",
93
97
  "esbuild": "^0.28.1"
94
98
  },
95
- "gitref": "5e87eb230755d35a3c37ebb1921442ab5e196245"
99
+ "gitref": "4338e0cd3aa57c2329696080bee010a1e80d2e87"
96
100
  }