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
152 changes: 152 additions & 0 deletions .github/workflows/pr-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
name: PR Preview Deploy

# This workflow deploys preview builds and cleans up closed PRs.
# It is triggered by the "PR Preview" workflow_run so that it always runs
# in the context of the base repository and receives a GITHUB_TOKEN with
# write access — even when the originating PR is from a fork that modifies
# workflow files (which would otherwise restrict the token to read-only).
on:
workflow_run:
workflows: ["PR Preview"]
types: [completed]

permissions:
contents: write
pull-requests: write
actions: read

jobs:
deploy-preview:
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Download preview artifact
id: download
uses: actions/download-artifact@v4
with:
pattern: pr-preview-*
path: ./artifacts
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true

- name: Find PR number and dist directory
id: pr
if: steps.download.outcome == 'success'
run: |
# Each workflow run processes exactly one PR, so at most one
# pr-preview-* artifact exists per run.
PREVIEW_DIR=$(find ./artifacts -maxdepth 1 -type d -name 'pr-preview-*' | head -1)
if [ -n "$PREVIEW_DIR" ]; then
PR_NUMBER=$(basename "$PREVIEW_DIR" | sed 's/pr-preview-//')
echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
echo "dist=$PREVIEW_DIR" >> "$GITHUB_OUTPUT"
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi

- name: Deploy preview to gh-pages
if: steps.pr.outputs.found == 'true'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ${{ steps.pr.outputs.dist }}
publish_branch: gh-pages
destination_dir: pr-previews/pr-${{ steps.pr.outputs.number }}
keep_files: true

- name: Post or update preview URL comment
if: steps.pr.outputs.found == 'true'
uses: actions/github-script@v7
with:
script: |
const prNumber = parseInt('${{ steps.pr.outputs.number }}', 10);
if (!prNumber || isNaN(prNumber)) {
core.setFailed('Could not determine PR number from artifact name');
return;
}
const previewUrl = `https://${{ github.repository_owner }}.github.io/ReproInventory/pr-previews/pr-${prNumber}/`;
const body = [
'## 🔍 PR Preview',
'',
`**Preview URL:** ${previewUrl}`,
'',
`_Last updated: ${new Date().toUTCString()}_`,
].join('\n');

const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});

const existing = comments.data.find(
c => c.user.type === 'Bot' && c.body.includes('## 🔍 PR Preview')
);

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}

cleanup-preview:
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Download close signal artifact
id: download
uses: actions/download-artifact@v4
with:
pattern: pr-closed-*
path: ./artifacts
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true

- name: Find closed PR number
id: pr
if: steps.download.outcome == 'success'
run: |
# Each workflow run processes exactly one PR, so at most one
# pr-closed-* artifact exists per run.
CLOSED_DIR=$(find ./artifacts -maxdepth 1 -type d -name 'pr-closed-*' | head -1)
if [ -n "$CLOSED_DIR" ]; then
PR_NUMBER=$(basename "$CLOSED_DIR" | sed 's/pr-closed-//')
echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi

- name: Checkout gh-pages branch
if: steps.pr.outputs.found == 'true'
uses: actions/checkout@v4
with:
ref: gh-pages

- name: Remove preview directory
if: steps.pr.outputs.found == 'true'
run: |
PR_DIR="pr-previews/pr-${{ steps.pr.outputs.number }}"
if [ -d "$PR_DIR" ]; then
rm -rf "$PR_DIR"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "chore: remove PR preview for #${{ steps.pr.outputs.number }}"
git push
else
echo "No preview directory found, nothing to clean up."
fi
86 changes: 18 additions & 68 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ on:
pull_request_target:
types: [opened, synchronize, reopened, closed]

# Read-only token is sufficient here; deploy/cleanup happen in pr-deploy.yml
# via workflow_run which is not subject to the fork-workflow-file restriction.
permissions:
contents: write
pull-requests: write
contents: read

jobs:
deploy-preview:
build-preview:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
Expand All @@ -31,74 +32,23 @@ jobs:
working-directory: ./frontend
run: npm run build -- --base=/ReproInventory/pr-previews/pr-${{ github.event.pull_request.number }}/

- name: Deploy preview
uses: peaceiris/actions-gh-pages@v3
- name: Upload preview artifact
uses: actions/upload-artifact@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./frontend/dist
publish_branch: gh-pages
destination_dir: pr-previews/pr-${{ github.event.pull_request.number }}
keep_files: true
name: pr-preview-${{ github.event.pull_request.number }}
path: frontend/dist/
retention-days: 1

- name: Post preview URL comment
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.pull_request.number;
const previewUrl = `https://${{ github.repository_owner }}.github.io/ReproInventory/pr-previews/pr-${prNumber}/`;
const body = [
'## 🔍 PR Preview',
'',
`**Preview URL:** ${previewUrl}`,
'',
`_Last updated: ${new Date().toUTCString()}_`,
].join('\n');

const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});

const existing = comments.data.find(
c => c.user.type === 'Bot' && c.body.includes('## 🔍 PR Preview')
);

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}

cleanup-preview:
record-close:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Checkout gh-pages branch
uses: actions/checkout@v4
with:
ref: gh-pages
- name: Create close signal
run: echo "closed" > close-signal.txt

- name: Remove preview directory
run: |
PR_DIR="pr-previews/pr-${{ github.event.pull_request.number }}"
if [ -d "$PR_DIR" ]; then
rm -rf "$PR_DIR"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "chore: remove PR preview for #${{ github.event.pull_request.number }}"
git push
else
echo "No preview directory found, nothing to clean up."
fi
- name: Upload close signal
uses: actions/upload-artifact@v4
with:
name: pr-closed-${{ github.event.pull_request.number }}
path: close-signal.txt
retention-days: 1
Loading