From 42d7af282528ba6708314af493cd92f26af5e489 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 27 May 2026 16:01:20 +0000 Subject: [PATCH] Bump Makefile in CI workspace before build, then sync master via PR - Compute version, sed Makefile locally, test, build, publish - Build fails if Makefile and release version differ - Follow-up PR updates master under branch protection Co-authored-by: Nick Jennings --- .github/workflows/release.yml | 45 ++++++++++++++++++++++------------- release.md | 42 ++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82ed920..3310ba7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,11 @@ -# Cut a release from GitHub: Actions → Release → Run workflow (no local git required). -# Bumps from the latest published release tag; works with protected master (no direct push). -# See release.md. -# Opens a PR to sync the Makefile after publishing. +# See release.md name: Release on: workflow_dispatch: inputs: bump: - description: "Bump from latest release (e.g. v1.3.0 + patch → v1.3.1)" + description: Version bump required: true type: choice default: patch @@ -74,14 +71,13 @@ jobs: esac VERSION="${MAJOR}.${MINOR}.${PATCH}" TAG="v${VERSION}" - echo "Bump: ${BUMP} → releasing ${VERSION}" + echo "Bump: ${BUMP} → ${VERSION} (was ${CURRENT})" if git ls-remote --exit-code origin "refs/tags/${TAG}" >/dev/null 2>&1; then echo "Tag ${TAG} already exists on remote" exit 1 fi echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - echo "previous=${CURRENT}" >> "$GITHUB_OUTPUT" - name: Check release does not exist env: @@ -94,6 +90,19 @@ jobs: exit 1 fi + - name: Update Makefile to release version (workspace only) + env: + VERSION: ${{ steps.meta.outputs.version }} + run: | + set -euo pipefail + sed -i "s/^VERSION ?=.*/VERSION ?= ${VERSION}/" Makefile + MAKEFILE_VERSION=$(sed -n 's/^VERSION ?=[[:space:]]*//p' Makefile | head -1) + if [ "${MAKEFILE_VERSION}" != "${VERSION}" ]; then + echo "Makefile VERSION is ${MAKEFILE_VERSION}, expected ${VERSION}" + exit 1 + fi + echo "Makefile in this job is now VERSION=${VERSION} (not committed to master yet)" + - name: Run tests run: | go test ./... -count=1 @@ -105,13 +114,18 @@ jobs: VERSION: ${{ steps.meta.outputs.version }} run: | set -euo pipefail + MAKEFILE_VERSION=$(sed -n 's/^VERSION ?=[[:space:]]*//p' Makefile | head -1) + if [ "${MAKEFILE_VERSION}" != "${VERSION}" ]; then + echo "Refusing to build: Makefile=${MAKEFILE_VERSION} release=${VERSION}" + exit 1 + fi LDFLAGS="-s -w -X dwatch/cmd.Version=${VERSION}" mkdir -p dist build() { local goos=$1 goarch=$2 local base="dwatch_${TAG}_${goos}_${goarch}" - echo "Building ${goos}/${goarch}..." + echo "Building ${goos}/${goarch} version ${VERSION}..." GOOS=$goos GOARCH=$goarch CGO_ENABLED=0 go build -ldflags "${LDFLAGS}" -o dwatch . tar -czf "dist/${base}.tar.gz" dwatch rm dwatch @@ -140,15 +154,14 @@ jobs: TAG: ${{ steps.meta.outputs.tag }} run: | set -euo pipefail - CURRENT=$(sed -n 's/^VERSION ?=[[:space:]]*//p' Makefile | head -1) - if [ "${CURRENT}" = "${VERSION}" ]; then - echo "Makefile already at ${VERSION}; no PR needed" + REMOTE_VERSION=$(git show origin/master:Makefile | sed -n 's/^VERSION ?=[[:space:]]*//p' | head -1) + if [ "${REMOTE_VERSION}" = "${VERSION}" ]; then + echo "master Makefile already at ${VERSION}" exit 0 fi BRANCH="chore/bump-version-${VERSION}" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git checkout -b "${BRANCH}" + git fetch origin master + git checkout -b "${BRANCH}" origin/master sed -i "s/^VERSION ?=.*/VERSION ?= ${VERSION}/" Makefile git add Makefile git commit -m "chore: bump VERSION to ${VERSION}" @@ -157,5 +170,5 @@ jobs: --base master \ --head "${BRANCH}" \ --title "chore: bump VERSION to ${VERSION}" \ - --body "Automated after ${TAG}. Merge to keep the Makefile in sync with the latest release." - gh pr merge --auto --squash || echo "Enable auto-merge on the PR or merge manually" + --body "Automated after ${TAG}. Merge so master Makefile matches the release." + gh pr merge --auto --squash || echo "Merge manually after CI passes" diff --git a/release.md b/release.md index 7304a7d..11417b5 100644 --- a/release.md +++ b/release.md @@ -1,39 +1,45 @@ # Releasing dwatch -Releases are cut from GitHub Actions on protected `master` — no local `git tag` or push required. +Releases are cut from GitHub Actions. Branch protection on `master` stays enabled. -Pre-built binaries for end users are listed on the [GitHub releases](https://github.com/silverbucket/dwatch/releases) page. +Pre-built binaries: [GitHub releases](https://github.com/silverbucket/dwatch/releases). ## One-time setup: `release` environment -In the repo on GitHub: **Settings** → **Environments** → **New environment** → name it `release`. +**Settings** → **Environments** → **New environment** → `release`. -Recommended for a protected `master` branch: +Recommended: -- **Required reviewers** — one or more people must approve before binaries are published -- **Deployment branches** — limit to `master` only (releases are built from `master` HEAD) +- **Required reviewers** before publish +- **Deployment branches** → `master` only -Ensure **Settings** → **Actions** → **General** → **Workflow permissions** is **Read and write**. +**Settings** → **Actions** → **General** → workflow permissions **Read and write**. ## Cut a release -1. **Actions** → **Release** → **Run workflow** — choose **patch** (default), **minor**, or **major** -2. CI reads the **latest published release** (e.g. `v1.3.0`), computes the next version (`1.3.1` for patch), runs tests, and publishes binaries -3. Approve the **release** environment deployment if reviewers are configured -4. Merge the automated **Makefile bump PR** when it appears (required for protected `master`; keeps `make build` in sync) +1. **Actions** → **Release** → **Run workflow** → **patch** (default), **minor**, or **major** +2. Approve the **release** environment if reviewers are configured +3. When the job finishes, merge the **Makefile bump PR** if one was opened (so `master` matches the release) -The workflow refuses to publish if that tag or GitHub Release already exists. +The workflow refuses to publish if that tag or release already exists. ## Version bumps -| Bump | Example (`v1.3.0` latest) | -|------|-----------------------------| -| **patch** (default) | `v1.3.1` | +| Bump | Example (latest `v1.3.0`) | +|------|---------------------------| +| **patch** | `v1.3.1` | | **minor** | `v1.4.0` | | **major** | `v2.0.0` | -Current version is taken from the latest GitHub release tag. If there are no releases yet, the workflow falls back to `VERSION` in the Makefile. +The starting point is the latest stable GitHub release tag (drafts and pre-releases excluded). If there are no releases yet, the Makefile `VERSION` on `master` is used. -## Protected `master` +## What the workflow does (order of operations) -The release job does **not** push directly to `master`. After a successful publish, it opens a pull request to update `VERSION` in the Makefile. Merge that PR so local `make build` matches the release. +1. **Compute** the new version from the latest release plus your bump choice. +2. **Edit the Makefile in the CI workspace** to that version (this happens **before** build; it does not commit to `master` yet). +3. **Run tests** on that tree. +4. **Build** binaries. The job checks that the Makefile matches the release version, then compiles with `-ldflags` so `dwatch --version` is correct. +5. **Publish** the GitHub Release and upload tarballs. +6. **Open a pull request** to update `VERSION` on `master`, because branch protection requires a PR for commits on `master`. + +Step 6 does not change what is inside the tarballs from step 4. It only brings `master` in line for anyone cloning the repo and running `make build` locally.