diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml new file mode 100644 index 00000000..9db88fd0 --- /dev/null +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -0,0 +1,334 @@ +# Tip: This workflow must be present on the base repo's default branch (e.g., main) for pull_request_target to trigger. +name: Cloudflare Pages preview (forked PRs) +# Requires a Cloudflare Pages project (Direct Upload). CF_PAGES_PROJECT must be that project name. +# No GitHub App integration is required for this workflow; deployments are done via API token. + +on: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review, closed] + +# Least privilege at the workflow level +permissions: + contents: read + +concurrency: + group: fork-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + build: + name: Build site (no secrets) + if: ${{ github.event.pull_request.head.repo.fork == true && github.event.action != 'closed' }} + runs-on: ubuntu-latest + permissions: + contents: read + env: + # Optional repo variables (Settings > Secrets and variables > Actions > Variables) + # If set, PAGES_BUILD_CMD will be executed and PAGES_OUTPUT_DIR used for packaging. + PAGES_BUILD_CMD: ${{ vars.PAGES_BUILD_CMD }} + PAGES_OUTPUT_DIR: ${{ vars.PAGES_OUTPUT_DIR }} + # New: Optional working directory override (e.g., "site", "website", "docs") + PAGES_WORKING_DIR: ${{ vars.PAGES_WORKING_DIR }} + steps: + - name: Checkout PR code (from fork) + uses: actions/checkout@v4 + with: + # Important: explicit checkout of the fork + head SHA to avoid using base workflow code + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Detect working directory + id: workdir + shell: bash + run: | + set -euo pipefail + if [ -n "${PAGES_WORKING_DIR:-}" ]; then + if [ ! -d "$PAGES_WORKING_DIR" ]; then + echo "::error::PAGES_WORKING_DIR '$PAGES_WORKING_DIR' does not exist." + exit 1 + fi + echo "workdir=${PAGES_WORKING_DIR}" >> "$GITHUB_OUTPUT" + exit 0 + fi + # Look for common project subdirs with recognizable configs + is_proj_dir() { + local d="$1" + test -d "$d" || return 1 + [ -f "$d/package.json" ] && return 0 + [ -f "$d/pnpm-lock.yaml" ] && return 0 + [ -f "$d/yarn.lock" ] && return 0 + [ -f "$d/hugo.toml" ] || [ -f "$d/hugo.yaml" ] || [ -f "$d/hugo.yml" ] && return 0 + [ -f "$d/config.toml" ] || [ -f "$d/config.yaml" ] || [ -f "$d/config.yml" ] && return 0 + return 1 + } + if is_proj_dir "."; then echo "workdir=." >> "$GITHUB_OUTPUT"; exit 0; fi + for d in site website web docs app; do + if is_proj_dir "$d"; then echo "workdir=$d" >> "$GITHUB_OUTPUT"; exit 0; fi + done + # Fallback to repo root + echo "workdir=." >> "$GITHUB_OUTPUT" + + - name: Print repo and workdir for debugging + run: | + echo "Repo root: $(pwd)" + echo "Chosen workdir: ${{ steps.workdir.outputs.workdir }}" + ls -la + echo "---" + ls -la "${{ steps.workdir.outputs.workdir }}" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + check-latest: true + + - name: Detect build type + id: detect + shell: bash + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + set -euo pipefail + if [ -n "${PAGES_BUILD_CMD:-}" ]; then + echo "type=custom" >> "$GITHUB_OUTPUT" + exit 0 + fi + if [ -f pnpm-lock.yaml ]; then + echo "type=pnpm" >> "$GITHUB_OUTPUT"; exit 0 + fi + if [ -f yarn.lock ]; then + echo "type=yarn" >> "$GITHUB_OUTPUT"; exit 0 + fi + if [ -f package.json ]; then + echo "type=npm" >> "$GITHUB_OUTPUT"; exit 0 + fi + # Detect Hugo by common config files + if [ -f hugo.toml ] || [ -f hugo.yaml ] || [ -f hugo.yml ] || [ -f config.toml ] || [ -f config.yaml ] || [ -f config.yml ]; then + echo "type=hugo" >> "$GITHUB_OUTPUT"; exit 0 + fi + echo "::warning::Could not detect build system in $PWD. Will deploy a minimal placeholder site. Set repo variable PAGES_BUILD_CMD and optionally PAGES_OUTPUT_DIR/PAGES_WORKING_DIR for a real build." + echo "type=none" >> "$GITHUB_OUTPUT" + + - name: Build (custom) + if: ${{ steps.detect.outputs.type == 'custom' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + set -euo pipefail + echo "+ ${PAGES_BUILD_CMD}" + eval "${PAGES_BUILD_CMD}" + + - name: Enable Corepack (pnpm/yarn) + if: ${{ steps.detect.outputs.type == 'pnpm' || steps.detect.outputs.type == 'yarn' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: corepack enable + + - name: Install deps and build (pnpm) + if: ${{ steps.detect.outputs.type == 'pnpm' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + pnpm --version + pnpm install --frozen-lockfile + pnpm run build + + - name: Install deps and build (yarn) + if: ${{ steps.detect.outputs.type == 'yarn' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + yarn --version + yarn install --frozen-lockfile + yarn build + + - name: Install deps and build (npm) + if: ${{ steps.detect.outputs.type == 'npm' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + npm ci || npm install + npm run build + + - name: Setup Hugo + if: ${{ steps.detect.outputs.type == 'hugo' }} + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: 'latest' + extended: true + + - name: Build (Hugo) + if: ${{ steps.detect.outputs.type == 'hugo' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: hugo --minify + + - name: Determine output directory + id: outdir + shell: bash + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + set -euo pipefail + if [ -n "${PAGES_OUTPUT_DIR:-}" ]; then + OUTDIR="${PAGES_OUTPUT_DIR}" + else + # Prefer Hugo 'public' for hugo builds + if [ "${{ steps.detect.outputs.type }}" = "hugo" ] && [ -d public ]; then + OUTDIR="public" + else + for d in dist build .output/public .vercel/output/static out public site _site; do + if [ -d "$d" ]; then OUTDIR="$d"; break; fi + done + fi + fi + if [ -z "${OUTDIR:-}" ] || [ ! -d "$OUTDIR" ]; then + if [ "${{ steps.detect.outputs.type }}" = "none" ]; then + OUTDIR=".cloudflare-fallback" + mkdir -p "$OUTDIR" + cat > "$OUTDIR/index.html" <<'HTML' + + +
+ +No build system or output directory was detected for this PR preview.
+To enable real previews, set repository variables in the base repo:
+PAGES_WORKING_DIR (optional): project subfolder (e.g., site).PAGES_BUILD_CMD (e.g., hugo --minify or npm ci && npm run build).PAGES_OUTPUT_DIR (e.g., public or dist).