@uipath/maestro-builder-sdk 6.9.5 → 6.10.1

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.
Files changed (46) hide show
  1. package/README.md +18 -4
  2. package/dist/api-index.md +15 -15
  3. package/dist/api-members.md +14 -14
  4. package/dist/bpmn/check-cli.js +2 -0
  5. package/dist/bpmn/compile-cli.js +3 -1
  6. package/dist/bpmn/decompile-cli.js +3 -1
  7. package/dist/bpmn/merge-cli.js +3 -1
  8. package/dist/bpmn/tidy-cli.js +3 -0
  9. package/dist/case/check-cli.js +2 -0
  10. package/dist/case/compile-cli.js +3 -1
  11. package/dist/case/decompile-cli.js +3 -0
  12. package/dist/case/merge-cli.js +3 -1
  13. package/dist/check-cli.js +2 -0
  14. package/dist/check.js +2 -2
  15. package/dist/cli/commands/bpmn.js +13 -7
  16. package/dist/cli/commands/case.js +13 -7
  17. package/dist/cli/commands/flow.js +14 -10
  18. package/dist/cli/commands/registry.js +11 -3
  19. package/dist/cli/common.d.ts +36 -1
  20. package/dist/cli/common.js +89 -0
  21. package/dist/cli/index.js +40 -2
  22. package/dist/cli-result.d.ts +22 -0
  23. package/dist/cli-result.js +31 -0
  24. package/dist/compile-cli.js +12 -0
  25. package/dist/core/actions.d.ts +1 -2
  26. package/dist/core/actions.js +1 -2
  27. package/dist/core/cli-spelling.d.ts +5 -3
  28. package/dist/core/cli-spelling.js +8 -4
  29. package/dist/core/lookups.js +1 -1
  30. package/dist/decompile-cli.js +3 -0
  31. package/dist/decompile.js +24 -2
  32. package/dist/merge-cli.js +3 -1
  33. package/dist/registry/generators/markdown/sections/choosing-object.js +1 -1
  34. package/dist/registry/generators/markdown/sections/example.js +2 -2
  35. package/dist/registry/generators/markdown/sections/lookups.js +1 -1
  36. package/dist/registry/generators/markdown/sections/schema-dynamic.js +3 -3
  37. package/dist/registry/library-build/cli.js +3 -0
  38. package/dist/registry/materialize-cli.js +7 -0
  39. package/dist/registry/path-cli.js +14 -7
  40. package/dist/registry/prepare/messages.js +4 -4
  41. package/dist/registry/prepare/pipeline.d.ts +2 -0
  42. package/dist/registry/prepare/pipeline.js +4 -2
  43. package/dist/registry/prepare-cli.js +6 -3
  44. package/dist/registry/pull-cli.js +10 -1
  45. package/dist/registry/search-cli.js +2 -0
  46. package/package.json +7 -2
@@ -1,4 +1,4 @@
1
- import { Option } from 'commander';
1
+ import { type Command, Option } from 'commander';
2
2
  export type ToolRun = (argv: string[]) => Promise<number> | number;
