提供 PR 预览部署 #70
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: 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 '/\<PR${{ env.GITHUB_PR_NUMBER }}\>/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 | |
| env: | |
| PREVIEW_WATCH: | |
| ${{ | |
| contains(github.event.pull_request.labels.*.name, 'preview/watch') || | |
| contains(github.event.issue.labels.*.name, 'preview/watch') || ( | |
| github.event_name == 'issue_comment' && | |
| github.event.comment.body == '/preview watch' | |
| ) | |
| }} | |
| outputs: | |
| artifact-id: ${{ steps.upload-site.outputs.artifact-id }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Merge PR | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git fetch https://github.com/${{ needs.preview-create-init.outputs.repository }} ${{ needs.preview-create-init.outputs.sha }} | |
| git merge --squash ${{ needs.preview-create-init.outputs.sha }} --no-edit | |
| git commit -m "Merge #${{ env.GITHUB_PR_NUMBER }} for preview build" | |
| - name: Setup Ruby | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: "3.4" | |
| bundler-cache: ${{ env.PREVIEW_WATCH }} | |
| 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 | |
| echo "preview:" >> _action.yml | |
| echo " pr-number: ${{ env.GITHUB_PR_NUMBER }}" >> _action.yml | |
| ${{ env.PREVIEW_WATCH }} || bundle install --jobs 4 | |
| 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 '/\<PR${{ env.GITHUB_PR_NUMBER }}\>/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.' }}" | |
| } | |
| }); |