Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 9 additions & 47 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to deploy (optional)'
description: 'PR number to deploy (optional, for manual testing)'
required: false
type: string
repository_dispatch:
types: [data-update]

# Note: PR staging deployments are handled by staging-aggregate.yaml
# This workflow focuses on production deployments

jobs:
build:
name: Build
Expand Down Expand Up @@ -165,14 +165,9 @@ jobs:
# =======================
- name: Build site
run: |
if [ "$BRANCH" != 'refs/heads/master' ]; then
hugo --gc --minify --cleanDestinationDir --destination public --baseURL https://staging.forrt.org
else
hugo --gc --minify --cleanDestinationDir --destination public
fi
hugo --gc --minify --cleanDestinationDir --destination public
env:
BRANCH: ${{ github.ref }}
HUGO_ENV: ${{ github.ref != 'refs/heads/master' && 'staging' || 'production' }}
HUGO_ENV: production

# =======================
# Deployment Artifact
Expand All @@ -186,50 +181,17 @@ jobs:
path: public/
retention-days: 1

#========================================
# Deploy website to staging GitHub Pages
#========================================
deploy-test:
name: Deploy - Test
runs-on: ubuntu-22.04
concurrency:
group: staging
permissions:
contents: write
needs: build
steps:
#========================================
# Download website artifact for staging deployment
#========================================
- name: Download Artifact - Website
uses: actions/download-artifact@v4
with:
name: forrt-website-${{ github.run_number }}
path: ${{ github.repository }}/forrt-website

#========================================
# Deploy website to staging GitHub Pages
#========================================
- name: Deploy - GitHub Pages
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e
with:
personal_token: ${{ secrets.STAGING_GITHUB_TOKEN }}
publish_dir: ${{ github.repository }}/forrt-website
external_repository: forrtproject/webpage-staging
publish_branch: staging
cname: staging.forrt.org

#========================================
# Deploy website to production GitHub Pages
# Note: Staging deployments are handled by staging-aggregate.yaml
#========================================
deploy-prod:
name: Deploy - Production
runs-on: ubuntu-22.04
permissions:
contents: write
needs:
- deploy-test
if: ((github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master' || github.event_name == 'repository_dispatch') && github.event.repository.fork == false
needs: build
if: github.event.repository.fork == false
steps:
#========================================
# Download website artifact for production deployment
Expand Down
73 changes: 57 additions & 16 deletions .github/workflows/staging-aggregate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
pull_request:
branches:
- master
types: [opened, synchronize, reopened, ready_for_review]
schedule:
- cron: '0 1 1 * *' # 1 AM UTC on the 1st of each month
workflow_dispatch:
Expand All @@ -20,11 +21,24 @@ on:
required: false
type: boolean
default: false
single_pr:
description: 'Deploy a single PR only (enter PR number, e.g. 123)'
required: false
type: string
default: ''

# Workflow-level concurrency to queue staging builds (wait instead of cancel)
concurrency:
group: staging-aggregate
cancel-in-progress: false

jobs:
aggregate-prs:
name: Aggregate PRs for Staging
runs-on: ubuntu-22.04
timeout-minutes: 10 # Prevent blocking the queue
# Skip draft PRs entirely - they should not trigger staging builds
if: github.event.pull_request.draft != true
permissions:
contents: write
pull-requests: read
Expand Down Expand Up @@ -55,23 +69,44 @@ jobs:
echo "🔍 Triggering PR: #${{ github.event.pull_request.number }}"
fi

# Get all open, non-draft PRs targeting master (regardless of CI status)
PRS=$(gh pr list --base master --state open --json number,title,headRefName,isDraft --jq '.[] | select(.isDraft == false) | .number')

if [ -z "$PRS" ]; then
echo "No open non-draft PRs found"
echo "branch=master" >> $GITHUB_OUTPUT
echo "included_prs=" >> $GITHUB_OUTPUT
echo "attempted_prs=" >> $GITHUB_OUTPUT
echo "total_prs=0" >> $GITHUB_OUTPUT
echo "has_prs=false" >> $GITHUB_OUTPUT
exit 0
# Check if single PR mode is enabled
SINGLE_PR="${{ github.event.inputs.single_pr }}"
if [ -n "$SINGLE_PR" ] && [ "$SINGLE_PR" != "" ]; then
echo "🎯 Single PR mode: deploying only PR #$SINGLE_PR"

# Validate PR exists and is open
if ! gh pr view "$SINGLE_PR" --json state --jq '.state' | grep -q "OPEN"; then
echo "❌ PR #$SINGLE_PR is not open or doesn't exist"
echo "branch=master" >> $GITHUB_OUTPUT
echo "included_prs=" >> $GITHUB_OUTPUT
echo "attempted_prs=$SINGLE_PR" >> $GITHUB_OUTPUT
echo "total_prs=0" >> $GITHUB_OUTPUT
echo "has_prs=false" >> $GITHUB_OUTPUT
exit 1
fi

PRS="$SINGLE_PR"
echo "has_prs=true" >> $GITHUB_OUTPUT
echo "total_prs=1" >> $GITHUB_OUTPUT
else
# Get all open, non-draft PRs targeting master (regardless of CI status)
PRS=$(gh pr list --base master --state open --json number,title,headRefName,isDraft --jq '.[] | select(.isDraft == false) | .number')

if [ -z "$PRS" ]; then
echo "No open non-draft PRs found"
echo "branch=master" >> $GITHUB_OUTPUT
echo "included_prs=" >> $GITHUB_OUTPUT
echo "attempted_prs=" >> $GITHUB_OUTPUT
echo "total_prs=0" >> $GITHUB_OUTPUT
echo "has_prs=false" >> $GITHUB_OUTPUT
exit 0
fi

echo "Found PRs: $PRS"
echo "total_prs=$(echo "$PRS" | wc -l)" >> $GITHUB_OUTPUT
echo "has_prs=true" >> $GITHUB_OUTPUT
fi

echo "Found PRs: $PRS"
echo "total_prs=$(echo "$PRS" | wc -l)" >> $GITHUB_OUTPUT
echo "has_prs=true" >> $GITHUB_OUTPUT

# Create a new branch for aggregation starting from master
AGGREGATE_BRANCH="staging-aggregate-$(date +%Y%m%d-%H%M%S)"
git checkout master
Expand Down Expand Up @@ -160,6 +195,7 @@ jobs:

echo "✅ Aggregation completed"
echo "📊 Summary:"
echo " - Mode: $( [ -n \"$SINGLE_PR\" ] && echo 'Single PR' || echo 'Aggregate' )"
echo " - Total PRs found: $(echo "$PRS" | wc -l)"
echo " - PRs attempted: $ATTEMPTED_PRS"
echo " - PRs successfully merged: $INCLUDED_PRS"
Expand All @@ -173,6 +209,7 @@ jobs:
build:
name: Build
runs-on: ubuntu-22.04
timeout-minutes: 20 # Prevent blocking the queue
needs: [aggregate-prs]
if: always() && (needs.aggregate-prs.outputs.has-prs == 'true' || github.event.inputs.force_deploy == 'true')
permissions:
Expand Down Expand Up @@ -346,21 +383,24 @@ jobs:
deploy-staging:
name: Deploy - Staging
runs-on: ubuntu-22.04
timeout-minutes: 10 # Prevent blocking the queue
concurrency:
group: staging
permissions:
contents: write
pull-requests: write
issues: write
needs: [build, aggregate-prs]
if: always() && needs.build.result == 'success'
# Run on successful build OR on schedule (for monthly reports even when no PRs)
if: always() && (needs.build.result == 'success' || github.event_name == 'schedule')
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.STAGING_GITHUB_TOKEN }}

