diff --git a/launch/verify-launch.mjs b/launch/verify-launch.mjs index 40edc11..97eab13 100644 --- a/launch/verify-launch.mjs +++ b/launch/verify-launch.mjs @@ -19,6 +19,9 @@ const report = JSON.parse(readFileSync(reportPath, "utf8")); const index = JSON.parse(readFileSync(indexPath, "utf8")); const requirements = JSON.parse(readFileSync(requirementsPath, "utf8")); const html = readFileSync(join(site, "index.html"), "utf8"); +const headers = readFileSync(join(site, "_headers"), "utf8"); +const robots = readFileSync(join(site, "robots.txt"), "utf8"); +const sitemap = readFileSync(join(site, "sitemap.xml"), "utf8"); function assert(condition, message) { if (!condition) throw new Error(message); @@ -82,6 +85,18 @@ const requiredCopy = [ for (const copy of requiredCopy) assert(html.includes(copy), `Homepage is missing required copy: ${copy}`); assert(!/official leaderboard[^<]{0,80}(live|launched|ready)/i.test(html), "Homepage overclaims official leaderboard readiness"); assert(html.includes("https://github.com/aronchick/gloss"), "Homepage must lead to GitHub"); +assert(html.includes(''), "Homepage canonical URL is missing"); +assert(html.includes('"codeRepository": "https://github.com/aronchick/gloss"'), "Homepage structured data must lead to GitHub"); +assert(robots.includes("Allow: /") && robots.includes("Sitemap: https://gloss.tools/sitemap.xml"), "robots.txt must expose the public site map"); +assert(sitemap.includes("https://gloss.tools/"), "sitemap.xml must contain the production homepage"); +for (const header of [ + "X-Content-Type-Options: nosniff", + "Referrer-Policy: strict-origin-when-cross-origin", + "Permissions-Policy: camera=(), microphone=(), geolocation=()", + "X-Frame-Options: DENY", +]) { + assert(headers.includes(header), `Cloudflare Pages header is missing: ${header}`); +} const localAssetPattern = /(?:href|src)="\/(?!\/)([^"?#]*)/g; for (const match of html.matchAll(localAssetPattern)) { diff --git a/site/_headers b/site/_headers new file mode 100644 index 0000000..6e03f76 --- /dev/null +++ b/site/_headers @@ -0,0 +1,11 @@ +/* + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + Permissions-Policy: camera=(), microphone=(), geolocation=() + X-Frame-Options: DENY + +/evidence/* + Cache-Control: public, max-age=300, must-revalidate + +/media/* + Cache-Control: public, max-age=3600 diff --git a/site/index.html b/site/index.html index 835ab36..3ac5809 100644 --- a/site/index.html +++ b/site/index.html @@ -4,10 +4,14 @@ + + + + @@ -15,6 +19,19 @@ + diff --git a/site/robots.txt b/site/robots.txt new file mode 100644 index 0000000..e25e194 --- /dev/null +++ b/site/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://gloss.tools/sitemap.xml diff --git a/site/sitemap.xml b/site/sitemap.xml new file mode 100644 index 0000000..9fb7907 --- /dev/null +++ b/site/sitemap.xml @@ -0,0 +1,8 @@ + + + + https://gloss.tools/ + weekly + 1.0 + +