www.pcbjam.com was the last piece of the stack on Vercel. It is now a
Cloudflare Pages project (pcbjam-site) deployed by deploy-site.yml on
every push to main touching site/** — content must not wait for a
release tag.
The Astro adapter is gone entirely: the build is pure static and the one
dynamic route, /api/waitlist, is a Pages Function. Going adapter-free
(rather than swapping in @astrojs/cloudflare, which has dropped Pages
support and only targets Workers) removes three problems at once — no
Astro/adapter major-version coupling, Footer.astro's build-time execSync
keeps working because prerendering stays in Node, and image optimisation
stays plain build-time sharp with no Cloudflare Images binding.
Verified against a real Pages runtime (wrangler pages dev): 21/21 parity
probes pass, versus 19/21 on live Vercel. The scripted runbook is in
deploy/site/ — every mutating step is dry-run by default.
Four behaviour differences were found by measurement and are handled here:
- The blog post's COOP/COEP was already broken in production. vercel.json
scoped the headers to the bare URL, but the page's own canonical is the
trailing-slash form, which served 200 with no isolation headers — so
search arrivals lost SharedArrayBuffer and the embedded Gerber viewer
degraded. public/_headers covers both forms.
- Pages answers unknown URLs with the homepage at HTTP 200 when the
output has no 404.html — a soft-404 that invites indexing junk URLs as
the homepage. Hence src/pages/404.astro.
- Vercel's edge refused cross-site form POSTs ("Cross-site POST form
submissions are forbidden"); Pages does not, and a cross-site <form>
submit needs no CORS permission to be sent, so the allowlist cannot
stop it. The Function reproduces the guard; JSON posts stay exempt as
that is demo.pcbjam.com's allowlisted path.
- Cache-Control: immutable on /_astro/* came from the Vercel adapter's
generated route config, so it is now an explicit _headers rule.
Secrets move to `wrangler pages secret put --project-name pcbjam-site`
(RESEND_API_KEY, RESEND_SEGMENT_ID, WAITLIST_FROM_EMAIL);
WAITLIST_ALLOWED_ORIGINS stays unset so the allowlist stays in code.
Local dev reads .dev.vars, now gitignored — the root repo's **/.dev.vars
does not cover a nested git repo.
privacy.md and cookies.md named Vercel as a GDPR Art. 28 processor; those
mentions are removed and the existing Cloudflare entry widened to cover
website hosting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAmkjM7okPdScp9XLW1JVr
104 lines
3.9 KiB
Shell
Executable file
104 lines
3.9 KiB
Shell
Executable file
#!/usr/bin/env bash
|
|
# Put the three waitlist secrets into the Pages project, for BOTH the production
|
|
# and preview environments, without ever printing a value.
|
|
#
|
|
# deploy/site/04-set-secrets.sh --prompt --apply
|
|
# deploy/site/04-set-secrets.sh --from-env-file site/.migration-secrets.env --apply
|
|
#
|
|
# Getting the values: pull them yourself, e.g.
|
|
# cd site && npx vercel env pull .migration-secrets.env \
|
|
# --environment=production --project pcbjam --scope pcbj-am
|
|
# .migration-secrets.env is gitignored. Delete it when you're done.
|
|
#
|
|
# Pages applies environment-variable changes to NEW deployments only, so this must
|
|
# run BEFORE 05-deploy.sh (or you must redeploy afterwards).
|
|
set -euo pipefail
|
|
. "$(dirname "$0")/lib/common.sh"
|
|
|
|
no_xtrace # never run with tracing: values would be echoed
|
|
require_cmd npx shasum awk
|
|
|
|
NAMES="RESEND_API_KEY RESEND_SEGMENT_ID WAITLIST_FROM_EMAIL"
|
|
MODE=prompt; ENV_FILE=""
|
|
parse_common_flags "$@"
|
|
set -- $CFM_ARGS
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--prompt) MODE=prompt; shift ;;
|
|
--from-env-file) MODE=file; ENV_FILE="$2"; shift 2 ;;
|
|
"") shift ;;
|
|
*) die "unknown arg: $1" ;;
|
|
esac
|
|
done
|
|
dry_banner
|
|
|
|
# WAITLIST_ALLOWED_ORIGINS is deliberately NOT settable here. It must stay unset
|
|
# so the Function keeps its in-code default (https://demo.pcbjam.com), matching
|
|
# what Vercel had. Setting it as a Pages secret would work but would hide the
|
|
# allowlist from code review.
|
|
guard_name() {
|
|
case "$1" in
|
|
WAITLIST_ALLOWED_ORIGINS)
|
|
die "refusing to set WAITLIST_ALLOWED_ORIGINS: it is intentionally unset so
|
|
the allowlist lives in functions/api/waitlist.ts where it is reviewable." ;;
|
|
esac
|
|
grep -q "$1" "$SITE_DIR/functions/api/waitlist.ts" \
|
|
|| die "refusing to set $1: functions/api/waitlist.ts never reads it"
|
|
}
|
|
|
|
read_value() { # read_value NAME -> echoes the value (never logged)
|
|
_n="$1"
|
|
if [ "$MODE" = file ]; then
|
|
[ -f "$ENV_FILE" ] || die "env file not found: $ENV_FILE"
|
|
( cd "$(dirname "$ENV_FILE")" && git check-ignore -q "$(basename "$ENV_FILE")" ) \
|
|
|| die "$ENV_FILE is NOT gitignored — refusing to read secrets from a trackable file"
|
|
awk -F= -v k="$_n" '$1==k { sub(/^[^=]*=/,""); gsub(/^"|"$/,""); print; exit }' "$ENV_FILE"
|
|
else
|
|
printf 'value for %s (input hidden, empty to skip): ' "$_n" >&2
|
|
read -r -s _v; echo >&2
|
|
printf '%s' "$_v"
|
|
fi
|
|
}
|
|
|
|
section "setting secrets on $PAGES_PROJECT"
|
|
set_count=0
|
|
for n in $NAMES; do
|
|
guard_name "$n"
|
|
v="$(read_value "$n")"
|
|
if [ -z "$v" ]; then warn "$n: empty, skipped"; continue; fi
|
|
# Length + short hash only. Enough to compare against Vercel without ever
|
|
# revealing the value.
|
|
fp="$(printf '%s' "$v" | shasum -a 256 | cut -c1-8)"
|
|
echo " $n: len=$(printf '%s' "$v" | wc -c | tr -d ' ') sha256=$fp"
|
|
for envname in production preview; do
|
|
if [ "$DRY_RUN" = 1 ]; then
|
|
echo "WOULD: pages secret put $n --project-name $PAGES_PROJECT --env $envname (value via stdin)"
|
|
else
|
|
# stdin, never argv — an argv value would be visible in `ps`.
|
|
printf '%s' "$v" | $WRANGLER pages secret put "$n" \
|
|
--project-name "$PAGES_PROJECT" --env "$envname" >/dev/null \
|
|
|| die "failed to set $n ($envname)"
|
|
echo " set: $n ($envname)"
|
|
fi
|
|
done
|
|
set_count=$((set_count+1))
|
|
unset v
|
|
done
|
|
|
|
if [ "$DRY_RUN" = 0 ]; then
|
|
section "verifying (names only)"
|
|
for envname in production preview; do
|
|
have="$($WRANGLER pages secret list --project-name "$PAGES_PROJECT" --env "$envname" 2>/dev/null || true)"
|
|
for n in $NAMES; do
|
|
case "$have" in *"$n"*) echo " ok $n ($envname)" ;;
|
|
*) die "$n missing from $envname after setting it" ;;
|
|
esac
|
|
done
|
|
done
|
|
stamp_write 04-secrets "count=$set_count"
|
|
fi
|
|
|
|
section "done"
|
|
echo "Pages applies env changes to NEW deployments only — run 05-deploy.sh next."
|
|
echo "If you used --from-env-file, delete it now: rm -f $ENV_FILE"
|
|
echo "done: $set_count secrets set (production + preview)"
|