- name: Download Artifact - Website
if: needs.build.result == 'success'
uses: actions/download-artifact@v4
with:
name: forrt-website-aggregate-${{ github.run_number }}
Expand Down Expand Up @@ -416,6 +456,7 @@ jobs:
echo "- ✅ Shows combined state of all compatible PRs" >> deployment-summary.md

- name: Deploy - GitHub Pages
if: needs.build.result == 'success'
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e
with:
personal_token: ${{ secrets.STAGING_GITHUB_TOKEN }}
Expand Down
64 changes: 63 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,66 @@ To edit it locally, you will then need to:
8. Then you can push this branch to GitHub.
9. Create a pull request to the original FORRT repo.

Please note that RStudio is not designed for website development, so you may find it easier to use the Dev Containers method described above.
Please note that RStudio is not designed for website development, so you may find it easier to use the Dev Containers method described above.

## Deployment & Staging

The FORRT website uses a dual deployment strategy to ensure quality and enable collaborative testing:

### Production Deployment

- **URL**: [https://forrt.org](https://forrt.org)
- **Workflow**: `.github/workflows/deploy.yaml`
- **Trigger**: Pushes to `master` branch
- **Target**: GitHub Pages (`gh-pages` branch)
- **Purpose**: Serves the live, production website

### Staging Deployment

- **URL**: [https://staging.forrt.org](https://staging.forrt.org)
- **Workflow**: `.github/workflows/staging-aggregate.yaml`
- **Trigger**: Pull requests to `master`, monthly schedule (1st of each month), or manual dispatch
- **Target**: External repository (`forrtproject/webpage-staging`)
- **Purpose**: Preview combined changes from all open PRs

#### How Staging Works

The staging deployment uses an **aggregation strategy** to provide a unified preview environment:

1. **Automatic Aggregation**: When any PR is opened, synchronized, or reopened, the workflow:
- Collects all open, non-draft PRs targeting `master`
- Creates a temporary aggregation branch from `master`
- Attempts to merge each PR in sequence

2. **Conflict Handling**:
- PRs that merge cleanly are included in the staging build
- PRs with merge conflicts are skipped but logged
- The deployment continues with all compatible PRs

3. **Deployment Comments**: Each PR receives an automated comment indicating:
- ✅ Successfully deployed (for PRs without conflicts)
- ⚠️ Skipped due to conflicts (for conflicting PRs)
- Deployment timestamp and staging URL

4. **Queuing & Timeouts**:
- Workflow uses concurrency control to queue builds (not cancel)
- Job timeouts (10-20 minutes) prevent indefinite blocking
- Draft PRs are filtered out to avoid unnecessary builds

5. **Branch Cleanup**:
- Keeps only the 5 most recent staging branches
- Automatically cleans up older staging-aggregate branches

#### Viewing Staging Changes

- Visit [https://staging.forrt.org](https://staging.forrt.org) to see the combined state of all open, compatible PRs
- Note: Staging shows aggregated changes from **all** open PRs, not individual PR changes
- PRs with merge conflicts won't appear in staging until conflicts are resolved

#### Monthly Reports

On the 1st of each month, an automated deployment report is created as a GitHub issue with:
- Total PRs processed
- Successfully merged PRs
- Skipped PRs (with conflict information)
- Deployment statistics