From 61bfc157f2b4b2f105e8b00d703fab876156894a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 16 Jul 2026 13:21:34 -0700 Subject: [PATCH] fix(cli): report source install version --- CHANGELOG.md | 1 + internal/cli/cli.go | 4 ++-- internal/cli/cli_test.go | 10 ++++++---- internal/cli/cloud_commands.go | 2 +- internal/cli/releasecheck.go | 2 +- internal/cli/releasecheck_test.go | 3 +++ internal/cli/remote_commands.go | 4 ++-- internal/cli/version.go | 29 +++++++++++++++++++++++++++- internal/cli/version_test.go | 32 +++++++++++++++++++++++++++++++ 9 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 internal/cli/version_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 49ef4cce..2bff8e0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixes - Show setup guidance instead of a raw missing-file error when configuration-dependent commands cannot find `config.toml`. Thanks @0xdevalias. +- Report module build metadata for source-installed binaries instead of a stale hard-coded release version. ## 0.11.5 - 2026-07-09 diff --git a/internal/cli/cli.go b/internal/cli/cli.go index f7f9e3f4..7c2f6b9a 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -56,7 +56,7 @@ func Run(ctx context.Context, args []string, stdout, stderr io.Writer) error { return usageErr(err) } if global.Version { - _, _ = io.WriteString(stdout, version+"\n") + _, _ = io.WriteString(stdout, currentVersion()+"\n") return nil } rest := global.Args @@ -70,7 +70,7 @@ func Run(ctx context.Context, args []string, stdout, stderr io.Writer) error { return printCommandUsage(stdout, topic) } if rest[0] == "version" { - _, _ = io.WriteString(stdout, version+"\n") + _, _ = io.WriteString(stdout, currentVersion()+"\n") return nil } level := slog.LevelInfo diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index da9eb414..dcf6faa1 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -51,9 +51,11 @@ func TestHelpAndVersion(t *testing.T) { require.NoError(t, Run(context.Background(), []string{"help"}, &out, &bytes.Buffer{})) require.Contains(t, out.String(), "discrawl") - out.Reset() - require.NoError(t, Run(context.Background(), []string{"--version"}, &out, &bytes.Buffer{})) - require.Contains(t, out.String(), "0.11.2") + for _, args := range [][]string{{"--version"}, {"version"}} { + out.Reset() + require.NoError(t, Run(context.Background(), args, &out, &bytes.Buffer{})) + require.Equal(t, currentVersion()+"\n", out.String()) + } err := Run(context.Background(), []string{"bogus"}, &out, &bytes.Buffer{}) require.Equal(t, 2, ExitCode(err)) @@ -4136,7 +4138,7 @@ func TestHelpFlagAfterDelimiterReachesCommand(t *testing.T) { for _, helpFlag := range []string{"-h", "--help"} { var stdout, stderr bytes.Buffer require.NoError(t, Run(context.Background(), []string{"version", "--", helpFlag}, &stdout, &stderr)) - require.Equal(t, version+"\n", stdout.String()) + require.Equal(t, currentVersion()+"\n", stdout.String()) require.Empty(t, stderr.String()) } } diff --git a/internal/cli/cloud_commands.go b/internal/cli/cloud_commands.go index f50d4182..93c580df 100644 --- a/internal/cli/cloud_commands.go +++ b/internal/cli/cloud_commands.go @@ -73,7 +73,7 @@ func (r *runtime) runCloudPublish(args []string) error { TokenEnv: firstNonEmpty(*tokenEnv, r.cfg.Remote.TokenEnv, config.DefaultRemoteTokenEnv), } client, err := crawlremote.NewClientFromConfig(remoteCfg, crawlremote.Options{ - UserAgent: "discrawl/" + version, + UserAgent: discrawlUserAgent(), HTTPClient: &http.Client{Timeout: 10 * time.Minute}, }) if err != nil { diff --git a/internal/cli/releasecheck.go b/internal/cli/releasecheck.go index 624af8dc..03b26f08 100644 --- a/internal/cli/releasecheck.go +++ b/internal/cli/releasecheck.go @@ -27,7 +27,7 @@ func discrawlReleaseCheckOptions(force bool) releasecheck.Options { AppName: "discrawl", Owner: owner, Repo: repo, - CurrentVersion: version, + CurrentVersion: currentVersion(), CacheDir: cfg.CacheDir, Force: force, } diff --git a/internal/cli/releasecheck_test.go b/internal/cli/releasecheck_test.go index 3e32a1b3..18277852 100644 --- a/internal/cli/releasecheck_test.go +++ b/internal/cli/releasecheck_test.go @@ -65,6 +65,9 @@ func TestDiscrawlReleaseCheckOptionsUsesModulePath(t *testing.T) { if opts.AppName != "discrawl" || opts.CurrentVersion == "" || opts.CacheDir == "" { t.Fatalf("incomplete options = %#v", opts) } + if opts.CurrentVersion != currentVersion() { + t.Fatalf("CurrentVersion = %q, want %q", opts.CurrentVersion, currentVersion()) + } } func TestRunCheckUpdateRejectsArgsBeforeNetwork(t *testing.T) { diff --git a/internal/cli/remote_commands.go b/internal/cli/remote_commands.go index 51057737..f6bba935 100644 --- a/internal/cli/remote_commands.go +++ b/internal/cli/remote_commands.go @@ -149,7 +149,7 @@ func (r *runtime) runRemoteLogin(args []string) error { if strings.TrimSpace(cfg.Remote.Endpoint) == "" { return usageErr(errors.New("remote login requires --endpoint or remote.endpoint")) } - client, err := crawlremote.NewClientFromConfig(cfg.Remote, crawlremote.Options{UserAgent: "discrawl/" + version}) + client, err := crawlremote.NewClientFromConfig(cfg.Remote, crawlremote.Options{UserAgent: discrawlUserAgent()}) if err != nil { return configErr(err) } @@ -325,7 +325,7 @@ func (r *runtime) remoteClient(requireArchive bool) (remoteArchiveClient, error) } client, err := crawlremote.NewClientFromConfig(r.cfg.Remote, crawlremote.Options{ TokenProvider: tokenProvider, - UserAgent: "discrawl/" + version, + UserAgent: discrawlUserAgent(), }) if err != nil { return nil, configErr(err) diff --git a/internal/cli/version.go b/internal/cli/version.go index 0e90a2bc..a964b17e 100644 --- a/internal/cli/version.go +++ b/internal/cli/version.go @@ -1,3 +1,30 @@ package cli -var version = "0.11.2" +import ( + "runtime/debug" + "strings" +) + +var version string + +func currentVersion() string { + moduleVersion := "" + if info, ok := debug.ReadBuildInfo(); ok { + moduleVersion = info.Main.Version + } + return resolveVersion(version, moduleVersion) +} + +func resolveVersion(linkerVersion, moduleVersion string) string { + if linkerVersion = strings.TrimSpace(linkerVersion); linkerVersion != "" { + return strings.TrimPrefix(linkerVersion, "v") + } + if moduleVersion = strings.TrimSpace(moduleVersion); moduleVersion != "" && moduleVersion != "(devel)" { + return strings.TrimPrefix(moduleVersion, "v") + } + return "devel" +} + +func discrawlUserAgent() string { + return "discrawl/" + currentVersion() +} diff --git a/internal/cli/version_test.go b/internal/cli/version_test.go new file mode 100644 index 00000000..04ce384c --- /dev/null +++ b/internal/cli/version_test.go @@ -0,0 +1,32 @@ +package cli + +import "testing" + +func TestResolveVersion(t *testing.T) { + for _, tc := range []struct { + name string + linkerVersion string + moduleVersion string + want string + }{ + {name: "release linker override", linkerVersion: "0.12.0", moduleVersion: "v0.11.5", want: "0.12.0"}, + {name: "release linker override with prefix", linkerVersion: " v0.12.0 ", moduleVersion: "v0.11.5", want: "0.12.0"}, + {name: "installed tagged module", moduleVersion: "v0.11.5", want: "0.11.5"}, + {name: "installed pseudo-version", moduleVersion: "v0.11.6-0.20260716120000-deadbeefcafe", want: "0.11.6-0.20260716120000-deadbeefcafe"}, + {name: "local build", moduleVersion: "(devel)", want: "devel"}, + {name: "missing build info", want: "devel"}, + } { + t.Run(tc.name, func(t *testing.T) { + if got := resolveVersion(tc.linkerVersion, tc.moduleVersion); got != tc.want { + t.Fatalf("resolveVersion(%q, %q) = %q, want %q", tc.linkerVersion, tc.moduleVersion, got, tc.want) + } + }) + } +} + +func TestDiscrawlUserAgentUsesCurrentVersion(t *testing.T) { + want := "discrawl/" + currentVersion() + if got := discrawlUserAgent(); got != want { + t.Fatalf("discrawlUserAgent() = %q, want %q", got, want) + } +}