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
52 changes: 52 additions & 0 deletions .github/workflows/post-release-bump.yml
Original file line number Diff line number Diff line change
@@ -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 = "<just-pushed-tag>-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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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"
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v0.17.0-dev
16 changes: 10 additions & 6 deletions internal/cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"context"
_ "embed"
"fmt"
"os"
"runtime/debug"
Expand All @@ -10,18 +11,21 @@ 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
)

func resolvedVersion() string {
v := strings.TrimSpace(version)
if v != "" && v != devVersion && !strings.HasSuffix(v, "-dev") {
if v != "" && v != sentinelDev {
return v
}
info, ok := readBuildInfo()
Expand All @@ -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 {
Expand Down
49 changes: 49 additions & 0 deletions internal/cmd/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down
8 changes: 8 additions & 0 deletions scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Loading