pcbjam/deploy/site/lib/common.sh

38 lines
1.5 KiB
Shell
Raw Normal View History

feat(site): move the marketing site from Vercel to Cloudflare Pages 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
2026-07-27 13:34:12 +02:00
#!/usr/bin/env bash
chore(deploy/site): retire the migration scaffolding, keep the health check The Vercel -> Cloudflare Pages move is done and the Vercel project is deleted, so the one-shot scripts have no remaining purpose. Nothing in CI ever called them — deploy-site.yml runs npm ci / test / build / pages deploy inline — so this removes 10 files and orphans nothing. Deleted: 00-baseline (refused to run without x-vercel-id, so permanently unrunnable), 01-preflight (proved Vercel state and API-token scopes), 07-dns-cutover (the phased cutover; in the end the records were attached through the dashboard, and the rules/apex phases went unused once we chose APEX_MODE=serve), 09-detach-vercel (its target project is gone), plus 03-ensure-project, 04-set-secrets, 05-deploy, 06-verify-deploy, 02-verify-local and 99-rollback, all either spent or duplicating CI. Their lib/cf-api.sh went with them: the survivors use wrangler, so the whole remaining path needs only `wrangler login` and no zone scopes. What is kept is the part with ongoing value: lib/parity.sh, the assertion set that caught five real defects during the migration — the live COOP/COEP bug on the post's canonical URL, the soft-404 Pages would have introduced, the cross-site form-POST guard Vercel had been providing for free, the missing immutable header, and HSTS max-age=0. "Does the page return 200" catches none of those. 08-verify-prod.sh becomes verify.sh, since the numbered sequence it belonged to no longer exists. It drops the stamp machinery, the dry-run plumbing and the Vercel-fallback messaging (there is no fallback now: recovery is promoting a previous Pages deployment), and gains --skip-dns / --skip-domains so it can be pointed at a single deployment via PROD_BASE before promoting it. The README is rewritten around the four invariants that fail SILENTLY — never widen _headers to /*, keep both URL forms of the Gerber post, never delete 404.astro, keep the cross-site form-POST guard — each with the reason, since the reason is the only thing that stops someone simplifying them back out. Verified after: 21 probes, 20 pass, 1 warn (HSTS max-age is 6 months vs Vercel's 2 years — on, just shorter), 0 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAmkjM7okPdScp9XLW1JVr
2026-07-27 15:08:56 +02:00
# Shared plumbing for the site verification scripts: paths, config, logging.
# Sourced, never executed.
feat(site): move the marketing site from Vercel to Cloudflare Pages 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
2026-07-27 13:34:12 +02:00
#
# bash 3.2 compatible (/usr/bin/env bash on macOS is 3.2.57): no associative
# arrays, no `mapfile`, no ${x,,}. Accumulators use temp files.
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEPLOY_SITE_DIR="$(dirname "$LIB_DIR")"
PCBJAM_DIR="$(cd "$DEPLOY_SITE_DIR/../.." && pwd)"
SITE_DIR="$PCBJAM_DIR/site"
chore(deploy/site): retire the migration scaffolding, keep the health check The Vercel -> Cloudflare Pages move is done and the Vercel project is deleted, so the one-shot scripts have no remaining purpose. Nothing in CI ever called them — deploy-site.yml runs npm ci / test / build / pages deploy inline — so this removes 10 files and orphans nothing. Deleted: 00-baseline (refused to run without x-vercel-id, so permanently unrunnable), 01-preflight (proved Vercel state and API-token scopes), 07-dns-cutover (the phased cutover; in the end the records were attached through the dashboard, and the rules/apex phases went unused once we chose APEX_MODE=serve), 09-detach-vercel (its target project is gone), plus 03-ensure-project, 04-set-secrets, 05-deploy, 06-verify-deploy, 02-verify-local and 99-rollback, all either spent or duplicating CI. Their lib/cf-api.sh went with them: the survivors use wrangler, so the whole remaining path needs only `wrangler login` and no zone scopes. What is kept is the part with ongoing value: lib/parity.sh, the assertion set that caught five real defects during the migration — the live COOP/COEP bug on the post's canonical URL, the soft-404 Pages would have introduced, the cross-site form-POST guard Vercel had been providing for free, the missing immutable header, and HSTS max-age=0. "Does the page return 200" catches none of those. 08-verify-prod.sh becomes verify.sh, since the numbered sequence it belonged to no longer exists. It drops the stamp machinery, the dry-run plumbing and the Vercel-fallback messaging (there is no fallback now: recovery is promoting a previous Pages deployment), and gains --skip-dns / --skip-domains so it can be pointed at a single deployment via PROD_BASE before promoting it. The README is rewritten around the four invariants that fail SILENTLY — never widen _headers to /*, keep both URL forms of the Gerber post, never delete 404.astro, keep the cross-site form-POST guard — each with the reason, since the reason is the only thing that stops someone simplifying them back out. Verified after: 21 probes, 20 pass, 1 warn (HSTS max-age is 6 months vs Vercel's 2 years — on, just shorter), 0 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAmkjM7okPdScp9XLW1JVr
2026-07-27 15:08:56 +02:00
# Defaults, all overridable by env — which is how you point the sweep at a
# preview deployment instead of production:
# PROD_BASE=https://abc123.pcbjam-site.pages.dev deploy/site/verify.sh
feat(site): move the marketing site from Vercel to Cloudflare Pages 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
2026-07-27 13:34:12 +02:00
PAGES_PROJECT="${PAGES_PROJECT:-pcbjam-site}"
ZONE_NAME="${ZONE_NAME:-pcbjam.com}"
PROD_BASE="${PROD_BASE:-https://www.pcbjam.com}"
APEX_BASE="${APEX_BASE:-https://pcbjam.com}"
WRANGLER="${WRANGLER_CMD:-npx --yes wrangler@4}"
chore(deploy/site): retire the migration scaffolding, keep the health check The Vercel -> Cloudflare Pages move is done and the Vercel project is deleted, so the one-shot scripts have no remaining purpose. Nothing in CI ever called them — deploy-site.yml runs npm ci / test / build / pages deploy inline — so this removes 10 files and orphans nothing. Deleted: 00-baseline (refused to run without x-vercel-id, so permanently unrunnable), 01-preflight (proved Vercel state and API-token scopes), 07-dns-cutover (the phased cutover; in the end the records were attached through the dashboard, and the rules/apex phases went unused once we chose APEX_MODE=serve), 09-detach-vercel (its target project is gone), plus 03-ensure-project, 04-set-secrets, 05-deploy, 06-verify-deploy, 02-verify-local and 99-rollback, all either spent or duplicating CI. Their lib/cf-api.sh went with them: the survivors use wrangler, so the whole remaining path needs only `wrangler login` and no zone scopes. What is kept is the part with ongoing value: lib/parity.sh, the assertion set that caught five real defects during the migration — the live COOP/COEP bug on the post's canonical URL, the soft-404 Pages would have introduced, the cross-site form-POST guard Vercel had been providing for free, the missing immutable header, and HSTS max-age=0. "Does the page return 200" catches none of those. 08-verify-prod.sh becomes verify.sh, since the numbered sequence it belonged to no longer exists. It drops the stamp machinery, the dry-run plumbing and the Vercel-fallback messaging (there is no fallback now: recovery is promoting a previous Pages deployment), and gains --skip-dns / --skip-domains so it can be pointed at a single deployment via PROD_BASE before promoting it. The README is rewritten around the four invariants that fail SILENTLY — never widen _headers to /*, keep both URL forms of the Gerber post, never delete 404.astro, keep the cross-site form-POST guard — each with the reason, since the reason is the only thing that stops someone simplifying them back out. Verified after: 21 probes, 20 pass, 1 warn (HSTS max-age is 6 months vs Vercel's 2 years — on, just shorter), 0 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAmkjM7okPdScp9XLW1JVr
2026-07-27 15:08:56 +02:00
# How the apex behaves.
# serve (current) the apex is a SECOND Pages custom domain and answers 200.
# No redirect rule, no placeholder record. The pages emit
# canonical=www, which is what consolidates the two hosts for search.
# redirect the apex 308s to www. Kept because the assertion is cheap to keep
# and the decision could be revisited; nothing sets it up any more.
APEX_MODE="${APEX_MODE:-serve}"
feat(site): move the marketing site from Vercel to Cloudflare Pages 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
2026-07-27 13:34:12 +02:00
section() { echo; echo "== $* =="; }
warn() { echo "warn: $*" >&2; }
chore(deploy/site): retire the migration scaffolding, keep the health check The Vercel -> Cloudflare Pages move is done and the Vercel project is deleted, so the one-shot scripts have no remaining purpose. Nothing in CI ever called them — deploy-site.yml runs npm ci / test / build / pages deploy inline — so this removes 10 files and orphans nothing. Deleted: 00-baseline (refused to run without x-vercel-id, so permanently unrunnable), 01-preflight (proved Vercel state and API-token scopes), 07-dns-cutover (the phased cutover; in the end the records were attached through the dashboard, and the rules/apex phases went unused once we chose APEX_MODE=serve), 09-detach-vercel (its target project is gone), plus 03-ensure-project, 04-set-secrets, 05-deploy, 06-verify-deploy, 02-verify-local and 99-rollback, all either spent or duplicating CI. Their lib/cf-api.sh went with them: the survivors use wrangler, so the whole remaining path needs only `wrangler login` and no zone scopes. What is kept is the part with ongoing value: lib/parity.sh, the assertion set that caught five real defects during the migration — the live COOP/COEP bug on the post's canonical URL, the soft-404 Pages would have introduced, the cross-site form-POST guard Vercel had been providing for free, the missing immutable header, and HSTS max-age=0. "Does the page return 200" catches none of those. 08-verify-prod.sh becomes verify.sh, since the numbered sequence it belonged to no longer exists. It drops the stamp machinery, the dry-run plumbing and the Vercel-fallback messaging (there is no fallback now: recovery is promoting a previous Pages deployment), and gains --skip-dns / --skip-domains so it can be pointed at a single deployment via PROD_BASE before promoting it. The README is rewritten around the four invariants that fail SILENTLY — never widen _headers to /*, keep both URL forms of the Gerber post, never delete 404.astro, keep the cross-site form-POST guard — each with the reason, since the reason is the only thing that stops someone simplifying them back out. Verified after: 21 probes, 20 pass, 1 warn (HSTS max-age is 6 months vs Vercel's 2 years — on, just shorter), 0 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAmkjM7okPdScp9XLW1JVr
2026-07-27 15:08:56 +02:00
die() { echo "verify: $*" >&2; exit 1; }
feat(site): move the marketing site from Vercel to Cloudflare Pages 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
2026-07-27 13:34:12 +02:00
require_cmd() {
for c in "$@"; do
command -v "$c" >/dev/null 2>&1 || die "missing required command: $c"
done
}