Skip to content

fix: deliver USAGE exit + JSON envelope for bad flags; clean switch output#116

Open
tcerqueira wants to merge 4 commits into
mainfrom
tc/cli-contract-fixes
Open

fix: deliver USAGE exit + JSON envelope for bad flags; clean switch output#116
tcerqueira wants to merge 4 commits into
mainfrom
tc/cli-contract-fixes

Conversation

@tcerqueira

Copy link
Copy Markdown

Fixes two CLI-contract rough edges from #112.

1. Bad flags now honor the agent/CI contract

Previously, an unknown/invalid flag dumped the full help text to stdout and a raw, ANSI-colored Cliffy error to stderr — even under --json — corrupting machine-readable output (the exit code already happened to be 2 because @cliffy/command's ValidationError.exitCode defaults to 2, but everything around it violated the contract).

Now the root parse() runs with .noExit() and thrown ValidationErrors (unknown/invalid/conflicting flags, missing values, and app-thrown ValidationErrors re-thrown by actionHandler) are funneled through the CLI error contract:

  • exit ExitCode.USAGE (2),
  • under --json: a single { "error": { "code": "VALIDATION_ERROR", "message": ... } } envelope on stderr, clean stdout, ANSI disabled,
  • otherwise: the standard human-readable error.

.reset() repoints Cliffy's builder to the root command before .noExit() (mounting/defining subcommands leaves the builder selecting a child, so the setting would otherwise land on the wrong command). --help and --version still exit 0.

2. switch no longer pollutes stdout

switch printed its confirmation to stdout via console.log and emitted nothing under --json. It now sends the human message to stderr and, under --json, writes a single { org, app } result to stdout (app is null for the sandbox variant, which doesn't resolve an app).

Tests

  • New token-free subprocess suite tests/cli_contract.test.ts: bad flag → 2 (deploy + standalone sandbox roots), --json bad flag → 2 with envelope on stderr and clean stdout, --help/--version → 0.
  • Tightened the existing invalid-flag test to assert USAGE (2) and a clean stdout.

Verified offline: bad flags (plain + --json), --help, --version, unknown subcommand-as-path, bad option values, and both switch output modes.

Run the root command's `parse()` with `.noExit()` and funnel thrown
`ValidationError`s (unknown/invalid/conflicting flags, missing values)
through the CLI error contract so they exit with `ExitCode.USAGE` (2)
and, under `--json`, emit the structured `{error:{code,message,...}}`
envelope on stderr instead of dumping help to stdout.

Previously a bad flag printed full help to stdout and a raw, ANSI-colored
Cliffy error to stderr even under `--json`, corrupting machine-readable
output. `.reset()` repoints the builder to the root command before
`.noExit()`, since mounting/defining subcommands leaves it selecting a
child. `--help`/`--version` still exit 0.

Adds a token-free subprocess test for the exit-code contract and tightens
the existing invalid-flag test to assert USAGE (2) plus a clean stdout.
`switch` printed its confirmation to stdout via console.log and emitted
nothing under `--json`, corrupting piped/machine-readable output. Route
the human-readable message to stderr and, under `--json`, write a single
`{ org, app }` result to stdout via `writeJsonResult` (app is null when
the sandbox variant doesn't resolve an application).
…dler

`handleCliError` picked the error output format with a `--json`/`-j`
substring-free includes check, missing `--json=...` and combined short
flags like `-jy`/`-yj`. A parse-time ValidationError in those invocations
printed the human-readable ANSI error instead of the structured
VALIDATION_ERROR envelope. Broaden detection to a parse-free heuristic
matching `--json`, `--json=...`, `-j`, and combined short flags containing
`j`. Adds a `-jy` envelope test case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant