diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f7e5f8f..81ee1cd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -17,4 +17,4 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - name: Smoke check production changelog - run: curl --fail --silent --show-error --location --max-time 30 https://mail.sassmaker.com/changelog >/dev/null + run: curl --fail --silent --show-error --location --max-time 30 https://mail.significanthobbies.com/changelog >/dev/null diff --git a/index.html b/index.html index 0effe69..0a90f7b 100644 --- a/index.html +++ b/index.html @@ -38,7 +38,7 @@ rel="stylesheet" /> - +
diff --git a/landing-astro/src/layouts/Layout.astro b/landing-astro/src/layouts/Layout.astro index b890dde..df12d5b 100644 --- a/landing-astro/src/layouts/Layout.astro +++ b/landing-astro/src/layouts/Layout.astro @@ -9,6 +9,8 @@ interface Props { const { title, description } = Astro.props; const SITE_URL = 'https://mail.significanthobbies.com'; +const canonicalPath = Astro.url.pathname === '/index.html' ? '/' : Astro.url.pathname.replace(/\.html$/, ''); +const canonicalUrl = new URL(canonicalPath, `${SITE_URL}/`).href; // OG image lives on the Next.js Worker at /opengraph-image (file // convention → dynamic JSX → PNG). Astro can't trivially reproduce // that. The Worker still serves every non-`/` route so this URL @@ -50,10 +52,10 @@ const structuredData = { - + - + diff --git a/package.json b/package.json index c3133ff..10f6a81 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dev:api": "wrangler dev --port 8787", "dev:spa": "vite", "build:landing": "pnpm --filter ./landing-astro build", - "build": "vite build && pnpm build:landing && node scripts/overlay-landing.mjs", + "build": "vite build && pnpm build:landing && node scripts/overlay-landing.mjs && pnpm verify:indexing", "preview": "vite preview", "typecheck": "tsc --noEmit -p tsconfig.app.json && tsc --noEmit -p tsconfig.worker.json", "lint": "biome lint .", @@ -27,6 +27,7 @@ "format:check": "biome format .", "check": "biome check .", "check:docs": "node scripts/check-docs.mjs", + "verify:indexing": "node scripts/verify-public-indexing.mjs", "foundry:evidence": "node scripts/foundry-evidence.mjs", "docs:dev": "pnpm --filter ./apps/docs-blume dev", "docs:build": "pnpm --filter ./apps/docs-blume build", @@ -91,7 +92,7 @@ "ip-address@>=10.0.0 <11.0.0": "10.3.1", "js-yaml@>=3.0.0 <4.0.0": "3.15.1", "js-yaml@>=4.0.0 <5.0.0": "4.3.1", - "nanoid@>=3.0.0 <4.0.0": "3.3.17", + "nanoid@>=3.0.0 <4.0.0": "3.3.18", "next@>=16.0.0 <17.0.0": "16.2.11", "path-to-regexp@>=6.0.0 <7.0.0": "6.3.0", "postcss@>=8.0.0 <9.0.0": "8.5.23", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 534ef6a..1bd04d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ overrides: ip-address@>=10.0.0 <11.0.0: 10.3.1 js-yaml@>=3.0.0 <4.0.0: 3.15.1 js-yaml@>=4.0.0 <5.0.0: 4.3.1 - nanoid@>=3.0.0 <4.0.0: 3.3.17 + nanoid@>=3.0.0 <4.0.0: 3.3.18 next@>=16.0.0 <17.0.0: 16.2.11 path-to-regexp@>=6.0.0 <7.0.0: 6.3.0 postcss@>=8.0.0 <9.0.0: 8.5.23 @@ -5367,8 +5367,8 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - nanoid@3.3.17: - resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -12035,7 +12035,7 @@ snapshots: muggle-string@0.4.1: {} - nanoid@3.3.17: {} + nanoid@3.3.18: {} nanoid@5.1.16: {} @@ -12334,7 +12334,7 @@ snapshots: postcss@8.5.23: dependencies: - nanoid: 3.3.17 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 diff --git a/scripts/verify-public-indexing.mjs b/scripts/verify-public-indexing.mjs new file mode 100644 index 0000000..283e87b --- /dev/null +++ b/scripts/verify-public-indexing.mjs @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +const ORIGIN = 'https://mail.significanthobbies.com'; +const pages = [ + { file: 'index.html', url: `${ORIGIN}/` }, + { file: 'faq.html', url: `${ORIGIN}/faq` }, + { file: 'changelog.html', url: `${ORIGIN}/changelog` }, +]; + +for (const page of pages) { + const html = await readFile(resolve('dist', page.file), 'utf8'); + const canonical = html.match(/]+rel=["']canonical["'][^>]+href=["']([^"']+)["']/i)?.[1]; + const openGraphUrl = html.match( + /]+property=["']og:url["'][^>]+content=["']([^"']+)["']/i + )?.[1]; + if (canonical !== page.url) { + throw new Error( + `${page.file}: expected canonical ${page.url}, found ${canonical || 'missing'}` + ); + } + if (openGraphUrl !== page.url) { + throw new Error( + `${page.file}: expected og:url ${page.url}, found ${openGraphUrl || 'missing'}` + ); + } +} + +const sitemap = await readFile(resolve('public', 'sitemap.xml'), 'utf8'); +const sitemapUrls = [...sitemap.matchAll(/