@uipath/maestro-builder-sdk 6.62.0 → 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 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 { diagnosticLine, reportDiagnostics } from './cli-result.js';
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 diags = check(built, checkOpts(argv));
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
- if (opts.serializer !== false)
48
- diags.push(...serializerRefusals(built, { library: opts.library, bindings: opts.bindings }, diags));
49
- return diags;
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
  }
@@ -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
- * Report a check's diagnostics as `{ errors, warnings, diagnostics }`, the one
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
- }>): void;
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), and the
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 {};
@@ -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), and the
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
- return `${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}`
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
@@ -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";
@@ -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',
@@ -41,6 +41,15 @@ export declare function compileSourceDraftTolerant(file: string, opts?: Omit<Ser
41
41
  export declare function baselineFromOriginal(originalPath: string, opts?: Omit<SerializeOpts, 'draft' | 'collect'>, warn?: (message: string) => void): Promise<DraftTolerantCompile & {
42
42
  built: BuiltFlow;
43
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>;
44
53
  /**
45
54
  * Run `check()` on the edited source, as `compile` does, and place each
46
55
  * diagnostic on its node ({@link placeDiagnostics}). The `check()` call stays
@@ -1,5 +1,6 @@
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
5
  * Both compiles tolerate draft nodes (`SerializeOpts.draft`) and run in collect
5
6
  * mode (`SerializeOpts.collect`), so a node that fails to lower is reported and
@@ -53,6 +54,17 @@ export async function compileSourceDraftTolerant(file, opts = {}) {
53
54
  * The source goes to a temporary directory that is removed afterwards.
54
55
  */
55
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) {
56
68
  const original = JSON.parse(readFileSync(originalPath, 'utf8'));
57
69
  const source = decompile(original, {
58
70
  importSpecifier: PACKAGE_NAME,
@@ -76,7 +88,7 @@ export async function baselineFromOriginal(originalPath, opts = {}, warn) {
76
88
  writeFileSync(join(dir, 'package.json'), '{"type":"module"}\n');
77
89
  const file = join(dir, 'Baseline.flow.ts');
78
90
  writeFileSync(file, source);
79
- return await compileSourceDraftTolerant(file, opts);
91
+ return await use(await loadBuiltFlow(file));
80
92
  }
81
93
  finally {
82
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.62.0",
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": "2a6094aa49f4cbf18cb309ba93c33573f3e1f226"
103
+ "gitref": "a4d62ecfe71de0865a81621edbf878ae84d0152b"
104
104
  }