From 25228475c731510f6b411850eeef88d28897878b Mon Sep 17 00:00:00 2001 From: Patrick Burns Date: Mon, 1 Jun 2026 15:50:03 -0500 Subject: [PATCH] ci: release pipeline for dev / main flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Splits the release pipeline so dev is the integration target and main is the production candidate. Every push to either branch publishes to Play internal so testers always have the latest; production promotion remains a manual step in Play Console. Changes to build-release.yml: - Push triggers: drop the legacy capital-D "Dev"; add lowercase "dev". - PR triggers: now fire for PRs to either main or dev (was main only) so feature → dev PRs get unit tests. - Tag determination grows a dev branch lane: push to dev produces a run-numbered "dev-" tag, marks the GitHub release as prerelease, and uploads to Play internal alongside the main-branch v* releases. Distinct tag prefix keeps the two streams visually separate in both the GitHub releases list and the Play Console releaseName column. - softprops/action-gh-release now honors the is_prerelease output so dev-* releases sort under the latest v* and don't shadow "latest". New main-gate.yml workflow: - A required PR status check that enforces PRs to main must come from the dev branch. Failing fast on any other source means the release discipline (feature → dev → main) is mechanically enforced rather than left to convention. Once this lands on main, the repo owner enables branch protection in GitHub Settings → Branches: - main: require PR, require status checks (test, build, enforce-source-is-dev), restrict who can push to the repo owner only. - dev: require PR, require status checks (test, build). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-release.yml | 34 ++++++++++++++++++++++++----- .github/workflows/main-gate.yml | 31 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/main-gate.yml diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index f9fa0d1e..68b89a56 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -2,11 +2,11 @@ name: Build & Release APK on: push: - branches: [main, Dev] + branches: [main, dev] tags: - 'v*' pull_request: - branches: [main] + branches: [main, dev] permissions: contents: write @@ -56,7 +56,7 @@ jobs: build: name: Build APK needs: test - # Run when tests pass (PR), or when tests were skipped (push to main/Dev/tags). + # Run when tests pass (PR), or when tests were skipped (push to main/dev/tags). # Block only on actual test failure or cancellation. if: ${{ always() && needs.test.result != 'failure' && needs.test.result != 'cancelled' }} runs-on: ubuntu-latest @@ -74,12 +74,19 @@ jobs: id: tag run: | set -euo pipefail + # Three release lanes (everything else is build-only): + # v* tag push -> production-named release, NOT prerelease + # push to main -> auto-bumped v* tag, NOT prerelease + # push to dev -> dev- tag, PRERELEASE + # Prereleases are still uploaded to Play internal but with a + # distinct releaseName so they're easy to tell apart from the + # v*-named candidates we'd actually promote to production. if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then - # Manual tag push — release as that tag echo "release_tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "is_prerelease=false" >> "$GITHUB_OUTPUT" elif [[ "${GITHUB_EVENT_NAME}" == "push" && "${GITHUB_REF}" == "refs/heads/main" ]]; then - # Push to main — auto-bump patch from latest v* tag + # Auto-bump patch from latest v* tag. latest=$(git tag --list 'v*' --sort=-v:refname | head -n1) if [[ -z "$latest" ]]; then new_tag="v0.1" @@ -93,10 +100,21 @@ jobs: echo "Latest tag: ${latest:-} -> new tag: $new_tag" echo "release_tag=$new_tag" >> "$GITHUB_OUTPUT" echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "is_prerelease=false" >> "$GITHUB_OUTPUT" + elif [[ "${GITHUB_EVENT_NAME}" == "push" && "${GITHUB_REF}" == "refs/heads/dev" ]]; then + # dev pushes get a run-numbered tag and are flagged as prereleases. + # Distinct prefix so they sort separately from v* in GitHub releases + # and so the Play Console releaseName disambiguates them from + # main-branch release candidates. + new_tag="dev-${GITHUB_RUN_NUMBER}" + echo "release_tag=$new_tag" >> "$GITHUB_OUTPUT" + echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "is_prerelease=true" >> "$GITHUB_OUTPUT" else - # PR or Dev push — build only, no release + # PR or other branch push — build only, no release echo "release_tag=" >> "$GITHUB_OUTPUT" echo "should_release=false" >> "$GITHUB_OUTPUT" + echo "is_prerelease=false" >> "$GITHUB_OUTPUT" fi - name: Set up JDK 17 @@ -186,6 +204,10 @@ jobs: tag_name: ${{ steps.tag.outputs.release_tag }} files: ft8cn/app/build/outputs/apk/release/FT8CN-*.apk generate_release_notes: true + # dev-* releases are flagged as prerelease so they sort under the + # latest v* and don't get picked up by downstream tooling that + # looks for "latest release". + prerelease: ${{ steps.tag.outputs.is_prerelease == 'true' }} append_body: true body: | diff --git a/.github/workflows/main-gate.yml b/.github/workflows/main-gate.yml new file mode 100644 index 00000000..8e8f6621 --- /dev/null +++ b/.github/workflows/main-gate.yml @@ -0,0 +1,31 @@ +name: Main branch source gate + +# Enforces release discipline: PRs targeting main must come from `dev`. +# Feature branches are expected to land on dev first; only dev → main is +# allowed as the "promote to production" step, gated by branch protection +# requiring this status check. +# +# To enforce it: GitHub → Settings → Branches → Branch protection rule for +# `main` → Require status checks to pass → add "Main branch source gate / +# enforce-source-is-dev" to the required list. + +on: + pull_request: + branches: [main] + +jobs: + enforce-source-is-dev: + name: enforce-source-is-dev + runs-on: ubuntu-latest + steps: + - name: Check PR head branch + env: + HEAD_REF: ${{ github.head_ref }} + run: | + set -euo pipefail + if [[ "${HEAD_REF}" != "dev" ]]; then + echo "::error title=PR source must be dev::PRs to main are only allowed from the dev branch. Source branch: ${HEAD_REF}" + echo "Land your changes on dev first (PR from your feature branch into dev), then open dev → main." + exit 1 + fi + echo "OK — PR source is dev."