Files
openclaw/.github/workflows/promote-branch.yml
quotentiroler 6035bbcd2c feat(ci): implement staged branch promotion workflow
- Add testing-strategy.yml that calls existing ci.yml + adds macOS/smoke for stable
- Add promote-branch.yml for develop  alpha  beta  main promotion PRs
- Add deployment-strategy.yml for npm (alpha/beta/latest) + Docker (GHCR)
- Add release-orchestrator.yml to coordinate version  changelog  test  deploy
- Add version-operations.yml for YYYY.M.D versioning with prerelease suffixes
- Add generate-changelog.yml for conventional commit parsing
- Add release.yml manual trigger workflow
- Add discord-notify composite action for notifications
- Modify ci.yml to support workflow_call for reuse by testing-strategy
2026-02-07 09:02:48 -08:00

249 lines
8.6 KiB
YAML

name: Promote Branch
# Staged branch promotion for openclaw:
#
# develop → alpha → beta → main
#
# - External contributors: target `develop`
# - develop → alpha: auto-creates PR after core checks pass
# - alpha → beta: auto-creates PR after alpha tests pass (+ secrets scan)
# - beta → main: auto-creates PR after full tests pass (+ Windows)
#
# Merging to main triggers a release (handled separately by release workflow)
on:
push:
branches:
- develop
- alpha
- beta
workflow_dispatch:
inputs:
source_branch:
description: 'Source branch to promote from'
required: true
type: choice
options:
- develop
- alpha
- beta
skip_tests:
description: 'Skip tests (use with caution)'
required: false
type: boolean
default: false
permissions:
contents: read
pull-requests: write
jobs:
# Determine promotion target
determine-target:
name: Determine Target Branch
runs-on: ubuntu-latest
outputs:
source: ${{ steps.determine.outputs.source }}
target: ${{ steps.determine.outputs.target }}
test_stage: ${{ steps.determine.outputs.test_stage }}
should_promote: ${{ steps.determine.outputs.should_promote }}
steps:
- name: Determine promotion target
id: determine
run: |
# Get source branch
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
SOURCE="${{ inputs.source_branch }}"
else
SOURCE="${{ github.ref_name }}"
fi
echo "source=$SOURCE" >> $GITHUB_OUTPUT
case "$SOURCE" in
develop)
echo "target=alpha" >> $GITHUB_OUTPUT
echo "test_stage=develop" >> $GITHUB_OUTPUT
echo "should_promote=true" >> $GITHUB_OUTPUT
;;
alpha)
echo "target=beta" >> $GITHUB_OUTPUT
echo "test_stage=alpha" >> $GITHUB_OUTPUT
echo "should_promote=true" >> $GITHUB_OUTPUT
;;
beta)
echo "target=main" >> $GITHUB_OUTPUT
echo "test_stage=beta" >> $GITHUB_OUTPUT
echo "should_promote=true" >> $GITHUB_OUTPUT
;;
*)
echo "target=" >> $GITHUB_OUTPUT
echo "test_stage=" >> $GITHUB_OUTPUT
echo "should_promote=false" >> $GITHUB_OUTPUT
;;
esac
# Run stage-appropriate tests
run-tests:
name: Run Tests
needs: determine-target
if: ${{ needs.determine-target.outputs.should_promote == 'true' && !inputs.skip_tests }}
uses: ./.github/workflows/testing-strategy.yml
with:
test_stage: ${{ needs.determine-target.outputs.test_stage }}
app_version: ${{ github.sha }}
secrets: inherit
# Create promotion PR
create-promotion-pr:
name: Create Promotion PR
needs: [determine-target, run-tests]
if: |
always() &&
needs.determine-target.outputs.should_promote == 'true' &&
(needs.run-tests.outputs.test_status == 'passed' || inputs.skip_tests)
runs-on: ubuntu-latest
outputs:
pr_number: ${{ steps.create-pr.outputs.pull-request-number }}
pr_url: ${{ steps.create-pr.outputs.pull-request-url }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.determine-target.outputs.source }}
fetch-depth: 0
- name: Get commit info
id: commits
run: |
TARGET="${{ needs.determine-target.outputs.target }}"
# Fetch target branch
git fetch origin $TARGET 2>/dev/null || true
# Get commits not in target
if git rev-parse origin/$TARGET >/dev/null 2>&1; then
COMMIT_COUNT=$(git rev-list --count origin/$TARGET..HEAD 2>/dev/null || echo "0")
COMMIT_SUMMARY=$(git log origin/$TARGET..HEAD --oneline --format="- %s (%h)" 2>/dev/null | head -20 || echo "Initial promotion")
else
COMMIT_COUNT=$(git rev-list --count HEAD 2>/dev/null || echo "0")
COMMIT_SUMMARY=$(git log --oneline --format="- %s (%h)" 2>/dev/null | head -20 || echo "Initial promotion")
fi
echo "count=$COMMIT_COUNT" >> $GITHUB_OUTPUT
echo "summary<<EOF" >> $GITHUB_OUTPUT
echo "$COMMIT_SUMMARY" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Pull Request
id: create-pr
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: promote/${{ needs.determine-target.outputs.source }}-to-${{ needs.determine-target.outputs.target }}
base: ${{ needs.determine-target.outputs.target }}
title: '🚀 Promote: ${{ needs.determine-target.outputs.source }} → ${{ needs.determine-target.outputs.target }}'
body: |
## Staged Promotion
| Property | Value |
|----------|-------|
| Source | `${{ needs.determine-target.outputs.source }}` |
| Target | `${{ needs.determine-target.outputs.target }}` |
| Test Stage | `${{ needs.determine-target.outputs.test_stage }}` |
| Test Result | ${{ needs.run-tests.outputs.test_status == 'passed' && '✅ Passed' || (inputs.skip_tests && '⚠️ Skipped' || '❓ Unknown') }} |
### Changes (${{ steps.commits.outputs.count }} commits)
${{ steps.commits.outputs.summary }}
### Test Coverage by Stage
| Stage | Tests |
|-------|-------|
| develop | tsgo, lint, format, protocol, unit (Node + Bun) |
| alpha | + secrets scan |
| beta | + Windows tests |
| stable | + macOS tests, install smoke |
### Checklist
- [ ] Changes reviewed
- [ ] CI passing
- [ ] Ready to promote
---
*Auto-generated by the branch promotion workflow.*
labels: |
promotion
stage:${{ needs.determine-target.outputs.test_stage }}
draft: false
# Auto-merge for develop → alpha (fast-track)
auto-merge:
name: Auto-merge (develop → alpha)
needs: [determine-target, create-promotion-pr]
if: |
needs.determine-target.outputs.source == 'develop' &&
needs.create-promotion-pr.outputs.pr_number != ''
runs-on: ubuntu-latest
steps:
- name: Enable auto-merge
run: |
gh pr merge ${{ needs.create-promotion-pr.outputs.pr_number }} \
--auto \
--squash \
--repo ${{ github.repository }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Notify about promotion
notify-promotion:
name: Notify Promotion
needs: [determine-target, create-promotion-pr]
if: always() && needs.create-promotion-pr.outputs.pr_url != ''
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
if: env.DISCORD_WEBHOOK_URL != ''
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: '🔄 Promotion PR: ${{ needs.determine-target.outputs.source }} → ${{ needs.determine-target.outputs.target }}'
description: |
**PR**: ${{ needs.create-promotion-pr.outputs.pr_url }}
**Stage**: ${{ needs.determine-target.outputs.test_stage }}
color: '3447003'
# Handle failed tests
notify-failure:
name: Notify Test Failure
needs: [determine-target, run-tests]
if: |
always() &&
needs.run-tests.outputs.test_status != 'passed' &&
!inputs.skip_tests
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
if: env.DISCORD_WEBHOOK_URL != ''
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: '❌ Promotion Blocked: ${{ needs.determine-target.outputs.source }}'
description: |
**Target**: ${{ needs.determine-target.outputs.target }}
**Reason**: Tests failed
[View Logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
color: '15158332'