Queue-North-Website/scripts/validate-content.js

30 lines
1.1 KiB
JavaScript
Raw Normal View History

feat(build): the copy is checked before a single page is built from it src/data is prose in a data structure, and nothing checked it. The long-form service pages make that dangerous in a specific way: their copy arrives as an owner-approved markdown sheet that MIXES DIRECTIONS TO THE WEBSITE MANAGER INTO THE COPY. "Do not promise that every number is always portable." "Keep this factual:" "Place an official 8x8 Work screenshot beside this section." Those lines look exactly like copy, and publishing one puts an internal instruction on a customer-facing page. scripts/lib/content.js decides whether the content layer is publishable, and prerender.js runs it before rendering anything, so every build enforces it: the pre-commit hook, npm run verify, and the Docker image build. It refuses a website-manager direction, an em dash, a U+FFFD, markdown or an HTML tag left in a string, an unknown block type, a section id that is not letter-first, unique and free of the layout's own ids, a section that does not open with its direct answer (unless it declares kind list or faq), a FAQ question with no answer, a link to a route or fragment that does not exist, an image whose src is missing from public/ or has no alt or no dimensions, and the missing benefits or idealFor list that the short layout maps without checking. A description over 160 characters is a note, not a failure: owner-approved copy is published as written. scripts/lib/routes.js is now the one route list. prerender.js built its own while src/routes.jsx built the router's, and nothing compared them: a route in one and not the other is never prerendered, so the server answers it with 404.html while the site's own navigation links to it. entry-server.jsx exports the router table so the build can compare the two. Proven by mutation, seventeen of them, each expecting exactly one finding and getting it: unknown block type, FAQ answer removed, answer moved below its list, duplicate id, digit-leading id, link to /services/contact-centre, #no-such-section, missing image file, image without dimensions, image without alt, em dash, a manager direction, markdown bold, U+FFFD, an HTML tag, missing h1, empty section. Both generated content modules pass unmutated. Against a real build: an em dash added to industries.js failed npm run build naming the field, and a /pricing route added to src/routes.jsx failed it naming the route. Closes #230. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 04:43:01 -05:00
#!/usr/bin/env node
//
// The content check, on its own, for proving it fails and for a quick answer
// while writing copy. The build runs the same check inside prerender.js, so a
// clean run here is not a substitute for `npm run build`.
//
// node scripts/validate-content.js
//
// Exit 0 nothing wrong, 1 findings, 2 nothing was checked.
import { services } from '../src/data/services.js'
import { industries } from '../src/data/industries.js'
import { validateContent } from './lib/content.js'
const { errors, warnings, checked } = validateContent({ services, industries })
for (const warning of warnings) console.warn(`content: note: ${warning}`)
if (!services?.length || !industries?.length || checked === 0) {
console.error('content: nothing was checked, which is not a pass. Did the data modules fail to import?')
process.exit(2)
}
if (errors.length) {
console.error(`content: ${errors.length} problem(s):`)
for (const error of errors) console.error(` ${error}`)
process.exit(1)
}
console.log(`content: ${checked} field(s) checked across ${services.length} service(s) and ${industries.length} industr(ies), nothing wrong.`)