pcbjam/deploy/site/06-verify-deploy.sh
Viktor Vaczi 04ce88f8a4 fix(deploy/site): make the Pages steps work with wrangler login alone
`wrangler login` yields an OAuth token that wrangler uses itself but that
cannot be replayed as a REST bearer token, and it only carries zone:read.
03 and 06 were reaching for the REST API for things wrangler can answer,
so they died on a missing CLOUDFLARE_API_TOKEN even though every Pages
operation they needed was already authorised.

03 now reads `pages project list --json` for existence and the Git
provider, and proves the production branch EMPIRICALLY from
`pages deployment list --environment production` — if a deployment on
that branch is labelled Production, the setting must be right. That is a
stronger check than reading the field, which the REST endpoint would have
given us. 06 resolves the production deployment the same way.

cf_have_token() marks the boundary, and cf_token() now explains why a
login is not sufficient for the DNS/ruleset/HSTS phases of 07.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAmkjM7okPdScp9XLW1JVr
2026-07-27 14:04:28 +02:00

99 lines
4.2 KiB
Shell
Executable file

#!/usr/bin/env bash
# Sweep a Pages deployment on its *.pages.dev URL — the real Cloudflare runtime,
# before any DNS is touched. One script for both the preview and the production
# deployment; only the stamp it writes differs.
#
# deploy/site/06-verify-deploy.sh --latest
# deploy/site/06-verify-deploy.sh --url https://abc123.pcbjam-site.pages.dev --scope prod-deploy
set -euo pipefail
. "$(dirname "$0")/lib/common.sh"
. "$(dirname "$0")/lib/parity.sh"
. "$(dirname "$0")/lib/cf-api.sh"
require_cmd curl jq awk sed
URL=""; SCOPE=preview
while [ $# -gt 0 ]; do
case "$1" in
--url) URL="$2"; shift 2 ;;
--latest) URL=""; shift ;;
--scope) SCOPE="$2"; shift 2 ;;
*) die "unknown arg: $1" ;;
esac
done
if [ -z "$URL" ] && [ -s "$STATE_DIR/state/last-deploy.json" ]; then
URL="$(jq -r .url "$STATE_DIR/state/last-deploy.json")"
fi
[ -n "$URL" ] || die "no deployment URL — pass --url, or run 05-deploy.sh first"
section "target"
echo " $URL (scope=$SCOPE)"
rc=0
assert_parity "$URL" --scope preview || rc=$?
section "Pages-specific checks"
# If the Function were not bundled (e.g. deployed from the wrong directory) the
# whole endpoint would simply be a static 404. The parity sweep would catch it,
# but say so explicitly — it is the most likely deploy-time mistake.
st="$(curl -sS -o /dev/null -w '%{http_code}' -X OPTIONS "$URL/api/waitlist" \
-H 'Origin: https://demo.pcbjam.com' 2>/dev/null || true)"
if [ "$st" = "404" ]; then
echo "FAIL /api/waitlist is 404 — the Function was not bundled."
echo " wrangler discovers functions/ relative to \$PWD; deploy from site/."
rc=1
else
echo "PASS /api/waitlist is routed (status $st)"
fi
# _routes.json restricts the Function to /api/*. Confirm that did not also break
# static 404 handling for everything else.
nf="$(curl -sS -L -o /dev/null -w '%{http_code}' "$URL/__cfm-parity-404__/" 2>/dev/null || true)"
[ "$nf" = "404" ] && echo "PASS static 404 handling intact under _routes.json" \
|| { echo "FAIL unknown path returned $nf, expected 404"; rc=1; }
if [ "$SCOPE" = prod-deploy ]; then
section "confirming this is the PRODUCTION deployment"
# Via wrangler, not the REST API: `wrangler login` is enough for this, whereas
# the REST call would need an API token that the Pages steps otherwise don't.
latest="$($WRANGLER pages deployment list --project-name "$PAGES_PROJECT" \
--environment production --json 2>/dev/null || true)"
envname="$(printf '%s' "$latest" | jq -r '.[0].Environment // "?"' 2>/dev/null || echo '?')"
did="$(printf '%s' "$latest" | jq -r '.[0].Id // "?"' 2>/dev/null || echo '?')"
brn="$(printf '%s' "$latest" | jq -r '.[0].Branch // "?"' 2>/dev/null || echo '?')"
if [ "$envname" != "Production" ]; then
echo "FAIL latest deployment environment is '$envname', not 'Production'"
echo " (a branch name other than $PAGES_PROD_BRANCH makes it a preview)"
rc=1
else
echo "PASS latest deployment is Production (branch=$brn id=$did)"
fi
fi
section "diff vs the Vercel baseline (informational)"
BASE="$STATE_DIR/baseline/vercel"
if [ -d "$BASE" ]; then
OUT="$STATE_DIR/baseline/pages-$(date +%s)"; mkdir -p "$OUT"
for p in / /pricing /blog /privacy /blog/porting-kicad-graphics-to-webgl-in-2026 /gerber-demo/boot.js; do
f="$(printf '%s' "$p" | sed 's|/|_|g')"; [ "$f" = "_" ] && f="_home"
eff="$(_trace "$URL$p" | cut -f1)"
_headers "$eff" | grep -vE '^(date|age|etag|last-modified|content-length|server|cf-ray|cf-cache-status|nel|report-to|alt-svc|set-cookie|x-vercel-id|x-vercel-cache|x-matched-path|expires|via):' | sort > "$OUT/$f.headers"
if [ -f "$BASE/$f.headers" ]; then
d="$(diff -u "$BASE/$f.headers" "$OUT/$f.headers" || true)"
[ -n "$d" ] && { echo "--- $p"; printf '%s\n' "$d" | sed -n '3,$p'; }
fi
done
echo "(header differences above are for human review, not assertions)"
else
warn "no baseline at $BASE — run 00-baseline.sh while Vercel is still live"
fi
[ "$rc" -eq 0 ] || die "deployment verification FAILED — do not cut DNS."
case "$SCOPE" in
prod-deploy) stamp_write 06-prod-deploy-parity "url=$URL" "deployment_id=${did:-unknown}" ;;
*) stamp_write 06-preview-parity "url=$URL" ;;
esac
section "done"
echo "done: verified $URL"