Promote Main to Release Branches #148
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |