diff --git a/.github/workflows/post-release-bump.yml b/.github/workflows/post-release-bump.yml new file mode 100644 index 00000000..d7a570f4 --- /dev/null +++ b/.github/workflows/post-release-bump.yml @@ -0,0 +1,52 @@ +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: | + 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" + printf 'next=%s\n' "$next" >> "$GITHUB_OUTPUT" + + - name: Write 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" + 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 $NEXT_VERSION + + [skip ci]" + git push origin main diff --git a/CHANGELOG.md b/CHANGELOG.md index 98f240ca..6a7a5c0a 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/docs/RELEASING.md b/docs/RELEASING.md index 3a3805d3..0b6ee5f6 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/internal/cmd/VERSION b/internal/cmd/VERSION new file mode 100644 index 00000000..475513a2 --- /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 584b1760..314e83ef 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 { return v } info, ok := readBuildInfo() @@ -31,10 +35,10 @@ func resolvedVersion() string { return moduleVersion } } - if v == "" { - return devVersion + if baked := strings.TrimSpace(embeddedVersion); baked != "" { + return baked } - return v + return sentinelDev } func VersionString() string { diff --git a/internal/cmd/version_test.go b/internal/cmd/version_test.go index bef57925..ffa8a6a4 100644 --- a/internal/cmd/version_test.go +++ b/internal/cmd/version_test.go @@ -62,6 +62,55 @@ 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 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() { + 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 }) diff --git a/scripts/release.sh b/scripts/release.sh index 574fbf76..96f73e2f 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}