Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 32 additions & 111 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,163 +14,84 @@ concurrency:
cancel-in-progress: true

jobs:
build-preview:
runs-on: ubuntu-latest
outputs:
artifact_name: ${{ steps.meta.outputs.artifact_name }}
screenshot_name: ${{ steps.meta.outputs.screenshot_name }}
post_path: ${{ steps.changed_post.outputs.post_path }}
post_url: ${{ steps.changed_post.outputs.post_url }}
steps:
- name: Set artifact name
id: meta
run: |
echo "artifact_name=site-preview-pr-${{ github.event.number }}" >> "$GITHUB_OUTPUT"
echo "screenshot_name=site-screenshots-pr-${{ github.event.number }}" >> "$GITHUB_OUTPUT"

- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true

- name: Build site
run: bundle exec jekyll build

- name: Detect changed post
id: changed_post
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.sha }}
run: |
mapfile -t posts < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- '_posts/*.md')
if [[ ${#posts[@]} -eq 0 ]]; then
echo "post_path=" >> "$GITHUB_OUTPUT"
echo "post_url=/" >> "$GITHUB_OUTPUT"
exit 0
fi

post="${posts[0]}"
slug="$(basename "$post" .md)"
slug="${slug#????-??-??-}"
echo "post_path=$post" >> "$GITHUB_OUTPUT"
echo "post_url=/$slug/" >> "$GITHUB_OUTPUT"

- name: Upload site artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.meta.outputs.artifact_name }}
path: _site
retention-days: 7

- name: Install Playwright Chromium
run: npx -y playwright@1.53.0 install --with-deps chromium

- name: Capture preview screenshot
env:
POST_URL: ${{ steps.changed_post.outputs.post_url }}
run: |
python3 -m http.server --directory _site 4173 > /tmp/pr-preview-server.log 2>&1 &
server_pid=$!
trap 'kill "$server_pid"' EXIT

for i in {1..20}; do
if curl -fsS "http://127.0.0.1:4173${POST_URL}" >/dev/null; then
break
fi
sleep 0.5
done

npx -y playwright@1.53.0 screenshot --browser=chromium --full-page "http://127.0.0.1:4173${POST_URL}" preview-long.png

- name: Upload screenshot artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.meta.outputs.screenshot_name }}
path: preview-long.png
retention-days: 7

deploy-cloudflare-preview:
runs-on: ubuntu-latest
needs: build-preview
if: ${{ !github.event.pull_request.head.repo.fork && vars.CLOUDFLARE_PAGES_PROJECT != '' && vars.CLOUDFLARE_ACCOUNT_ID != '' && secrets.CLOUDFLARE_API_TOKEN != '' }}
if: ${{ !github.event.pull_request.head.repo.fork && vars.CLOUDFLARE_PAGES_PROJECT != '' && vars.CLOUDFLARE_ACCOUNT_ID != '' }}
permissions:
contents: read
deployments: write
outputs:
preview_url: ${{ steps.cf.outputs.alias }}
preview_url: ${{ steps.cf.outputs.pages-deployment-alias-url }}
deployment_url: ${{ steps.cf.outputs.deployment-url }}
has_token: ${{ steps.token_check.outputs.has_token }}
steps:
- name: Check Cloudflare token
id: token_check
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
if [ -n "$CLOUDFLARE_API_TOKEN" ]; then
echo "has_token=true" >> "$GITHUB_OUTPUT"
else
echo "has_token=false" >> "$GITHUB_OUTPUT"
fi

- name: Checkout
uses: actions/checkout@v4
if: ${{ steps.token_check.outputs.has_token == 'true' }}
uses: actions/checkout@v6

- name: Set up Ruby
if: ${{ steps.token_check.outputs.has_token == 'true' }}
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.1"
bundler-cache: true

- name: Build site
if: ${{ steps.token_check.outputs.has_token == 'true' }}
run: bundle exec jekyll build

