From 7a86bcf006a00641ccac57bdbb452890e1f34e9f Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Thu, 4 Sep 2025 15:03:01 -0600 Subject: [PATCH 01/25] build: allow forked PRs to build previews (experimental) --- .../workflows/cloudflare-preview-forks.yml | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 .github/workflows/cloudflare-preview-forks.yml diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml new file mode 100644 index 00000000..95bd8464 --- /dev/null +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -0,0 +1,175 @@ +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 + 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 + + # NOTE: + # - Configure your build exactly like your existing non-fork preview build, + # but without any secrets. Copy the same toolchain and commands. + # - The deploy job targets a Cloudflare Pages "Direct Upload" project. + - name: Setup build tooling + run: | + # ...existing code (e.g., setup Hugo/Node/PNPM/NPM/yarn/etc)... + echo "Setup steps as in your current preview workflow" + + - name: Build site + run: | + # ...existing code (exact same build commands you use today)... + # Example placeholders (replace with your real build): + # npm ci + # npm run build + # or: hugo --minify + echo "Build commands as in your current preview workflow" + + - name: Package built site + run: | + # Replace 'public' with your build output dir if different (e.g., 'dist') + # Ensure the directory exists before upload. + if [ ! -d "public" ] && [ ! -d "dist" ]; then + echo "Please adjust the output directory (public/dist) to match your build" + exit 1 + fi + OUTDIR="public" + [ -d "dist" ] && OUTDIR="dist" + echo "Using output dir: $OUTDIR" + mkdir -p artifact && cp -a "$OUTDIR"/. artifact/ + + - 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 + 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.', + }); From 6dab6bcec1d67707e21c1d279a711085e945f996 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Thu, 4 Sep 2025 15:07:27 -0600 Subject: [PATCH 02/25] details --- .github/workflows/cloudflare-preview-forks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 95bd8464..20f03dfe 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -1,3 +1,4 @@ +# 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. From df84051fa64339f231aa35ee8f21b16fb89b18e2 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Thu, 4 Sep 2025 15:16:55 -0600 Subject: [PATCH 03/25] Push the real thing up there in main --- .../workflows/cloudflare-preview-forks.yml | 108 ++++++++++++++---- 1 file changed, 86 insertions(+), 22 deletions(-) diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 20f03dfe..160148ac 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -22,6 +22,11 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + env: + # Optional repo variables for precise control (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 }} steps: - name: Checkout PR code (from fork) uses: actions/checkout@v4 @@ -31,36 +36,94 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false - # NOTE: - # - Configure your build exactly like your existing non-fork preview build, - # but without any secrets. Copy the same toolchain and commands. - # - The deploy job targets a Cloudflare Pages "Direct Upload" project. - - name: Setup build tooling + - name: Print repo root for debugging run: | - # ...existing code (e.g., setup Hugo/Node/PNPM/NPM/yarn/etc)... - echo "Setup steps as in your current preview workflow" + pwd + ls -la - - name: Build site + - name: Setup Node.js (for common JS builds) + uses: actions/setup-node@v4 + with: + node-version: 20 + check-latest: true + + - name: Build (auto-detect or use PAGES_BUILD_CMD) + id: build run: | - # ...existing code (exact same build commands you use today)... - # Example placeholders (replace with your real build): - # npm ci - # npm run build - # or: hugo --minify - echo "Build commands as in your current preview workflow" + set -euo pipefail - - name: Package built site + run_cmd() { echo "+ $*"; eval "$*"; } + + if [ -n "${PAGES_BUILD_CMD:-}" ]; then + echo "Using PAGES_BUILD_CMD from repo variables: ${PAGES_BUILD_CMD}" + run_cmd "${PAGES_BUILD_CMD}" + else + echo "Auto-detecting build system..." + if [ -f "pnpm-lock.yaml" ]; then + echo "Detected pnpm" + corepack enable >/dev/null 2>&1 || true + run_cmd "pnpm --version" + run_cmd "pnpm install --frozen-lockfile" + if jq -e '.scripts.build' package.json >/dev/null 2>&1; then + run_cmd "pnpm run build" + else + echo "No build script found in package.json"; exit 1 + fi + elif [ -f "yarn.lock" ]; then + echo "Detected yarn" + corepack enable >/dev/null 2>&1 || true + run_cmd "yarn --version" + run_cmd "yarn install --frozen-lockfile" + if jq -e '.scripts.build' package.json >/dev/null 2>&1; then + run_cmd "yarn build" + else + echo "No build script found in package.json"; exit 1 + fi + elif [ -f "package-lock.json" ] || [ -f "package.json" ]; then + echo "Detected npm" + run_cmd "npm ci || npm install" + if jq -e '.scripts.build' package.json >/dev/null 2>&1; then + run_cmd "npm run build" + else + echo "No build script found in package.json"; exit 1 + fi + elif [ -f "hugo.toml" ] || [ -f "hugo.yaml" ] || [ -f "hugo.yml" ] || [ -f "config.toml" ] || [ -f "config.yaml" ] || [ -f "config.yml" ]; then + echo "Detected Hugo" + sudo apt-get update -y + sudo apt-get install -y hugo + run_cmd "hugo version" + run_cmd "hugo --minify" + else + echo "Could not detect build system. Set repo variable PAGES_BUILD_CMD (and optionally PAGES_OUTPUT_DIR)." + exit 1 + fi + fi + + - name: Determine output directory + id: outdir run: | - # Replace 'public' with your build output dir if different (e.g., 'dist') - # Ensure the directory exists before upload. - if [ ! -d "public" ] && [ ! -d "dist" ]; then - echo "Please adjust the output directory (public/dist) to match your build" + set -e + if [ -n "${PAGES_OUTPUT_DIR:-}" ]; then + OUTDIR="${PAGES_OUTPUT_DIR}" + else + # Try common static output directories in priority order + 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 + if [ -z "${OUTDIR:-}" ] || [ ! -d "$OUTDIR" ]; then + echo "Could not determine output directory. Set repo variable PAGES_OUTPUT_DIR to the static build output." + echo "Checked candidates: ${PAGES_OUTPUT_DIR:-}, dist, build, .output/public, .vercel/output/static, out, public, site, _site" exit 1 fi - OUTDIR="public" - [ -d "dist" ] && OUTDIR="dist" echo "Using output dir: $OUTDIR" - mkdir -p artifact && cp -a "$OUTDIR"/. artifact/ + echo "outdir=$OUTDIR" >> "$GITHUB_OUTPUT" + + - name: Package built site + run: | + mkdir -p artifact + cp -a "${{ steps.outdir.outputs.outdir }}"/. artifact/ + echo "Packaged $(find artifact -type f | wc -l) files." - name: Upload built artifact uses: actions/upload-artifact@v4 @@ -78,6 +141,7 @@ jobs: permissions: contents: read pull-requests: write + issues: write steps: - name: Check required secrets env: From af5bf04f92ab1ac9fbba60feca8ae4ac74d509c4 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Thu, 4 Sep 2025 15:32:26 -0600 Subject: [PATCH 04/25] Another round? --- .../workflows/cloudflare-preview-forks.yml | 131 ++++++++++-------- 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 160148ac..a6c22c9d 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -23,7 +23,7 @@ jobs: permissions: contents: read env: - # Optional repo variables for precise control (Settings > Secrets and variables > Actions > Variables) + # 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 }} @@ -41,79 +41,98 @@ jobs: pwd ls -la - - name: Setup Node.js (for common JS builds) + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 check-latest: true - - name: Build (auto-detect or use PAGES_BUILD_CMD) - id: build + - name: Detect build type + id: detect + shell: bash run: | set -euo pipefail - - run_cmd() { echo "+ $*"; eval "$*"; } - if [ -n "${PAGES_BUILD_CMD:-}" ]; then - echo "Using PAGES_BUILD_CMD from repo variables: ${PAGES_BUILD_CMD}" - run_cmd "${PAGES_BUILD_CMD}" - else - echo "Auto-detecting build system..." - if [ -f "pnpm-lock.yaml" ]; then - echo "Detected pnpm" - corepack enable >/dev/null 2>&1 || true - run_cmd "pnpm --version" - run_cmd "pnpm install --frozen-lockfile" - if jq -e '.scripts.build' package.json >/dev/null 2>&1; then - run_cmd "pnpm run build" - else - echo "No build script found in package.json"; exit 1 - fi - elif [ -f "yarn.lock" ]; then - echo "Detected yarn" - corepack enable >/dev/null 2>&1 || true - run_cmd "yarn --version" - run_cmd "yarn install --frozen-lockfile" - if jq -e '.scripts.build' package.json >/dev/null 2>&1; then - run_cmd "yarn build" - else - echo "No build script found in package.json"; exit 1 - fi - elif [ -f "package-lock.json" ] || [ -f "package.json" ]; then - echo "Detected npm" - run_cmd "npm ci || npm install" - if jq -e '.scripts.build' package.json >/dev/null 2>&1; then - run_cmd "npm run build" - else - echo "No build script found in package.json"; exit 1 - fi - elif [ -f "hugo.toml" ] || [ -f "hugo.yaml" ] || [ -f "hugo.yml" ] || [ -f "config.toml" ] || [ -f "config.yaml" ] || [ -f "config.yml" ]; then - echo "Detected Hugo" - sudo apt-get update -y - sudo apt-get install -y hugo - run_cmd "hugo version" - run_cmd "hugo --minify" - else - echo "Could not detect build system. Set repo variable PAGES_BUILD_CMD (and optionally PAGES_OUTPUT_DIR)." - exit 1 - fi + 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 "::error::Could not detect build system. Set repo variable PAGES_BUILD_CMD (and optionally PAGES_OUTPUT_DIR)." + exit 1 + + - name: Build (custom) + if: ${{ steps.detect.outputs.type == 'custom' }} + 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' }} + run: corepack enable + + - name: Install deps and build (pnpm) + if: ${{ steps.detect.outputs.type == 'pnpm' }} + run: | + pnpm --version + pnpm install --frozen-lockfile + pnpm run build + + - name: Install deps and build (yarn) + if: ${{ steps.detect.outputs.type == 'yarn' }} + run: | + yarn --version + yarn install --frozen-lockfile + yarn build + + - name: Install deps and build (npm) + if: ${{ steps.detect.outputs.type == 'npm' }} + 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' }} + run: hugo --minify - name: Determine output directory id: outdir + shell: bash run: | - set -e + set -euo pipefail if [ -n "${PAGES_OUTPUT_DIR:-}" ]; then OUTDIR="${PAGES_OUTPUT_DIR}" else - # Try common static output directories in priority order - for d in dist build .output/public .vercel/output/static out public site _site; do - if [ -d "$d" ]; then OUTDIR="$d"; break; fi - done + # 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 - echo "Could not determine output directory. Set repo variable PAGES_OUTPUT_DIR to the static build output." - echo "Checked candidates: ${PAGES_OUTPUT_DIR:-}, dist, build, .output/public, .vercel/output/static, out, public, site, _site" + echo "::error::Could not determine output directory. Set repo variable PAGES_OUTPUT_DIR." exit 1 fi echo "Using output dir: $OUTDIR" From ebb42f3625addd3d924ce333e8d8bef3ca4ff1bf Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Thu, 4 Sep 2025 15:40:05 -0600 Subject: [PATCH 05/25] Add debugging --- .../workflows/cloudflare-preview-forks.yml | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index a6c22c9d..b7b97845 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -27,6 +27,8 @@ jobs: # 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 @@ -36,10 +38,44 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false - - name: Print repo root for debugging + - 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: | - pwd + 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 @@ -50,6 +86,7 @@ jobs: - name: Detect build type id: detect shell: bash + working-directory: ${{ steps.workdir.outputs.workdir }} run: | set -euo pipefail if [ -n "${PAGES_BUILD_CMD:-}" ]; then @@ -69,11 +106,12 @@ jobs: 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 "::error::Could not detect build system. Set repo variable PAGES_BUILD_CMD (and optionally PAGES_OUTPUT_DIR)." + echo "::error::Could not detect build system in $PWD. Set repo variable PAGES_BUILD_CMD and optionally PAGES_OUTPUT_DIR/PAGES_WORKING_DIR." exit 1 - name: Build (custom) if: ${{ steps.detect.outputs.type == 'custom' }} + working-directory: ${{ steps.workdir.outputs.workdir }} run: | set -euo pipefail echo "+ ${PAGES_BUILD_CMD}" @@ -81,10 +119,12 @@ jobs: - 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 @@ -92,6 +132,7 @@ jobs: - 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 @@ -99,6 +140,7 @@ jobs: - 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 @@ -112,11 +154,13 @@ jobs: - 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 @@ -132,7 +176,7 @@ jobs: fi fi if [ -z "${OUTDIR:-}" ] || [ ! -d "$OUTDIR" ]; then - echo "::error::Could not determine output directory. Set repo variable PAGES_OUTPUT_DIR." + echo "::error::Could not determine output directory in $PWD. Set repo variable PAGES_OUTPUT_DIR." exit 1 fi echo "Using output dir: $OUTDIR" @@ -141,7 +185,9 @@ jobs: - name: Package built site run: | mkdir -p artifact - cp -a "${{ steps.outdir.outputs.outdir }}"/. 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 From 8e45c89d4047d100bf702fc613ba211e7a7eafe0 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Thu, 4 Sep 2025 15:46:21 -0600 Subject: [PATCH 06/25] it keeps fiddling --- .../workflows/cloudflare-preview-forks.yml | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index b7b97845..9db88fd0 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -106,8 +106,8 @@ jobs: 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 "::error::Could not detect build system in $PWD. Set repo variable PAGES_BUILD_CMD and optionally PAGES_OUTPUT_DIR/PAGES_WORKING_DIR." - exit 1 + 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' }} @@ -176,8 +176,37 @@ jobs: fi fi if [ -z "${OUTDIR:-}" ] || [ ! -d "$OUTDIR" ]; then - echo "::error::Could not determine output directory in $PWD. Set repo variable PAGES_OUTPUT_DIR." - exit 1 + 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:

