From dfc9137a1267f4f2a900774355e2e20e788c60a9 Mon Sep 17 00:00:00 2001 From: wuyangfan <1102042793@qq.com> Date: Sat, 30 May 2026 12:48:51 +0800 Subject: [PATCH] feat: add version metadata and CLI -h/-v flags Declare ldflags-injected build variables, print them for -v/--version, and add -h/--help usage. Stamp release binaries with the same metadata as build.sh. Fixes #26 --- .github/workflows/release.yml | 17 ++++++++++--- find_replace.go | 12 ++++++++- version.go | 39 +++++++++++++++++++++++++++++ version_test.go | 46 +++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 version.go create mode 100644 version_test.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9848e10..b977cb3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -120,12 +120,23 @@ jobs: - name: Build release binary if: steps.bump.outputs.bump != 'skip' - # Static linux/amd64 binary. CGO off so the binary has no glibc - # dependency; -trimpath + -s -w shrinks size and strips local paths. + env: + TAG: ${{ steps.tag.outputs.next }} run: | set -euo pipefail + COMMIT="${HEAD_SHA:0:7}" + GO_VERSION="$(go version | cut -d' ' -f3)" + BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + LD="-s -w \ + -X 'main.GitTag=${TAG}' \ + -X 'main.GitCommit=${COMMIT}' \ + -X 'main.GoVersion=${GO_VERSION}' \ + -X 'main.BuildTimestamp=${BUILD_DATE}' \ + -X 'main.BuildOS=linux' \ + -X 'main.BuildArch=amd64' \ + -X 'main.BuildTainted=false'" CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ - go build -trimpath -ldflags="-s -w" \ + go build -trimpath -ldflags="${LD}" \ -o find-replace-linux-amd64 . ls -l find-replace-linux-amd64 diff --git a/find_replace.go b/find_replace.go index 9bdbe79..a7f7007 100644 --- a/find_replace.go +++ b/find_replace.go @@ -77,8 +77,18 @@ func run(args []string, stderr io.Writer) int { // Remove date/time from logging output. log.SetFlags(0) + if len(args) == 2 && (args[1] == "-v" || args[1] == "--version") { + fmt.Fprintln(stderr, versionString()) + return 0 + } + + if len(args) == 2 && (args[1] == "-h" || args[1] == "--help") { + printUsage(stderr) + return 0 + } + if len(args) != 3 { - fmt.Fprintln(stderr, "Usage: find-replace FIND REPLACE") + printUsage(stderr) return 1 } diff --git a/version.go b/version.go new file mode 100644 index 0000000..53809b1 --- /dev/null +++ b/version.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "io" +) + +// Build metadata injected by build.sh via -ldflags -X. +var ( + GitTag string + GitCommit string + GoVersion string + BuildTimestamp string + BuildOS string + BuildArch string + BuildTainted string +) + +func versionString() string { + tag := GitTag + if tag == "" { + tag = "dev" + } + commit := GitCommit + if commit == "" { + commit = "unknown" + } + return fmt.Sprintf( + "find-replace %s (%s) go=%s built=%s os=%s arch=%s tainted=%s", + tag, commit, GoVersion, BuildTimestamp, BuildOS, BuildArch, BuildTainted, + ) +} + +func printUsage(w io.Writer) { + fmt.Fprintln(w, "Usage: find-replace FIND REPLACE") + fmt.Fprintln(w, "") + fmt.Fprintln(w, "Recursively find and replace strings in file names and contents.") + fmt.Fprintln(w, "Skips .git/ directories and binary-looking files.") +} diff --git a/version_test.go b/version_test.go new file mode 100644 index 0000000..2faf28b --- /dev/null +++ b/version_test.go @@ -0,0 +1,46 @@ +package main + +import ( + "bytes" + "strings" + "testing" +) + +func TestVersionString(t *testing.T) { + GitTag = "v1.2.3" + GitCommit = "abc1234" + GoVersion = "go1.22.0" + BuildTimestamp = "2026-01-01T00:00:00Z" + BuildOS = "linux" + BuildArch = "amd64" + BuildTainted = "false" + + got := versionString() + for _, want := range []string{"v1.2.3", "abc1234", "go1.22.0", "linux", "amd64"} { + if !strings.Contains(got, want) { + t.Fatalf("versionString() = %q; want substring %q", got, want) + } + } +} + +func TestRunVersionFlag(t *testing.T) { + var stderr bytes.Buffer + code := run([]string{"find-replace", "--version"}, &stderr) + if code != 0 { + t.Fatalf("run --version exit = %d; want 0", code) + } + if !strings.Contains(stderr.String(), "find-replace") { + t.Fatalf("version output = %q; want find-replace", stderr.String()) + } +} + +func TestRunHelpFlag(t *testing.T) { + var stderr bytes.Buffer + code := run([]string{"find-replace", "--help"}, &stderr) + if code != 0 { + t.Fatalf("run --help exit = %d; want 0", code) + } + if !strings.Contains(stderr.String(), "Usage: find-replace") { + t.Fatalf("help output = %q; want usage line", stderr.String()) + } +}