|
| 1 | +# Build Docs |
| 2 | +# |
| 3 | +# Dual-purpose workflow: |
| 4 | +# - On PRs: Tests that MkDocs and spec-docs build successfully |
| 5 | +# - On push to allowlisted branches: Builds, uploads artifacts, and triggers |
| 6 | +# the aggregator workflow in steel-website for deployment |
| 7 | +# |
| 8 | +# Site structure at steel.ethereum.foundation/docs/: |
| 9 | +# /docs/ - Default branch docs (mirrored) |
| 10 | +# /docs/spec/ - Default branch spec (mirrored) |
| 11 | +# /docs/<branch>/ - Branch-specific docs |
| 12 | +# /docs/<branch>/spec - Branch-specific spec |
| 13 | + |
| 14 | +name: Build Docs |
| 15 | + |
| 16 | +on: |
| 17 | + push: |
| 18 | + branches: |
| 19 | + - mainnet |
| 20 | + - 'forks/**' |
| 21 | + - 'devnets/**' |
| 22 | + - 'eips/**' |
| 23 | + paths: &docs_paths |
| 24 | + - 'src/ethereum/**' |
| 25 | + - 'tests/**' |
| 26 | + - 'docs/**' |
| 27 | + - 'packages/testing/**' |
| 28 | + - 'static/**' |
| 29 | + - 'mkdocs.yml' |
| 30 | + - 'uv.lock' |
| 31 | + - 'pyproject.toml' |
| 32 | + - '.github/workflows/docs-build.yaml' |
| 33 | + - '.github/configs/docs-branches.yaml' |
| 34 | + |
| 35 | + pull_request: |
| 36 | + paths: *docs_paths |
| 37 | + |
| 38 | + workflow_dispatch: |
| 39 | + inputs: |
| 40 | + branch: |
| 41 | + description: 'Branch to build (must be allowlisted)' |
| 42 | + required: false |
| 43 | + type: string |
| 44 | + |
| 45 | +concurrency: |
| 46 | + group: docs-producer-${{ github.ref }} |
| 47 | + cancel-in-progress: true |
| 48 | + |
| 49 | +permissions: |
| 50 | + contents: read |
| 51 | + |
| 52 | +jobs: |
| 53 | + # Check if the branch is in the allowlist |
| 54 | + check-allowlist: |
| 55 | + name: "Check Branch Allowlist" |
| 56 | + runs-on: ubuntu-latest |
| 57 | + outputs: |
| 58 | + allowed: ${{ steps.check.outputs.allowed }} |
| 59 | + branch: ${{ steps.check.outputs.branch }} |
| 60 | + branch_slug_safe: ${{ steps.check.outputs.branch_slug_safe }} |
| 61 | + is_deploy: ${{ steps.check.outputs.is_deploy }} |
| 62 | + steps: |
| 63 | + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 |
| 64 | + with: |
| 65 | + sparse-checkout: .github/configs/docs-branches.yaml |
| 66 | + sparse-checkout-cone-mode: false |
| 67 | + |
| 68 | + - name: Check branch allowlist |
| 69 | + id: check |
| 70 | + run: | |
| 71 | + BRANCH="${{ github.event.inputs.branch || github.ref_name }}" |
| 72 | + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" |
| 73 | +
|
| 74 | + # Create safe slug for artifact naming (replace / with -) |
| 75 | + BRANCH_SLUG_SAFE="${BRANCH//\//-}" |
| 76 | + echo "branch_slug_safe=$BRANCH_SLUG_SAFE" >> "$GITHUB_OUTPUT" |
| 77 | +
|
| 78 | + # Determine if this is a deploy run (push to allowlisted branch, not PR) |
| 79 | + IS_DEPLOY="false" |
| 80 | + if [ "${{ github.event_name }}" = "push" ] || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then |
| 81 | + IS_DEPLOY="true" |
| 82 | + fi |
| 83 | + echo "is_deploy=$IS_DEPLOY" >> "$GITHUB_OUTPUT" |
| 84 | +
|
| 85 | + # Extract branch paths from config |
| 86 | + ALLOWLISTED_BRANCHES=$(yq '.branches[].path' .github/configs/docs-branches.yaml 2>/dev/null || echo "") |
| 87 | +
|
| 88 | + if [ -z "$ALLOWLISTED_BRANCHES" ]; then |
| 89 | + echo "ERROR: Could not read allowlist from .github/configs/docs-branches.yaml" |
| 90 | + echo "allowed=false" >> "$GITHUB_OUTPUT" |
| 91 | + exit 0 |
| 92 | + fi |
| 93 | +
|
| 94 | + # Check if branch is in allowlist |
| 95 | + if echo "$ALLOWLISTED_BRANCHES" | grep -qxF "$BRANCH"; then |
| 96 | + echo "allowed=true" >> "$GITHUB_OUTPUT" |
| 97 | + echo "Branch '$BRANCH' is allowlisted" |
| 98 | + else |
| 99 | + echo "allowed=false" >> "$GITHUB_OUTPUT" |
| 100 | + echo "Branch '$BRANCH' is NOT allowlisted" |
| 101 | + echo "Allowlisted branches:" |
| 102 | + # shellcheck disable=SC2001 # sed is appropriate for multi-line prefix |
| 103 | + echo "$ALLOWLISTED_BRANCHES" | sed 's/^/ - /' |
| 104 | + fi |
| 105 | +
|
| 106 | + # Build documentation |
| 107 | + build: |
| 108 | + name: "Build Documentation" |
| 109 | + runs-on: ubuntu-latest |
| 110 | + needs: check-allowlist |
| 111 | + # Always build for PRs (for testing), only build for push if allowlisted |
| 112 | + if: github.event_name == 'pull_request' || needs.check-allowlist.outputs.allowed == 'true' |
| 113 | + |
| 114 | + steps: |
| 115 | + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 |
| 116 | + with: |
| 117 | + fetch-depth: 0 # Full history for git-authors plugin |
| 118 | + submodules: recursive |
| 119 | + |
| 120 | + - name: Setup Python |
| 121 | + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 |
| 122 | + with: |
| 123 | + python-version: "3.12" |
| 124 | + |
| 125 | + - name: Install uv |
| 126 | + uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 |
| 127 | + |
| 128 | + - name: Install dependencies |
| 129 | + run: | |
| 130 | + uv sync |
| 131 | +
|
| 132 | + - name: Build MkDocs documentation |
| 133 | + run: | |
| 134 | + BRANCH="${{ needs.check-allowlist.outputs.branch }}" |
| 135 | +
|
| 136 | + # Set site URL based on whether this is a deploy or PR build |
| 137 | + if [ "${{ needs.check-allowlist.outputs.is_deploy }}" = "true" ]; then |
| 138 | + export SITE_URL="https://steel.ethereum.foundation/docs/${BRANCH}/" |
| 139 | + else |
| 140 | + # PR build - use a placeholder URL for testing |
| 141 | + export SITE_URL="https://example.com/docs/${BRANCH}/" |
| 142 | + fi |
| 143 | +
|
| 144 | + echo "Building MkDocs with site_url: $SITE_URL" |
| 145 | + uv run mkdocs build --site-dir .tox/docs-site --strict |
| 146 | +
|
| 147 | + - name: Build rendered spec |
| 148 | + run: | |
| 149 | + uv run docc --output .tox/docs-spec |
| 150 | +
|
| 151 | + - name: Stage artifact contents |
| 152 | + run: | |
| 153 | + BRANCH="${{ needs.check-allowlist.outputs.branch }}" |
| 154 | +
|
| 155 | + mkdir -p "stage/${BRANCH}/spec" |
| 156 | +
|
| 157 | + # Copy MkDocs output |
| 158 | + rsync -av .tox/docs-site/ "stage/${BRANCH}/" |
| 159 | +
|
| 160 | + # Copy spec output into spec/ subdirectory |
| 161 | + rsync -av .tox/docs-spec/ "stage/${BRANCH}/spec/" |
| 162 | +
|
| 163 | + # Generate metadata |
| 164 | + cat > "stage/${BRANCH}/metadata.json" << EOF |
| 165 | + { |
| 166 | + "branch": "${BRANCH}", |
| 167 | + "branch_slug": "${BRANCH}", |
| 168 | + "commit_sha": "${GITHUB_SHA}", |
| 169 | + "commit_sha_short": "${GITHUB_SHA:0:7}", |
| 170 | + "build_timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", |
| 171 | + "build_url": "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" |
| 172 | + } |
| 173 | + EOF |
| 174 | +
|
| 175 | + # Add .nojekyll |
| 176 | + touch "stage/.nojekyll" |
| 177 | +
|
| 178 | + echo "Staged content for branch: $BRANCH" |
| 179 | + ls -la "stage/${BRANCH}/" |
| 180 | +
|
| 181 | + - name: Validate staged output |
| 182 | + run: | |
| 183 | + BRANCH="${{ needs.check-allowlist.outputs.branch }}" |
| 184 | +
|
| 185 | + # Check MkDocs entrypoint exists |
| 186 | + if [ ! -f "stage/${BRANCH}/index.html" ]; then |
| 187 | + echo "ERROR: Missing stage/${BRANCH}/index.html" |
| 188 | + exit 1 |
| 189 | + fi |
| 190 | +
|
| 191 | + # Check spec entrypoint exists |
| 192 | + if [ ! -f "stage/${BRANCH}/spec/index.html" ]; then |
| 193 | + echo "ERROR: Missing stage/${BRANCH}/spec/index.html" |
| 194 | + exit 1 |
| 195 | + fi |
| 196 | +
|
| 197 | + # Check metadata exists |
| 198 | + if [ ! -f "stage/${BRANCH}/metadata.json" ]; then |
| 199 | + echo "ERROR: Missing stage/${BRANCH}/metadata.json" |
| 200 | + exit 1 |
| 201 | + fi |
| 202 | +
|
| 203 | + echo "Validation passed" |
| 204 | + echo "" |
| 205 | + echo "=== Staged content summary ===" |
| 206 | + du -sh "stage/${BRANCH}/" |
| 207 | + du -sh "stage/${BRANCH}/spec/" |
| 208 | +
|
| 209 | + - name: Upload docs artifact |
| 210 | + if: needs.check-allowlist.outputs.is_deploy == 'true' |
| 211 | + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 |
| 212 | + with: |
| 213 | + name: docs-${{ needs.check-allowlist.outputs.branch_slug_safe }} |
| 214 | + path: stage/ |
| 215 | + retention-days: 90 |
| 216 | + if-no-files-found: error |
| 217 | + |
| 218 | + # Trigger the aggregator workflow in steel-website |
| 219 | + trigger-aggregator: |
| 220 | + name: "Trigger Aggregator" |
| 221 | + runs-on: ubuntu-latest |
| 222 | + needs: [check-allowlist, build] |
| 223 | + # Only trigger on push/dispatch to allowlisted branches (not PRs) |
| 224 | + if: needs.check-allowlist.outputs.is_deploy == 'true' && needs.check-allowlist.outputs.allowed == 'true' |
| 225 | + |
| 226 | + steps: |
| 227 | + - name: Trigger aggregator workflow |
| 228 | + uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 |
| 229 | + with: |
| 230 | + token: ${{ secrets.STEEL_WEBSITE_DISPATCH }} |
| 231 | + repository: ethereum/steel-website |
| 232 | + event-type: docs-artifact-ready |
| 233 | + client-payload: | |
| 234 | + { |
| 235 | + "branch": "${{ needs.check-allowlist.outputs.branch }}", |
| 236 | + "sha": "${{ github.sha }}", |
| 237 | + "run_id": "${{ github.run_id }}" |
| 238 | + } |
| 239 | +
|
| 240 | + - name: Log trigger status |
| 241 | + run: | |
| 242 | + echo "Triggered aggregator workflow in ethereum/steel-website" |
| 243 | + echo " Branch: ${{ needs.check-allowlist.outputs.branch }}" |
| 244 | + echo " SHA: ${{ github.sha }}" |
| 245 | + echo " Run ID: ${{ github.run_id }}" |
0 commit comments