Skip to content

Promote Main to Release Branches #148

Promote Main to Release Branches

Promote Main to Release Branches #148

Workflow file for this run

name: Promote Main to Release Branches
permissions:
contents: write
on:
workflow_dispatch:
inputs:
target:
description: "Promote target release branch selection"
required: true
default: "all"
type: choice
options:
- all
- web
- university
force_redeploy:
description: "When up to date, skip divergence and print manual redeploy guidance"
required: true
default: false
type: boolean
jobs:
resolve_targets:
name: Resolve release targets
runs-on: ubuntu-latest
outputs:
targets: ${{ steps.resolve.outputs.targets }}
steps:
- name: Resolve selected targets
id: resolve
run: |
set -euo pipefail
TARGET="${{ github.event.inputs.target }}"
case "$TARGET" in
web)
TARGETS='["web"]'
;;
university)
TARGETS='["university"]'
;;
all)
TARGETS='["web","university"]'
;;
*)
echo "Unsupported target: $TARGET" >&2
exit 1
;;
esac
echo "targets=$TARGETS" >> "$GITHUB_OUTPUT"
release_target:
name: Release ${{ matrix.target }}
runs-on: ubuntu-latest
needs: resolve_targets
strategy:
fail-fast: false
matrix:
target: ${{ fromJson(needs.resolve_targets.outputs.targets) }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve app release config
id: app
env:
CREATE_TAG: "false"
TARGET: ${{ matrix.target }}
run: bash .github/scripts/create-headver-tag.sh
- name: Detect target changes
id: changes
env:
FORCE_REDEPLOY: ${{ github.event.inputs.force_redeploy }}
PATH_REGEX: ${{ steps.app.outputs.path_regex }}
RELEASE_BRANCH: ${{ steps.app.outputs.release_branch }}
TARGET: ${{ matrix.target }}
run: |
set -euo pipefail
git fetch origin main
MAIN_SHA=$(git rev-parse origin/main)
if git fetch origin "$RELEASE_BRANCH"; then
RELEASE_SHA=$(git rev-parse "origin/$RELEASE_BRANCH")
else
RELEASE_SHA=""
fi
{
echo "## Release target: $TARGET"
echo "- Release branch: $RELEASE_BRANCH"
echo "- Main SHA: $MAIN_SHA"
} >> "$GITHUB_STEP_SUMMARY"
if [ -n "$RELEASE_SHA" ]; then
echo "- Current release SHA: $RELEASE_SHA" >> "$GITHUB_STEP_SUMMARY"
fi
echo "main_sha=$MAIN_SHA" >> "$GITHUB_OUTPUT"
echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT"
if [ -z "$RELEASE_SHA" ]; then
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "- Decision: release branch does not exist; creating it from main" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
if [ "$MAIN_SHA" = "$RELEASE_SHA" ]; then
echo "should_release=false" >> "$GITHUB_OUTPUT"
{
echo "- Decision: already up to date"
if [ "$FORCE_REDEPLOY" = "true" ]; then
echo "- force_redeploy=true requested"
echo "- Skipped empty commit to keep release branch ancestry clean"
echo "- Trigger redeploy manually in Vercel if needed"
fi
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
CHANGED_FILES=$(git diff --name-only "origin/$RELEASE_BRANCH" origin/main)
RELEVANT_FILES=$(echo "$CHANGED_FILES" | grep -E "$PATH_REGEX" || true)
if [ -z "$RELEVANT_FILES" ]; then
echo "should_release=false" >> "$GITHUB_OUTPUT"
{
echo "- Decision: skipped; no $TARGET or shared changes"
echo "- Changed files were outside this target"
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
echo "should_release=true" >> "$GITHUB_OUTPUT"
{
echo "- Decision: release required"
echo "- Relevant changed files:"
echo "$RELEVANT_FILES" | sed 's/^/ - /'
} >> "$GITHUB_STEP_SUMMARY"
- name: Create app HeadVer tag
id: create_tag
if: steps.changes.outputs.should_release == 'true'
env:
TARGET: ${{ matrix.target }}
run: bash .github/scripts/create-headver-tag.sh
- name: Create GitHub Release
if: steps.changes.outputs.should_release == 'true'
uses: ncipollo/release-action@v1
with:
tag: ${{ steps.create_tag.outputs.tag }}
release_name: "${{ steps.create_tag.outputs.display_name }} Release ${{ steps.create_tag.outputs.tag }}"
body: |
Automated release created for ${{ steps.create_tag.outputs.display_name }}.
- Target: ${{ matrix.target }}
- Tag: ${{ steps.create_tag.outputs.tag }}
- Version: ${{ steps.create_tag.outputs.version }}
- Release branch: ${{ steps.app.outputs.release_branch }}
- Promoted main SHA: ${{ steps.changes.outputs.main_sha }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Promote main branch to target release branch
if: steps.changes.outputs.should_release == 'true'
env:
RELEASE_BRANCH: ${{ steps.app.outputs.release_branch }}
RELEASE_SHA: ${{ steps.changes.outputs.release_sha }}
run: |
set -euo pipefail
if [ -z "$RELEASE_SHA" ]; then
git push origin origin/main:"refs/heads/$RELEASE_BRANCH"
echo "- $RELEASE_BRANCH: created from main" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
if ! git merge-base --is-ancestor "origin/$RELEASE_BRANCH" origin/main; then
echo "- $RELEASE_BRANCH: non-ancestor detected, forcing reset to main" >> "$GITHUB_STEP_SUMMARY"
fi
git push --force-with-lease origin origin/main:"refs/heads/$RELEASE_BRANCH"
echo "- $RELEASE_BRANCH: updated" >> "$GITHUB_STEP_SUMMARY"
- name: Deployment note
if: steps.changes.outputs.should_release == 'true'
run: |
echo "- Note: Vercel production deploy is triggered by corresponding release branch update" >> "$GITHUB_STEP_SUMMARY"