feat: auto-cascade main → dev workflow #9

Merged
jmiller merged 1 commits from feat/cascade-dev-workflow into main 2026-05-05 20:38:58 +00:00
2 changed files with 153 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# 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
+1
View File
@@ -18,6 +18,7 @@ on:
- "Joomla Build & Release"
- "Joomla Extension CI"
- "Deploy"
- "Cascade Main → Dev"
types:
- completed