From eea691ae086790dc5c9bf1b65ff8517ff6b00348 Mon Sep 17 00:00:00 2001 From: Umesh Date: Thu, 17 Sep 2026 21:04:06 +0530 Subject: [PATCH 1/3] chore(release): cut v0.3.1 with Bash 3.2 portability and proxy retry hardening --- CHANGELOG.md | 10 ++++++++-- README.md | 4 ++-- SECURITY.md | 5 +++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0875d6..a25ac6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.1] - 2026-09-17 + ### Changed -- dependencies: Require verified published `github.com/umesh0492/go-libs@v0.2.1` and `github.com/umesh0492/go-fintech-india@v0.2.3`; remove the machine-specific local replacement. -- release: Record immutable module and tag evidence, including the separate lightweight `v0.3.0` artifact, and verify proxy-only isolated consumers before a release. +- ci: Replace all Bash 4+ `mapfile`/`readarray` built-ins with POSIX/Bash 3.2-compatible `while IFS= read -r` array loops in `scripts/check_version.sh` and `scripts/check_coverage.sh`, fixing macOS CI failures. +- ci: Add `scripts/test_bash_compat.sh` — static compatibility guard integrated into CI, release workflow, and Makefile. +- release: Remove fragile zero-retry isolated consumer step from `release.yml`; replaced with `scripts/verify_release.sh` using bounded exponential backoff (up to 12 attempts, per-attempt isolated GOMODCACHE). +- release: Add `scripts/test_verify_release.sh` — unit-tests proxy retry logic, backoff timing, manifest generation, and pre-release SemVer parsing. +- ci: Add `scripts/verify_release_baseline.sh` — post-merge downstream consumer integrity check. +- docs: Add `docs/RELEASE_BASELINE.md` documenting full release procedure and version gates. ## [0.2.1] - 2026-09-17 diff --git a/README.md b/README.md index 9390dd9..e30104d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # go-app-kit -> Current main-line published release: `v0.2.1`. The lightweight `v0.3.0` tag points to an earlier commit and remains an immutable, separate artifact. See [Release Baseline](docs/RELEASE_BASELINE.md) before selecting a release or publishing a reconciliation. +> Current main-line published release: `v0.3.1`. The lightweight `v0.3.0` tag points to an earlier commit and remains an immutable, separate artifact. See [Release Baseline](docs/RELEASE_BASELINE.md) before selecting a release or publishing a reconciliation. [![CI](https://github.com/umesh0492/go-app-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/umesh0492/go-app-kit/actions/workflows/ci.yml) [![Code Quality: golangci-lint](https://img.shields.io/badge/code%20quality-golangci--lint-brightgreen?logo=go)](https://golangci-lint.run/) @@ -53,7 +53,7 @@ While [`go-libs`](https://github.com/umesh0492/go-libs) provides low-level, zero ### Standalone Import When consuming `go-app-kit` in your microservice: ```bash -go get github.com/umesh0492/go-app-kit@v0.2.1 +go get github.com/umesh0492/go-app-kit@v0.3.1 ``` ### Multi-Module Local Development (`go.work`) diff --git a/SECURITY.md b/SECURITY.md index dc8bdbc..23d69e3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,8 +6,9 @@ | Version Series | Security Updates | Status | | -------------- | ----------------------- | -------------------- | -| 0.2.x | :white_check_mark: Yes | **Active / Current** | -| 0.1.x | :white_check_mark: Yes | Maintenance | +| 0.3.x | :white_check_mark: Yes | **Active / Current** | +| 0.2.x | :white_check_mark: Yes | Maintenance | +| 0.1.x | :x: No | End-of-Life | | < 0.1.0 | :x: No | End-of-Life | --- From 77db4a2ec017a03b6bdf3f6ca647e3d71c7a9bec Mon Sep 17 00:00:00 2001 From: Umesh Date: Fri, 18 Sep 2026 00:51:01 +0530 Subject: [PATCH 2/3] chore(release): finalize v0.3.1 changelog and bump go-libs to v0.3.0 --- CHANGELOG.md | 3 +-- go.mod | 2 +- go.sum | 4 ++-- scripts/check_version.sh | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a25ac6c..7290c5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,10 @@ All notable changes to this project 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). -## [Unreleased] - ## [0.3.1] - 2026-09-17 ### Changed +- dependencies: Bump `github.com/umesh0492/go-libs` to `v0.3.0`. - ci: Replace all Bash 4+ `mapfile`/`readarray` built-ins with POSIX/Bash 3.2-compatible `while IFS= read -r` array loops in `scripts/check_version.sh` and `scripts/check_coverage.sh`, fixing macOS CI failures. - ci: Add `scripts/test_bash_compat.sh` — static compatibility guard integrated into CI, release workflow, and Makefile. - release: Remove fragile zero-retry isolated consumer step from `release.yml`; replaced with `scripts/verify_release.sh` using bounded exponential backoff (up to 12 attempts, per-attempt isolated GOMODCACHE). diff --git a/go.mod b/go.mod index 00978df..3b78a79 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/testcontainers/testcontainers-go v0.44.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.44.0 github.com/umesh0492/go-fintech-india v0.2.3 - github.com/umesh0492/go-libs v0.2.1 + github.com/umesh0492/go-libs v0.3.0 ) require ( diff --git a/go.sum b/go.sum index 62ee029..65827b7 100644 --- a/go.sum +++ b/go.sum @@ -111,8 +111,8 @@ github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqo github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= github.com/umesh0492/go-fintech-india v0.2.3 h1:f2QB8HdhsxaCRFvu0HWQmu55rqPtxA6/kliAYIYNqtU= github.com/umesh0492/go-fintech-india v0.2.3/go.mod h1:UPKoeW7JKYkR0nQtY4rW1dmJ9oI20RzDOeE4s7fDrgc= -github.com/umesh0492/go-libs v0.2.1 h1:9Fzm2GZMkd+rnFAFOd5MURnOorqM98O4H/zcVUb6uoY= -github.com/umesh0492/go-libs v0.2.1/go.mod h1:R7gQaadUNwpnavd5P96ThNbhYyUUKyVbfKCR/mu29/o= +github.com/umesh0492/go-libs v0.3.0 h1:B6rPK2M7OdXc5a3Q8xsxUvJawgf/C4nJVG1KvHwpyXg= +github.com/umesh0492/go-libs v0.3.0/go.mod h1:R7gQaadUNwpnavd5P96ThNbhYyUUKyVbfKCR/mu29/o= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= diff --git a/scripts/check_version.sh b/scripts/check_version.sh index b06826c..6bfcb8e 100755 --- a/scripts/check_version.sh +++ b/scripts/check_version.sh @@ -75,7 +75,7 @@ fi if grep -qE '^[[:space:]]*replace[[:space:]]+' go.mod; then fail 'go.mod contains a replace directive; use a caller-owned go.work for local companion development' fi -[[ "${golibs_version}" == 'v0.2.1' ]] || fail "go.mod must require go-libs v0.2.1, got ${golibs_version:-none}" +[[ "${golibs_version}" == 'v0.3.0' ]] || fail "go.mod must require go-libs v0.3.0, got ${golibs_version:-none}" [[ "${fintech_version}" == 'v0.2.3' ]] || fail "go.mod must require go-fintech-india v0.2.3, got ${fintech_version:-none}" # Bash 3.2 has indexed arrays but not mapfile/readarray. Populate arrays with From 719ae5c917c3e901008dc173b8769acfc16fcb6a Mon Sep 17 00:00:00 2001 From: Umesh Date: Fri, 18 Sep 2026 18:25:47 +0530 Subject: [PATCH 3/3] fix(release): reconcile v0.3.2 release baseline, bump go-libs v0.3.0, and add pre-tag release guards --- .github/RELEASE_PROCEDURE.md | 97 ++++++++++++++++++++++++++++++++++ CHANGELOG.md | 9 +++- Makefile | 18 ++++++- README.md | 4 +- docs/RELEASE_BASELINE.md | 24 ++++++--- outbox/outbox_test.go | 5 ++ scripts/check_tag_readiness.sh | 71 +++++++++++++++++++++++++ scripts/git-hooks/pre-push | 40 ++++++++++++++ 8 files changed, 256 insertions(+), 12 deletions(-) create mode 100644 .github/RELEASE_PROCEDURE.md create mode 100755 scripts/check_tag_readiness.sh create mode 100755 scripts/git-hooks/pre-push diff --git a/.github/RELEASE_PROCEDURE.md b/.github/RELEASE_PROCEDURE.md new file mode 100644 index 0000000..b9d6e34 --- /dev/null +++ b/.github/RELEASE_PROCEDURE.md @@ -0,0 +1,97 @@ +# Release Procedure & Guardrails + +This document establishes the mandatory, step-by-step release procedure for `go-app-kit`. + +--- + +## 1. Immutable Release Principles + +Go modules rely on public proxy and checksum services (`proxy.golang.org` and `sum.golang.org`). + +- **Never Overwrite or Move Tags**: Once a Git tag is pushed to remote, Go module proxies immediately cache the source archive and its cryptographic checksum (`h1:...`). Retagging the same version name to a different commit will break downstream builds with checksum mismatch errors. +- **Monotonic Releases Only**: If a release fails or needs corrections (such as the historical `v0.3.1` incident where a tag was pushed before docs were updated), a new monotonic version (e.g. `v0.3.2`) must be published. +- **Annotated Tags Only**: All release tags must be created as annotated Git tags (`git tag -a vX.Y.Z -m "Release vX.Y.Z"`). Never use lightweight tags for official releases. + +--- + +## 2. Release Guardrails & Automated Checks + +To prevent releasing unverified code or untracked changes, multiple layers of defense are active: + +1. **Pre-Tag Verification Script** (`scripts/check_tag_readiness.sh`): + - Verifies the Git working tree is completely clean. + - Validates that `TAG` argument is valid SemVer (e.g. `v0.3.2`). + - Ensures `CHANGELOG.md` has an entry for the version as its top released section. + - Ensures `README.md` published version and installation snippet match. + - Runs `GIT_TAG=$TAG ./scripts/check_version.sh` (validates package count, DDL, docs, symbol inventory, and coverage). + - Runs `./scripts/test_bash_compat.sh` (Bash 3.2 macOS compatibility). + - Runs `go test -race ./...` (concurrency race detector). + +2. **Git Pre-Push Hook** (`scripts/git-hooks/pre-push`): + - Intercepts any `git push` attempting to push a tag `refs/tags/v*`. + - Executes `GIT_TAG=$tag ./scripts/check_version.sh`. + - Rejects the push if version gates or test checks fail. + +3. **Makefile Targets**: + - `make install-hooks`: Configures Git to use `scripts/git-hooks`. + - `make check-release-readiness TAG=vX.Y.Z`: Runs pre-tag validation. + - `make tag-release TAG=vX.Y.Z`: Runs readiness validation and creates the annotated tag only if all checks pass. + +--- + +## 3. Step-by-Step Release Checklist + +### Step 1: Install Git Hooks (One-Time Setup) +```bash +make install-hooks +``` + +### Step 2: Prepare Release PR on Feature Branch +1. Create a release branch from `main`: + ```bash + git checkout -b release/vX.Y.Z + ``` +2. Update documentation and release metadata: + - **`CHANGELOG.md`**: Add `## [X.Y.Z] - YYYY-MM-DD` at the top of released versions. Document all changes under appropriate categories (`Changed`, `Added`, `Fixed`). + - **`README.md`**: Update current published release header and `go get github.com/umesh0492/go-app-kit@vX.Y.Z` snippet. + - **`SECURITY.md`**: Verify that the version series is marked **Active / Current**. + - **`docs/RELEASE_BASELINE.md`**: Document the release baseline and history. + - **`scripts/check_version.sh`**: Ensure dependency versions and expectations match `go.mod`. +3. Verify changes locally: + ```bash + bash scripts/test_bash_compat.sh + bash scripts/check_version.sh + go test -race ./... + ``` +4. Commit and push the branch: + ```bash + git add -A + git commit -m "chore(release): prepare vX.Y.Z release" + git push origin release/vX.Y.Z + ``` +5. Open a Pull Request into `main` and wait for all CI checks to pass. + +### Step 3: Merge PR to `main` +- Merge the Release PR into `main` (Squash or Rebase). + +### Step 4: Tag the Release on `main` +1. Switch to `main` and ensure your local branch is synchronized: + ```bash + git checkout main + git pull origin main + ``` +2. Execute the automated release tagging target: + ```bash + make tag-release TAG=vX.Y.Z + ``` + *Note: This command runs `scripts/check_tag_readiness.sh`. It will fail if your working tree is dirty or any version mismatch exists.* + +### Step 5: Push the Tag +```bash +git push origin vX.Y.Z +``` +*Note: The `pre-push` hook will run a final validation before the tag leaves your machine.* + +### Step 6: Verify CI Release Workflow +- Monitor the GitHub Actions **Release** workflow run. +- Ensure that the release manifest and assets are created and published on GitHub. diff --git a/CHANGELOG.md b/CHANGELOG.md index 7290c5e..45a2d63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,17 @@ All notable changes to this project 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). +## [0.3.2] - 2026-09-18 + +### Changed +- release: Reconciled release baseline following v0.3.1 truth-gate trip; published monotonic v0.3.2. +- dependencies: Bumped `github.com/umesh0492/go-libs` to `v0.3.0`. +- outbox: Added `TypeMap()` implementation to test `mockRows` for pgx/v5 5.11.0 compatibility. +- tooling: Added `scripts/check_tag_readiness.sh`, `scripts/git-hooks/pre-push`, and `make tag-release` to structurally prevent untracked changelog releases. + ## [0.3.1] - 2026-09-17 ### Changed -- dependencies: Bump `github.com/umesh0492/go-libs` to `v0.3.0`. - ci: Replace all Bash 4+ `mapfile`/`readarray` built-ins with POSIX/Bash 3.2-compatible `while IFS= read -r` array loops in `scripts/check_version.sh` and `scripts/check_coverage.sh`, fixing macOS CI failures. - ci: Add `scripts/test_bash_compat.sh` — static compatibility guard integrated into CI, release workflow, and Makefile. - release: Remove fragile zero-retry isolated consumer step from `release.yml`; replaced with `scripts/verify_release.sh` using bounded exponential backoff (up to 12 attempts, per-attempt isolated GOMODCACHE). diff --git a/Makefile b/Makefile index 3921cad..7a47b32 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all fmt-check test test-race cover test-integration lint vulncheck verify verify-release-baseline test-bash-compat tidy build couple decouple workspace-init clean help +.PHONY: all fmt-check test test-race cover test-integration lint vulncheck verify verify-release-baseline test-bash-compat tidy build couple decouple workspace-init install-hooks check-release-readiness tag-release clean help all: fmt-check verify lint test-race vulncheck build @@ -49,6 +49,22 @@ decouple: couple: @echo "DEPRECATED: refusing to modify go.mod. Run 'make workspace-init' to use go-libs locally." +# Install git hooks to enforce release and pre-push verification +install-hooks: + @git config core.hooksPath scripts/git-hooks || (mkdir -p .git/hooks && cp scripts/git-hooks/* .git/hooks/) + @chmod +x scripts/git-hooks/* 2>/dev/null || true + @echo "✅ Git hooks configured to scripts/git-hooks" + +# Verify that the repository is completely ready for a release tag (usage: make check-release-readiness TAG=v0.3.2) +check-release-readiness: + @if [ -z "$(TAG)" ]; then echo "❌ TAG is required. Usage: make check-release-readiness TAG=vX.Y.Z" >&2; exit 1; fi + ./scripts/check_tag_readiness.sh $(TAG) + +# Check release readiness and create an annotated git tag (usage: make tag-release TAG=v0.3.2) +tag-release: check-release-readiness + git tag -a $(TAG) -m "Release $(TAG)" + @echo "✅ Successfully created annotated release tag $(TAG)." + # Clean build and test artifacts clean: rm -f invoice_service examples/invoice_service/invoice_service coverage.out coverage.out.packages .packages coverage.txt coverage.html *.test diff --git a/README.md b/README.md index e30104d..ffdc076 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # go-app-kit -> Current main-line published release: `v0.3.1`. The lightweight `v0.3.0` tag points to an earlier commit and remains an immutable, separate artifact. See [Release Baseline](docs/RELEASE_BASELINE.md) before selecting a release or publishing a reconciliation. +> Current main-line published release: `v0.3.2`. The lightweight `v0.3.0` tag points to an earlier commit and remains an immutable, separate artifact. See [Release Baseline](docs/RELEASE_BASELINE.md) before selecting a release or publishing a reconciliation. [![CI](https://github.com/umesh0492/go-app-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/umesh0492/go-app-kit/actions/workflows/ci.yml) [![Code Quality: golangci-lint](https://img.shields.io/badge/code%20quality-golangci--lint-brightgreen?logo=go)](https://golangci-lint.run/) @@ -53,7 +53,7 @@ While [`go-libs`](https://github.com/umesh0492/go-libs) provides low-level, zero ### Standalone Import When consuming `go-app-kit` in your microservice: ```bash -go get github.com/umesh0492/go-app-kit@v0.3.1 +go get github.com/umesh0492/go-app-kit@v0.3.2 ``` ### Multi-Module Local Development (`go.work`) diff --git a/docs/RELEASE_BASELINE.md b/docs/RELEASE_BASELINE.md index 0adb339..dc1da4c 100644 --- a/docs/RELEASE_BASELINE.md +++ b/docs/RELEASE_BASELINE.md @@ -14,7 +14,8 @@ altering local refs. | --- | --- | --- | --- | --- | --- | | `github.com/umesh0492/go-app-kit` | `v0.2.1` | annotated (`277ad0741cd723d8c2c445b24dec5608cf1a1c3b`) | `bbb29710fd5622023b9b30bd3c9151d4d5517832` | `11c92c0eabf36fefe0b0a38eceebe8d3f311cda8b72e27edf479a3a64192d325` | `h1:fIcEZxtHfIsYSfaoZPN5+xMGRh6WGltgBMNmkix+mx0=` | | `github.com/umesh0492/go-app-kit` | `v0.3.0` | lightweight | `a4501b6ee165c475811e6e3c43dab9b20907d25e` | `52036d15c805f2f10f795e868abbe32248f71e210c6f4213524f2a464c1806f4` | `h1:vxh+SnwkUot1nLkWKDUBuhLfEPI877ABFxjOfYYdTiY=` | -| `github.com/umesh0492/go-libs` | `v0.2.1` | annotated (verified in its baseline) | `b75acc6d82e47189ef174a4ea80134fed8cd392f` | `d519b6624138503f1cbe8f3de071f0fe685b521ad5f1f9634a0a85a1992fc9db` | `h1:9Fzm2GZMkd+rnFAFOd5MURnOorqM98O4H/zcVUb6uoY=` | +| `github.com/umesh0492/go-app-kit` | `v0.3.1` | lightweight | `815823212936738de790eba35118c36acdecfea1` | cached proxy entry (release truth-gate failure) | cached sumdb entry | +| `github.com/umesh0492/go-libs` | `v0.3.0` | annotated (verified in its baseline) | `69e9b0857997380907ad53ec220d36baadbe53e3` | verified in go-libs baseline | `h1:B6rPK2M7OdXc5a3Q8xsxUvJawgf/C4nJVG1KvHwpyXg=` | | `github.com/umesh0492/go-fintech-india` | `v0.2.3` | lightweight (verified in its baseline) | `55888b8fb529bb6379eb96f9a04ce649b7a08b90` | `17b5e66a4f1164cabca95e4bfef849b956162552f32db7e2e7ea6d57b30f8504` | `h1:f2QB8HdhsxaCRFvu0HWQmu55rqPtxA6/kliAYIYNqtU=` | The `v0.3.0` tag is a legitimate immutable published artifact, but it points to @@ -22,11 +23,17 @@ a4501b6, which predates main's `v0.2.0` and annotated `v0.2.1` commits. It is not a release of the current main-line source. Consumers of the source checkout must not be told to use it, and a release must not overwrite it. +The `v0.3.1` tag is an immutable remote artifact pointing to commit `8158232`. It tripped +the release workflow truth gate because commit 8158232 was tagged before CHANGELOG.md +and README.md were updated to v0.3.1. Because Go module proxies cache tags immutably, +v0.3.1 cannot be re-pointed or overwritten; it remains an immutable entry, and +reconciliation is published monotonically as `v0.3.2`. + ## Dependency baseline The current source requires only verified published dependencies: -- `github.com/umesh0492/go-libs v0.2.1` +- `github.com/umesh0492/go-libs v0.3.0` - `github.com/umesh0492/go-fintech-india v0.2.3` There are no `replace` directives in `go.mod`. Use a caller-owned `go.work` file @@ -57,10 +64,11 @@ on failure. It creates a missing GitHub release or replaces only the `release-manifest.json` asset on an existing release; tags and other assets are never changed. -## Next release recommendation +## Current release baseline (v0.3.2) -The source baseline removes an absolute local replacement, upgrades the direct -fintech dependency, and adds release verification. It must be published as a -new version; do not reuse `v0.2.1` or `v0.3.0`. Because `v0.3.0` already exists, -the next monotonic version is **`v0.3.1` or later**. A `v0.3.1` patch release is -appropriate if compatibility checks remain green. +Following the immutable remote caching of `v0.3.1` at commit `8158232`, this repository +reconciled its release baseline to **`v0.3.2`**. +The release includes: +- Upgrading `github.com/umesh0492/go-libs` to `v0.3.0`. +- Implementing `TypeMap() *pgtype.Map` on `*mockRows` in `outbox/outbox_test.go` for pgx/v5 5.11.0 compatibility. +- Introducing structural release guardrails: `scripts/check_tag_readiness.sh`, `scripts/git-hooks/pre-push`, and `make tag-release` to prevent untracked or unverified tags from being cut. diff --git a/outbox/outbox_test.go b/outbox/outbox_test.go index 6d53c28..883ecdd 100644 --- a/outbox/outbox_test.go +++ b/outbox/outbox_test.go @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" "github.com/umesh0492/go-app-kit/outbox" ) @@ -1023,6 +1024,10 @@ func (m *mockRows) Conn() *pgx.Conn { return nil } +func (m *mockRows) TypeMap() *pgtype.Map { + return nil +} + func reflectAssign(dest any, src any) error { if dest == nil { return errors.New("nil destination") diff --git a/scripts/check_tag_readiness.sh b/scripts/check_tag_readiness.sh new file mode 100755 index 0000000..8222916 --- /dev/null +++ b/scripts/check_tag_readiness.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Validates repository, documentation, and test state before creating or pushing a release tag. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +cd "${ROOT_DIR}" + +readonly SEMVER_PATTERN='[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?' + +fail() { + printf '\033[0;31mERROR: %s\033[0m\n' "$*" >&2 + exit 1 +} + +tag="${1:-}" +if [[ -z "${tag}" ]]; then + fail "Usage: $0 " +fi + +if [[ ! "${tag}" =~ ^v${SEMVER_PATTERN}$ ]]; then + fail "Tag must be valid SemVer prefixed with 'v' (e.g. v0.3.2): ${tag}" +fi + +version="${tag#v}" + +echo "========================================================" +echo "Checking release tag readiness for: ${tag}" +echo "========================================================" + +# 1. Validates that the working tree is clean. +echo "-> Checking working tree status..." +if [[ -n "$(git status --porcelain)" ]]; then + git status --short >&2 + fail "Working tree has uncommitted or untracked changes. Commit or stash them before releasing." +fi + +# 2. Validates that CHANGELOG.md has an entry for this exact version as its top released section. +echo "-> Validating CHANGELOG.md top released version..." +top_changelog_version="$(grep -E "^## \[${SEMVER_PATTERN}\]" CHANGELOG.md | head -n1 | sed -E "s/^## \[(${SEMVER_PATTERN})\].*/\1/")" +if [[ "${top_changelog_version}" != "${version}" ]]; then + fail "Top CHANGELOG.md release entry is v${top_changelog_version:-none}, but expected v${version}" +fi + +# 3. Validates that README.md has this exact version. +echo "-> Validating README.md version references..." +readme_pub="$(sed -n -E "s/^> Current main-line published release: \`v(${SEMVER_PATTERN})\`\..*/\1/p" README.md | head -n1)" +readme_inst="$(sed -n -E "s|.*github\.com/umesh0492/go-app-kit@v(${SEMVER_PATTERN}).*|\1|p" README.md | head -n1)" +if [[ "${readme_pub}" != "${version}" ]]; then + fail "README.md published release header is v${readme_pub:-none}, but expected v${version}" +fi +if [[ "${readme_inst}" != "${version}" ]]; then + fail "README.md installation snippet is v${readme_inst:-none}, but expected v${version}" +fi + +# 4. Runs GIT_TAG=$1 ./scripts/check_version.sh. +echo "-> Running check_version.sh for ${tag}..." +GIT_TAG="${tag}" ./scripts/check_version.sh + +# 5. Runs ./scripts/test_bash_compat.sh. +echo "-> Running test_bash_compat.sh..." +./scripts/test_bash_compat.sh + +# 6. Runs go test -race ./... +echo "-> Running go test -race ./..." +go test -race ./... + +echo "========================================================" +printf '\033[0;32m✅ Release readiness confirmed! Tag %s is ready to be created and pushed.\033[0m\n' "${tag}" +echo "========================================================" diff --git a/scripts/git-hooks/pre-push b/scripts/git-hooks/pre-push new file mode 100755 index 0000000..f04882b --- /dev/null +++ b/scripts/git-hooks/pre-push @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Git pre-push hook: intercepts git push attempting to push a tag refs/tags/v* +# Verifies version, changelog, and test gates via check_version.sh. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" +cd "${ROOT_DIR}" + +remote="$1" +url="$2" + +readonly ZERO="0000000000000000000000000000000000000000" + +while read -r local_ref local_sha remote_ref remote_sha; do + # Skip deletion of refs + if [[ "${local_sha}" == "${ZERO}" || "${local_ref}" == "(delete)" ]]; then + continue + fi + + tag="" + if [[ "${remote_ref}" =~ ^refs/tags/(v[0-9]+.*) ]]; then + tag="${BASH_REMATCH[1]}" + elif [[ "${local_ref}" =~ ^refs/tags/(v[0-9]+.*) ]]; then + tag="${BASH_REMATCH[1]}" + fi + + if [[ -n "${tag}" ]]; then + echo "🔒 [pre-push hook] Release tag detected: ${tag}. Running release verification gates..." + if ! GIT_TAG="${tag}" ./scripts/check_version.sh; then + echo "❌ [pre-push hook] Push aborted! Version and release checks failed for ${tag}." >&2 + echo " Run './scripts/check_tag_readiness.sh ${tag}' to diagnose and verify release requirements." >&2 + exit 1 + fi + echo "✅ [pre-push hook] Release tag ${tag} verified successfully." + fi +done + +exit 0