Skip to content

feat(cli): allow known globals after command; query output + contract…#926

Merged
zerodevblock-cyber merged 2 commits intorelease_v4.9.5from
fix/output-formater-and-global-flag
Apr 28, 2026
Merged

feat(cli): allow known globals after command; query output + contract…#926
zerodevblock-cyber merged 2 commits intorelease_v4.9.5from
fix/output-formater-and-global-flag

Conversation

@gummy789j
Copy link
Copy Markdown
Collaborator

@gummy789j gummy789j commented Apr 28, 2026

  1. GlobalOptions parses the full argv: after the command token, recognized globals (--network, --output, --wallet, --grpc-endpoint, etc.) are applied and removed from command args; --help/-h after the command are left for the command parser; unknown trailing flags pass through unchanged. Malformed or invalid values for known globals still fail at the global layer (validated globally).

  2. Client.requestsJsonOutput no longer stops scanning at the first non-flag so post-command --output json is respected.

  3. OutputFormatter adds queryResult(): text mode renders small flat primitive maps as Metadata, larger/nested/array payloads as indented Result: JSON; success/formatMessage-only paths wrap plain strings for TEXT JSON payloads.

  4. QueryCommands and ContractCommands use queryResult for RPC-style successes; several protobuf paths now emit not_found when the RPC returns null.

  5. Updates standard-cli-contract-spec and qa/contracts.tsv (post-command network succeeds; unknown post-command option stays command-local usage).

  6. Adds GlobalOptions, OutputFormatter, and ClientMain tests for the above.

… docs

GlobalOptions parses the full argv: after the command token, recognized
globals (--network, --output, --wallet, --grpc-endpoint, etc.) are applied
and removed from command args; --help/-h after the command are left for the
command parser; unknown trailing flags pass through unchanged. Malformed or
invalid values for known globals still fail at the global layer (validated
globally).

Client.requestsJsonOutput no longer stops scanning at the first non-flag so
post-command --output json is respected.

OutputFormatter adds queryResult(): text mode renders small flat primitive
maps as Metadata, larger/nested/array payloads as indented Result: JSON;
success/formatMessage-only paths wrap plain strings for TEXT JSON payloads.

QueryCommands and ContractCommands use queryResult for RPC-style successes;
several protobuf paths now emit not_found when the RPC returns null.

Updates standard-cli-contract-spec and qa/contracts.tsv (post-command
network succeeds; unknown post-command option stays command-local usage).

Adds GlobalOptions, OutputFormatter, and ClientMain tests for the above.
Comment on lines +228 to +231
private void renderQueryResult(Object jsonData) {
if (shouldRenderAsMetadata(jsonData)) {
renderMetadata(jsonData);
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

queryResult() in TEXT mode drops both empty objects and message-only results.
normalizeJsonData() wraps non-JSON strings into { message: ... }, and empty protobuf/list payloads may become {}.
Then shouldRenderAsMetadata() returns true, but renderMetadata() (line 242) skips the message field, leaving nothing to print—so the output degrades to just “X successful !!”.

This affects the commands switched to queryResult() in this commit (e.g., QueryCommands.java:746).
The previous printMessage at least printed the raw payload.

Suggestion:

  • Make shouldRenderAsMetadata() require at least one non-message field.
  • Otherwise, fall back to Result: output, or directly print message for message-only cases.

Suggested tests:

Input After normalize shouldRenderAsMetadata renderMetadata output
"plain text" (non-JSON) {"message":"plain text"} (Map) true (only message, skipped) empty → nothing printed
"{}" (empty JSON, e.g. empty proto) empty JsonObject true (size ≤ 12, loop not entered) empty → nothing printed
{"message":"only"} single-key JsonObject true (only key skipped) empty → nothing printed

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. shouldRenderAsMetadata() now requires at least one printable non-message field before using the Metadata rendering path, so message-only payloads and empty objects fall back to Result: instead of being dropped.

Added regression coverage for non-JSON text (data.message) and empty JSON object query results in TEXT mode.

Comment on lines 83 to 94
case "help":
if (commandSeen) {
commandArgs.add(token);
break;
}
ensureNoInlineValue(parsed, "--help");
opts.help = true;
break;
case "version":
ensureNoInlineValue(parsed, "--version");
opts.version = true;
break;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behavior change:
--version and --interactive are now accepted as post-command global options, and are handled early in Client.java:4721 before command execution.

This means wallet-cli get-balance --version returns the version instead of a command usage error.

The docs seem to allow “known global options can appear after the command”, but --help is explicitly reserved for command-level handling.

Suggestion:
Confirm whether --version / --interactive should also be restricted to pre-command only, for consistency with --help.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this contract: --version and --interactive are now treated as top-level mode selectors and are recognized only before the command token. When they appear after a command, they are passed through as command-local args and produce command usage errors such as Unknown option: --version.

Updated standard-cli-contract-spec.md, added parser/runner tests, and added QA contract cases for post-command --version / --interactive.

Comment on lines +238 to +239
private String indentResult(String text) {
return " " + text.replace(System.lineSeparator(), System.lineSeparator() + " ");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-platform bug:
OutputFormatter.indentResult() uses System.lineSeparator(), but Gson always emits \n (see OutputFormatter.java:238-240):

private String indentResult(String text) {
    return "  " + text.replace(System.lineSeparator(), System.lineSeparator() + "  ");
}

gson.toJson(...) internally hardcodes \n as the line break, regardless of platform.
On Windows, System.lineSeparator() == "\r\n", so replace() never matches → multiline JSON is not indented except for the first line.

Suggestion:

return "  " + text.replace("\n", "\n  ");

(println(...) will append the platform-specific newline anyway; using \n here for internal formatting is sufficient.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. indentResult() now indents Gson pretty-printed output by replacing literal \n, which matches Gson's internal line separator regardless of the platform line separator.

Added TEXT mode coverage that asserts pretty-printed JSON lines are indented after line feeds.

Prevent text query results from dropping message-only or empty payloads, and make Gson-formatted result indentation platform-independent. Treat --version and --interactive as pre-command top-level modes only, with post-command usage now handled by command parsing; update the standard CLI contract, QA cases, and regression tests accordingly.
@zerodevblock-cyber zerodevblock-cyber merged commit cdb3273 into release_v4.9.5 Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants