From 351ea3086f696f4d9ea3e67d27208558d1405614 Mon Sep 17 00:00:00 2001 From: Sheng Date: Wed, 20 May 2026 19:03:14 +0800 Subject: [PATCH 1/4] fix(version): embed VERSION file so source builds show a real version `gog --version` previously returned the bare string "dev" when built via plain `go build ./cmd/gog` from a source checkout. That is the most common install path for contributors, and the existing fallback chain (ldflags -> debug.BuildInfo) does not cover it because debug.BuildInfo.Main.Version is "(devel)" for binaries built directly from a main module. Bake a VERSION file (currently "v0.17.0-dev") into the package via //go:embed and consult it after BuildInfo. Adds a post-release-bump workflow that rewrites internal/cmd/VERSION to "-dev" on every v* tag push, so the fallback stays current with zero maintainer effort. Goreleaser / Makefile / Dockerfile ldflags paths are unchanged and still win when available. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/post-release-bump.yml | 44 +++++++++++++++++++++++++ CHANGELOG.md | 1 + internal/cmd/VERSION | 1 + internal/cmd/version.go | 17 +++++++--- internal/cmd/version_test.go | 32 ++++++++++++++++++ 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/post-release-bump.yml create mode 100644 internal/cmd/VERSION diff --git a/.github/workflows/post-release-bump.yml b/.github/workflows/post-release-bump.yml new file mode 100644 index 000000000..fca78e55e --- /dev/null +++ b/.github/workflows/post-release-bump.yml @@ -0,0 +1,44 @@ +name: post-release-bump + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + bump-version-file: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + ref: main + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute next dev version + id: nextver + run: | + # Policy: VERSION = "-dev". The just-pushed tag is + # the latest released version; -dev marks the period afterwards. + next="${GITHUB_REF_NAME}-dev" + echo "next=$next" >> "$GITHUB_OUTPUT" + + - name: Write VERSION + run: echo "${{ steps.nextver.outputs.next }}" > internal/cmd/VERSION + + - name: Commit & push + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + if git diff --quiet -- internal/cmd/VERSION; then + echo "VERSION unchanged, nothing to commit" + exit 0 + fi + git add internal/cmd/VERSION + git commit -m "chore(release): bump VERSION to ${{ steps.nextver.outputs.next }} + + [skip ci]" + git push origin main diff --git a/CHANGELOG.md b/CHANGELOG.md index 98f240ca7..6a7a5c0a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - CLI: harden backup writes, config/credentials atomic saves, keyring write verification, line input buffering, disabled-API hints, JSON transform number handling, and untrusted-content wrapping after ClawPatch review. - CLI: bound retry request replay buffering, recover failed async backup pushes, ignore global git commit signing in backup snapshots, and protect account manager OAuth redirects with CSRF checks. - Release: update the Homebrew handoff to publish through `openclaw/tap`. +- Version: `gog --version` now reports an informative fallback (for example, `v0.17.0-dev`) when built from source with plain `go build` instead of returning `dev`. ## 0.17.0 - 2026-05-15 diff --git a/internal/cmd/VERSION b/internal/cmd/VERSION new file mode 100644 index 000000000..475513a20 --- /dev/null +++ b/internal/cmd/VERSION @@ -0,0 +1 @@ +v0.17.0-dev diff --git a/internal/cmd/version.go b/internal/cmd/version.go index 584b17602..14824e688 100644 --- a/internal/cmd/version.go +++ b/internal/cmd/version.go @@ -2,6 +2,7 @@ package cmd import ( "context" + _ "embed" "fmt" "os" "runtime/debug" @@ -10,10 +11,13 @@ import ( "github.com/steipete/gogcli/internal/outfmt" ) -const devVersion = "dev" +//go:embed VERSION +var embeddedVersion string + +const sentinelDev = "dev" var ( - version = devVersion + version = sentinelDev commit = "" date = "" readBuildInfo = debug.ReadBuildInfo @@ -21,7 +25,7 @@ var ( func resolvedVersion() string { v := strings.TrimSpace(version) - if v != "" && v != devVersion && !strings.HasSuffix(v, "-dev") { + if v != "" && v != sentinelDev && !strings.HasSuffix(v, "-dev") { return v } info, ok := readBuildInfo() @@ -31,8 +35,11 @@ func resolvedVersion() string { return moduleVersion } } - if v == "" { - return devVersion + if baked := strings.TrimSpace(embeddedVersion); baked != "" { + return baked + } + if v == "" || v == sentinelDev { + return sentinelDev } return v } diff --git a/internal/cmd/version_test.go b/internal/cmd/version_test.go index bef579252..7f4998a43 100644 --- a/internal/cmd/version_test.go +++ b/internal/cmd/version_test.go @@ -62,6 +62,38 @@ func TestVersionStringPrefersInjectedVersion(t *testing.T) { } } +func TestResolvedVersionUsesEmbeddedVersionWhenBuildInfoIsDevel(t *testing.T) { + origVersion, origReadBuildInfo, origEmbedded := version, readBuildInfo, embeddedVersion + t.Cleanup(func() { + version, readBuildInfo, embeddedVersion = origVersion, origReadBuildInfo, origEmbedded + }) + + version = sentinelDev + embeddedVersion = "v0.17.0-dev\n" + readBuildInfo = func() (*debug.BuildInfo, bool) { + return &debug.BuildInfo{Main: debug.Module{Version: "(devel)"}}, true + } + + if got := resolvedVersion(); got != "v0.17.0-dev" { + t.Fatalf("expected v0.17.0-dev, got %q", got) + } +} + +func TestResolvedVersionFallsBackToSentinelWhenEverythingEmpty(t *testing.T) { + origVersion, origReadBuildInfo, origEmbedded := version, readBuildInfo, embeddedVersion + t.Cleanup(func() { + version, readBuildInfo, embeddedVersion = origVersion, origReadBuildInfo, origEmbedded + }) + + version = sentinelDev + embeddedVersion = "" + readBuildInfo = func() (*debug.BuildInfo, bool) { return nil, false } + + if got := resolvedVersion(); got != sentinelDev { + t.Fatalf("expected dev, got %q", got) + } +} + func TestVersionCmd_JSON(t *testing.T) { origVersion, origCommit, origDate, origReadBuildInfo := version, commit, date, readBuildInfo t.Cleanup(func() { version, commit, date, readBuildInfo = origVersion, origCommit, origDate, origReadBuildInfo }) From 22e81c243ad1b94095befb8173c36db8c9c709fc Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 20 May 2026 19:50:18 +0100 Subject: [PATCH 2/4] fix(version): preserve injected dev versions --- internal/cmd/version.go | 7 ++----- internal/cmd/version_test.go | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/internal/cmd/version.go b/internal/cmd/version.go index 14824e688..314e83efc 100644 --- a/internal/cmd/version.go +++ b/internal/cmd/version.go @@ -25,7 +25,7 @@ var ( func resolvedVersion() string { v := strings.TrimSpace(version) - if v != "" && v != sentinelDev && !strings.HasSuffix(v, "-dev") { + if v != "" && v != sentinelDev { return v } info, ok := readBuildInfo() @@ -38,10 +38,7 @@ func resolvedVersion() string { if baked := strings.TrimSpace(embeddedVersion); baked != "" { return baked } - if v == "" || v == sentinelDev { - return sentinelDev - } - return v + return sentinelDev } func VersionString() string { diff --git a/internal/cmd/version_test.go b/internal/cmd/version_test.go index 7f4998a43..ffa8a6a4c 100644 --- a/internal/cmd/version_test.go +++ b/internal/cmd/version_test.go @@ -79,6 +79,23 @@ func TestResolvedVersionUsesEmbeddedVersionWhenBuildInfoIsDevel(t *testing.T) { } } +func TestResolvedVersionPrefersInjectedDevVersionOverEmbedded(t *testing.T) { + origVersion, origReadBuildInfo, origEmbedded := version, readBuildInfo, embeddedVersion + t.Cleanup(func() { + version, readBuildInfo, embeddedVersion = origVersion, origReadBuildInfo, origEmbedded + }) + + version = "v0.18.0-dev" + embeddedVersion = "v0.17.0-dev\n" + readBuildInfo = func() (*debug.BuildInfo, bool) { + return &debug.BuildInfo{Main: debug.Module{Version: "(devel)"}}, true + } + + if got := resolvedVersion(); got != "v0.18.0-dev" { + t.Fatalf("expected injected dev version, got %q", got) + } +} + func TestResolvedVersionFallsBackToSentinelWhenEverythingEmpty(t *testing.T) { origVersion, origReadBuildInfo, origEmbedded := version, readBuildInfo, embeddedVersion t.Cleanup(func() { From 3701e767579106970677f1508d1f63a3cfe23c2f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 20 May 2026 19:53:14 +0100 Subject: [PATCH 3/4] ci: harden post-release version bump --- .github/workflows/post-release-bump.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/post-release-bump.yml b/.github/workflows/post-release-bump.yml index fca78e55e..d7a570f4c 100644 --- a/.github/workflows/post-release-bump.yml +++ b/.github/workflows/post-release-bump.yml @@ -21,15 +21,23 @@ jobs: - name: Compute next dev version id: nextver run: | + if ! [[ "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then + echo "::error::Invalid tag format: $GITHUB_REF_NAME" + exit 1 + fi # Policy: VERSION = "-dev". The just-pushed tag is # the latest released version; -dev marks the period afterwards. next="${GITHUB_REF_NAME}-dev" - echo "next=$next" >> "$GITHUB_OUTPUT" + printf 'next=%s\n' "$next" >> "$GITHUB_OUTPUT" - name: Write VERSION - run: echo "${{ steps.nextver.outputs.next }}" > internal/cmd/VERSION + env: + NEXT_VERSION: ${{ steps.nextver.outputs.next }} + run: printf '%s\n' "$NEXT_VERSION" > internal/cmd/VERSION - name: Commit & push + env: + NEXT_VERSION: ${{ steps.nextver.outputs.next }} run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" @@ -38,7 +46,7 @@ jobs: exit 0 fi git add internal/cmd/VERSION - git commit -m "chore(release): bump VERSION to ${{ steps.nextver.outputs.next }} + git commit -m "chore(release): bump VERSION to $NEXT_VERSION [skip ci]" git push origin main From 000858097607fe0b9ba20c5961be28418c368893 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 20 May 2026 19:58:27 +0100 Subject: [PATCH 4/4] release: require VERSION before tagging --- docs/RELEASING.md | 4 +++- scripts/release.sh | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 3a3805d38..0b6ee5f66 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -43,6 +43,8 @@ gh run list -L 5 --branch main ## 2) Update changelog - Update `CHANGELOG.md` for the version you’re releasing. +- Update `internal/cmd/VERSION` to `vX.Y.Z` before tagging. The post-release + bump workflow rewrites it to `vX.Y.Z-dev` on `main` after the tag is pushed. Example heading: - `## 0.1.0 - 2025-12-12` @@ -52,7 +54,7 @@ Example heading: git checkout main git pull -# commit changelog + any release tweaks +# commit changelog, internal/cmd/VERSION, and any release tweaks git commit -am "release: vX.Y.Z" git tag -a vX.Y.Z -m "Release X.Y.Z" diff --git a/scripts/release.sh b/scripts/release.sh index 574fbf76f..96f73e2f2 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -30,6 +30,14 @@ if rg -q "^## ${version} - Unreleased" "$changelog"; then exit 2 fi +version_file="internal/cmd/VERSION" +expected_version="v$version" +if [[ "$(tr -d '[:space:]' < "$version_file")" != "$expected_version" ]]; then + echo "$version_file must contain $expected_version before tagging" >&2 + echo "This keeps source archives built from the release tag from embedding a stale dev fallback." >&2 + exit 2 +fi + notes_file="$(mktemp -t gogcli-release-notes)" awk -v ver="$version" ' $0 ~ "^## "ver" " {print "## "ver; in_section=1; next}