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' + + + + + Preview placeholder + + + + +

Cloudflare Pages preview placeholder

+

No build system or output directory was detected for this PR preview.

+

To enable real previews, set repository variables in the base repo:

+ + + +HTML + else + echo "::error::Could not determine output directory in $PWD. Set repo variable PAGES_OUTPUT_DIR." + exit 1 + fi + fi + echo "Using output dir: $OUTDIR" + echo "outdir=$OUTDIR" >> "$GITHUB_OUTPUT" + + - name: Package built site + run: | + mkdir -p artifact + SRC="${{ steps.workdir.outputs.workdir }}/${{ steps.outdir.outputs.outdir }}" + echo "Copying from: $SRC" + cp -a "$SRC"/. artifact/ + echo "Packaged $(find artifact -type f | wc -l) files." + + - name: Upload built artifact + uses: actions/upload-artifact@v4 + with: + name: site-dist + path: artifact + if-no-files-found: error + retention-days: 7 + + deploy: + name: Deploy preview to Cloudflare Pages + if: ${{ github.event.pull_request.head.repo.fork == true && github.event.action != 'closed' }} + runs-on: ubuntu-latest + needs: build + permissions: + contents: read + pull-requests: write + issues: write + steps: + - name: Check required secrets + env: + CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} + CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} + CF_PAGES_PROJECT: ${{ secrets.CF_PAGES_PROJECT }} + run: | + missing=0 + for v in CF_API_TOKEN CF_ACCOUNT_ID CF_PAGES_PROJECT; do + if [ -z "${!v}" ]; then + echo "::error::Missing repository secret '$v'." + missing=1 + fi + done + if [ "$missing" -ne 0 ]; then + echo "Set CF_API_TOKEN, CF_ACCOUNT_ID, CF_PAGES_PROJECT in repo settings. CF_PAGES_PROJECT must be a Cloudflare Pages Direct Upload project." + exit 1 + fi + + - name: Download built artifact + uses: actions/download-artifact@v4 + with: + name: site-dist + path: site-dist + + - name: Publish to Cloudflare Pages (preview) + id: pages + uses: cloudflare/pages-action@v1 + with: + # Required repo secrets (GitHub > Settings > Secrets and variables > Actions) + # CF_PAGES_PROJECT should be a Pages project created as "Direct Upload" (no Git integration). + apiToken: ${{ secrets.CF_API_TOKEN }} + accountId: ${{ secrets.CF_ACCOUNT_ID }} + projectName: ${{ secrets.CF_PAGES_PROJECT }} + directory: site-dist + # Stable per-PR preview + branch: pr-${{ github.event.pull_request.number }} + commitHash: ${{ github.event.pull_request.head.sha }} + wranglerVersion: '3' + + - name: Comment preview URL + if: ${{ always() }} + uses: actions/github-script@v7 + env: + PREVIEW_URL: ${{ steps.pages.outputs.deployment-url || steps.pages.outputs.url || '' }} + with: + script: | + const url = process.env.PREVIEW_URL; + if (!url) { + core.info('No preview URL found from Cloudflare action outputs.'); + return; + } + const body = `Cloudflare Pages preview: ${url}`; + // Update existing bot comment if present; otherwise create a new one + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const existing = comments.find(c => + c.user.type === 'Bot' && c.body && c.body.includes('Cloudflare Pages 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: context.issue.number, + body, + }); + } + + # Optional: do nothing on close (Cloudflare will mark preview inactive). + # You can add a small comment on close if desired. + closed-note: + name: Note on PR close + if: ${{ github.event.pull_request.head.repo.fork == true && github.event.action == 'closed' }} + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: 'PR closed. The Cloudflare Pages preview is no longer updated.', + }); diff --git a/src/content/blog/ddev-august-2026-newsletter.md b/src/content/blog/ddev-august-2026-newsletter.md new file mode 100644 index 00000000..f754d03f --- /dev/null +++ b/src/content/blog/ddev-august-2026-newsletter.md @@ -0,0 +1,25 @@ +--- +title: "DDEV August 2026 Newsletter" +pubDate: 2026-10-12 +#modifiedDate: 2025-04-07 +summary: "Did nothing at all happen in the last year???" +author: Randy Fay +featureImage: + src: /img/blog/2025/08/laracon-photo.jpg + alt: Nuno Maduro at Laracon US 2025 + caption: "Nuno Maduro at Laracon US 2025" +categories: + - Community +--- + +🚀 Dive into August 2026 with DDEV! 🌟 + +We're right where we were a year ago! + +## Stay in the Loop—Follow Us and Join the Conversation + +- [Blog↗](https://ddev.com/blog/) +- [LinkedIn↗](https://www.linkedin.com/company/ddev-foundation) +- [Mastodon↗](https://fosstodon.org/@ddev) +- [Bluesky↗](https://bsky.app/profile/ddev.bsky.social) +- [Discord↗](/s/discord)