Skip to content

Development

Rod Christiansen edited this page Sep 3, 2026 · 3 revisions

Development

How to build, test and release the CLI. The repo is deliberately small: five Rust source files, two GitHub Actions workflows, and no build script.

Prerequisites

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

Layout

Path What is in it
src/main.rs Entry point, the command match arms, query-string helpers, and the unit tests
src/cli.rs The whole clap definition — every command, subcommand and flag
src/client.rs HTTP client: auth headers, timeout, and the GET/POST/PUT/PATCH/DELETE helpers
src/config.rs Environment-variable loading and credential selection
src/output.rs JSON pretty-printing and the two table renderers
Cargo.toml Crate metadata and the release profile
.github/workflows/ci.yml Format, clippy, build, test on every push and PR to main
.github/workflows/release.yml Cross-platform binaries on v* tags

The dependency set is intentionally short: clap, reqwest, tokio, serde, serde_json, anyhow, comfy-table, plus libc on Unix only (for the SIGPIPE reset).

Build and run

git clone https://github.com/reportmate/reportmate-cli
cd reportmate-cli
cargo build

Run against a live API by exporting the variables from Configuration and using cargo run. Note the -- separating cargo's arguments from the CLI's.

cargo run -- devices --limit 5

Test, lint, format

CI runs these four steps in order and treats any clippy warning as an error, so run them locally before pushing.

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

--locked means Cargo.lock must already be up to date; a dependency bump that is not committed will fail CI rather than silently resolving.

Test coverage, honestly

The test module in src/main.rs covers urlencode and query_string only — five tests, all pure functions. Nothing that performs a request is tested: there is no mock HTTP server, no fixture responses, no snapshot tests of the table renderers, and no end-to-end test against a running API.

If you are adding a command, the practical consequence is that CI will not catch a wrong path, a wrong method, or a broken table. Verify against a real or local API by hand.

Adding a command

  1. Add a variant to Command in src/cli.rs, with doc comments — clap turns those into the help text.
  2. Add the matching arm in main.rs and call the relevant Client helper.
  3. If it needs a table, add a renderer to output.rs and branch on args.output; otherwise call output::print_json.
  4. Update the Commands page.

Field names in clap's derive become kebab-case flags, so include_archived becomes --include-archived and the CleanupUsage variant becomes cleanup-usage.

Versioning and releases

The version string comes from RM_VERSION at compile time when set, and falls back to the Cargo package version otherwise. The release workflow sets it from the tag with the leading v stripped, so tag names carry the real version and Cargo.toml's version field is only a development placeholder.

Cutting a release is pushing a v* tag. The workflow then builds four targets in parallel, packages each binary as a .tar.gz, and attaches the archives to the GitHub release with softprops/action-gh-release.

git tag v2026.09.03.1400
git push origin v2026.09.03.1400

The matrix has fail-fast: false, so one target failing still ships the others — check that all four archives are attached before announcing a release.

There is no signing, notarization, checksum generation, or package-manager publication in the workflow, and the crate is not published to crates.io.

Known rough edges

Worth knowing before you file a bug that is really a design gap:

  • No config file or profiles; the source comments flag this as future work.
  • Responses are untyped serde_json::Value. The stated intent is to generate a typed client from the API's OpenAPI spec later.
  • No retry, backoff, or rate-limit handling.
  • The 120-second timeout is hard-coded.
  • No Intel macOS release target.

Clone this wiki locally