@uipath/maestro-builder-sdk 6.41.1 → 6.42.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/cli/commands/registry.js +15 -0
- package/dist/core/cli-spelling.d.ts +9 -0
- package/dist/core/cli-spelling.js +9 -0
- package/dist/core/library-cache.d.ts +0 -9
- package/dist/core/library-cache.js +0 -9
- package/dist/core/library.d.ts +47 -0
- package/dist/core/library.js +135 -34
- package/dist/core/list-envelope.d.ts +36 -1
- package/dist/core/list-envelope.js +24 -19
- package/dist/core/lookup-store.d.ts +4 -3
- package/dist/core/lookup-store.js +46 -38
- package/dist/core/read-text.d.ts +26 -0
- package/dist/core/read-text.js +43 -0
- package/dist/merge-variables.d.ts +7 -0
- package/dist/merge-variables.js +28 -15
- package/dist/merge.d.ts +2 -1
- package/dist/merge.js +77 -7
- package/dist/registry/describe-cli.d.ts +10 -0
- package/dist/registry/describe-cli.js +41 -0
- package/dist/registry/library-reader.d.ts +12 -2
- package/dist/registry/library-reader.js +17 -16
- package/dist/registry/lookup-specs.d.ts +17 -1
- package/dist/registry/lookup-specs.js +3 -7
- package/dist/registry/path-cli.js +2 -1
- package/dist/registry/search-cli.js +2 -1
- package/package.json +2 -2
|
@@ -233,4 +233,19 @@ export function registerRegistryCommands(program) {
|
|
|
233
233
|
addValue(argv, '--limit', options.limit);
|
|
234
234
|
await invoke('registry search', () => import('../../registry/search-cli.js'), argv);
|
|
235
235
|
});
|
|
236
|
+
family.command('describe')
|
|
237
|
+
.description("Print one connector operation's page, from the prepared copy first, then the library.")
|
|
238
|
+
.argument('<node-type>', 'the nodeType that registry search returns')
|
|
239
|
+
.option('--connector-version <version>', 'the operation version (default: the newest)')
|
|
240
|
+
.option('--object <name>', 'the object of a generic operation, as registry search names it')
|
|
241
|
+
.option('--library <dir>', 'extracted library-json directory (default: the cached one)')
|
|
242
|
+
.option('--connectors-local <dir>', 'the prepared overlay (default: .flow-sdk/connectors-local)')
|
|
243
|
+
.action(async (nodeType, options) => {
|
|
244
|
+
const argv = [nodeType];
|
|
245
|
+
addValue(argv, '--connector-version', options.connectorVersion);
|
|
246
|
+
addValue(argv, '--object', options.object);
|
|
247
|
+
addValue(argv, '--library', options.library);
|
|
248
|
+
addValue(argv, '--connectors-local', options.connectorsLocal);
|
|
249
|
+
await invoke('registry describe', () => import('../../registry/describe-cli.js'), argv);
|
|
250
|
+
});
|
|
236
251
|
}
|
|
@@ -49,3 +49,12 @@ export declare function prepareCommand(connectorKey?: string, action?: string, .
|
|
|
49
49
|
* @returns `channel:profile.email=d@x.com` as is; `'users[*]:profile.real_name=Coder Evals'` quoted.
|
|
50
50
|
*/
|
|
51
51
|
export declare function resolveFlagArg(field: string, by: string, value: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* The command a message tells a person or agent to run when the cache is
|
|
54
|
+
* empty. It is `uip`, not `npx flow-sdk registry pull`: `uip maestro registry
|
|
55
|
+
* pull` runs this package's pull and fills the same cache, and it is the one
|
|
56
|
+
* command the authoring skill teaches. Each message used to spell the command
|
|
57
|
+
* out by hand, so they kept naming the SDK's own entry point after the skill
|
|
58
|
+
* moved to `uip`. Name it here once.
|
|
59
|
+
*/
|
|
60
|
+
export declare const PULL_COMMAND = "uip maestro registry pull";
|
|
@@ -88,3 +88,12 @@ export function resolveFlagArg(field, by, value) {
|
|
|
88
88
|
return arg;
|
|
89
89
|
return `'${arg.replaceAll("'", `'\\''`)}'`;
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* The command a message tells a person or agent to run when the cache is
|
|
93
|
+
* empty. It is `uip`, not `npx flow-sdk registry pull`: `uip maestro registry
|
|
94
|
+
* pull` runs this package's pull and fills the same cache, and it is the one
|
|
95
|
+
* command the authoring skill teaches. Each message used to spell the command
|
|
96
|
+
* out by hand, so they kept naming the SDK's own entry point after the skill
|
|
97
|
+
* moved to `uip`. Name it here once.
|
|
98
|
+
*/
|
|
99
|
+
export const PULL_COMMAND = 'uip maestro registry pull';
|
|
@@ -21,15 +21,6 @@
|
|
|
21
21
|
export declare const LIBRARY_JSON_DIR = "library-json";
|
|
22
22
|
export declare const LIBRARY_MARKDOWN_DIR = "library-md";
|
|
23
23
|
export declare const CURRENT_LINK = "current";
|
|
24
|
-
/**
|
|
25
|
-
* The command a message tells a person or agent to run when the cache is
|
|
26
|
-
* empty. It is `uip`, not `npx flow-sdk registry pull`: `uip maestro registry
|
|
27
|
-
* pull` runs this package's pull and fills the same cache, and it is the one
|
|
28
|
-
* command the authoring skill teaches. Each message used to spell the command
|
|
29
|
-
* out by hand, so they kept naming the SDK's own entry point after the skill
|
|
30
|
-
* moved to `uip`. Name it here once.
|
|
31
|
-
*/
|
|
32
|
-
export declare const PULL_COMMAND = "uip maestro registry pull";
|
|
33
24
|
/**
|
|
34
25
|
* Root of the library cache. `UIP_FLOW_SDK_LIBRARY_CACHE` overrides it.
|
|
35
26
|
*
|
|
@@ -24,15 +24,6 @@ import { join } from "node:path";
|
|
|
24
24
|
export const LIBRARY_JSON_DIR = "library-json";
|
|
25
25
|
export const LIBRARY_MARKDOWN_DIR = "library-md";
|
|
26
26
|
export const CURRENT_LINK = "current";
|
|
27
|
-
/**
|
|
28
|
-
* The command a message tells a person or agent to run when the cache is
|
|
29
|
-
* empty. It is `uip`, not `npx flow-sdk registry pull`: `uip maestro registry
|
|
30
|
-
* pull` runs this package's pull and fills the same cache, and it is the one
|
|
31
|
-
* command the authoring skill teaches. Each message used to spell the command
|
|
32
|
-
* out by hand, so they kept naming the SDK's own entry point after the skill
|
|
33
|
-
* moved to `uip`. Name it here once.
|
|
34
|
-
*/
|
|
35
|
-
export const PULL_COMMAND = "uip maestro registry pull";
|
|
36
27
|
/**
|
|
37
28
|
* Root of the library cache. `UIP_FLOW_SDK_LIBRARY_CACHE` overrides it.
|
|
38
29
|
*
|
package/dist/core/library.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* library — resolve Integration Service connector definitions from the on-disk
|
|
3
|
+
* connector library cache (the same `library-json` the Flow Code arm uses).
|
|
4
|
+
*
|
|
5
|
+
* For a connector action we need two files per action:
|
|
6
|
+
* <key>/<action>@<ver>.json the entry (connector key, HTTP method, endpoint)
|
|
7
|
+
* <key>/<action>@<ver>.v1def.json the Flow `definitions[]` shape (model, ports, …)
|
|
8
|
+
*/
|
|
9
|
+
import { type ReadText } from './read-text.js';
|
|
1
10
|
import { type LookupResolutions, type LookupSpec } from './lookups.js';
|
|
2
11
|
import type { ByoaEventFacts, ByoaLedger } from './byoa.js';
|
|
3
12
|
export interface ResolvedConnector {
|
|
@@ -217,8 +226,27 @@ export interface LibraryOpts {
|
|
|
217
226
|
* library exposed (a thin `describe` response must not shrink what compiles).
|
|
218
227
|
*/
|
|
219
228
|
overlayDir?: string;
|
|
229
|
+
/** Reads each library and overlay file. Defaults to the disk; a host without one passes its own. */
|
|
230
|
+
readText?: ReadText;
|
|
231
|
+
/** Receives the library's warnings. Defaults to stderr; a host without `process` passes its own. */
|
|
232
|
+
warn?: (message: string) => void;
|
|
220
233
|
}
|
|
221
234
|
export { ConnectorResolveError, type ConnectorResolveCode } from './connector-resolve-error.js';
|
|
235
|
+
/** What `registry describe` renders one operation's page from. */
|
|
236
|
+
export interface PageSource {
|
|
237
|
+
entry: any;
|
|
238
|
+
/** The Flow definition beside the entry, or `undefined` when there is none. */
|
|
239
|
+
v1def: any;
|
|
240
|
+
version: string;
|
|
241
|
+
/** True when the operation came from the prepared `connectors-local` overlay. */
|
|
242
|
+
prepared: boolean;
|
|
243
|
+
/** The folder the operation came from. */
|
|
244
|
+
dir: string;
|
|
245
|
+
/** That layer's index rows. The page names the operation's symbol from them. */
|
|
246
|
+
rows: readonly any[];
|
|
247
|
+
/** `objectName`s the ledgers declare connector-invariant. */
|
|
248
|
+
invariant: ReadonlySet<string>;
|
|
249
|
+
}
|
|
222
250
|
export declare class Library {
|
|
223
251
|
private layers;
|
|
224
252
|
/** The LIST-ENVELOPE LEDGER, overlay first so a prepared entry can add a row. */
|
|
@@ -239,6 +267,8 @@ export declare class Library {
|
|
|
239
267
|
readonly byoaVerifications: ByoaLedger;
|
|
240
268
|
/** Collections the ledger declares connector-invariant; overlay unions over baked. */
|
|
241
269
|
private readonly invariantCollections;
|
|
270
|
+
private readonly read;
|
|
271
|
+
private readonly warn;
|
|
242
272
|
constructor(dir: string, opts?: LibraryOpts);
|
|
243
273
|
/**
|
|
244
274
|
* Resolve a connector ACTION (`uipath.connector.<key>.<action>`).
|
|
@@ -267,6 +297,23 @@ export declare class Library {
|
|
|
267
297
|
*/
|
|
268
298
|
lookupSpecs(key: string, action: string, version?: string, objectName?: string): Record<string, LookupSpec>;
|
|
269
299
|
resolve(key: string, action: string, version?: string, objectName?: string): ResolvedConnector;
|
|
300
|
+
/**
|
|
301
|
+
* One operation's page source, for `registry describe`: the entry and definition
|
|
302
|
+
* of one index row, from the first layer that carries it. That is the prepared
|
|
303
|
+
* overlay before the library, the order `resolve` reads in. Without a version, the
|
|
304
|
+
* newest. A generic operation has a row per object, as `registry pull` writes a
|
|
305
|
+
* page per object: `objectName` picks the row; without it, the object-less row is
|
|
306
|
+
* used when there is one, else CONNECTOR_OBJECT_REQUIRED lists the objects. Rows are
|
|
307
|
+
* chosen as `resolveInLayer` chooses them, so a page shows only what `compile` accepts.
|
|
308
|
+
*/
|
|
309
|
+
pageSource(nodeType: string, version?: string, objectName?: string): PageSource;
|
|
310
|
+
/**
|
|
311
|
+
* The row an object the layer does not list reads its page from, as `resolveInLayer`
|
|
312
|
+
* case 1 accepts it: the one object-less row of an un-expanded generic operation.
|
|
313
|
+
* A single non-generic operation takes no object at all.
|
|
314
|
+
*/
|
|
315
|
+
private connectionScopedRow;
|
|
316
|
+
private pageOf;
|
|
270
317
|
/**
|
|
271
318
|
* Whether a connector step's schema is materialized locally — see
|
|
272
319
|
* {@link SchemaReadiness}. Index rows are enough for the `object` question
|
package/dist/core/library.js
CHANGED
|
@@ -6,10 +6,8 @@
|
|
|
6
6
|
* <key>/<action>@<ver>.json the entry (connector key, HTTP method, endpoint)
|
|
7
7
|
* <key>/<action>@<ver>.v1def.json the Flow `definitions[]` shape (model, ports, …)
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { prepareCommand } from './cli-spelling.js';
|
|
12
|
-
import { PULL_COMMAND } from './library-cache.js';
|
|
9
|
+
import { cacheFor, libraryPath, readJson, readTextFromDisk } from './read-text.js';
|
|
10
|
+
import { PULL_COMMAND, prepareCommand } from './cli-spelling.js';
|
|
13
11
|
import { editDistance } from './edit-distance.js';
|
|
14
12
|
import { loadListEnvelopes, lookupListEnvelope } from './list-envelope.js';
|
|
15
13
|
import { lookupSpecOf } from './lookups.js';
|
|
@@ -37,26 +35,35 @@ export class Library {
|
|
|
37
35
|
byoaVerifications;
|
|
38
36
|
/** Collections the ledger declares connector-invariant; overlay unions over baked. */
|
|
39
37
|
invariantCollections;
|
|
38
|
+
read;
|
|
39
|
+
warn;
|
|
40
40
|
constructor(dir, opts = {}) {
|
|
41
|
-
this.
|
|
42
|
-
this.
|
|
41
|
+
const read = (this.read = opts.readText ?? readTextFromDisk);
|
|
42
|
+
this.warn = opts.warn ?? ((message) => { globalThis.process?.stderr?.write(message); });
|
|
43
|
+
// A new identity per Library, so the per-reader caches never keep an overlay file: `prepare` rewrites them.
|
|
44
|
+
const readOverlay = (path) => read(path);
|
|
45
|
+
this.lookupResolutions = loadLookupResolutions(opts.overlayDir, readOverlay);
|
|
46
|
+
this.byoaVerifications = loadByoaLedger(opts.overlayDir, readOverlay);
|
|
43
47
|
this.invariantCollections = new Set([
|
|
44
|
-
...loadInvariantCollections(dir),
|
|
45
|
-
...loadInvariantCollections(opts.overlayDir),
|
|
48
|
+
...loadInvariantCollections(dir, read),
|
|
49
|
+
...loadInvariantCollections(opts.overlayDir, readOverlay),
|
|
46
50
|
]);
|
|
47
51
|
// Overlay first (local wins), then the baked library.
|
|
48
52
|
if (opts.overlayDir) {
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
this.layers.push({ dir: opts.overlayDir, entries: JSON.parse(
|
|
52
|
-
this.envelopeRows.push(...loadListEnvelopes(opts.overlayDir));
|
|
53
|
+
const overlayIndex = readOverlay(libraryPath(opts.overlayDir, 'index.json'));
|
|
54
|
+
if (overlayIndex !== undefined)
|
|
55
|
+
this.layers.push({ dir: opts.overlayDir, entries: JSON.parse(overlayIndex).entries ?? [], overlay: true });
|
|
56
|
+
this.envelopeRows.push(...loadListEnvelopes(opts.overlayDir, readOverlay));
|
|
53
57
|
}
|
|
54
|
-
this.envelopeRows.push(...loadListEnvelopes(dir));
|
|
55
|
-
const idx =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
this.envelopeRows.push(...loadListEnvelopes(dir, read));
|
|
59
|
+
const idx = libraryPath(dir, 'index.json');
|
|
60
|
+
const entries = libraryEntries(read, idx);
|
|
61
|
+
if (entries === undefined)
|
|
62
|
+
throw new Error(`connector library not found at ${idx}. `
|
|
63
|
+
+ (read === readTextFromDisk
|
|
64
|
+
? `Run \`${PULL_COMMAND}\`, or point --library / $FLOW_SDK_LIBRARY_JSON at a library-json dir.`
|
|
65
|
+
: 'The reader passed to Library has no such file.'));
|
|
66
|
+
this.layers.push({ dir, entries });
|
|
60
67
|
}
|
|
61
68
|
/**
|
|
62
69
|
* Resolve a connector ACTION (`uipath.connector.<key>.<action>`).
|
|
@@ -102,6 +109,83 @@ export class Library {
|
|
|
102
109
|
resolve(key, action, version, objectName) {
|
|
103
110
|
return this.resolveType(`uipath.connector.${key}.${action}`, version, objectName);
|
|
104
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* One operation's page source, for `registry describe`: the entry and definition
|
|
114
|
+
* of one index row, from the first layer that carries it. That is the prepared
|
|
115
|
+
* overlay before the library, the order `resolve` reads in. Without a version, the
|
|
116
|
+
* newest. A generic operation has a row per object, as `registry pull` writes a
|
|
117
|
+
* page per object: `objectName` picks the row; without it, the object-less row is
|
|
118
|
+
* used when there is one, else CONNECTOR_OBJECT_REQUIRED lists the objects. Rows are
|
|
119
|
+
* chosen as `resolveInLayer` chooses them, so a page shows only what `compile` accepts.
|
|
120
|
+
*/
|
|
121
|
+
pageSource(nodeType, version, objectName) {
|
|
122
|
+
const layers = this.layers.flatMap((layer) => {
|
|
123
|
+
const matches = layer.entries.filter((e) => e?.nodeType === nodeType && (!version || String(e.version) === version)
|
|
124
|
+
&& typeof e.path === 'string' && e.path.endsWith('.json'));
|
|
125
|
+
if (!matches.length)
|
|
126
|
+
return [];
|
|
127
|
+
const newest = matches.map((e) => String(e.version))
|
|
128
|
+
.reduce((a, b) => (b.localeCompare(a, undefined, { numeric: true }) > 0 ? b : a));
|
|
129
|
+
return [{ layer, rows: matches.filter((e) => String(e.version) === newest) }];
|
|
130
|
+
});
|
|
131
|
+
if (!layers.length) {
|
|
132
|
+
throw new ConnectorResolveError('CONNECTOR_NOT_IN_LIBRARY', `connector not in library: ${nodeType}${version ? '@' + version : ''}.`
|
|
133
|
+
+ this.describeMiss(nodeType, version, undefined, false));
|
|
134
|
+
}
|
|
135
|
+
const objects = [...new Set(layers.flatMap(({ rows }) => rows.map((e) => e.objectName)
|
|
136
|
+
.filter((o) => typeof o === 'string' && o !== '')))].sort();
|
|
137
|
+
// `compile` refuses a missing object when any layer holds several rows with no one template row.
|
|
138
|
+
if (objectName === undefined && layers.some(({ rows }) => rows.length > 1 && rows.filter((e) => !e.objectName).length !== 1)) {
|
|
139
|
+
throw new ConnectorResolveError('CONNECTOR_OBJECT_REQUIRED', `connector ${nodeType} is a GENERIC operation covering ${objects.length} objects — `
|
|
140
|
+
+ `say which one with --object, e.g. --object ${objects[0] ?? '<object>'}. ${describeObjects(objects)}`);
|
|
141
|
+
}
|
|
142
|
+
for (const { layer, rows } of layers) {
|
|
143
|
+
const row = objectName === undefined
|
|
144
|
+
? (rows.length === 1 ? rows[0] : rows.find((e) => !e.objectName))
|
|
145
|
+
: rows.find((e) => e.objectName === objectName) ?? this.connectionScopedRow(nodeType, rows, objectName);
|
|
146
|
+
if (row)
|
|
147
|
+
return this.pageOf(nodeType, layer, row);
|
|
148
|
+
}
|
|
149
|
+
const parts = nodeType.split('.');
|
|
150
|
+
const eventNs = parts[2] === 'trigger' || parts[2] === 'event';
|
|
151
|
+
const near = nearest(objectName ?? '', objects);
|
|
152
|
+
throw new ConnectorResolveError('CONNECTOR_NOT_IN_LIBRARY', `connector ${nodeType} has no page for object "${objectName}"`
|
|
153
|
+
+ `${near ? ` (nearest: "${near}")` : ''}. ${describeObjects(objects)} If it is a connection-scoped object, run `
|
|
154
|
+
+ `\`${prepareCommand(eventNs ? parts[3] : parts[2], (eventNs ? parts.slice(4) : parts.slice(3)).join('.'), '--object', String(objectName))}\` `
|
|
155
|
+
+ 'to prepare it, then describe again.');
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The row an object the layer does not list reads its page from, as `resolveInLayer`
|
|
159
|
+
* case 1 accepts it: the one object-less row of an un-expanded generic operation.
|
|
160
|
+
* A single non-generic operation takes no object at all.
|
|
161
|
+
*/
|
|
162
|
+
connectionScopedRow(nodeType, rows, objectName) {
|
|
163
|
+
if (rows.length !== 1)
|
|
164
|
+
return undefined;
|
|
165
|
+
const only = rows[0];
|
|
166
|
+
if (only.activityType === 'Generic' || only.activityType === 'GenericTrigger' || only.objectFromDiscovery === true) {
|
|
167
|
+
return only.objectName ? undefined : only;
|
|
168
|
+
}
|
|
169
|
+
throw new ConnectorResolveError('CONNECTOR_OBJECT_NOT_GENERIC', `connector ${nodeType} does not have an object "${objectName}": it is a single, `
|
|
170
|
+
+ `non-generic operation addressing ${only.objectName ? `"${only.objectName}"` : 'no object'}. `
|
|
171
|
+
+ 'Drop --object (it is only for generic operations, whose node type covers many objects).');
|
|
172
|
+
}
|
|
173
|
+
pageOf(nodeType, layer, row) {
|
|
174
|
+
const entryPath = libraryPath(layer.dir, row.path);
|
|
175
|
+
const entry = readLibraryFile(nodeType, entryPath, () => readJson(this.read, entryPath));
|
|
176
|
+
const sidecarPath = v1defPathOf(entryPath);
|
|
177
|
+
const sidecar = this.read(sidecarPath);
|
|
178
|
+
const raw = sidecar === undefined ? undefined : readLibraryFile(nodeType, sidecarPath, () => JSON.parse(sidecar));
|
|
179
|
+
return {
|
|
180
|
+
entry,
|
|
181
|
+
v1def: Array.isArray(raw) ? raw[0] : raw,
|
|
182
|
+
version: String(row.version),
|
|
183
|
+
prepared: layer.overlay === true,
|
|
184
|
+
dir: layer.dir,
|
|
185
|
+
rows: layer.entries,
|
|
186
|
+
invariant: this.invariantCollections,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
105
189
|
/**
|
|
106
190
|
* Whether a connector step's schema is materialized locally — see
|
|
107
191
|
* {@link SchemaReadiness}. Index rows are enough for the `object` question
|
|
@@ -158,7 +242,7 @@ export class Library {
|
|
|
158
242
|
let entry;
|
|
159
243
|
try {
|
|
160
244
|
const source = rows.find(({ overlay }) => !overlay) ?? rows[0];
|
|
161
|
-
entry =
|
|
245
|
+
entry = readJson(this.read, libraryPath(source.dir, source.row.path));
|
|
162
246
|
}
|
|
163
247
|
catch {
|
|
164
248
|
return undefined;
|
|
@@ -191,7 +275,7 @@ export class Library {
|
|
|
191
275
|
continue;
|
|
192
276
|
if (wire.version && indexed.version !== wire.version)
|
|
193
277
|
continue;
|
|
194
|
-
const entry =
|
|
278
|
+
const entry = readJson(this.read, libraryPath(layer.dir, indexed.path));
|
|
195
279
|
const operation = entry.operation ?? {};
|
|
196
280
|
if (wire.httpMethod && String(operation.httpMethod ?? '').toUpperCase() !== wire.httpMethod.toUpperCase())
|
|
197
281
|
continue;
|
|
@@ -289,8 +373,9 @@ export class Library {
|
|
|
289
373
|
continue;
|
|
290
374
|
if (wire.version && indexed.version !== wire.version)
|
|
291
375
|
continue;
|
|
292
|
-
const
|
|
293
|
-
const
|
|
376
|
+
const entryPath = libraryPath(layer.dir, indexed.path);
|
|
377
|
+
const entry = readJson(this.read, entryPath);
|
|
378
|
+
const operation = normalizeEventOperation({ entry, v1def: readV1def(this.read, entryPath) }).entry.operation ?? {};
|
|
294
379
|
// A GENERIC event's entry names no object — the wire's object is the
|
|
295
380
|
// author's choice, so it identifies nothing here and comes back as `object`.
|
|
296
381
|
const generic = operation.activityType === 'GenericTrigger';
|
|
@@ -600,7 +685,7 @@ export class Library {
|
|
|
600
685
|
* `connector not in library: …send-mail-v2.` said nothing that would reveal
|
|
601
686
|
* that. It resorted to grepping the library's JSON out of `node_modules`.
|
|
602
687
|
*/
|
|
603
|
-
describeMiss(nodeType, version, objectName) {
|
|
688
|
+
describeMiss(nodeType, version, objectName, searched = true) {
|
|
604
689
|
// `uipath.connector.<key>.<op>`, or `uipath.connector.(trigger|event).<key>.<ev>`
|
|
605
690
|
// for the two event namespaces. No connector key contains a dot.
|
|
606
691
|
const parts = nodeType.split('.');
|
|
@@ -609,7 +694,8 @@ export class Library {
|
|
|
609
694
|
const asked = (eventNs ? parts.slice(4) : parts.slice(3)).join('.');
|
|
610
695
|
if (!key)
|
|
611
696
|
return '';
|
|
612
|
-
|
|
697
|
+
// `describe` names no folder: on a browser host it is a store the agent cannot open.
|
|
698
|
+
const where = searched ? ` Searched: ${this.layers.map((l) => l.dir).join(', ')}.` : '';
|
|
613
699
|
if (eventNs)
|
|
614
700
|
return this.describeEventMiss(parts[2], key, asked, where, version, objectName);
|
|
615
701
|
const ops = this.opsUnder(`uipath.connector.${key}.`);
|
|
@@ -753,7 +839,7 @@ export class Library {
|
|
|
753
839
|
return this.build(layer.dir, { ...only, objectName }, nodeType, { objectNameUnverified: true });
|
|
754
840
|
}
|
|
755
841
|
if (only.activityType === 'Generic' || only.objectFromDiscovery === true) {
|
|
756
|
-
|
|
842
|
+
this.warn(` warn: connector ${nodeType}: the library carries this operation un-expanded `
|
|
757
843
|
+ `(one object-less entry). Object "${objectName}" is accepted as a connection-scoped `
|
|
758
844
|
+ `object — the schema comes from the generic entry and the tenant resolves the name at `
|
|
759
845
|
+ `dispatch. If it is a typo, compile will not catch it.\n`);
|
|
@@ -784,7 +870,7 @@ export class Library {
|
|
|
784
870
|
// sibling whose path template is exactly `/<its object>` — the only shape a
|
|
785
871
|
// substitution can re-point cleanly.
|
|
786
872
|
const near = nearest(objectName, objects);
|
|
787
|
-
|
|
873
|
+
this.warn(` warn: connector ${nodeType}: object "${objectName}" is not one of the `
|
|
788
874
|
+ `${objects.length} the library lists for it${near ? ` (nearest: "${near}")` : ''}. `
|
|
789
875
|
+ `Accepted as a connection-scoped object — the schema comes from a sibling entry and the `
|
|
790
876
|
+ `tenant resolves the name at dispatch. If it is a typo, compile will not catch it.\n`);
|
|
@@ -800,7 +886,7 @@ export class Library {
|
|
|
800
886
|
pickDerivationSibling(dir, matches) {
|
|
801
887
|
const templateOf = (e) => {
|
|
802
888
|
try {
|
|
803
|
-
const entry =
|
|
889
|
+
const entry = readJson(this.read, libraryPath(dir, e.path));
|
|
804
890
|
return entry.operation?.pathTemplate ?? entry.operation?.path ?? '';
|
|
805
891
|
}
|
|
806
892
|
catch {
|
|
@@ -814,14 +900,14 @@ export class Library {
|
|
|
814
900
|
return substitutable ?? matches[0];
|
|
815
901
|
}
|
|
816
902
|
build(dir, e, nodeType, opts = {}) {
|
|
817
|
-
const entryPath =
|
|
903
|
+
const entryPath = libraryPath(dir, e.path);
|
|
818
904
|
// The two files the index row promises. A missing or corrupt one is a fault in
|
|
819
905
|
// the library on disk, not in the author's source, but compile cannot go on
|
|
820
906
|
// without it, so it is a typed refusal like the others: `check` reports it with
|
|
821
907
|
// the same code and text instead of passing a step compile then dies on with a
|
|
822
908
|
// bare ENOENT (#1015).
|
|
823
|
-
const entry = readLibraryFile(nodeType, entryPath, () =>
|
|
824
|
-
const v1def = readLibraryFile(nodeType, v1defPathOf(entryPath), () => readV1def(entryPath));
|
|
909
|
+
const entry = readLibraryFile(nodeType, entryPath, () => readJson(this.read, entryPath));
|
|
910
|
+
const v1def = readLibraryFile(nodeType, v1defPathOf(entryPath), () => readV1def(this.read, entryPath));
|
|
825
911
|
// A stamped object (case 4) reaches here with the index entry's `objectName`
|
|
826
912
|
// replaced but the sibling's FILE on disk — so restate the object and derive
|
|
827
913
|
// the endpoint from the sibling's own template, rather than shipping a node
|
|
@@ -846,7 +932,7 @@ export class Library {
|
|
|
846
932
|
&& entry.operation.objectName !== e.objectName) {
|
|
847
933
|
const stale = entry.operation.objectName;
|
|
848
934
|
const derived = deriveObjectPath(entry.operation, e.objectName);
|
|
849
|
-
|
|
935
|
+
this.warn(` warn: connector ${nodeType}: the library index says objectName "${e.objectName}" but its `
|
|
850
936
|
+ `definition file says "${stale}". Using the index and deriving the endpoint`
|
|
851
937
|
+ (derived.path ? ` (${entry.operation.path} -> ${derived.path})` : '')
|
|
852
938
|
+ `; the definition is the stale side when these disagree.\n`);
|
|
@@ -950,13 +1036,28 @@ function v1defPathOf(entryPath) {
|
|
|
950
1036
|
* damaged after it was built: an interrupted pull, a hand-edited overlay. The
|
|
951
1037
|
* remedy is the same either way: rebuild the layer the file belongs to.
|
|
952
1038
|
*/
|
|
1039
|
+
/** Each host reader's parsed library index. A library build does not change; the disk default still reads afresh. */
|
|
1040
|
+
const INDEX_CACHE = new WeakMap();
|
|
1041
|
+
/** The `entries` of the index at `path`, or `undefined` when there is none. */
|
|
1042
|
+
function libraryEntries(read, path) {
|
|
1043
|
+
const cache = read === readTextFromDisk ? undefined : cacheFor(INDEX_CACHE, read);
|
|
1044
|
+
const cached = cache?.get(path);
|
|
1045
|
+
if (cached)
|
|
1046
|
+
return cached;
|
|
1047
|
+
const text = read(path);
|
|
1048
|
+
if (text === undefined)
|
|
1049
|
+
return undefined;
|
|
1050
|
+
const entries = JSON.parse(text).entries ?? [];
|
|
1051
|
+
cache?.set(path, entries);
|
|
1052
|
+
return entries;
|
|
1053
|
+
}
|
|
953
1054
|
function readLibraryFile(nodeType, path, read) {
|
|
954
1055
|
try {
|
|
955
1056
|
return read();
|
|
956
1057
|
}
|
|
957
1058
|
catch (error) {
|
|
958
1059
|
const message = error instanceof Error ? error.message : String(error);
|
|
959
|
-
//
|
|
1060
|
+
// A read error already names the path; a JSON SyntaxError does not.
|
|
960
1061
|
const reason = message.includes(path) ? message : `${path}: ${message}`;
|
|
961
1062
|
throw new ConnectorResolveError('CONNECTOR_LIBRARY_ENTRY_UNREADABLE', `connector library entry for ${nodeType} is unreadable: ${reason}. The library index lists `
|
|
962
1063
|
+ `this operation, but the file is missing or not valid JSON, so the library on disk is incomplete. `
|
|
@@ -980,8 +1081,8 @@ function readLibraryFile(nodeType, path, read) {
|
|
|
980
1081
|
* reported both its connection and `FolderKey` as "not configured" (UV-16774) while
|
|
981
1082
|
* validate and debug — which fold the case — passed.
|
|
982
1083
|
*/
|
|
983
|
-
function readV1def(entryPath) {
|
|
984
|
-
const raw =
|
|
1084
|
+
function readV1def(read, entryPath) {
|
|
1085
|
+
const raw = readJson(read, v1defPathOf(entryPath));
|
|
985
1086
|
const v1def = Array.isArray(raw) ? raw[0] : raw;
|
|
986
1087
|
const bindings = v1def?.model?.bindings;
|
|
987
1088
|
if (typeof bindings?.resource === 'string' && bindings.resource.toLowerCase() === 'connection') {
|
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* list-envelope — read the LIST-ENVELOPE LEDGER that sits beside the connector
|
|
3
|
+
* library's `index.json`.
|
|
4
|
+
*
|
|
5
|
+
* ## What the ledger answers
|
|
6
|
+
*
|
|
7
|
+
* A connector LIST operation's node output is either the array itself (**bare**,
|
|
8
|
+
* `envelope: null`) or that array inside a one-field wrapper object
|
|
9
|
+
* (`envelope: "records"` ⇒ `{records: […]}`). **Nothing in the library declares
|
|
10
|
+
* which**: `outputSchema.fields` describes ONE record either way, and the two
|
|
11
|
+
* operations that declare no fields at all sit on opposite sides of what the
|
|
12
|
+
* corpus guessed ([#156](https://github.com/UiPath/flow-builder-sdk/issues/156)).
|
|
13
|
+
* It cannot be derived, so it is measured and written down — one row per
|
|
14
|
+
* operation, each carrying the evidence that put it there.
|
|
15
|
+
*
|
|
16
|
+
* ## The rule this file exists to enforce
|
|
17
|
+
*
|
|
18
|
+
* **An operation with no row is UNDECLARED, and undeclared means every reader
|
|
19
|
+
* behaves exactly as it did before the ledger existed.** The emitter declares no
|
|
20
|
+
* container, and a reader must go on treating an unknown name on a list output as
|
|
21
|
+
* a possible wrapper. 2,149 of the shipped library's 7,029 entries are List
|
|
22
|
+
* operations and the ledger holds eight — that ratio is the design, not a gap.
|
|
23
|
+
* It grows on demand, one measurement at a time.
|
|
24
|
+
*
|
|
25
|
+
* Measured 2026-08-02 (card G13, eight `flow debug` / product-runtime readings):
|
|
26
|
+
* every operation measured so far is **bare**, including both the corpus called
|
|
27
|
+
* wrappers. The ledger's own `$comment` carries the mechanism and the evidence
|
|
28
|
+
* tiers; this module only reads it.
|
|
29
|
+
*
|
|
30
|
+
* If a second reader is ever needed outside this package, copy this one rather
|
|
31
|
+
* than sharing it through a new package boundary: a 40-line JSON reader is the
|
|
32
|
+
* cheaper half. The LEDGER is the single source of truth — the readers are
|
|
33
|
+
* interchangeable.
|
|
34
|
+
*/
|
|
35
|
+
import { type ReadText } from './read-text.js';
|
|
1
36
|
/** One measured operation. `envelope: null` is the BARE array. */
|
|
2
37
|
export interface ListEnvelopeRow {
|
|
3
38
|
nodeType: string;
|
|
@@ -21,7 +56,7 @@ export interface ListEnvelopeRow {
|
|
|
21
56
|
* an empty ledger — every operation undeclared — which is what a library built
|
|
22
57
|
* before this card, or a bare fixture library, looks like.
|
|
23
58
|
*/
|
|
24
|
-
export declare function loadListEnvelopes(libraryDir: string | undefined): ListEnvelopeRow[];
|
|
59
|
+
export declare function loadListEnvelopes(libraryDir: string | undefined, read?: ReadText): ListEnvelopeRow[];
|
|
25
60
|
/**
|
|
26
61
|
* The row for one operation, or `undefined` when it is undeclared.
|
|
27
62
|
*
|
|
@@ -32,36 +32,41 @@
|
|
|
32
32
|
* cheaper half. The LEDGER is the single source of truth — the readers are
|
|
33
33
|
* interchangeable.
|
|
34
34
|
*/
|
|
35
|
-
import {
|
|
36
|
-
|
|
37
|
-
const CACHE = new Map();
|
|
35
|
+
import { cacheFor, libraryPath, readTextFromDisk } from './read-text.js';
|
|
36
|
+
const CACHE = new WeakMap();
|
|
38
37
|
/**
|
|
39
38
|
* Every declared row, from `<libraryDir>/list-envelopes.json`. An absent file is
|
|
40
39
|
* an empty ledger — every operation undeclared — which is what a library built
|
|
41
40
|
* before this card, or a bare fixture library, looks like.
|
|
42
41
|
*/
|
|
43
|
-
export function loadListEnvelopes(libraryDir) {
|
|
42
|
+
export function loadListEnvelopes(libraryDir, read = readTextFromDisk) {
|
|
44
43
|
if (!libraryDir)
|
|
45
44
|
return [];
|
|
46
|
-
const
|
|
45
|
+
const cache = cacheFor(CACHE, read);
|
|
46
|
+
const cached = cache.get(libraryDir);
|
|
47
47
|
if (cached)
|
|
48
48
|
return cached;
|
|
49
|
+
let text;
|
|
50
|
+
try {
|
|
51
|
+
text = read(libraryPath(libraryDir, 'list-envelopes.json'));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Not cached: the reader may serve the file on a later call.
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
49
57
|
let rows = [];
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// edit is losing the exactness, never breaking a compile.
|
|
61
|
-
rows = [];
|
|
62
|
-
}
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(text ?? '{}');
|
|
60
|
+
if (Array.isArray(parsed.operations))
|
|
61
|
+
rows = parsed.operations;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// A malformed ledger reads as an EMPTY one rather than throwing: the
|
|
65
|
+
// permissive path is the pre-ledger behaviour, so the worst case of a bad
|
|
66
|
+
// edit is losing the exactness, never breaking a compile.
|
|
67
|
+
rows = [];
|
|
63
68
|
}
|
|
64
|
-
|
|
69
|
+
cache.set(libraryDir, rows);
|
|
65
70
|
return rows;
|
|
66
71
|
}
|
|
67
72
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReadText } from './read-text.js';
|
|
1
2
|
import type { ByoaLedger } from './byoa.js';
|
|
2
3
|
import type { LookupResolutions } from './lookups.js';
|
|
3
4
|
/**
|
|
@@ -8,7 +9,7 @@ import type { LookupResolutions } from './lookups.js';
|
|
|
8
9
|
* — it is a compile error naming the `prepare` command to run, which is the
|
|
9
10
|
* behaviour an author wants from a missing resolution anyway.
|
|
10
11
|
*/
|
|
11
|
-
export declare function loadLookupResolutions(overlayDir: string | undefined): LookupResolutions;
|
|
12
|
+
export declare function loadLookupResolutions(overlayDir: string | undefined, read?: ReadText): LookupResolutions;
|
|
12
13
|
/**
|
|
13
14
|
* Load the `byoa` section of `<overlayDir>/resolutions.json` — the BYOA checks
|
|
14
15
|
* `registry prepare` recorded (see `core/byoa.ts`) — or an empty map.
|
|
@@ -19,7 +20,7 @@ export declare function loadLookupResolutions(overlayDir: string | undefined): L
|
|
|
19
20
|
* call rather than cached: `prepare` rewrites this file, and a stale "never
|
|
20
21
|
* checked" answer would refuse a flow that was just verified.
|
|
21
22
|
*/
|
|
22
|
-
export declare function loadByoaLedger(overlayDir: string | undefined): ByoaLedger;
|
|
23
|
+
export declare function loadByoaLedger(overlayDir: string | undefined, read?: ReadText): ByoaLedger;
|
|
23
24
|
/**
|
|
24
25
|
* The invariant-collection ledger's `objectName` set, from a library dir.
|
|
25
26
|
*
|
|
@@ -31,4 +32,4 @@ export declare function loadByoaLedger(overlayDir: string | undefined): ByoaLedg
|
|
|
31
32
|
* @param libraryDir - A `library-json` directory, or the local overlay.
|
|
32
33
|
* @returns Every `objectName` declared connector-invariant.
|
|
33
34
|
*/
|
|
34
|
-
export declare function loadInvariantCollections(libraryDir: string | undefined): Set<string>;
|
|
35
|
+
export declare function loadInvariantCollections(libraryDir: string | undefined, read?: ReadText): Set<string>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from 'node:path';
|
|
1
|
+
import { cacheFor, libraryPath, readTextFromDisk } from './read-text.js';
|
|
3
2
|
/**
|
|
4
3
|
* core/lookup-store — the two on-disk side-tables the lookup surface reads.
|
|
5
4
|
*
|
|
@@ -14,7 +13,7 @@ import { join } from 'node:path';
|
|
|
14
13
|
* the CLIs need to read files. Keeping the split makes that structural rather
|
|
15
14
|
* than a thing to remember.
|
|
16
15
|
*/
|
|
17
|
-
const RESOLUTION_CACHE = new
|
|
16
|
+
const RESOLUTION_CACHE = new WeakMap();
|
|
18
17
|
/**
|
|
19
18
|
* Load `<overlayDir>/resolutions.json`, or an empty map.
|
|
20
19
|
*
|
|
@@ -23,26 +22,32 @@ const RESOLUTION_CACHE = new Map();
|
|
|
23
22
|
* — it is a compile error naming the `prepare` command to run, which is the
|
|
24
23
|
* behaviour an author wants from a missing resolution anyway.
|
|
25
24
|
*/
|
|
26
|
-
export function loadLookupResolutions(overlayDir) {
|
|
25
|
+
export function loadLookupResolutions(overlayDir, read = readTextFromDisk) {
|
|
27
26
|
if (!overlayDir)
|
|
28
27
|
return {};
|
|
29
|
-
const
|
|
28
|
+
const cache = cacheFor(RESOLUTION_CACHE, read);
|
|
29
|
+
const cached = cache.get(overlayDir);
|
|
30
30
|
if (cached)
|
|
31
31
|
return cached;
|
|
32
|
+
let text;
|
|
33
|
+
try {
|
|
34
|
+
text = read(libraryPath(overlayDir, 'resolutions.json'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Not cached: the reader may serve the file on a later call.
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
32
40
|
let rows = {};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (parsed.resolutions && typeof parsed.resolutions === 'object') {
|
|
38
|
-
rows = parsed.resolutions;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
rows = {};
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(text ?? '{}');
|
|
43
|
+
if (parsed.resolutions && typeof parsed.resolutions === 'object') {
|
|
44
|
+
rows = parsed.resolutions;
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
|
-
|
|
47
|
+
catch {
|
|
48
|
+
rows = {};
|
|
49
|
+
}
|
|
50
|
+
cache.set(overlayDir, rows);
|
|
46
51
|
return rows;
|
|
47
52
|
}
|
|
48
53
|
/**
|
|
@@ -55,14 +60,11 @@ export function loadLookupResolutions(overlayDir) {
|
|
|
55
60
|
* call rather than cached: `prepare` rewrites this file, and a stale "never
|
|
56
61
|
* checked" answer would refuse a flow that was just verified.
|
|
57
62
|
*/
|
|
58
|
-
export function loadByoaLedger(overlayDir) {
|
|
63
|
+
export function loadByoaLedger(overlayDir, read = readTextFromDisk) {
|
|
59
64
|
if (!overlayDir)
|
|
60
65
|
return {};
|
|
61
|
-
const file = join(overlayDir, 'resolutions.json');
|
|
62
|
-
if (!existsSync(file))
|
|
63
|
-
return {};
|
|
64
66
|
try {
|
|
65
|
-
const parsed = JSON.parse(
|
|
67
|
+
const parsed = JSON.parse(read(libraryPath(overlayDir, 'resolutions.json')) ?? '{}');
|
|
66
68
|
const byoa = parsed?.byoa;
|
|
67
69
|
return byoa && typeof byoa === 'object' && !Array.isArray(byoa) ? byoa : {};
|
|
68
70
|
}
|
|
@@ -81,30 +83,36 @@ export function loadByoaLedger(overlayDir) {
|
|
|
81
83
|
* @param libraryDir - A `library-json` directory, or the local overlay.
|
|
82
84
|
* @returns Every `objectName` declared connector-invariant.
|
|
83
85
|
*/
|
|
84
|
-
export function loadInvariantCollections(libraryDir) {
|
|
86
|
+
export function loadInvariantCollections(libraryDir, read = readTextFromDisk) {
|
|
85
87
|
if (!libraryDir)
|
|
86
88
|
return new Set();
|
|
87
|
-
const
|
|
89
|
+
const cache = cacheFor(INVARIANT_CACHE, read);
|
|
90
|
+
const cached = cache.get(libraryDir);
|
|
88
91
|
if (cached)
|
|
89
92
|
return cached;
|
|
93
|
+
let text;
|
|
94
|
+
try {
|
|
95
|
+
text = read(libraryPath(libraryDir, 'invariant-collections.json'));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Not cached: the reader may serve the file on a later call.
|
|
99
|
+
return new Set();
|
|
100
|
+
}
|
|
90
101
|
const out = new Set();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (typeof name === 'string')
|
|
99
|
-
out.add(name);
|
|
100
|
-
}
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(text ?? '{}');
|
|
104
|
+
if (Array.isArray(parsed.collections)) {
|
|
105
|
+
for (const row of parsed.collections) {
|
|
106
|
+
const name = row?.objectName;
|
|
107
|
+
if (typeof name === 'string')
|
|
108
|
+
out.add(name);
|
|
101
109
|
}
|
|
102
110
|
}
|
|
103
|
-
catch {
|
|
104
|
-
/* an empty ledger — see above */
|
|
105
|
-
}
|
|
106
111
|
}
|
|
107
|
-
|
|
112
|
+
catch {
|
|
113
|
+
/* an empty ledger — see above */
|
|
114
|
+
}
|
|
115
|
+
cache.set(libraryDir, out);
|
|
108
116
|
return out;
|
|
109
117
|
}
|
|
110
|
-
const INVARIANT_CACHE = new
|
|
118
|
+
const INVARIANT_CACHE = new WeakMap();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/read-text — how the connector library reads its files.
|
|
3
|
+
*
|
|
4
|
+
* `Library` and `loadLibrary` read every library and overlay file through a
|
|
5
|
+
* `ReadText`, so a host without a disk can serve them: Studio Web keeps the library
|
|
6
|
+
* zip in memory and answers each read from it. A read is synchronous because QuickJS
|
|
7
|
+
* cannot wait on one while it compiles.
|
|
8
|
+
*
|
|
9
|
+
* `Library` keeps what a host reader returns for library files (the index and the
|
|
10
|
+
* ledgers) for as long as the reader lives, because a library build does not change:
|
|
11
|
+
* pass a new reader for a new build. Overlay files are read afresh each time, because
|
|
12
|
+
* `prepare` rewrites them.
|
|
13
|
+
*
|
|
14
|
+
* The default reads the disk. `node:fs` comes from `process.getBuiltinModule`, not an
|
|
15
|
+
* `import`, so the library loads no `node:` builtin (FB015 exempts this fallback), and
|
|
16
|
+
* `process` is read through `globalThis`, as in `core/typescript-module.ts`.
|
|
17
|
+
*/
|
|
18
|
+
/** The file's text, or `undefined` when there is none. */
|
|
19
|
+
export type ReadText = (path: string) => string | undefined;
|
|
20
|
+
export declare function readTextFromDisk(path: string): string | undefined;
|
|
21
|
+
/** `dir/rel`. Index paths are relative and `/`-separated. */
|
|
22
|
+
export declare function libraryPath(dir: string, rel: string): string;
|
|
23
|
+
/** The parsed file. A missing one throws an error that names the path. */
|
|
24
|
+
export declare function readJson(read: ReadText, path: string): unknown;
|
|
25
|
+
/** One reader's cache. Two readers can serve different files at the same path. */
|
|
26
|
+
export declare function cacheFor<T>(caches: WeakMap<ReadText, Map<string, T>>, read: ReadText): Map<string, T>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/read-text — how the connector library reads its files.
|
|
3
|
+
*
|
|
4
|
+
* `Library` and `loadLibrary` read every library and overlay file through a
|
|
5
|
+
* `ReadText`, so a host without a disk can serve them: Studio Web keeps the library
|
|
6
|
+
* zip in memory and answers each read from it. A read is synchronous because QuickJS
|
|
7
|
+
* cannot wait on one while it compiles.
|
|
8
|
+
*
|
|
9
|
+
* `Library` keeps what a host reader returns for library files (the index and the
|
|
10
|
+
* ledgers) for as long as the reader lives, because a library build does not change:
|
|
11
|
+
* pass a new reader for a new build. Overlay files are read afresh each time, because
|
|
12
|
+
* `prepare` rewrites them.
|
|
13
|
+
*
|
|
14
|
+
* The default reads the disk. `node:fs` comes from `process.getBuiltinModule`, not an
|
|
15
|
+
* `import`, so the library loads no `node:` builtin (FB015 exempts this fallback), and
|
|
16
|
+
* `process` is read through `globalThis`, as in `core/typescript-module.ts`.
|
|
17
|
+
*/
|
|
18
|
+
export function readTextFromDisk(path) {
|
|
19
|
+
const getBuiltinModule = globalThis.process?.getBuiltinModule;
|
|
20
|
+
if (getBuiltinModule === undefined) {
|
|
21
|
+
throw new Error(`cannot read ${path}: outside Node, pass a readText function to the connector library`);
|
|
22
|
+
}
|
|
23
|
+
const fs = getBuiltinModule('node:fs');
|
|
24
|
+
return fs.existsSync(path) ? fs.readFileSync(path, 'utf8') : undefined;
|
|
25
|
+
}
|
|
26
|
+
/** `dir/rel`. Index paths are relative and `/`-separated. */
|
|
27
|
+
export function libraryPath(dir, rel) {
|
|
28
|
+
return dir.endsWith('/') ? `${dir}${rel}` : `${dir}/${rel}`;
|
|
29
|
+
}
|
|
30
|
+
/** The parsed file. A missing one throws an error that names the path. */
|
|
31
|
+
export function readJson(read, path) {
|
|
32
|
+
const text = read(path);
|
|
33
|
+
if (text === undefined)
|
|
34
|
+
throw new Error(`no such file: ${path}`);
|
|
35
|
+
return JSON.parse(text);
|
|
36
|
+
}
|
|
37
|
+
/** One reader's cache. Two readers can serve different files at the same path. */
|
|
38
|
+
export function cacheFor(caches, read) {
|
|
39
|
+
let cache = caches.get(read);
|
|
40
|
+
if (cache === undefined)
|
|
41
|
+
caches.set(read, (cache = new Map()));
|
|
42
|
+
return cache;
|
|
43
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
type Json = unknown;
|
|
2
|
+
type Obj = Record<string, Json>;
|
|
2
3
|
/** JSON with object keys sorted, so key order is not a difference. */
|
|
3
4
|
export declare function stableJson(v: Json): string;
|
|
4
5
|
/**
|
|
@@ -16,4 +17,10 @@ export declare function isDeltaSpelling(variables: Json): boolean;
|
|
|
16
17
|
export declare function mergeVariables(original: Json, edited: Json, baseline: {
|
|
17
18
|
variables?: Json;
|
|
18
19
|
} | undefined, goneNodeIds: Set<string>): Json;
|
|
20
|
+
/**
|
|
21
|
+
* `o` with the author's changes in `e` applied key by key under the delta rule,
|
|
22
|
+
* keys in the original's order; `inner` merges a value all three have and the
|
|
23
|
+
* author changed. Built from entries, so an own `__proto__` key stays a key.
|
|
24
|
+
*/
|
|
25
|
+
export declare function mergeObject(o: Obj, b: Obj, e: Obj, inner: (o: Json, b: Json, e: Json) => Json): Obj;
|
|
19
26
|
export {};
|
package/dist/merge-variables.js
CHANGED
|
@@ -75,7 +75,7 @@ export function mergeVariables(original, edited, baseline, goneNodeIds) {
|
|
|
75
75
|
return copy(edited);
|
|
76
76
|
const orig = isObj(original) ? original : {};
|
|
77
77
|
const baseObj = isObj(base) ? base : {};
|
|
78
|
-
const
|
|
78
|
+
const entries = [];
|
|
79
79
|
for (const key of keysInOrder(orig, edited)) {
|
|
80
80
|
const updates = key === 'variableUpdates';
|
|
81
81
|
const inner = updates
|
|
@@ -83,16 +83,16 @@ export function mergeVariables(original, edited, baseline, goneNodeIds) {
|
|
|
83
83
|
: (o, b, e) => mergeList(o, b, e, goneNodeIds);
|
|
84
84
|
// A section the original lacks but the round trip writes is merged as an
|
|
85
85
|
// empty one, so its minted entries stay out and only the author's land.
|
|
86
|
-
const absent = !(key
|
|
86
|
+
const absent = !has(orig, key) && has(baseObj, key) && has(edited, key) && !same(edited[key], baseObj[key]);
|
|
87
87
|
const value = absent
|
|
88
88
|
? nonEmpty(inner(updates ? {} : [], baseObj[key], edited[key]))
|
|
89
|
-
: mergeKey(orig
|
|
89
|
+
: mergeKey(own(orig, key), own(baseObj, key), own(edited, key), has(orig, key), has(baseObj, key), has(edited, key), inner);
|
|
90
90
|
if (value !== undefined)
|
|
91
|
-
|
|
91
|
+
entries.push([key, value]);
|
|
92
92
|
}
|
|
93
|
-
if (original === undefined &&
|
|
93
|
+
if (original === undefined && entries.length === 0)
|
|
94
94
|
return undefined;
|
|
95
|
-
return withoutGoneUpdates(
|
|
95
|
+
return withoutGoneUpdates(Object.fromEntries(entries), goneNodeIds);
|
|
96
96
|
}
|
|
97
97
|
/** `undefined` for an empty list or map, so an absent section stays absent. */
|
|
98
98
|
function nonEmpty(v) {
|
|
@@ -113,9 +113,29 @@ function withoutGoneUpdates(variables, goneNodeIds) {
|
|
|
113
113
|
return variables;
|
|
114
114
|
return { ...variables, variableUpdates: Object.fromEntries(Object.entries(updates).filter(([k]) => !goneNodeIds.has(k))) };
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Own keys only, so a key named like an `Object.prototype` member
|
|
118
|
+
* (`constructor`, `toString`) is not taken as present everywhere.
|
|
119
|
+
*/
|
|
120
|
+
const has = (v, k) => Object.hasOwn(v, k);
|
|
121
|
+
const own = (v, k) => (has(v, k) ? v[k] : undefined);
|
|
116
122
|
/** Original's keys in its order, then the edited file's new ones. */
|
|
117
123
|
function keysInOrder(original, edited) {
|
|
118
|
-
return [...Object.keys(original), ...Object.keys(edited).filter((k) => !(k
|
|
124
|
+
return [...Object.keys(original), ...Object.keys(edited).filter((k) => !has(original, k))];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* `o` with the author's changes in `e` applied key by key under the delta rule,
|
|
128
|
+
* keys in the original's order; `inner` merges a value all three have and the
|
|
129
|
+
* author changed. Built from entries, so an own `__proto__` key stays a key.
|
|
130
|
+
*/
|
|
131
|
+
export function mergeObject(o, b, e, inner) {
|
|
132
|
+
const entries = [];
|
|
133
|
+
for (const key of keysInOrder(o, e)) {
|
|
134
|
+
const v = mergeKey(own(o, key), own(b, key), own(e, key), has(o, key), has(b, key), has(e, key), inner);
|
|
135
|
+
if (v !== undefined)
|
|
136
|
+
entries.push([key, v]);
|
|
137
|
+
}
|
|
138
|
+
return Object.fromEntries(entries);
|
|
119
139
|
}
|
|
120
140
|
/**
|
|
121
141
|
* One key's value under the delta rule; `undefined` = leave the key out.
|
|
@@ -173,14 +193,7 @@ function insertAt(out, edited, i) {
|
|
|
173
193
|
* keys in the original's order.
|
|
174
194
|
*/
|
|
175
195
|
function mergeFields(o, b, e) {
|
|
176
|
-
|
|
177
|
-
const out = {};
|
|
178
|
-
for (const key of keysInOrder(o, e)) {
|
|
179
|
-
const v = mergeKey(o[key], base[key], e[key], key in o, key in base, key in e, (_o, _b, ev) => copy(ev));
|
|
180
|
-
if (v !== undefined)
|
|
181
|
-
out[key] = v;
|
|
182
|
-
}
|
|
183
|
-
return out;
|
|
196
|
+
return mergeObject(o, b ?? {}, e, (_o, _b, ev) => copy(ev));
|
|
184
197
|
}
|
|
185
198
|
function idList(v) {
|
|
186
199
|
return Array.isArray(v) && v.every((x) => isObj(x) && typeof x.id === 'string');
|
package/dist/merge.d.ts
CHANGED
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
* `edited` is byte-equal to its `baseline` counterpart, the user did NOT
|
|
20
20
|
* touch it, so the merge keeps the ORIGINAL node verbatim (its real type,
|
|
21
21
|
* inputs, layout — even for a placeholder). Only nodes the user actually
|
|
22
|
-
* changed (or added) are taken from `edited
|
|
22
|
+
* changed (or added) are taken from `edited`, and a changed node takes only
|
|
23
|
+
* the fields that differ from the baseline (see withEdits).
|
|
23
24
|
* - Without a `baseline`, the merge is two-way: it takes `edited`'s graph and
|
|
24
25
|
* re-attaches the original layout by node-id match. This is correct as long
|
|
25
26
|
* as decompile was faithful for every node (the common case).
|
package/dist/merge.js
CHANGED
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
* `edited` is byte-equal to its `baseline` counterpart, the user did NOT
|
|
20
20
|
* touch it, so the merge keeps the ORIGINAL node verbatim (its real type,
|
|
21
21
|
* inputs, layout — even for a placeholder). Only nodes the user actually
|
|
22
|
-
* changed (or added) are taken from `edited
|
|
22
|
+
* changed (or added) are taken from `edited`, and a changed node takes only
|
|
23
|
+
* the fields that differ from the baseline (see withEdits).
|
|
23
24
|
* - Without a `baseline`, the merge is two-way: it takes `edited`'s graph and
|
|
24
25
|
* re-attaches the original layout by node-id match. This is correct as long
|
|
25
26
|
* as decompile was faithful for every node (the common case).
|
|
@@ -85,12 +86,13 @@
|
|
|
85
86
|
*/
|
|
86
87
|
import { bindingIdentity } from './core/binding-identity.js';
|
|
87
88
|
import { jsonClone } from './core/json-clone.js';
|
|
89
|
+
import { isRecord } from './core/node-envelope.js';
|
|
88
90
|
import { DEFINITIONLESS_NODE_TYPES } from './core/definitionless-node-types.js';
|
|
89
91
|
import { MergeRefusedError, mergedFileFindings, roundTripFindings } from './merge-roundtrip.js';
|
|
90
92
|
import { placeAddedNodes } from './node-placement.js';
|
|
91
93
|
import { mergeAgentSidecars } from './merge-sidecars.js';
|
|
92
94
|
import { embeddedAgentFindings, isEmbeddedAgent, mergeEmbeddedAgentNode } from './merge-embedded-agent.js';
|
|
93
|
-
import { mergeVariables } from './merge-variables.js';
|
|
95
|
+
import { mergeObject, mergeVariables, stableJson } from './merge-variables.js';
|
|
94
96
|
export { MergeRefusedError } from './merge-roundtrip.js';
|
|
95
97
|
function byId(nodes) {
|
|
96
98
|
return new Map((nodes ?? []).map((n) => [n.id, n]));
|
|
@@ -163,6 +165,65 @@ function withOriginalLayout(edited, orig, originalLayout) {
|
|
|
163
165
|
}
|
|
164
166
|
return edited;
|
|
165
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Give `node` the original's `display` with the author's display edits applied,
|
|
170
|
+
* key by key: a key whose edited value differs from the baseline's is the
|
|
171
|
+
* author's (a relabel); every other key stays as the original has it. Compile
|
|
172
|
+
* writes only a label and the type's default icon, so the edited display taken
|
|
173
|
+
* whole drops a canvas file's `subLabel`, `iconBackground` and custom icon, and
|
|
174
|
+
* the original's taken whole drops the relabel.
|
|
175
|
+
*/
|
|
176
|
+
function withDisplayEdits(node, orig, e, base) {
|
|
177
|
+
const o = isRecord(orig.display) ? orig.display : {};
|
|
178
|
+
const ed = isRecord(e.display) ? e.display : {};
|
|
179
|
+
const b = isRecord(base.display) ? base.display : {};
|
|
180
|
+
const display = {};
|
|
181
|
+
for (const k of new Set([...Object.keys(o), ...Object.keys(ed)])) {
|
|
182
|
+
const v = sortedJson(ed[k]) === sortedJson(b[k]) ? o[k] : ed[k];
|
|
183
|
+
if (v !== undefined)
|
|
184
|
+
display[k] = jsonClone(v);
|
|
185
|
+
}
|
|
186
|
+
const next = sortedJson(display) === sortedJson(orig.display ?? {}) ? orig.display : display;
|
|
187
|
+
if (JSON.stringify(next) === JSON.stringify(node.display))
|
|
188
|
+
return node;
|
|
189
|
+
// In place, so the node keeps its key order.
|
|
190
|
+
const out = { ...node, display: undefined };
|
|
191
|
+
if (next === undefined)
|
|
192
|
+
delete out.display;
|
|
193
|
+
else
|
|
194
|
+
out.display = jsonClone(next);
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The original with the author's edits applied, by the delta rule of
|
|
199
|
+
* merge-variables.ts: where `e` equals the baseline `b` the original's value
|
|
200
|
+
* stays (absent stays absent), and inside objects all three have, the rule
|
|
201
|
+
* recurses key by key. Lists are taken whole, and so is a changed expression
|
|
202
|
+
* value, whose `type` says how to read its `expression`: decompile turns a
|
|
203
|
+
* legacy `{ type: 'literal', expression: '{{ $vars.x }}' }` agent input into a
|
|
204
|
+
* `jsExpression`, so an edited reference merged key by key would keep
|
|
205
|
+
* `literal` and hand the agent the text. Applied to an edited node, a
|
|
206
|
+
* one-input edit to a connector keeps the rest of its `detail` as written, and
|
|
207
|
+
* compile's `model`, derived `outputs` and other spellings stay out of it.
|
|
208
|
+
*/
|
|
209
|
+
function withEdits(o, b, e) {
|
|
210
|
+
if (stableJson(e) === stableJson(b))
|
|
211
|
+
return o === undefined ? o : jsonClone(o);
|
|
212
|
+
if (!isRecord(o) || !isRecord(b) || !isRecord(e) || isExpressionValue(o) || isExpressionValue(e)) {
|
|
213
|
+
return e === undefined ? e : jsonClone(e);
|
|
214
|
+
}
|
|
215
|
+
return mergeObject(o, b, e, withEdits);
|
|
216
|
+
}
|
|
217
|
+
const isExpressionValue = (v) => typeof v.type === 'string' && Object.hasOwn(v, 'expression');
|
|
218
|
+
/**
|
|
219
|
+
* Whether an edited node can merge field by field onto its original: all three
|
|
220
|
+
* have one type and one `typeVersion`. A node whose version the edit or the
|
|
221
|
+
* round trip moved is taken from `edited` whole, so it never mixes two
|
|
222
|
+
* versions' inputs.
|
|
223
|
+
*/
|
|
224
|
+
function mergesByField(orig, base, e) {
|
|
225
|
+
return orig.type === e.type && base.type === e.type && orig.typeVersion === e.typeVersion && base.typeVersion === e.typeVersion;
|
|
226
|
+
}
|
|
166
227
|
/**
|
|
167
228
|
* Give an edited node the original's `inputs.source` when only the round trip
|
|
168
229
|
* changed it (the baseline's equals the edited one's).
|
|
@@ -243,18 +304,23 @@ function mergeFlowFindings(original, edited, options) {
|
|
|
243
304
|
const editedIds = new Set();
|
|
244
305
|
for (const e of edited.nodes ?? []) {
|
|
245
306
|
const orig = origNodes.get(e.id);
|
|
307
|
+
const base = baseNodes.get(e.id);
|
|
246
308
|
if (isKept(e, origNodes, baseNodes, options.baseline) && orig) {
|
|
247
309
|
// Untouched by the user (equal to the faithful baseline) → keep the
|
|
248
|
-
// original node exactly, placeholder-safe.
|
|
249
|
-
finalNodes.push(jsonClone(orig));
|
|
310
|
+
// original node exactly, placeholder-safe, plus any relabel.
|
|
311
|
+
finalNodes.push(base ? withDisplayEdits(jsonClone(orig), orig, e, base) : jsonClone(orig));
|
|
250
312
|
keptFromOriginal.add(e.id);
|
|
251
313
|
}
|
|
252
314
|
else {
|
|
253
|
-
// Added or edited →
|
|
315
|
+
// Added or edited → the original plus the author's changes, field by
|
|
316
|
+
// field (withEdits), else the edited node; then re-attach original layout.
|
|
254
317
|
// An EMBEDDED agent stays embedded: the original plus the author's change
|
|
255
318
|
// to the node, never the edited shell whole (#1220, merge-embedded-agent.ts).
|
|
256
|
-
const
|
|
257
|
-
|
|
319
|
+
const taken = keepsEmbeddedAgent(orig, e) ? mergeEmbeddedAgentNode(orig, e, base)
|
|
320
|
+
: orig && base && mergesByField(orig, base, e) ? withEdits(orig, base, e)
|
|
321
|
+
: jsonClone(e);
|
|
322
|
+
const node = orig && base ? withDisplayEdits(taken, orig, e, base) : taken;
|
|
323
|
+
const placed = withOriginalSource(withOriginalLayout(node, orig, layoutNodesOf(original)), orig, base);
|
|
258
324
|
finalNodes.push(placed);
|
|
259
325
|
// Without a baseline every node lands here; count one as edited only
|
|
260
326
|
// when the merged node differs from the original.
|
|
@@ -289,6 +355,10 @@ function mergeFlowFindings(original, edited, options) {
|
|
|
289
355
|
...(original.nodes ?? []),
|
|
290
356
|
...Object.values(original.subflows ?? {}).flatMap((sf) => sf?.nodes ?? []),
|
|
291
357
|
].map((n) => n.id).filter((id) => !mergedNodeIds.has(id)));
|
|
358
|
+
const layoutNodes = layoutNodesOf(result);
|
|
359
|
+
if (layoutNodes)
|
|
360
|
+
for (const id of goneNodeIds)
|
|
361
|
+
delete layoutNodes[id];
|
|
292
362
|
const variables = mergeVariables(original.variables, edited.variables, options.baseline, goneNodeIds);
|
|
293
363
|
if (variables === undefined)
|
|
294
364
|
delete result.variables;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Print one connector operation's page: its inputs, outputs, lookups and the code to
|
|
3
|
+
* write.
|
|
4
|
+
*
|
|
5
|
+
* The page comes from where `check` and `compile` read: the flow's prepared
|
|
6
|
+
* `connectors-local` overlay first, then the library. So after `registry prepare`
|
|
7
|
+
* it shows the connection's real fields. It is rendered from the entry on each call,
|
|
8
|
+
* so it needs no `library-md` tree and no file the agent must keep a path to.
|
|
9
|
+
*/
|
|
10
|
+
export declare function run(argv: string[]): Promise<number>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { printResult, reportResult } from '../cli-result.js';
|
|
2
|
+
import { flagValue } from '../cli-run.js';
|
|
3
|
+
import { openCliLibrary } from '../core/cli-library.js';
|
|
4
|
+
import { PULL_COMMAND } from '../core/cli-spelling.js';
|
|
5
|
+
import { renderMarkdownPage } from './generators/markdown/index.js';
|
|
6
|
+
import { symbolIndex } from './naming.js';
|
|
7
|
+
/**
|
|
8
|
+
* Print one connector operation's page: its inputs, outputs, lookups and the code to
|
|
9
|
+
* write.
|
|
10
|
+
*
|
|
11
|
+
* The page comes from where `check` and `compile` read: the flow's prepared
|
|
12
|
+
* `connectors-local` overlay first, then the library. So after `registry prepare`
|
|
13
|
+
* it shows the connection's real fields. It is rendered from the entry on each call,
|
|
14
|
+
* so it needs no `library-md` tree and no file the agent must keep a path to.
|
|
15
|
+
*/
|
|
16
|
+
export async function run(argv) {
|
|
17
|
+
const nodeType = argv[0];
|
|
18
|
+
const library = openCliLibrary(argv);
|
|
19
|
+
if (!library) {
|
|
20
|
+
console.error(`No connector library is cached. Run \`${PULL_COMMAND}\`, or pass --library <dir>.`);
|
|
21
|
+
return 2;
|
|
22
|
+
}
|
|
23
|
+
let source;
|
|
24
|
+
try {
|
|
25
|
+
source = library.pageSource(nodeType, flagValue(argv, '--connector-version'), flagValue(argv, '--object'));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
// The symbol names `prepare`'s module exports: it counts GenericExpanded entries only when the overlay has one.
|
|
32
|
+
const expanded = source.prepared && source.rows.some((e) => e?.activityType === 'GenericExpanded');
|
|
33
|
+
const body = renderMarkdownPage(source.entry, source.v1def, { symbols: symbolIndex(source.rows, expanded), invariant: source.invariant });
|
|
34
|
+
// No path: on a browser host it names a store the agent cannot open.
|
|
35
|
+
const origin = source.prepared ? 'the prepared copy from `registry prepare`' : 'the connector library';
|
|
36
|
+
const page = `> Source: ${origin}\n\n${body.replace(/\n$/, '')}`;
|
|
37
|
+
// The page alone, so `uip … --output plain` prints just the Markdown.
|
|
38
|
+
reportResult({ page });
|
|
39
|
+
printResult(page);
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading an extracted `library-json/` tree.
|
|
3
|
+
*
|
|
4
|
+
* Every generator walks the same index and opens the same two files per
|
|
5
|
+
* operation. This is that walk, once, with the path rules the Python spelled in
|
|
6
|
+
* three places: the entry is `index.json`'s `path`, the sidecar is the same path
|
|
7
|
+
* with `.json` swapped for `.v1def.json`, and a sidecar that is a one-element
|
|
8
|
+
* list is unwrapped.
|
|
9
|
+
*/
|
|
10
|
+
import { type ReadText } from "../core/read-text.js";
|
|
1
11
|
import type { IndexRow, LibraryEntry, LibraryIndex, V1Definition } from "./model/library.js";
|
|
2
12
|
export interface LoadedLibrary {
|
|
3
13
|
/** The `library-json` directory. */
|
|
@@ -14,7 +24,7 @@ export interface LoadedLibrary {
|
|
|
14
24
|
}
|
|
15
25
|
/** `<path minus .json>.v1def.json`, the sidecar beside an entry. */
|
|
16
26
|
export declare function sidecarPath(entryRelativePath: string): string;
|
|
17
|
-
export declare function readJsonFile(path: string): unknown;
|
|
18
27
|
/** A sidecar may be `[definition]` in some snapshots; the definition is what callers want. */
|
|
19
28
|
export declare function unwrapV1Def(raw: unknown): V1Definition | undefined;
|
|
20
|
-
|
|
29
|
+
/** `read` defaults to the disk; a host without one passes its own (`core/read-text.ts`). */
|
|
30
|
+
export declare function loadLibrary(dir: string, read?: ReadText): LoadedLibrary;
|
|
@@ -7,16 +7,12 @@
|
|
|
7
7
|
* with `.json` swapped for `.v1def.json`, and a sidecar that is a one-element
|
|
8
8
|
* list is unwrapped.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
11
|
-
import { join } from "node:path";
|
|
10
|
+
import { libraryPath, readTextFromDisk } from "../core/read-text.js";
|
|
12
11
|
import { loadInvariantCollections } from "./lookup-specs.js";
|
|
13
12
|
/** `<path minus .json>.v1def.json`, the sidecar beside an entry. */
|
|
14
13
|
export function sidecarPath(entryRelativePath) {
|
|
15
14
|
return `${entryRelativePath.slice(0, -".json".length)}.v1def.json`;
|
|
16
15
|
}
|
|
17
|
-
export function readJsonFile(path) {
|
|
18
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
19
|
-
}
|
|
20
16
|
/** A sidecar may be `[definition]` in some snapshots; the definition is what callers want. */
|
|
21
17
|
export function unwrapV1Def(raw) {
|
|
22
18
|
if (Array.isArray(raw))
|
|
@@ -25,26 +21,31 @@ export function unwrapV1Def(raw) {
|
|
|
25
21
|
return raw;
|
|
26
22
|
return undefined;
|
|
27
23
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
/** `read` defaults to the disk; a host without one passes its own (`core/read-text.ts`). */
|
|
25
|
+
export function loadLibrary(dir, read = readTextFromDisk) {
|
|
26
|
+
const indexPath = libraryPath(dir, "index.json");
|
|
27
|
+
const indexText = read(indexPath);
|
|
28
|
+
if (indexText === undefined) {
|
|
31
29
|
throw new Error(`library missing index.json: ${indexPath}`);
|
|
32
30
|
}
|
|
33
|
-
const index =
|
|
34
|
-
const invariant = loadInvariantCollections(dir);
|
|
31
|
+
const index = JSON.parse(indexText);
|
|
32
|
+
const invariant = loadInvariantCollections(dir, read);
|
|
33
|
+
const readIfPresent = (path) => {
|
|
34
|
+
const text = read(path);
|
|
35
|
+
return text === undefined ? undefined : JSON.parse(text);
|
|
36
|
+
};
|
|
35
37
|
return {
|
|
36
38
|
dir,
|
|
37
39
|
index,
|
|
38
40
|
invariant,
|
|
39
|
-
entryPath: (row) =>
|
|
40
|
-
v1defPath: (row) =>
|
|
41
|
+
entryPath: (row) => libraryPath(dir, row.path),
|
|
42
|
+
v1defPath: (row) => libraryPath(dir, sidecarPath(row.path)),
|
|
41
43
|
readEntry(row) {
|
|
42
|
-
|
|
43
|
-
return existsSync(path) ? readJsonFile(path) : undefined;
|
|
44
|
+
return readIfPresent(libraryPath(dir, row.path));
|
|
44
45
|
},
|
|
45
46
|
readV1Def(row) {
|
|
46
|
-
const
|
|
47
|
-
return
|
|
47
|
+
const raw = readIfPresent(libraryPath(dir, sidecarPath(row.path)));
|
|
48
|
+
return raw === undefined ? undefined : unwrapV1Def(raw);
|
|
48
49
|
},
|
|
49
50
|
};
|
|
50
51
|
}
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which connector input fields earn a `.by*` helper, and what the helper needs.
|
|
3
|
+
*
|
|
4
|
+
* A `reference` with a `lookupValue` says a field's value is an opaque id to be
|
|
5
|
+
* resolved against a live connection. Two classes are excluded: IDENTITY
|
|
6
|
+
* lookups, where `lookupValue` is the only `lookupNames` entry so resolving
|
|
7
|
+
* returns what it was given, and INVARIANT collections, whose ids are a property
|
|
8
|
+
* of the connector's API rather than of a connection (a country list), read from
|
|
9
|
+
* the curated `invariant-collections.json` ledger. Everything else is a lookup;
|
|
10
|
+
* the fallback direction is the safe one.
|
|
11
|
+
*
|
|
12
|
+
* Ported from `integrations/scripts/_lookups.py` and the `lookup_specs` half of
|
|
13
|
+
* `generate_connectors_ts.py`. `core/lookups.ts` keeps `check` and `compile`
|
|
14
|
+
* agreeing with this by importing it.
|
|
15
|
+
*/
|
|
16
|
+
import { type ReadText } from "../core/read-text.js";
|
|
1
17
|
import type { FieldReference, LibraryField } from "./model/library.js";
|
|
2
18
|
export declare const LEDGER_FILENAME = "invariant-collections.json";
|
|
3
19
|
/**
|
|
@@ -5,7 +21,7 @@ export declare const LEDGER_FILENAME = "invariant-collections.json";
|
|
|
5
21
|
* ledger reads as empty rather than raising: the permissive path is the
|
|
6
22
|
* pre-ledger behaviour, so a bad edit loses exactness, never a build.
|
|
7
23
|
*/
|
|
8
|
-
export declare function loadInvariantCollections(libraryDir: string | undefined): Set<string>;
|
|
24
|
+
export declare function loadInvariantCollections(libraryDir: string | undefined, read?: ReadText): Set<string>;
|
|
9
25
|
/** The field's `reference` block when it declares a resolvable value. */
|
|
10
26
|
export declare function referenceOf(field: {
|
|
11
27
|
reference?: unknown;
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
* `generate_connectors_ts.py`. `core/lookups.ts` keeps `check` and `compile`
|
|
14
14
|
* agreeing with this by importing it.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
17
|
-
import { join } from "node:path";
|
|
16
|
+
import { libraryPath, readTextFromDisk } from "../core/read-text.js";
|
|
18
17
|
import { pyTruthy } from "./generators/type-tree.js";
|
|
19
18
|
import { byAliases } from "./naming.js";
|
|
20
19
|
export const LEDGER_FILENAME = "invariant-collections.json";
|
|
@@ -23,15 +22,12 @@ export const LEDGER_FILENAME = "invariant-collections.json";
|
|
|
23
22
|
* ledger reads as empty rather than raising: the permissive path is the
|
|
24
23
|
* pre-ledger behaviour, so a bad edit loses exactness, never a build.
|
|
25
24
|
*/
|
|
26
|
-
export function loadInvariantCollections(libraryDir) {
|
|
25
|
+
export function loadInvariantCollections(libraryDir, read = readTextFromDisk) {
|
|
27
26
|
if (!libraryDir)
|
|
28
27
|
return new Set();
|
|
29
|
-
const path = join(libraryDir, LEDGER_FILENAME);
|
|
30
|
-
if (!existsSync(path))
|
|
31
|
-
return new Set();
|
|
32
28
|
let parsed;
|
|
33
29
|
try {
|
|
34
|
-
parsed = JSON.parse(
|
|
30
|
+
parsed = JSON.parse(read(libraryPath(libraryDir, LEDGER_FILENAME)) ?? "{}");
|
|
35
31
|
}
|
|
36
32
|
catch {
|
|
37
33
|
return new Set();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { printResult, reportResult } from '../cli-result.js';
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { PULL_COMMAND } from "../core/cli-spelling.js";
|
|
4
|
+
import { CURRENT_LINK, currentEntrySha, libraryCacheRoot, resolveCachedLibrary, resolveCachedMarkdown, } from "../core/library-cache.js";
|
|
4
5
|
/**
|
|
5
6
|
* Say where the connector library is.
|
|
6
7
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { printResult, reportResult } from '../cli-result.js';
|
|
2
2
|
import { readFile, stat } from "node:fs/promises";
|
|
3
|
-
import { PULL_COMMAND
|
|
3
|
+
import { PULL_COMMAND } from "../core/cli-spelling.js";
|
|
4
|
+
import { resolveLibraryDir } from "../core/library-cache.js";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
5
6
|
export const DEFAULT_SEARCH_LIMIT = 20;
|
|
6
7
|
const USAGE = "Usage: flow-sdk registry search [query] [--library <dir>] [--library-md <dir>] [--connector <key>] [--limit <n>]";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.42.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",
|
|
@@ -93,5 +93,5 @@
|
|
|
93
93
|
"@types/node": "^22.7.0",
|
|
94
94
|
"esbuild": "^0.28.1"
|
|
95
95
|
},
|
|
96
|
-
"gitref": "
|
|
96
|
+
"gitref": "167eb5f8f1c8f3f5d039cf72e7c901e3b4737cb6"
|
|
97
97
|
}
|