Skip to content

Development

Rod Christiansen edited this page Oct 8, 2026 · 3 revisions

Development

How to build, test and release the CLI.

Prerequisites

A stable Rust toolchain with rustfmt and clippy. TLS is rustls, so there is no OpenSSL or other system library to install, and there is no code-generation step.

Layout

Path What is in it
src/main.rs Entry point, the command dispatch, the query-string builder, the config printer, unit tests
src/cli.rs The clap definition: every command, subcommand and flag, and the version constant
src/client.rs HTTP client: credential headers, timeout, JSON-or-text bodies
src/config.rs Endpoint and credential resolution (environment, app connection, runner preferences, Entra, AWS), unit tests
src/output.rs JSON and text printing, the devices and events tables
tests/routes.rs Contract tests against a recording mock API
tests/openapi.json The API's OpenAPI document, generated from the API source
.github/workflows/ci.yml Format, clippy, build and test on every push and pull request to main
.github/workflows/build.yml Every platform binary on every push and pull request; the release on a v* tag
.github/workflows/openapi-sync.yml Weekly regeneration of tests/openapi.json

Dependencies: clap, reqwest (rustls), tokio, serde, serde_json, anyhow, comfy-table, plus libc on Unix for the SIGPIPE reset.

Build and run

cargo build

Run against a live API with cargo run; the -- separates cargo's arguments from the CLI's.

cargo run -- devices --limit 5

Test, lint, format

CI runs these steps in order and treats any clippy warning as an error.

cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo build --locked
cargo test --locked

--locked means Cargo.lock must be committed and current.

Tests

Unit tests cover URL encoding, the query builder, connection.json parsing, reg query parsing, cloud detection from the host, and reading the audience from /api/v1/auth/config.

tests/routes.rs starts a recording mock of the API and runs the built binary against it with REPORTMATE_NO_DISCOVERY=1, so nothing on the test machine leaks in. It checks:

  • every command sends the exact method, path and query documented in its ROUTES table;
  • POST and PUT bodies are the documented JSON;
  • each credential maps to its header;
  • API errors fail with the upstream status;
  • text endpoints print verbatim and JSON is the body pretty-printed;
  • destructive commands refuse without --confirm;
  • OpenAPI parity: every operation in tests/openapi.json has a ROUTES row, and no row names an operation the API no longer has (GET /api/v1/metrics is the one listed exception, since it is not in the spec).

The openapi-sync workflow runs every Monday (and on demand), regenerates tests/openapi.json from the reportmate-api source with its scripts/export_openapi.py, and opens a pull request on chore/openapi-sync when it changes. When the refreshed spec gains an operation, the parity test (every_api_operation_has_a_command) fails on that branch until the CLI has a command for it and a ROUTES row. A pull request opened by the workflow's own token does not start the ci workflow by itself, so run it on that branch or push to it to see the result.

Adding a command

  1. Add the variant to Command (or a subcommand enum) in src/cli.rs, with doc comments — clap turns them into help text.
  2. Add the arm in src/main.rs, building the query with Query so unset flags are not sent.
  3. Add a row to ROUTES in tests/routes.rs naming the exact request and the OpenAPI operation it implements.
  4. Update the Commands page.

clap turns field names into kebab-case flags: include_archived becomes --include-archived, and CollectionHealth becomes collection-health.

Versioning and releases

The version comes from RM_VERSION at compile time and falls back to the Cargo package version (0.1.0), which is only a development placeholder. Release tags are v plus a calendar version, vYYYY.MM.DD.HHMM; the build workflow strips the v into RM_VERSION.

Releases are cut deliberately by pushing a tag; merging to main builds artifacts but publishes nothing.

git tag v2026.09.11.1807
git push origin v2026.09.11.1807

build.yml builds Linux and both Windows targets in a matrix, and both macOS architectures on one macOS runner so it can lipo the universal binary and build the unsigned .pkg. The release job runs only on a tag, only after every build job succeeded, and attaches every asset in one call, so a release never appears with an asset missing. Nothing is signed, notarized or published to crates.io.

Clone this wiki locally