name: PR Preview on: issue_comment: types: [created] pull_request_target: types: [labeled, synchronize, closed] concurrency: group: pr-preview cancel-in-progress: false env: GITHUB_PR_NUMBER: ${{ github.event_name == 'issue_comment' && github.event.issue.number || github.event.pull_request.number }} JEKYLL_ENV: production jobs: preview-remove: if: >- ${{ !github.event.repository.fork && ( ( github.event_name == 'pull_request_target' && ( github.event.action == 'closed' || ( github.event.action == 'labeled' && github.event.label.name == 'preview/remove' ) ) ) || ( github.event_name == 'issue_comment' && github.event.issue.pull_request && github.event.comment.body == '/preview remove' && contains('OWNER,MEMBER', github.event.comment.author_association) ) ) }} runs-on: ubuntu-latest permissions: actions: write contents: write pull-requests: write steps: - id: restore-pages name: Restore Pages uses: actions/cache/restore@v4 with: key: pages-${{ github.run_id }} path: /home/runner/gh-pages restore-keys: pages- - name: Prepare Pages run: | mkdir -p /home/runner/gh-pages/data/{files,refs} rm -rf /home/runner/gh-pages/PR${{ env.GITHUB_PR_NUMBER }} grep -rlw 'PR${{ env.GITHUB_PR_NUMBER }}' /home/runner/gh-pages/data/refs | xargs -r sed -i '/\/d' for ref in /home/runner/gh-pages/data/refs/*; do if [ ! -s "$ref" ]; then base=$(basename "$ref") rm -f "/home/runner/gh-pages/data/files/$base" rm -f "$ref" fi done echo "[$(date)] Remove Preview PR${{ env.GITHUB_PR_NUMBER }}" > /home/runner/gh-pages/index.html - name: Save Pages uses: actions/cache/save@v4 with: key: pages-${{ github.run_id }} path: /home/runner/gh-pages - if: ${{ github.event_name == 'issue_comment' }} id: comment-message name: Comment Message (issue_comment) uses: actions/github-script@v8 with: script: | github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: context.payload.comment.id, issue_number: context.issue.number, body: "[Preview] Preview has been removed." }); - if: ${{ github.event_name == 'pull_request_target' || steps.comment-message.outcome == 'failure' }} name: Comment Message uses: actions/github-script@v8 with: script: | github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: "[Preview] Preview has been removed." }); - if: ${{ steps.restore-pages.outputs.cache-matched-key }} name: Remove History Cache uses: actions/github-script@v8 with: script: | github.rest.actions.deleteActionsCacheByKey({ owner: context.repo.owner, repo: context.repo.repo, key: "${{ steps.restore-pages.outputs.cache-matched-key }}" }); - if: ${{ contains(github.event.pull_request.labels.*.name, 'preview/remove') || contains(github.event.issue.labels.*.name, 'preview/remove') }} name: Remove Label (preview/remove) continue-on-error: true uses: actions/github-script@v8 with: script: | github.rest.issues.removeLabel({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, name: "preview/remove" }); - if: ${{ contains(github.event.pull_request.labels.*.name, 'preview/watch') || contains(github.event.issue.labels.*.name, 'preview/watch') }} name: Remove Label (preview/watch) continue-on-error: true uses: actions/github-script@v8 with: script: | github.rest.issues.removeLabel({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, name: "preview/watch" }); preview-create-init: if: >- ${{ !github.event.repository.fork && ( ( github.event_name == 'pull_request_target' && ( ( github.event.action == 'labeled' && contains(fromJSON('["preview/create", "preview/watch"]'), github.event.label.name) ) || ( github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'preview/watch') ) ) ) || ( github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(fromJSON('["/preview create", "/preview watch"]'), github.event.comment.body) && contains('OWNER,MEMBER', github.event.comment.author_association) ) ) }} runs-on: ubuntu-latest permissions: checks: write pull-requests: write outputs: sha: ${{ steps.init.outputs.sha }} domain: ${{ steps.init.outputs.domain }} pathname: ${{ steps.init.outputs.pathname }} repository: ${{ steps.init.outputs.repository }} check-id: ${{ steps.init.outputs.check-id }} steps: - id: init name: Init uses: actions/github-script@v8 with: script: | const { owner, repo } = context.repo; const pages = (await octokit.rest.repos.getPages({ owner, repo })).data; const domain = pages.cname || `${owner.toLowerCase()}.github.io`; core.setOutput('domain', domain); core.setOutput('pathname', (pages.cname || repo.toLowerCase() === domain) ? '' : `/${repo}`); const pr = context.payload.pull_request || (await github.rest.pulls.get({ owner, repo, pull_number: context.issue.number })).data; core.setOutput('sha', pr.head.sha); core.setOutput('repository', pr.head.repo.full_name); const { data: check } = await github.rest.checks.create({ owner, repo, name: "Create preview", head_sha: pr.head.sha, status: "in_progress", started_at: new Date().toISOString(), details_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${{ github.run_id }}`, output: { title: "Create preview by command", summary: "Preview is being created..." } }); core.setOutput('check-id', check.id); - if: ${{ github.event_name == 'issue_comment' && github.event.comment.body == '/preview watch' }} name: Add Label (preview/watch) continue-on-error: true uses: actions/github-script@v8 with: script: | github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, labels: ["preview/watch"] }); preview-create-build: needs: preview-create-init runs-on: ubuntu-latest outputs: artifact-id: ${{ steps.upload-site.outputs.artifact-id }} steps: - name: Checkout PR uses: actions/checkout@v5 with: ref: ${{ needs.preview-create-init.outputs.sha }} repository: ${{ needs.preview-create-init.outputs.repository }} fetch-depth: 1 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" bundler-cache: true cache-version: pr${{ env.GITHUB_PR_NUMBER }} - name: Jekyll Build run: | echo "url: https://${{ needs.preview-create-init.outputs.domain }}" > _action.yml echo "baseurl: ${{ needs.preview-create-init.outputs.pathname }}/PR${{ env.GITHUB_PR_NUMBER }}" >> _action.yml echo "source: ${{ github.workspace }}" >> _action.yml echo "destination: /home/runner/site" >> _action.yml bundle exec jekyll build --config _config.yml,_action.yml - id: upload-site name: Upload Site uses: actions/upload-artifact@v4 with: name: site path: /home/runner/site preview-create-deploy: needs: [preview-create-init, preview-create-build] runs-on: ubuntu-latest environment: name: github-pages url: https://${{ needs.preview-create-init.outputs.domain }}${{ needs.preview-create-init.outputs.pathname }}/PR${{ env.GITHUB_PR_NUMBER }} permissions: actions: write pages: write id-token: write contents: write steps: - id: restore-pages name: Restore Pages uses: actions/cache/restore@v4 with: key: pages-${{ github.run_id }} path: /home/runner/gh-pages restore-keys: pages- - name: Merge Site Before run: | mkdir -p /home/runner/gh-pages/data/{files,refs} rm -rf /home/runner/gh-pages/PR${{ env.GITHUB_PR_NUMBER }} grep -rlw 'PR${{ env.GITHUB_PR_NUMBER }}' /home/runner/gh-pages/data/refs | xargs -r sed -i '/\/d' - name: Merge Site uses: actions/download-artifact@v5 with: name: site path: /home/runner/gh-pages/PR${{ env.GITHUB_PR_NUMBER }} - name: Merge Site After run: | find /home/runner/gh-pages/PR${{ env.GITHUB_PR_NUMBER }} -type f | while read file; do hash=$(sha256sum "$file" | cut -d' ' -f1) size=$(stat -c%s "$file") key="${hash}-${size}" target="/home/runner/gh-pages/data/files/$key" if [ ! -f "$target" ]; then mv "$file" "$target" else rm -f "$file" fi ln -s "$(realpath --relative-to="$(dirname "$file")" "$target")" "$file" echo "PR${{ env.GITHUB_PR_NUMBER }}" >> "/home/runner/gh-pages/data/refs/$key" done for ref in /home/runner/gh-pages/data/refs/*; do if [ ! -s "$ref" ]; then base=$(basename "$ref") rm -f "/home/runner/gh-pages/data/files/$base" rm -f "$ref" fi done echo "[$(date)] Preview PR${{ env.GITHUB_PR_NUMBER }}" > /home/runner/gh-pages/index.html - id: upload-pages name: Upload Pages uses: actions/upload-pages-artifact@v4 with: name: github-pages path: /home/runner/gh-pages - name: Deploy Pages uses: actions/deploy-pages@v4 with: artifact_name: github-pages - name: Save Pages uses: actions/cache/save@v4 with: key: pages-${{ github.run_id }} path: /home/runner/gh-pages - if: ${{ steps.restore-pages.outputs.cache-matched-key }} name: Remove History Cache uses: actions/github-script@v8 with: script: | github.rest.actions.deleteActionsCacheByKey({ owner: context.repo.owner, repo: context.repo.repo, key: "${{ steps.restore-pages.outputs.cache-matched-key }}" }); - if: ${{ steps.upload-pages.outputs.artifact_id }} name: Remove Pages Artifact uses: actions/github-script@v8 with: script: | github.rest.actions.deleteArtifact({ owner: context.repo.owner, repo: context.repo.repo, artifact_id: "${{ steps.upload-pages.outputs.artifact_id }}", }); - if: ${{ needs.preview-create-build.outputs.artifact-id }} name: Remove Temp Artifact uses: actions/github-script@v8 with: script: | github.rest.actions.deleteArtifact({ owner: context.repo.owner, repo: context.repo.repo, artifact_id: "${{ needs.preview-create-build.outputs.artifact-id }}" }); preview-create-finally: if: ${{ always() && needs.preview-create-init.result == 'success' }} needs: [preview-create-init, preview-create-deploy] runs-on: ubuntu-latest permissions: checks: write pull-requests: write steps: - if: ${{ github.event_name == 'issue_comment' }} id: comment-message name: Comment Message (issue_comment) env: PREVIEW_URL: https://${{ needs.preview-create-init.outputs.domain }}${{ needs.preview-create-init.outputs.pathname }}/PR${{ env.GITHUB_PR_NUMBER }} continue-on-error: true uses: actions/github-script@v8 with: script: | github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: context.payload.comment.id, issue_number: context.issue.number, body: "${{ needs.preview-create-deploy.result == 'success' && format('[Preview] {0}', env.PREVIEW_URL) || '[Preview] Failed, see logs for more information.' }}" }); - if: ${{ steps.comment-message.outcome == 'failure' }} name: Comment Message env: PREVIEW_URL: https://${{ needs.preview-create-init.outputs.domain }}${{ needs.preview-create-init.outputs.pathname }}/PR${{ env.GITHUB_PR_NUMBER }} uses: actions/github-script@v8 with: script: | github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: "${{ needs.preview-create-deploy.result == 'success' && format('[Preview] {0}', env.PREVIEW_URL) || '[Preview] Failed, see logs for more information.' }}" }); - if: ${{ contains(github.event.pull_request.labels.*.name, 'preview/create') || contains(github.event.issue.labels.*.name, 'preview/create') }} name: Remove Label (preview/create) continue-on-error: true uses: actions/github-script@v8 with: script: | github.rest.issues.removeLabel({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, name: "preview/create" }); - name: Check Over env: PREVIEW_URL: https://${{ needs.preview-create-init.outputs.domain }}${{ needs.preview-create-init.outputs.pathname }}/PR${{ env.GITHUB_PR_NUMBER }} uses: actions/github-script@v8 with: script: | github.rest.checks.update({ owner: context.repo.owner, repo: context.repo.repo, check_run_id: ${{ needs.preview-create-init.outputs.check-id }}, status: "completed", conclusion: "${{ needs.preview-create-deploy.result == 'success' && 'success' || 'failure' }}", completed_at: new Date().toISOString(), details_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/runs/${{ github.run_id }}`, output: { title: "Create preview ${{ needs.preview-create-deploy.result == 'success' && 'success' || 'failure' }}", summary: "${{ needs.preview-create-deploy.result == 'success' && format('[Preview] {0}', env.PREVIEW_URL) || '[Preview] Failed, see logs for more information.' }}" } });