v1.2.0
What's New
Honest exit codes and valid JSON from every test runner
run and run_all no longer behave differently depending on --output. Two bugs made -o json unusable in CI, and both existed in all twelve run/run_all commands across unit_test, workflow_test, tenant, and sandbox — not just the two the tickets named. (#49, #50)
run -o jsonexited0on a failing test. Thethis.exit(1)sat inside the summary-only branch, so any CI job that added-o jsonto get machine-readable output silently lost failure detection and the pipeline went green over red tests. (DEV-7545)run_all -o jsonprinted a bare string on an empty branch. The empty-list early return loggedNo workflow tests foundbefore ever consulting--output, soJSON.parse()threw aSyntaxError. A branch with no tests is a normal state — fresh branches routinely have none. (DEV-7546)
Two further defects surfaced during the fix and are also resolved:
- Summary mode exited
2, not1.this.exit(1)throws an oclifExitErrorthat the surrounding catch swallowed and re-raised throughthis.error(), producing exit2plus a spuriousFailed to run workflow test: EEXIT: 1line. The same swallow double-wrapped genuine API error messages. workflow_test run_allcounted a timing-less result twice..toFixed()ran on atimingthat was typed required but can be absent, throwing after the result had already been pushed — so the inner catch pushed a second result for the same test, reporting1 passed, 1 failed (NaNs total)and exit1for a suite where nothing failed.
process.exitCode replaces this.exit(1) throughout. It does not throw, so the catch cannot swallow it, it applies in both output modes, and it is the mechanism run_all already used — so run and run_all now agree by construction rather than by coincidence.
Exit-code contract (now documented in the README)
| Code | Meaning |
|---|---|
0 |
All tests passed, or there were no tests to run |
1 |
At least one test failed |
2 |
The command could not run the tests at all — bad workspace, auth failure, unreadable profile, or the test-list request failed |
Identical for -o json and summary, on all four surfaces.
One asymmetry is documented rather than hidden: inside run_all, a non-2xx for an individual test is recorded as a failed test (API error <status>) and contributes to exit 1, because run_all's job is to finish the batch and report a roll-up. The same 500 in run is a CLI error and exits 2. A README claiming clean 1-vs-2 separation would send CI authors to file bugs against healthy tests during an outage. Consumers needing to tell the two apart inspect results[].message.
⚠️ Behavior change
A failing run in summary mode now exits 1 instead of 2. The prior value was accidental, not designed — it was the swallowed ExitError described above. Non-zero checks (the common case) are unaffected; anything matching specifically on 2 needs updating.
Output shapes are otherwise unchanged. total_timing remains emitted by workflow_test run_all only.
Testing
test/commands/test-runner-exit-parity.test.ts adds 79 cases across all twelve commands, verified to fail against the pre-fix tree — 35 go red when src/ is reverted, so they catch the defects rather than merely describing them. Three layers: behavioral (stubbing globalThis.fetch and asserting the real process.exitCode), static flag-shape, and a source scan asserting all 18 catch blocks re-throw oclif errors and no command calls this.exit().
Published to npm as @xano/cli@1.2.0.
Full Changelog: v1.1.0...v1.2.0