From 38c639e7356cbd3b809dd961e7ccd4972e43f9ed Mon Sep 17 00:00:00 2001 From: Jack Sullivan Date: Thu, 30 Jul 2026 12:14:16 -0700 Subject: [PATCH] fix: report the installed version for go install builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `commitlint version` reported 0.0.0-dev for every binary the release workflow did not build — including `go install ...@vX.Y.Z`, which is the install path the README documents and the one the GitHub Action uses. Only release archives carried a real version, so the common case was the broken one. The Go toolchain already records the module version in the binary: $ go version -m commitlint mod github.com/DivergentCodes/commitlint v1.1.1 h1:uqR3... Read it back with debug.ReadBuildInfo when the ldflags stamp is absent. Precedence is stamp, then build info, then the default, so release archives are unaffected. "(devel)", which a local build records, is suppressed in favor of the more informative default. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VSrrciEDBTuFNMtKocScML --- main.go | 25 ++++++++++++++++++++++++- main_test.go | 21 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index f8b599e..6fc9272 100644 --- a/main.go +++ b/main.go @@ -29,14 +29,37 @@ import ( "io" "os" "os/exec" + "runtime/debug" "strconv" "strings" "github.com/DivergentCodes/commitlint/lint" ) +// version is stamped by the release workflow via -ldflags. Builds that are not +// produced by that workflow — notably `go install ...@v1.2.3`, the documented +// install path — leave it at the default, so fall back to the version the Go +// toolchain recorded in the binary. var version = "0.0.0-dev" +// resolveVersion prefers an ldflags-stamped version, then the module version +// embedded by `go install`, and otherwise reports the default. +func resolveVersion() string { + if version != "0.0.0-dev" { + return version + } + info, ok := debug.ReadBuildInfo() + if !ok || info.Main.Version == "" { + return version + } + // A build from a local directory records "(devel)", which is less + // informative than the default. + if info.Main.Version == "(devel)" { + return version + } + return info.Main.Version +} + func main() { if len(os.Args) < 2 { usage() @@ -46,7 +69,7 @@ func main() { case "lint": os.Exit(runLint(os.Args[2:])) case "version", "--version", "-v": - fmt.Println(version) + fmt.Println(resolveVersion()) case "help", "--help", "-h": usage() default: diff --git a/main_test.go b/main_test.go index f89a691..4314d23 100644 --- a/main_test.go +++ b/main_test.go @@ -144,3 +144,24 @@ func TestWriteTextSkipsPassingMessages(t *testing.T) { t.Errorf("failing message should be printed:\n%s", buf.String()) } } + +// `go install ...@vX.Y.Z` does not run the release workflow's -ldflags, so +// without a build-info fallback every installed binary reported 0.0.0-dev. +// Under `go test` the module version is "(devel)", so this cannot assert the +// installed-version path directly; it pins the precedence and the guarantee +// that the fallback never surfaces "(devel)" or an empty string. +func TestResolveVersion(t *testing.T) { + orig := version + defer func() { version = orig }() + + version = "v2.3.4" + if got := resolveVersion(); got != "v2.3.4" { + t.Errorf("stamped version should win: got %q, want v2.3.4", got) + } + + version = "0.0.0-dev" + got := resolveVersion() + if got == "" || got == "(devel)" { + t.Errorf("fallback must be informative, got %q", got) + } +}