ci: reduce Next.js build heap to 6 GB (#6) #10
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
| # ============================================================================= | |
| # Release - Full CI/CD Pipeline | |
| # ============================================================================= | |
| # Complete release pipeline: Build → Scan → Security Gate → Update Helm | |
| # | |
| # Triggers: | |
| # - Push to main: Builds image + auto-deploys to TESTNET (via ArgoCD) | |
| # - Manual: Builds image + auto-deploys to TESTNET (toggle with `deploy: false`) | |
| # | |
| # A single SHA-tagged image is produced and deployed to testnet. Promoting the | |
| # same image to MAINNET is a separate manual action via deploy.yml (which | |
| # requires the DEPLOY-TO-PROD confirmation). | |
| # | |
| # Pipeline: | |
| # ┌─────────┐ ┌─────────┐ ┌───────────────┐ ┌──────────────┐ | |
| # │ Build │ ──► │ Scan │ ──► │ Security Gate │ ──► │ Update Helm │ | |
| # └─────────┘ └─────────┘ └───────────────┘ └──────────────┘ | |
| # | |
| # Security Gate (after scan): | |
| # - No CRITICAL vulns → passes automatically → deploy proceeds | |
| # - CRITICAL vulns → gate fails → deploy is blocked | |
| # - CRITICAL vulns + admin override → re-trigger manually with | |
| # skip_security_gate: true | |
| # The override actor and timestamp are permanently recorded in | |
| # GitHub Actions workflow run history. | |
| # | |
| # ⚠️ MAINNET deploys are MANUAL ONLY via deploy.yml | |
| # ============================================================================= | |
| name: Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| deploy: | |
| description: "Deploy to TESTNET after build (uncheck to build only)" | |
| required: false | |
| default: true | |
| type: boolean | |
| skip_security_gate: | |
| description: "Override security gate when CRITICAL vulnerabilities are found (admin only)" | |
| required: false | |
| default: false | |
| type: boolean | |
| push: | |
| branches: [main] | |
| paths: | |
| - "pages/**" | |
| - "ui/**" | |
| - "lib/**" | |
| - "toolkit/**" | |
| - "configs/**" | |
| - "nextjs/**" | |
| - "public/**" | |
| - "package.json" | |
| - "package-lock.json" | |
| - "yarn.lock" | |
| - "Dockerfile" | |
| - "next.config.*" | |
| permissions: | |
| contents: read | |
| security-events: write | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Resolve should_deploy | |
| # --------------------------------------------------------------------------- | |
| # Push to main → always deploy. Manual dispatch → honor the `deploy` input | |
| # (default: true). Mainnet promotions never run here — use deploy.yml. | |
| # --------------------------------------------------------------------------- | |
| setup: | |
| name: 🔧 Setup | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_deploy: ${{ steps.env.outputs.should_deploy }} | |
| steps: | |
| - name: Determine should_deploy | |
| id: env | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| echo "should_deploy=${{ inputs.deploy }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "should_deploy=true" >> $GITHUB_OUTPUT | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Build Docker Image (centralized workflow) | |
| # --------------------------------------------------------------------------- | |
| build: | |
| name: 🐳 Build | |
| needs: [setup] | |
| uses: ./.github/workflows/_build.yml | |
| with: | |
| image_name: rayls-explorer-frontend | |
| environment: dev | |
| push: true | |
| secrets: inherit | |
| # --------------------------------------------------------------------------- | |
| # Security Scan (local reusable workflow) | |
| # --------------------------------------------------------------------------- | |
| scan: | |
| name: 🔒 Scan | |
| needs: [setup, build] | |
| uses: ./.github/workflows/_security-scan.yml | |
| with: | |
| image_ref: registry.digitalocean.com/rayls-public-chain-registry/rayls-explorer-frontend:${{ needs.build.outputs.image_tag }} | |
| secrets: inherit | |
| # --------------------------------------------------------------------------- | |
| # Security Gate (inlined — continue-on-error not supported on reusable workflow calls) | |
| # - No CRITICAL vulns → ✅ green → deploy proceeds | |
| # - CRITICAL vulns, no override → ❌ red → deploy blocked | |
| # - CRITICAL vulns + override → ⚠️ orange → deploy proceeds (admin audit trail preserved) | |
| # --------------------------------------------------------------------------- | |
| gate: | |
| name: 🛡️ Security Gate | |
| runs-on: ubuntu-latest | |
| needs: [scan] | |
| continue-on-error: ${{ inputs.skip_security_gate || false }} | |
| steps: | |
| - name: Verify admin permission | |
| if: inputs.skip_security_gate == true | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| PERMISSION=$(gh api repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission --jq '.role_name' 2>/dev/null || echo "none") | |
| if [[ "$PERMISSION" != "admin" ]]; then | |
| echo "❌ Only repository admins can override the security gate." | |
| echo "Actor '${{ github.actor }}' has permission level: $PERMISSION" | |
| exit 1 | |
| fi | |
| echo "✅ Admin permission verified for ${{ github.actor }} (role: $PERMISSION)" | |
| - name: Evaluate | |
| run: | | |
| if [[ "${{ needs.scan.outputs.has_critical }}" == "true" ]]; then | |
| if [[ "${{ inputs.skip_security_gate }}" == "true" ]]; then | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo " ⚠️ SECURITY OVERRIDE — DEPLOY UNBLOCKED ⚠️" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo " CRITICAL vulnerabilities are present in the image." | |
| echo " An admin has explicitly overridden the security gate." | |
| echo "" | |
| echo " Override actor : ${{ github.actor }}" | |
| echo " Audit trail : GitHub Actions → workflow run history" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| exit 1 | |
| else | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo " ❌ SECURITY GATE — DEPLOY BLOCKED ❌" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo " CRITICAL vulnerabilities were found in the image." | |
| echo " To unblock: re-trigger this workflow manually with" | |
| echo " 'skip_security_gate: true'." | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| exit 1 | |
| fi | |
| else | |
| echo "✅ No CRITICAL vulnerabilities found. Proceeding with deploy." | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Update Helm Chart (GitOps) (centralized workflow) | |
| # ArgoCD monitors the Helm chart repository and automatically deploys changes. | |
| # --------------------------------------------------------------------------- | |
| update-helm: | |
| name: 📦 Update Helm Chart | |
| needs: [setup, build, scan, gate] | |
| if: >- | |
| always() && | |
| needs.build.result == 'success' && | |
| (needs.gate.result == 'success' || (needs.gate.result == 'failure' && (inputs.skip_security_gate || false) == true)) && | |
| needs.setup.outputs.should_deploy == 'true' | |
| uses: ./.github/workflows/_update-helm.yml | |
| with: | |
| app_name: rayls-explorer-frontend | |
| environment: dev | |
| image_tag: ${{ needs.build.outputs.image_tag }} | |
| values_file: blockscout-testnet/values.yaml | |
| image_tag_yaml_path: .frontend.image.tag | |
| secrets: inherit | |
| # --------------------------------------------------------------------------- | |
| # Summary | |
| # --------------------------------------------------------------------------- | |
| summary: | |
| name: 📋 Summary | |
| runs-on: ubuntu-latest | |
| needs: [setup, build, scan, gate, update-helm] | |
| if: always() | |
| steps: | |
| - name: Pipeline Results | |
| run: | | |
| # Determine gate status | |
| if [[ "${{ needs.gate.result }}" == "success" ]]; then | |
| GATE_STATUS="✅ Passed (no critical vulns)" | |
| elif [[ "${{ needs.gate.result }}" == "failure" && "${{ inputs.skip_security_gate }}" == "true" ]]; then | |
| GATE_STATUS="⚠️ Override by ${{ github.actor }}" | |
| elif [[ "${{ needs.gate.result }}" == "failure" ]]; then | |
| GATE_STATUS="❌ Blocked — re-trigger with skip_security_gate: true to override" | |
| else | |
| GATE_STATUS="⏭️ Skipped" | |
| fi | |
| { | |
| echo "## 📋 Release Pipeline" | |
| echo "" | |
| echo "| Stage | Status |" | |
| echo "|-------|--------|" | |
| echo "| Setup | ${{ needs.setup.result == 'success' && '✅' || '❌' }} |" | |
| echo "| Build | ${{ needs.build.result == 'success' && '✅' || needs.build.result == 'skipped' && '⏭️' || '❌' }} |" | |
| echo "| Scan | ${{ needs.scan.result == 'success' && '✅' || needs.scan.result == 'skipped' && '⏭️' || '❌' }} |" | |
| echo "| Security Gate | $GATE_STATUS |" | |
| echo "| Update Helm | ${{ needs.update-helm.result == 'success' && '✅' || needs.update-helm.result == 'skipped' && '⏭️' || needs.update-helm.result == 'cancelled' && '🚫' || '❌' }} |" | |
| echo "" | |
| echo "**Target:** \`testnet (blockscout-testnet/values.yaml)\`" | |
| echo "**Image Tag:** \`${{ needs.build.outputs.image_tag }}\`" | |
| echo "" | |
| if [[ "${{ needs.update-helm.result }}" == "success" ]]; then | |
| echo "🔄 **ArgoCD** will automatically detect and deploy the changes." | |
| fi | |
| } >> $GITHUB_STEP_SUMMARY |