From 515290798ffd1f3ca7491ad99f80be0a028aa18d Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 5 May 2026 15:38:23 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20auto-cascade=20main=20=E2=86=92=20dev?= =?UTF-8?q?=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds cascade-dev.yml that forward-merges main into dev after every push. Uses Gitea API merge endpoint — no repo checkout needed. On conflict, auto-creates a PR for manual resolution with duplicate prevention. Adds cascade workflow to ntfy notifications. Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitea/workflows/cascade-dev.yml | 152 +++++++++++++++++++++++++++++++ .gitea/workflows/notify.yml | 1 + 2 files changed, 153 insertions(+) create mode 100644 .gitea/workflows/cascade-dev.yml diff --git a/.gitea/workflows/cascade-dev.yml b/.gitea/workflows/cascade-dev.yml new file mode 100644 index 0000000..151c6a7 --- /dev/null +++ b/.gitea/workflows/cascade-dev.yml @@ -0,0 +1,152 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: MokoStandards.Maintenance +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API +# PATH: /templates/workflows/cascade-dev.yml.template +# VERSION: 01.00.00 +# BRIEF: Forward-merge main → dev after every push to main +# +# +========================================================================+ +# | CASCADE MAIN → DEV | +# +========================================================================+ +# | | +# | Triggers on every push to main (PR merges, bot commits, etc.) | +# | | +# | 1. Check if a 'dev' branch exists | +# | 2. Attempt API merge (main → dev) | +# | 3. On conflict: create a PR for manual resolution | +# | | +# +========================================================================+ + +name: Cascade Main → Dev + +on: + push: + branches: + - main + workflow_dispatch: + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} + GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} + +permissions: + contents: write + pull-requests: write + +jobs: + cascade: + name: Merge main → dev + runs-on: ubuntu-latest + if: >- + !contains(github.event.head_commit.message, '[skip ci]') && + !contains(github.event.head_commit.message, '[skip cascade]') + + steps: + - name: Check dev branch exists + id: check + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + run: | + API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + + STATUS=$(curl -sS -o /dev/null -w "%{http_code}" \ + -H "Authorization: token ${GA_TOKEN}" \ + "${API}/branches/dev") + + if [ "$STATUS" = "200" ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + echo "✅ dev branch exists" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "ℹ️ No dev branch found (HTTP ${STATUS}) — skipping cascade" + fi + + - name: Merge main → dev via API + if: steps.check.outputs.exists == 'true' + id: merge + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + run: | + API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + COMMIT_MSG="chore: cascade main → dev [skip ci]" + + RESPONSE=$(curl -sS -w "\n%{http_code}" \ + -X POST \ + -H "Authorization: token ${GA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"base\": \"dev\", + \"head\": \"main\", + \"merge_message_field\": \"${COMMIT_MSG}\" + }" \ + "${API}/merges") + + HTTP_CODE=$(echo "$RESPONSE" | tail -1) + BODY=$(echo "$RESPONSE" | sed '$d') + + echo "HTTP ${HTTP_CODE}" + + if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then + echo "result=success" >> "$GITHUB_OUTPUT" + echo "✅ main merged into dev successfully" + elif [ "$HTTP_CODE" = "204" ]; then + echo "result=noop" >> "$GITHUB_OUTPUT" + echo "✅ dev is already up to date with main" + elif [ "$HTTP_CODE" = "409" ]; then + echo "result=conflict" >> "$GITHUB_OUTPUT" + echo "⚠️ Merge conflict detected — will create PR" + else + echo "result=error" >> "$GITHUB_OUTPUT" + echo "❌ Unexpected response: ${BODY}" + exit 1 + fi + + - name: Create conflict-resolution PR + if: steps.merge.outputs.result == 'conflict' + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + run: | + API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + SHORT_SHA="${GITHUB_SHA:0:7}" + + # Check if a cascade PR already exists + EXISTING=$(curl -sS \ + -H "Authorization: token ${GA_TOKEN}" \ + "${API}/pulls?state=open&labels=cascade&head=${GITEA_ORG}:main&base=dev&limit=1" \ + | jq 'length') + + if [ "$EXISTING" -gt 0 ]; then + echo "ℹ️ Cascade PR already exists — skipping duplicate" + exit 0 + fi + + # Create the PR + RESPONSE=$(curl -sS -w "\n%{http_code}" \ + -X POST \ + -H "Authorization: token ${GA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"title\": \"chore: cascade main → dev (conflicts)\", + \"body\": \"## Automatic cascade\\n\\nMerging \`main\` (${SHORT_SHA}) into \`dev\` hit merge conflicts.\\n\\nPlease resolve conflicts and merge this PR to keep \`dev\` in sync with \`main\`.\\n\\n> Auto-created by the **Cascade Main → Dev** workflow.\", + \"head\": \"main\", + \"base\": \"dev\" + }" \ + "${API}/pulls") + + HTTP_CODE=$(echo "$RESPONSE" | tail -1) + BODY=$(echo "$RESPONSE" | sed '$d') + PR_URL=$(echo "$BODY" | jq -r '.html_url // empty') + + if [ "$HTTP_CODE" = "201" ] && [ -n "$PR_URL" ]; then + echo "✅ Created conflict-resolution PR: ${PR_URL}" + else + echo "❌ Failed to create PR (HTTP ${HTTP_CODE}): ${BODY}" + exit 1 + fi diff --git a/.gitea/workflows/notify.yml b/.gitea/workflows/notify.yml index 4413a05..8cc8382 100644 --- a/.gitea/workflows/notify.yml +++ b/.gitea/workflows/notify.yml @@ -18,6 +18,7 @@ on: - "Joomla Build & Release" - "Joomla Extension CI" - "Deploy" + - "Cascade Main → Dev" types: - completed -- 2.52.0