Docs - Fork PR - Deploy #235
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: "Docs - Fork PR - Deploy" | |
| # This workflow runs in a TRUSTED context (base repo token with write access). | |
| # It is triggered AFTER "Docs - Deploy" completes for a fork PR. | |
| # The "Docs - Deploy" build job uploads the site artifact; this workflow | |
| # downloads it and deploys to gh-pages/staging/{pr}/, then comments on the PR. | |
| # | |
| # Security: no untrusted code is executed here — we only deploy a pre-built | |
| # artifact produced by our own workflow. The workflow_run trigger always runs | |
| # with the base repository's token, regardless of where the PR originated. | |
| on: | |
| workflow_run: | |
| workflows: [ "Docs - Deploy" ] | |
| types: [ completed ] | |
| # Default to no permissions — each job declares only what it needs. | |
| permissions: {} | |
| # Ensure documentation workflows run sequentially | |
| concurrency: | |
| group: "docs-deployment" | |
| cancel-in-progress: false | |
| jobs: | |
| deploy-fork-pr-staging: | |
| # Only run for successful fork PR builds | |
| if: | | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'pull_request' && | |
| github.event.workflow_run.head_repository.full_name != github.repository | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # push to gh-pages | |
| pull-requests: write # comment on PR | |
| steps: | |
| - name: Find staging artifact for this PR | |
| id: artifact | |
| uses: actions/github-script@v7 | |
| env: | |
| WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} | |
| with: | |
| script: | | |
| const runId = parseInt(process.env.WORKFLOW_RUN_ID, 10); | |
| const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: runId | |
| }); | |
| const staging = artifacts.data.artifacts.find(a => a.name.startsWith('staging-site-')); | |
| if (!staging) { | |
| core.setFailed('No staging artifact found for this workflow run'); | |
| return; | |
| } | |
| // Extract and validate PR number (must be a positive integer) | |
| const prNumberStr = staging.name.replace('staging-site-', ''); | |
| const prNumber = parseInt(prNumberStr, 10); | |
| if (!prNumber || prNumber <= 0 || String(prNumber) !== prNumberStr) { | |
| core.setFailed(`Invalid PR number extracted from artifact name: ${staging.name}`); | |
| return; | |
| } | |
| core.setOutput('artifact_name', staging.name); | |
| core.setOutput('pr_number', String(prNumber)); | |
| - name: Download staging artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| name: ${{ steps.artifact.outputs.artifact_name }} | |
| path: /tmp/site-temp | |
| - name: Checkout gh-pages branch | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: gh-pages | |
| fetch-depth: 0 | |
| - name: Deploy staging content | |
| env: | |
| PR_NUMBER: ${{ steps.artifact.outputs.pr_number }} | |
| run: | | |
| # Validate PR_NUMBER is a positive integer before using in paths | |
| if ! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then | |
| echo "Invalid PR number: $PR_NUMBER" | |
| exit 1 | |
| fi | |
| mkdir -p staging/$PR_NUMBER | |
| rm -rf staging/$PR_NUMBER/* | |
| cp -r /tmp/site-temp/. staging/$PR_NUMBER/ | |
| rm -rf /tmp/site-temp | |
| # Ensure .nojekyll exists at root so _framework is served | |
| touch .nojekyll | |
| git config --local user.email "action@github.com" | |
| git config --local user.name "GitHub Action" | |
| git add .nojekyll staging/$PR_NUMBER | |
| if ! git diff --cached --quiet; then | |
| git commit -m "Deploy staging docs for PR #$PR_NUMBER" | |
| git push origin gh-pages | |
| else | |
| echo "No changes to commit" | |
| fi | |
| - name: Comment on PR | |
| uses: actions/github-script@v7 | |
| env: | |
| PR_NUMBER: ${{ steps.artifact.outputs.pr_number }} | |
| with: | |
| script: | | |
| const prNumber = parseInt(process.env.PR_NUMBER, 10); | |
| if (!prNumber || prNumber <= 0) { | |
| core.setFailed('Invalid PR number'); | |
| return; | |
| } | |
| const stagingUrl = `https://mono.github.io/SkiaSharp.Extended/staging/${prNumber}/`; | |
| const sampleUrl = `https://mono.github.io/SkiaSharp.Extended/staging/${prNumber}/sample/`; | |
| // Only comment once; check for an existing bot comment | |
| const comments = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber | |
| }); | |
| const botComment = comments.data.find(c => | |
| c.user.type === 'Bot' && | |
| c.body.includes('Documentation Preview') | |
| ); | |
| if (botComment) { | |
| console.log('Bot comment already exists, skipping.'); | |
| return; | |
| } | |
| const commentBody = [ | |
| '📖 **Documentation Preview**', | |
| '', | |
| 'The documentation for this PR has been deployed and is available at:', | |
| '', | |
| `🔗 **[View Staging Documentation](${stagingUrl})**`, | |
| '', | |
| `🔗 **[View Staging Blazor Sample](${sampleUrl})**`, | |
| '', | |
| 'This preview will be updated automatically when you push new commits to this PR.', | |
| '', | |
| '---', | |
| '*This comment is automatically updated by the documentation staging workflow.*' | |
| ].join('\n'); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: commentBody | |
| }); | |