Skip to content

fix(cli): emit JSON envelope for Commander parse errors under --output json#22

Open
crypticsaiyan wants to merge 1 commit into
TestSprite:mainfrom
crypticsaiyan:fix/commander-parse-errors-respect-output-json
Open

fix(cli): emit JSON envelope for Commander parse errors under --output json#22
crypticsaiyan wants to merge 1 commit into
TestSprite:mainfrom
crypticsaiyan:fix/commander-parse-errors-respect-output-json

Conversation

@crypticsaiyan

Copy link
Copy Markdown

Summary

When --output json is set, Commander-level parse errors (e.g., unknown command, missing arg) previously exited with code 5 but wrote plain text to stderr. This broke machine consumers expecting structured JSON data. All other error paths already correctly emit JSON; Commander was the sole exception.

The Fix

Root Cause: The configureOutput setting was only applied to the root program. Subcommands kept Commander's default behavior, which writes plain text immediately before the CommanderError is thrown and the output mode is checked.

Changes:

  • Propagation & Buffering: applyExitOverrideDeep now passes configureOutput to all subcommands. Errors are buffered instead of written immediately.
  • Smart Envelope Generation: The CommanderError catch block now evaluates the output mode to write either a VALIDATION_ERROR JSON envelope or standard plain text.
  • Argv Scan Fallback: Added a fallback to detect --output json if it appears in the command line after the invalid argument.
  • Testability: Extracted renderCommanderError to src/lib/render-error.ts for direct unit testing without needing a subprocess.

Testing & Validation

Regression Tests:

  • Unit Tests: 8 tests added in src/lib/render-error.test.ts covering JSON/text modes, null fallbacks, and envelope shapes.
  • Subprocess Tests: 4 tests added in test/cli.subprocess.test.ts covering missing args, unknown commands, argv fallbacks, and text mode baselines.

CI Gates: All local checks pass.

  • Lint & format check
  • TypeScript compiler (typecheck)
  • Unit tests (1459 passing)
  • Coverage (≥80% threshold met)

Files Changed:

  • src/lib/render-error.ts
  • src/index.ts
  • src/lib/render-error.test.ts
  • test/cli.subprocess.test.ts

…t json

When a subcommand fired a parse error (unknown command, missing required
argument, invalid option), Commander's outputError callback wrote plain
text to stderr immediately and the catch block exited 5 with no further
output. A machine consumer that always parses stderr as JSON received an
unexpected plain-text string and crashed its JSON.parse.

Root cause: configureOutput was only applied to the root program, not to
subcommands. Each subcommand retained the default outputError that calls
write(str) directly. applyExitOverrideDeep now also propagates
configureOutput to every leaf so the message is buffered instead of
written.

In the CommanderError catch block, a resolved output mode is used to
either write a VALIDATION_ERROR JSON envelope or the buffered plain-text
message. An argv scan fallback handles the edge case where --output json
appears after the bad argument and was not yet parsed when the error fired.

The renderCommanderError helper is extracted to src/lib/render-error.ts
(alongside the existing rephraseUnknownOption helper) so it is unit-testable
without a subprocess. Eight unit tests cover JSON/text output, null fallback,
message trimming, and rephrased global-flag embedding. Four subprocess
regression tests in the [fix-5] block cover missing-arg, unknown subcommand,
argv-fallback, and text-mode no-regression paths.
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.

1 participant