- name: Publish to Cloudflare Pages
id: cf
uses: cloudflare/pages-action@v1
if: ${{ steps.token_check.outputs.has_token == 'true' }}
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ vars.CLOUDFLARE_PAGES_PROJECT }}
directory: _site
command: pages deploy _site --project-name=${{ vars.CLOUDFLARE_PAGES_PROJECT }} --branch=pr-${{ github.event.number }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: pr-${{ github.event.number }}
wranglerVersion: '3'
wranglerVersion: "3"

comment-preview:
runs-on: ubuntu-latest
needs: [build-preview, deploy-cloudflare-preview]
if: ${{ always() && !github.event.pull_request.head.repo.fork && needs.build-preview.result == 'success' && (needs.deploy-cloudflare-preview.result == 'success' || needs.deploy-cloudflare-preview.result == 'skipped') }}
needs: deploy-cloudflare-preview
if: ${{ always() && !github.event.pull_request.head.repo.fork && (needs.deploy-cloudflare-preview.result == 'success' || needs.deploy-cloudflare-preview.result == 'skipped' || needs.deploy-cloudflare-preview.result == 'failure') }}
steps:
- name: Add or update PR preview comment
uses: actions/github-script@v7
env:
RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
ARTIFACT_NAME: ${{ needs.build-preview.outputs.artifact_name }}
SCREENSHOT_NAME: ${{ needs.build-preview.outputs.screenshot_name }}
POST_PATH: ${{ needs.build-preview.outputs.post_path }}
POST_URL: ${{ needs.build-preview.outputs.post_url }}
CLOUDFLARE_URL: ${{ needs.deploy-cloudflare-preview.outputs.preview_url }}
CLOUDFLARE_ENABLED: ${{ vars.CLOUDFLARE_PAGES_PROJECT != '' && vars.CLOUDFLARE_ACCOUNT_ID != '' && secrets.CLOUDFLARE_API_TOKEN != '' }}
CLOUDFLARE_DEPLOYMENT_URL: ${{ needs.deploy-cloudflare-preview.outputs.deployment_url }}
CLOUDFLARE_ENABLED: ${{ vars.CLOUDFLARE_PAGES_PROJECT != '' && vars.CLOUDFLARE_ACCOUNT_ID != '' && needs.deploy-cloudflare-preview.outputs.has_token == 'true' }}
CLOUDFLARE_RESULT: ${{ needs.deploy-cloudflare-preview.result }}
with:
script: |
const marker = "<!-- pr-preview-comment -->";
const postLine = process.env.POST_PATH
? `- Changed post detected: \`${process.env.POST_PATH}\`\n- Suggested file to open after extracting: \`_site${process.env.POST_URL}index.html\``
: "- No post markdown file changed in this PR; preview starts at `_site/index.html`.";
const previewUrl = process.env.CLOUDFLARE_URL || process.env.CLOUDFLARE_DEPLOYMENT_URL;

const cloudflareLine = process.env.CLOUDFLARE_ENABLED === "true"
? (process.env.CLOUDFLARE_RESULT === "success"
? (process.env.CLOUDFLARE_URL
? `- Hosted preview (Cloudflare Pages): [Open preview](${process.env.CLOUDFLARE_URL})`
? (previewUrl
? `- Hosted preview (Cloudflare Pages): [Open preview](${previewUrl})`
: "- Hosted preview (Cloudflare Pages): deployed, but URL output was empty. Check deployment details.")
: "- Hosted preview (Cloudflare Pages): deployment did not succeed; check workflow logs.")
: `- Hosted preview (Cloudflare Pages): deployment did not succeed; check [workflow logs](${process.env.RUN_URL}).`)
: "- Hosted preview (Cloudflare Pages): not configured (set repository vars/secrets; see README).";

const body = `${marker}
## PR Preview Ready

A static preview was built for this PR.

- Download artifact: **${process.env.ARTIFACT_NAME}** from [this workflow run](${process.env.RUN_URL})
- Download long screenshot artifact: **${process.env.SCREENSHOT_NAME}** from [this workflow run](${process.env.RUN_URL})
${postLine}
${cloudflareLine}
- Quick local preview:
- Extract artifact zip
- Run: \`python3 -m http.server --directory _site 4173\`
- Open: \`http://localhost:4173${process.env.POST_URL}\`
`;

const { owner, repo } = context.repo;
Expand Down
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,8 @@ This repo now includes a PR preview workflow at `.github/workflows/pr-preview.ym
For each pull request update, it:

1. Builds the site with Jekyll.
2. Uploads the full `_site` output as a workflow artifact.
3. Captures a full-page screenshot artifact for quick visual review.
4. Optionally deploys to Cloudflare Pages for a hosted preview URL.
5. Posts or updates a sticky PR comment with links and instructions.
2. Deploys to Cloudflare Pages for a hosted preview URL.
3. Posts or updates a sticky PR comment with the preview link.

This gives reviewers a consistent way to preview rendered pages without running Jekyll locally.

Expand All @@ -63,4 +61,4 @@ Notes:

- Cloudflare deploy is skipped automatically if these vars/secrets are missing.
- Cloudflare deploy is also skipped for forked PRs for security.
- Artifact and screenshot previews still run even when Cloudflare is skipped.
- No local artifact or screenshot is generated by this workflow.
Loading