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
77 changes: 77 additions & 0 deletions cmd/azform/buildinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"regexp"
"runtime/debug"
"strings"
)

// pseudoVersionRe matches the timestamp-hash tail of a Go pseudo-version
// ("v0.0.0-20260926165719-25ff1eef784e"), optionally followed by build
// metadata such as "+dirty".
var pseudoVersionRe = regexp.MustCompile(`\d{14}-[0-9a-f]{12}(\+.*)?$`)

// fillBuildInfo backfills version, commit and date from the Go build info
// when the release ldflags did not set them. Goreleaser sets all three;
// `go install ...@vX.Y.Z` records only the module version, and `go build`
// in a git checkout (make build) records only the VCS stamp. Without this
// both of those report a bare "dev" with no way to tell which code it is.
func fillBuildInfo() {
info, ok := debug.ReadBuildInfo()
if !ok {
return
}
version, commit, date = resolveBuildInfo(version, commit, date, info)
}

func resolveBuildInfo(v, c, d string, info *debug.BuildInfo) (string, string, string) {
// Only a tagged module version replaces "dev". A local `go build`
// reports "(devel)" or, since Go 1.24, a pseudo-version like
// "v0.0.0-20260926165719-25ff1eef784e+dirty"; both keep "dev", which
// the update check never nags (see update.compareSemver) and which
// does not invalidate the metadata cache on every commit. The commit
// below still says which code it is.
if v == "dev" {
mv := info.Main.Version
if mv != "" && mv != "(devel)" && !strings.Contains(mv, "+") && !pseudoVersionRe.MatchString(mv) {
v = strings.TrimPrefix(mv, "v")
}
}
if c != "" {
return v, c, d
}
var modified bool
for _, s := range info.Settings {
switch s.Key {
case "vcs.revision":
c = s.Value
if len(c) > 7 {
c = c[:7]
}
case "vcs.time":
if d == "" {
d = s.Value
}
case "vcs.modified":
modified = s.Value == "true"
}
}
if c != "" && modified {
c += "-dirty"
}
return v, c, d
}

// versionString is the one-line version shown by --version, -h and the
// help overlay: "azform 0.4.1 (abc1234, 2026-09-01T10:00:00Z)".
func versionString() string {
s := "azform " + version
if commit != "" {
s += " (" + commit
if date != "" {
s += ", " + date
}
s += ")"
}
return s
}
67 changes: 67 additions & 0 deletions cmd/azform/buildinfo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package main

import (
"runtime/debug"
"testing"
)

func TestResolveBuildInfo(t *testing.T) {
vcs := []debug.BuildSetting{
{Key: "vcs.revision", Value: "0123456789abcdef"},
{Key: "vcs.time", Value: "2026-09-01T10:00:00Z"},
{Key: "vcs.modified", Value: "false"},
}
cases := []struct {
name string
v, c, d string
mainVersion string
settings []debug.BuildSetting
wantV, wantC string
wantD string
}{
{
name: "ldflags win",
v: "0.4.1", c: "abc1234", d: "2026-08-01T00:00:00Z",
mainVersion: "v9.9.9", settings: vcs,
wantV: "0.4.1", wantC: "abc1234", wantD: "2026-08-01T00:00:00Z",
},
{
name: "go install at a tag",
v: "dev", mainVersion: "v0.4.1",
wantV: "0.4.1",
},
{
name: "local go build keeps dev, gains commit",
v: "dev", mainVersion: "(devel)", settings: vcs,
wantV: "dev", wantC: "0123456", wantD: "2026-09-01T10:00:00Z",
},
{
name: "local go build pseudo-version keeps dev",
v: "dev", mainVersion: "v0.0.0-20260926165719-25ff1eef784e+dirty",
wantV: "dev",
},
{
name: "go install at a commit keeps dev",
v: "dev", mainVersion: "v0.4.2-0.20260926165719-25ff1eef784e",
wantV: "dev",
},
{
name: "dirty tree",
v: "dev", mainVersion: "(devel)",
settings: []debug.BuildSetting{
{Key: "vcs.revision", Value: "0123456789abcdef"},
{Key: "vcs.modified", Value: "true"},
},
wantV: "dev", wantC: "0123456-dirty",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
info := &debug.BuildInfo{Main: debug.Module{Version: tc.mainVersion}, Settings: tc.settings}
v, c, d := resolveBuildInfo(tc.v, tc.c, tc.d, info)
if v != tc.wantV || c != tc.wantC || d != tc.wantD {
t.Errorf("got (%q, %q, %q), want (%q, %q, %q)", v, c, d, tc.wantV, tc.wantC, tc.wantD)
}
})
}
}
13 changes: 4 additions & 9 deletions cmd/azform/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ func main() {
}

func run(args []string) int {
fillBuildInfo()

// Subcommands are dispatched before flag parsing: `shell-init` is a
// bare positional, which the flag path would otherwise mistake for
// an az command path.
Expand Down Expand Up @@ -80,6 +82,7 @@ func run(args []string) int {
fs.BoolVar(&doctorFlag, "doctor", false, "print environment summary and exit (spec §15.3)")

fs.Usage = func() {
fmt.Fprintf(fs.Output(), "%s\n\n", versionString())
fmt.Fprintf(fs.Output(), "Usage: azform --line <buffer> --out <path> [--vars <path>] [--cwd <path>]\n")
fmt.Fprintf(fs.Output(), " azform shell-init <bash|zsh> print the shell widget to stdout\n\n")
fmt.Fprintf(fs.Output(), "Flags:\n")
Expand Down Expand Up @@ -272,15 +275,7 @@ func cursorByte(line string, cursor int, prefix string) int {
}

func printVersion() {
fmt.Printf("azform %s", version)
if commit != "" {
fmt.Printf(" (%s", commit)
if date != "" {
fmt.Printf(", %s", date)
}
fmt.Printf(")")
}
fmt.Println()
fmt.Println(versionString())
}

func effectiveCacheDir(override string) string {
Expand Down
3 changes: 3 additions & 0 deletions internal/ui/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1806,6 +1806,9 @@ func TestHelpOverlayToggles(t *testing.T) {
if !strings.Contains(view, "keyboard shortcuts") {
t.Errorf("help view should announce itself; got:\n%s", view)
}
if !strings.Contains(view, "azform test") {
t.Errorf("help view should show the azform version; got:\n%s", view)
}

m, _ = form.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("x")})
form = m.(ui.Form)
Expand Down
14 changes: 13 additions & 1 deletion internal/ui/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,19 @@ func (m Form) renderHelp() string {
}

var sb strings.Builder
writeLine(&sb, headerStyle.Render("azform — keyboard shortcuts"))
// The version sits right-aligned on the title line: the help overlay
// is the one place inside the form a user looks for "what is this",
// and a bug report needs the version without leaving the form.
title := headerStyle.Render("azform — keyboard shortcuts")
if m.version != "" {
ver := hintStyle.Render("azform " + m.version)
if pad := w - ansi.StringWidth(title) - ansi.StringWidth(ver); pad >= 2 {
title += strings.Repeat(" ", pad) + ver
} else {
title += " " + ver
}
}
writeLine(&sb, title)
writeLine(&sb, sep)

sections := []struct {
Expand Down
Loading