Wait for Pages deployment before posting preview link #46
Workflow file for this run
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: PR build | |
| on: | |
| pull_request: | |
| # `closed` is included so the preview deployment is torn down when the PR | |
| # closes; the build/link-check job skips that event | |
| types: [opened, reopened, synchronize, closed] | |
| workflow_dispatch: # Allows manual triggering from the GitHub UI | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| jobs: | |
| build: | |
| if: github.event.action != 'closed' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| # Images are Git LFS-tracked (.gitattributes); without this the | |
| # build ships LFS pointer files instead of the actual binaries | |
| lfs: true | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| # The ADO registry requires auth even to install; the repo .npmrc is | |
| # credential-less so creds go into ~/.npmrc here (fork PRs don't get | |
| # secrets and will fail) | |
| - name: Authenticate to codat-npm feed | |
| run: | | |
| { | |
| echo "//pkgs.dev.azure.com/codat/Codat/_packaging/codat-npm/npm/registry/:username=codat" | |
| echo "//pkgs.dev.azure.com/codat/Codat/_packaging/codat-npm/npm/registry/:_password=${ADO_NPM_FEED_TOKEN}" | |
| echo "//pkgs.dev.azure.com/codat/Codat/_packaging/codat-npm/npm/registry/:email=npm-requires-email@example.com" | |
| } >> ~/.npmrc | |
| env: | |
| ADO_NPM_FEED_TOKEN: ${{ secrets.ADO_NPM_FEED_TOKEN }} | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build site | |
| run: npm run build | |
| env: | |
| # plugin-google-gtag requires a trackingID, so PR builds need it too | |
| GTM_ID: ${{ vars.GTM_ID }} | |
| # Link checking reuses the build above: linkinator serves ./build on an | |
| # ephemeral localhost port, so no deployed preview environment is needed. | |
| # --silent keeps npm's banner out of the JSON on stdout. | |
| - name: Check for broken links | |
| if: github.actor != 'codatbot' | |
| run: npm run --silent links:check -- --format json > link-results.json | |
| continue-on-error: true # Broken links are reported below, not here | |
| - name: Delete previous link check comments | |
| uses: actions/github-script@v8 | |
| if: github.event_name == 'pull_request' && github.actor != 'codatbot' | |
| with: | |
| github-token: ${{secrets.GITHUB_TOKEN}} | |
| script: | | |
| const issue_number = context.issue.number; | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const comments = await github.rest.issues.listComments({ | |
| issue_number, | |
| owner, | |
| repo, | |
| }); | |
| const actionComments = comments.data.filter(comment => comment.user.login === 'github-actions[bot]' && comment.body.includes('Link check results')); | |
| for (const comment of actionComments) { | |
| await github.rest.issues.deleteComment({ | |
| owner, | |
| repo, | |
| comment_id: comment.id, | |
| }); | |
| } | |
| - name: Post link check results | |
| uses: actions/github-script@v8 | |
| if: github.actor != 'codatbot' | |
| with: | |
| github-token: ${{secrets.GITHUB_TOKEN}} | |
| script: | | |
| const fs = require('fs'); | |
| if (!fs.existsSync('link-results.json') || fs.statSync('link-results.json').size === 0) { | |
| core.setFailed("The link check did not produce any results — see the 'Check for broken links' step."); | |
| return; | |
| } | |
| // --verbosity error means the JSON only contains broken links, so | |
| // filter out the statuses that bot protection (403) and flaky or | |
| // briefly-down external hosts (0, 5xx) return rather than genuine | |
| // link rot. Without this the check goes red on other people's | |
| // outages. | |
| const results = JSON.parse(fs.readFileSync('link-results.json', 'utf8')); | |
| const isNoise = status => status === 0 || status === 403 || status >= 500; | |
| const filtered = results.links.filter(link => !isNoise(link.status)) | |
| const printList = filtered | |
| .map(link => `[${link.status}] ${link.url}`); | |
| if (context.eventName === 'pull_request') { | |
| const output = `Link check results:\n\`\`\`\n${JSON.stringify(printList, null, 2)}\n\`\`\``; | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: output, | |
| }); | |
| } else { | |
| core.info(printList.join('\n')); | |
| } | |
| if (filtered.length > 0) { | |
| core.setFailed("There are broken links in the documentation."); | |
| } | |
| # Deploys the built site to GitHub Pages under pr-preview/pr-<number>/ and | |
| # posts a sticky comment on the PR with the preview URL; the preview is | |
| # removed when the PR closes. Builds separately from the job above because | |
| # the preview needs a different baseUrl than the link check. Fork PRs are | |
| # skipped: they get neither secrets nor a writable GITHUB_TOKEN. | |
| deploy-preview: | |
| if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| # Concurrent runs for the same PR would race on pushing to gh-pages | |
| concurrency: pr-preview-${{ github.ref }} | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| # wait-for-pages-deployment polls the Pages builds API | |
| pages: read | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| # Images are Git LFS-tracked; skip fetching them on teardown, where | |
| # the checkout is only needed so the action can push to gh-pages | |
| lfs: ${{ github.event.action != 'closed' }} | |
| - uses: actions/setup-node@v6 | |
| if: github.event.action != 'closed' | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| # The ADO registry requires auth even to install; the repo .npmrc is | |
| # credential-less so creds go into ~/.npmrc here | |
| - name: Authenticate to codat-npm feed | |
| if: github.event.action != 'closed' | |
| run: | | |
| { | |
| echo "//pkgs.dev.azure.com/codat/Codat/_packaging/codat-npm/npm/registry/:username=codat" | |
| echo "//pkgs.dev.azure.com/codat/Codat/_packaging/codat-npm/npm/registry/:_password=${ADO_NPM_FEED_TOKEN}" | |
| echo "//pkgs.dev.azure.com/codat/Codat/_packaging/codat-npm/npm/registry/:email=npm-requires-email@example.com" | |
| } >> ~/.npmrc | |
| env: | |
| ADO_NPM_FEED_TOKEN: ${{ secrets.ADO_NPM_FEED_TOKEN }} | |
| - name: Install dependencies | |
| if: github.event.action != 'closed' | |
| run: npm ci | |
| - name: Build site for preview | |
| if: github.event.action != 'closed' | |
| run: npm run build | |
| env: | |
| GTM_ID: ${{ vars.GTM_ID }} | |
| # GitHub Pages serves the preview from a subpath (no trailing slash) | |
| BASE_URL: /codat-docs/pr-preview/pr-${{ github.event.number }} | |
| - name: Deploy preview | |
| uses: rossjrw/pr-preview-action@v1 | |
| with: | |
| source-dir: ./build | |
| # Hold the sticky comment until the Pages deployment is live; | |
| # otherwise the preview link 404s for the first minute or so | |
| wait-for-pages-deployment: true |