@uipath/maestro-builder-sdk 6.61.1 → 6.63.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/check-cli.js +79 -8
- package/dist/check.d.ts +80 -0
- package/dist/check.js +123 -5
- package/dist/cli/commands/flow.js +8 -1
- package/dist/cli-result.d.ts +21 -16
- package/dist/cli-result.js +21 -6
- package/dist/core/refusals.d.ts +1 -0
- package/dist/core/refusals.js +3 -0
- package/dist/edit-scope.d.ts +59 -2
- package/dist/edit-scope.js +82 -0
- package/dist/merge-cli.js +55 -8
- package/dist/merge-compile.d.ts +27 -8
- package/dist/merge-compile.js +39 -11
- package/package.json +2 -2
package/dist/check-cli.js
CHANGED
|
@@ -2,19 +2,31 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* check-cli — static-validate an authored flow without emitting.
|
|
4
4
|
*
|
|
5
|
-
* Usage: node --experimental-strip-types check-cli.js <flow.ts | BaseName>
|
|
5
|
+
* Usage: node --experimental-strip-types check-cli.js [--baseline <original.flow>] <flow.ts | BaseName>
|
|
6
6
|
* Prints diagnostics; exits non-zero if any errors.
|
|
7
|
+
*
|
|
8
|
+
* `--baseline <original.flow>` scopes the findings to the edit
|
|
9
|
+
* (docs/CHECK_COMPILE_CONTRACT_DESIGN.md §3.3-3.4): the original is decompiled
|
|
10
|
+
* and checked in memory the same way, and each finding is marked `(edited)` or
|
|
11
|
+
* `(pre-existing)`. A pre-existing finding (its node unchanged, and the
|
|
12
|
+
* original reports it too) is a warning, so only the edit's errors fail the
|
|
13
|
+
* check; a pre-existing lowering one is printed as a probable SDK defect.
|
|
14
|
+
* `--package <name>` trusts a node-type package the original names, as
|
|
15
|
+
* `flow merge` and `flow decompile` take it.
|
|
7
16
|
*/
|
|
8
|
-
import {
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { diagnosticLine, reportDiagnostics, reportResult } from './cli-result.js';
|
|
9
19
|
import { defaultBindingsFile } from './workdir.js';
|
|
10
20
|
import { resolveFlowFile, loadBuiltFlow } from './load.js';
|
|
11
|
-
import { check } from './check.js';
|
|
21
|
+
import { check, checkCompiled, scopeToEdit } from './check.js';
|
|
12
22
|
import { flowCheckOpts, openCliLibrary } from './cli-library.js';
|
|
13
23
|
import { Bindings } from './bindings.js';
|
|
14
|
-
import { runWhenInvokedDirectly } from './cli-run.js';
|
|
24
|
+
import { flagValue, flagValues, runWhenInvokedDirectly } from './cli-run.js';
|
|
15
25
|
import { ensureTypeScriptRuntime } from './node-runtime.js';
|
|
26
|
+
import { withDecompiledOriginal } from './merge-compile.js';
|
|
27
|
+
import { loadNodePackages } from './node-packages.js';
|
|
16
28
|
/** Flags that take a value, so the value is never read as the source argument. */
|
|
17
|
-
const VALUE_FLAGS = new Set(['--library', '--bindings', '--connectors-local']);
|
|
29
|
+
const VALUE_FLAGS = new Set(['--library', '--bindings', '--connectors-local', '--baseline', '--package']);
|
|
18
30
|
/**
|
|
19
31
|
* Library lookups for the checks whose facts live in the connector corpus:
|
|
20
32
|
* event parameters and measured list envelopes, and the library `check`'s
|
|
@@ -41,13 +53,21 @@ function checkOpts(argv) {
|
|
|
41
53
|
export async function run(argv) {
|
|
42
54
|
const arg = argv.find((a, i) => !a.startsWith('--') && !VALUE_FLAGS.has(argv[i - 1])) ?? argv[0];
|
|
43
55
|
if (!arg || arg === '-h' || arg === '--help') {
|
|
44
|
-
console.error('usage: check [--library <dir>] [--connectors-local <dir>] [--bindings <file>] <flow.ts | BaseName>');
|
|
56
|
+
console.error('usage: check [--library <dir>] [--connectors-local <dir>] [--bindings <file>] [--baseline <original.flow> [--package <name>]] <flow.ts | BaseName>');
|
|
57
|
+
return 2;
|
|
58
|
+
}
|
|
59
|
+
const baselinePath = flagValue(argv, '--baseline');
|
|
60
|
+
const original = baselinePath !== undefined ? readBaseline(baselinePath) : undefined;
|
|
61
|
+
if (original instanceof Error) {
|
|
62
|
+
console.error(`check: ${original.message}`);
|
|
63
|
+
reportResult({ error: original.message });
|
|
45
64
|
return 2;
|
|
46
65
|
}
|
|
47
66
|
const file = resolveFlowFile(arg);
|
|
48
67
|
ensureTypeScriptRuntime();
|
|
49
68
|
const built = await loadBuiltFlow(file);
|
|
50
|
-
const
|
|
69
|
+
const opts = checkOpts(argv);
|
|
70
|
+
const diags = baselinePath !== undefined ? await checkAgainstBaseline(built, baselinePath, original, opts, flagValues(argv, '--package')) : check(built, opts);
|
|
51
71
|
reportDiagnostics(diags);
|
|
52
72
|
if (diags.length === 0) {
|
|
53
73
|
console.log('✓ no issues');
|
|
@@ -56,7 +76,58 @@ export async function run(argv) {
|
|
|
56
76
|
for (const d of diags)
|
|
57
77
|
console.log(diagnosticLine(d));
|
|
58
78
|
const errors = diags.filter((d) => d.level === 'error').length;
|
|
59
|
-
console.log(`\n${errors} error(s), ${diags.length - errors} warning(s)`);
|
|
79
|
+
console.log(`\n${baselinePath !== undefined ? baselineSummary(diags) : `${errors} error(s), ${diags.length - errors} warning(s)`}`);
|
|
60
80
|
return errors ? 1 : 0;
|
|
61
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Check `built` and scope its findings to the edit ({@link scopeToEdit}). The
|
|
84
|
+
* baseline is the original decompiled and checked with the same options, as
|
|
85
|
+
* `flow merge --baseline-from` builds its baseline: first the node-type
|
|
86
|
+
* packages the original names are loaded, under the project's trust (or
|
|
87
|
+
* `--package`), so its nodes decompile to the same calls the edited source has.
|
|
88
|
+
*/
|
|
89
|
+
async function checkAgainstBaseline(built, originalPath, original, opts, packages) {
|
|
90
|
+
const loaded = await loadNodePackages(originalPath, original, packages);
|
|
91
|
+
for (const message of loaded.warnings)
|
|
92
|
+
console.error(`check: warning: ${message}`);
|
|
93
|
+
const baseline = await withDecompiledOriginal(originalPath, (b) => checkCompiled(b, opts), (m) => console.error(`check: decompile warning: ${m}`));
|
|
94
|
+
return scopeToEdit(checkCompiled(built, opts), baseline);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The `--baseline` file, parsed, or the usage error to refuse it with: a path
|
|
98
|
+
* that does not exist, or a file that is not a `.flow` (JSON with `nodes`),
|
|
99
|
+
* such as the `.flow.ts` source passed by mistake. Refused before anything is
|
|
100
|
+
* loaded, with the usage exit code, so a bad flag never reads as a failed check.
|
|
101
|
+
*/
|
|
102
|
+
function readBaseline(path) {
|
|
103
|
+
const usage = `--baseline must be the original .flow (JSON) the source was decompiled from: ${path}`;
|
|
104
|
+
if (!existsSync(path))
|
|
105
|
+
return new Error(`${usage} (no such file)`);
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
return new Error(`${usage} (not JSON: ${error instanceof Error ? error.message : String(error)})`);
|
|
112
|
+
}
|
|
113
|
+
if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.nodes)) {
|
|
114
|
+
return new Error(`${usage} (no "nodes" array)`);
|
|
115
|
+
}
|
|
116
|
+
return parsed;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The `--baseline` summary: the errors that fail the check (the edit's), the
|
|
120
|
+
* pre-existing lowering findings (probable SDK defects) and the other
|
|
121
|
+
* pre-existing findings, and the edit's own warnings when there are any. For
|
|
122
|
+
* instance `1 error(s) in edited nodes, 1 pre-existing SDK defect(s), 1 pre-existing warning(s)`.
|
|
123
|
+
*/
|
|
124
|
+
function baselineSummary(diags) {
|
|
125
|
+
const edited = diags.filter((d) => d.origin !== 'pre-existing');
|
|
126
|
+
const pre = diags.filter((d) => d.origin === 'pre-existing');
|
|
127
|
+
const errors = edited.filter((d) => d.level === 'error').length;
|
|
128
|
+
const defects = pre.filter((d) => d.class === 'lowering').length;
|
|
129
|
+
const warnings = edited.length - errors;
|
|
130
|
+
return `${errors} error(s) in edited nodes, ${defects} pre-existing SDK defect(s), ${pre.length - defects} pre-existing warning(s)`
|
|
131
|
+
+ (warnings ? `, ${warnings} warning(s) in edited nodes` : '');
|
|
132
|
+
}
|
|
62
133
|
runWhenInvokedDirectly(import.meta.url, 'check', run);
|
package/dist/check.d.ts
CHANGED
|
@@ -3,12 +3,34 @@ import type { BuiltFlow } from './flow-sdk.js';
|
|
|
3
3
|
import { type EventCheckOpts } from './core/event-checks.js';
|
|
4
4
|
export type { EventParamFields } from './core/event-checks.js';
|
|
5
5
|
import { type ConnectorCheckOpts } from './core/connector-checks.js';
|
|
6
|
+
import { type RefusalClass } from './core/refusals.js';
|
|
6
7
|
export interface Diagnostic {
|
|
7
8
|
level: 'error' | 'warning';
|
|
8
9
|
code: string;
|
|
9
10
|
message: string;
|
|
10
11
|
step?: string;
|
|
11
12
|
suggestion?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The id of the node the finding is on, in the flow `compile` would write:
|
|
15
|
+
* the step's node, or for a step inside a subflow the node inside it.
|
|
16
|
+
* Absent for a finding that names no node (a flow-level rule, a refusal
|
|
17
|
+
* about the whole scope such as two steps with one name).
|
|
18
|
+
*/
|
|
19
|
+
nodeId?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The code's class (`core/refusals.ts`): `lint`, the flow is probably wrong
|
|
22
|
+
* but `compile` would still write it; `lowering`, `compile` cannot write a
|
|
23
|
+
* correct `.flow`; `draft`, an unfinished canvas node. Set on every finding
|
|
24
|
+
* whose code the table lists.
|
|
25
|
+
*/
|
|
26
|
+
class?: RefusalClass;
|
|
27
|
+
/**
|
|
28
|
+
* Only when `check` is given a baseline (`flow check --baseline`, {@link scopeToEdit}):
|
|
29
|
+
* `pre-existing` when the finding's node is unchanged from the baseline and
|
|
30
|
+
* the baseline reports the same code on it, so the edit did not cause it
|
|
31
|
+
* (such a finding is a warning); `edited` for every other finding.
|
|
32
|
+
*/
|
|
33
|
+
origin?: 'edited' | 'pre-existing';
|
|
12
34
|
}
|
|
13
35
|
export interface ListEnvelopeFields {
|
|
14
36
|
envelope: string | null;
|
|
@@ -47,6 +69,64 @@ export interface CheckOpts extends ConnectorCheckOpts, EventCheckOpts {
|
|
|
47
69
|
serializer?: false;
|
|
48
70
|
}
|
|
49
71
|
export declare function check(built: BuiltFlow, opts?: CheckOpts): Diagnostic[];
|
|
72
|
+
/**
|
|
73
|
+
* What {@link checkCompiled} found, with the flow its serializer pass compiled,
|
|
74
|
+
* which {@link scopeToEdit} compares against a baseline.
|
|
75
|
+
*/
|
|
76
|
+
export interface CheckedFlow {
|
|
77
|
+
diagnostics: Diagnostic[];
|
|
78
|
+
/**
|
|
79
|
+
* The collect-mode compile, stand-ins and all (`SerializeOpts.collect`);
|
|
80
|
+
* nothing writes it. `undefined` when collection stopped or the serializer
|
|
81
|
+
* pass was skipped.
|
|
82
|
+
*/
|
|
83
|
+
flow: any;
|
|
84
|
+
/**
|
|
85
|
+
* The refusal that stopped collection (a scope-level one, such as two Ends
|
|
86
|
+
* with one name), or the internal error that did: no serializer finding past
|
|
87
|
+
* it is reported. Absent when collection ran to the end or was skipped.
|
|
88
|
+
*/
|
|
89
|
+
stoppedAt?: {
|
|
90
|
+
code?: string;
|
|
91
|
+
message: string;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `check`, keeping the flow its serializer pass compiled. Each finding is
|
|
96
|
+
* placed on its node (`nodeId`: a refusal on the node collect mode claimed for
|
|
97
|
+
* its step, any other finding on the node its step emitted) and given its
|
|
98
|
+
* code's `class`.
|
|
99
|
+
*/
|
|
100
|
+
export declare function checkCompiled(built: BuiltFlow, opts?: CheckOpts): CheckedFlow;
|
|
101
|
+
/**
|
|
102
|
+
* Scope `edited`'s findings to the edit, against `baseline`: the original
|
|
103
|
+
* `.flow` decompiled and checked the same way (`flow check --baseline`,
|
|
104
|
+
* docs/CHECK_COMPILE_CONTRACT_DESIGN.md §3.3). Every finding gets an `origin`:
|
|
105
|
+
*
|
|
106
|
+
* - `pre-existing` when its node is unchanged from the baseline (by id and
|
|
107
|
+
* content, as merge judges it: `classifyDrafts`; a node inside a subflow
|
|
108
|
+
* by its subflow) AND the baseline reports the same code on that node,
|
|
109
|
+
* once per occurrence. A finding that names no node is `pre-existing` when
|
|
110
|
+
* the baseline reports the same code and message (`splitUnplaced`).
|
|
111
|
+
* - `edited` otherwise, including a finding on an untouched node that the
|
|
112
|
+
* baseline does not have: an edit elsewhere caused it (a deleted variable
|
|
113
|
+
* the node updates, a deleted step it reads).
|
|
114
|
+
*
|
|
115
|
+
* A pre-existing error becomes a warning, so it does not fail the check. A
|
|
116
|
+
* pre-existing `lowering` one also says it is probably an SDK defect, since the
|
|
117
|
+
* designer's node is one the SDK cannot write back: `compile` still refuses it,
|
|
118
|
+
* and `merge --baseline-from` keeps it as saved.
|
|
119
|
+
*
|
|
120
|
+
* When the edited flow's collection stopped at a scope-level refusal, the rest
|
|
121
|
+
* of the flow was never judged, so one more error, CHECK_INCOMPLETE (class
|
|
122
|
+
* `lowering`: `compile` stops at the refusal it names), says so:
|
|
123
|
+
* passing never means "not checked".
|
|
124
|
+
*
|
|
125
|
+
* For instance, an `EMPTY_ARM` on a branch the edit did not touch, which the
|
|
126
|
+
* baseline reports too, comes back as a `pre-existing` warning; the same
|
|
127
|
+
* finding after the edit changed that branch's condition is an `edited` error.
|
|
128
|
+
*/
|
|
129
|
+
export declare function scopeToEdit(edited: CheckedFlow, baseline: CheckedFlow): Diagnostic[];
|
|
50
130
|
/**
|
|
51
131
|
* The ISO-8601 repeating-interval pattern, copied VERBATIM from the
|
|
52
132
|
* `core.trigger.scheduled@1.1` definition (`inputDefinition.then.properties.
|
package/dist/check.js
CHANGED
|
@@ -30,7 +30,18 @@ import { stepDeclaredVariables } from './flow-step-variables.js';
|
|
|
30
30
|
import { plainHttpOnErrorMessage } from './nodes/http/flow/family.js';
|
|
31
31
|
import { resolveVersion, resolveVersionCarried } from './core/nodes/versioned.js';
|
|
32
32
|
import { loopParallelWarning, resolveLoopDefinition, loopVersion } from './loop-version.js';
|
|
33
|
+
import { refusalClass } from './core/refusals.js';
|
|
34
|
+
import { classifyDrafts, findingCounts, placeDiagnostics, splitUnplaced } from './edit-scope.js';
|
|
33
35
|
export function check(built, opts = {}) {
|
|
36
|
+
return checkCompiled(built, opts).diagnostics;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* `check`, keeping the flow its serializer pass compiled. Each finding is
|
|
40
|
+
* placed on its node (`nodeId`: a refusal on the node collect mode claimed for
|
|
41
|
+
* its step, any other finding on the node its step emitted) and given its
|
|
42
|
+
* code's `class`.
|
|
43
|
+
*/
|
|
44
|
+
export function checkCompiled(built, opts = {}) {
|
|
34
45
|
// A connection two connectors use, and an agent resource's `nodeId` another
|
|
35
46
|
// node of the file has, are the serializer's CONNECTION_CONNECTOR_CONFLICT /
|
|
36
47
|
// INLINE_AGENT_RESOURCE_NODE_ID: it claims ids and resolves connections file-wide.
|
|
@@ -44,10 +55,109 @@ export function check(built, opts = {}) {
|
|
|
44
55
|
...(d.suggestion ? { suggestion: d.suggestion } : {}),
|
|
45
56
|
});
|
|
46
57
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
const serialized = opts.serializer !== false
|
|
59
|
+
? serializerRefusals(built, { library: opts.library, bindings: opts.bindings }, diags)
|
|
60
|
+
: { diagnostics: [], flow: undefined };
|
|
61
|
+
diags.push(...serialized.diagnostics);
|
|
62
|
+
const placed = serialized.flow !== undefined ? placeDiagnostics(diags, serialized.flow) : undefined;
|
|
63
|
+
const diagnostics = diags.map((d, i) => {
|
|
64
|
+
const nodeId = d.nodeId ?? (placed?.[i].nodeId || undefined);
|
|
65
|
+
const cls = refusalClass(d.code);
|
|
66
|
+
return { ...d, ...(nodeId !== undefined ? { nodeId } : {}), ...(cls !== undefined ? { class: cls } : {}) };
|
|
67
|
+
});
|
|
68
|
+
return { diagnostics, flow: serialized.flow, ...(serialized.stoppedAt ? { stoppedAt: serialized.stoppedAt } : {}) };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Scope `edited`'s findings to the edit, against `baseline`: the original
|
|
72
|
+
* `.flow` decompiled and checked the same way (`flow check --baseline`,
|
|
73
|
+
* docs/CHECK_COMPILE_CONTRACT_DESIGN.md §3.3). Every finding gets an `origin`:
|
|
74
|
+
*
|
|
75
|
+
* - `pre-existing` when its node is unchanged from the baseline (by id and
|
|
76
|
+
* content, as merge judges it: `classifyDrafts`; a node inside a subflow
|
|
77
|
+
* by its subflow) AND the baseline reports the same code on that node,
|
|
78
|
+
* once per occurrence. A finding that names no node is `pre-existing` when
|
|
79
|
+
* the baseline reports the same code and message (`splitUnplaced`).
|
|
80
|
+
* - `edited` otherwise, including a finding on an untouched node that the
|
|
81
|
+
* baseline does not have: an edit elsewhere caused it (a deleted variable
|
|
82
|
+
* the node updates, a deleted step it reads).
|
|
83
|
+
*
|
|
84
|
+
* A pre-existing error becomes a warning, so it does not fail the check. A
|
|
85
|
+
* pre-existing `lowering` one also says it is probably an SDK defect, since the
|
|
86
|
+
* designer's node is one the SDK cannot write back: `compile` still refuses it,
|
|
87
|
+
* and `merge --baseline-from` keeps it as saved.
|
|
88
|
+
*
|
|
89
|
+
* When the edited flow's collection stopped at a scope-level refusal, the rest
|
|
90
|
+
* of the flow was never judged, so one more error, CHECK_INCOMPLETE (class
|
|
91
|
+
* `lowering`: `compile` stops at the refusal it names), says so:
|
|
92
|
+
* passing never means "not checked".
|
|
93
|
+
*
|
|
94
|
+
* For instance, an `EMPTY_ARM` on a branch the edit did not touch, which the
|
|
95
|
+
* baseline reports too, comes back as a `pre-existing` warning; the same
|
|
96
|
+
* finding after the edit changed that branch's condition is an `edited` error.
|
|
97
|
+
*/
|
|
98
|
+
export function scopeToEdit(edited, baseline) {
|
|
99
|
+
const locate = (c) => placeDiagnostics(c.diagnostics, c.flow)
|
|
100
|
+
.map((d, i) => ({ ...d, nodeId: c.diagnostics[i].nodeId ?? d.nodeId, index: i }));
|
|
101
|
+
const sorted = classifyDrafts(baseline.flow ?? {}, edited.flow ?? {}, baseline.flow, locate(edited));
|
|
102
|
+
// The baseline's findings on each node, by code; each one vouches for one edited finding.
|
|
103
|
+
const onNode = (f) => `${f.nodeId}\n${f.code}`;
|
|
104
|
+
const pool = new Map();
|
|
105
|
+
for (const b of locate(baseline)) {
|
|
106
|
+
if (b.nodeId === '')
|
|
107
|
+
continue;
|
|
108
|
+
pool.set(onNode(b), [...(pool.get(onNode(b)) ?? []), b.message]);
|
|
109
|
+
}
|
|
110
|
+
const preExisting = new Set();
|
|
111
|
+
// Exact messages are matched first: of two findings of one code on a node,
|
|
112
|
+
// the one the baseline has word for word is the pre-existing one. The second
|
|
113
|
+
// pass forgives wording the edit can shift without touching the finding (a
|
|
114
|
+
// "Known steps: ..." list), but only between messages that name the same
|
|
115
|
+
// quoted subjects: `UNKNOWN_REF "lookup" in "audit"` is a different finding
|
|
116
|
+
// from the baseline's `UNKNOWN_REF "ghost" in "audit"`, so it stays edited.
|
|
117
|
+
for (const exact of [true, false]) {
|
|
118
|
+
for (const f of sorted.kept) {
|
|
119
|
+
if (preExisting.has(f.index))
|
|
120
|
+
continue;
|
|
121
|
+
const messages = pool.get(onNode(f)) ?? [];
|
|
122
|
+
const j = exact ? messages.indexOf(f.message) : messages.findIndex((m) => quotedSubjects(m) === quotedSubjects(f.message));
|
|
123
|
+
if (j < 0)
|
|
124
|
+
continue;
|
|
125
|
+
messages.splice(j, 1);
|
|
126
|
+
preExisting.add(f.index);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const f of splitUnplaced(sorted.unplaced, findingCounts(baseline.diagnostics)).known)
|
|
130
|
+
preExisting.add(f.index);
|
|
131
|
+
const out = edited.diagnostics.map((d, i) => {
|
|
132
|
+
if (!preExisting.has(i))
|
|
133
|
+
return { ...d, origin: 'edited' };
|
|
134
|
+
const defect = d.level === 'error' && d.class === 'lowering';
|
|
135
|
+
return {
|
|
136
|
+
...d,
|
|
137
|
+
level: 'warning',
|
|
138
|
+
message: defect ? `${d.message}\n ${PRE_EXISTING_LOWERING_NOTE}` : d.message,
|
|
139
|
+
origin: 'pre-existing',
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
if (edited.stoppedAt) {
|
|
143
|
+
const at = edited.stoppedAt.code !== undefined ? `[${edited.stoppedAt.code}] ` : '';
|
|
144
|
+
out.push({
|
|
145
|
+
level: 'error', code: 'CHECK_INCOMPLETE',
|
|
146
|
+
message: `check stopped at ${at}${edited.stoppedAt.message} Nothing after that refusal was checked, so the edit is not judged. `
|
|
147
|
+
+ 'Fix it, even if the original has it too, and run check again.',
|
|
148
|
+
class: refusalClass('CHECK_INCOMPLETE'), origin: 'edited',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
/** The `"quoted"` names in a finding's message, in order: what it is about, apart from its wording. */
|
|
154
|
+
function quotedSubjects(message) {
|
|
155
|
+
return JSON.stringify(message.match(/"[^"]*"/g) ?? []);
|
|
50
156
|
}
|
|
157
|
+
/** What a pre-existing lowering finding adds to its message ({@link scopeToEdit}). */
|
|
158
|
+
const PRE_EXISTING_LOWERING_NOTE = 'This node is unchanged from the baseline, so this is probably an SDK defect: '
|
|
159
|
+
+ 'the SDK cannot read what the designer wrote. `compile` refuses this node; `merge --baseline-from` keeps it as saved. '
|
|
160
|
+
+ 'Report it: https://github.com/UiPath/builder-sdk/issues';
|
|
51
161
|
/** The error-level diagnostics a custom node's own `diagnostics()` reported (see `serializerRefusals`). */
|
|
52
162
|
const OWN_NODE_ERRORS = new WeakSet();
|
|
53
163
|
/**
|
|
@@ -67,13 +177,20 @@ const OWN_NODE_ERRORS = new WeakSet();
|
|
|
67
177
|
*/
|
|
68
178
|
function serializerRefusals(built, compile, found) {
|
|
69
179
|
const collected = [];
|
|
180
|
+
let flow;
|
|
181
|
+
let stoppedAt;
|
|
70
182
|
try {
|
|
71
|
-
serialize(built, { ...compile, collect: (r) => collected.push(r) });
|
|
183
|
+
flow = serialize(built, { ...compile, collect: (r) => collected.push(r) });
|
|
184
|
+
// Collection stopped: the refusal that stopped it is the last one reported.
|
|
185
|
+
const stop = collected[collected.length - 1];
|
|
186
|
+
if (flow === undefined && stop)
|
|
187
|
+
stoppedAt = { code: stop.code, message: stop.message };
|
|
72
188
|
}
|
|
73
189
|
catch (error) {
|
|
74
190
|
// LIBRARY_REQUIRED is dropped below, so it does not count as something reported.
|
|
75
191
|
if (!found.some((d) => d.level === 'error') && !collected.some((r) => r.code !== 'LIBRARY_REQUIRED'))
|
|
76
192
|
throw error;
|
|
193
|
+
stoppedAt = { message: error instanceof Error ? error.message : String(error) };
|
|
77
194
|
}
|
|
78
195
|
const out = [];
|
|
79
196
|
const seen = new Set();
|
|
@@ -100,9 +217,10 @@ function serializerRefusals(built, compile, found) {
|
|
|
100
217
|
level: 'error', code: r.code, message,
|
|
101
218
|
...(step !== undefined ? { step } : {}),
|
|
102
219
|
...(r.suggestion !== undefined ? { suggestion: r.suggestion } : {}),
|
|
220
|
+
...(r.nodeId !== undefined ? { nodeId: r.nodeId } : {}),
|
|
103
221
|
});
|
|
104
222
|
}
|
|
105
|
-
return out;
|
|
223
|
+
return { diagnostics: out, flow, ...(stoppedAt ? { stoppedAt } : {}) };
|
|
106
224
|
}
|
|
107
225
|
/**
|
|
108
226
|
* `ancestors` is the chain of child flows currently being checked, innermost
|
|
@@ -40,7 +40,10 @@ export function registerFlowCommands(program) {
|
|
|
40
40
|
.option('--source', 'check authored TypeScript')
|
|
41
41
|
.addOption(retiredCompiledOption())
|
|
42
42
|
.option('--library <dir>', 'connector library directory')
|
|
43
|
-
.option('--connectors-local <dir>', 'connection-resolved connector overlay')
|
|
43
|
+
.option('--connectors-local <dir>', 'connection-resolved connector overlay')
|
|
44
|
+
.option('--bindings <file>', 'bindings.json path')
|
|
45
|
+
.option('--baseline <file>', 'the original .flow the source was decompiled from (what merge --baseline-from takes, not the compiled baseline merge --baseline takes): findings on nodes the edit did not touch, which the original has too, become warnings')
|
|
46
|
+
.option('--package <name...>', 'trust and load this node-type package for --baseline (repeatable)');
|
|
44
47
|
// Hidden, not removed: scripts that pass these keep parsing.
|
|
45
48
|
for (const [flags, , description] of RETIRED_COMPILED_CHECK_FLAGS) {
|
|
46
49
|
check.addOption(new Option(flags, description).hideHelp());
|
|
@@ -69,6 +72,10 @@ export function registerFlowCommands(program) {
|
|
|
69
72
|
const argv = [input];
|
|
70
73
|
addValue(argv, '--library', options.library);
|
|
71
74
|
addValue(argv, '--connectors-local', options.connectorsLocal);
|
|
75
|
+
addValue(argv, '--bindings', options.bindings);
|
|
76
|
+
addValue(argv, '--baseline', options.baseline);
|
|
77
|
+
for (const name of options.package ?? [])
|
|
78
|
+
argv.push('--package', name);
|
|
72
79
|
await invoke('flow check', () => import('../../check-cli.js'), argv);
|
|
73
80
|
return;
|
|
74
81
|
}
|
package/dist/cli-result.d.ts
CHANGED
|
@@ -24,30 +24,34 @@ export declare function reportResult(fields: Record<string, unknown>): void;
|
|
|
24
24
|
* @param text - the result as text; a trailing newline is added.
|
|
25
25
|
*/
|
|
26
26
|
export declare function printResult(text: string): void;
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
* shape every family's check (and compile's check pass) uses.
|
|
30
|
-
*/
|
|
31
|
-
export declare function reportDiagnostics(diags: ReadonlyArray<{
|
|
27
|
+
/** A diagnostic as `reportDiagnostics` and `diagnosticLine` read it: any family's, with the fields it has. */
|
|
28
|
+
interface ReportedDiagnostic {
|
|
32
29
|
level: string;
|
|
33
30
|
code: string;
|
|
34
31
|
message: string;
|
|
35
32
|
step?: string;
|
|
36
33
|
suggestion?: string;
|
|
37
|
-
|
|
34
|
+
nodeId?: string;
|
|
35
|
+
class?: string;
|
|
36
|
+
origin?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Report a check's diagnostics as `{ errors, warnings, diagnostics }`, the one
|
|
40
|
+
* shape every family's check (and compile's check pass) uses. Each diagnostic
|
|
41
|
+
* keeps the optional fields it has: `step`, `suggestion`, and Flow's `nodeId`,
|
|
42
|
+
* `class` and (with `check --baseline`) `origin`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function reportDiagnostics(diags: ReadonlyArray<ReportedDiagnostic>): void;
|
|
38
45
|
/**
|
|
39
46
|
* One diagnostic as `check` and `compile` print it: its mark and code, the
|
|
40
|
-
* message, the step it is on (unless the message already names it),
|
|
41
|
-
* suggestion. For instance
|
|
42
|
-
* `✗ [STEP_SWITCH_EXIT_UNKNOWN] no exit named "maybe". … (step "route")
|
|
47
|
+
* message, the step it is on (unless the message already names it), the
|
|
48
|
+
* suggestion, and, with `check --baseline`, its origin. For instance
|
|
49
|
+
* `✗ [STEP_SWITCH_EXIT_UNKNOWN] no exit named "maybe". … (step "route")`, or
|
|
50
|
+
* `⚠ [EMPTY_ARM] … (pre-existing, not counted)`. With an origin, a message's
|
|
51
|
+
* later lines (the note on a pre-existing lowering finding) follow the line
|
|
52
|
+
* the origin ends.
|
|
43
53
|
*/
|
|
44
|
-
export declare function diagnosticLine(d:
|
|
45
|
-
level: string;
|
|
46
|
-
code: string;
|
|
47
|
-
message: string;
|
|
48
|
-
step?: string;
|
|
49
|
-
suggestion?: string;
|
|
50
|
-
}): string;
|
|
54
|
+
export declare function diagnosticLine(d: ReportedDiagnostic): string;
|
|
51
55
|
/**
|
|
52
56
|
* The error diagnostic for a throw that ends a compile: the error's own string
|
|
53
57
|
* `code` when it carries one, else `COMPILE_FAILED`.
|
|
@@ -57,3 +61,4 @@ export declare function thrownDiagnostic(error: unknown): {
|
|
|
57
61
|
code: string;
|
|
58
62
|
message: string;
|
|
59
63
|
};
|
|
64
|
+
export {};
|
package/dist/cli-result.js
CHANGED
|
@@ -38,7 +38,9 @@ export function printResult(text) {
|
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* Report a check's diagnostics as `{ errors, warnings, diagnostics }`, the one
|
|
41
|
-
* shape every family's check (and compile's check pass) uses.
|
|
41
|
+
* shape every family's check (and compile's check pass) uses. Each diagnostic
|
|
42
|
+
* keeps the optional fields it has: `step`, `suggestion`, and Flow's `nodeId`,
|
|
43
|
+
* `class` and (with `check --baseline`) `origin`.
|
|
42
44
|
*/
|
|
43
45
|
export function reportDiagnostics(diags) {
|
|
44
46
|
reportResult({
|
|
@@ -48,21 +50,34 @@ export function reportDiagnostics(diags) {
|
|
|
48
50
|
level: d.level, code: d.code, message: d.message,
|
|
49
51
|
...(d.step !== undefined ? { step: d.step } : {}),
|
|
50
52
|
...(d.suggestion ? { suggestion: d.suggestion } : {}),
|
|
53
|
+
...(d.nodeId !== undefined ? { nodeId: d.nodeId } : {}),
|
|
54
|
+
...(d.class !== undefined ? { class: d.class } : {}),
|
|
55
|
+
...(d.origin !== undefined ? { origin: d.origin } : {}),
|
|
51
56
|
})),
|
|
52
57
|
});
|
|
53
58
|
}
|
|
54
59
|
/**
|
|
55
60
|
* One diagnostic as `check` and `compile` print it: its mark and code, the
|
|
56
|
-
* message, the step it is on (unless the message already names it),
|
|
57
|
-
* suggestion. For instance
|
|
58
|
-
* `✗ [STEP_SWITCH_EXIT_UNKNOWN] no exit named "maybe". … (step "route")
|
|
61
|
+
* message, the step it is on (unless the message already names it), the
|
|
62
|
+
* suggestion, and, with `check --baseline`, its origin. For instance
|
|
63
|
+
* `✗ [STEP_SWITCH_EXIT_UNKNOWN] no exit named "maybe". … (step "route")`, or
|
|
64
|
+
* `⚠ [EMPTY_ARM] … (pre-existing, not counted)`. With an origin, a message's
|
|
65
|
+
* later lines (the note on a pre-existing lowering finding) follow the line
|
|
66
|
+
* the origin ends.
|
|
59
67
|
*/
|
|
60
68
|
export function diagnosticLine(d) {
|
|
61
69
|
// A step inside a subflow is `<caller> › <step>`; the message names the innermost one.
|
|
62
70
|
const named = d.step !== undefined && d.message.includes(`"${d.step.split(' › ').pop()}"`);
|
|
63
|
-
|
|
64
|
-
+ (d.step !== undefined && !named ? ` (step "${d.step}")` : '')
|
|
71
|
+
const suffix = (d.step !== undefined && !named ? ` (step "${d.step}")` : '')
|
|
65
72
|
+ (d.suggestion ? ` (did you mean "${d.suggestion}"?)` : '');
|
|
73
|
+
const mark = `${d.level === 'error' ? '✗' : '⚠'} [${d.code}]`;
|
|
74
|
+
if (d.origin === undefined)
|
|
75
|
+
return `${mark} ${d.message}${suffix}`;
|
|
76
|
+
// With a baseline the origin ends the finding's first line, where it is seen.
|
|
77
|
+
const origin = d.origin === 'edited' ? ' (edited)'
|
|
78
|
+
: d.class === 'lowering' ? ' (pre-existing)' : ' (pre-existing, not counted)';
|
|
79
|
+
const [first, ...rest] = d.message.split('\n');
|
|
80
|
+
return [`${mark} ${first}${suffix}${origin}`, ...rest].join('\n');
|
|
66
81
|
}
|
|
67
82
|
/**
|
|
68
83
|
* The error diagnostic for a throw that ends a compile: the error's own string
|
package/dist/core/refusals.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ export declare const REFUSALS: {
|
|
|
55
55
|
readonly CARRIED_DEFINITION_CONFLICT: "lowering";
|
|
56
56
|
readonly CARRIED_DEFINITION_INVALID: "lowering";
|
|
57
57
|
readonly CEQL_UNPARSEABLE: "lowering";
|
|
58
|
+
readonly CHECK_INCOMPLETE: "lowering";
|
|
58
59
|
readonly COMPILE_FAILED: "lowering";
|
|
59
60
|
readonly CONNECTION_CONNECTOR_CONFLICT: "draft";
|
|
60
61
|
readonly CONNECTION_STUB: "lint";
|
package/dist/core/refusals.js
CHANGED
|
@@ -93,6 +93,9 @@ export const REFUSALS = {
|
|
|
93
93
|
CARRIED_DEFINITION_CONFLICT: 'lowering',
|
|
94
94
|
CARRIED_DEFINITION_INVALID: 'lowering',
|
|
95
95
|
CEQL_UNPARSEABLE: 'lowering',
|
|
96
|
+
// Only `check --baseline` reports it: collection stopped at a scope-level
|
|
97
|
+
// refusal, and `compile` (and `merge --baseline-from`) stops at that refusal.
|
|
98
|
+
CHECK_INCOMPLETE: 'lowering',
|
|
96
99
|
// A throw that is no refusal, which `compile` cannot get past: check reports it on its step.
|
|
97
100
|
COMPILE_FAILED: 'lowering',
|
|
98
101
|
CONNECTION_CONNECTOR_CONFLICT: 'draft',
|
package/dist/edit-scope.d.ts
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
*
|
|
13
13
|
* For instance, a canvas Slack node with no connection yet that the author did
|
|
14
14
|
* not touch is `kept`; the same node with a new message text is `refused`.
|
|
15
|
+
* A node the SDK cannot lower is compared by the stand-ins the two collect-mode
|
|
16
|
+
* compiles emit for it ({@link isStandIn}), and kept only when the original's
|
|
17
|
+
* own round trip refuses it too ({@link classifyLowering}).
|
|
15
18
|
*
|
|
16
19
|
* Pure: no file access, so a portable entry (`check.ts`, `merge.ts`) can use
|
|
17
20
|
* it. Reading the original and compiling its baseline is merge-compile.ts's.
|
|
@@ -70,6 +73,13 @@ export declare function byId(nodes: AnyNode[] | undefined): Map<string, AnyNode>
|
|
|
70
73
|
* decide "did the user edit this node?" against the baseline.
|
|
71
74
|
*/
|
|
72
75
|
export declare function nodeUnchanged(a: AnyNode | undefined, b: AnyNode | undefined): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Whether `n` is the opaque node a collect-mode compile emits in place of a
|
|
78
|
+
* step it could not lower (serialize.ts `standInNode`): the node type, the
|
|
79
|
+
* step's `{ version }`, and `inputs: { standIn: true, spec }`, the spec as
|
|
80
|
+
* authored. It is never written: merge restores the original node or refuses.
|
|
81
|
+
*/
|
|
82
|
+
export declare function isStandIn(n: AnyNode | undefined): boolean;
|
|
73
83
|
/**
|
|
74
84
|
* The keys of the subflows `mergeFlow` (merge.ts) takes from `edited` rather than
|
|
75
85
|
* restoring from `original`: every subflow that is new, that the original does
|
|
@@ -96,9 +106,15 @@ export declare function isKept(e: AnyNode, origNodes: Map<string, AnyNode>, base
|
|
|
96
106
|
* Why a finding is an error: `edited`, its node is one the author changed or
|
|
97
107
|
* added; `subflow`, it sits in a subflow the edit changed, which merge takes
|
|
98
108
|
* whole; `unplaced`, it names no node and the original's own round trip does
|
|
99
|
-
* not report it, so the edit introduced it.
|
|
109
|
+
* not report it, so the edit introduced it. Two more are for a lowering
|
|
110
|
+
* refusal on a node merge would keep ({@link classifyLowering}):
|
|
111
|
+
* `introduced`, the original's own round trip does not report it on that node,
|
|
112
|
+
* so an edit elsewhere broke it; `unrestorable`, restoring the node from the
|
|
113
|
+
* original would not undo it; `uncompared`, the node is the original's, but
|
|
114
|
+
* there is no collect-mode compile of the original to compare its stand-in
|
|
115
|
+
* with (no `--baseline-from`), so merge cannot tell whether the edit touched it.
|
|
100
116
|
*/
|
|
101
|
-
export type TakenReason = 'edited' | 'subflow' | 'unplaced';
|
|
117
|
+
export type TakenReason = 'edited' | 'subflow' | 'unplaced' | 'introduced' | 'unrestorable' | 'uncompared';
|
|
102
118
|
/**
|
|
103
119
|
* A finding on a node merge takes from the edited flow, with the reason it
|
|
104
120
|
* takes it: `edited`, the author changed or added the node; `subflow`, the
|
|
@@ -135,6 +151,47 @@ export declare function classifyDrafts<T extends {
|
|
|
135
151
|
refused: Refused<T>[];
|
|
136
152
|
unplaced: T[];
|
|
137
153
|
};
|
|
154
|
+
/** A refusal a compile reported on the node `nodeId` (`''` when it names none). */
|
|
155
|
+
export interface NodeRefusal {
|
|
156
|
+
nodeId: string;
|
|
157
|
+
code: string;
|
|
158
|
+
message: string;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Sort the lowering refusals of a collect-mode compile of the edited flow
|
|
162
|
+
* (`SerializeOpts.collect`, design D3) by what merge does with their node, as
|
|
163
|
+
* {@link classifyDrafts} does for drafts, then hold a kept one to the rest of
|
|
164
|
+
* what "pre-existing" means (docs/CHECK_COMPILE_CONTRACT_DESIGN.md §3.3). It
|
|
165
|
+
* stays `kept`, and merge restores the original node, only when:
|
|
166
|
+
*
|
|
167
|
+
* - the ORIGINAL's own round trip (`baselineRefusals`, the baseline compiled
|
|
168
|
+
* in collect mode) reports the same code on the same node; else it is
|
|
169
|
+
* refused as `introduced`: an edit elsewhere broke a node the author did
|
|
170
|
+
* not touch (a deleted node it reads), and restoring it would keep the
|
|
171
|
+
* break;
|
|
172
|
+
* - restoring the node undoes the refusal; else it is refused as
|
|
173
|
+
* `unrestorable`. That holds for a root node the compile stood in for
|
|
174
|
+
* ({@link isStandIn}: its own emission failed, and its edges are wired as
|
|
175
|
+
* any node's), and for any node of an untouched subflow, which merge
|
|
176
|
+
* restores whole. A refusal on a root node that did lower (an edge out of
|
|
177
|
+
* it, a join it feeds) is about edges merge takes from the edited flow.
|
|
178
|
+
* The {@link NEVER_KEPT_CODES} are `unrestorable` too.
|
|
179
|
+
*
|
|
180
|
+
* `baselineRefusals` is `undefined` when the baseline was not compiled in
|
|
181
|
+
* collect mode (a precompiled `--baseline` file, or none). Then a stand-in has
|
|
182
|
+
* nothing to equal (a precompiled baseline holds the node lowered, or the file
|
|
183
|
+
* would not exist), and nothing says how the original's round trip treats the
|
|
184
|
+
* node, so every refusal on a node the original has is `uncompared`, never
|
|
185
|
+
* `edited`: the author may not have touched it.
|
|
186
|
+
*
|
|
187
|
+
* For instance a canvas Jira node whose input the SDK cannot read, untouched,
|
|
188
|
+
* is `kept`; the same node with a changed summary is `refused` as `edited`.
|
|
189
|
+
*/
|
|
190
|
+
export declare function classifyLowering<T extends NodeRefusal>(original: any, edited: any, baseline: any, refusals: readonly T[], baselineRefusals: readonly NodeRefusal[] | undefined): {
|
|
191
|
+
kept: Kept<T>[];
|
|
192
|
+
refused: Refused<T>[];
|
|
193
|
+
unplaced: T[];
|
|
194
|
+
};
|
|
138
195
|
/**
|
|
139
196
|
* A `check()` diagnostic, placed on the node its step emitted (`''` when it
|
|
140
197
|
* names no node); `subflow` is the calling step when the finding is inside a
|
package/dist/edit-scope.js
CHANGED
|
@@ -15,9 +15,25 @@ export function nodeUnchanged(a, b) {
|
|
|
15
15
|
return false;
|
|
16
16
|
if (a.type !== b.type)
|
|
17
17
|
return false;
|
|
18
|
+
// A step that fails to lower has no compiled node on either side, only the
|
|
19
|
+
// collect-mode stand-in (D5): equal stand-ins mean the edit did not touch the
|
|
20
|
+
// spec. Its `typeVersion` is the author's `{ version }` pin, so it counts.
|
|
21
|
+
if (isStandIn(a) || isStandIn(b)) {
|
|
22
|
+
return isStandIn(a) && isStandIn(b) && a.typeVersion === b.typeVersion
|
|
23
|
+
&& JSON.stringify(a.inputs) === JSON.stringify(b.inputs);
|
|
24
|
+
}
|
|
18
25
|
return JSON.stringify(a.inputs ?? {}) === JSON.stringify(b.inputs ?? {})
|
|
19
26
|
&& JSON.stringify(outputSources(a)) === JSON.stringify(outputSources(b));
|
|
20
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Whether `n` is the opaque node a collect-mode compile emits in place of a
|
|
30
|
+
* step it could not lower (serialize.ts `standInNode`): the node type, the
|
|
31
|
+
* step's `{ version }`, and `inputs: { standIn: true, spec }`, the spec as
|
|
32
|
+
* authored. It is never written: merge restores the original node or refuses.
|
|
33
|
+
*/
|
|
34
|
+
export function isStandIn(n) {
|
|
35
|
+
return n?.inputs?.standIn === true;
|
|
36
|
+
}
|
|
21
37
|
/** Each output's `source` by output name; outputs without one are left out. */
|
|
22
38
|
function outputSources(n) {
|
|
23
39
|
const sources = {};
|
|
@@ -160,6 +176,72 @@ export function classifyDrafts(original, edited, baseline, findings) {
|
|
|
160
176
|
}
|
|
161
177
|
return { kept, refused, unplaced };
|
|
162
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Lowering codes merge never keeps as saved, even on an untouched node:
|
|
181
|
+
* `COMPILE_FAILED` is a throw the SDK has no refusal for (a crash, not a
|
|
182
|
+
* finding to step over), and `LIBRARY_REQUIRED` is about compile's inputs,
|
|
183
|
+
* not about the node.
|
|
184
|
+
*/
|
|
185
|
+
const NEVER_KEPT_CODES = new Set(['COMPILE_FAILED', 'LIBRARY_REQUIRED']);
|
|
186
|
+
/**
|
|
187
|
+
* Sort the lowering refusals of a collect-mode compile of the edited flow
|
|
188
|
+
* (`SerializeOpts.collect`, design D3) by what merge does with their node, as
|
|
189
|
+
* {@link classifyDrafts} does for drafts, then hold a kept one to the rest of
|
|
190
|
+
* what "pre-existing" means (docs/CHECK_COMPILE_CONTRACT_DESIGN.md §3.3). It
|
|
191
|
+
* stays `kept`, and merge restores the original node, only when:
|
|
192
|
+
*
|
|
193
|
+
* - the ORIGINAL's own round trip (`baselineRefusals`, the baseline compiled
|
|
194
|
+
* in collect mode) reports the same code on the same node; else it is
|
|
195
|
+
* refused as `introduced`: an edit elsewhere broke a node the author did
|
|
196
|
+
* not touch (a deleted node it reads), and restoring it would keep the
|
|
197
|
+
* break;
|
|
198
|
+
* - restoring the node undoes the refusal; else it is refused as
|
|
199
|
+
* `unrestorable`. That holds for a root node the compile stood in for
|
|
200
|
+
* ({@link isStandIn}: its own emission failed, and its edges are wired as
|
|
201
|
+
* any node's), and for any node of an untouched subflow, which merge
|
|
202
|
+
* restores whole. A refusal on a root node that did lower (an edge out of
|
|
203
|
+
* it, a join it feeds) is about edges merge takes from the edited flow.
|
|
204
|
+
* The {@link NEVER_KEPT_CODES} are `unrestorable` too.
|
|
205
|
+
*
|
|
206
|
+
* `baselineRefusals` is `undefined` when the baseline was not compiled in
|
|
207
|
+
* collect mode (a precompiled `--baseline` file, or none). Then a stand-in has
|
|
208
|
+
* nothing to equal (a precompiled baseline holds the node lowered, or the file
|
|
209
|
+
* would not exist), and nothing says how the original's round trip treats the
|
|
210
|
+
* node, so every refusal on a node the original has is `uncompared`, never
|
|
211
|
+
* `edited`: the author may not have touched it.
|
|
212
|
+
*
|
|
213
|
+
* For instance a canvas Jira node whose input the SDK cannot read, untouched,
|
|
214
|
+
* is `kept`; the same node with a changed summary is `refused` as `edited`.
|
|
215
|
+
*/
|
|
216
|
+
export function classifyLowering(original, edited, baseline, refusals, baselineRefusals) {
|
|
217
|
+
const sorted = classifyDrafts(original, edited, baseline, refusals);
|
|
218
|
+
if (baselineRefusals === undefined) {
|
|
219
|
+
const originalIds = new Set([
|
|
220
|
+
...(original?.nodes ?? []).map((n) => n.id),
|
|
221
|
+
...Object.values(original?.subflows ?? {}).flatMap((sf) => (sf?.nodes ?? []).map((n) => n.id)),
|
|
222
|
+
]);
|
|
223
|
+
const refused = [...sorted.kept, ...sorted.refused].map((f) => ({
|
|
224
|
+
...f,
|
|
225
|
+
reason: originalIds.has(f.nodeId) ? 'uncompared' : f.reason ?? 'edited',
|
|
226
|
+
}));
|
|
227
|
+
return { kept: [], refused, unplaced: sorted.unplaced };
|
|
228
|
+
}
|
|
229
|
+
const onNode = (r) => `${r.nodeId}\n${r.code}`;
|
|
230
|
+
const inBaseline = new Set(baselineRefusals.filter((r) => r.nodeId !== '').map(onNode));
|
|
231
|
+
const editedNodes = byId(edited?.nodes);
|
|
232
|
+
const kept = [];
|
|
233
|
+
const refused = [...sorted.refused];
|
|
234
|
+
for (const f of sorted.kept) {
|
|
235
|
+
const restorable = !NEVER_KEPT_CODES.has(f.code) && (f.subflow !== undefined || isStandIn(editedNodes.get(f.nodeId)));
|
|
236
|
+
if (!restorable)
|
|
237
|
+
refused.push({ ...f, reason: 'unrestorable' });
|
|
238
|
+
else if (!inBaseline.has(onNode(f)))
|
|
239
|
+
refused.push({ ...f, reason: 'introduced' });
|
|
240
|
+
else
|
|
241
|
+
kept.push(f);
|
|
242
|
+
}
|
|
243
|
+
return { kept, refused, unplaced: sorted.unplaced };
|
|
244
|
+
}
|
|
163
245
|
/**
|
|
164
246
|
* Place each `check()` diagnostic on the node its step emitted in `editedFlow`, so
|
|
165
247
|
* {@link classifyDrafts} can sort it: a root step on its node (by label, then
|
package/dist/merge-cli.js
CHANGED
|
@@ -9,8 +9,14 @@
|
|
|
9
9
|
* `--baseline-from <original.flow>` makes merge build the baseline itself
|
|
10
10
|
* (decompile + compile, nothing written), and an edited `.flow.ts` makes merge
|
|
11
11
|
* compile it: both compiles tolerate DRAFT nodes, and merge refuses a draft
|
|
12
|
-
* only where it would take the node from the edited flow.
|
|
13
|
-
*
|
|
12
|
+
* only where it would take the node from the edited flow. Both also run in
|
|
13
|
+
* collect mode, so a node the SDK cannot lower (#1402) is stood in for: merge
|
|
14
|
+
* restores it from the original when the edit did not touch it and the
|
|
15
|
+
* original's own round trip refuses it too (`LOWERING_NODE_KEPT`, printed as a
|
|
16
|
+
* probable SDK defect), and refuses the write when the edit changed it. See
|
|
17
|
+
* merge-compile.ts and edit-scope.ts. Neither warning depends on `--strict`:
|
|
18
|
+
* both nodes are written as the canvas saved them; `--strict` still refuses a
|
|
19
|
+
* stand-in whose wiring changed the round trip.
|
|
14
20
|
*
|
|
15
21
|
* `edited.flow` is compile(decompile(original) after your edits). `--baseline`
|
|
16
22
|
* is compile(decompile(original)) BEFORE any edit — supplying it enables the
|
|
@@ -57,7 +63,7 @@ import { Bindings } from './bindings.js';
|
|
|
57
63
|
import { defaultBindingsFile } from './workdir.js';
|
|
58
64
|
import { loadNodePackages } from './node-packages.js';
|
|
59
65
|
import { baselineFromOriginal, checkEdited, compileSourceDraftTolerant } from './merge-compile.js';
|
|
60
|
-
import { classifyDrafts, findingCounts, splitUnplaced } from './edit-scope.js';
|
|
66
|
+
import { classifyDrafts, classifyLowering, findingCounts, splitUnplaced } from './edit-scope.js';
|
|
61
67
|
const USAGE = 'usage: flow-merge <original.flow> <edited.flow | Edited.flow.ts> [-o merged.flow] '
|
|
62
68
|
+ '[--baseline <baseline.flow> | --baseline-from <original.flow>] [--library <dir>] [--bindings <file>] [--strict]';
|
|
63
69
|
/**
|
|
@@ -185,6 +191,7 @@ export async function run(argv) {
|
|
|
185
191
|
const compileOpts = { library, bindings };
|
|
186
192
|
const original = JSON.parse(readFileSync(originalPath, 'utf8'));
|
|
187
193
|
let drafts = [];
|
|
194
|
+
let lowering = [];
|
|
188
195
|
let diagnostics = [];
|
|
189
196
|
let edited;
|
|
190
197
|
let editedSidecars;
|
|
@@ -198,6 +205,7 @@ export async function run(argv) {
|
|
|
198
205
|
edited = compiled.flow;
|
|
199
206
|
editedSidecars = compiled.sidecars;
|
|
200
207
|
drafts = compiled.drafts;
|
|
208
|
+
lowering = compiled.lowering.map(onNode);
|
|
201
209
|
diagnostics = checkEdited(compiled.built, edited, flowCheckOpts(library, bindings));
|
|
202
210
|
}
|
|
203
211
|
else {
|
|
@@ -229,6 +237,9 @@ export async function run(argv) {
|
|
|
229
237
|
// check()'s errors follow the same rule; its warnings are printed as compile prints them.
|
|
230
238
|
const { kept, refused, unplaced } = classifyDrafts(original, edited, baseline, drafts);
|
|
231
239
|
const checked = classifyDrafts(original, edited, baseline, diagnostics.filter((d) => d.level === 'error'));
|
|
240
|
+
// A node the SDK cannot lower follows the draft rule, held to the baseline:
|
|
241
|
+
// kept only when the original's own round trip refuses it the same way.
|
|
242
|
+
const lowered = classifyLowering(original, edited, baseline, lowering, baselineCompile?.lowering.map(onNode));
|
|
232
243
|
for (const d of diagnostics)
|
|
233
244
|
if (d.level === 'warning')
|
|
234
245
|
console.error(` ⚠ [${d.code}] ${d.message}`);
|
|
@@ -237,6 +248,14 @@ export async function run(argv) {
|
|
|
237
248
|
? ` ⚠ [DRAFT_NODE_KEPT] "${d.nodeId}" is unfinished in the original (${d.code}); it is inside subflow "${d.subflow}", which the edit did not touch, so the original subflow is kept as is.`
|
|
238
249
|
: ` ⚠ [DRAFT_NODE_KEPT] "${d.nodeId}" is unfinished in the original (${d.code}); it is untouched, so the original node is kept as is.`);
|
|
239
250
|
}
|
|
251
|
+
for (const d of lowered.kept) {
|
|
252
|
+
const where = d.subflow !== undefined
|
|
253
|
+
? `is inside subflow "${d.subflow}", which the edit did not touch, so the original subflow is kept as is`
|
|
254
|
+
: 'is untouched, so the original node is kept as is';
|
|
255
|
+
console.error(` ⚠ [LOWERING_NODE_KEPT] "${d.nodeId}" ${where}, but the SDK cannot compile it (${d.code}). `
|
|
256
|
+
+ 'This is probably an SDK defect: the SDK cannot read what the designer wrote. Report it: https://github.com/UiPath/builder-sdk/issues');
|
|
257
|
+
console.error(` ${d.message}`);
|
|
258
|
+
}
|
|
240
259
|
// A finding merge cannot place on a node — an unnamed End's `.return`, a
|
|
241
260
|
// flow-level finding — is printed when the ORIGINAL's own round trip reports
|
|
242
261
|
// it too (the designer's file had it; check has already said it), and
|
|
@@ -245,16 +264,18 @@ export async function run(argv) {
|
|
|
245
264
|
// one is refused, as before.
|
|
246
265
|
const baselineFindings = baselineCompile === undefined ? undefined : findingCounts([
|
|
247
266
|
...baselineCompile.drafts,
|
|
267
|
+
...baselineCompile.lowering,
|
|
248
268
|
...checkEdited(baselineCompile.built, baselineCompile.flow, flowCheckOpts(library, bindings)).filter((d) => d.level === 'error'),
|
|
249
269
|
]);
|
|
250
270
|
const unplacedDrafts = splitUnplaced(unplaced, baselineFindings);
|
|
251
271
|
const unplacedChecks = splitUnplaced(checked.unplaced, baselineFindings);
|
|
252
|
-
|
|
272
|
+
const unplacedLowering = splitUnplaced(lowered.unplaced, baselineFindings);
|
|
273
|
+
for (const d of [...unplacedDrafts.known, ...unplacedChecks.known, ...unplacedLowering.known]) {
|
|
253
274
|
console.error(` ⚠ [CHECK_FINDING_UNPLACED] ${d.code} names no node, and the original's own round trip reports it too, so it does not stop the write: ${d.message}`);
|
|
254
275
|
}
|
|
255
276
|
const errors = [
|
|
256
|
-
...refused, ...checked.refused,
|
|
257
|
-
...[...unplacedDrafts.introduced, ...unplacedChecks.introduced].map((d) => ({ ...d, reason: 'unplaced' })),
|
|
277
|
+
...refused, ...checked.refused, ...lowered.refused,
|
|
278
|
+
...[...unplacedDrafts.introduced, ...unplacedChecks.introduced, ...unplacedLowering.introduced].map((d) => ({ ...d, reason: 'unplaced' })),
|
|
258
279
|
];
|
|
259
280
|
if (errors.length) {
|
|
260
281
|
for (const d of errors) {
|
|
@@ -262,10 +283,17 @@ export async function run(argv) {
|
|
|
262
283
|
? 'the edited flow (this finding names no node, and the original\'s own round trip does not report it, so the edit introduced it)'
|
|
263
284
|
: d.reason === 'subflow'
|
|
264
285
|
? `"${d.nodeId}" is inside subflow "${d.subflow}", which the edit changed, so merge takes that subflow whole; finish it`
|
|
265
|
-
:
|
|
286
|
+
: d.reason === 'introduced'
|
|
287
|
+
? `"${d.nodeId}" is untouched, but the original's own round trip does not refuse it, so an edit elsewhere broke it`
|
|
288
|
+
: d.reason === 'unrestorable'
|
|
289
|
+
? unrestorable(d.nodeId, d.code)
|
|
290
|
+
: d.reason === 'uncompared'
|
|
291
|
+
? `"${d.nodeId}" is the original's, but the SDK cannot compile it, and ${baseline ? 'a precompiled --baseline has no record of' : 'with no baseline merge cannot tell'} `
|
|
292
|
+
+ 'whether the edit touched it; pass --baseline-from <original.flow> (instead of --baseline) so merge compiles the original itself and can compare the node'
|
|
293
|
+
: `"${d.nodeId}" is changed or new in the edited flow`;
|
|
266
294
|
console.error(` ✗ [${d.code}] ${where}: ${d.message}`);
|
|
267
295
|
}
|
|
268
|
-
console.error(`flow-merge: ${errors.length} error(s) in nodes merge takes from the edited flow; finish
|
|
296
|
+
console.error(`flow-merge: ${errors.length} error(s) in nodes merge takes from the edited flow; each line says what to do (finish the node, revert the edit, or pass what merge needs). Nothing written.`);
|
|
269
297
|
return 1;
|
|
270
298
|
}
|
|
271
299
|
// Each compile writes `<source>/` beside its own output, so a baseline in the
|
|
@@ -345,4 +373,23 @@ export async function run(argv) {
|
|
|
345
373
|
});
|
|
346
374
|
return 0;
|
|
347
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* The error for a refusal on an untouched node that restoring it from the
|
|
378
|
+
* original would not undo, with the step to take: a crash is an SDK defect;
|
|
379
|
+
* a missing input to compile (the node library) is the caller's to pass; any
|
|
380
|
+
* other is about the edges merge takes from the edited flow.
|
|
381
|
+
*/
|
|
382
|
+
function unrestorable(nodeId, code) {
|
|
383
|
+
const report = 'https://github.com/UiPath/builder-sdk/issues';
|
|
384
|
+
if (code === 'COMPILE_FAILED')
|
|
385
|
+
return `"${nodeId}" is untouched, but the SDK failed while compiling it; this is an SDK defect, report it: ${report}`;
|
|
386
|
+
if (code === 'LIBRARY_REQUIRED')
|
|
387
|
+
return `"${nodeId}" is untouched, but compiling it needs the node library; pass --library <dir> and merge again`;
|
|
388
|
+
return `"${nodeId}" is untouched, but restoring it from the original would not undo this: revert the edit's changes to the edges `
|
|
389
|
+
+ `into or out of it; if there are none, this is an SDK defect, report it: ${report}`;
|
|
390
|
+
}
|
|
391
|
+
/** A collect-mode refusal as edit-scope.ts sorts it: on its node, or on none (`''`). */
|
|
392
|
+
function onNode(r) {
|
|
393
|
+
return { nodeId: r.nodeId ?? '', code: r.code, message: r.message };
|
|
394
|
+
}
|
|
348
395
|
runWhenInvokedDirectly(import.meta.url, 'flow-merge', run);
|
package/dist/merge-compile.d.ts
CHANGED
|
@@ -1,36 +1,55 @@
|
|
|
1
|
-
import { type DraftFinding, type SerializeOpts } from './serialize.js';
|
|
1
|
+
import { type CollectedRefusal, type DraftFinding, type SerializeOpts } from './serialize.js';
|
|
2
2
|
import { type CheckOpts } from './check.js';
|
|
3
3
|
import { type PlacedDiagnostic } from './edit-scope.js';
|
|
4
4
|
import type { BuiltFlow } from './flow-sdk.js';
|
|
5
5
|
/**
|
|
6
|
-
* A compile's flow, the draft refusals it reported instead of throwing,
|
|
7
|
-
*
|
|
6
|
+
* A compile's flow, the draft refusals it reported instead of throwing, the
|
|
7
|
+
* lowering refusals collect mode reported on the steps it stood in for (or on
|
|
8
|
+
* no step), and the agent sidecars (`<source>/agent.json`, resource files) it
|
|
9
|
+
* produced, none written.
|
|
8
10
|
*/
|
|
9
11
|
export interface DraftTolerantCompile {
|
|
10
12
|
flow: any;
|
|
11
13
|
drafts: DraftFinding[];
|
|
14
|
+
lowering: CollectedRefusal[];
|
|
12
15
|
sidecars: Array<{
|
|
13
16
|
path: string;
|
|
14
17
|
json: unknown;
|
|
15
18
|
}>;
|
|
16
19
|
}
|
|
17
|
-
/**
|
|
18
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Compile `built` with draft tolerance, in collect mode: a step whose
|
|
22
|
+
* emission is refused is reported in `lowering` and stood in for, so the
|
|
23
|
+
* rest of the flow is still compiled. A scope-level refusal (two steps with
|
|
24
|
+
* one name, a variable plan) leaves no flow to merge, so it is thrown, as an
|
|
25
|
+
* ordinary compile throws it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function compileDraftTolerant(built: BuiltFlow, opts?: Omit<SerializeOpts, 'draft' | 'collect'>): DraftTolerantCompile;
|
|
19
28
|
/**
|
|
20
29
|
* Load a `.flow.ts` source and compile it with draft tolerance.
|
|
21
30
|
* The caller has already called `ensureTypeScriptRuntime()`.
|
|
22
31
|
*/
|
|
23
|
-
export declare function compileSourceDraftTolerant(file: string, opts?: Omit<SerializeOpts, 'draft'>): Promise<DraftTolerantCompile & {
|
|
32
|
+
export declare function compileSourceDraftTolerant(file: string, opts?: Omit<SerializeOpts, 'draft' | 'collect'>): Promise<DraftTolerantCompile & {
|
|
24
33
|
built: BuiltFlow;
|
|
25
34
|
}>;
|
|
26
35
|
/**
|
|
27
36
|
* The baseline of `originalPath`: decompile it (reading agent sidecars beside
|
|
28
|
-
* it, as `flow decompile` does), then compile the source with draft tolerance
|
|
37
|
+
* it, as `flow decompile` does), then compile the source with draft tolerance,
|
|
38
|
+
* in collect mode ({@link compileDraftTolerant}).
|
|
29
39
|
* The source goes to a temporary directory that is removed afterwards.
|
|
30
40
|
*/
|
|
31
|
-
export declare function baselineFromOriginal(originalPath: string, opts?: Omit<SerializeOpts, 'draft'>, warn?: (message: string) => void): Promise<DraftTolerantCompile & {
|
|
41
|
+
export declare function baselineFromOriginal(originalPath: string, opts?: Omit<SerializeOpts, 'draft' | 'collect'>, warn?: (message: string) => void): Promise<DraftTolerantCompile & {
|
|
32
42
|
built: BuiltFlow;
|
|
33
43
|
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Decompile the `.flow` at `originalPath` (reading agent sidecars beside it, as
|
|
46
|
+
* `flow decompile` does), load the source, and hand the built flow to `use`
|
|
47
|
+
* while the source is still on disk. `flow merge --baseline-from` compiles it
|
|
48
|
+
* ({@link baselineFromOriginal}); `flow check --baseline` checks it. The source
|
|
49
|
+
* goes to a temporary directory that is removed afterwards. The caller has
|
|
50
|
+
* already called `ensureTypeScriptRuntime()`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function withDecompiledOriginal<T>(originalPath: string, use: (built: BuiltFlow) => T | Promise<T>, warn?: (message: string) => void): Promise<T>;
|
|
34
53
|
/**
|
|
35
54
|
* Run `check()` on the edited source, as `compile` does, and place each
|
|
36
55
|
* diagnostic on its node ({@link placeDiagnostics}). The `check()` call stays
|
package/dist/merge-compile.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* merge-compile — the compiles `flow merge --baseline-from` runs itself (#1134)
|
|
2
|
+
* merge-compile — the compiles `flow merge --baseline-from` runs itself (#1134),
|
|
3
|
+
* and the decompile of an original `.flow` that it and `flow check --baseline` share.
|
|
3
4
|
*
|
|
4
|
-
* Both compiles tolerate draft nodes (`SerializeOpts.draft`)
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* is
|
|
5
|
+
* Both compiles tolerate draft nodes (`SerializeOpts.draft`) and run in collect
|
|
6
|
+
* mode (`SerializeOpts.collect`), so a node that fails to lower is reported and
|
|
7
|
+
* stood in for instead of stopping the compile. A draft or lowering refusal on
|
|
8
|
+
* a node merge keeps from the original is a warning; one on a node merge takes
|
|
9
|
+
* from the edited flow, like a `check()` error there, is an error. Nothing
|
|
10
|
+
* these compiles produce is written to disk. Which nodes count as edited, and
|
|
11
|
+
* how each finding is sorted, is edit-scope.ts's.
|
|
9
12
|
*/
|
|
10
13
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
14
|
import { tmpdir } from 'node:os';
|
|
@@ -13,15 +16,28 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
13
16
|
import { decompile } from './decompile.js';
|
|
14
17
|
import { loadBuiltFlow } from './load.js';
|
|
15
18
|
import { serialize } from './serialize.js';
|
|
19
|
+
import { CompileRefusal } from './core/compile-refusal.js';
|
|
16
20
|
import { PACKAGE_NAME } from './package-name.js';
|
|
17
21
|
import { check } from './check.js';
|
|
18
22
|
import { placeDiagnostics } from './edit-scope.js';
|
|
19
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Compile `built` with draft tolerance, in collect mode: a step whose
|
|
25
|
+
* emission is refused is reported in `lowering` and stood in for, so the
|
|
26
|
+
* rest of the flow is still compiled. A scope-level refusal (two steps with
|
|
27
|
+
* one name, a variable plan) leaves no flow to merge, so it is thrown, as an
|
|
28
|
+
* ordinary compile throws it.
|
|
29
|
+
*/
|
|
20
30
|
export function compileDraftTolerant(built, opts = {}) {
|
|
21
31
|
const drafts = [];
|
|
32
|
+
const lowering = [];
|
|
22
33
|
const sidecars = [];
|
|
23
|
-
const flow = serialize(built, { ...opts, draft: (f) => drafts.push(f), sidecars });
|
|
24
|
-
|
|
34
|
+
const flow = serialize(built, { ...opts, draft: (f) => drafts.push(f), collect: (r) => lowering.push(r), sidecars });
|
|
35
|
+
if (flow === undefined) {
|
|
36
|
+
// Collection stopped: the refusal that stopped it is the last one reported.
|
|
37
|
+
const stop = lowering[lowering.length - 1];
|
|
38
|
+
throw new CompileRefusal(stop.code, stop.message, stop.suggestion);
|
|
39
|
+
}
|
|
40
|
+
return { flow, drafts, lowering, sidecars };
|
|
25
41
|
}
|
|
26
42
|
/**
|
|
27
43
|
* Load a `.flow.ts` source and compile it with draft tolerance.
|
|
@@ -33,10 +49,22 @@ export async function compileSourceDraftTolerant(file, opts = {}) {
|
|
|
33
49
|
}
|
|
34
50
|
/**
|
|
35
51
|
* The baseline of `originalPath`: decompile it (reading agent sidecars beside
|
|
36
|
-
* it, as `flow decompile` does), then compile the source with draft tolerance
|
|
52
|
+
* it, as `flow decompile` does), then compile the source with draft tolerance,
|
|
53
|
+
* in collect mode ({@link compileDraftTolerant}).
|
|
37
54
|
* The source goes to a temporary directory that is removed afterwards.
|
|
38
55
|
*/
|
|
39
56
|
export async function baselineFromOriginal(originalPath, opts = {}, warn) {
|
|
57
|
+
return withDecompiledOriginal(originalPath, (built) => ({ ...compileDraftTolerant(built, opts), built }), warn);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Decompile the `.flow` at `originalPath` (reading agent sidecars beside it, as
|
|
61
|
+
* `flow decompile` does), load the source, and hand the built flow to `use`
|
|
62
|
+
* while the source is still on disk. `flow merge --baseline-from` compiles it
|
|
63
|
+
* ({@link baselineFromOriginal}); `flow check --baseline` checks it. The source
|
|
64
|
+
* goes to a temporary directory that is removed afterwards. The caller has
|
|
65
|
+
* already called `ensureTypeScriptRuntime()`.
|
|
66
|
+
*/
|
|
67
|
+
export async function withDecompiledOriginal(originalPath, use, warn) {
|
|
40
68
|
const original = JSON.parse(readFileSync(originalPath, 'utf8'));
|
|
41
69
|
const source = decompile(original, {
|
|
42
70
|
importSpecifier: PACKAGE_NAME,
|
|
@@ -60,7 +88,7 @@ export async function baselineFromOriginal(originalPath, opts = {}, warn) {
|
|
|
60
88
|
writeFileSync(join(dir, 'package.json'), '{"type":"module"}\n');
|
|
61
89
|
const file = join(dir, 'Baseline.flow.ts');
|
|
62
90
|
writeFileSync(file, source);
|
|
63
|
-
return await
|
|
91
|
+
return await use(await loadBuiltFlow(file));
|
|
64
92
|
}
|
|
65
93
|
finally {
|
|
66
94
|
rmSync(dir, { recursive: true, force: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.63.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",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"@types/node": "^22.7.0",
|
|
101
101
|
"esbuild": "^0.28.1"
|
|
102
102
|
},
|
|
103
|
-
"gitref": "
|
|
103
|
+
"gitref": "a4d62ecfe71de0865a81621edbf878ae84d0152b"
|
|
104
104
|
}
|