From 91b7389feb127b3a53bd332e25c36918491098a5 Mon Sep 17 00:00:00 2001 From: Will Kahn-Greene Date: Thu, 23 Jul 2026 13:08:31 -0700 Subject: [PATCH] feat(cmd): print name, version, commit, and date from --version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make `markfluence --version` emit a build stamp instead of cobra's default "markfluence version <...>" wrapper, and unify it with the token the converter embeds in published pages so both print the same string. The stamp is now "markfluence VERSION (SHA, DATE)" — a compact, parenthesized form that adds the short vcs.revision commit hash (the standard build identifier) alongside the commit date. The hash and date are each omitted when the toolchain hasn't embedded VCS info, and the parenthetical drops entirely if both are missing. buildinfo gains Revision() and a setting() helper; the now-unused CommitDate() is removed. root.go sets a {{.Version}} version template so the stamp (which already carries the "markfluence" prefix) prints verbatim. --- cmd/root.go | 9 ++++- internal/buildinfo/buildinfo.go | 52 +++++++++++++++++++++++----- internal/buildinfo/buildinfo_test.go | 28 +++++++++++++++ 3 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 internal/buildinfo/buildinfo_test.go diff --git a/cmd/root.go b/cmd/root.go index a3fc52c..9ff2422 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -34,7 +34,10 @@ var rootCmd = &cobra.Command{ ".env file. The base URL (--url / CONFLUENCE_URL) and username (--username /\n" + "CONFLUENCE_USERNAME) may be set any of those ways; the API token\n" + "(CONFLUENCE_TOKEN) comes only from the environment or .env, never a flag.", - Version: buildinfo.Version, + // --version prints the build stamp ("markfluence VERSION (SHA, DATE)"), the + // same string the converter substitutes for the + // token. + Version: buildinfo.Stamp(), PersistentPreRunE: func(_ *cobra.Command, _ []string) error { if noColorFlag { if err := os.Setenv("NO_COLOR", "1"); err != nil { @@ -109,6 +112,10 @@ func init() { "Emit machine-readable JSON to stdout instead of human output") rootCmd.PersistentFlags().SortFlags = false + // The stamp already carries its own "markfluence v" prefix; print it verbatim + // rather than cobra's default "markfluence version <...>" wrapper. + rootCmd.SetVersionTemplate("{{.Version}}\n") + // Append a docs footer to every command's --help output. Subcommands inherit // the root's help template, so setting it once covers them all. rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + diff --git a/internal/buildinfo/buildinfo.go b/internal/buildinfo/buildinfo.go index d679f41..64d3a22 100644 --- a/internal/buildinfo/buildinfo.go +++ b/internal/buildinfo/buildinfo.go @@ -1,25 +1,59 @@ -// Package buildinfo exposes the binary's version and commit date. +// Package buildinfo exposes the binary's version, commit, and commit date. package buildinfo -import "runtime/debug" +import ( + "runtime/debug" + "strings" +) // Version is set at build time via -ldflags; it's "dev" for un-stamped builds. var Version = "dev" -// CommitDate returns the VCS commit time the Go toolchain embeds at build time -// (the vcs.time build setting), or "unknown" when it isn't available. -func CommitDate() string { +// setting returns a debug build setting (e.g. vcs.time, vcs.revision), or "". +func setting(key string) string { if info, ok := debug.ReadBuildInfo(); ok { for _, s := range info.Settings { - if s.Key == "vcs.time" { + if s.Key == key { return s.Value } } } - return "unknown" + return "" } -// Stamp is the human-readable build stamp: "markfluence vVERSION COMMITDATE". +// Revision returns the short (7-char) VCS commit hash (the vcs.revision build +// setting), or "" when it isn't available. +func Revision() string { + rev := setting("vcs.revision") + if len(rev) > 7 { + return rev[:7] + } + return rev +} + +// Stamp is the build stamp used both for --version and for the +// token embedded in published pages: +// "markfluence VERSION (SHA, DATE)". The commit hash and date are each omitted +// when unavailable (and the parenthetical drops entirely if both are). func Stamp() string { - return "markfluence v" + Version + " " + CommitDate() + s := "markfluence " + Version + var meta []string + if rev := Revision(); rev != "" { + meta = append(meta, rev) + } + if ts := setting("vcs.time"); ts != "" { + meta = append(meta, dateOnly(ts)) + } + if len(meta) > 0 { + s += " (" + strings.Join(meta, ", ") + ")" + } + return s +} + +// dateOnly trims an RFC3339 timestamp to its YYYY-MM-DD date. +func dateOnly(ts string) string { + if len(ts) >= 10 { + return ts[:10] + } + return ts } diff --git a/internal/buildinfo/buildinfo_test.go b/internal/buildinfo/buildinfo_test.go new file mode 100644 index 0000000..b624cb3 --- /dev/null +++ b/internal/buildinfo/buildinfo_test.go @@ -0,0 +1,28 @@ +package buildinfo + +import ( + "strings" + "testing" +) + +func TestDateOnly(t *testing.T) { + tests := []struct{ in, want string }{ + {"2026-07-23T19:27:49Z", "2026-07-23"}, + {"2026-07-23", "2026-07-23"}, + {"short", "short"}, + {"", ""}, + } + for _, tt := range tests { + if got := dateOnly(tt.in); got != tt.want { + t.Errorf("dateOnly(%q) = %q, want %q", tt.in, got, tt.want) + } + } +} + +func TestStampPrefix(t *testing.T) { + // Under `go test` the toolchain embeds vcs.* settings, so the parenthetical is + // present; regardless, the stamp always leads with "markfluence ". + if got := Stamp(); !strings.HasPrefix(got, "markfluence "+Version) { + t.Errorf("Stamp() = %q, want prefix %q", got, "markfluence "+Version) + } +}