diff --git a/.github/workflows/gh-pages-cleanup.yml b/.github/workflows/gh-pages-cleanup.yml new file mode 100644 index 0000000..df1034f --- /dev/null +++ b/.github/workflows/gh-pages-cleanup.yml @@ -0,0 +1,32 @@ +name: GH Pages Cleanup + +on: + schedule: + - cron: '0 3 1 * *' # monthly, 1st day 3am UTC + workflow_dispatch: + +jobs: + cleanup: + name: Squash History + runs-on: ubuntu-22.04 + permissions: + contents: write + steps: + - name: Squash gh-pages history + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + + const { data: ref } = await github.rest.git.getRef({ owner, repo, ref: 'heads/gh-pages' }); + const { data: commit } = await github.rest.git.getCommit({ owner, repo, commit_sha: ref.object.sha }); + + // Create an orphan commit (no parents) reusing the current tree + const { data: newCommit } = await github.rest.git.createCommit({ + owner, repo, + message: 'Squash gh-pages history', + tree: commit.tree.sha, + parents: [] + }); + await github.rest.git.updateRef({ owner, repo, ref: 'heads/gh-pages', sha: newCommit.sha, force: true }); diff --git a/.github/workflows/pr-preview-build.yml b/.github/workflows/pr-preview-build.yml new file mode 100644 index 0000000..1181c06 --- /dev/null +++ b/.github/workflows/pr-preview-build.yml @@ -0,0 +1,48 @@ +name: PR Preview Build + +on: + pull_request: + branches: [master] + +permissions: + contents: read + +concurrency: + group: pr-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + build: + name: Build Preview + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + submodules: recursive + - name: Setup Hugo + uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 # v3.2.1 + with: + hugo-version: '0.78.1' + extended: true + - name: Compute preview base URL + id: base + run: | + OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + REPO="${{ github.event.repository.name }}" + echo "url=https://${OWNER}.github.io/${REPO}/pr-previews/pr-${{ github.event.number }}/" >> "$GITHUB_OUTPUT" + - name: Build website + run: hugo --gc --minify --buildFuture -b "${{ steps.base.outputs.url }}" + - name: Save PR number + run: | + mkdir -p ./pr-metadata + echo "${{ github.event.number }}" > ./pr-metadata/pr-number + - name: Upload preview artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: preview-site + path: ./public + - name: Upload PR metadata + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: pr-metadata + path: ./pr-metadata diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml new file mode 100644 index 0000000..25ffbaf --- /dev/null +++ b/.github/workflows/pr-preview-deploy.yml @@ -0,0 +1,70 @@ +name: PR Preview Deploy + +# NOTE: Do NOT add any steps that run scripts from the repository (e.g., npm install, +# npm run, or any other commands that execute repository code). Doing so would allow +# malicious PR authors to execute arbitrary code with access to repository secrets. + +on: + workflow_run: + workflows: ['PR Preview Build'] + types: [completed] + +permissions: + actions: read + contents: write + pull-requests: write + +concurrency: + group: pr-preview-deploy + cancel-in-progress: false + +jobs: + deploy: + name: Deploy Preview + if: > + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-22.04 + steps: + - name: Download PR metadata + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: pr-metadata + path: ./pr-metadata + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + # The pr-number artifact comes from the untrusted (fork-controlled) build + # job. Reading a number is low-risk, but we still sanity-check it's digits + # only so it can't inject output or traverse paths when used below. + - name: Read PR number + id: pr + run: | + number="$(cat ./pr-metadata/pr-number)" + [[ "$number" =~ ^[0-9]+$ ]] || { echo "::error::Refusing non-numeric PR number from build artifact"; exit 1; } + echo "number=$number" >> "$GITHUB_OUTPUT" + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Download preview site + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: preview-site + path: ./preview-site + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + - name: Deploy preview + uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1.8.1 + id: preview + with: + source-dir: ./preview-site + preview-branch: gh-pages + umbrella-dir: pr-previews + pr-number: ${{ steps.pr.outputs.number }} + action: deploy + comment: false + - name: Comment on PR + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 + with: + header: pr-preview + number: ${{ steps.pr.outputs.number }} + message: | + 🚀 **Website preview deployed to** ${{ steps.preview.outputs.deployment-url }} diff --git a/.github/workflows/pr-preview-remove.yml b/.github/workflows/pr-preview-remove.yml new file mode 100644 index 0000000..f3053cd --- /dev/null +++ b/.github/workflows/pr-preview-remove.yml @@ -0,0 +1,43 @@ +name: PR Preview Remove + +# SECURITY: This workflow uses pull_request_target which has access to secrets. +# It is safe because: +# 1. It only triggers on PR close (not open/sync) +# 2. It only checks out the base branch (not PR code) +# 3. It never executes any PR-controlled scripts +# +# NOTE: Do NOT add any steps that run scripts from the repository (e.g., npm install, +# npm run, or any other commands that execute repository code). Doing so would allow +# malicious PR authors to execute arbitrary code with access to repository secrets. + +on: + pull_request_target: + types: [closed] + +permissions: + contents: write + pull-requests: write + +concurrency: + group: pr-preview-deploy + cancel-in-progress: false + +jobs: + remove: + name: Remove Preview + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Remove preview + uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1.8.1 + with: + preview-branch: gh-pages + umbrella-dir: pr-previews + action: remove + - name: Update PR comment + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 + with: + header: pr-preview + number: ${{ github.event.pull_request.number }} + message: '**🌐 Website preview:** Removed (PR closed).' diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index f2b3d52..0000000 --- a/netlify.toml +++ /dev/null @@ -1,35 +0,0 @@ -[build] -publish = "public" -command = "hugo --gc --minify -b $DEPLOY_PRIME_URL" - -[context.production.environment] -HUGO_VERSION = "0.78.1" -HUGO_ENV = "production" -HUGO_ENABLEGITINFO = "true" - -[context.split1] -command = "hugo --gc --minify --enableGitInfo" - -[context.split1.environment] -HUGO_VERSION = "0.78.1" -HUGO_ENV = "production" - -[context.deploy-preview] -command = "hugo --gc --minify --buildFuture -b $DEPLOY_PRIME_URL" - -[context.deploy-preview.environment] -HUGO_VERSION = "0.78.1" - -[context.branch-deploy] -command = "hugo --gc --minify -b $DEPLOY_PRIME_URL" - -[context.branch-deploy.environment] -HUGO_VERSION = "0.78.1" - -[context.next.environment] -HUGO_ENABLEGITINFO = "true" - -[[headers]] - for = "/*" - [headers.values] - Access-Control-Allow-Origin = "*"