#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const cp = require('child_process');
const repoRoot = process.cwd();
const pkgPath = path.join(repoRoot, 'package.json');
const publicDir = path.join(repoRoot, 'public');
if (!fs.existsSync(pkgPath) || !fs.existsSync(publicDir)) process.exit(0);
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
const version = pkg.version;
const failures = [];
function git(args) {
return cp.execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8' }).trim();
}
function walkHtmlFiles(dir) {
const out = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) out.push(...walkHtmlFiles(full));
else if (entry.isFile() && entry.name.endsWith('.html')) out.push(full);
}
return out;
}
function isLocalAsset(rawUrl) {
return !/^(?:[a-z]+:|\/\/|#)/i.test(rawUrl);
}
for (const file of walkHtmlFiles(publicDir)) {
const rel = path.relative(repoRoot, file);
const content = fs.readFileSync(file, 'utf8');
for (const match of content.matchAll(/]*href=["']([^"']+\.css(?:\?[^"']*)?)["']/gi)) {
const url = match[1];
if (isLocalAsset(url) && !new URLSearchParams((url.split('?')[1] || '')).get('v')?.includes(version)) {
failures.push(rel + ': stylesheet version does not match package.json');
}
}
for (const match of content.matchAll(/