3
3
  export declare function loadTool(baseUrl: string, relativePath: string): Promise<{
4
4
  run: ToolRun;
@@ -6,6 +6,24 @@ export declare function loadTool(baseUrl: string, relativePath: string): Promise
6
6
  export declare function invoke(label: string, load: () => Promise<{
7
7
  run: ToolRun;
8
8
  }>, argv: string[]): Promise<void>;
9
+ /**
10
+ * Refuse the verb before any tool runs: the message on stderr, the exit code
11
+ * (2, a usage error or blocked mode, unless given), and the message as the
12
+ * `--json` object's `error`.
13
+ */
14
+ export declare function refuse(message: string, exitCode?: number): void;
15
+ /** Record why the verb failed before any tool ran, for the `--json` object. */
16
+ export declare function recordJsonError(message: string): void;
17
+ /** Turn `--json` on for this run (the umbrella's `preAction` hook). */
18
+ export declare function requestJsonResult(): void;
19
+ export declare function jsonResultRequested(): boolean;
20
+ /**
21
+ * Print the verb's result as one JSON object on stdout: `ok` (the exit code
22
+ * was 0) plus whatever the tool reported. Printed even when the verb refused
23
+ * before reaching a tool (a usage error, emit-only mode), so a `--json` caller
24
+ * always gets one object to parse.
25
+ */
26
+ export declare function emitJsonResult(): void;
9
27
  export declare function addValue(argv: string[], flag: string, value: unknown): void;
10
28
  /**
11
29
  * Is emit-only mode on for the PROJECT containing `cwd`?
@@ -34,3 +52,20 @@ export declare function inferCheckMode(family: 'flow' | 'case' | 'bpmn', input:
34
52
  * script that passes it gets the redirect to `uip maestro <family> validate`.
35
53
  */
36
54
  export declare function retiredCompiledOption(): Option;
55
+ /**
56
+ * The primary positional and a named second value (`--original`, `--action`),
57
+ * also accepting the deprecated two-positional form with a warning.
58
+ * `legacyNamedFirst` says where the named value sat in that form: first for
59
+ * merge (`<original> <edited>`), second for prepare (`<key> <action>`). The
60
+ * verb must call `allowExcessArguments()` so the legacy value reaches
61
+ * `command.args`.
62
+ *
63
+ * Returns `[primary, named]`, or `null` after reporting a usage error (exit 2).
64
+ */
65
+ export declare function namedOrLegacySecond(command: Command, spec: {
66
+ label: string;
67
+ flag: string;
68
+ named: string | undefined;
69
+ legacyNamedFirst: boolean;
70
+ usage: string;
71
+ }): [string, string] | null;
@@ -5,6 +5,10 @@ export function loadTool(baseUrl, relativePath) {
5
5
  return import(new URL(relativePath, baseUrl).href);
6
6
  }
7
7
  export async function invoke(label, load, argv) {
8
+ // In `--json` mode stdout carries exactly one JSON object, printed by
9
+ // `emitJsonResult` once the verb is done. The tool's own human lines go to
10
+ // stderr meanwhile, so they are still seen but never mixed into it.
11
+ const restore = jsonResultRequested() ? routeStdoutToStderr() : () => { };
8
12
  try {
9
13
  const code = await (await load()).run(argv);
10
14
  if (code !== 0)
@@ -13,8 +17,63 @@ export async function invoke(label, load, argv) {
13
17
  catch (error) {
14
18
  const message = error instanceof Error ? error.message : String(error);
15
19
  console.error(`flow-sdk ${label}: ${message}`);
20
+ resultSlot().fields.error ??= message;
16
21
  process.exitCode = 1;
17
22
  }
23
+ finally {
24
+ restore();
25
+ }
26
+ }
27
+ /**
28
+ * The slot `src/cli-result.ts` writes (`reportResult`). Read here through the
29
+ * same `Symbol.for` key, since `dist/cli/` and `dist/` are separate builds.
30
+ */
31
+ const RESULT_SLOT = Symbol.for('uipath.maestro-builder-sdk.cli-result');
32
+ function resultSlot() {
33
+ const global = globalThis;
34
+ global[RESULT_SLOT] ??= { json: false, fields: {} };
35
+ return global[RESULT_SLOT];
36
+ }
37
+ /**
38
+ * Refuse the verb before any tool runs: the message on stderr, the exit code
39
+ * (2, a usage error or blocked mode, unless given), and the message as the
40
+ * `--json` object's `error`.
41
+ */
42
+ export function refuse(message, exitCode = 2) {
43
+ console.error(message);
44
+ recordJsonError(message);
45
+ process.exitCode = exitCode;
46
+ }
47
+ /** Record why the verb failed before any tool ran, for the `--json` object. */
48
+ export function recordJsonError(message) {
49
+ resultSlot().fields.error ??= message.replace(/^error: /, '');
50
+ }
51
+ /** Turn `--json` on for this run (the umbrella's `preAction` hook). */
52
+ export function requestJsonResult() {
53
+ resultSlot().json = true;
54
+ }
55
+ export function jsonResultRequested() {
56
+ return resultSlot().json;
57
+ }
58
+ /** Route `process.stdout.write` to stderr; returns the undo. */
59
+ function routeStdoutToStderr() {
60
+ const original = process.stdout.write;
61
+ process.stdout.write = ((chunk, ...rest) => process.stderr.write(chunk, ...rest));
62
+ return () => {
63
+ process.stdout.write = original;
64
+ };
65
+ }
66
+ /**
67
+ * Print the verb's result as one JSON object on stdout: `ok` (the exit code
68
+ * was 0) plus whatever the tool reported. Printed even when the verb refused
69
+ * before reaching a tool (a usage error, emit-only mode), so a `--json` caller
70
+ * always gets one object to parse.
71
+ */
72
+ export function emitJsonResult() {
73
+ if (!jsonResultRequested())
74
+ return;
75
+ const ok = (process.exitCode ?? 0) === 0;
76
+ process.stdout.write(`${JSON.stringify({ ok, ...resultSlot().fields })}\n`);
18
77
  }
19
78
  export function addValue(argv, flag, value) {
20
79
  if (typeof value === 'string')
@@ -98,3 +157,33 @@ export function inferCheckMode(family, input, source, compiled) {
98
157
  export function retiredCompiledOption() {
99
158
  return new Option('--compiled', 'retired: a compiled artifact is checked by `uip maestro <family> validate`').hideHelp();
100
159
  }
160
+ /**
161
+ * The primary positional and a named second value (`--original`, `--action`),
162
+ * also accepting the deprecated two-positional form with a warning.
163
+ * `legacyNamedFirst` says where the named value sat in that form: first for
164
+ * merge (`<original> <edited>`), second for prepare (`<key> <action>`). The
165
+ * verb must call `allowExcessArguments()` so the legacy value reaches
166
+ * `command.args`.
167
+ *
168
+ * Returns `[primary, named]`, or `null` after reporting a usage error (exit 2).
169
+ */
170
+ export function namedOrLegacySecond(command, spec) {
171
+ const [first, extra, ...rest] = command.args;
172
+ const usageError = (message) => {
173
+ refuse(`flow-sdk ${spec.label}: ${message} (usage: ${spec.usage})`);
174
+ return null;
175
+ };
176
+ if (rest.length > 0)
177
+ return usageError(`unexpected argument '${rest[0]}'`);
178
+ if (spec.named !== undefined) {
179
+ if (extra !== undefined)
180
+ return usageError(`pass ${spec.flag} or a second argument, not both`);
181
+ return [first, spec.named];
182
+ }
183
+ if (extra === undefined)
184
+ return usageError(`missing ${spec.flag}`);
185
+ // Spelled for this package's bin: a `uip` release without the named form
186
+ // (before UiPath/cli#4526) rejects it, while `npx flow-sdk …` always works.
187
+ console.error(`flow-sdk ${spec.label}: the two-argument form is deprecated; use \`npx flow-sdk ${spec.usage}\``);
188
+ return spec.legacyNamedFirst ? [extra, first] : [first, extra];
189
+ }
package/dist/cli/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from 'node:fs';
3
- import { Command } from 'commander';
3
+ import { Command, CommanderError } from 'commander';
4
+ import { emitJsonResult, recordJsonError, requestJsonResult } from './common.js';
4
5
  import { registerBpmnCommands } from './commands/bpmn.js';
5
6
  import { registerCaseCommands } from './commands/case.js';
6
7
  import { registerFlowCommands } from './commands/flow.js';
@@ -15,4 +16,41 @@ registerFlowCommands(program);
15
16
  registerCaseCommands(program);
16
17
  registerBpmnCommands(program);
17
18
  registerRegistryCommands(program);
18
- await program.parseAsync(process.argv);
19
+ // `--json` on every verb (#809): one JSON object on stdout, human lines on
20
+ // stderr. Added here, once, so a new verb cannot ship without it.
21
+ const addJson = (command) => {
22
+ if (command.commands.length > 0) {
23
+ command.commands.forEach(addJson);
24
+ return;
25
+ }
26
+ if (!command.options.some((option) => option.long === '--json')) {
27
+ command.option('--json', 'print the result as one JSON object on stdout (human lines go to stderr)');
28
+ }
29
+ };
30
+ program.commands.forEach(addJson);
31
+ program.hook('preAction', (_program, action) => {
32
+ if (action.opts().json === true)
33
+ requestJsonResult();
34
+ });
35
+ // A parse error (unknown option, extra argument) is raised before any hook
36
+ // runs, so `--json` is read from argv for that case: the caller still gets its
37
+ // one object. Help and version are the exception — they are text by request.
38
+ program.exitOverride();
39
+ program.commands.forEach(function override(command) {
40
+ command.exitOverride();
41
+ command.commands.forEach(override);
42
+ });
43
+ try {
44
+ await program.parseAsync(process.argv);
45
+ }
46
+ catch (error) {
47
+ if (!(error instanceof CommanderError))
48
+ throw error;
49
+ process.exitCode = error.exitCode;
50
+ const text = error.code === 'commander.helpDisplayed' || error.code === 'commander.version' || error.code === 'commander.help';
51
+ if (!text && process.argv.includes('--json')) {
52
+ requestJsonResult();
53
+ recordJsonError(error.message);
54
+ }
55
+ }
56
+ emitJsonResult();
@@ -0,0 +1,22 @@
1
+ /**
2
+ * cli-result — the structured result a verb reports for `--json`. One
3
+ * `globalThis` slot because `dist/cli/` and `dist/` are separate builds.
4
+ */
5
+ /**
6
+ * Record fields of this verb's result. Later calls add to earlier ones.
7
+ *
8
+ * Cheap and harmless without `--json`: the fields are kept and never printed.
9
+ *
10
+ * @param fields - JSON-serializable values, e.g. `{ path, nodes, edges }`.
11
+ */
12
+ export declare function reportResult(fields: Record<string, unknown>): void;
13
+ /**
14
+ * Report a check's diagnostics as `{ errors, warnings, diagnostics }`, the one
15
+ * shape every family's check (and compile's check pass) uses.
16
+ */
17
+ export declare function reportDiagnostics(diags: ReadonlyArray<{
18
+ level: string;
19
+ code: string;
20
+ message: string;
21
+ suggestion?: string;
22
+ }>): void;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * cli-result — the structured result a verb reports for `--json`. One
3
+ * `globalThis` slot because `dist/cli/` and `dist/` are separate builds.
4
+ */
5
+ const SLOT = Symbol.for('uipath.maestro-builder-sdk.cli-result');
6
+ function slot() {
7
+ const global = globalThis;
8
+ global[SLOT] ??= { json: false, fields: {} };
9
+ return global[SLOT];
10
+ }
11
+ /**
12
+ * Record fields of this verb's result. Later calls add to earlier ones.
13
+ *
14
+ * Cheap and harmless without `--json`: the fields are kept and never printed.
15
+ *
16
+ * @param fields - JSON-serializable values, e.g. `{ path, nodes, edges }`.
17
+ */
18
+ export function reportResult(fields) {
19
+ Object.assign(slot().fields, fields);
20
+ }
21
+ /**
22
+ * Report a check's diagnostics as `{ errors, warnings, diagnostics }`, the one
23
+ * shape every family's check (and compile's check pass) uses.
24
+ */
25
+ export function reportDiagnostics(diags) {
26
+ reportResult({
27
+ errors: diags.filter((d) => d.level === 'error').length,
28
+ warnings: diags.filter((d) => d.level !== 'error').length,
29
+ diagnostics: diags.map((d) => ({ level: d.level, code: d.code, message: d.message, ...(d.suggestion ? { suggestion: d.suggestion } : {}) })),
30
+ });
31
+ }
@@ -17,6 +17,7 @@
17
17
  * product's own gates are supposed to answer. `.build()` still throws on
18
18
  * structural problems; only the diagnostic pass is skipped.
19
19
  */
20
+ import { reportDiagnostics, reportResult } from './cli-result.js';
20
21
  import { writeFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
21
22
  import { basename, dirname, join, resolve } from 'node:path';
22
23
  import { resolveFlowFile, loadBuiltFlow } from './load.js';
@@ -123,6 +124,7 @@ export async function run(argv) {
123
124
  for (const d of diags)
124
125
  console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}${d.suggestion ? ` (did you mean "${d.suggestion}"?)` : ''}`);
125
126
  if (errors.length) {
127
+ reportDiagnostics(diags);
126
128
  console.error(`compile: ${errors.length} error(s); not emitting.`);
127
129
  return 1;
128
130
  }
@@ -146,6 +148,10 @@ export async function run(argv) {
146
148
  for (const w of warnings)
147
149
  console.error(` ⚠ [${w.code}] ${w.message}`);
148
150
  console.error(` ✗ [${error.code}] ${error.message}`);
151
+ reportDiagnostics([
152
+ ...warnings.map((w) => ({ level: 'warning', code: w.code, message: w.message })),
153
+ { level: 'error', code: error.code, message: error.message },
154
+ ]);
149
155
  console.error('compile: 1 error(s); not emitting.');
150
156
  return 1;
151
157
  }
@@ -155,6 +161,10 @@ export async function run(argv) {
155
161
  const out = opt(argv, '-o') ?? `${built.name || basename(file).replace(/\.flow\.ts$|\.ts$/, '')}.flow`;
156
162
  writeFileSync(out, JSON.stringify(flowJson, null, 2) + '\n');
157
163
  console.log(`compile: wrote ${out} (${flowJson.nodes.length} nodes, ${flowJson.edges.length} edges)`);
164
+ const written = [];
165
+ reportResult({ path: resolve(out), nodes: flowJson.nodes.length, edges: flowJson.edges.length, sidecars: written });
166
+ // Same keys and types as a failed compile: `warnings` a count, `diagnostics` the list.
167
+ reportDiagnostics(warnings.map((w) => ({ level: 'warning', code: w.code, message: w.message })));
158
168
  // An inline agent is TWO artifacts: the node, and the agent's own configuration
159
169
  // at `<source>/agent.json` — which is where flow-v1's serializer points the
160
170
  // runtime (`entryPoint: "<source>/agent.json"`, resolved relative to the
@@ -168,11 +178,13 @@ export async function run(argv) {
168
178
  const target = resolve(outDir, s.path);
169
179
  if (target !== outDir && !target.startsWith(outDir + '/')) {
170
180
  console.error(`compile: refusing to write a sidecar outside the flow's directory: ${s.path}`);
181
+ reportResult({ error: `refusing to write a sidecar outside the flow's directory: ${s.path}` });
171
182
  return 1;
172
183
  }
173
184
  mkdirSync(dirname(target), { recursive: true });
174
185
  writeFileSync(target, JSON.stringify(s.json, null, 2) + '\n');
175
186
  console.log(`compile: wrote ${s.path}`);
187
+ written.push(target);
176
188
  }
177
189
  // PRUNE the agent resources this compile owns but did not write this time.
178
190
  // Compile otherwise only ever ADDS files, and an agent resource is keyed by a
@@ -3153,8 +3153,7 @@ export declare function dynamicExtract(inputs: DynamicExtractInputs): ActionSpec
3153
3153
  *
3154
3154
  * A connector operation's body fields come from the entity, so the library
3155
3155
  * cannot carry them: run `uip maestro registry prepare uipath-uipath-dataservice
3156
- * get-entity-record-by-id -f entityName=<Entity>` first (`npx flow-sdk registry
3157
- * prepare …` is the same command when invoking the package's own bin directly).
3156
+ * get-entity-record-by-id -f entityName=<Entity>` first.
3158
3157
  * The routing table is in the `data-fabric.md` reference and the parent-field
3159
3158
  * loop in `connector-params.md` — cited by name rather than linked, because this
3160
3159
  * comment renders both into the flat corpus, where those files sit alongside
@@ -918,8 +918,7 @@ export function dynamicExtract(inputs) {
918
918
  *
919
919
  * A connector operation's body fields come from the entity, so the library
920
920
  * cannot carry them: run `uip maestro registry prepare uipath-uipath-dataservice
921
- * get-entity-record-by-id -f entityName=<Entity>` first (`npx flow-sdk registry
922
- * prepare …` is the same command when invoking the package's own bin directly).
921
+ * get-entity-record-by-id -f entityName=<Entity>` first.
923
922
  * The routing table is in the `data-fabric.md` reference and the parent-field
924
923
  * loop in `connector-params.md` — cited by name rather than linked, because this
925
924
  * comment renders both into the flat corpus, where those files sit alongside
@@ -26,17 +26,19 @@ export declare function cliSpelling(env?: CliSpellingEnv): string;
26
26
  /**
27
27
  * A `registry prepare` command line, spelled for whoever is driving.
28
28
  *
29
- * @param args - Everything after `registry prepare`, e.g. the connector key and action.
29
+ * @param connectorKey - The connector key, or omitted for the bare command.
30
+ * @param action - The operation, printed as `--action <action>`.
31
+ * @param rest - Further arguments, e.g. `--object`, `<name>`.
30
32
  * @returns The full command, no trailing newline.
31
33
  *
32
34
  * @example
33
35
  * ```ts
34
36
  * prepareCommand('uipath-salesforce-slack', 'send-message-to-user');
35
- * // -> "npx flow-sdk registry prepare uipath-salesforce-slack send-message-to-user"
37
+ * // -> "npx flow-sdk registry prepare uipath-salesforce-slack --action send-message-to-user"
36
38
  * // -> "uip maestro registry prepare …" when FLOW_SDK_INVOKED_AS="uip maestro"
37
39
  * ```
38
40
  */
39
- export declare function prepareCommand(...args: string[]): string;
41
+ export declare function prepareCommand(connectorKey?: string, action?: string, ...rest: string[]): string;
40
42
  /**
41
43
  * The argument of one `--resolve <field>:<by>=<value>`, single-quoted when the
42
44
  * shell would otherwise mangle it.
@@ -47,18 +47,22 @@ export function cliSpelling(env = ambientEnv()) {
47
47
  /**
48
48
  * A `registry prepare` command line, spelled for whoever is driving.
49
49
  *
50
- * @param args - Everything after `registry prepare`, e.g. the connector key and action.
50
+ * @param connectorKey - The connector key, or omitted for the bare command.
51
+ * @param action - The operation, printed as `--action <action>`.
52
+ * @param rest - Further arguments, e.g. `--object`, `<name>`.
51
53
  * @returns The full command, no trailing newline.
52
54
  *
53
55
  * @example
54
56
  * ```ts
55
57
  * prepareCommand('uipath-salesforce-slack', 'send-message-to-user');
56
- * // -> "npx flow-sdk registry prepare uipath-salesforce-slack send-message-to-user"
58
+ * // -> "npx flow-sdk registry prepare uipath-salesforce-slack --action send-message-to-user"
57
59
  * // -> "uip maestro registry prepare …" when FLOW_SDK_INVOKED_AS="uip maestro"
58
60
  * ```
59
61
  */
60
- export function prepareCommand(...args) {
61
- return [cliSpelling(), 'registry', 'prepare', ...args.filter(Boolean)].join(' ');
62
+ export function prepareCommand(connectorKey, action, ...rest) {
63
+ return [cliSpelling(), 'registry', 'prepare', connectorKey, ...(action ? ['--action', action] : []), ...rest]
64
+ .filter(Boolean)
65
+ .join(' ');
62
66
  }
63
67
  /**
64
68
  * Characters that make a `--resolve` argument unsafe to paste unquoted. `[*]`
@@ -285,7 +285,7 @@ export function refuseLookupToken(value, family, field) {
285
285
  `only Flow reads connectors-local/resolutions.json today, so this would be ` +
286
286
  `emitted as a raw token where the connector expects an id.\n` +
287
287
  `Resolve it first and pass the value:\n` +
288
- ` ${prepareCommand()} ${nodeType.replace(/^uipath\.connector\.(?:trigger\.|event\.)?/, '').replace(/\.([^.]+)$/, ' $1')} \\\n` +
288
+ ` ${prepareCommand()} ${nodeType.replace(/^uipath\.connector\.(?:trigger\.|event\.)?/, '').replace(/\.([^.]+)$/, ' --action $1')} \\\n` +
289
289
  ` --resolve ${field}:${by}=${String(wanted)}\n` +
290
290
  `then read the id out of connectors-local/resolutions.json.`);
291
291
  }
@@ -17,6 +17,7 @@
17
17
  * agent can run one script for the whole brownfield write-back loop. Pass
18
18
  * `--no-pipeline` to skip it.
19
19
  */
20
+ import { reportResult } from './cli-result.js';
20
21
  import { readFileSync, writeFileSync } from 'node:fs';
21
22
  import { basename, resolve } from 'node:path';
22
23
  import { decompile } from './decompile.js';
@@ -67,10 +68,12 @@ export function run(argv) {
67
68
  const out = opt(argv, '-o') ?? `${baseName(flow)}.flow.ts`;
68
69
  writeFileSync(out, ts);
69
70
  console.log(`flow-decompile: wrote ${basename(out)}`);
71
+ reportResult({ path: resolve(out) });
70
72
  if (!argv.includes('--no-pipeline')) {
71
73
  const pipelinePath = out.replace(/\.flow\.ts$|\.ts$/, '') + '.pipeline.mjs';
72
74
  writeFileSync(pipelinePath, pipelineScript(resolve(pos), resolve(out)));
73
75
  console.log(`flow-decompile: wrote ${basename(pipelinePath)}`);
76
+ reportResult({ pipeline: resolve(pipelinePath) });
74
77
  }
75
78
  return 0;
76
79
  }
package/dist/decompile.js CHANGED
@@ -1923,15 +1923,37 @@ function armCb(param, segs) {
1923
1923
  * fall-off End the serializer always appends; it now decompiles to an explicit
1924
1924
  * `.return({}, { version })` so the End's exact definition version survives the
1925
1925
  * round trip (design §5.3) — recompiling emits the identical node.
1926
+ *
1927
+ * An End written by the designer or the product CLI carries its returned value
1928
+ * ONLY in `outputs[name].source` — it writes no `variableUpdates` row. Reading
1929
+ * updates alone decompiled such an End to `.return({})` and silently dropped the
1930
+ * value it returned (a Decision's two Ends returning `'nice day'` /
1931
+ * `'bring a jacket'` both came back empty). So an output whose source is not the
1932
+ * identity `$vars.<name>` mapping `endOutputs` fills in is a returned value too.
1926
1933
  */
1927
1934
  function returnSeg(node, ctx) {
1928
1935
  const updates = ctx.flow.variables?.variableUpdates?.[node.id] ?? [];
1929
1936
  const opts = returnOptsArg(node);
1930
- if (updates.length === 0)
1931
- return `.return({}${opts})`;
1932
1937
  const entries = updates.map((u) => `${key(u.variableId)}: ${emitVal(u.expression, ctx.imp, ctx.inputNames)}`);
1938
+ const written = new Set(updates.map((u) => u.variableId));
1939
+ for (const [name, out] of Object.entries(node.outputs ?? {})) {
1940
+ if (written.has(name))
1941
+ continue;
1942
+ const source = out?.source;
1943
+ const raw = unwrapSource(source);
1944
+ if (raw === undefined || isIdentityEndSource(raw, name))
1945
+ continue;
1946
+ entries.push(`${key(name)}: ${emitVal(source, ctx.imp, ctx.inputNames)}`);
1947
+ }
1948
+ if (entries.length === 0)
1949
+ return `.return({}${opts})`;
1933
1950
  return `.return({ ${entries.join(', ')} }${opts})`;
1934
1951
  }
1952
+ /** The fill-in mapping `endOutputs` writes for an output no `.return` named: `=js:$vars.<name>`. */
1953
+ function isIdentityEndSource(raw, name) {
1954
+ const body = raw.startsWith('=js:') ? raw.slice(4) : raw;
1955
+ return body.trim() === `$vars.${name}`;
1956
+ }
1935
1957
  /**
1936
1958
  * The options argument for a `.return(...)` — `version`, plus the End's `name`
1937
1959
  * and `label` when they are not what the serializer would derive on its own.
package/dist/merge-cli.js CHANGED
@@ -11,8 +11,9 @@
11
11
  * omit it for a two-way merge (take the edited graph, re-attach original layout).
12
12
  * See merge.ts.
13
13
  */
14
+ import { reportResult } from './cli-result.js';
14
15
  import { readFileSync, writeFileSync } from 'node:fs';
15
- import { basename } from 'node:path';
16
+ import { basename, resolve } from 'node:path';
16
17
  import { mergeFlow } from './merge.js';
17
18
  import { runWhenInvokedDirectly } from './cli-run.js';
18
19
  function opt(argv, name) {
@@ -42,6 +43,7 @@ export function run(argv) {
42
43
  const out = opt(argv, '-o') ?? 'merged.flow';
43
44
  writeFileSync(out, JSON.stringify(merged, null, 2) + '\n');
44
45
  console.log(`flow-merge: wrote ${basename(out)} (${(merged.nodes ?? []).length} nodes, ${(merged.edges ?? []).length} edges)`);
46
+ reportResult({ path: resolve(out), nodes: (merged.nodes ?? []).length, edges: (merged.edges ?? []).length });
45
47
  return 0;
46
48
  }
47
49
  runWhenInvokedDirectly(import.meta.url, 'flow-merge', run);
@@ -24,7 +24,7 @@ export function choosingObjectLines(j, nodeType) {
24
24
  "casing), then one command matches it deterministically:",
25
25
  "",
26
26
  "```bash",
27
- `npx flow-sdk registry prepare ${connectorKey} ${mdAction} --object <name-as-the-task-said-it>`,
27
+ `npx flow-sdk registry prepare ${connectorKey} --action ${mdAction} --object <name-as-the-task-said-it>`,
28
28
  "```",
29
29
  "",
30
30
  "The name is matched, never equality-tested: exact, then " +
@@ -208,7 +208,7 @@ export function exampleLines(j, fields, symbol, invariant, v1def) {
208
208
  if (pyTruthy(operation.objectFromDiscovery)) {
209
209
  out.push("The descriptor comes from `./connectors-local/` and this " +
210
210
  "import resolves only after " +
211
- `\`npx flow-sdk registry prepare ${connectorKey} ${action} ` +
211
+ `\`npx flow-sdk registry prepare ${connectorKey} --action ${action} ` +
212
212
  "--object <name>` runs. That never blocks authoring: write " +
213
213
  "the step first with the string form and " +
214
214
  "`{ object: '<name-as-the-task-said-it>' }` — `check` " +
@@ -217,7 +217,7 @@ export function exampleLines(j, fields, symbol, invariant, v1def) {
217
217
  else {
218
218
  out.push("The descriptor comes from `./connectors-local/` and this " +
219
219
  "import resolves only after " +
220
- `\`npx flow-sdk registry prepare ${connectorKey} ${action}\` ` +
220
+ `\`npx flow-sdk registry prepare ${connectorKey} --action ${action}\` ` +
221
221
  "runs. That never blocks authoring: the fields in this " +
222
222
  "catalog are authorable immediately, and `check` flags any " +
223
223
  "input outside them with this same command.");
@@ -128,7 +128,7 @@ export function lookupLines(connectorKey, action, fields, symbol, invariant) {
128
128
  out.push("");
129
129
  // ONE command carrying every --resolve, and no `--connection-id`.
130
130
  out.push("```bash");
131
- out.push(`npx flow-sdk registry prepare ${connectorKey} ${action} \\`);
131
+ out.push(`npx flow-sdk registry prepare ${connectorKey} --action ${action} \\`);
132
132
  // The WIRE name (`users[*]`), which is what prepare's lookup table is keyed
133
133
  // on — the authoring name (`users`) is refused as RESOLVE_UNKNOWN_FIELD.
134
134
  const resolves = resolvable.map(([f, reference]) => {
@@ -54,14 +54,14 @@ export function schemaDynamicLines(j, nodeType) {
54
54
  out.push("> ```bash");
55
55
  // ONE spelling on every page, the one `check` prints; no `--connection-id`.
56
56
  if (discovery) {
57
- out.push(`> npx flow-sdk registry prepare ${key} ${action} ${supply}`);
57
+ out.push(`> npx flow-sdk registry prepare ${key} --action ${action} ${supply}`);
58
58
  }
59
59
  else if (supply) {
60
- out.push(`> npx flow-sdk registry prepare ${key} ${action} \\`);
60
+ out.push(`> npx flow-sdk registry prepare ${key} --action ${action} \\`);
61
61
  out.push(`> ${supply}`);
62
62
  }
63
63
  else {
64
- out.push(`> npx flow-sdk registry prepare ${key} ${action}`);
64
+ out.push(`> npx flow-sdk registry prepare ${key} --action ${action}`);
65
65
  }
66
66
  out.push("> ```");
67
67
  out.push(">");
@@ -13,6 +13,8 @@
13
13
  * tenant, which is the only way its exit codes can be pinned.
14
14
  */
15
15
  import { rmSync } from "node:fs";
16
+ import { resolve } from "node:path";
17
+ import { reportResult } from "../../cli-result.js";
16
18
  import { UsageError, exitCodeOf, messageOf } from "../errors.js";
17
19
  import { consoleReporter } from "../reporter.js";
18
20
  import { OfflineUipClient, defaultUipClient } from "../uip/client.js";
@@ -173,6 +175,7 @@ export async function run(argv, deps = {}) {
173
175
  }
174
176
  }
175
177
  reporter.info("Done!");
178
+ reportResult({ outputDir: resolve(options.outputDir) });
176
179
  return 0;
177
180
  }
178
181
  catch (error) {
@@ -1,3 +1,5 @@
1
+ import { resolve } from "node:path";
2
+ import { reportResult } from "../cli-result.js";
1
3
  import { exitCodeOf, messageOf } from "./errors.js";
2
4
  import { materializeRegistry } from "./materializer.js";
3
5
  /**
@@ -51,6 +53,11 @@ export async function run(argv) {
51
53
  checkSymbols: argv.includes("--check-symbols"),
52
54
  checkOrphans: argv.includes("--check-orphans"),
53
55
  });
56
+ reportResult({
57
+ libraryJson: resolve(libraryJsonPath),
58
+ ...(libraryMarkdownPath ? { libraryMarkdown: resolve(libraryMarkdownPath) } : {}),
59
+ ...(connectorsPath ? { connectors: resolve(connectorsPath) } : {}),
60
+ });
54
61
  return 0;
55
62
  }
56
63
  catch (error) {
@@ -1,3 +1,4 @@
1
+ import { reportResult } from '../cli-result.js';
1
2
  import { join } from "node:path";
2
3
  import { CURRENT_LINK, currentEntrySha, libraryCacheRoot, resolveCachedLibrary, resolveCachedMarkdown, } from "./cache.js";
3
4
  /**
@@ -27,7 +28,11 @@ export async function run(argv) {
27
28
  const libraryJson = resolveCachedLibrary();
28
29
  const libraryMarkdown = resolveCachedMarkdown();
29
30
  const sha256 = currentEntrySha();
30
- const single = (value) => {
31
+ // With one flag, `--json` reports just that field, like the text output. A
32
+ // caller capturing one value (`uip maestro registry path --sha --output
33
+ // plain`, which prints each Data value) must still get exactly one.
34
+ const single = (field, value) => {
35
+ reportResult({ [field]: value ?? null });
31
36
  if (!value) {
32
37
  console.error("No connector library in the cache yet. Run `flow-sdk registry pull`.");
33
38
  return 1;
@@ -36,20 +41,22 @@ export async function run(argv) {
36
41
  return 0;
37
42
  };
38
43
  if (argv.includes("--library-json"))
39
- return single(libraryJson);
44
+ return single("libraryJson", libraryJson);
40
45
  if (argv.includes("--library-md"))
41
- return single(libraryMarkdown);
46
+ return single("libraryMarkdown", libraryMarkdown);
42
47
  if (argv.includes("--root"))
43
- return single(root);
48
+ return single("root", root);
44
49
  if (argv.includes("--sha"))
45
- return single(sha256);
46
- console.log(JSON.stringify({
50
+ return single("sha256", sha256);
51
+ const all = {
47
52
  root,
48
53
  current: join(root, CURRENT_LINK),
49
54
  sha256: sha256 ?? null,
50
55
  libraryJson: libraryJson ?? null,
51
56
  libraryMarkdown: libraryMarkdown ?? null,
52
- }, null, 2));
57
+ };
58
+ reportResult(all);
59
+ console.log(JSON.stringify(all, null, 2));
53
60
  // Not an error: a caller asking for the whole picture is entitled to be told
54
61
  // the cache is empty, rather than getting a non-zero exit for a plain report.
55
62
  return 0;