ci: consolidate and streamline workflows #15
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: Pull Request | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| types: | |
| - opened | |
| - reopened | |
| - synchronize | |
| - closed | |
| - ready_for_review | |
| env: | |
| DEFAULT_PREVIEW_BASE_URL: 'https://openfga.github.io/openfga.dev' | |
| permissions: | |
| contents: read | |
| # Prevent concurrent builds for the same PR | |
| concurrency: | |
| group: pr-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Run code quality checks | |
| code-checks: | |
| name: Code Quality (${{ matrix.check }}) | |
| runs-on: ubuntu-latest | |
| if: github.event.action != 'closed' && github.actor != 'dependabot[bot]' && github.event.pull_request.draft == false | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - check: format | |
| command: npm run format:check | |
| - check: lint | |
| command: npm run lint | |
| - check: types | |
| command: npm run typecheck | |
| - check: audit | |
| command: npm audit | |
| continue-on-error: true | |
| - check: circular-deps | |
| command: npx madge@8.0.0 --circular . --extensions ts,js,jsx,tsx | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| lfs: false | |
| fetch-depth: 2 | |
| - name: Setup dependencies | |
| uses: ./.github/actions/setup-and-build | |
| with: | |
| build: 'false' | |
| - name: Run ${{ matrix.check }} | |
| run: ${{ matrix.command }} | |
| continue-on-error: ${{ matrix.continue-on-error || false }} | |
| # Check markdown links | |
| markdown-link-check: | |
| name: Check Documentation Links (${{ matrix.extension }}) | |
| runs-on: ubuntu-latest | |
| if: github.event.action != 'closed' && github.actor != 'dependabot[bot]' && github.event.pull_request.draft == false | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| extension: ['.md', '.mdx'] | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| fetch-depth: 1 | |
| lfs: false | |
| - name: Check Markdown links (${{ matrix.extension }}) | |
| uses: tcort/github-action-markdown-link-check@a800ad5f1c35bf61987946fd31c15726a1c9f2ba # v1.1.0 | |
| continue-on-error: true | |
| with: | |
| file-extension: ${{ matrix.extension }} | |
| use-quiet-mode: 'yes' | |
| config-file: '.github/workflows/markdown.links.config.json' | |
| folder-path: '.' | |
| # Build and test deployment | |
| build-test: | |
| name: Build and Test | |
| if: | | |
| github.event.action != 'closed' && | |
| github.event.pull_request.draft == false && | |
| (needs.code-checks.result == 'success' || needs.code-checks.result == 'skipped' || github.actor == 'dependabot[bot]') && | |
| (needs.markdown-link-check.result == 'success' || needs.markdown-link-check.result == 'skipped' || github.actor == 'dependabot[bot]') | |
| needs: [code-checks, markdown-link-check] # Only build after all checks pass | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| lfs: true | |
| fetch-depth: 2 | |
| - name: Set base URL for build | |
| id: set-base-url | |
| run: | | |
| # Set base URL for PR preview builds (excluding Dependabot) | |
| if [ "${{ github.actor }}" != "dependabot[bot]" ]; then | |
| # PR number is validated by GitHub and guaranteed to be numeric | |
| pr_number="${{ github.event.pull_request.number }}" | |
| echo "build_base_url=/pr-preview/pr-${pr_number}/" >> $GITHUB_OUTPUT | |
| echo "Building with PR preview URL: /pr-preview/pr-${pr_number}/" | |
| else | |
| echo "build_base_url=" >> $GITHUB_OUTPUT | |
| echo "Dependabot PR - building without preview URL" | |
| fi | |
| - name: Setup and build | |
| uses: ./.github/actions/setup-and-build | |
| with: | |
| build-base-url: ${{ steps.set-base-url.outputs.build_base_url }} | |
| - name: Upload build artifacts | |
| if: github.actor != 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: build-artifacts | |
| path: ./build/ | |
| retention-days: 1 | |
| compression-level: 9 | |
| # Deploy preview only for non-Dependabot PRs from the main repo (not forks) | |
| deploy-preview: | |
| name: Deploy Preview | |
| needs: build-test | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event.action != 'closed' && | |
| github.actor != 'dependabot[bot]' && | |
| github.event.pull_request.draft == false && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| permissions: | |
| contents: write # Required to push to gh-pages | |
| pull-requests: write # Required for PR comments | |
| steps: | |
| - name: Checkout gh-pages branch | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| ref: gh-pages | |
| fetch-depth: 1 | |
| lfs: false | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 | |
| with: | |
| name: build-artifacts | |
| path: ./build/ | |
| - name: Deploy preview to gh-pages | |
| run: | | |
| # Configure git | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Setup preview directory | |
| preview_dir="pr-preview/pr-${{ github.event.pull_request.number }}" | |
| # Ensure preview directory is within pr-preview folder | |
| if [[ "${preview_dir}" != pr-preview/pr-* ]]; then | |
| echo "Error: Invalid preview directory path: ${preview_dir}" | |
| exit 1 | |
| fi | |
| # Check if this is a new preview or an update | |
| if [ -d "${preview_dir}" ]; then | |
| echo "Updating existing preview for PR #${{ github.event.pull_request.number }}" | |
| is_update=true | |
| else | |
| echo "Creating new preview for PR #${{ github.event.pull_request.number }}" | |
| mkdir -p "${preview_dir}" | |
| is_update=false | |
| fi | |
| # Sync build files to preview directory (only changed files) | |
| # Using rsync for efficient incremental updates | |
| # The --delete flag removes files that don't exist in source | |
| rsync -av --delete \ | |
| --exclude='.git' \ | |
| --exclude='.github' \ | |
| ./build/ "${preview_dir}/" | |
| # Add and commit changes | |
| git add "${preview_dir}" | |
| if git diff --staged --quiet; then | |
| echo "No changes detected in build output" | |
| else | |
| # Commit with appropriate message | |
| if [ "$is_update" = true ]; then | |
| git commit -m "Update preview for PR #${{ github.event.pull_request.number }}" | |
| else | |
| git commit -m "Deploy preview for PR #${{ github.event.pull_request.number }}" | |
| fi | |
| # Push with retry logic to handle concurrent updates | |
| max_attempts=5 | |
| attempt=1 | |
| while [ $attempt -le $max_attempts ]; do | |
| echo "Push attempt $attempt of $max_attempts" | |
| if git push origin gh-pages; then | |
| echo "Successfully pushed to gh-pages" | |
| break | |
| else | |
| if [ $attempt -eq $max_attempts ]; then | |
| echo "Failed to push after $max_attempts attempts" | |
| exit 1 | |
| fi | |
| echo "Push failed, pulling latest changes and retrying..." | |
| git pull --rebase origin gh-pages | |
| attempt=$((attempt + 1)) | |
| sleep 2 | |
| fi | |
| done | |
| fi | |
| # Set outputs for summary | |
| # Use default preview base URL (can be overridden via env variable if needed) | |
| echo "preview_url=${{ env.DEFAULT_PREVIEW_BASE_URL }}/${preview_dir}/" >> $GITHUB_ENV | |
| - name: Comment preview link on PR | |
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
| with: | |
| script: | | |
| const preview_url = process.env.preview_url; | |
| const pr_number = context.payload.pull_request.number; | |
| const comment_body = `### Preview Deployment\n\nPreview is ready at: ${preview_url}\n\nThis preview will be automatically removed when the PR is closed.`; | |
| // Find existing comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr_number | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user?.login === 'github-actions[bot]' && | |
| comment.body?.includes('Preview Deployment') | |
| ); | |
| if (botComment) { | |
| // Update existing comment | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: comment_body | |
| }); | |
| } else { | |
| // Create new comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr_number, | |
| body: comment_body | |
| }); | |
| } | |
| - name: Add preview link to summary | |
| run: | | |
| echo "### Preview Deployment" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "Preview deployed to: ${preview_url}" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "[Open preview](${preview_url})" >> "$GITHUB_STEP_SUMMARY" | |
| # Clean up preview on PR close (only for PRs from the main repo) | |
| cleanup-preview: | |
| name: Cleanup Preview | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event.action == 'closed' && | |
| github.actor != 'dependabot[bot]' && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout gh-pages branch | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| ref: gh-pages | |
| fetch-depth: 1 | |
| lfs: false | |
| - name: Remove preview directory | |
| run: | | |
| # Configure git | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Remove preview directory | |
| preview_dir="pr-preview/pr-${{ github.event.pull_request.number }}" | |
| # Ensure we're only removing from pr-preview folder | |
| if [[ "${preview_dir}" != pr-preview/pr-* ]]; then | |
| echo "Error: Invalid preview directory path: ${preview_dir}" | |
| exit 1 | |
| fi | |
| if [ -d "${preview_dir}" ]; then | |
| git rm -rf "${preview_dir}" | |
| git commit -m "Remove preview for PR #${{ github.event.pull_request.number }}" | |
| # Push with retry logic to handle concurrent updates | |
| max_attempts=5 | |
| attempt=1 | |
| while [ $attempt -le $max_attempts ]; do | |
| echo "Push attempt $attempt of $max_attempts" | |
| if git push origin gh-pages; then | |
| echo "Preview removed for PR #${{ github.event.pull_request.number }}" >> "$GITHUB_STEP_SUMMARY" | |
| break | |
| else | |
| if [ $attempt -eq $max_attempts ]; then | |
| echo "Error: Failed to remove preview after $max_attempts attempts" >> "$GITHUB_STEP_SUMMARY" | |
| exit 1 | |
| fi | |
| echo "Push failed, pulling latest changes and retrying..." | |
| git pull --rebase origin gh-pages | |
| attempt=$((attempt + 1)) | |
| sleep 2 | |
| fi | |
| done | |
| else | |
| echo "No preview directory found for PR #${{ github.event.pull_request.number }}" >> "$GITHUB_STEP_SUMMARY" | |
| fi |