@uipath/packager-tool-flow 1.203.0-preview.212 → 1.204.0-preview.211
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/flow-io.d.ts +49 -0
- package/dist/flow-tool.d.ts +30 -1
- package/dist/flow-validation-hook.d.ts +46 -0
- package/dist/flow-validation.d.ts +135 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +440 -11
- package/dist/inline-agent-utils.d.ts +9 -0
- package/package.json +6 -5
package/dist/flow-io.d.ts
CHANGED
|
@@ -42,6 +42,15 @@ export declare function resolveWorkflowRefs(parsed: unknown, resolver: ResolveRe
|
|
|
42
42
|
resolved: unknown;
|
|
43
43
|
refMap: RefMap;
|
|
44
44
|
}>;
|
|
45
|
+
/**
|
|
46
|
+
* A `.flow`'s text with its `$ref` chunks inlined — the document the build
|
|
47
|
+
* packs — for a validator that parses text. The schema parser strips an
|
|
48
|
+
* unresolved `$ref`, so a chunked subflow fails the parse and a chunked
|
|
49
|
+
* variable set silently disappears. Text with no `$ref` comes back as it
|
|
50
|
+
* was, and so does text that is not JSON, for the validator to report.
|
|
51
|
+
* Throws as {@link resolveWorkflowRefs} does.
|
|
52
|
+
*/
|
|
53
|
+
export declare function inlineFlowRefs(fs: IFileSystem, content: string, filePath: string, logger?: FlowIoLogger): Promise<string>;
|
|
45
54
|
/**
|
|
46
55
|
* The full read pipeline for a parsed `.flow` document: resolve `$ref`
|
|
47
56
|
* chunks, project to the fleet write floor, convert to the in-memory
|
|
@@ -75,3 +84,43 @@ export interface ReadFlowWorkflowOptions {
|
|
|
75
84
|
* otherwise the process-wide `getFileSystem()` is used.
|
|
76
85
|
*/
|
|
77
86
|
export declare function readFlowWorkflow(filePath: string, options?: ReadFlowWorkflowOptions): Promise<Workflow>;
|
|
87
|
+
/**
|
|
88
|
+
* Resolve the `.flow` a project runs: `MainFile` first, then `<Name>.flow`,
|
|
89
|
+
* then any `.flow` in the project. One resolver so the pack gate, the debug
|
|
90
|
+
* run, the conversational pre-flight and local eval preparation all read the
|
|
91
|
+
* same file. An unreadable `project.uiproj` degrades to the plain search
|
|
92
|
+
* rather than failing.
|
|
93
|
+
*
|
|
94
|
+
* Takes the file system as a parameter: the packager hands in the one it was
|
|
95
|
+
* constructed with, which in a browser host is not the Node one.
|
|
96
|
+
*/
|
|
97
|
+
export declare function resolveProjectFlowFile(fs: IFileSystem, projectPath: string): Promise<string>;
|
|
98
|
+
/**
|
|
99
|
+
* {@link resolveProjectFlowFile}, answering `undefined` for a project that
|
|
100
|
+
* has no `.flow` instead of throwing. See {@link tryFindProjectFlowFile} for
|
|
101
|
+
* why the two answers stay apart.
|
|
102
|
+
*/
|
|
103
|
+
export declare function tryResolveProjectFlowFile(fs: IFileSystem, projectPath: string): Promise<string | undefined>;
|
|
104
|
+
/**
|
|
105
|
+
* Locate a flow project's `.flow` source file. Prefers the `project.uiproj`
|
|
106
|
+
* `MainFile` when it names a `.flow` inside the project, then the first
|
|
107
|
+
* `.flow` found in the project root, `content/`, or `flow_files/`.
|
|
108
|
+
*
|
|
109
|
+
* Studio Web-exported solutions ship the flow as `new.flow` (not
|
|
110
|
+
* `<ProjectName>.flow`) with `MainFile: null`, so callers must NOT assume the
|
|
111
|
+
* file is named after the project. When a directory holds multiple `.flow`s,
|
|
112
|
+
* pass `preferredBaseName` (the project name) to disambiguate to
|
|
113
|
+
* `<preferredBaseName>.flow` deterministically. Throws when no `.flow` is found.
|
|
114
|
+
*/
|
|
115
|
+
export declare function findProjectFlowFile(fs: IFileSystem, projectPath: string, mainFile?: string | null, preferredBaseName?: string): Promise<string>;
|
|
116
|
+
/**
|
|
117
|
+
* {@link findProjectFlowFile}, except that a project with no `.flow` answers
|
|
118
|
+
* `undefined` rather than throwing.
|
|
119
|
+
*
|
|
120
|
+
* For a caller to whom "this project has none" is an ordinary outcome — a
|
|
121
|
+
* Lite or Maestro Automate project packs fine without one. Reading the
|
|
122
|
+
* directory can still throw (EACCES, EIO); that is not the same answer and
|
|
123
|
+
* must not be flattened into it, or an unreadable project reads as an empty
|
|
124
|
+
* one.
|
|
125
|
+
*/
|
|
126
|
+
export declare function tryFindProjectFlowFile(fs: IFileSystem, projectPath: string, mainFile?: string | null, preferredBaseName?: string): Promise<string | undefined>;
|
package/dist/flow-tool.d.ts
CHANGED
|
@@ -7,7 +7,36 @@ export declare class FlowTool extends ProjectTool {
|
|
|
7
7
|
private readonly _temporaryStorage;
|
|
8
8
|
constructor(fileSystem: IFileSystem, logger: IToolLogger);
|
|
9
9
|
restoreAsync(_options: IProjectRestoreOptions, _cancellationToken?: AbortSignal): Promise<ToolResult>;
|
|
10
|
-
validateAsync(
|
|
10
|
+
validateAsync(options: IProjectValidateOptions, _cancellationToken?: AbortSignal): Promise<ToolResult>;
|
|
11
|
+
/**
|
|
12
|
+
* Validate the project's `.flow` with the designer's rules, and turn what
|
|
13
|
+
* they found into a result the solution packager can put on the wire.
|
|
14
|
+
*
|
|
15
|
+
* Every issue becomes a finding, warnings included — Studio renders the
|
|
16
|
+
* severity, and a warning row next to the error that blocked the publish
|
|
17
|
+
* is worth more than the error alone. Only errors decide the verdict.
|
|
18
|
+
*
|
|
19
|
+
* The rules are the designer's own (`validateFlowSource`), so a flow that
|
|
20
|
+
* packs here is one the designer draws clean — in every host, Studio Web
|
|
21
|
+
* included, because nothing has to be supplied from outside. The CLI's
|
|
22
|
+
* extra authoring checks stay in `uip maestro flow validate`.
|
|
23
|
+
*
|
|
24
|
+
* Two ways this ends without a verdict, both building as before: no
|
|
25
|
+
* `.flow` in the project (a Lite or Maestro Automate project, which is
|
|
26
|
+
* ordinary, hence info not warn); and a `.flow` that cannot be read,
|
|
27
|
+
* `$ref` chunks that cannot be resolved (a cycle), or a rule that
|
|
28
|
+
* throws — an infrastructure failure rather than a verdict on
|
|
29
|
+
* the flow, logged and continued, because refusing to pack over it would
|
|
30
|
+
* break packs that work today.
|
|
31
|
+
*/
|
|
32
|
+
private runValidation;
|
|
33
|
+
/**
|
|
34
|
+
* `flowFilePath` as the finding contract wants it — relative to the
|
|
35
|
+
* project, forward slashes. Studio refuses to navigate a path that
|
|
36
|
+
* escapes the project directory, so anything that would is reduced to the
|
|
37
|
+
* bare file name, which still names the file in the Error List.
|
|
38
|
+
*/
|
|
39
|
+
private toProjectRelative;
|
|
11
40
|
buildAsync(options: IProjectBuildOptions, _cancellationToken?: AbortSignal): Promise<ToolResult>;
|
|
12
41
|
packAsync(options: IProjectPackOptions, cancellationToken?: AbortSignal): Promise<ToolResult>;
|
|
13
42
|
dispose(): Promise<void>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of a Flow validation issue, and the two helpers that turn one
|
|
3
|
+
* into a finding Studio can navigate: `splitIssueMessage` peels the rule code
|
|
4
|
+
* off the text, `nodeIdFromIssuePath` finds the node the issue is about.
|
|
5
|
+
*
|
|
6
|
+
* Issues come from `validateFlowSource` (the pack gate) and from flow-tool's
|
|
7
|
+
* `FlowValidateService`, whose `ValidationIssue` has the same shape.
|
|
8
|
+
*/
|
|
9
|
+
export type FlowValidationSeverity = "error" | "warning";
|
|
10
|
+
/** One problem found in a `.flow`. */
|
|
11
|
+
export interface FlowValidationIssue {
|
|
12
|
+
/**
|
|
13
|
+
* Dot path into the flow — `nodes[<node-id>].inputs.<field>`, or
|
|
14
|
+
* `subflows[<subflow-id>].nodes[<node-id>]…` inside a subflow.
|
|
15
|
+
*/
|
|
16
|
+
path: string;
|
|
17
|
+
/**
|
|
18
|
+
* Human-readable text. Rules that carry a code prefix it as `[CODE] `;
|
|
19
|
+
* {@link splitIssueMessage} peels that back off for the finding contract.
|
|
20
|
+
*/
|
|
21
|
+
message: string;
|
|
22
|
+
severity: FlowValidationSeverity;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Split `[CODE] text` into its parts, so the finding contract can carry the
|
|
26
|
+
* code in `ErrorCode` and leave `Description` readable. Rules that emit no
|
|
27
|
+
* code (the per-node validators) come back with `code: undefined` and the
|
|
28
|
+
* message untouched.
|
|
29
|
+
*/
|
|
30
|
+
export declare function splitIssueMessage(message: string): {
|
|
31
|
+
code?: string;
|
|
32
|
+
description: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The node a dot path points at, or `undefined` for a workflow-scoped issue.
|
|
36
|
+
* The last `nodes[…]` segment wins, so a subflow-prefixed path yields the node
|
|
37
|
+
* inside the subflow rather than the subflow id.
|
|
38
|
+
*
|
|
39
|
+
* A purely numeric segment is an array index, not an id: a schema-parse issue
|
|
40
|
+
* carries a Zod path, and flow-schema's `formatZodPath` renders those as
|
|
41
|
+
* `nodes[0].type`. Treating the `0` as a node id would have Studio claim the
|
|
42
|
+
* row, fail to focus a node by that name, and — because the id read as
|
|
43
|
+
* present — drop the path from the description, leaving nothing to locate the
|
|
44
|
+
* problem by.
|
|
45
|
+
*/
|
|
46
|
+
export declare function nodeIdFromIssuePath(path: string): string | undefined;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The designer's validation rules, run over a `.flow` without the designer.
|
|
3
|
+
*
|
|
4
|
+
* This is what decides whether a Flow project packs: parse the file against
|
|
5
|
+
* the flow schema, load each inline agent's prompts and settings from its
|
|
6
|
+
* `agent.json` the way the designer does, then run `@uipath/flow-schema`'s
|
|
7
|
+
* rule set — the same rules the canvas runs — over the root graph and every
|
|
8
|
+
* subflow. No network or login, and file access only through the file system
|
|
9
|
+
* the caller hands in, so it runs wherever the packager does: the CLI,
|
|
10
|
+
* desktop Studio, and Studio Web.
|
|
11
|
+
*
|
|
12
|
+
* `uip maestro flow validate` runs the same two steps (via
|
|
13
|
+
* {@link parseFlowSource} and {@link runDesignerRules}) and adds its own
|
|
14
|
+
* authoring checks around them. Those extra checks are for flows written by
|
|
15
|
+
* agents or by hand, which the designer never shows, so they stay out of the
|
|
16
|
+
* pack verdict.
|
|
17
|
+
*/
|
|
18
|
+
import { type ParsedWorkflow, type ValidationRule, type WorkflowValidationContext, type WorkflowVariables } from "@uipath/flow-schema";
|
|
19
|
+
import type { FlowValidationIssue } from "./flow-validation-hook.js";
|
|
20
|
+
import { type ReadAgentJson } from "./inline-agent-utils.js";
|
|
21
|
+
/** Graph shape shared by root workflow and each subflow entry; avoids `as ParsedWorkflow` over `unknown[]`. */
|
|
22
|
+
export interface FlowGraph {
|
|
23
|
+
nodes: Array<{
|
|
24
|
+
id: string;
|
|
25
|
+
type: string;
|
|
26
|
+
typeVersion?: string;
|
|
27
|
+
display?: {
|
|
28
|
+
label?: string;
|
|
29
|
+
} & Record<string, unknown>;
|
|
30
|
+
inputs?: Record<string, unknown>;
|
|
31
|
+
outputs?: Record<string, unknown>;
|
|
32
|
+
model?: Record<string, unknown>;
|
|
33
|
+
}>;
|
|
34
|
+
edges: Array<{
|
|
35
|
+
id: string;
|
|
36
|
+
sourceNodeId: string;
|
|
37
|
+
sourcePort: string;
|
|
38
|
+
targetNodeId: string;
|
|
39
|
+
targetPort: string;
|
|
40
|
+
}>;
|
|
41
|
+
definitions: Array<{
|
|
42
|
+
nodeType: string;
|
|
43
|
+
version: string;
|
|
44
|
+
category?: string;
|
|
45
|
+
display?: {
|
|
46
|
+
label: string;
|
|
47
|
+
};
|
|
48
|
+
handleConfiguration: unknown[];
|
|
49
|
+
inputDefinition?: Record<string, unknown>;
|
|
50
|
+
outputDefinition?: Record<string, unknown>;
|
|
51
|
+
/** BPMN element the node lowers to at runtime, e.g. `bpmn:ParallelGateway`. */
|
|
52
|
+
model?: {
|
|
53
|
+
type?: string;
|
|
54
|
+
} & Record<string, unknown>;
|
|
55
|
+
tags?: string[];
|
|
56
|
+
}>;
|
|
57
|
+
variables?: WorkflowVariables;
|
|
58
|
+
/** Top-level binding registry that `<bindings.{name}>` placeholders resolve against. */
|
|
59
|
+
bindings?: Array<Record<string, unknown>>;
|
|
60
|
+
}
|
|
61
|
+
/** A `.flow` that parsed, or the issues explaining why it did not. */
|
|
62
|
+
export type ParsedFlowSource = {
|
|
63
|
+
ok: true;
|
|
64
|
+
workflow: ParsedWorkflow;
|
|
65
|
+
/**
|
|
66
|
+
* `supportsErrorHandling` per `nodeType:version`, recovered from the
|
|
67
|
+
* raw JSON — the schema parser strips it, and the edge-handle check
|
|
68
|
+
* needs it to honour the implicit `error` source port.
|
|
69
|
+
*/
|
|
70
|
+
rawDefErrorHandling: Map<string, boolean>;
|
|
71
|
+
} | {
|
|
72
|
+
ok: false;
|
|
73
|
+
issues: FlowValidationIssue[];
|
|
74
|
+
};
|
|
75
|
+
export interface DesignerRuleOptions {
|
|
76
|
+
/**
|
|
77
|
+
* The graph is a subflow body (a loop body, a nested call activity), so
|
|
78
|
+
* rules scoped to the whole workflow — `trigger-required` — do not apply.
|
|
79
|
+
*/
|
|
80
|
+
isSubflow?: boolean;
|
|
81
|
+
governanceEnforcements?: WorkflowValidationContext["governanceEnforcements"];
|
|
82
|
+
availableModelNames?: WorkflowValidationContext["availableModelNames"];
|
|
83
|
+
/** Appended after the designer's rules. */
|
|
84
|
+
extraRules?: readonly ValidationRule[];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Copy onto an inline agent's node what the designer shows on it, which the
|
|
88
|
+
* `.flow` keeps in the agent's own `agent.json`: the system and user prompts
|
|
89
|
+
* from its messages, and model / temperature / token limit from its settings
|
|
90
|
+
* (voice settings too, for a voice agent). Without this every stored inline
|
|
91
|
+
* agent reads as missing its prompts and model. A value already on the node
|
|
92
|
+
* is never overwritten, and the first system and first user message win.
|
|
93
|
+
*/
|
|
94
|
+
export declare function hydrateAgentNodeInputs(node: {
|
|
95
|
+
type: string;
|
|
96
|
+
inputs?: Record<string, unknown>;
|
|
97
|
+
}, agent: Record<string, unknown>): void;
|
|
98
|
+
/**
|
|
99
|
+
* Parse and validate against flow schema. `parseWorkflowJson` returns a
|
|
100
|
+
* structured `parseIssues` array (one entry per Zod issue) on flow-schema
|
|
101
|
+
* versions ≥ the one shipping the side-channel; on older versions only the
|
|
102
|
+
* joined `parseError` string is populated. The cast below keeps us
|
|
103
|
+
* forward-compatible without forcing a dep bump: when the field arrives we
|
|
104
|
+
* emit one issue per Zod issue with its real path; otherwise we split the
|
|
105
|
+
* formatted multi-line `parseError` string ourselves so each issue keeps its
|
|
106
|
+
* Zod path (UiPath/cli#2029). Only when neither shape is available do we
|
|
107
|
+
* collapse to a single `(root)` issue (legacy / non-schema errors like JSON
|
|
108
|
+
* parse failures). flow-workbench PR #1479.
|
|
109
|
+
*/
|
|
110
|
+
export declare function parseFlowSource(content: string): ParsedFlowSource;
|
|
111
|
+
/**
|
|
112
|
+
* The designer's rules over one graph. Paths come back relative to the graph
|
|
113
|
+
* — a caller validating a subflow prefixes them.
|
|
114
|
+
*
|
|
115
|
+
* Semantic validation using built-in rules from flow-schema.
|
|
116
|
+
* conditionExpressionRule is deliberately excluded from cliRules on the
|
|
117
|
+
* canvas (it runs as a main-thread fallback there); with no competing
|
|
118
|
+
* validator here we opt it in, to catch malformed HTTP/Decision/Switch
|
|
119
|
+
* condition expressions. triggerRequiredRule is scoped to the root workflow
|
|
120
|
+
* only — subflows (loop bodies, nested call activities) are not expected to
|
|
121
|
+
* carry their own trigger. expression-ref is dropped everywhere: it treats
|
|
122
|
+
* the `any` that script, transform and loop `currentItem` outputs declare as
|
|
123
|
+
* a closed primitive, so it refuses deep reads of them, and reads the
|
|
124
|
+
* manifest rather than the `.flow`, so no author can suppress it. The canvas
|
|
125
|
+
* dropped it over the same false positives (MST-9593); flow-workbench#3638
|
|
126
|
+
* removes it from `cliRules`, after which this filter matches nothing.
|
|
127
|
+
*/
|
|
128
|
+
export declare function runDesignerRules(graph: FlowGraph, options?: DesignerRuleOptions): FlowValidationIssue[];
|
|
129
|
+
/**
|
|
130
|
+
* {@link parseFlowSource}, then {@link runDesignerRules} over the root graph
|
|
131
|
+
* and every subflow — with each inline agent first hydrated from its
|
|
132
|
+
* `agent.json` through `readAgentJson`, as the designer shows it. This is the
|
|
133
|
+
* pack verdict: any `error` fails the pack.
|
|
134
|
+
*/
|
|
135
|
+
export declare function validateFlowSource(content: string, readAgentJson?: ReadAgentJson): Promise<FlowValidationIssue[]>;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,10 +2,12 @@ export { type AgentInputLeaf, canonicalAgentInputPath, collectAgentInputLeaves,
|
|
|
2
2
|
export { buildInlineAgentContract, type InlineAgentContract, } from "./agents.js";
|
|
3
3
|
export { createProcessBindings, ensureProcessBindings, extractProcessGuid, } from "./ensure-process-bindings.js";
|
|
4
4
|
export { replaceExporterVersion } from "./exporter-version.js";
|
|
5
|
-
export { createFlowRefResolver, type FlowIoLogger, migrateRawWorkflow, readFlowWorkflow, resolveAndMigrateWorkflow, resolveWorkflowRefs, } from "./flow-io.js";
|
|
5
|
+
export { createFlowRefResolver, type FlowIoLogger, findProjectFlowFile, inlineFlowRefs, migrateRawWorkflow, readFlowWorkflow, resolveAndMigrateWorkflow, resolveProjectFlowFile, resolveWorkflowRefs, tryFindProjectFlowFile, tryResolveProjectFlowFile, } from "./flow-io.js";
|
|
6
6
|
export { FlowTool } from "./flow-tool.js";
|
|
7
7
|
export { FlowToolFactory } from "./flow-tool-factory.js";
|
|
8
|
-
export { type
|
|
8
|
+
export { type DesignerRuleOptions, type FlowGraph, hydrateAgentNodeInputs, type ParsedFlowSource, parseFlowSource, runDesignerRules, validateFlowSource, } from "./flow-validation.js";
|
|
9
|
+
export type { FlowValidationIssue, FlowValidationSeverity, } from "./flow-validation-hook.js";
|
|
10
|
+
export { type AgentInputVariable, agentJsonReader, hydrateInlineAgentInputVariables, isInlineAgentNodeType, type ReadAgentJson, readInlineAgentSource, reconcileInlineAgentInputVariables, setPublishIntentOnInlineAgents, } from "./inline-agent-utils.js";
|
|
9
11
|
export { applyRuntimeProfile, isMaestroAutomateSentinel, RuntimeProfile, resolveRuntimeProfile, resolveRuntimeProfileForFlowFile, withRuntimeProfile, } from "./maestro-automate.js";
|
|
10
12
|
export { operateRuntimeOptions } from "./operate-runtime-options.js";
|
|
11
13
|
export { assertVoiceAgentDefinitionsEmbedded, type VoiceAgentFlowNode, voiceConvertOptions, } from "./voice-agent-definitions.js";
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
// package.json
|
|
33
33
|
var package_default = {
|
|
34
34
|
name: "@uipath/packager-tool-flow",
|
|
35
|
-
version: "1.
|
|
35
|
+
version: "1.204.0-preview.211",
|
|
36
36
|
description: "UiPath Flow tool implementation",
|
|
37
37
|
type: "module",
|
|
38
38
|
exports: {
|
|
@@ -49,8 +49,9 @@ var package_default = {
|
|
|
49
49
|
},
|
|
50
50
|
types: "./dist/index.d.ts",
|
|
51
51
|
scripts: {
|
|
52
|
-
build: "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --external @uipath/tool-agent --external @uipath/flow-core --external @uipath/flow-converter --external @uipath/flow-migrations --external @uipath/flow-schema --sourcemap=linked && bun build ./src/maestro-automate-entry.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --sourcemap=linked && tsc --emitDeclarationOnly --outDir dist",
|
|
53
|
-
|
|
52
|
+
build: "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --external @uipath/tool-agent --external @uipath/flow-core --external @uipath/flow-converter --external @uipath/flow-migrations --external @uipath/flow-schema --external i18next --sourcemap=linked && bun build ./src/maestro-automate-entry.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --sourcemap=linked && tsc -p tsconfig.build.json --emitDeclarationOnly --noCheck --outDir dist",
|
|
53
|
+
typecheck: "tsc --noEmit",
|
|
54
|
+
dev: "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --external @uipath/tool-agent --external @uipath/flow-core --external @uipath/flow-converter --external @uipath/flow-migrations --external @uipath/flow-schema --external i18next --watch",
|
|
54
55
|
test: "vitest run",
|
|
55
56
|
"test:browser": "vitest run --config=vitest.browser.config.ts",
|
|
56
57
|
"test:coverage": "vitest run --coverage",
|
|
@@ -77,7 +78,8 @@ var package_default = {
|
|
|
77
78
|
"@uipath/flow-migrations": "0.56.2",
|
|
78
79
|
"@uipath/flow-schema": "0.68.0",
|
|
79
80
|
"@uipath/solutionpackager-tool-core": "workspace:*",
|
|
80
|
-
"@uipath/tool-agent": "^2.0.0"
|
|
81
|
+
"@uipath/tool-agent": "^2.0.0",
|
|
82
|
+
i18next: "^23.16.8"
|
|
81
83
|
},
|
|
82
84
|
devDependencies: {
|
|
83
85
|
"@types/node": "^25.5.2",
|
|
@@ -89,6 +91,7 @@ var package_default = {
|
|
|
89
91
|
"@vitest/browser": "^4.1.11",
|
|
90
92
|
"@vitest/browser-playwright": "^4.1.11",
|
|
91
93
|
"@vitest/coverage-v8": "^4.1.11",
|
|
94
|
+
i18next: "^23.16.8",
|
|
92
95
|
playwright: "^1.57.0",
|
|
93
96
|
typescript: "^7.0.2",
|
|
94
97
|
vitest: "^4.1.11"
|
|
@@ -428,6 +431,18 @@ async function resolveWorkflowRefs(parsed, resolver, baseUri) {
|
|
|
428
431
|
}
|
|
429
432
|
return { resolved, refMap };
|
|
430
433
|
}
|
|
434
|
+
async function inlineFlowRefs(fs, content, filePath, logger) {
|
|
435
|
+
if (!content.includes('"$ref"'))
|
|
436
|
+
return content;
|
|
437
|
+
let parsed;
|
|
438
|
+
try {
|
|
439
|
+
parsed = JSON.parse(content);
|
|
440
|
+
} catch {
|
|
441
|
+
return content;
|
|
442
|
+
}
|
|
443
|
+
const { resolved } = await resolveWorkflowRefs(parsed, createFlowRefResolver(fs, logger), filePath);
|
|
444
|
+
return JSON.stringify(resolved);
|
|
445
|
+
}
|
|
431
446
|
async function resolveAndMigrateWorkflow(parsed, resolver, baseUri, logger) {
|
|
432
447
|
const { resolved, refMap } = await resolveWorkflowRefs(parsed, resolver, baseUri);
|
|
433
448
|
const migrated = migrateRawWorkflow(resolved, logger);
|
|
@@ -456,6 +471,79 @@ async function readUtf8OrNull(fs, filePath) {
|
|
|
456
471
|
return null;
|
|
457
472
|
return typeof buffer === "string" ? buffer : new TextDecoder().decode(buffer);
|
|
458
473
|
}
|
|
474
|
+
async function resolveProjectFlowFile(fs, projectPath) {
|
|
475
|
+
const found = await tryResolveProjectFlowFile(fs, projectPath);
|
|
476
|
+
if (!found) {
|
|
477
|
+
throw new Error(`Missing .flow file in ${projectPath}`);
|
|
478
|
+
}
|
|
479
|
+
return found;
|
|
480
|
+
}
|
|
481
|
+
async function tryResolveProjectFlowFile(fs, projectPath) {
|
|
482
|
+
let uiproj;
|
|
483
|
+
try {
|
|
484
|
+
const raw = await fs.readFile(fs.path.join(projectPath, "project.uiproj"), "utf-8");
|
|
485
|
+
uiproj = JSON.parse(raw);
|
|
486
|
+
} catch {
|
|
487
|
+
uiproj = undefined;
|
|
488
|
+
}
|
|
489
|
+
const safeName = uiproj?.Name?.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
490
|
+
return tryFindProjectFlowFile(fs, projectPath, uiproj?.MainFile, safeName);
|
|
491
|
+
}
|
|
492
|
+
async function findProjectFlowFile(fs, projectPath, mainFile, preferredBaseName) {
|
|
493
|
+
const found = await tryFindProjectFlowFile(fs, projectPath, mainFile, preferredBaseName);
|
|
494
|
+
if (!found) {
|
|
495
|
+
throw new Error(`Missing .flow file in ${projectPath}`);
|
|
496
|
+
}
|
|
497
|
+
return found;
|
|
498
|
+
}
|
|
499
|
+
async function tryFindProjectFlowFile(fs, projectPath, mainFile, preferredBaseName) {
|
|
500
|
+
if (mainFile?.endsWith(".flow")) {
|
|
501
|
+
const mainFilePath = fs.path.join(projectPath, mainFile);
|
|
502
|
+
if (isInsideDirectory(fs, projectPath, mainFilePath) && await fs.exists(mainFilePath)) {
|
|
503
|
+
return mainFilePath;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const preferred = preferredBaseName ? `${preferredBaseName}.flow`.toLowerCase() : undefined;
|
|
507
|
+
const candidateDirs = [
|
|
508
|
+
projectPath,
|
|
509
|
+
fs.path.join(projectPath, "content"),
|
|
510
|
+
fs.path.join(projectPath, "flow_files")
|
|
511
|
+
];
|
|
512
|
+
for (const dir of candidateDirs) {
|
|
513
|
+
if (!await fs.exists(dir)) {
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
const stat = await fs.stat(dir);
|
|
517
|
+
if (!stat?.isDirectory()) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const flowFiles = (await fs.readdir(dir)).filter((entry) => entry.endsWith(".flow"));
|
|
521
|
+
if (flowFiles.length === 0) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
const chosen = (preferred && flowFiles.find((f) => f.toLowerCase() === preferred)) ?? flowFiles[0];
|
|
525
|
+
return fs.path.join(dir, chosen);
|
|
526
|
+
}
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
function isInsideDirectory(fs, directory, candidate) {
|
|
530
|
+
const relative = fs.path.relative(directory, candidate);
|
|
531
|
+
return relative !== "" && !/^\.\.(?:[\\/]|$)/.test(relative) && !fs.path.isAbsolute(relative);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/flow-validation.ts
|
|
535
|
+
import {
|
|
536
|
+
AGENT_NODE_TYPES,
|
|
537
|
+
cliRules,
|
|
538
|
+
conditionExpressionRule,
|
|
539
|
+
isBlockingSeverity,
|
|
540
|
+
isVoiceAgentNodeType as isVoiceAgentNodeType2,
|
|
541
|
+
parseWorkflowJson,
|
|
542
|
+
registerFlowSchemaI18n,
|
|
543
|
+
runRules,
|
|
544
|
+
toSerializedNodes
|
|
545
|
+
} from "@uipath/flow-schema";
|
|
546
|
+
import i18next from "i18next";
|
|
459
547
|
|
|
460
548
|
// src/inline-agent-utils.ts
|
|
461
549
|
import { isAgentNodeType, isVoiceAgentNodeType } from "@uipath/flow-schema";
|
|
@@ -523,6 +611,19 @@ function readInlineAgentSource(node) {
|
|
|
523
611
|
}
|
|
524
612
|
return;
|
|
525
613
|
}
|
|
614
|
+
function agentJsonReader(fs, flowDir) {
|
|
615
|
+
return async (sourceId) => {
|
|
616
|
+
try {
|
|
617
|
+
const raw = await fs.readFile(fs.path.join(flowDir, sourceId, "agent.json"), "utf-8");
|
|
618
|
+
if (!raw)
|
|
619
|
+
return;
|
|
620
|
+
const agent = JSON.parse(raw);
|
|
621
|
+
return typeof agent === "object" && agent !== null && !Array.isArray(agent) ? agent : undefined;
|
|
622
|
+
} catch {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
}
|
|
526
627
|
function isInlineAgentNodeType(type) {
|
|
527
628
|
return isAgentNodeType(type);
|
|
528
629
|
}
|
|
@@ -587,6 +688,260 @@ function setPublishIntentOnInlineAgents(fileFormat) {
|
|
|
587
688
|
}
|
|
588
689
|
}
|
|
589
690
|
|
|
691
|
+
// src/flow-validation.ts
|
|
692
|
+
var validationMessagesRegistered = false;
|
|
693
|
+
function ensureValidationMessages() {
|
|
694
|
+
if (validationMessagesRegistered)
|
|
695
|
+
return;
|
|
696
|
+
if (!i18next.isInitialized) {
|
|
697
|
+
i18next.init({
|
|
698
|
+
lng: "en",
|
|
699
|
+
fallbackLng: "en",
|
|
700
|
+
defaultNS: "validation",
|
|
701
|
+
resources: {},
|
|
702
|
+
interpolation: { escapeValue: false }
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
registerFlowSchemaI18n(i18next);
|
|
706
|
+
validationMessagesRegistered = true;
|
|
707
|
+
}
|
|
708
|
+
function hydrateAgentNodeInputs(node, agent) {
|
|
709
|
+
if (!node.inputs)
|
|
710
|
+
node.inputs = {};
|
|
711
|
+
const inputs = node.inputs;
|
|
712
|
+
const settings = agent.settings;
|
|
713
|
+
if (isVoiceAgentNodeType2(node.type)) {
|
|
714
|
+
const voiceSettings = isRecord2(settings) ? settings.voice : undefined;
|
|
715
|
+
if (isRecord2(voiceSettings) && inputs.voice === undefined) {
|
|
716
|
+
inputs.voice = voiceSettings;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
const messages = agent.messages;
|
|
720
|
+
if (!Array.isArray(messages))
|
|
721
|
+
return;
|
|
722
|
+
for (const msg of messages) {
|
|
723
|
+
if (!msg || typeof msg !== "object")
|
|
724
|
+
continue;
|
|
725
|
+
const m = msg;
|
|
726
|
+
if (m.role === "system" && typeof m.content === "string") {
|
|
727
|
+
inputs.systemPrompt ??= m.content;
|
|
728
|
+
}
|
|
729
|
+
if (m.role === "user" && typeof m.content === "string") {
|
|
730
|
+
inputs.userPrompt ??= m.content;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (settings && typeof settings === "object") {
|
|
734
|
+
const s = settings;
|
|
735
|
+
if (typeof s.temperature === "number" && inputs.temperature === undefined) {
|
|
736
|
+
inputs.temperature = s.temperature;
|
|
737
|
+
}
|
|
738
|
+
if (typeof s.maxTokens === "number" && inputs.maxTokenPerResponse === undefined) {
|
|
739
|
+
inputs.maxTokenPerResponse = s.maxTokens;
|
|
740
|
+
}
|
|
741
|
+
if (typeof s.model === "string" && inputs.model === undefined) {
|
|
742
|
+
inputs.model = s.model;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async function hydrateInlineAgents(nodes, readAgentJson) {
|
|
747
|
+
for (const node of nodes) {
|
|
748
|
+
if (!AGENT_NODE_TYPES.includes(node.type)) {
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const source = readInlineAgentSource(node);
|
|
752
|
+
if (source === undefined || /[/\\.]/.test(source))
|
|
753
|
+
continue;
|
|
754
|
+
const agent = await readAgentJson(source);
|
|
755
|
+
if (agent)
|
|
756
|
+
hydrateAgentNodeInputs(node, agent);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function isRecord2(value) {
|
|
760
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
761
|
+
}
|
|
762
|
+
function parseFlowSource(content) {
|
|
763
|
+
ensureValidationMessages();
|
|
764
|
+
const parsed = parseWorkflowJson(content);
|
|
765
|
+
const { workflow, parseError, parseIssues } = parsed;
|
|
766
|
+
if (parseError || !workflow) {
|
|
767
|
+
const recoveredIssues = parseIssues && parseIssues.length > 0 ? parseIssues : parseError ? splitSchemaParseError(parseError) : null;
|
|
768
|
+
const fallback = [
|
|
769
|
+
{
|
|
770
|
+
path: "(root)",
|
|
771
|
+
message: parseError ?? "File is not valid JSON",
|
|
772
|
+
severity: "error"
|
|
773
|
+
}
|
|
774
|
+
];
|
|
775
|
+
return {
|
|
776
|
+
ok: false,
|
|
777
|
+
issues: recoveredIssues && recoveredIssues.length > 0 ? recoveredIssues.map((pi) => ({
|
|
778
|
+
path: pi.path,
|
|
779
|
+
message: pi.message,
|
|
780
|
+
severity: "error"
|
|
781
|
+
})) : fallback
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
ok: true,
|
|
786
|
+
workflow,
|
|
787
|
+
rawDefErrorHandling: extractSupportsErrorHandling(content)
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
function runDesignerRules(graph, options = {}) {
|
|
791
|
+
ensureValidationMessages();
|
|
792
|
+
const ctx = buildValidationContext(graph);
|
|
793
|
+
if (options.governanceEnforcements) {
|
|
794
|
+
ctx.governanceEnforcements = options.governanceEnforcements;
|
|
795
|
+
}
|
|
796
|
+
if (options.availableModelNames) {
|
|
797
|
+
ctx.availableModelNames = options.availableModelNames;
|
|
798
|
+
}
|
|
799
|
+
const dropped = new Set(["expression-ref"]);
|
|
800
|
+
if (options.isSubflow)
|
|
801
|
+
dropped.add("trigger-required");
|
|
802
|
+
const rules = cliRules.filter((r) => !dropped.has(r.id));
|
|
803
|
+
const ruleErrors = runRules([...rules, conditionExpressionRule, ...options.extraRules ?? []], ctx);
|
|
804
|
+
return ruleErrors.map(validationErrorToIssue);
|
|
805
|
+
}
|
|
806
|
+
async function validateFlowSource(content, readAgentJson) {
|
|
807
|
+
const parsed = parseFlowSource(content);
|
|
808
|
+
if (!parsed.ok) {
|
|
809
|
+
return parsed.issues;
|
|
810
|
+
}
|
|
811
|
+
const { workflow } = parsed;
|
|
812
|
+
const subflows = workflow.subflows ?? {};
|
|
813
|
+
if (readAgentJson) {
|
|
814
|
+
await hydrateInlineAgents(workflow.nodes, readAgentJson);
|
|
815
|
+
for (const entry of Object.values(subflows)) {
|
|
816
|
+
await hydrateInlineAgents(entry.nodes, readAgentJson);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
const issues = runDesignerRules(workflow);
|
|
820
|
+
for (const [subflowId, entry] of Object.entries(subflows)) {
|
|
821
|
+
const subGraph = {
|
|
822
|
+
nodes: entry.nodes,
|
|
823
|
+
edges: entry.edges,
|
|
824
|
+
definitions: workflow.definitions,
|
|
825
|
+
variables: entry.variables
|
|
826
|
+
};
|
|
827
|
+
const prefix = `subflows[${subflowId}].`;
|
|
828
|
+
issues.push(...runDesignerRules(subGraph, { isSubflow: true }).map((issue) => ({
|
|
829
|
+
...issue,
|
|
830
|
+
path: `${prefix}${issue.path}`
|
|
831
|
+
})));
|
|
832
|
+
}
|
|
833
|
+
return issues;
|
|
834
|
+
}
|
|
835
|
+
function extractSupportsErrorHandling(content) {
|
|
836
|
+
const map = new Map;
|
|
837
|
+
let raw;
|
|
838
|
+
try {
|
|
839
|
+
raw = JSON.parse(content);
|
|
840
|
+
} catch {
|
|
841
|
+
return map;
|
|
842
|
+
}
|
|
843
|
+
if (!raw || typeof raw !== "object")
|
|
844
|
+
return map;
|
|
845
|
+
const defs = raw.definitions;
|
|
846
|
+
if (!Array.isArray(defs))
|
|
847
|
+
return map;
|
|
848
|
+
for (const def of defs) {
|
|
849
|
+
if (!def || typeof def !== "object")
|
|
850
|
+
continue;
|
|
851
|
+
const d = def;
|
|
852
|
+
if (typeof d.nodeType === "string" && typeof d.version === "string" && d.supportsErrorHandling === true) {
|
|
853
|
+
map.set(`${d.nodeType}:${d.version}`, true);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return map;
|
|
857
|
+
}
|
|
858
|
+
function validationErrorToIssue(error) {
|
|
859
|
+
let path;
|
|
860
|
+
if (error.nodeId) {
|
|
861
|
+
path = error.fieldPath ? `nodes[${error.nodeId}].${error.fieldPath}` : `nodes[${error.nodeId}]`;
|
|
862
|
+
} else {
|
|
863
|
+
path = error.fieldPath ?? "(workflow)";
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
path,
|
|
867
|
+
message: `[${error.code}] ${error.message}`,
|
|
868
|
+
severity: isBlockingSeverity(error.severity) ? "error" : "warning"
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
function buildValidationContext(flow) {
|
|
872
|
+
const nodes = toSerializedNodes(flow.nodes);
|
|
873
|
+
const edges = flow.edges.map((e) => ({
|
|
874
|
+
id: e.id,
|
|
875
|
+
source: e.sourceNodeId,
|
|
876
|
+
sourceHandle: e.sourcePort ?? null,
|
|
877
|
+
target: e.targetNodeId,
|
|
878
|
+
targetHandle: e.targetPort ?? null
|
|
879
|
+
}));
|
|
880
|
+
const manifests = new Map;
|
|
881
|
+
for (const def of flow.definitions) {
|
|
882
|
+
manifests.set(`${def.nodeType}:${def.version}`, {
|
|
883
|
+
nodeType: def.nodeType,
|
|
884
|
+
version: def.version,
|
|
885
|
+
category: def.category,
|
|
886
|
+
display: def.display,
|
|
887
|
+
handleConfiguration: def.handleConfiguration,
|
|
888
|
+
inputDefinition: def.inputDefinition,
|
|
889
|
+
outputDefinition: def.outputDefinition,
|
|
890
|
+
tags: def.tags
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
const outputVariableIds = [];
|
|
894
|
+
const variables = flow.variables;
|
|
895
|
+
if (variables?.globals) {
|
|
896
|
+
for (const v of variables.globals) {
|
|
897
|
+
if (v.direction === "out" || v.direction === "inout") {
|
|
898
|
+
outputVariableIds.push(v.id);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return {
|
|
903
|
+
nodes,
|
|
904
|
+
edges,
|
|
905
|
+
manifests,
|
|
906
|
+
outputVariableIds
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
function splitSchemaParseError(parseError) {
|
|
910
|
+
const header = "Schema validation failed:";
|
|
911
|
+
if (!parseError.startsWith(header)) {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
const body = parseError.slice(header.length);
|
|
915
|
+
const lines = body.split(`
|
|
916
|
+
`);
|
|
917
|
+
const issues = [];
|
|
918
|
+
for (const rawLine of lines) {
|
|
919
|
+
const match = /^\s*-\s+(.+?):\s(.*)$/.exec(rawLine);
|
|
920
|
+
if (!match)
|
|
921
|
+
continue;
|
|
922
|
+
const [, path, message] = match;
|
|
923
|
+
if (!path || !message)
|
|
924
|
+
continue;
|
|
925
|
+
issues.push({ path, message });
|
|
926
|
+
}
|
|
927
|
+
return issues.length > 0 ? issues : null;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// src/flow-validation-hook.ts
|
|
931
|
+
function splitIssueMessage(message) {
|
|
932
|
+
const match = /^\[([A-Z][A-Z0-9_]*)\]\s+(.*)$/s.exec(message);
|
|
933
|
+
return match?.[2] ? { code: match[1], description: match[2] } : { description: message };
|
|
934
|
+
}
|
|
935
|
+
function nodeIdFromIssuePath(path) {
|
|
936
|
+
let nodeId;
|
|
937
|
+
for (const match of path.matchAll(/nodes\[([^\]]+)\]/g)) {
|
|
938
|
+
if (match[1] !== undefined && !/^\d+$/.test(match[1])) {
|
|
939
|
+
nodeId = match[1];
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return nodeId;
|
|
943
|
+
}
|
|
944
|
+
|
|
590
945
|
// src/maestro-automate.ts
|
|
591
946
|
import { Path as Path3 } from "@uipath/solutionpackager-tool-core";
|
|
592
947
|
var SENTINEL_FILE_NAME = ".maestro_automate";
|
|
@@ -641,7 +996,7 @@ function operateRuntimeOptions(nodes) {
|
|
|
641
996
|
}
|
|
642
997
|
|
|
643
998
|
// src/voice-agent-definitions.ts
|
|
644
|
-
import { isVoiceAgentNodeType as
|
|
999
|
+
import { isVoiceAgentNodeType as isVoiceAgentNodeType3 } from "@uipath/flow-schema";
|
|
645
1000
|
function nodeLabel(node) {
|
|
646
1001
|
const label = node.display?.label;
|
|
647
1002
|
return typeof label === "string" && label.length > 0 ? label : node.id;
|
|
@@ -649,7 +1004,7 @@ function nodeLabel(node) {
|
|
|
649
1004
|
function collectVoiceAgentNodes(nodes) {
|
|
650
1005
|
const result = [];
|
|
651
1006
|
for (const node of nodes) {
|
|
652
|
-
if (!
|
|
1007
|
+
if (!isVoiceAgentNodeType3(node.type))
|
|
653
1008
|
continue;
|
|
654
1009
|
const source = readInlineAgentSource(node);
|
|
655
1010
|
if (!source) {
|
|
@@ -680,7 +1035,7 @@ function assertNoSubflowVoiceAgents(flowJson) {
|
|
|
680
1035
|
const workflow = JSON.parse(flowJson);
|
|
681
1036
|
for (const [subflowId, entry] of Object.entries(workflow.subflows ?? {})) {
|
|
682
1037
|
for (const node of entry.nodes ?? []) {
|
|
683
|
-
if (!
|
|
1038
|
+
if (!isVoiceAgentNodeType3(node.type))
|
|
684
1039
|
continue;
|
|
685
1040
|
throw new Error(`Voice agent node "${nodeLabel(node)}" is inside subflow "${subflowId}". ` + "Voice agent nodes are not supported inside subflows — move it to the top-level flow.");
|
|
686
1041
|
}
|
|
@@ -713,6 +1068,9 @@ var FlowConstants = {
|
|
|
713
1068
|
EntryPointsFileName: "entry-points.json",
|
|
714
1069
|
BindingsV2FileName: "bindings_v2.json"
|
|
715
1070
|
};
|
|
1071
|
+
var FLOW_VALIDATION_FAILED = "FLOW_VALIDATION_FAILED";
|
|
1072
|
+
var FLOW_VALIDATION_SOURCE = "Validator";
|
|
1073
|
+
var SUBFLOW_PATH_SEGMENT = "subflows[";
|
|
716
1074
|
|
|
717
1075
|
class FlowTool extends ProjectTool {
|
|
718
1076
|
_temporaryStorage;
|
|
@@ -724,11 +1082,59 @@ class FlowTool extends ProjectTool {
|
|
|
724
1082
|
this.logger.info("Restore operation is not required for Flow projects");
|
|
725
1083
|
return ToolResult.success();
|
|
726
1084
|
}
|
|
727
|
-
async validateAsync(
|
|
728
|
-
this.
|
|
729
|
-
|
|
1085
|
+
async validateAsync(options, _cancellationToken) {
|
|
1086
|
+
return this.runValidation(options.projectPath);
|
|
1087
|
+
}
|
|
1088
|
+
async runValidation(projectPath) {
|
|
1089
|
+
let flowFile;
|
|
1090
|
+
let issues;
|
|
1091
|
+
try {
|
|
1092
|
+
flowFile = await tryResolveProjectFlowFile(this.fileSystem, projectPath);
|
|
1093
|
+
if (flowFile === undefined) {
|
|
1094
|
+
this.logger.info("Flow project has no .flow file; skipping Flow validation");
|
|
1095
|
+
return ToolResult.success();
|
|
1096
|
+
}
|
|
1097
|
+
const content = await this.fileSystem.readFile(flowFile, "utf-8");
|
|
1098
|
+
if (content === null) {
|
|
1099
|
+
throw new Error(`Could not read ${flowFile}`);
|
|
1100
|
+
}
|
|
1101
|
+
this.logger.progress("Validating Flow project...");
|
|
1102
|
+
const source = await inlineFlowRefs(this.fileSystem, content, flowFile, this.logger);
|
|
1103
|
+
issues = await validateFlowSource(source, agentJsonReader(this.fileSystem, projectPath));
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1106
|
+
this.logger.warn(`Could not validate the Flow project, continuing without validation: ${message}`);
|
|
1107
|
+
return ToolResult.success();
|
|
1108
|
+
}
|
|
1109
|
+
const flowFilePath = this.toProjectRelative(projectPath, flowFile);
|
|
1110
|
+
const findings = issues.map((issue) => toFlowFinding(issue, flowFilePath));
|
|
1111
|
+
const errors = issues.filter((issue) => issue.severity === "error");
|
|
1112
|
+
for (const issue of issues) {
|
|
1113
|
+
const line = `[${issue.path}] ${issue.message}`;
|
|
1114
|
+
if (issue.severity === "error") {
|
|
1115
|
+
this.logger.error(line);
|
|
1116
|
+
} else {
|
|
1117
|
+
this.logger.warn(line);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
const result = errors.length === 0 ? ToolResult.success() : ToolResult.error(FLOW_VALIDATION_FAILED, `Flow validation failed for ${flowFilePath}: ${errors.length} error(s).`, "Open the flow in the designer and fix the reported errors, " + "or run 'uip maestro flow validate <project-path>' for the full list.");
|
|
1121
|
+
if (findings.length > 0) {
|
|
1122
|
+
result.details = { ...result.details, findings };
|
|
1123
|
+
}
|
|
1124
|
+
return result;
|
|
1125
|
+
}
|
|
1126
|
+
toProjectRelative(projectPath, filePath) {
|
|
1127
|
+
const root = `${Path4.normalize(projectPath).replace(/\/$/, "")}/`;
|
|
1128
|
+
const file = Path4.normalize(filePath);
|
|
1129
|
+
return file.toLowerCase().startsWith(root.toLowerCase()) ? file.slice(root.length) : Path4.basename(file);
|
|
730
1130
|
}
|
|
731
1131
|
async buildAsync(options, _cancellationToken) {
|
|
1132
|
+
if (!options.skipValidate) {
|
|
1133
|
+
const validationResult = await this.runValidation(options.projectPath);
|
|
1134
|
+
if (!validationResult.isSuccess) {
|
|
1135
|
+
return validationResult;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
732
1138
|
const tempFolder = await this._temporaryStorage.getTempFolderPath();
|
|
733
1139
|
const localBuildFolder = Path4.join(tempFolder, NugetConstants.OutputFolderName);
|
|
734
1140
|
const contentFolder = Path4.join(localBuildFolder, NugetConstants.ContentFolderName);
|
|
@@ -1022,6 +1428,19 @@ class FlowTool extends ProjectTool {
|
|
|
1022
1428
|
await this.fileSystem.writeFile(packageDescriptorPath, packageDescriptorJson);
|
|
1023
1429
|
}
|
|
1024
1430
|
}
|
|
1431
|
+
function toFlowFinding(issue, flowFilePath) {
|
|
1432
|
+
const { code, description } = splitIssueMessage(issue.message);
|
|
1433
|
+
const nodeId = nodeIdFromIssuePath(issue.path);
|
|
1434
|
+
const pathAddsLocation = nodeId === undefined || issue.path.includes(SUBFLOW_PATH_SEGMENT);
|
|
1435
|
+
return {
|
|
1436
|
+
Level: issue.severity === "warning" ? "Warning" : "Error",
|
|
1437
|
+
Source: FLOW_VALIDATION_SOURCE,
|
|
1438
|
+
...code !== undefined && { ErrorCode: code },
|
|
1439
|
+
Description: pathAddsLocation ? `${issue.path}: ${description}` : description,
|
|
1440
|
+
FilePath: flowFilePath,
|
|
1441
|
+
...nodeId !== undefined && { ActivityIdRef: nodeId }
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1025
1444
|
|
|
1026
1445
|
// src/flow-tool-factory.ts
|
|
1027
1446
|
class FlowToolFactory {
|
|
@@ -1038,6 +1457,7 @@ export {
|
|
|
1038
1457
|
FlowToolFactory,
|
|
1039
1458
|
RuntimeProfile,
|
|
1040
1459
|
SCHEMA_CONTAINER_MARKER,
|
|
1460
|
+
agentJsonReader,
|
|
1041
1461
|
applyRuntimeProfile,
|
|
1042
1462
|
assertVoiceAgentDefinitionsEmbedded,
|
|
1043
1463
|
buildInlineAgentContract,
|
|
@@ -1047,23 +1467,32 @@ export {
|
|
|
1047
1467
|
createProcessBindings,
|
|
1048
1468
|
ensureProcessBindings,
|
|
1049
1469
|
extractProcessGuid,
|
|
1470
|
+
findProjectFlowFile,
|
|
1471
|
+
hydrateAgentNodeInputs,
|
|
1050
1472
|
hydrateInlineAgentInputVariables,
|
|
1473
|
+
inlineFlowRefs,
|
|
1051
1474
|
isInlineAgentNodeType,
|
|
1052
1475
|
isMaestroAutomateSentinel,
|
|
1053
1476
|
leafToBinding,
|
|
1054
1477
|
migrateRawWorkflow,
|
|
1055
1478
|
operateRuntimeOptions,
|
|
1479
|
+
parseFlowSource,
|
|
1056
1480
|
readFlowWorkflow,
|
|
1057
1481
|
readInlineAgentSource,
|
|
1058
1482
|
reconcileInlineAgentInputVariables,
|
|
1059
1483
|
replaceExporterVersion,
|
|
1060
1484
|
resolveAndMigrateWorkflow,
|
|
1485
|
+
resolveProjectFlowFile,
|
|
1061
1486
|
resolveRuntimeProfile,
|
|
1062
1487
|
resolveRuntimeProfileForFlowFile,
|
|
1063
1488
|
resolveWorkflowRefs,
|
|
1489
|
+
runDesignerRules,
|
|
1064
1490
|
setPublishIntentOnInlineAgents,
|
|
1491
|
+
tryFindProjectFlowFile,
|
|
1492
|
+
tryResolveProjectFlowFile,
|
|
1493
|
+
validateFlowSource,
|
|
1065
1494
|
voiceConvertOptions,
|
|
1066
1495
|
withRuntimeProfile
|
|
1067
1496
|
};
|
|
1068
1497
|
|
|
1069
|
-
//# debugId=
|
|
1498
|
+
//# debugId=00A47C9E92F8453364756E2164756E21
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IFileSystem } from "@uipath/filesystem";
|
|
1
2
|
/**
|
|
2
3
|
* Resolve the inline-agent's projectId UUID from a flow node. Falls back to
|
|
3
4
|
* the legacy `model.source` / `agentProjectId` aliases so packaging tolerates
|
|
@@ -8,6 +9,14 @@ export declare function readInlineAgentSource(node: {
|
|
|
8
9
|
inputs?: Record<string, unknown>;
|
|
9
10
|
model?: Record<string, unknown>;
|
|
10
11
|
}): string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* An inline agent's `agent.json`, looked up by the agent's source id.
|
|
14
|
+
* `undefined` when there is none, or it cannot be read or parsed — the agent
|
|
15
|
+
* then stays as stored and the rules report what it is missing.
|
|
16
|
+
*/
|
|
17
|
+
export type ReadAgentJson = (sourceId: string) => Promise<Record<string, unknown> | undefined>;
|
|
18
|
+
/** Reads `<flowDir>/<sourceId>/agent.json` through the caller's file system. */
|
|
19
|
+
export declare function agentJsonReader(fs: IFileSystem, flowDir: string): ReadAgentJson;
|
|
11
20
|
/** True for a known inline-agent node type. */
|
|
12
21
|
export declare function isInlineAgentNodeType(type: string | undefined): boolean;
|
|
13
22
|
/** One flow-node `agentInputVariables[]` entry. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/packager-tool-flow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.204.0-preview.211",
|
|
4
4
|
"description": "UiPath Flow tool implementation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -22,13 +22,14 @@
|
|
|
22
22
|
"author": "UiPath",
|
|
23
23
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@uipath/filesystem": "1.
|
|
25
|
+
"@uipath/filesystem": "1.204.0",
|
|
26
26
|
"@uipath/flow-converter": "0.69.3",
|
|
27
27
|
"@uipath/flow-core": "0.115.4-develop",
|
|
28
28
|
"@uipath/flow-migrations": "0.56.2",
|
|
29
29
|
"@uipath/flow-schema": "0.68.0",
|
|
30
|
-
"@uipath/solutionpackager-tool-core": "1.
|
|
31
|
-
"@uipath/tool-agent": "^2.0.0"
|
|
30
|
+
"@uipath/solutionpackager-tool-core": "1.204.0",
|
|
31
|
+
"@uipath/tool-agent": "^2.0.0",
|
|
32
|
+
"i18next": "^23.16.8"
|
|
32
33
|
},
|
|
33
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "275e01f1f6098d4c3bc9673f5225f33372822534"
|
|
34
35
|
}
|