diff --git a/nbd/README.md b/nbd/README.md index 1bc3010..6f34277 100644 --- a/nbd/README.md +++ b/nbd/README.md @@ -220,6 +220,7 @@ cargo install --path . ```sh # From the nbd/ directory +cargo run -- --version # prints e.g. nbd 0.1.0+7e311d6 cargo run -- init cargo run -- create --title "Test ticket" --priority 7 --type bug cargo run -- list diff --git a/nbd/build.rs b/nbd/build.rs new file mode 100644 index 0000000..bd494da --- /dev/null +++ b/nbd/build.rs @@ -0,0 +1,31 @@ +//! Build script for `nbd`. +//! +//! Captures the short git SHA at compile time and emits it as the +//! `GIT_SHORT_SHA` environment variable, making it available via +//! `env!("GIT_SHORT_SHA")` in the crate source. +//! +//! Falls back to `"unknown"` when git is unavailable (e.g. a clean Nix +//! sandbox build where `.git/` is not present). + +fn main() { + // Capture the short git SHA at build time. + 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 or any ref changes (new commits, branch switches). + println!("cargo:rerun-if-changed=.git/HEAD"); + println!("cargo:rerun-if-changed=.git/refs"); +} diff --git a/nbd/src/main.rs b/nbd/src/main.rs index 618e8b3..6363264 100644 --- a/nbd/src/main.rs +++ b/nbd/src/main.rs @@ -19,6 +19,12 @@ mod ticket; /// ``` const CLAUDE_MD_SNIPPET: &str = include_str!("claude_md_snippet.md"); +/// Full version string embedded at compile time: `"X.Y.Z+shortsha"`. +/// +/// The semver comes from `Cargo.toml` via `CARGO_PKG_VERSION`; the short SHA +/// is injected by `build.rs` via `GIT_SHORT_SHA`. +const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "+", env!("GIT_SHORT_SHA")); + #[cfg(test)] mod tests; @@ -39,7 +45,7 @@ use crate::ticket::{generate_id, validate_priority, Status, Ticket, TicketType}; /// ancestor directory that contains a `.nbd/` folder, discovered by traversing /// upward from the current working directory (like `git` finds `.git/`). #[derive(Parser)] -#[command(name = "nbd", about = "Manage work tickets for agent workflows")] +#[command(name = "nbd", about = "Manage work tickets for agent workflows", version = VERSION)] struct Cli { /// Output machine-readable JSON instead of a human-readable table. #[arg(long, global = true)] diff --git a/nbd/tests/integration.rs b/nbd/tests/integration.rs index 9d1b9fc..f3356e7 100644 --- a/nbd/tests/integration.rs +++ b/nbd/tests/integration.rs @@ -2158,3 +2158,33 @@ fn update_no_changes_prints_no_changes() { "should print '(no changes)' when nothing changed: {stdout}" ); } + +// ── nbd --version tests ─────────────────────────────────────────────────────── + +/// `nbd --version` exits 0 and stdout contains the semver and a git SHA. +#[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("failed to spawn nbd"); + + assert!( + output.status.success(), + "--version should exit 0, stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + 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 between semver and git SHA. + assert!( + stdout.contains('+'), + "--version should contain '+' separator: {stdout}" + ); +}