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
This commit is contained in:
parent
f1ea6b3965
commit
04ce88f8a4
3 changed files with 60 additions and 29 deletions
|
|
@ -16,42 +16,60 @@ parse_common_flags "$@"
|
||||||
dry_banner
|
dry_banner
|
||||||
|
|
||||||
section "project $PAGES_PROJECT"
|
section "project $PAGES_PROJECT"
|
||||||
if existing="$(cf_pages_project 2>/dev/null)"; then
|
row="$($WRANGLER pages project list --json 2>/dev/null \
|
||||||
|
| jq -r --arg n "$PAGES_PROJECT" '.[] | select(."Project Name"==$n)')"
|
||||||
|
if [ -n "$row" ]; then
|
||||||
echo "exists already"
|
echo "exists already"
|
||||||
else
|
else
|
||||||
existing=""
|
|
||||||
dry "create Pages project $PAGES_PROJECT (production branch: $PAGES_PROD_BRANCH)" -- \
|
dry "create Pages project $PAGES_PROJECT (production branch: $PAGES_PROD_BRANCH)" -- \
|
||||||
$WRANGLER pages project create "$PAGES_PROJECT" \
|
$WRANGLER pages project create "$PAGES_PROJECT" \
|
||||||
--production-branch "$PAGES_PROD_BRANCH" \
|
--production-branch "$PAGES_PROD_BRANCH" \
|
||||||
--compatibility-date 2026-06-01 --compatibility-flags nodejs_compat
|
--compatibility-date 2026-06-01 --compatibility-flags nodejs_compat
|
||||||
[ "$DRY_RUN" = 1 ] && { echo; echo "done: (dry run) would create $PAGES_PROJECT"; exit 0; }
|
[ "$DRY_RUN" = 1 ] && { echo; echo "done: (dry run) would create $PAGES_PROJECT"; exit 0; }
|
||||||
existing="$(cf_pages_project)"
|
row="$($WRANGLER pages project list --json 2>/dev/null \
|
||||||
|
| jq -r --arg n "$PAGES_PROJECT" '.[] | select(."Project Name"==$n)')"
|
||||||
|
[ -n "$row" ] || die "project still not listed after create"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
section "asserting settings"
|
section "asserting settings"
|
||||||
pb="$(printf '%s' "$existing" | jq -r '.result.production_branch // "?"')"
|
# `wrangler pages project list` reports the Git provider but not the production
|
||||||
src="$(printf '%s' "$existing" | jq -r '.result.source // "null"')"
|
# branch, and the REST endpoint that would needs an API token. So prove the branch
|
||||||
sub="$(printf '%s' "$existing" | jq -r '.result.subdomain // "?"')"
|
# EMPIRICALLY instead, from the deployments: if a deployment on $PAGES_PROD_BRANCH
|
||||||
|
# is labelled Production, the project's production branch is that branch. That is
|
||||||
|
# a stronger check than reading the setting.
|
||||||
|
src="$(printf '%s' "$row" | jq -r '."Git Provider" // "?"')"
|
||||||
|
dom="$(printf '%s' "$row" | jq -r '."Project Domains" // "?"')"
|
||||||
|
|
||||||
# The single most expensive mistake available here: if the project's production
|
if [ "$src" != "No" ]; then
|
||||||
# branch is anything other than what deploy-site.yml passes to --branch, every
|
die "project appears Git-connected (Git Provider: $src). Cloudflare's own builds
|
||||||
# deploy lands as a PREVIEW and the live site silently never updates. The same
|
would race the uploads from deploy-site.yml. Disconnect it in the dashboard."
|
||||||
# warning is written into deploy-demo.yml.
|
|
||||||
if [ "$pb" != "$PAGES_PROD_BRANCH" ]; then
|
|
||||||
die "production_branch is '$pb' but deploys use '$PAGES_PROD_BRANCH'.
|
|
||||||
Every deploy would land as a preview and www.pcbjam.com would never update.
|
|
||||||
Fix it in Pages -> $PAGES_PROJECT -> Settings -> Builds & deployments, then re-run.
|
|
||||||
(This script will NOT change it: doing so retroactively re-labels deployments.)"
|
|
||||||
fi
|
|
||||||
echo " ok production_branch = $pb"
|
|
||||||
|
|
||||||
if [ "$src" != "null" ]; then
|
|
||||||
die "project is Git-connected (source: $src). Cloudflare's own builds would race
|
|
||||||
the uploads from deploy-site.yml. Disconnect it in the dashboard first."
|
|
||||||
fi
|
fi
|
||||||
echo " ok Direct Upload (not Git-connected)"
|
echo " ok Direct Upload (not Git-connected)"
|
||||||
|
|
||||||
|
prod="$($WRANGLER pages deployment list --project-name "$PAGES_PROJECT" \
|
||||||
|
--environment production --json 2>/dev/null || true)"
|
||||||
|
nprod="$(printf '%s' "$prod" | jq 'length' 2>/dev/null || echo 0)"
|
||||||
|
if [ "${nprod:-0}" -gt 0 ]; then
|
||||||
|
br="$(printf '%s' "$prod" | jq -r '.[0].Branch // "?"')"
|
||||||
|
env="$(printf '%s' "$prod" | jq -r '.[0].Environment // "?"')"
|
||||||
|
# The most expensive mistake available here: if the project's production branch
|
||||||
|
# is not what deploy-site.yml passes to --branch, every deploy lands as a
|
||||||
|
# PREVIEW and the live site silently never updates (see deploy-demo.yml).
|
||||||
|
if [ "$br" = "$PAGES_PROD_BRANCH" ] && [ "$env" = "Production" ]; then
|
||||||
|
echo " ok branch '$br' deploys land as $env"
|
||||||
|
else
|
||||||
|
die "latest production-environment deployment is branch='$br' env='$env',
|
||||||
|
but deploys use --branch '$PAGES_PROD_BRANCH'. If those disagree, every deploy
|
||||||
|
becomes a preview and www.pcbjam.com never updates. Fix it in
|
||||||
|
Pages -> $PAGES_PROJECT -> Settings -> Builds & deployments."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "no production deployment yet — the branch check runs after 05-deploy.sh"
|
||||||
|
fi
|
||||||
|
sub="$(printf '%s' "$dom" | sed 's/,.*//')"
|
||||||
|
|
||||||
section "done"
|
section "done"
|
||||||
echo "note: the custom domain is NOT set here — there is no 'wrangler pages domain'"
|
echo "note: the custom domain is NOT set here — there is no 'wrangler pages domain'"
|
||||||
echo " subcommand. 07-dns-cutover.sh attaches www.pcbjam.com via the API."
|
echo " subcommand. 07-dns-cutover.sh attaches www.pcbjam.com via the API,"
|
||||||
echo "done: https://${sub}.pages.dev"
|
echo " which needs a real API token (wrangler's OAuth login is zone:read only)."
|
||||||
|
echo "done: https://${sub}"
|
||||||
|
|
|
||||||
|
|
@ -55,15 +55,19 @@ nf="$(curl -sS -L -o /dev/null -w '%{http_code}' "$URL/__cfm-parity-404__/" 2>/d
|
||||||
|
|
||||||
if [ "$SCOPE" = prod-deploy ]; then
|
if [ "$SCOPE" = prod-deploy ]; then
|
||||||
section "confirming this is the PRODUCTION deployment"
|
section "confirming this is the PRODUCTION deployment"
|
||||||
latest="$(cf_api GET "/accounts/$(cf_account)/pages/projects/$PAGES_PROJECT/deployments?per_page=1" 2>/dev/null || true)"
|
# Via wrangler, not the REST API: `wrangler login` is enough for this, whereas
|
||||||
envname="$(printf '%s' "$latest" | jq -r '.result[0].environment // "?"')"
|
# the REST call would need an API token that the Pages steps otherwise don't.
|
||||||
did="$(printf '%s' "$latest" | jq -r '.result[0].id // "?"')"
|
latest="$($WRANGLER pages deployment list --project-name "$PAGES_PROJECT" \
|
||||||
if [ "$envname" != "production" ]; then
|
--environment production --json 2>/dev/null || true)"
|
||||||
echo "FAIL latest deployment environment is '$envname', not 'production'"
|
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)"
|
echo " (a branch name other than $PAGES_PROD_BRANCH makes it a preview)"
|
||||||
rc=1
|
rc=1
|
||||||
else
|
else
|
||||||
echo "PASS latest deployment is production ($did)"
|
echo "PASS latest deployment is Production (branch=$brn id=$did)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,17 @@
|
||||||
|
|
||||||
CF_API="https://api.cloudflare.com/client/v4"
|
CF_API="https://api.cloudflare.com/client/v4"
|
||||||
|
|
||||||
|
# True when a REST token is available. `wrangler login` gives an OAuth token that
|
||||||
|
# wrangler itself uses but that cannot be replayed as a REST bearer token, and its
|
||||||
|
# zone scope is read-only — so the DNS/ruleset/HSTS steps need a real API token
|
||||||
|
# while the Pages steps are happy with either. Scripts branch on this.
|
||||||
|
cf_have_token() { [ -n "${CLOUDFLARE_API_TOKEN:-}" ] && [ -n "${CLOUDFLARE_ACCOUNT_ID:-}" ]; }
|
||||||
|
|
||||||
cf_token() {
|
cf_token() {
|
||||||
[ -n "${CLOUDFLARE_API_TOKEN:-}" ] || die "CLOUDFLARE_API_TOKEN is not set"
|
[ -n "${CLOUDFLARE_API_TOKEN:-}" ] || die "CLOUDFLARE_API_TOKEN is not set.
|
||||||
|
\`wrangler login\` is not enough for this step: its OAuth token cannot be used
|
||||||
|
as a REST bearer token, and it only carries zone:read. Create an API token with
|
||||||
|
the scopes at the top of this file and export it (plus CLOUDFLARE_ACCOUNT_ID)."
|
||||||
printf '%s' "$CLOUDFLARE_API_TOKEN"
|
printf '%s' "$CLOUDFLARE_API_TOKEN"
|
||||||
}
|
}
|
||||||
cf_account() {
|
cf_account() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue