You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
4.3 KiB
Markdown

+++
title = "Add --version flag with X.Y.Z+GitSha format"
priority = 5
status = "todo"
ticket_type = "feature"
dependencies = []
+++
## Goal
Add a `--version` / `-V` flag to `nbd` that prints the version in the format:
```
0.1.0+7e311d6
```
where `0.1.0` comes from `Cargo.toml` and `7e311d6` is the short git SHA of the commit the binary was built from, embedded at compile time via `build.rs`.
## Why
Allows agents and users to confirm exactly which build of `nbd` is running without inspecting the binary separately.
## Implementation plan
### 1. Create `build.rs` at the crate root
```rust
fn main() {
// Capture the short git SHA at build time.
// Falls back to "unknown" when git is unavailable (e.g. CI without repo).
let sha = std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()
.and_then(|o| if o.status.success() { Some(o.stdout) } else { None })
.and_then(|b| String::from_utf8(b).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
println\!("cargo:rustc-env=GIT_SHORT_SHA={sha}");
// Re-run whenever HEAD changes (new commits).
println\!("cargo:rerun-if-changed=.git/HEAD");
println\!("cargo:rerun-if-changed=.git/refs");
}
```
Key points:
- Uses `std::process::Command` — no extra build dependencies.
- Graceful fallback to `"unknown"` when git is absent (clean Nix sandbox builds may not have `.git/`).
- `rerun-if-changed` directives ensure the SHA is refreshed on every commit without forcing a full rebuild every run.
### 2. Add a `VERSION` constant in `src/main.rs`
Add near the top of `main.rs` after the existing `CLAUDE_MD_SNIPPET` constant:
```rust
/// Full version string embedded at compile time: `"X.Y.Z+shortsha"`.
const VERSION: &str = concat\!(env\!("CARGO_PKG_VERSION"), "+", env\!("GIT_SHORT_SHA"));
```
### 3. Wire `VERSION` into the clap `#[command(...)]` attribute
Change:
```rust
#[command(name = "nbd", about = "Manage work tickets for agent workflows")]
```
to:
```rust
#[command(name = "nbd", about = "Manage work tickets for agent workflows", version = VERSION)]
```
clap automatically handles `-V` / `--version` when `version` is set: it prints the string and exits 0. No manual subcommand or dispatch code needed.
### 4. Update `README.md`
Add a brief note in the **Usage** section or **Installation** section:
```sh
nbd --version # prints e.g. 0.1.0+7e311d6
```
### 5. Add an integration test in `tests/integration.rs`
```rust
/// `nbd --version` exits 0 and stdout contains the semver.
#[test]
fn version_flag_exits_zero_with_semver() {
let tmp = tempfile::tempdir().expect("tempdir");
let output = std::process::Command::new(env\!("CARGO_BIN_EXE_nbd"))
.arg("--version")
.current_dir(tmp.path())
.output()
.expect("spawn nbd");
assert\!(output.status.success(), "--version should exit 0");
let stdout = String::from_utf8(output.stdout).unwrap();
// Should contain the package version (semver).
assert\!(stdout.contains("0.1.0"), "--version should include semver: {stdout}");
// Should contain a '+' separator and at least one hex char after it.
assert\!(stdout.contains('+'), "--version should contain '+': {stdout}");
}
```
## Files to change
| File | Change |
|---|---|
| `build.rs` | **Create** — emit `GIT_SHORT_SHA` env var |
| `src/main.rs` | Add `VERSION` const; add `version = VERSION` to `#[command]` |
| `README.md` | Add `nbd --version` example |
| `tests/integration.rs` | Add `version_flag_exits_zero_with_semver` test |
## Edge cases
- **No git available (Nix sandbox):** `build.rs` falls back to `"unknown"`, producing `0.1.0+unknown`. This is acceptable for packaged builds where the version is already pinned by the derivation.
- **clap version output format:** clap 4 prints `nbd X.Y.Z+sha` (prefixed by the binary name) to stdout, then exits 0. This is standard behaviour — the test should check `stdout.contains("0.1.0")` rather than an exact match.
- **No `--json` support for `--version`:** clap intercepts `--version` before dispatch reaches the `--json` handling. This is acceptable; version output is always plain text.
## Validation
```sh
cargo fmt && cargo check && cargo clippy && cargo test
cargo run -- --version
# Expected output: nbd 0.1.0+<sha>
```