Repository navigation
Development
How to build, test and release the CLI.
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.
| 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.
cargo build
Run against a live API with cargo run; the -- separates cargo's arguments
from the CLI's.
cargo run -- devices --limit 5
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.
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
ROUTEStable; - 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.jsonhas aROUTESrow, and no row names an operation the API no longer has (GET /api/v1/metricsis 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.
- Add the variant to
Command(or a subcommand enum) insrc/cli.rs, with doc comments —clapturns them into help text. - Add the arm in
src/main.rs, building the query withQueryso unset flags are not sent. - Add a row to
ROUTESintests/routes.rsnaming the exact request and the OpenAPI operation it implements. - Update the Commands page.
clap turns field names into kebab-case flags: include_archived becomes
--include-archived, and CollectionHealth becomes collection-health.
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.
AGPL-3.0-or-later — reportmate.app