feat: build-gate validators (enforce the completeness checklist + caught a favicon bug)#1
Conversation
…nically) Adds scripts/build_validators.mjs orchestrator + scripts/validators/ (validate-links, validate-route-metadata, validate-assets) — the first slice of the build-validators spec (heymegabyte-claude-skills rules/build-validators-manifest.md). Run via 'npm run validate' after build; exits non-zero on any build-break so no site deploys with an unenforced completeness point. Verified against this template's own dist/ — validate-links + validate-route-metadata pass, and validate-assets immediately caught a REAL bug: index.html references 5 favicons (favicon.ico, favicon-16/32, apple-touch-icon, safari-pinned-tab) that exist in neither public/ nor dist/, so every cloned site ships 404'd favicons (violates the favicon-set gate). Left as a separate 'npm run validate' step (not wired into postbuild) until that favicon issue is fixed, so it doesn't block the build today. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ient-only per-route head) Caught on this template: 20 sitemap routes all serve one index.html (SPA fallback /* → /index.html) with NO per-route server head — useSEO.ts is client-only, functions/ has only API routes (no HTMLRewriter), no prerendered route HTML. Crawlers read the HOMEPAGE head on every URL → the site collapses to one indexable URL (the exact njsk.org incident always.md warns is a build-fail). WARN level (not build-break) because per-route head MAY be injected by the projectsites.dev PLATFORM Worker at serve time (outside this repo) — so it's a definite bug for STANDALONE deploys (current _redirects = CF Pages SPA fallback) but possibly handled behind the platform. Either way: verify per-route server head on the live URLs, or add vite-ssg prerender / a Worker HTMLRewriter keyed on getMeta(pathname) to the template. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
…ONFIRMED live) curl of the raw served HTML (no JS) on projectsites.dev itself proves the collapse — the platform Worker does NOT inject per-route head: / → <title>ProjectSites — We deliver websites in minutes</title> canonical / /about → SAME title, canonical / (should be /about) /contact → SAME title, canonical / (should be /contact) Every route serves the homepage head + canonical=/ → the whole site is one indexable URL. No longer architecturally-ambiguous → build-break. Every site cloned from this template has the same collapse until per-route server head is added (vite-ssg prerender or a Pages Function/_middleware HTMLRewriter keyed on getMeta(pathname)). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🔴 SEO-collapse EMPIRICALLY CONFIRMED — live on projectsites.dev itselfCurled the raw served HTML (no JS) — the platform does NOT inject per-route head:
Every route ships the homepage Fix (one of): (a) |
Fix scoping — why it's architectural (and the recommended path)Dug into implementing the per-route head fix. The blocker: per-route meta is currently runtime-only React. Each page calls Consequences for each fix path:
Recommendation: extract route meta into a central This is a focused 1–2h architectural change with real edge/SSR testing — I didn't want to blind-ship a partial (titles-only) or untested fix to the serving path of every customer site. Say go and I'll do the central-map + vite-ssg PR (tested: build → curl each route → assert unique server-rendered title/canonical). Meanwhile |
index.html referenced favicon.ico, favicon-16/32.png, apple-touch-icon.png, safari-pinned- tab.svg — none existed in public/ (caught by validate-assets, PR #1). Every cloned site shipped 404'd favicons (violates checklist #23). scripts/generate-favicons.mjs generates the full set brand-colored from _brand.json (brandHue → HSL→RGB) with ZERO external deps — pure node:zlib for valid RGBA PNGs (rounded square), a BMP/PNG-in-ICO writer, and SVG (rich, brand bg + business initial) + safari mask. Wired into prebuild so it regenerates per brand and vite copies public/ → dist/. Also added an SVG-favicon <link> (modern browsers). Committed brand-default placeholders so the repo is clean even before a per-brand rebuild. Verified on a clean build: all 6 favicons land in dist/, 0 dead refs, valid PNG/ICO magic bytes. validate-assets (PR #1) now passes the favicon gate. Co-authored-by: Brian Zalewski <hey@megabyte.space> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Adds the first slice of the build-validators enforcement layer —
scripts/build_validators.mjsorchestrator +scripts/validators/(validate-links, validate-route-metadata, validate-assets), run vianpm run validate. This is the machine enforcement for the 62-point website-completeness-checklist (spec:heymegabyte-claude-skills/rules/build-validators-manifest.md) — a checklist point with no validator is an unenforced point, and today the template ships onlyvalidate-brand.mjs.Verified — and it caught a real bug
Run against this template's own
dist/:validate-links— no dead internal linksvalidate-route-metadata— every page has title + meta descriptionvalidate-assets—index.htmlreferences 5 favicons that exist in neitherpublic/nordist/(favicon.ico,favicon-16x16.png,favicon-32x32.png,apple-touch-icon.png,safari-pinned-tab.svg). Every cloned site currently ships 404'd favicons — violates the favicon-set completeness gate.Scope / safety
npm run validatestep (NOT wired intopostbuild) so it doesn't block today's build until the favicon issue is fixed. Once the template is clean, wire it into CI/lefthook + the deploy gate.Next
More validators from the spec (word-count, faq, skip-link, axe, legal, consent-gate) + the favicon fix, then promote
npm run validateto a blocking gate.🤖 Generated with Claude Code