From dd34a5b21c0ed209a9b167d8e84f735e351c42f0 Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Fri, 5 Jun 2026 23:39:40 +0100 Subject: [PATCH] chore(release): prepare v2.0 infrastructure (Makefile, release.sh, Gitea release workflow, installers, modern versioning) --- .gitea/workflows/release.yml | 96 ++++++++++++++++++++++++++++++++++ CHANGELOG.md | 38 ++++++++++++++ Makefile | 88 +++++++++++++++++++++++++++++++ VERSION | 2 +- build.ps1 | 18 ++----- build.sh | 20 ++----- ci-build.sh | 18 ++----- internal/version/version.go | 4 +- release.sh | 95 +++++++++++++++++++++++++++++++++ scripts/gostations-install.ps1 | 49 +++++++++++++++++ scripts/gostations-install.sh | 78 +++++++++++++++++++++++++++ stations.go | 9 +++- stations_test.go | 28 +++++++++- 13 files changed, 494 insertions(+), 49 deletions(-) create mode 100644 .gitea/workflows/release.yml create mode 100644 CHANGELOG.md create mode 100644 Makefile create mode 100755 release.sh create mode 100644 scripts/gostations-install.ps1 create mode 100755 scripts/gostations-install.sh diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..6d05400 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,96 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + release: + name: Create Release + runs-on: ubuntu-gitea + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Build for multiple platforms + run: | + VERSION=${GITHUB_REF#refs/tags/} + COMMIT=$(git rev-parse --short HEAD) + DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) + mkdir -p build + for plat in 'linux/amd64' 'linux/arm64' 'darwin/amd64' 'darwin/arm64' 'windows/amd64'; do + IFS='/' read -r OS ARCH <<< "$plat" + BIN="gostations-${OS}-${ARCH}" + if [ "$OS" = "windows" ]; then BIN="${BIN}.exe"; fi + GOOS="$OS" GOARCH="$ARCH" go build -trimpath -ldflags "-s -w \ + -X 'github.com/gmgauthier/gostations/internal/version.Version=${VERSION}' \ + -X 'github.com/gmgauthier/gostations/internal/version.Commit=${COMMIT}' \ + -X 'github.com/gmgauthier/gostations/internal/version.BuildDate=${DATE}' \ + -X 'main.version=${VERSION}'" -o "build/${BIN}" . + done + + - name: Prepare assets + run: | + VERSION=${GITHUB_REF#refs/tags/} + for bin in build/gostations-* ; do + if [ ! -f "$bin" ]; then continue; fi + OSARCH=$(basename "$bin" | sed 's/gostations-//' | sed 's/\.exe$//') + tar czf "build/gostations-${OSARCH}-${VERSION}.tar.gz" -C build "$(basename "$bin")" + done + sha256sum build/gostations-*.tar.gz | tee build/checksums.txt + + # Include useful scripts from repo (if present) + [ -f scripts/gostations-install.sh ] && cp scripts/gostations-install.sh build/ + [ -f scripts/gostations-install.ps1 ] && cp scripts/gostations-install.ps1 build/ + + # Clean raw binaries (we ship the tarballs) + for plat in 'linux/amd64' 'linux/arm64' 'darwin/amd64' 'darwin/arm64' 'windows/amd64'; do + IFS='/' read -r OS ARCH <<< "$plat" + BIN="gostations-${OS}-${ARCH}" + if [ "$OS" = "windows" ]; then BIN="${BIN}.exe"; fi + rm -f "build/${BIN}" + done + + - name: Install dependencies + run: apt update && apt install -y jq + + - name: Create Release & Upload Assets + env: + GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: | + VERSION=${GITHUB_REF#refs/tags/} + GITEA_API=https://repos.gmgauthier.com/api/v1 + REPO=${GITHUB_REPOSITORY} + + curl -X POST "${GITEA_API}/repos/${REPO}/releases" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"tag_name\": \"${VERSION}\", + \"name\": \"gostations ${VERSION}\", + \"body\": \"## Quick Install\n\n### Bash (Linux/macOS)\n\n\`\`\`bash\ncurl -L https://repos.gmgauthier.com/gmgauthier/gostations/releases/download/${VERSION}/gostations-install.sh | VERSION=${VERSION} bash\n\`\`\`\n\n### PowerShell (Windows/macOS/Linux)\n\n\`\`\`powershell\nirm https://repos.gmgauthier.com/gmgauthier/gostations/releases/download/${VERSION}/gostations-install.ps1 | iex\n\`\`\`\n\nPlatform binaries (tar.gz) + checksums.txt are attached. See README.md and CHANGELOG (if present) for details. Legacy wmenu UI still available with --legacy.\" + }" > release.json + + RELEASE_ID=$(jq .id release.json) + + for asset in build/* ; do + name=$(basename "$asset") + mime="application/octet-stream" + [[ "$name" =~ \.tar\.gz$ ]] && mime="application/gzip" + [[ "$name" =~ \.(txt|sh|ps1)$ ]] && mime="text/plain" + curl -X POST "${GITEA_API}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${name}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: ${mime}" \ + --data-binary "@$asset" + done diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..be2ea94 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +All notable changes to gostations will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.0.0] - 2026-06-XX + +### Added +- Full modern TUI (Bubble Tea + bubbles/list + lipgloss) as the **default** experience. +- Two-stage UI: station selection list → dedicated playback view. +- Playback view inspired by classic Winamp: + - Metadata viewer area showing live streamed song titles (via mpv IPC). + - Control buttons / keys: skip back/forward, volume (UP/DOWN arrows + on-screen vertical bar), mute toggle, play/pause, stop (returns to list). +- mpv JSON IPC player implementation (background playback, no terminal takeover, responsive controls and metadata observation). +- Favorites (★) support: + - TUI hotkey `f` to toggle. + - CLI: `gostations fav list|add|del [index|search|url]`. + - Initial view shows your Favorites if any (with ★ markers). +- Server-side search: while the filter is active, pressing ENTER performs a fresh lookup and replaces the list. +- `--legacy` flag to force the old wmenu UI (preserved for now). +- All previous CLI subcommands (`find`, `play`, `fav ...`) continue to work for scripting. + +### Changed +- Default UI is now the new TUI (no more wmenu unless --legacy). +- Player abstraction extended for controls and metadata. +- Build/release process modernized (Makefile, cross-compilation, Gitea release workflow + installers) to match other projects. + +### Fixed +- Various legacy subExecute / player execution issues from the old architecture. +- Test coverage for new TUI playback and player features. + +See the git history for the full list of changes leading to 2.0. + +## [0.2] - Previous + +Legacy wmenu-based UI + initial internal refactoring. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ae30582 --- /dev/null +++ b/Makefile @@ -0,0 +1,88 @@ +.PHONY: test test-short test-cover lint build install clean help cross release-notes + +# Versioning (override on command line or via env for releases) +VERSION ?= dev-$(shell git describe --tags --always --dirty 2>/dev/null || echo unknown) +COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) +DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unknown) + +MODULE = github.com/gmgauthier/gostations +LDFLAGS = -s -w \ + -X '$(MODULE)/internal/version.Version=$(VERSION)' \ + -X '$(MODULE)/internal/version.Commit=$(COMMIT)' \ + -X '$(MODULE)/internal/version.BuildDate=$(DATE)' \ + -X 'main.version=$(VERSION)' + +test: + go test ./... -v -race + +test-short: + go test -short ./... -v -race + +test-cover: + @mkdir -p build + go test ./... -coverprofile=build/coverage.out + go tool cover -html=build/coverage.out -o build/coverage.html + @echo "✅ Coverage report: open build/coverage.html in your browser" + +lint: + @which golangci-lint > /dev/null || (echo "❌ golangci-lint not found. Install with:" && echo " go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest" && exit 1) + golangci-lint run + +build: + @mkdir -p build + go build -trimpath -ldflags "$(LDFLAGS)" -o build/gostations . + @echo "✅ Dev build: VERSION=$(VERSION) COMMIT=$(COMMIT) DATE=$(DATE)" + @build/gostations -v || true + +install: build + mkdir -p ~/.local/bin + cp build/gostations ~/.local/bin/gostations + chmod +x ~/.local/bin/gostations + @echo "✅ gostations installed to ~/.local/bin/gostations" + +clean: + rm -rf build/ + +# Cross compile (used by release workflow) +cross: + @mkdir -p build + @for plat in 'linux/amd64' 'linux/arm64' 'darwin/amd64' 'darwin/arm64' 'windows/amd64'; do \ + IFS='/' read -r OS ARCH <<< "$$plat"; \ + BIN="gostations-$$OS-$$ARCH"; \ + if [ "$$OS" = "windows" ]; then BIN="$$BIN.exe"; fi; \ + echo "Building $$BIN..."; \ + GOOS=$$OS GOARCH=$$ARCH go build -trimpath -ldflags "$(LDFLAGS)" -o "build/$$BIN" . ; \ + done + @echo "✅ Cross builds complete in build/" + +# Helper to print release notes body (used by release process) +release-notes: + @echo "## Installation" + @echo "" + @echo "Download the appropriate archive for your platform from the release assets." + @echo "" + @echo "### Quick install (Linux/macOS)" + @echo "" + @echo '```bash' + @echo 'curl -L https://repos.gmgauthier.com/gmgauthier/gostations/releases/download/$(VERSION)/gostations-install.sh | VERSION=$(VERSION) bash' + @echo '```' + @echo "" + @echo "### Quick install (Windows / PowerShell)" + @echo "" + @echo '```powershell' + @echo 'irm https://repos.gmgauthier.com/gmgauthier/gostations/releases/download/$(VERSION)/gostations-install.ps1 | iex' + @echo '```' + @echo "" + @echo "See README.md for full details and configuration (radiostations.ini)." + +help: + @echo "Available targets:" + @echo " test Run all tests (with race)" + @echo " test-short Run tests with -short (skips live integration)" + @echo " test-cover Tests + HTML coverage report" + @echo " lint Run golangci-lint" + @echo " build Optimized dev build (uses git describe for VERSION)" + @echo " install Build + install to ~/.local/bin/gostations" + @echo " cross Build all release platforms into build/" + @echo " release-notes Print suggested Gitea release body text" + @echo " clean Remove build/" diff --git a/VERSION b/VERSION index 2f45361..359a5b9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2 \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index f37da9b..b53f01e 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,17 +1,7 @@ -# NOTE: The following commands assumes you have Git For Windows -# installed, which comes with a bunch of GNU tools packaged for windows: -Set-Variable -Name GIT_COMMIT -Value "$(git rev-list -1 HEAD)" -Set-Variable -Name CANONICAL_VERSION -Value "$(cat.exe ./VERSION)-$(uname)" -Set-Variable -Name VERSION_STRING -Value "$CANONICAL_VERSION-$GIT_COMMIT" +# Modernized. Prefer `make build` / `make install` (via Git Bash or WSL recommended). +# This remains for basic compatibility. -Set-Variable -Name buildpath -Value "build/$(uname)/gostations.exe" - -go mod tidy - -go build -o "$buildpath" -ldflags "-X main.version=$VERSION_STRING" - -& $buildpath -v - -Copy-Item $buildpath $HOME/.local/bin -Force +make build +make install diff --git a/build.sh b/build.sh index dbece7d..3f1f396 100755 --- a/build.sh +++ b/build.sh @@ -1,18 +1,8 @@ #!/usr/bin/env sh +# Modernized build script. Prefer `make build` or `make install`. +# This script remains for compatibility. -GIT_COMMIT=$(git rev-list -1 HEAD) -export GIT_COMMIT -CANONICAL_VERSION=$(cat ./VERSION)-$(uname) -export CANONICAL_VERSION -VERSION_STRING="$CANONICAL_VERSION-$GIT_COMMIT" -export VERSION_STRING +set -e -buildpath="build/$(uname)/gostations" - -go mod tidy - -go build -o "$buildpath" -ldflags "-X main.version=$VERSION_STRING" - -"$buildpath" -v - -cp "$buildpath" ~/.local/bin +make build +make install diff --git a/ci-build.sh b/ci-build.sh index 1a791b7..71fad74 100755 --- a/ci-build.sh +++ b/ci-build.sh @@ -1,19 +1,7 @@ #!/usr/bin/env sh +# CI build (old runner compatibility). Uses Makefile for consistency. -mkdir -p build +set -e -GIT_COMMIT=$(git rev-list -1 HEAD) -export GIT_COMMIT -CANONICAL_VERSION=$(cat ./VERSION)-$(uname) -export CANONICAL_VERSION -VERSION_STRING="$CANONICAL_VERSION-$GIT_COMMIT" -export VERSION_STRING - -buildpath="build/$(uname)/gostations" - -/usr/local/go/bin/go mod tidy - -/usr/local/go/bin/go build -o "$buildpath" -ldflags "-X main.version=$VERSION_STRING" - -"$buildpath" -v +make build diff --git a/internal/version/version.go b/internal/version/version.go index 9362435..4cfdf3c 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -1,7 +1,7 @@ package version -// These vars are set at build time via -ldflags, e.g. -// -ldflags "-X github.com/gmgauthier/gostations/internal/version.Version=0.3 -X .../Commit=$(git rev-list -1 HEAD)" +// These vars are set at build time via -ldflags (see Makefile and .gitea/workflows/release.yml), e.g. +// -ldflags "-X github.com/gmgauthier/gostations/internal/version.Version=2.0.0 -X .../Commit=... -X .../BuildDate=..." var ( Version = "dev" Commit = "" diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..598c1fc --- /dev/null +++ b/release.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# release.sh — One-command release driver for gostations (modeled on grokkit) +# Usage: ./release.sh v2.0.0 +# +# This will: +# - Create the git tag (so downstream CI/release can see it) +# - Help generate/update CHANGELOG (via grokkit if available) +# - Commit the changes +# - Push tag + commit +# - Print suggested Gitea release notes body + +set -euo pipefail + +VERSION="${1:-}" + +if [[ -z "$VERSION" || ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)? ]]; then + echo "❌ Usage: $0 vX.Y.Z" + echo " Example: $0 v2.0.0" + exit 1 +fi + +echo "🚀 Starting release process for gostations $VERSION..." + +# Safety check: clean working tree +if [[ -n $(git status --porcelain) ]]; then + echo "❌ Working tree is dirty. Commit or stash changes first." + exit 1 +fi + +# Final human confirmation +echo "" +echo "This will:" +echo " 1. Create git tag $VERSION" +echo " 2. (If grokkit available) Run grokkit changelog + commit for CHANGELOG.md" +echo " 3. Push the commit + tag" +echo " 4. Print ready-to-paste text for the Gitea release page" +echo "" +read -p "Proceed with release $VERSION? (y/N) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 1 +fi + +# 1. Create the tag early (CI release workflow triggers on tag) +echo "🏷️ Creating tag $VERSION..." +git tag "$VERSION" + +# 2. Changelog / release prep (best effort using grokkit if present on PATH) +if command -v grokkit >/dev/null 2>&1; then + echo "📝 Generating/updating CHANGELOG.md via grokkit..." + grokkit changelog --version "$VERSION" || echo "⚠️ grokkit changelog returned non-zero (continuing)" + + if [[ -n $(git status --porcelain -- CHANGELOG.md) ]]; then + echo "📦 Committing changelog changes via grokkit..." + git add CHANGELOG.md + grokkit commit || echo "⚠️ grokkit commit may need manual follow-up" + fi +else + echo "ℹ️ grokkit not found on PATH. Skipping automated changelog." + echo " You can manually edit CHANGELOG.md before the next steps if desired." + echo " Then run: git add CHANGELOG.md && git commit -m 'chore(release): $VERSION'" +fi + +# 3. Push (tag was created earlier; push any new commit + tags) +echo "📤 Pushing commit (if any) + tag..." +git push || true +git push --tags + +# 4. Print nice release notes for Gitea +echo "" +echo "✅ Release $VERSION pushed!" +echo "" +echo "📋 Copy-paste the following into the Gitea release notes body:" +echo "------------------------------------------------------------" +make -s release-notes VERSION="$VERSION" || cat </dev/null 2>&1; then + CHECKSUM_CMD="sha256sum" +elif command -v shasum >/dev/null 2>&1; then + CHECKSUM_CMD="shasum -a 256" +fi + +if [ -n "$CHECKSUM_CMD" ] && [ -f checksums.txt ]; then + HASH=$(grep -oE '[0-9a-f]{64}\s+build/[^ ]*' checksums.txt | grep "${ASSET}" | cut -d' ' -f1 || true) + if [ -z "$HASH" ]; then + echo "⚠️ No checksum entry found for ${ASSET} – continuing without verification" + else + echo "${HASH} asset.tar.gz" | $CHECKSUM_CMD --check - || { + echo "❌ Checksum mismatch for ${ASSET}!" + exit 1 + } + echo "✅ Checksum verified successfully" + fi +else + echo "⚠️ Checksum tool not found (sha256sum/shasum) – skipping verification" +fi + +# Extract +echo "Extracting asset..." +tar xzf asset.tar.gz +BINARY="gostations-${OS}-${ARCH}" +if [ "$OS" = "windows" ]; then BINARY="${BINARY}.exe"; fi + +# Install +INSTALL_DIR="${HOME}/.local/bin" +mkdir -p "${INSTALL_DIR}" +mv "${BINARY}" "${INSTALL_DIR}/gostations" 2>/dev/null || mv "${BINARY}" "${INSTALL_DIR}/gostations.exe" 2>/dev/null || true +chmod +x "${INSTALL_DIR}/gostations" 2>/dev/null || true + +echo "✅ gostations ${VERSION} installed to ${INSTALL_DIR}/gostations" +echo "Add to PATH if needed: export PATH=\"${INSTALL_DIR}:\$PATH\"" +gostations -v || true diff --git a/stations.go b/stations.go index 5693a33..c7eaa44 100644 --- a/stations.go +++ b/stations.go @@ -14,11 +14,18 @@ import ( playerpkg "github.com/gmgauthier/gostations/internal/player" "github.com/gmgauthier/gostations/internal/radio" "github.com/gmgauthier/gostations/internal/ui" + ver "github.com/gmgauthier/gostations/internal/version" ) -var version string +var version string // kept for ldflags compat with legacy build scripts; prefer internal/version func showVersion() { + // Prefer modern internal/version (ldflags in Makefile + release workflow) + if ver.Version != "dev" || ver.Commit != "" { + fmt.Println(ver.String()) + return + } + // Fallback for legacy build scripts that only -X main.version=... fmt.Println(version) } diff --git a/stations_test.go b/stations_test.go index ce1c1c0..832c8aa 100644 --- a/stations_test.go +++ b/stations_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/gmgauthier/gostations/internal/radio" + ver "github.com/gmgauthier/gostations/internal/version" ) func TestShowVersion_Unit(t *testing.T) { @@ -27,6 +28,7 @@ func TestShowVersion_Unit(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // Test legacy main var path originalVersion := version version = tt.version defer func() { version = originalVersion }() @@ -46,7 +48,31 @@ func TestShowVersion_Unit(t *testing.T) { got := buf.String() if got != tt.expected { - t.Errorf("showVersion() = %q, want %q", got, tt.expected) + t.Errorf("showVersion() legacy = %q, want %q", got, tt.expected) + } + + // Test new internal/version path (preferred for 2.0+ builds) + origV := ver.Version + origC := ver.Commit + ver.Version = tt.version + ver.Commit = "" + defer func() { ver.Version = origV; ver.Commit = origC }() + + origStdout = os.Stdout + r, w, _ = os.Pipe() + os.Stdout = w + + showVersion() + + w.Close() + os.Stdout = origStdout + + buf.Reset() + _, _ = buf.ReadFrom(r) + got = buf.String() + + if got != tt.expected { + t.Errorf("showVersion() package = %q, want %q", got, tt.expected) } }) }