Notes /
Testing a site with no build step
This site is hand-written HTML, CSS and a little JavaScript. There's no compiler to say something broke, so most mistakes don't fail. They just go live. One script checks for them before every deploy.
01
Loud and quiet mistakes
Some mistakes are loud. A typo in the CSS shows up the moment I open the page. Those don't need a test.
The ones worth testing are quiet: the page looks fine on my machine, and the problem is somewhere I won't look. A script the browser refuses to run. A note that never made it into the feed. A config file served to anyone who guesses its name. A link to an anchor I renamed. check.ps1 looks for exactly these, in about 150 lines of PowerShell, and the deploy script won't run if it fails.
02
Scripts the browser will refuse
Every page has one small inline script in its <head>. The Content Security Policy allows it by its SHA-256 hash, so changing a single character, even whitespace, makes the browser block it silently. (I've written about one time that hash mattered.)
The check hashes every inline script on every page and compares it with the hashes in _headers, the file that sets the policy:
foreach ($m in [regex]::Matches($html, '<script([^>]*)>([\s\S]*?)</script>')) {
if ($m.Groups[1].Value -match 'type="application/ld\+json"') { continue }
$bytes = [Text.Encoding]::UTF8.GetBytes($m.Groups[2].Value)
$hash = [Convert]::ToBase64String($sha.ComputeHash($bytes))
if ($allowed -notcontains $hash) { Fail "${page}: hash sha256-$hash is not in the CSP" }
}
JSON-LD blocks are skipped, because the browser never runs them. The same pass fails on any onclick=-style attribute, which the policy blocks too.
03
One note, five listings
Publishing a note means adding it in five places by hand: the list on the notes page, the structured data on that page, the teaser on the homepage, the Atom feed and the sitemap. Forget one, and the note is still live. It's just missing from feed readers, or from search.
The check works backwards from the files. For every page in notes/, it looks for that note's URL in each of the five places and names the ones that are missing.
04
What gets published
The site is served straight from the repository folder. Anything in it is public unless it's listed in .assetsignore, so a new script or config file in the root goes live by default.
A list of things to hide only works if I remember to update it. So the check flips it around: the site is the HTML pages, a handful of named files, and three folders. Any other file that .assetsignore doesn't cover fails the check.
The same idea runs in the other direction for images. Anything in assets/ that no page, stylesheet or feed mentions gets flagged, so replaced screenshots don't pile up.
05
Links, resolved like the server does
Pages are served without .html, so /jarvis has to find jarvis.html. The link check follows the same rule. For each link it tries the path, the path plus .html, and the path plus /index.html. Then, if the link has a #fragment, it checks that the target page has an element with that id.
It also checks full URLs that start with https://hsynrsd.dev, wherever they appear: social-card tags, structured data, the feed and the sitemap. Nobody clicks those by hand, so a broken one could go unnoticed for months.
06
Validation, and what's left
The last step runs html-validate at a pinned version, so the same markup always gets the same answer. Then the script prints every problem it found, not just the first, and exits with an error if there were any.
The deploy script runs it first, and so does every push to GitHub, so a broken link can't reach the site from either side.
It doesn't render anything. A layout that falls apart on a phone, or a sentence that says the wrong thing, still passes. Those I check by reading the page, which no script replaces.