From a90e0c83259d23f52ba8c63a5f965d7c84fc8fa7 Mon Sep 17 00:00:00 2001 From: richarddushime Date: Wed, 4 Feb 2026 20:48:37 +0100 Subject: [PATCH 1/6] consolidate staging deployment --- .github/workflows/deploy.yaml | 56 ++++-------------------- .github/workflows/staging-aggregate.yaml | 11 +++++ 2 files changed, 20 insertions(+), 47 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index ad8fd73455c..c6c3be878a7 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -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 @@ -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 @@ -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 diff --git a/.github/workflows/staging-aggregate.yaml b/.github/workflows/staging-aggregate.yaml index 4d337592880..cf0c13594f2 100644 --- a/.github/workflows/staging-aggregate.yaml +++ b/.github/workflows/staging-aggregate.yaml @@ -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: @@ -21,10 +22,18 @@ on: type: boolean default: false +# 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 @@ -173,6 +182,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: @@ -346,6 +356,7 @@ jobs: deploy-staging: name: Deploy - Staging runs-on: ubuntu-22.04 + timeout-minutes: 10 # Prevent blocking the queue concurrency: group: staging permissions: From 05cdb54fa55a60979f246bc9b9a869156c4ae2c3 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:10:50 +0000 Subject: [PATCH 2/6] docs: Document staging deployment aggregation strategy (#621) * Initial plan * docs: Document staging deployment approach in README Co-authored-by: LukasWallrich <60155545+LukasWallrich@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: LukasWallrich <60155545+LukasWallrich@users.noreply.github.com> --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/README.md b/README.md index d5137e66357..22e64129e86 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,68 @@ Please note that it's very important to us that we maintain a positive and suppo This is the website for the **Framework for Open and Reproducible Research Training (FORRT)**, built with [hugo](https://gohugo.io/), and deployed with [Github Actions](https://docs.github.com/en/actions). You can find the website at [forrt.org](https://forrt.org/). See CONTRIBUTING.md for more information on how to contribute. +## 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 + ## License Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. From 019b60adf5e61081da16ebe74e08dc13c7070e7e Mon Sep 17 00:00:00 2001 From: richarddushime Date: Thu, 5 Feb 2026 18:04:57 +0100 Subject: [PATCH 3/6] Added deployment information as a section in contributing --- CONTRIBUTING.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++- README.md | 62 ----------------------------------------------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f2cad98942..a9894e32e0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. \ No newline at end of file +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 diff --git a/README.md b/README.md index 22e64129e86..d5137e66357 100644 --- a/README.md +++ b/README.md @@ -54,68 +54,6 @@ Please note that it's very important to us that we maintain a positive and suppo This is the website for the **Framework for Open and Reproducible Research Training (FORRT)**, built with [hugo](https://gohugo.io/), and deployed with [Github Actions](https://docs.github.com/en/actions). You can find the website at [forrt.org](https://forrt.org/). See CONTRIBUTING.md for more information on how to contribute. -## 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 - ## License Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. From 3c55c1315197c4b49e8dd325d6d549bde7d10f88 Mon Sep 17 00:00:00 2001 From: richarddushime Date: Thu, 5 Feb 2026 18:07:56 +0100 Subject: [PATCH 4/6] minor fix: monthly report sumary as an issue --- .github/workflows/staging-aggregate.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/staging-aggregate.yaml b/.github/workflows/staging-aggregate.yaml index cf0c13594f2..ad65c2736dc 100644 --- a/.github/workflows/staging-aggregate.yaml +++ b/.github/workflows/staging-aggregate.yaml @@ -364,7 +364,8 @@ jobs: 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 @@ -372,6 +373,7 @@ jobs: 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 }} @@ -427,6 +429,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 }} From e4046d331c3b4c0e33d4d2976cdbca25aea20800 Mon Sep 17 00:00:00 2001 From: richarddushime Date: Fri, 6 Feb 2026 14:39:40 +0100 Subject: [PATCH 5/6] single PR staging deployment --- .github/workflows/staging-aggregate.yaml | 57 +++++++++++++++++------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/.github/workflows/staging-aggregate.yaml b/.github/workflows/staging-aggregate.yaml index ad65c2736dc..fbb289d3423 100644 --- a/.github/workflows/staging-aggregate.yaml +++ b/.github/workflows/staging-aggregate.yaml @@ -21,6 +21,11 @@ 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: @@ -64,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 @@ -169,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" From 50f375274bd78e62321217d7528a8103c56ae697 Mon Sep 17 00:00:00 2001 From: Lukas Wallrich Date: Tue, 10 Feb 2026 14:34:28 +0000 Subject: [PATCH 6/6] Remove PR number input from deploy workflow Removed optional input for PR number from workflow dispatch. --- .github/workflows/deploy.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index c6c3be878a7..62fe8b4668c 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -12,11 +12,6 @@ on: branches: - master workflow_dispatch: - inputs: - pr_number: - description: 'PR number to deploy (optional, for manual testing)' - required: false - type: string repository_dispatch: types: [data-update]