Skip to content

Docs - Fork PR - Deploy #228

Docs - Fork PR - Deploy

Docs - Fork PR - Deploy #228

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
});