Output & Integration
LATdx is built to drop into existing tooling without surprises. The output streams follow a strict contract, JSON modes are stable, and the human-friendly live renderer disables itself automatically when piped or run under CI.
Stream Contract
The CLI separates payload from diagnostics on the two output streams:
| Stream | What’s on it |
|---|---|
| stdout | Command result only. With --json, exactly one JSON document. Without --json, the human-formatted result. |
| stderr | Logs (text or NDJSON), spinners, the live test renderer, --progress-events, and interactive prompts. |
The two streams are never mixed. Pipes like latdx test run --json | jq work without any extra flags.
The CLI auto-degrades the stderr-side affordances (spinner, live renderer, color) when:
- stdout is not a TTY,
CIis set to any non-empty value,NO_COLORis set,TERM=dumb,- or
--jsonis passed.
You can force the degraded mode with --plain. You can also force colors off explicitly with --no-color. There is no --live / --no-live toggle; the live renderer is automatic.
--json (Result Payload)
latdx test run --json and latdx test list --json emit exactly one JSON document on stdout. The test run envelope is drop-in compatible with sf apex test run --json:
{
"status": 0,
"result": {
"summary": {
"outcome": "Passed",
"testsRan": 12,
"passing": 12,
"failing": 0,
"passRate": "100%",
"failRate": "0%",
"testStartTime": "2026-04-27T12:00:00.000Z",
"testExecutionTime": "1500 ms",
"testTotalTime": "1700 ms",
"commandTime": "1900 ms",
"hostname": "...",
"orgId": "00D...",
"username": "user@example.com",
"userId": "005...",
"testRunId": ""
},
"tests": [
{
"Id": "",
"QueueItemId": "",
"StackTrace": null,
"Message": null,
"AsyncApexJobId": "",
"MethodName": "passes_simple",
"Outcome": "Pass",
"ApexClass": { "Id": "", "Name": "MyTestClass", "NamespacePrefix": "" },
"RunTime": 120,
"FullName": "MyTestClass.passes_simple"
}
]
},
"warnings": []
}Notes:
statusis0on full pass,1if any test failed.OutcomeisPass/Fail/Skip.FullNameis<ClassName>.<methodName>— same assf.- Org-side identifiers LATdx cannot produce locally (
Id,QueueItemId,AsyncApexJobId,ApexClass.Id,testRunId) are emitted as empty strings, matchingsf’s behavior on local-only runs. result.coverageis not yet populated; tracked as a follow-up.--jsonimplies--plain, so the live renderer and spinner stay off and stdout remains a single parseable document.
latdx test list --json returns a structured list of discovered methods on stdout instead of the human one-line-per-method format. Diagnostics still go to stderr.
--progress-events (Streaming NDJSON)
For programmatic consumers that want method-by-method progress without parsing the live renderer, pass --progress-events. Each event is one JSON object on stderr, newline-delimited:
// queued: discovery / scheduling
{ "event": "queued", "className": "MyTest", "methodName": "happy" }
// start: batch began executing this method
{ "event": "start", "className": "MyTest", "methodName": "happy" }
// complete (success): final state
{ "event": "complete", "className": "MyTest", "methodName": "happy",
"status": "passed", "success": true }
// complete (failure): includes errorMessage / message when available
{ "event": "complete", "className": "MyTest", "methodName": "boom",
"status": "failed", "success": false,
"errorMessage": "System.AssertException: ...",
"message": "Assertion Failed: expected 1 was 2" }Consumer rules:
- Read line by line on stderr; ignore non-JSON lines (logger output may interleave when log level allows).
- Dedupe by
(className, methodName)forqueuedevents: a packed batch failure causes the runner to re-enqueue remaining tests, which re-emits aqueuedevent for each. - A method always ends with exactly one
completeevent per attempt.
--progress-events composes with --json: the JSON result still lands on stdout, and the NDJSON progress stream lands on stderr.
CI Recipes
latdx test run is non-interactive when CI=true or stdout is not a TTY: it never blocks on a confirmation prompt. The Delete all logs to continue? debug-log-storage prompt auto-accepts and retries. Set LATDX_AUTO_CONFIRM=true to also force auto-confirmation in interactive shells.
GitHub Actions
name: Apex Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
env:
CI: "true"
NO_COLOR: "1"
SF_AUTH_URL: ${{ secrets.SF_AUTH_URL }}
steps:
- uses: actions/checkout@v4
- name: Install Salesforce CLI
run: npm install --global @salesforce/cli
- name: Authenticate org
run: |
echo "$SF_AUTH_URL" | sf org login sfdx-url --sfdx-url-stdin --alias ci-org --set-default
- name: Install LATdx
run: curl -fsSL https://raw.githubusercontent.com/nebulity/latdx-cli/main/install.sh | bash
- name: Verify org readiness
run: latdx --version -o ci-org
- name: Run tests
run: latdx test run --json -o ci-org > test-result.json
- name: Upload result
if: always()
uses: actions/upload-artifact@v4
with:
name: latdx-test-result
path: test-result.json--json > file.json is the simplest way to capture results: stdout is a clean JSON document, while logs and progress remain on stderr (and in the Actions log).
Generic Shell
export CI=true
latdx test run --json -o ci-org > test-result.json
exit_code=$?
jq '.result.summary | {outcome, passing, failing}' test-result.json
exit $exit_code--progress-events pairs naturally with custom dashboards or chat notifications:
latdx test run --json --progress-events -o ci-org > result.json 2> events.ndjson
# tail -f events.ndjson | jq -c 'select(.event == "complete")'Container Image
LATdx ships static binaries with no Node runtime. A minimal container only needs the binary plus sf and a CA bundle:
FROM debian:stable-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl bash git && rm -rf /var/lib/apt/lists/*
RUN npm install --global @salesforce/cli
RUN curl -fsSL https://raw.githubusercontent.com/nebulity/latdx-cli/main/install.sh | bash
ENV CI=true NO_COLOR=1
ENTRYPOINT ["latdx"]Editor Integration
LATdx ships a VS Code extension (apps/extension) with the same engine as the CLI: in-editor coverage gutters, code-lens “Run / Debug” affordances on Apex test methods, and a sidebar showing live progress. CLI behavior and extension behavior are intentionally aligned, so both pick up the same ~/.latdx/ caches and the same managed-package version requirements.
The extension is distributed via the GitHub releases for now; a Marketplace listing is planned.
When both the CLI and the extension are in use on the same workspace, they share the workspace daemon. Stopping the daemon (latdx daemon stop) affects both; running tests through either path warms the cache for the other.
Exit Codes
| Code | Meaning |
|---|---|
0 | Successful run / list / adapt path; all tests passed. |
1 | Validation failure, runtime error, or one-or-more test failures. |
latdx uninstall and latdx restore also exit 1 when class restoration fails. latdx --version -o <alias> exits 1 if the org package query fails. Prefer --json in CI: parse the envelope to distinguish “test failed” (status: 1, result.summary.failing > 0) from “engine failed” (no envelope, non-zero exit, log line on stderr).