fix(cli): emit JSON error envelopes for argument-parse errors under --output json#25
Closed
Davidson3556 wants to merge 1 commit into
Closed
Conversation
…-output json
`--output json` is the contract machine consumers (CI, coding agents) rely
on: parse stderr as JSON on failure. But Commander's own parse errors —
unknown command, unknown option, missing/excess argument — were written as
plain text even under `--output json`, so a single malformed invocation
crashed any consumer doing `JSON.parse(stderr)`. Commands that reached their
action already emitted JSON; only the pre-action parse layer did not.
Make the error path honor the mode:
- Resolve the requested `--output` mode directly from argv (order-
independent, and available even when the parse fails before Commander
binds the global flag).
- In JSON mode, suppress Commander's plain-text `outputError` write and let
the central catch emit a structured `VALIDATION_ERROR` envelope (exit 5,
same family/exit code as before) carrying the message and the
`commanderCode`. Apply the output config to every command in the tree,
since subcommands do not inherit the root's configuration.
- Text mode is unchanged: the friendly rephrasing and Commander's
"Did you mean …?" hints still print. Help/version still exit 0 with
human-readable text.
Adds subprocess regressions covering unknown command, unknown option on a
subcommand (deep config), missing argument on a nested subcommand, the
flag-after-failing-token ordering, and the text-mode / --help guards.
Author
|
Closing as a duplicate of #22, which was opened earlier and covers the same fix (JSON envelopes for Commander parse errors under |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #24
Describe the changes you have made in this PR -
--output jsonis the contract a machine consumer (CI, coding agent) relies on: on failure,JSON.parse(stderr). That held for any error that reached a command action, but not for Commander's own argument-parse errors — unknown command, unknown option, missing/excess argument — which printed plain text even under--output json. A single malformed invocation crashed any consumer parsing stderr as JSON.Changes:
--outputmode directly from argv. A parse error can be thrown before Commander binds the global--outputoption (e.g. an unknown command, or--outputplaced after the failing token), so scanning argv is the only order-independent way to know what the caller asked for.outputErrorwrite and emit a structuredVALIDATION_ERRORenvelope (exit 5 — same family and exit code as before) from the central catch, carrying the message and thecommanderCodeindetails.project list --bogus/test code get.--help/--versionstill exit 0 with human-readable text.Demo/Screenshot for feature changes and bug fixes -
Before (main) — plain text breaks
JSON.parse:After (this branch) — structured envelopes:
Text mode and help are untouched:
Tests:
Code Understanding and AI Usage
Did you use AI assistance (ChatGPT, Claude, Copilot, etc.) to write any part of this code?
If you used AI assistance:
Explain your implementation approach:
Problem: the JSON output contract was enforced for command-action errors but not for the argument-parse layer that runs first. Commander writes parse errors via its
outputErrorhook (plain text) and then throws aCommanderErrorthat the entry point's catch maps to exit 5 — so the plain text was already on stderr before the catch could format anything.Alternatives considered:
outputErroritself emit the JSON envelope. Rejected:outputErroronly sees the message string, not a clean code; the catch already classifies theCommanderError(help/version vs parse error) and owns exit-code mapping, so it is the right place to emit.outputErroronly needs to suppress the duplicate plain-text write in JSON mode.outputError(suppress in JSON, rephrase in text) applied to every command, plus a JSON branch in the existingCommanderErrorhandler in the catch.Key pieces:
outputModeFromArgv(argv)scans for--output <mode>/--output=<mode>(returnstextfor an unknown value or when absent) — needed becauseprogram.opts()is unreliable when parsing failed early.configureErrorOutput(cmd)recursively installs the mode-awareoutputError(mirroring the existingapplyExitOverrideDeep, sinceaddCommand()subcommands don't inherit it). The catch strips any leadingerror:prefix fromerr.messageand emits{ code: VALIDATION_ERROR, message, nextAction, requestId: "local", details: { commanderCode } }.Edge cases handled/tested: unknown command; unknown option on a subcommand (proves the deep config, not just root); missing required argument on a nested subcommand (
test code get);--outputplaced after the failing token; text mode still plain text (regression guard);--helpunder--output jsonstill exits 0 with human text (help/version are checked before the JSON branch, and don't go throughoutputError).Checklist before requesting a review
Note: Please check Allow edits from maintainers if you would like us to assist in the PR.