@uipath/maestro-builder-sdk 6.47.3 → 6.49.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/README.md +2 -2
- package/dist/api-index.md +44 -44
- package/dist/api-members.md +151 -151
- package/dist/bpmn/bpmn-sdk.d.ts +1 -1
- package/dist/bpmn/compile-cli.js +1 -1
- package/dist/bpmn/decompile-cli.js +2 -2
- package/dist/bpmn/decompile.js +3 -3
- package/dist/bpmn/merge.js +1 -1
- package/dist/bpmn/serialize.d.ts +1 -2
- package/dist/bpmn/serialize.js +3 -4
- package/dist/check.js +6 -97
- package/dist/cli/commands/bpmn.js +0 -9
- package/dist/core/actions.d.ts +0 -151
- package/dist/core/actions.js +0 -96
- package/dist/core/nodes/registry.d.ts +2 -1
- package/dist/core/nodes/registry.js +13 -5
- package/dist/core/nodes/versioned.d.ts +8 -0
- package/dist/decompile.js +0 -31
- package/dist/nodes/document/flow/factories.d.ts +59 -4
- package/dist/nodes/document/flow/factories.js +60 -3
- package/dist/nodes/document/flow/family.d.ts +1 -1
- package/dist/nodes/document/flow/family.js +3 -2
- package/dist/nodes/document/flow/ixp.d.ts +34 -0
- package/dist/nodes/document/flow/ixp.js +389 -0
- package/dist/nodes/document/flow/node-type.js +1 -3
- package/dist/nodes/document/flow/spec.d.ts +102 -2
- package/dist/nodes/document/flow/spec.js +39 -1
- package/dist/serialize.d.ts +2 -21
- package/dist/serialize.js +6 -248
- package/package.json +2 -3
- package/dist/bpmn/tidy-cli.d.ts +0 -2
- package/dist/bpmn/tidy-cli.js +0 -38
package/dist/decompile.js
CHANGED
|
@@ -38,7 +38,6 @@ import { commentText } from './core/comment-text.js';
|
|
|
38
38
|
import { tsKey as key, tsLiteral, tsNumber, tsObject as objLit, tsOptions as opts, tsText } from './core/ts-literal.js';
|
|
39
39
|
import { wireExprBody } from './core/wire-expr.js';
|
|
40
40
|
import { hasExits } from './core/step-ports.js';
|
|
41
|
-
import { parseIxpProjectType } from './core/actions.js';
|
|
42
41
|
import { readFlowGlobals } from './flow-variables.js';
|
|
43
42
|
import { outputBackingGlobal, outputBackingName } from './flow-output-backing.js';
|
|
44
43
|
import { mapJsCode } from './core/js-code-spans.js';
|
|
@@ -802,36 +801,6 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
|
|
|
802
801
|
if (sub !== undefined)
|
|
803
802
|
return `${imp.use('waitForEvent')}(${sub})`;
|
|
804
803
|
}
|
|
805
|
-
// A published IxP extractor: `uipath.ixp.<slug>.<versionId>-<folderKey>`.
|
|
806
|
-
// The document-processing pair (`uipath.ixp.extract-document-builder`,
|
|
807
|
-
// `uipath.document.classify`) are their own families, handled above and
|
|
808
|
-
// excluded here by the parse — their tails are not the GUID pair.
|
|
809
|
-
if (node.type.startsWith('uipath.ixp.') && parseIxpProjectType(node.type) !== undefined) {
|
|
810
|
-
const i = (node.inputs ?? {});
|
|
811
|
-
const plain = (k) => {
|
|
812
|
-
const v = unwrapSource(i[k]);
|
|
813
|
-
return v === undefined || v === '' ? undefined : v;
|
|
814
|
-
};
|
|
815
|
-
const fr = unwrapSource(i.fileRef) ?? '';
|
|
816
|
-
// The DEPLOYMENT's display name is nested in `inputs.model`, not at the
|
|
817
|
-
// top of `inputs` — the two are different values (the model block's
|
|
818
|
-
// `modelDisplayName` is 'InvoiceIXP'; `modelName` is the slug), so
|
|
819
|
-
// reading the wrong one recovers the slug as the name.
|
|
820
|
-
const model = (i.model ?? {});
|
|
821
|
-
const nested = (k) => typeof model[k] === 'string' && model[k] !== '' ? model[k] : undefined;
|
|
822
|
-
return `${imp.use('ixpExtract')}(${opts([
|
|
823
|
-
['project', tsLiteral(node.type)],
|
|
824
|
-
// `modelName`/`projectName` are one value in two fields; either recovers it.
|
|
825
|
-
['modelName', tsLiteral(plain('modelName') ?? plain('projectName') ?? nested('modelName') ?? '')],
|
|
826
|
-
['name', tsLiteral(nested('modelDisplayName') ?? plain('modelName') ?? '')],
|
|
827
|
-
['folderName', tsLiteral(plain('folderName') ?? nested('folderName') ?? '')],
|
|
828
|
-
['fileRef', fr.startsWith('=js:') ? emitBareExpr(fr.slice(4), imp, inputNames) : tsLiteral(fr)],
|
|
829
|
-
['pageRange', plain('pageRange') !== undefined ? tsLiteral(plain('pageRange')) : undefined],
|
|
830
|
-
['versionTag', plain('versionTag') !== undefined ? tsLiteral(plain('versionTag')) : undefined],
|
|
831
|
-
['description', plain('description') ?? nested('description') !== undefined
|
|
832
|
-
? tsLiteral((plain('description') ?? nested('description'))) : undefined],
|
|
833
|
-
])})`;
|
|
834
|
-
}
|
|
835
804
|
// Every family this SDK AUTHORS is recovered above — a guard test asserts
|
|
836
805
|
// it (`flow-decompile-families.test.ts`). Reaching here means a node type
|
|
837
806
|
// the SDK cannot author: a hand-written file, a newer platform family, or
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The document factories, `documentClassify()
|
|
3
|
-
* from core/actions.ts in Phase 4 of
|
|
4
|
-
* package entry re-exports them.
|
|
2
|
+
* The document factories, `documentClassify()`, `dynamicExtract()` and
|
|
3
|
+
* `ixpExtract()`. Moved from core/actions.ts in Phase 4 of
|
|
4
|
+
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports them.
|
|
5
5
|
*/
|
|
6
6
|
import { type ActionSpec } from '../../../core/actions.js';
|
|
7
|
-
import type { DocumentClassifyInputs, DynamicExtractInputs } from './spec.js';
|
|
7
|
+
import type { DocumentClassifyInputs, DynamicExtractInputs, IxpExtractInputs } from './spec.js';
|
|
8
8
|
/**
|
|
9
9
|
* Classify a document into a document type (`uipath.document.classify`). The
|
|
10
10
|
* result publishes `ClassificationResult` — an array of typed classifications
|
|
@@ -25,3 +25,58 @@ export declare function documentClassify(inputs: DocumentClassifyInputs): Action
|
|
|
25
25
|
* @returns An action spec to pass to `.step(name, …)`.
|
|
26
26
|
*/
|
|
27
27
|
export declare function dynamicExtract(inputs: DynamicExtractInputs): ActionSpec;
|
|
28
|
+
/**
|
|
29
|
+
* Extract fields from a document with a published IxP project — the platform's
|
|
30
|
+
* **Extract** node (`uipath.ixp.*`, service contract `IXP.Extraction`).
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* **The result's shape is three levels deep and PascalCase.** A field lands at
|
|
34
|
+
* `out('<step>', 'ExtractionResult.ResultsDocument.Fields')` — an array of
|
|
35
|
+
* `{ FieldId, FieldName, FieldType, IsMissing, Values[], Confidence }`, looked
|
|
36
|
+
* up by `FieldName`. `Fields[]` is under `ResultsDocument`, not directly under
|
|
37
|
+
* `ExtractionResult`, and there is no `result` wrapper: the platform's
|
|
38
|
+
* serializer strips it before the value reaches the variable.
|
|
39
|
+
*
|
|
40
|
+
* **A local run cannot extract anything** — extraction needs a real document on
|
|
41
|
+
* the IxP service. An offline run returns the baked
|
|
42
|
+
* fixture shape, and only a platform run reaches the model. So a green local
|
|
43
|
+
* rung proves the wiring and the definition, never that a document was read.
|
|
44
|
+
*
|
|
45
|
+
* **`fileRef` is emitted as a PLAIN `=js:` string, not the expression envelope
|
|
46
|
+
* the rest of the file uses** — the one place this family diverges from the
|
|
47
|
+
* format's general spelling, and the contract this node owes the platform. The
|
|
48
|
+
* product's own `ixp-node` validator tests `typeof fileRef !== 'string'` and
|
|
49
|
+
* refuses the object form (*"inputs.fileRef must be a `=js:$vars.…`
|
|
50
|
+
* expression"*), the node's registry manifest declares
|
|
51
|
+
* `{type:'string', minLength:1}`, and so do all 150 v1-authored instances in the
|
|
52
|
+
* deployed corpus that carry the field — at file versions 1.6, 1.8 and 1.9 alike,
|
|
53
|
+
* with zero envelopes among them. The RUNTIME
|
|
54
|
+
* accepts both, so only `uip maestro flow validate` sees a wrong one; the
|
|
55
|
+
* emitter's exemption lives in `migrate.ts` (`UNWRAPPED_INPUTS`).
|
|
56
|
+
* See the IxP node reference (`website/reference/ixp.md`) §3.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* **Extract an invoice total and read it out of the nested result**
|
|
60
|
+
* ```ts
|
|
61
|
+
* flow('InvoiceIntake')
|
|
62
|
+
* .input({ invoiceFile: types.file })
|
|
63
|
+
* .output({ total: types.string })
|
|
64
|
+
* .step('extract', ixpExtract({
|
|
65
|
+
* project: 'uipath.ixp.invoiceixp-cef0d447-ixp.ff973488-5d89-8035-86ac-f980b4be6deb-c4359cde-55f0-4f0e-9322-c6cdce74ab4c',
|
|
66
|
+
* modelName: 'invoiceixp-cef0d447-ixp',
|
|
67
|
+
* name: 'InvoiceIXP',
|
|
68
|
+
* folderName: 'Shared',
|
|
69
|
+
* fileRef: out('start', 'invoiceFile'),
|
|
70
|
+
* }))
|
|
71
|
+
* .step('readTotal', script({ code: `
|
|
72
|
+
* const fields = $vars.extract.output.ExtractionResult.ResultsDocument.Fields || [];
|
|
73
|
+
* return { total: fields.find(f => f.FieldName === 'invoiceTotal')?.Values?.[0] };
|
|
74
|
+
* ` }))
|
|
75
|
+
* .return({ total: out('readTotal', 'total') })
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @param inputs - The published IxP `project` and `modelName`, and the document
|
|
79
|
+
* to read (`fileRef`).
|
|
80
|
+
* @returns An action spec to pass to `.step(name, …)`.
|
|
81
|
+
*/
|
|
82
|
+
export declare function ixpExtract(inputs: IxpExtractInputs): ActionSpec;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The document factories, `documentClassify()
|
|
3
|
-
* from core/actions.ts in Phase 4 of
|
|
4
|
-
* package entry re-exports them.
|
|
2
|
+
* The document factories, `documentClassify()`, `dynamicExtract()` and
|
|
3
|
+
* `ixpExtract()`. Moved from core/actions.ts in Phase 4 of
|
|
4
|
+
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports them.
|
|
5
5
|
*/
|
|
6
6
|
import { builtinActionSpec } from '../../../core/actions.js';
|
|
7
7
|
/**
|
|
@@ -28,3 +28,60 @@ export function documentClassify(inputs) {
|
|
|
28
28
|
export function dynamicExtract(inputs) {
|
|
29
29
|
return builtinActionSpec({ kind: 'dynamicExtract', inputs });
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Extract fields from a document with a published IxP project — the platform's
|
|
33
|
+
* **Extract** node (`uipath.ixp.*`, service contract `IXP.Extraction`).
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* **The result's shape is three levels deep and PascalCase.** A field lands at
|
|
37
|
+
* `out('<step>', 'ExtractionResult.ResultsDocument.Fields')` — an array of
|
|
38
|
+
* `{ FieldId, FieldName, FieldType, IsMissing, Values[], Confidence }`, looked
|
|
39
|
+
* up by `FieldName`. `Fields[]` is under `ResultsDocument`, not directly under
|
|
40
|
+
* `ExtractionResult`, and there is no `result` wrapper: the platform's
|
|
41
|
+
* serializer strips it before the value reaches the variable.
|
|
42
|
+
*
|
|
43
|
+
* **A local run cannot extract anything** — extraction needs a real document on
|
|
44
|
+
* the IxP service. An offline run returns the baked
|
|
45
|
+
* fixture shape, and only a platform run reaches the model. So a green local
|
|
46
|
+
* rung proves the wiring and the definition, never that a document was read.
|
|
47
|
+
*
|
|
48
|
+
* **`fileRef` is emitted as a PLAIN `=js:` string, not the expression envelope
|
|
49
|
+
* the rest of the file uses** — the one place this family diverges from the
|
|
50
|
+
* format's general spelling, and the contract this node owes the platform. The
|
|
51
|
+
* product's own `ixp-node` validator tests `typeof fileRef !== 'string'` and
|
|
52
|
+
* refuses the object form (*"inputs.fileRef must be a `=js:$vars.…`
|
|
53
|
+
* expression"*), the node's registry manifest declares
|
|
54
|
+
* `{type:'string', minLength:1}`, and so do all 150 v1-authored instances in the
|
|
55
|
+
* deployed corpus that carry the field — at file versions 1.6, 1.8 and 1.9 alike,
|
|
56
|
+
* with zero envelopes among them. The RUNTIME
|
|
57
|
+
* accepts both, so only `uip maestro flow validate` sees a wrong one; the
|
|
58
|
+
* emitter's exemption lives in `migrate.ts` (`UNWRAPPED_INPUTS`).
|
|
59
|
+
* See the IxP node reference (`website/reference/ixp.md`) §3.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* **Extract an invoice total and read it out of the nested result**
|
|
63
|
+
* ```ts
|
|
64
|
+
* flow('InvoiceIntake')
|
|
65
|
+
* .input({ invoiceFile: types.file })
|
|
66
|
+
* .output({ total: types.string })
|
|
67
|
+
* .step('extract', ixpExtract({
|
|
68
|
+
* project: 'uipath.ixp.invoiceixp-cef0d447-ixp.ff973488-5d89-8035-86ac-f980b4be6deb-c4359cde-55f0-4f0e-9322-c6cdce74ab4c',
|
|
69
|
+
* modelName: 'invoiceixp-cef0d447-ixp',
|
|
70
|
+
* name: 'InvoiceIXP',
|
|
71
|
+
* folderName: 'Shared',
|
|
72
|
+
* fileRef: out('start', 'invoiceFile'),
|
|
73
|
+
* }))
|
|
74
|
+
* .step('readTotal', script({ code: `
|
|
75
|
+
* const fields = $vars.extract.output.ExtractionResult.ResultsDocument.Fields || [];
|
|
76
|
+
* return { total: fields.find(f => f.FieldName === 'invoiceTotal')?.Values?.[0] };
|
|
77
|
+
* ` }))
|
|
78
|
+
* .return({ total: out('readTotal', 'total') })
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @param inputs - The published IxP `project` and `modelName`, and the document
|
|
82
|
+
* to read (`fileRef`).
|
|
83
|
+
* @returns An action spec to pass to `.step(name, …)`.
|
|
84
|
+
*/
|
|
85
|
+
export function ixpExtract(inputs) {
|
|
86
|
+
return builtinActionSpec({ kind: 'ixpExtract', inputs });
|
|
87
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/** The document node family, as flow-node-registry.ts lists it: Document Classify and
|
|
1
|
+
/** The document node family, as flow-node-registry.ts lists it: Document Classify, Dynamic Extract and an IxP project's extractor. */
|
|
2
2
|
import type { FlowNodeFamily } from '../../../flow-node-type.js';
|
|
3
3
|
export declare const documentFlowFamily: FlowNodeFamily;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { IXP_DIAGNOSTICS, ixpExtractNodeType } from './ixp.js';
|
|
1
2
|
import { documentClassifyNodeType, DOCUMENT_DIAGNOSTICS, dynamicExtractNodeType } from './node-type.js';
|
|
2
3
|
export const documentFlowFamily = {
|
|
3
4
|
id: 'document',
|
|
4
5
|
title: 'Document understanding',
|
|
5
|
-
types: [documentClassifyNodeType, dynamicExtractNodeType],
|
|
6
|
-
diagnostics: DOCUMENT_DIAGNOSTICS,
|
|
6
|
+
types: [documentClassifyNodeType, dynamicExtractNodeType, ixpExtractNodeType],
|
|
7
|
+
diagnostics: { ...DOCUMENT_DIAGNOSTICS, ...IXP_DIAGNOSTICS },
|
|
7
8
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DiagnosticCatalog } from '../../../core/nodes/family.js';
|
|
2
|
+
import type { FlowVersionedNodeType } from '../../../flow-node-type.js';
|
|
3
|
+
import { type IxpExtractInputs } from './spec.js';
|
|
4
|
+
export type IxpExtractSpec = {
|
|
5
|
+
kind: 'ixpExtract';
|
|
6
|
+
inputs: IxpExtractInputs;
|
|
7
|
+
};
|
|
8
|
+
export declare const IXP_DIAGNOSTICS: DiagnosticCatalog;
|
|
9
|
+
/**
|
|
10
|
+
* Build the definition for one published IxP project.
|
|
11
|
+
*
|
|
12
|
+
* The per-project half is derived, not asked for twice: the node type's tail
|
|
13
|
+
* packs the project VERSION id (the binding's `resourceKey`, and the model
|
|
14
|
+
* blob's `id`) and the FOLDER key, so the author passes the type once and those
|
|
15
|
+
* two come out of it.
|
|
16
|
+
*
|
|
17
|
+
* `inputDefaults` matters more here than in the other per-instance families,
|
|
18
|
+
* because the platform's own instance IS its defaults: measured on today's 1.0
|
|
19
|
+
* nightly artifact, `node.inputs` equals `inputDefaults` verbatim with exactly
|
|
20
|
+
* two edits — `fileRef` authored, and `model.modelName` forced non-empty (the
|
|
21
|
+
* registry returns `null` for published/OOB deployments, and both `flow
|
|
22
|
+
* validate` and Studio Web's property panel refuse that). So the emitter writes
|
|
23
|
+
* the defaults and the node reads them back, rather than the two drifting.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ixpExtractDef(spec: {
|
|
26
|
+
project: string;
|
|
27
|
+
modelName: string;
|
|
28
|
+
name: string;
|
|
29
|
+
folderName: string;
|
|
30
|
+
folderPath?: string;
|
|
31
|
+
versionTag?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
}): any;
|
|
34
|
+
export declare const ixpExtractNodeType: FlowVersionedNodeType<IxpExtractSpec>;
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An IxP project's extractor — `ixpExtract()`, a node type per published
|
|
3
|
+
* project: `uipath.ixp.<projectSlug>.<projectVersionId>-<folderKey>`. The type
|
|
4
|
+
* owns every name under `uipath.ixp.` whose tail is that pair of uuids
|
|
5
|
+
* (`typeNameMatches`), which keeps Dynamic Extract's exact
|
|
6
|
+
* `uipath.ixp.extract-document-builder` and the two older, unresolvable tail
|
|
7
|
+
* spellings out of it. Per-tenant: the definition is one bundled template plus
|
|
8
|
+
* the project's identity. Moved from serialize.ts / check.ts / decompile.ts in
|
|
9
|
+
* Phase 4 of docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; serialized output, printed
|
|
10
|
+
* source, codes and messages unchanged.
|
|
11
|
+
*/
|
|
12
|
+
import ixpDefinition from '../../../core/ixp-definition.json' with { type: 'json' };
|
|
13
|
+
import { definitionHasErrorPort } from '../../../core/error-port.js';
|
|
14
|
+
import { Expr } from '../../../core/expr.js';
|
|
15
|
+
import { jsonClone } from '../../../core/json-clone.js';
|
|
16
|
+
import { parseIxpProjectType } from './spec.js';
|
|
17
|
+
const IXP_PREFIX = 'uipath.ixp.';
|
|
18
|
+
export const IXP_DIAGNOSTICS = {
|
|
19
|
+
IXP_FORBIDDEN_INPUT: { severity: 'error', summary: 'an IxP step setting an input the current node does not declare' },
|
|
20
|
+
IXP_NO_FILE: { severity: 'error', summary: 'an IxP step with no fileRef' },
|
|
21
|
+
IXP_NO_FOLDER: { severity: 'error', summary: 'an IxP step with no folderName' },
|
|
22
|
+
IXP_NO_MODEL_NAME: { severity: 'error', summary: 'an IxP step with no modelName' },
|
|
23
|
+
IXP_NO_NAME: { severity: 'error', summary: 'an IxP step with no name' },
|
|
24
|
+
IXP_NO_PROJECT: { severity: 'error', summary: 'an IxP step with no project' },
|
|
25
|
+
IXP_PROJECT_NOT_NODE_TYPE: { severity: 'error', summary: 'an IxP project that is not a uipath.ixp.* node type' },
|
|
26
|
+
IXP_PROJECT_TAIL_UNPARSEABLE: { severity: 'error', summary: "an IxP project whose tail is not the version id and folder key" },
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The parts every `uipath.ixp.*` definition shares, bundled verbatim from
|
|
30
|
+
* `uip maestro flow registry get` (2026-07-31, after `registry pull --force`).
|
|
31
|
+
*
|
|
32
|
+
* Unlike rpa's and api-workflow's, an extractor's definition is MOSTLY generic:
|
|
33
|
+
* three projects' definitions (`invoiceixp`, `idp-benchmark---invoices`,
|
|
34
|
+
* `invoices-billing`) are byte-identical in `category`, `tags`, `sortOrder`,
|
|
35
|
+
* `version`, `supportsErrorHandling`, `handleConfiguration`, `form`, `debug`,
|
|
36
|
+
* `inputDefinition` and the 18.7 KB `outputDefinition` — and differ in exactly
|
|
37
|
+
* five per-project places (`nodeType`, `description`, `display.label`,
|
|
38
|
+
* `model.bindings`, `inputDefaults`), all of which `ixpExtractDef` builds from
|
|
39
|
+
* the author's call. So the family costs one bundled template, not 13.
|
|
40
|
+
*
|
|
41
|
+
* **The one thing to know before re-recording it:** `registry get` answers from
|
|
42
|
+
* a LOCAL CACHE (`~/.uipath/nodes/index.json`). Without
|
|
43
|
+
* `uip maestro flow registry pull --force` first you get whenever you last
|
|
44
|
+
* synced — which is how a stale copy of this file would claim `required:
|
|
45
|
+
* ["fileRef"]` (the pre-pull answer) instead of the three the platform's
|
|
46
|
+
* `ixp-node` validator actually enforces.
|
|
47
|
+
*/
|
|
48
|
+
const IXP_DEF = ixpDefinition;
|
|
49
|
+
/**
|
|
50
|
+
* Build the definition for one published IxP project.
|
|
51
|
+
*
|
|
52
|
+
* The per-project half is derived, not asked for twice: the node type's tail
|
|
53
|
+
* packs the project VERSION id (the binding's `resourceKey`, and the model
|
|
54
|
+
* blob's `id`) and the FOLDER key, so the author passes the type once and those
|
|
55
|
+
* two come out of it.
|
|
56
|
+
*
|
|
57
|
+
* `inputDefaults` matters more here than in the other per-instance families,
|
|
58
|
+
* because the platform's own instance IS its defaults: measured on today's 1.0
|
|
59
|
+
* nightly artifact, `node.inputs` equals `inputDefaults` verbatim with exactly
|
|
60
|
+
* two edits — `fileRef` authored, and `model.modelName` forced non-empty (the
|
|
61
|
+
* registry returns `null` for published/OOB deployments, and both `flow
|
|
62
|
+
* validate` and Studio Web's property panel refuse that). So the emitter writes
|
|
63
|
+
* the defaults and the node reads them back, rather than the two drifting.
|
|
64
|
+
*/
|
|
65
|
+
export function ixpExtractDef(spec) {
|
|
66
|
+
const parts = parseIxpProjectType(spec.project);
|
|
67
|
+
if (!parts) {
|
|
68
|
+
throw new Error(`ixpExtract: "${spec.project}" is not a resolvable extractor node type. It must be `
|
|
69
|
+
+ `uipath.ixp.<slug>.<projectVersionId>-<folderKey> — copy it from `
|
|
70
|
+
+ `\`uip maestro flow registry pull --force && uip maestro flow registry search -f "type:startsWith=uipath.ixp"\`.`);
|
|
71
|
+
}
|
|
72
|
+
const { versionId, folderKey } = parts;
|
|
73
|
+
const model = {
|
|
74
|
+
id: versionId,
|
|
75
|
+
// Authoring rule #1 in v1's own IxP reference: non-empty or the `ixp-node`
|
|
76
|
+
// validator fails the flow and the designer's panel crashes destructuring it.
|
|
77
|
+
modelName: spec.modelName,
|
|
78
|
+
modelDisplayName: spec.name,
|
|
79
|
+
folderKey,
|
|
80
|
+
folderName: spec.folderName,
|
|
81
|
+
// The folder's ancestor-key chain for a nested folder; the folder key alone
|
|
82
|
+
// for one published straight into a root folder, which is what the platform
|
|
83
|
+
// writes there.
|
|
84
|
+
folderPath: spec.folderPath ?? folderKey,
|
|
85
|
+
description: '',
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
nodeType: spec.project,
|
|
89
|
+
version: IXP_DEF.version,
|
|
90
|
+
category: IXP_DEF.category,
|
|
91
|
+
tags: jsonClone(IXP_DEF.tags),
|
|
92
|
+
// The platform's own definition describes an extractor by its FOLDER, and
|
|
93
|
+
// the designer prints exactly this in the node's subtitle.
|
|
94
|
+
description: `(${spec.folderName})`,
|
|
95
|
+
sortOrder: IXP_DEF.sortOrder,
|
|
96
|
+
supportsErrorHandling: IXP_DEF.supportsErrorHandling,
|
|
97
|
+
display: {
|
|
98
|
+
canvasLabel: IXP_DEF.canvasLabel,
|
|
99
|
+
label: spec.name,
|
|
100
|
+
description: `(${spec.folderName})`,
|
|
101
|
+
icon: IXP_DEF.icon,
|
|
102
|
+
},
|
|
103
|
+
handleConfiguration: jsonClone(IXP_DEF.handleConfiguration),
|
|
104
|
+
model: {
|
|
105
|
+
...jsonClone(IXP_DEF.model),
|
|
106
|
+
bindings: {
|
|
107
|
+
...jsonClone(IXP_DEF.bindingsFrame),
|
|
108
|
+
resourceKey: versionId,
|
|
109
|
+
name: spec.name,
|
|
110
|
+
values: [
|
|
111
|
+
{ name: 'name', propertyAttribute: 'name', default: spec.name },
|
|
112
|
+
{ name: 'folderPath', propertyAttribute: 'folderPath', default: spec.folderName },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
form: jsonClone(IXP_DEF.form),
|
|
117
|
+
// A `description` the author asked for is DECLARED in the same write. The
|
|
118
|
+
// registry's own copy carries `description` in `inputDefaults` and not in
|
|
119
|
+
// `inputDefinition.properties` — lesson 42's gap, live on this family — so
|
|
120
|
+
// emitting the input without this would flag our own output as setting an
|
|
121
|
+
// undeclared input, which
|
|
122
|
+
// is what v1's deployed artifacts do (measured: 1 warning on today's green
|
|
123
|
+
// nightly file). The default is to emit no `description` at all, which is
|
|
124
|
+
// why this is conditional and the definition otherwise stays byte-identical
|
|
125
|
+
// to the platform's.
|
|
126
|
+
inputDefinition: spec.description === undefined
|
|
127
|
+
? jsonClone(IXP_DEF.inputDefinition)
|
|
128
|
+
: {
|
|
129
|
+
...jsonClone(IXP_DEF.inputDefinition),
|
|
130
|
+
properties: { ...jsonClone(IXP_DEF.inputDefinition.properties), description: { type: 'string' } },
|
|
131
|
+
},
|
|
132
|
+
inputDefaults: {
|
|
133
|
+
model,
|
|
134
|
+
modelName: spec.modelName,
|
|
135
|
+
...(spec.description === undefined ? {} : { description: spec.description }),
|
|
136
|
+
// `modelName` and `projectName` are ONE value in two fields: all three
|
|
137
|
+
// measured definitions agree, and the designer shows it in two disabled
|
|
138
|
+
// boxes (the IXP-model and Folder rows of the `ixp-model` form section).
|
|
139
|
+
projectName: spec.modelName,
|
|
140
|
+
versionTag: spec.versionTag ?? '',
|
|
141
|
+
folderKey,
|
|
142
|
+
folderName: spec.folderName,
|
|
143
|
+
fileRef: '',
|
|
144
|
+
pageRange: '',
|
|
145
|
+
},
|
|
146
|
+
outputDefinition: jsonClone(IXP_DEF.outputDefinition),
|
|
147
|
+
debug: jsonClone(IXP_DEF.debug),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The `ixpDeployment` binding pair for one IxP project — the THIRD resource kind
|
|
152
|
+
* this SDK emits, after `process` and `queue`.
|
|
153
|
+
*
|
|
154
|
+
* Same shape and same one-pair-per-resource rule as
|
|
155
|
+
* {@link processResourceBindings}: keyed by `resource + resourceKey +
|
|
156
|
+
* propertyAttribute`, so two steps extracting with the same project share one
|
|
157
|
+
* pair rather than shadowing each other. `resourceKey` is the project VERSION
|
|
158
|
+
* id, which is what the definition's `model.bindings.resourceKey` names.
|
|
159
|
+
*
|
|
160
|
+
* Measured on 48 of the 151 deployed IxP artifacts (and 3 of today's 4 green
|
|
161
|
+
* ones): exactly these two entries, `name` defaulting to the deployment's
|
|
162
|
+
* display name and `folderPath` to its folder — note `folderPath` here holds the
|
|
163
|
+
* folder NAME, not the key chain that `inputs.model.folderPath` carries.
|
|
164
|
+
*/
|
|
165
|
+
function ixpDeploymentBindings(spec, existing) {
|
|
166
|
+
const found = (attr) => [...existing.values()].find((b) => b.resource === 'ixpDeployment' && b.resourceKey === spec.versionId && b.propertyAttribute === attr)?.id;
|
|
167
|
+
const already = { name: found('name'), folderPath: found('folderPath') };
|
|
168
|
+
if (already.name && already.folderPath) {
|
|
169
|
+
return { ids: { name: already.name, folderPath: already.folderPath }, entries: [] };
|
|
170
|
+
}
|
|
171
|
+
const slug = spec.name.replace(/[^A-Za-z0-9]/g, '') || 'Ixp';
|
|
172
|
+
let base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}`;
|
|
173
|
+
for (let n = 2; existing.has(`${base}Name`) || existing.has(`${base}FolderPath`); n++) {
|
|
174
|
+
base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}${n}`;
|
|
175
|
+
}
|
|
176
|
+
const ids = { name: `${base}Name`, folderPath: `${base}FolderPath` };
|
|
177
|
+
return { ids, entries: [
|
|
178
|
+
{
|
|
179
|
+
id: ids.name,
|
|
180
|
+
name: 'name',
|
|
181
|
+
type: 'string',
|
|
182
|
+
resource: 'ixpDeployment',
|
|
183
|
+
resourceKey: spec.versionId,
|
|
184
|
+
default: spec.name,
|
|
185
|
+
propertyAttribute: 'name',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: ids.folderPath,
|
|
189
|
+
name: 'folderPath',
|
|
190
|
+
type: 'string',
|
|
191
|
+
resource: 'ixpDeployment',
|
|
192
|
+
resourceKey: spec.versionId,
|
|
193
|
+
default: spec.folderName,
|
|
194
|
+
propertyAttribute: 'folderPath',
|
|
195
|
+
},
|
|
196
|
+
] };
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* The five forbidden inputs on an IxP node. They belonged to a prior schema and
|
|
200
|
+
* v1's own authoring reference calls them "the most common training-data-recall
|
|
201
|
+
* mistake" — the current node declares none of them, so each is an input the
|
|
202
|
+
* definition does not have.
|
|
203
|
+
*
|
|
204
|
+
* They are not reachable through `ixpExtract()`'s typed surface, which is the
|
|
205
|
+
* point of having one; this check exists for the JS caller and for a
|
|
206
|
+
* hand-written spec object, where TypeScript is not in the way.
|
|
207
|
+
*/
|
|
208
|
+
const IXP_FORBIDDEN_INPUTS = ['digitizationMode', 'documentTaxonomy', 'attachmentId', 'fileName', 'mimeType'];
|
|
209
|
+
/**
|
|
210
|
+
* `ixpExtract(...)`-specific checks.
|
|
211
|
+
*
|
|
212
|
+
* All four identity fields are load-bearing on the tenant and none of them can
|
|
213
|
+
* be derived, so a missing one is an error rather than a warning: the node type
|
|
214
|
+
* is per-project, and the definition's own `inputDefinition` marks `modelName`
|
|
215
|
+
* and `folderKey` required (`minLength: 1`) beside `fileRef` — which is why
|
|
216
|
+
* `flow validate`'s `ixp-node` validator rejects a node with an empty one.
|
|
217
|
+
*/
|
|
218
|
+
function checkIxpExtract(inputs, ctx) {
|
|
219
|
+
const step = ctx.step;
|
|
220
|
+
const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
|
|
221
|
+
const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
|
|
222
|
+
const project = String(inputs?.project ?? '').trim();
|
|
223
|
+
if (blank(project)) {
|
|
224
|
+
at('IXP_NO_PROJECT', `IxP step "${step}" has no \`project\`. That is the extractor's whole node type, and it carries the `
|
|
225
|
+
+ `project version id and folder key the platform resolves the deployment by. Copy it from the registry `
|
|
226
|
+
+ `— never construct it: three different spellings of its tail exist in deployed flows.`, `project: 'uipath.ixp.<slug>.<projectVersionId>-<folderKey>' // uip maestro flow registry pull --force && uip maestro flow registry search -f "type:startsWith=uipath.ixp"`);
|
|
227
|
+
}
|
|
228
|
+
else if (!project.startsWith('uipath.ixp.')) {
|
|
229
|
+
at('IXP_PROJECT_NOT_NODE_TYPE', `IxP step "${step}"'s \`project\` is ${JSON.stringify(inputs.project)}, which is not a \`uipath.ixp.*\` node `
|
|
230
|
+
+ `type. This field takes the type verbatim from \`registry search\`, not the project's name or id — the `
|
|
231
|
+
+ `name goes in \`name\` and the slug in \`modelName\`.`);
|
|
232
|
+
}
|
|
233
|
+
else if (parseIxpProjectType(project) === undefined) {
|
|
234
|
+
at('IXP_PROJECT_TAIL_UNPARSEABLE', `IxP step "${step}"'s \`project\` ends in ${JSON.stringify(project.slice(project.lastIndexOf('.') + 1))}, which is `
|
|
235
|
+
+ `not the \`<projectVersionId>-<folderKey>\` pair of uuids the current registry serves. Two older spellings `
|
|
236
|
+
+ `exist in the corpus — a sanitized folder path, and the literal string "undefined" — and neither resolves `
|
|
237
|
+
+ `today. Re-read the type from \`registry search\` after \`registry pull --force\`.`);
|
|
238
|
+
}
|
|
239
|
+
if (blank(inputs?.modelName)) {
|
|
240
|
+
at('IXP_NO_MODEL_NAME', `IxP step "${step}" has no \`modelName\`. The definition marks it required (\`minLength: 1\`) and the `
|
|
241
|
+
+ `platform's \`ixp-node\` validator refuses an empty one — it is the project's own name as the tenant `
|
|
242
|
+
+ `spells it, which the node type's slug has lower-cased and de-punctuated beyond recovery.`, `modelName: 'invoiceixp-cef0d447-ixp' // registry get → inputDefaults.modelName`);
|
|
243
|
+
}
|
|
244
|
+
if (blank(inputs?.name)) {
|
|
245
|
+
at('IXP_NO_NAME', `IxP step "${step}" has no \`name\`. It is the deployment's display name — the \`ixpDeployment\` binding's `
|
|
246
|
+
+ `\`name\`, which is how the deployed flow finds the project.`, `name: 'InvoiceIXP' // registry get → inputDefaults.model.modelDisplayName`);
|
|
247
|
+
}
|
|
248
|
+
if (blank(inputs?.folderName)) {
|
|
249
|
+
at('IXP_NO_FOLDER', `IxP step "${step}" has no \`folderName\`. IxP deployments are folder-scoped, and this is the binding's `
|
|
250
|
+
+ `\`folderPath\` default — the folder the runtime looks in.`, `folderName: 'Shared' // registry get → inputDefaults.folderName`);
|
|
251
|
+
}
|
|
252
|
+
const fileRef = inputs?.fileRef;
|
|
253
|
+
if (fileRef === undefined || fileRef === null
|
|
254
|
+
|| (typeof fileRef === 'string' && fileRef.trim() === '')) {
|
|
255
|
+
at('IXP_NO_FILE', `IxP step "${step}" has no \`fileRef\`. It is the node's only required INPUT (\`inputDefinition.required\`) `
|
|
256
|
+
+ `and it takes a whole Flow Attachment object — normally a \`file\`-typed flow input.`, `fileRef: out('start', 'invoiceFile') // .input({ invoiceFile: types.file })`);
|
|
257
|
+
}
|
|
258
|
+
for (const f of IXP_FORBIDDEN_INPUTS) {
|
|
259
|
+
if (inputs?.[f] !== undefined) {
|
|
260
|
+
at('IXP_FORBIDDEN_INPUT', `IxP step "${step}" sets \`${f}\`, which the current node does not declare — it belonged to a prior `
|
|
261
|
+
+ `schema. ${f === 'documentTaxonomy' ? 'The taxonomy travels in the resolved `model` blob.'
|
|
262
|
+
: f === 'digitizationMode' ? 'The serializer defaults it to `fileUpload` internally.'
|
|
263
|
+
: 'It is derived from `fileRef` upstream.'}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The extractor's node: the definition's `inputDefaults` with the file the
|
|
269
|
+
* author named, and the fixed output pair.
|
|
270
|
+
*/
|
|
271
|
+
function serializeIxpExtract(s, ctx) {
|
|
272
|
+
const def = ixpExtractDef(s); // throws on an unparseable project
|
|
273
|
+
const { versionId } = parseIxpProjectType(s.project); // ixpExtractDef threw if it could not parse
|
|
274
|
+
// The bindings first: two steps on the same project share ONE pair, and the
|
|
275
|
+
// definition's `model.bindings.resourceKey` names the key they hang off.
|
|
276
|
+
const { entries } = ixpDeploymentBindings({ name: s.name, folderName: s.folderName, versionId }, ctx.bindings);
|
|
277
|
+
for (const b of entries)
|
|
278
|
+
ctx.bind(b);
|
|
279
|
+
return {
|
|
280
|
+
definition: { source: 'snapshot', nodeType: def.nodeType, version: def.version, manifest: def },
|
|
281
|
+
// No node-level `model` — v1's authoring reference makes this rule #15 for
|
|
282
|
+
// this family ("Studio Web–authored .flow files never carry one"), and 151
|
|
283
|
+
// of 151 deployed instances agree. Same reason the rpa/pattern families
|
|
284
|
+
// delete theirs.
|
|
285
|
+
model: 'omit',
|
|
286
|
+
// Every extractor's definition declares `success` (+ `error`, gated on
|
|
287
|
+
// `errorHandlingEnabled`) on the right — so the forward edge is `success`,
|
|
288
|
+
// as it is in all 151 deployed IxP instances.
|
|
289
|
+
port: 'success',
|
|
290
|
+
// The instance IS the definition's defaults, with the file the author named.
|
|
291
|
+
// Every field the designer writes, in the platform's own order.
|
|
292
|
+
inputs: {
|
|
293
|
+
...jsonClone(def.inputDefaults),
|
|
294
|
+
fileRef: ctx.render(s.fileRef),
|
|
295
|
+
...(s.pageRange === undefined ? {} : { pageRange: s.pageRange }),
|
|
296
|
+
},
|
|
297
|
+
// The instance's `outputs` is a FIXED four-field pair, not a copy of the
|
|
298
|
+
// definition's `outputDefinition` — which would drag the 18.7 KB extraction
|
|
299
|
+
// schema onto the node. v1's authoring reference is explicit ("copying
|
|
300
|
+
// outputDefinition verbatim also validates, but drags in a schema blob the
|
|
301
|
+
// runtime does not need"), and today's green nightly artifact carries
|
|
302
|
+
// exactly this. Both halves are REQUIRED: `flow validate`'s `ixp-node`
|
|
303
|
+
// validator fails a node missing either one.
|
|
304
|
+
outputs: {
|
|
305
|
+
output: { name: 'output', type: 'object', source: '=this', var: 'output' },
|
|
306
|
+
error: {
|
|
307
|
+
type: 'object',
|
|
308
|
+
description: 'Error information if the node fails',
|
|
309
|
+
source: '=Error',
|
|
310
|
+
var: 'error',
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
replaceOutputs: true,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function liftIxpExtract(node, ctx) {
|
|
317
|
+
const n = node;
|
|
318
|
+
const i = (n.inputs ?? {});
|
|
319
|
+
const plain = (k) => {
|
|
320
|
+
const v = ctx.sourceText(i[k]);
|
|
321
|
+
return v === undefined || v === '' ? undefined : v;
|
|
322
|
+
};
|
|
323
|
+
const fr = ctx.sourceText(i.fileRef) ?? '';
|
|
324
|
+
// The DEPLOYMENT's display name is nested in `inputs.model`, not at the top
|
|
325
|
+
// of `inputs` — the two are different values (the model block's
|
|
326
|
+
// `modelDisplayName` is 'InvoiceIXP'; `modelName` is the slug), so reading
|
|
327
|
+
// the wrong one recovers the slug as the name.
|
|
328
|
+
const model = (i.model ?? {});
|
|
329
|
+
const nested = (k) => typeof model[k] === 'string' && model[k] !== '' ? model[k] : undefined;
|
|
330
|
+
const lifted = {
|
|
331
|
+
project: n.type,
|
|
332
|
+
// `modelName`/`projectName` are one value in two fields; either recovers it.
|
|
333
|
+
modelName: plain('modelName') ?? plain('projectName') ?? nested('modelName') ?? '',
|
|
334
|
+
name: nested('modelDisplayName') ?? plain('modelName') ?? '',
|
|
335
|
+
folderName: plain('folderName') ?? nested('folderName') ?? '',
|
|
336
|
+
fileRef: fr.startsWith('=js:') ? new Expr(fr.slice(4)) : fr,
|
|
337
|
+
pageRange: plain('pageRange'),
|
|
338
|
+
versionTag: plain('versionTag'),
|
|
339
|
+
description: plain('description') ?? nested('description'),
|
|
340
|
+
};
|
|
341
|
+
return { kind: 'ixpExtract', inputs: lifted };
|
|
342
|
+
}
|
|
343
|
+
function printIxpExtract(spec, ctx) {
|
|
344
|
+
const l = spec.inputs;
|
|
345
|
+
const optional = (v) => (v === undefined ? undefined : ctx.string(v));
|
|
346
|
+
return {
|
|
347
|
+
factory: 'ixpExtract',
|
|
348
|
+
options: [
|
|
349
|
+
['project', ctx.string(l.project)],
|
|
350
|
+
['modelName', ctx.string(l.modelName)],
|
|
351
|
+
['name', ctx.string(l.name)],
|
|
352
|
+
['folderName', ctx.string(l.folderName)],
|
|
353
|
+
['fileRef', ctx.value(l.fileRef)],
|
|
354
|
+
['pageRange', optional(l.pageRange)],
|
|
355
|
+
['versionTag', optional(l.versionTag)],
|
|
356
|
+
['description', optional(l.description)],
|
|
357
|
+
],
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const ixpExtract10 = {
|
|
361
|
+
kind: 'ixpExtract', typeName: `${IXP_PREFIX}*`, version: IXP_DEF.version, factory: 'ixpExtract',
|
|
362
|
+
definition: 'per-tenant',
|
|
363
|
+
// The definition this SDK builds is the one serialize emits; asking it keeps
|
|
364
|
+
// the two in step (#1040). A project the builder cannot parse has no
|
|
365
|
+
// definition to ask, and the family's own rules report it (IXP_PROJECT_*).
|
|
366
|
+
errorPort: (spec) => {
|
|
367
|
+
try {
|
|
368
|
+
return definitionHasErrorPort(ixpExtractDef(spec.inputs));
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
serialize: (spec, ctx) => serializeIxpExtract(spec.inputs, ctx),
|
|
375
|
+
deserialize: (node, ctx) => liftIxpExtract(node, ctx),
|
|
376
|
+
print: (spec, ctx) => printIxpExtract(spec, ctx),
|
|
377
|
+
check: (spec, ctx) => checkIxpExtract(spec.inputs, ctx),
|
|
378
|
+
// check collected no expressions for an IxP step before the port; unchanged by it.
|
|
379
|
+
expressions: () => [],
|
|
380
|
+
output: () => ({ kind: 'opaque' }),
|
|
381
|
+
};
|
|
382
|
+
export const ixpExtractNodeType = {
|
|
383
|
+
kind: 'ixpExtract', typeName: `${IXP_PREFIX}*`, typeNamePrefix: IXP_PREFIX, factory: 'ixpExtract',
|
|
384
|
+
// An extractor is a tail of two uuids; Dynamic Extract's
|
|
385
|
+
// `uipath.ixp.extract-document-builder` and the older tails are not.
|
|
386
|
+
typeNameMatches: (typeName) => parseIxpProjectType(typeName) !== undefined,
|
|
387
|
+
defaultVersion: ixpExtract10.version,
|
|
388
|
+
versions: { [ixpExtract10.version]: ixpExtract10 },
|
|
389
|
+
};
|
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; serialized output, printed source,
|
|
6
6
|
* codes and messages unchanged.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* from the model rather than being one fixed name, which the contract does not
|
|
10
|
-
* express yet.
|
|
8
|
+
* `ixpExtract()` (`uipath.ixp.<slug>.<versionId>-<folderKey>`) is in `ixp.ts`.
|
|
11
9
|
*/
|
|
12
10
|
import { toExpr } from '../../../core/expr.js';
|
|
13
11
|
import { jsonClone } from '../../../core/json-clone.js';
|