Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
334 changes: 334 additions & 0 deletions .github/workflows/cloudflare-preview-forks.yml
Original file line number Diff line number Diff line change
@@ -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'
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Preview placeholder</title>
<meta name="robots" content="noindex,nofollow">
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; padding: 2rem; line-height: 1.5; }
code { background: #f4f4f4; padding: 0.1rem 0.3rem; border-radius: 4px; }
</style>
</head>
<body>
<h1>Cloudflare Pages preview placeholder</h1>
<p>No build system or output directory was detected for this PR preview.</p>
<p>To enable real previews, set repository variables in the base repo:</p>
<ul>
<li><code>PAGES_WORKING_DIR</code> (optional): project subfolder (e.g., <code>site</code>).</li>
<li><code>PAGES_BUILD_CMD</code> (e.g., <code>hugo --minify</code> or <code>npm ci && npm run build</code>).</li>
<li><code>PAGES_OUTPUT_DIR</code> (e.g., <code>public</code> or <code>dist</code>).</li>
</ul>
</body>
</html>
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.',
});
25 changes: 25 additions & 0 deletions src/content/blog/ddev-august-2026-newsletter.md
Original file line number Diff line number Diff line change
@@ -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)
Loading