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) + } +}