Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <!-- markfluence-version -->
// token.
Version: buildinfo.Stamp(),
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
if noColorFlag {
if err := os.Setenv("NO_COLOR", "1"); err != nil {
Expand Down Expand Up @@ -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() +
Expand Down
52 changes: 43 additions & 9 deletions internal/buildinfo/buildinfo.go
Original file line number Diff line number Diff line change
@@ -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
// <!-- markfluence-version --> 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
}
28 changes: 28 additions & 0 deletions internal/buildinfo/buildinfo_test.go
Original file line number Diff line number Diff line change
@@ -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 <version>".
if got := Stamp(); !strings.HasPrefix(got, "markfluence "+Version) {
t.Errorf("Stamp() = %q, want prefix %q", got, "markfluence "+Version)
}
}