+
    +
  • 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).
  • +
+ + +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" From aae7f918e30a66f466a8d6ac07a88720ef9974fe Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Thu, 4 Sep 2025 15:58:16 -0600 Subject: [PATCH 07/25] again --- .github/workflows/cloudflare-preview-forks.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 9db88fd0..2a294e6c 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -280,23 +280,23 @@ HTML if: ${{ always() }} uses: actions/github-script@v7 env: - PREVIEW_URL: ${{ steps.pages.outputs.deployment-url || steps.pages.outputs.url || '' }} + DEPLOYMENT_URL: ${{ steps.pages.outputs.deployment-url }} + ALT_URL: ${{ steps.pages.outputs.url }} with: script: | - const url = process.env.PREVIEW_URL; + const url = process.env.DEPLOYMENT_URL || process.env.ALT_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:') + c.user?.type === 'Bot' && c.body && c.body.includes('Cloudflare Pages preview:') ); if (existing) { await github.rest.issues.updateComment({ From a67475a837917606f65e9711c5774bf27243c5b8 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 10:19:41 -0600 Subject: [PATCH 08/25] Enhance fork preview workflow with security validation and documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add content validation checks for executable files and unsafe patterns - Integrate textlint and prettier quality checks before build - Validate blog post frontmatter structure (title, pubDate, author) - Add image size warnings for files >2MB - Fix YAML syntax issues with placeholder HTML generation - Create comprehensive setup guide with troubleshooting section - Document required Cloudflare secrets and configuration steps 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/FORK_PREVIEW_SETUP.md | 143 ++++++++++++++++++ .../workflows/cloudflare-preview-forks.yml | 92 ++++++++--- 2 files changed, 211 insertions(+), 24 deletions(-) create mode 100644 .github/FORK_PREVIEW_SETUP.md diff --git a/.github/FORK_PREVIEW_SETUP.md b/.github/FORK_PREVIEW_SETUP.md new file mode 100644 index 00000000..e35d5c33 --- /dev/null +++ b/.github/FORK_PREVIEW_SETUP.md @@ -0,0 +1,143 @@ +# Fork Preview Setup Guide + +This guide explains how to configure the automated preview generation for forked PRs using Cloudflare Pages. + +## Overview + +The workflow in `.github/workflows/cloudflare-preview-forks.yml` implements a secure two-stage process: +1. **Build Stage**: Safely builds the site from fork code without exposing secrets +2. **Deploy Stage**: Uses Cloudflare API to deploy the built site with secure credentials + +## Required Setup + +### 1. Cloudflare Pages Project + +Create a **Direct Upload** Cloudflare Pages project (not Git-connected): + +1. Go to [Cloudflare Pages](https://dash.cloudflare.com/pages) +2. Click "Create a project" +3. Choose "Direct Upload" (not "Connect to Git") +4. Name your project (e.g., `ddev-com-fork-previews`) +5. Note the project name for step 3 + +### 2. Cloudflare API Token + +Create an API token with Pages permissions: + +1. Go to [API Tokens](https://dash.cloudflare.com/profile/api-tokens) +2. Click "Create Token" +3. Use "Custom token" template +4. Set permissions: + - `Zone:Zone:Read` + - `Zone:Page Rules:Edit` + - `Account:Cloudflare Pages:Edit` +5. Set account and zone resources as needed +6. Save the token + +### 3. Repository Secrets + +Add these secrets in GitHub repository settings → Secrets and variables → Actions: + +- `CF_API_TOKEN`: The API token from step 2 +- `CF_ACCOUNT_ID`: Your Cloudflare Account ID (found in dashboard sidebar) +- `CF_PAGES_PROJECT`: The project name from step 1 + +### 4. Repository Variables (Optional) + +For custom build configurations, set these in GitHub repository settings → Secrets and variables → Actions → Variables: + +- `PAGES_BUILD_CMD`: Custom build command (e.g., `npm ci && npm run build`) +- `PAGES_OUTPUT_DIR`: Build output directory (e.g., `dist`, `public`, `build`) +- `PAGES_WORKING_DIR`: Project subdirectory if not root (e.g., `site`, `docs`) + +### 5. Enable Workflow + +The workflow is triggered automatically for: +- Forked repository PRs only +- Events: `opened`, `synchronize`, `reopened`, `ready_for_review`, `closed` + +## Security Features + +### Two-Stage Architecture +- **Stage 1 (Build)**: Runs fork code without any secrets +- **Stage 2 (Deploy)**: Uses secrets only after build artifact is created + +### Content Validation +- Checks for executable files in content directories +- Validates blog post frontmatter structure +- Detects potentially unsafe content patterns +- Warns about oversized images (>2MB) +- Runs textlint and prettier if available + +### Access Controls +- Only processes PRs from forked repositories +- Uses `pull_request_target` with explicit fork checkout +- Separates untrusted code execution from credential access + +## Workflow Behavior + +### Build Process +1. Detects build system (npm/yarn/pnpm/hugo/custom) +2. Runs content validation and security checks +3. Installs dependencies and runs linting +4. Builds the site +5. Packages output as artifact + +### Deployment Process +1. Downloads build artifact from Stage 1 +2. Deploys to Cloudflare Pages using API +3. Creates stable preview URL: `https://project.pages.dev/pr-{number}` +4. Comments preview URL on the PR +5. Updates comment on subsequent pushes + +### PR Lifecycle +- **Opened/Updated**: Creates or updates preview +- **Closed**: Adds closure note (preview remains accessible) +- **Draft**: Still builds and deploys (no special handling) + +## Troubleshooting + +### Build Failures +- Check build logs in GitHub Actions +- Ensure dependencies install correctly +- Verify build command produces output directory + +### Missing Secrets +- Workflow will fail with clear error messages +- Verify all three secrets are set correctly +- Check Cloudflare API token permissions + +### Content Validation Errors +- Review security check output +- Fix frontmatter issues in blog posts +- Address linting warnings locally with: + - `ddev npm run textlint:fix` + - `ddev npm run prettier:fix` + +### Preview URL Issues +- Verify Cloudflare Pages project exists +- Check account ID matches organization +- Ensure project name in `CF_PAGES_PROJECT` is exact + +## Manual Testing + +To test the workflow: + +1. Create a test fork of the repository +2. Make a content change (e.g., add a blog post) +3. Open a PR from the fork +4. Watch GitHub Actions for build/deploy progress +5. Check for preview URL comment on the PR + +## Maintenance + +### Regular Tasks +- Monitor Cloudflare Pages usage and costs +- Review security warnings in build logs +- Update dependencies in fork validation steps +- Clean up old preview deployments if needed + +### Updates +- Keep `cloudflare/pages-action` version current +- Monitor Cloudflare API changes +- Update content validation rules as needed \ No newline at end of file diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 2a294e6c..8ddfcedc 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -83,6 +83,43 @@ jobs: node-version: 20 check-latest: true + - name: Content validation and security checks + shell: bash + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + set -euo pipefail + echo "Running content validation and security checks..." + + # Check for potentially malicious files + if find . -name "*.php" -o -name "*.exe" -o -name "*.sh" -path "*/src/content/*" | grep -q .; then + echo "::warning::Executable files found in content directory. Manual review recommended." + fi + + # Validate blog post frontmatter structure + if [ -d "src/content/blog" ]; then + echo "Validating blog post structure..." + for file in src/content/blog/*.md; do + if [ -f "$file" ]; then + # Check for required frontmatter fields + if ! grep -q "^title:" "$file" || ! grep -q "^pubDate:" "$file" || ! grep -q "^author:" "$file"; then + echo "::error::Blog post $file missing required frontmatter (title, pubDate, author)" + exit 1 + fi + # Check for suspicious content patterns + if grep -qi "javascript:" "$file" || grep -qi "/dev/null | xargs -I {} sh -c 'size=$(stat -c%s "{}"); if [ $size -gt 2097152 ]; then echo "::warning::Large image detected: {} ($(($size/1024))KB)"; fi' 2>/dev/null || true; then + echo "Image size check completed" + fi + + echo "Content validation completed" + - name: Detect build type id: detect shell: bash @@ -109,6 +146,36 @@ jobs: 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: Install dependencies for linting + if: ${{ steps.detect.outputs.type == 'npm' || steps.detect.outputs.type == 'yarn' || steps.detect.outputs.type == 'pnpm' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + if [ -f package.json ]; then + if [ -f pnpm-lock.yaml ]; then + corepack enable && pnpm install --frozen-lockfile + elif [ -f yarn.lock ]; then + corepack enable && yarn install --frozen-lockfile + else + npm ci || npm install + fi + fi + + - name: Run content linting + if: ${{ steps.detect.outputs.type == 'npm' || steps.detect.outputs.type == 'yarn' || steps.detect.outputs.type == 'pnpm' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + # Run textlint if available (for content quality) + if [ -f package.json ] && npm list textlint >/dev/null 2>&1; then + echo "Running textlint..." + npm run textlint || echo "::warning::Textlint found issues. Consider running 'ddev npm run textlint:fix' locally." + fi + + # Run prettier check if available (for code formatting) + if [ -f package.json ] && npm list prettier >/dev/null 2>&1; then + echo "Running prettier check..." + npm run prettier || echo "::warning::Prettier found formatting issues. Consider running 'ddev npm run prettier:fix' locally." + fi + - name: Build (custom) if: ${{ steps.detect.outputs.type == 'custom' }} working-directory: ${{ steps.workdir.outputs.workdir }} @@ -179,30 +246,7 @@ jobs: 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:

-
    -
  • 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).
  • -
- - -HTML + echo '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:

  • 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).
' > "$OUTDIR/index.html" else echo "::error::Could not determine output directory in $PWD. Set repo variable PAGES_OUTPUT_DIR." exit 1 From 6c78b220287ccc3a1a86817830b5c817fd6353df Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 10:25:51 -0600 Subject: [PATCH 09/25] Pacify prettier --- .github/FORK_PREVIEW_SETUP.md | 20 ++++++++++++++++--- .../workflows/cloudflare-preview-forks.yml | 14 ++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/.github/FORK_PREVIEW_SETUP.md b/.github/FORK_PREVIEW_SETUP.md index e35d5c33..d4326104 100644 --- a/.github/FORK_PREVIEW_SETUP.md +++ b/.github/FORK_PREVIEW_SETUP.md @@ -5,6 +5,7 @@ This guide explains how to configure the automated preview generation for forked ## Overview The workflow in `.github/workflows/cloudflare-preview-forks.yml` implements a secure two-stage process: + 1. **Build Stage**: Safely builds the site from fork code without exposing secrets 2. **Deploy Stage**: Uses Cloudflare API to deploy the built site with secure credentials @@ -15,7 +16,7 @@ The workflow in `.github/workflows/cloudflare-preview-forks.yml` implements a se Create a **Direct Upload** Cloudflare Pages project (not Git-connected): 1. Go to [Cloudflare Pages](https://dash.cloudflare.com/pages) -2. Click "Create a project" +2. Click "Create a project" 3. Choose "Direct Upload" (not "Connect to Git") 4. Name your project (e.g., `ddev-com-fork-previews`) 5. Note the project name for step 3 @@ -28,7 +29,7 @@ Create an API token with Pages permissions: 2. Click "Create Token" 3. Use "Custom token" template 4. Set permissions: - - `Zone:Zone:Read` + - `Zone:Zone:Read` - `Zone:Page Rules:Edit` - `Account:Cloudflare Pages:Edit` 5. Set account and zone resources as needed @@ -53,16 +54,19 @@ For custom build configurations, set these in GitHub repository settings → Sec ### 5. Enable Workflow The workflow is triggered automatically for: + - Forked repository PRs only - Events: `opened`, `synchronize`, `reopened`, `ready_for_review`, `closed` ## Security Features ### Two-Stage Architecture + - **Stage 1 (Build)**: Runs fork code without any secrets - **Stage 2 (Deploy)**: Uses secrets only after build artifact is created ### Content Validation + - Checks for executable files in content directories - Validates blog post frontmatter structure - Detects potentially unsafe content patterns @@ -70,6 +74,7 @@ The workflow is triggered automatically for: - Runs textlint and prettier if available ### Access Controls + - Only processes PRs from forked repositories - Uses `pull_request_target` with explicit fork checkout - Separates untrusted code execution from credential access @@ -77,6 +82,7 @@ The workflow is triggered automatically for: ## Workflow Behavior ### Build Process + 1. Detects build system (npm/yarn/pnpm/hugo/custom) 2. Runs content validation and security checks 3. Installs dependencies and runs linting @@ -84,6 +90,7 @@ The workflow is triggered automatically for: 5. Packages output as artifact ### Deployment Process + 1. Downloads build artifact from Stage 1 2. Deploys to Cloudflare Pages using API 3. Creates stable preview URL: `https://project.pages.dev/pr-{number}` @@ -91,6 +98,7 @@ The workflow is triggered automatically for: 5. Updates comment on subsequent pushes ### PR Lifecycle + - **Opened/Updated**: Creates or updates preview - **Closed**: Adds closure note (preview remains accessible) - **Draft**: Still builds and deploys (no special handling) @@ -98,16 +106,19 @@ The workflow is triggered automatically for: ## Troubleshooting ### Build Failures + - Check build logs in GitHub Actions - Ensure dependencies install correctly - Verify build command produces output directory ### Missing Secrets + - Workflow will fail with clear error messages - Verify all three secrets are set correctly - Check Cloudflare API token permissions ### Content Validation Errors + - Review security check output - Fix frontmatter issues in blog posts - Address linting warnings locally with: @@ -115,6 +126,7 @@ The workflow is triggered automatically for: - `ddev npm run prettier:fix` ### Preview URL Issues + - Verify Cloudflare Pages project exists - Check account ID matches organization - Ensure project name in `CF_PAGES_PROJECT` is exact @@ -132,12 +144,14 @@ To test the workflow: ## Maintenance ### Regular Tasks + - Monitor Cloudflare Pages usage and costs - Review security warnings in build logs - Update dependencies in fork validation steps - Clean up old preview deployments if needed ### Updates + - Keep `cloudflare/pages-action` version current - Monitor Cloudflare API changes -- Update content validation rules as needed \ No newline at end of file +- Update content validation rules as needed diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 8ddfcedc..4dc10cb2 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -89,12 +89,12 @@ jobs: run: | set -euo pipefail echo "Running content validation and security checks..." - + # Check for potentially malicious files if find . -name "*.php" -o -name "*.exe" -o -name "*.sh" -path "*/src/content/*" | grep -q .; then echo "::warning::Executable files found in content directory. Manual review recommended." fi - + # Validate blog post frontmatter structure if [ -d "src/content/blog" ]; then echo "Validating blog post structure..." @@ -112,12 +112,12 @@ jobs: fi done fi - + # Check for oversized images if find public -name "*.jpg" -o -name "*.png" -o -name "*.jpeg" 2>/dev/null | xargs -I {} sh -c 'size=$(stat -c%s "{}"); if [ $size -gt 2097152 ]; then echo "::warning::Large image detected: {} ($(($size/1024))KB)"; fi' 2>/dev/null || true; then echo "Image size check completed" fi - + echo "Content validation completed" - name: Detect build type @@ -169,7 +169,7 @@ jobs: echo "Running textlint..." npm run textlint || echo "::warning::Textlint found issues. Consider running 'ddev npm run textlint:fix' locally." fi - + # Run prettier check if available (for code formatting) if [ -f package.json ] && npm list prettier >/dev/null 2>&1; then echo "Running prettier check..." @@ -216,7 +216,7 @@ jobs: if: ${{ steps.detect.outputs.type == 'hugo' }} uses: peaceiris/actions-hugo@v2 with: - hugo-version: 'latest' + hugo-version: "latest" extended: true - name: Build (Hugo) @@ -318,7 +318,7 @@ jobs: # Stable per-PR preview branch: pr-${{ github.event.pull_request.number }} commitHash: ${{ github.event.pull_request.head.sha }} - wranglerVersion: '3' + wranglerVersion: "3" - name: Comment preview URL if: ${{ always() }} From 1b421cbb1051057cea332b58c5d50d4ca6f398fa Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 10:40:39 -0600 Subject: [PATCH 10/25] Fix Cloudflare workflow to use vars instead of secrets for account/project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change CF_ACCOUNT_ID and CF_PAGES_PROJECT from secrets to vars references - Update documentation to clarify secrets vs variables configuration - This resolves deployment failures due to missing secret references 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/FORK_PREVIEW_SETUP.md | 7 +++++-- .github/workflows/cloudflare-preview-forks.yml | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/FORK_PREVIEW_SETUP.md b/.github/FORK_PREVIEW_SETUP.md index d4326104..48bb59e4 100644 --- a/.github/FORK_PREVIEW_SETUP.md +++ b/.github/FORK_PREVIEW_SETUP.md @@ -35,11 +35,14 @@ Create an API token with Pages permissions: 5. Set account and zone resources as needed 6. Save the token -### 3. Repository Secrets +### 3. Repository Secrets and Variables -Add these secrets in GitHub repository settings → Secrets and variables → Actions: +Add these in GitHub repository settings → Secrets and variables → Actions: +**Repository Secrets:** - `CF_API_TOKEN`: The API token from step 2 + +**Repository Variables:** - `CF_ACCOUNT_ID`: Your Cloudflare Account ID (found in dashboard sidebar) - `CF_PAGES_PROJECT`: The project name from step 1 diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 4dc10cb2..9ad7ea76 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -284,8 +284,8 @@ jobs: - 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 }} + CF_ACCOUNT_ID: ${{ vars.CF_ACCOUNT_ID }} + CF_PAGES_PROJECT: ${{ vars.CF_PAGES_PROJECT }} run: | missing=0 for v in CF_API_TOKEN CF_ACCOUNT_ID CF_PAGES_PROJECT; do @@ -309,11 +309,11 @@ jobs: id: pages uses: cloudflare/pages-action@v1 with: - # Required repo secrets (GitHub > Settings > Secrets and variables > Actions) + # Required repo secrets and variables (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 }} + accountId: ${{ vars.CF_ACCOUNT_ID }} + projectName: ${{ vars.CF_PAGES_PROJECT }} directory: site-dist # Stable per-PR preview branch: pr-${{ github.event.pull_request.number }} From a863d70a37495096523876a443f185aa872a101f Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 10:57:56 -0600 Subject: [PATCH 11/25] pacify prettier --- .github/FORK_PREVIEW_SETUP.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/FORK_PREVIEW_SETUP.md b/.github/FORK_PREVIEW_SETUP.md index 48bb59e4..c156e998 100644 --- a/.github/FORK_PREVIEW_SETUP.md +++ b/.github/FORK_PREVIEW_SETUP.md @@ -40,9 +40,11 @@ Create an API token with Pages permissions: Add these in GitHub repository settings → Secrets and variables → Actions: **Repository Secrets:** + - `CF_API_TOKEN`: The API token from step 2 **Repository Variables:** + - `CF_ACCOUNT_ID`: Your Cloudflare Account ID (found in dashboard sidebar) - `CF_PAGES_PROJECT`: The project name from step 1 From b08fb22b65ad8c372596d6f2433eca7a813aced4 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 11:28:34 -0600 Subject: [PATCH 12/25] Update fork preview workflow to use main ddev-com-front-end project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Configure workflow to use existing ddev-com-front-end Cloudflare project - Provides consistent preview URLs with main repository - Update documentation to reflect unified project approach - Preview URLs will be: https://pr-{number}.ddev-com-front-end.pages.dev - Eliminates need for separate Cloudflare project setup 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/FORK_PREVIEW_SETUP.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/FORK_PREVIEW_SETUP.md b/.github/FORK_PREVIEW_SETUP.md index c156e998..f4105ba4 100644 --- a/.github/FORK_PREVIEW_SETUP.md +++ b/.github/FORK_PREVIEW_SETUP.md @@ -13,13 +13,9 @@ The workflow in `.github/workflows/cloudflare-preview-forks.yml` implements a se ### 1. Cloudflare Pages Project -Create a **Direct Upload** Cloudflare Pages project (not Git-connected): +The workflow uses the existing `ddev-com-front-end` Cloudflare Pages project that serves the main site. This provides consistent preview URLs and centralized management. -1. Go to [Cloudflare Pages](https://dash.cloudflare.com/pages) -2. Click "Create a project" -3. Choose "Direct Upload" (not "Connect to Git") -4. Name your project (e.g., `ddev-com-fork-previews`) -5. Note the project name for step 3 +**No additional project setup needed** - the workflow will create `pr-{number}` branch deployments within the existing project using Cloudflare's Direct Upload API. ### 2. Cloudflare API Token @@ -46,7 +42,7 @@ Add these in GitHub repository settings → Secrets and variables → Actions: **Repository Variables:** - `CF_ACCOUNT_ID`: Your Cloudflare Account ID (found in dashboard sidebar) -- `CF_PAGES_PROJECT`: The project name from step 1 +- `CF_PAGES_PROJECT`: Set to `ddev-com-front-end` (the main site's Cloudflare project) ### 4. Repository Variables (Optional) @@ -98,7 +94,7 @@ The workflow is triggered automatically for: 1. Downloads build artifact from Stage 1 2. Deploys to Cloudflare Pages using API -3. Creates stable preview URL: `https://project.pages.dev/pr-{number}` +3. Creates stable preview URL: `https://pr-{number}.ddev-com-front-end.pages.dev` 4. Comments preview URL on the PR 5. Updates comment on subsequent pushes @@ -132,9 +128,9 @@ The workflow is triggered automatically for: ### Preview URL Issues -- Verify Cloudflare Pages project exists -- Check account ID matches organization -- Ensure project name in `CF_PAGES_PROJECT` is exact +- Verify `ddev-com-front-end` Cloudflare Pages project exists and is accessible +- Check account ID matches the project's organization +- Ensure `CF_PAGES_PROJECT` is set to `ddev-com-front-end` ## Manual Testing From 970c346862558191fe5c71600133ec7b8dad5ac0 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 11:52:11 -0600 Subject: [PATCH 13/25] Correct usage of CF_API_TOKEN --- .github/FORK_PREVIEW_SETUP.md | 3 ++- .github/workflows/cloudflare-preview-forks.yml | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/FORK_PREVIEW_SETUP.md b/.github/FORK_PREVIEW_SETUP.md index f4105ba4..31f03fd3 100644 --- a/.github/FORK_PREVIEW_SETUP.md +++ b/.github/FORK_PREVIEW_SETUP.md @@ -37,7 +37,8 @@ Add these in GitHub repository settings → Secrets and variables → Actions: **Repository Secrets:** -- `CF_API_TOKEN`: The API token from step 2 +- `PUSH_SERVICE_ACCOUNT_TOKEN`: 1Password service account token (if not already configured) +- Note: `CF_API_TOKEN` is loaded from 1Password vault, not directly as a repository secret **Repository Variables:** diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 9ad7ea76..5a6f33d6 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -281,9 +281,16 @@ jobs: pull-requests: write issues: write steps: + - name: Load 1password secret(s) + uses: 1password/load-secrets-action@v3 + with: + export-env: true + env: + OP_SERVICE_ACCOUNT_TOKEN: "${{ secrets.TESTS_SERVICE_ACCOUNT_TOKEN }}" + CF_API_TOKEN: "op://test-secrets/CF_API_TOKEN/credential" + - name: Check required secrets env: - CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} CF_ACCOUNT_ID: ${{ vars.CF_ACCOUNT_ID }} CF_PAGES_PROJECT: ${{ vars.CF_PAGES_PROJECT }} run: | @@ -311,7 +318,7 @@ jobs: with: # Required repo secrets and variables (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 }} + apiToken: ${{ env.CF_API_TOKEN }} accountId: ${{ vars.CF_ACCOUNT_ID }} projectName: ${{ vars.CF_PAGES_PROJECT }} directory: site-dist From 53a7616bc7b9d91927482e6ac66262ddbd306be0 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 12:02:55 -0600 Subject: [PATCH 14/25] Remove invalid commitHash parameter from Cloudflare Pages action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/cloudflare-preview-forks.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 5a6f33d6..49aca915 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -324,7 +324,6 @@ jobs: 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 From 7fc306c3cee6d52518bf8d3f4457ed3b3ea1501b Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 12:18:59 -0600 Subject: [PATCH 15/25] Fix 1Password vault references in documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update to use TESTS_SERVICE_ACCOUNT_TOKEN (not PUSH_SERVICE_ACCOUNT_TOKEN) - Clarify CF_API_TOKEN comes from test-secrets vault - Match actual workflow configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/FORK_PREVIEW_SETUP.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/FORK_PREVIEW_SETUP.md b/.github/FORK_PREVIEW_SETUP.md index 31f03fd3..587bc7b8 100644 --- a/.github/FORK_PREVIEW_SETUP.md +++ b/.github/FORK_PREVIEW_SETUP.md @@ -37,8 +37,8 @@ Add these in GitHub repository settings → Secrets and variables → Actions: **Repository Secrets:** -- `PUSH_SERVICE_ACCOUNT_TOKEN`: 1Password service account token (if not already configured) -- Note: `CF_API_TOKEN` is loaded from 1Password vault, not directly as a repository secret +- `TESTS_SERVICE_ACCOUNT_TOKEN`: 1Password service account token (if not already configured) +- Note: `CF_API_TOKEN` is loaded from 1Password `test-secrets` vault, not directly as a repository secret **Repository Variables:** From 2ba469ca9b97fd0f74ecd5b10a97b2b9c070e627 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 12:22:10 -0600 Subject: [PATCH 16/25] Prioritize stable branch URLs over commit-specific URLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Prefer steps.pages.outputs.url (branch-based) over deployment-url (commit-based) - Add logging to show which URL type is being used - Ensure fork PRs get stable pr-{number}.ddev-com-front-end.pages.dev URLs - Update comment text to indicate URL type for clarity 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/cloudflare-preview-forks.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 49aca915..7c8466dd 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -334,12 +334,20 @@ jobs: ALT_URL: ${{ steps.pages.outputs.url }} with: script: | - const url = process.env.DEPLOYMENT_URL || process.env.ALT_URL || ''; + // Prefer stable branch URL over commit-specific URL + const branchUrl = process.env.ALT_URL; + const commitUrl = process.env.DEPLOYMENT_URL; + const url = branchUrl || commitUrl || ''; + if (!url) { core.info('No preview URL found from Cloudflare action outputs.'); return; } - const body = `Cloudflare Pages preview: ${url}`; + + const urlType = branchUrl ? 'stable branch preview' : 'deployment preview'; + const body = `Cloudflare Pages ${urlType}: ${url}`; + + core.info(`Using ${urlType} URL: ${url}`); const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, From 59a76e67bd13fec2eaad85c869338d49c4dcc029 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 12:26:37 -0600 Subject: [PATCH 17/25] pacify prettier --- .github/workflows/cloudflare-preview-forks.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml index 7c8466dd..9bc9038e 100644 --- a/.github/workflows/cloudflare-preview-forks.yml +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -338,15 +338,15 @@ jobs: const branchUrl = process.env.ALT_URL; const commitUrl = process.env.DEPLOYMENT_URL; const url = branchUrl || commitUrl || ''; - + if (!url) { core.info('No preview URL found from Cloudflare action outputs.'); return; } - + const urlType = branchUrl ? 'stable branch preview' : 'deployment preview'; const body = `Cloudflare Pages ${urlType}: ${url}`; - + core.info(`Using ${urlType} URL: ${url}`); const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, From bcb538b69483b97ad78ff73259fd296c421f012c Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 09:56:09 -0600 Subject: [PATCH 18/25] Create AGENTS.md with team communication style and development guidelines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace CLAUDE.md with symlink to AGENTS.md. Added communication style guidelines including banned vague superlatives and team development patterns from main DDEV repo. 🤖 Developed with assistance from [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- AGENTS.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 39b278c8..12b7a69e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,13 +61,12 @@ Only commit when explicitly requested by the user. Use descriptive branch names that include: - Date in YYYYMMDD format -- Your GitHub username +- Your GitHub username - Brief description of the work Format: `YYYYMMDD__` Examples: - - `20250919_rfay_update_quickstart` - `20250919_username_fix_blog_styling` - `20250919_contributor_add_sponsor` @@ -200,7 +199,6 @@ Post content here... ### Authors Add new authors to `src/content/authors/` with schema: - - name (must match blog post frontmatter) - firstName - avatarUrl (optional) @@ -257,4 +255,4 @@ Production requires `GITHUB_TOKEN` environment variable in Cloudflare Pages sett - [Astro Documentation](https://docs.astro.build) - [DDEV Documentation](https://ddev.readthedocs.io/) -- [Contributing to ddev.com Training](https://ddev.com/blog/ddev-website-for-contributors/) +- [Contributing to ddev.com Training](https://ddev.com/blog/ddev-website-for-contributors/) \ No newline at end of file From 46f7a2244009d486c97f59545616b3175adf610e Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 10:25:51 -0600 Subject: [PATCH 19/25] Pacify prettier --- .github/FORK_PREVIEW_SETUP.md | 30 ++++++++++++++---------------- AGENTS.md | 6 ++++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/FORK_PREVIEW_SETUP.md b/.github/FORK_PREVIEW_SETUP.md index 587bc7b8..d4326104 100644 --- a/.github/FORK_PREVIEW_SETUP.md +++ b/.github/FORK_PREVIEW_SETUP.md @@ -13,9 +13,13 @@ The workflow in `.github/workflows/cloudflare-preview-forks.yml` implements a se ### 1. Cloudflare Pages Project -The workflow uses the existing `ddev-com-front-end` Cloudflare Pages project that serves the main site. This provides consistent preview URLs and centralized management. +Create a **Direct Upload** Cloudflare Pages project (not Git-connected): -**No additional project setup needed** - the workflow will create `pr-{number}` branch deployments within the existing project using Cloudflare's Direct Upload API. +1. Go to [Cloudflare Pages](https://dash.cloudflare.com/pages) +2. Click "Create a project" +3. Choose "Direct Upload" (not "Connect to Git") +4. Name your project (e.g., `ddev-com-fork-previews`) +5. Note the project name for step 3 ### 2. Cloudflare API Token @@ -31,19 +35,13 @@ Create an API token with Pages permissions: 5. Set account and zone resources as needed 6. Save the token -### 3. Repository Secrets and Variables +### 3. Repository Secrets -Add these in GitHub repository settings → Secrets and variables → Actions: - -**Repository Secrets:** - -- `TESTS_SERVICE_ACCOUNT_TOKEN`: 1Password service account token (if not already configured) -- Note: `CF_API_TOKEN` is loaded from 1Password `test-secrets` vault, not directly as a repository secret - -**Repository Variables:** +Add these secrets in GitHub repository settings → Secrets and variables → Actions: +- `CF_API_TOKEN`: The API token from step 2 - `CF_ACCOUNT_ID`: Your Cloudflare Account ID (found in dashboard sidebar) -- `CF_PAGES_PROJECT`: Set to `ddev-com-front-end` (the main site's Cloudflare project) +- `CF_PAGES_PROJECT`: The project name from step 1 ### 4. Repository Variables (Optional) @@ -95,7 +93,7 @@ The workflow is triggered automatically for: 1. Downloads build artifact from Stage 1 2. Deploys to Cloudflare Pages using API -3. Creates stable preview URL: `https://pr-{number}.ddev-com-front-end.pages.dev` +3. Creates stable preview URL: `https://project.pages.dev/pr-{number}` 4. Comments preview URL on the PR 5. Updates comment on subsequent pushes @@ -129,9 +127,9 @@ The workflow is triggered automatically for: ### Preview URL Issues -- Verify `ddev-com-front-end` Cloudflare Pages project exists and is accessible -- Check account ID matches the project's organization -- Ensure `CF_PAGES_PROJECT` is set to `ddev-com-front-end` +- Verify Cloudflare Pages project exists +- Check account ID matches organization +- Ensure project name in `CF_PAGES_PROJECT` is exact ## Manual Testing diff --git a/AGENTS.md b/AGENTS.md index 12b7a69e..39b278c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,12 +61,13 @@ Only commit when explicitly requested by the user. Use descriptive branch names that include: - Date in YYYYMMDD format -- Your GitHub username +- Your GitHub username - Brief description of the work Format: `YYYYMMDD__` Examples: + - `20250919_rfay_update_quickstart` - `20250919_username_fix_blog_styling` - `20250919_contributor_add_sponsor` @@ -199,6 +200,7 @@ Post content here... ### Authors Add new authors to `src/content/authors/` with schema: + - name (must match blog post frontmatter) - firstName - avatarUrl (optional) @@ -255,4 +257,4 @@ Production requires `GITHUB_TOKEN` environment variable in Cloudflare Pages sett - [Astro Documentation](https://docs.astro.build) - [DDEV Documentation](https://ddev.readthedocs.io/) -- [Contributing to ddev.com Training](https://ddev.com/blog/ddev-website-for-contributors/) \ No newline at end of file +- [Contributing to ddev.com Training](https://ddev.com/blog/ddev-website-for-contributors/) From be11d493bfd987384c7e7be842eebce1105ca627 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 09:56:09 -0600 Subject: [PATCH 20/25] Create AGENTS.md with team communication style and development guidelines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace CLAUDE.md with symlink to AGENTS.md. Added communication style guidelines including banned vague superlatives and team development patterns from main DDEV repo. 🤖 Developed with assistance from [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- AGENTS.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 39b278c8..12b7a69e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,13 +61,12 @@ Only commit when explicitly requested by the user. Use descriptive branch names that include: - Date in YYYYMMDD format -- Your GitHub username +- Your GitHub username - Brief description of the work Format: `YYYYMMDD__` Examples: - - `20250919_rfay_update_quickstart` - `20250919_username_fix_blog_styling` - `20250919_contributor_add_sponsor` @@ -200,7 +199,6 @@ Post content here... ### Authors Add new authors to `src/content/authors/` with schema: - - name (must match blog post frontmatter) - firstName - avatarUrl (optional) @@ -257,4 +255,4 @@ Production requires `GITHUB_TOKEN` environment variable in Cloudflare Pages sett - [Astro Documentation](https://docs.astro.build) - [DDEV Documentation](https://ddev.readthedocs.io/) -- [Contributing to ddev.com Training](https://ddev.com/blog/ddev-website-for-contributors/) +- [Contributing to ddev.com Training](https://ddev.com/blog/ddev-website-for-contributors/) \ No newline at end of file From bbbff20a3ad60d2659445c2dabcf0535904d0c4d Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 10:31:27 -0600 Subject: [PATCH 21/25] simple edit to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ca7e0c83..e9462623 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # ddev.com Astro code + Source code for [ddev.com](https://ddev.com)’s static front end, built with [Astro](https://astro.build) to keep things organized, maintainable, and fast. ## Overview From 175cafcba7e634cb3419bd205f2496d0f6ec4207 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 10:51:23 -0600 Subject: [PATCH 22/25] dummy commit From 18c1e502a2b858b7e9f4b6fe12f1e3ca2fea3ba4 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 10:55:15 -0600 Subject: [PATCH 23/25] Add blog copy --- ...ate-github-actions-contributor-training.md | 159 +----------------- 1 file changed, 5 insertions(+), 154 deletions(-) diff --git a/src/content/blog/tmate-github-actions-contributor-training.md b/src/content/blog/tmate-github-actions-contributor-training.md index 370bb623..2d57b2fb 100644 --- a/src/content/blog/tmate-github-actions-contributor-training.md +++ b/src/content/blog/tmate-github-actions-contributor-training.md @@ -1,8 +1,7 @@ --- -title: "Contributor Training: Tmate for Debugging GitHub Actions Workflows" -pubDate: 2024-10-23 -modifiedDate: 2025-02-26 -summary: Contributor training - Using tmate to debug and experiment with GitHub Actions. +title: "A new blog with stuff in it and forked preview" +pubDate: 2025-10-23 +summary: New experimental blog author: Randy Fay featureImage: src: /img/blog/2024/10/github-actions-tmate-debugging.png @@ -12,154 +11,6 @@ categories: - Guides --- -Here's our October 23, 2024 [Contributor Training](/blog/category/training) on using `ddev debug test` to help other users: - -
- -
- -## What is Tmate? - -[mxschmitt/action-tmate](https://github.com/mxschmitt/action-tmate) provides a way to SSH into actual running GitHub Actions VMs to debug your tests. - -## Why do we need Tmate? - -Often it's hard to understand what has happened with an test because all we see in GitHub's web UI is the output, and we can't interact with it. And trying to recreate the test environment is sometimes fine, but sometimes it's hard to recreate the test. GitHub runners have different memory configuration, disk space, and different packages installed, and they're typically running AMD64 Ubuntu, which may not be something we have easy access to. - -## Alternatives to Tmate - -1. We normally will try to understand a test failure by running it locally. -2. Running in a similar Linux/AMD64 system like GitHub Codespaceds is a pretty easy option. -3. [nektos/act](https://github.com/nektos/act) is another recommended competitor to Tmate. It uses Docker and a Docker image to run an action on your local machine. I haven't had luck with it when I've tried it. See Stas's experience with `act` [below](#how-to-useact). - -## Security Concerns - -If your test has secrets, then anyone who can SSH into it has access to those secrets. - -In addition, the owners of `ssh.tmate.io` clearly have access to the SSH session you're experimenting with, so think carefully about secrets that might be exposed. (In many tests, there are no secrets likely exposed or available. We have a couple of DDEV GitHub Actions that have sensitive secrets, and a few more that have far-less sensitive secrets.) - -I recommend always using `limit-access-to-actor: true` so that only the user that has launched the test can SSH into it. - -## Usage Examples - -These examples are all at [rfay/tmate-demos](https://github.com/rfay/tmate-demos/), which you can fork and experiment with to your heart's delight. - -### Basic on-push example with tmate running after the work is done - -[This on-push example](https://github.com/rfay/tmate-demos/blob/main/.github/workflows/ddev-drupal-setup-on-push.yaml) just does some work (sets up DDEV and a Drupal project) and then right after that the Tmate action starts up and starts telling you how to SSH into the test. - -### Detached example, where Tmate starts at the end - -In the [detached example](https://github.com/rfay/tmate-demos/blob/main/.github/workflows/detached.yaml) Tmate is set up early in the workflow, but is set to `detached: true`, so doesn't become active until everything else is done. However, if there's an error, we won't get to the Tmate step this way. - -### Failure Example - -Often we have a complex step and want to be able to debug it if it fails. For this we can used `if: ${{ failure() }}`, as shown in the [failure example](https://github.com/rfay/tmate-demos/blob/main/.github/workflows/on_fail.yaml). Tmate kicks in automatically if the step _before_ it fails. It would be nicer if it kicked in on any failure, but it just kicks in when the step before fails. - -### Workflow Dispatch Example - -The [Workflow Dispatch](https://github.com/rfay/tmate-demos/blob/main/.github/workflows/workflow_dispatch.yaml) is one of my favorite techniques, because you can easily restart the workflow as many times as you like, and choosing whether to invoke Tmate is just a click of a checkbox. - -## How to Use Act - -[nektos/act](https://github.com/nektos/act) offers a way to locally simulate GitHub Actions workflows. - -In this example `.github/workflows/jekyll-gh-pages.yml`, we deploy Jekyll to GitHub Pages with dynamic addition of JSON data to the website. Our focus is to debug the API call to GitHub using JavaScript: - -```yaml -name: Deploy Jekyll with GitHub Pages - -on: - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Pages - uses: actions/configure-pages@v5 - - name: Build with Jekyll - uses: actions/jekyll-build-pages@v1 - with: - source: ./ - destination: ./_site - - name: Get GitHub repositories - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { data } = await github.rest.search.repos({q: 'user:stasadev'}) - console.log(data) - const fs = require('fs'); - fs.writeFileSync('data.json', JSON.stringify(data, null, 2)); - - name: Add JSON files to the site - run: | - cat data.json | sudo tee ./_site/data.json - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 -``` - -1. Simplify the workflow file `.github/workflows/jekyll-gh-pages.yml` by removing unrelated code to focus on testing the API call: - - ```yaml - jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Get my GitHub repositories - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { data } = await github.rest.search.repos({q: 'user:stasadev'}) - console.log(data) - const fs = require('fs'); - fs.writeFileSync('data.json', JSON.stringify(data, null, 2)); - ``` - - This workflow retrieves repositories for a specified user, writes them to `data.json`, and outputs the result with `console.log()`. - -2. Run the workflow locally with `act` in your project's root directory: - - ```bash - act -P ubuntu-latest=catthehacker/ubuntu:act-latest \ - --bind \ - --job build \ - -s GITHUB_TOKEN=my_token - ``` - - - `-P ubuntu-latest=catthehacker/ubuntu:act-latest`: Specifies the Docker image to use for the `ubuntu-latest` environment. If not specified, `act` uses the default image from its `.actrc` file (see [GitHub issue](https://github.com/nektos/act/issues/2219) for more details). - - `--bind`: Mounts the current working directory into the container, allowing files generated in the container (like `data.json`) to be accessible in the host file system. - - `--job build`: Tells `act` to run only the `build` job from the workflow. - - `-s GITHUB_TOKEN=my_token`: Sets a secret (`GITHUB_TOKEN`) for the workflow, where `my_token` should be replaced with a valid GitHub token for authentication. - -The primary advantage of using `act` in this context is the ability to efficiently debug API calls locally in just a few seconds, without the need to commit changes, push them to GitHub, and wait for the workflow to complete, which typically takes several minutes. - -## Contributions welcome! - -Your suggestions to improve this blog are welcome. You can do a PR to this blog adding your techniques. Info and a training session on how to do a PR to anything in ddev.com is at [DDEV Website For Contributors](ddev-website-for-contributors.md). - Join us for the next [DDEV Live Contributor Training](/blog/contributor-training/). Use the [contact](/contact) link to ask for a calendar invitation. +:w +: From 29f9f755726a762ee2ecf5c7d4df2f813449a1cc Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 11:01:16 -0600 Subject: [PATCH 24/25] pacify prettier --- .ddev/config.yaml | 1 - AGENTS.md | 6 ++++-- README.md | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 0462ca68..95a84979 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -1,4 +1,3 @@ -name: ddev.com type: php docroot: dist php_version: "8.1" diff --git a/AGENTS.md b/AGENTS.md index 12b7a69e..39b278c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,12 +61,13 @@ Only commit when explicitly requested by the user. Use descriptive branch names that include: - Date in YYYYMMDD format -- Your GitHub username +- Your GitHub username - Brief description of the work Format: `YYYYMMDD__` Examples: + - `20250919_rfay_update_quickstart` - `20250919_username_fix_blog_styling` - `20250919_contributor_add_sponsor` @@ -199,6 +200,7 @@ Post content here... ### Authors Add new authors to `src/content/authors/` with schema: + - name (must match blog post frontmatter) - firstName - avatarUrl (optional) @@ -255,4 +257,4 @@ Production requires `GITHUB_TOKEN` environment variable in Cloudflare Pages sett - [Astro Documentation](https://docs.astro.build) - [DDEV Documentation](https://ddev.readthedocs.io/) -- [Contributing to ddev.com Training](https://ddev.com/blog/ddev-website-for-contributors/) \ No newline at end of file +- [Contributing to ddev.com Training](https://ddev.com/blog/ddev-website-for-contributors/) diff --git a/README.md b/README.md index e9462623..ca7e0c83 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # ddev.com Astro code - Source code for [ddev.com](https://ddev.com)’s static front end, built with [Astro](https://astro.build) to keep things organized, maintainable, and fast. ## Overview From 50d44da10a70c4ae28528b89f44798911436e92d Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 19 Sep 2025 12:32:43 -0600 Subject: [PATCH 25/25] dummy commit