diff --git a/internal/cli/version.go b/internal/cli/version.go index f5763a4..9d7d9a8 100644 --- a/internal/cli/version.go +++ b/internal/cli/version.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "runtime/debug" "github.com/devrimcavusoglu/skern/internal/output" "github.com/spf13/cobra" @@ -14,6 +15,38 @@ var ( Date = "unknown" ) +func init() { + initVersionFromBuildInfo(debug.ReadBuildInfo) +} + +// initVersionFromBuildInfo populates Version, Commit, and Date from Go build +// info when ldflags have not been set. The reader parameter allows tests to +// inject a fake ReadBuildInfo. +func initVersionFromBuildInfo(reader func() (*debug.BuildInfo, bool)) { + if Version != "dev" { + return // ldflags already set + } + info, ok := reader() + if !ok { + return + } + if info.Main.Version != "" && info.Main.Version != "(devel)" { + Version = info.Main.Version + } + for _, s := range info.Settings { + switch s.Key { + case "vcs.revision": + if s.Value != "" { + Commit = s.Value + } + case "vcs.time": + if s.Value != "" { + Date = s.Value + } + } + } +} + func newVersionCmd() *cobra.Command { return &cobra.Command{ Use: "version", diff --git a/internal/cli/version_test.go b/internal/cli/version_test.go index 391bb17..bfd3d09 100644 --- a/internal/cli/version_test.go +++ b/internal/cli/version_test.go @@ -2,6 +2,7 @@ package cli import ( "bytes" + "runtime/debug" "testing" "github.com/stretchr/testify/assert" @@ -36,6 +37,115 @@ func TestVersionCommand_JSON(t *testing.T) { require.NoError(t, err) } +func TestInitVersionFromBuildInfo(t *testing.T) { + tests := []struct { + name string + initialVersion string + buildInfo *debug.BuildInfo + buildInfoOk bool + expectedVersion string + expectedCommit string + expectedDate string + }{ + { + name: "ldflags already set", + initialVersion: "v1.0.0", + buildInfo: &debug.BuildInfo{Main: debug.Module{Version: "v2.0.0"}}, + buildInfoOk: true, + expectedVersion: "v1.0.0", + expectedCommit: "none", + expectedDate: "unknown", + }, + { + name: "build info unavailable", + initialVersion: "dev", + buildInfo: nil, + buildInfoOk: false, + expectedVersion: "dev", + expectedCommit: "none", + expectedDate: "unknown", + }, + { + name: "go install with version and vcs info", + initialVersion: "dev", + buildInfo: &debug.BuildInfo{ + Main: debug.Module{Version: "v0.1.1"}, + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: "abc123def456"}, + {Key: "vcs.time", Value: "2026-01-15T10:30:00Z"}, + }, + }, + buildInfoOk: true, + expectedVersion: "v0.1.1", + expectedCommit: "abc123def456", + expectedDate: "2026-01-15T10:30:00Z", + }, + { + name: "devel version is ignored", + initialVersion: "dev", + buildInfo: &debug.BuildInfo{ + Main: debug.Module{Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: "abc123"}, + }, + }, + buildInfoOk: true, + expectedVersion: "dev", + expectedCommit: "abc123", + expectedDate: "unknown", + }, + { + name: "version set but no vcs info", + initialVersion: "dev", + buildInfo: &debug.BuildInfo{ + Main: debug.Module{Version: "v0.2.0"}, + }, + buildInfoOk: true, + expectedVersion: "v0.2.0", + expectedCommit: "none", + expectedDate: "unknown", + }, + { + name: "empty vcs values are ignored", + initialVersion: "dev", + buildInfo: &debug.BuildInfo{ + Main: debug.Module{Version: "v0.3.0"}, + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: ""}, + {Key: "vcs.time", Value: ""}, + }, + }, + buildInfoOk: true, + expectedVersion: "v0.3.0", + expectedCommit: "none", + expectedDate: "unknown", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Save and restore globals + origVersion, origCommit, origDate := Version, Commit, Date + t.Cleanup(func() { + Version, Commit, Date = origVersion, origCommit, origDate + }) + + Version = tt.initialVersion + Commit = "none" + Date = "unknown" + + reader := func() (*debug.BuildInfo, bool) { + return tt.buildInfo, tt.buildInfoOk + } + initVersionFromBuildInfo(reader) + + assert.Equal(t, tt.expectedVersion, Version) + assert.Equal(t, tt.expectedCommit, Commit) + assert.Equal(t, tt.expectedDate, Date) + }) + } +} + func TestRootCommand_Help(t *testing.T) { cmd := NewRootCmd() buf := new(bytes.Buffer)