import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),".."); const data=JSON.parse(fs.readFileSync(path.join(root,"src/content/site-content.json"),"utf8")); const marketing=data.pages.filter((p)=>p.category!=="legal"); const errors=[]; const moneyPaths=new Set(["/","/services/account-management","/pricing","/blog/questions/creator-taxes-guide","/blog/questions/how-to-start-onlyfans","/blog/questions/how-to-choose-a-creator-agency"]); const wordCount=(value)=>{try{return [...new Intl.Segmenter(data.site.locale,{granularity:"word"}).segment(value)].filter((item)=>item.isWordLike).length}catch{return value.trim().split(/\s+/).length}}; if(marketing.length<38)errors.push("fewer than 38 marketing/content pages"); for(const field of ["title","description","h1"]){const seen=new Map();for(const p of data.pages){const key=p[field].trim().toLocaleLowerCase();if(seen.has(key))errors.push("duplicate "+field+": "+p.path+" and "+seen.get(key));else seen.set(key,p.path)}} for(const p of data.pages){for(const field of ["path","title","description","h1","summary","image","alt","keyword"]){if(!p[field])errors.push(p.path+" missing "+field)}if(!p.faq?.length)errors.push(p.path+" missing visible FAQ");if(p.title.length>60)errors.push(p.path+" title exceeds 60 characters");if(p.description.length>155)errors.push(p.path+" description exceeds 155 characters");if(p.citations.length<2)errors.push(p.path+" has fewer than 2 citations");if(!/\d/.test(p.summary))errors.push(p.path+" summary has no quotable number or date");for(const faq of p.faq){if(!/\d/.test(faq.answer))errors.push(p.path+" FAQ answer has no quotable number or date: "+faq.question)}if(p.category==="service"&&!p.h1.toLocaleLowerCase().includes(p.keyword.toLocaleLowerCase()))errors.push(p.path+" service keyword missing from H1");if(p.category==="blog"&&![p.h1,...p.sections.map((section)=>section.heading)].some((value)=>value.toLocaleLowerCase().includes(p.keyword.toLocaleLowerCase())))errors.push(p.path+" blog keyword missing from H1/H2");if(moneyPaths.has(p.path)){if(p.sections.length<5||p.sections.length>7)errors.push(p.path+" money page needs 5-7 sections");if(p.faq.length<5)errors.push(p.path+" money page needs at least 5 FAQs");const rendered=[p.summary,...p.sections.flatMap((section)=>[section.heading,section.body,...(section.items??[]),...(section.steps??[])]),...p.faq.flatMap((faq)=>[faq.question,faq.answer])].join(" ");if(["zh-TW","th-TH","ko-KR"].includes(data.site.locale)){if(rendered.length<1400)errors.push(p.path+" money page is too short")}else{const words=wordCount(rendered);if(words<580||words>1000)errors.push(p.path+" money page outside 580-1000 words: "+words)}}const image=path.join(root,"public",p.image);if(!fs.existsSync(image))errors.push(p.path+" missing image "+p.image)} if(!data.site.hreflangXDefault)errors.push("missing cluster-wide hreflangXDefault");if(data.site.foundingDate!=="2019")errors.push("missing verified foundingDate");if(!data.site.aggregateRating?.ratingValue||!data.site.aggregateRating?.reviewCount)errors.push("missing aggregate rating evidence");if((data.site.schemaSameAs?.length??0)<15)errors.push("entity sameAs graph is incomplete"); for(const file of ["public/llms.txt","public/llms-full.txt","src/app/robots.ts","src/app/sitemap.ts","src/components/TrackedHubSpotForm.tsx","src/components/SiteJsonLd.tsx","src/components/Breadcrumbs.tsx"]){if(!fs.existsSync(path.join(root,file)))errors.push("missing "+file)} if(errors.length){console.error(errors.join("\n"));process.exit(1)}console.log(data.site.code+": "+marketing.length+" marketing pages, "+data.pages.length+" total pages; SEO registry valid");