claude again

This commit is contained in:
Andras Schmelczer 2026-05-11 08:12:35 +01:00
parent df2267a968
commit f3fc893675
81 changed files with 945 additions and 2813 deletions

View file

@ -35,14 +35,41 @@ if (jsFiles.length > 0) {
);
}
// Script tags are only allowed if they declare one of these safe `type`
// attributes (or are tagged with `data-theme-script`). All other scripts —
// including untyped ones, which default to executable JavaScript — are
// flagged.
const SAFE_SCRIPT_TYPES = new Set([
'application/ld+json',
'importmap',
'speculationrules',
]);
function isSafeScriptTag(tag) {
if (tag.includes('data-theme-script')) return true;
const typeMatch = tag.match(/\btype=["']([^"']+)["']/i);
if (!typeMatch) return false;
return SAFE_SCRIPT_TYPES.has(typeMatch[1].trim().toLowerCase());
}
for (const file of files.filter((candidate) => candidate.endsWith('.html'))) {
const html = await readFile(file, 'utf8');
const scripts = (
html.match(/<script\b(?![^>]*type=["']application\/ld\+json["'])[^>]*>/gi) ?? []
).filter((script) => !script.includes('data-theme-script'));
if (scripts?.length) {
const scripts = (html.match(/<script\b[^>]*>/gi) ?? []).filter(
(tag) => !isSafeScriptTag(tag)
);
if (scripts.length) {
failures.push(`Unexpected script tag in ${file}:\n${scripts.join('\n')}`);
}
// Inline event handlers (onclick=, onload=, etc.) execute JavaScript even
// without a <script> tag, so flag any attribute matching `on*=`. We strip
// <script> blocks first to avoid false positives from JSON-LD payloads.
const stripped = html.replace(/<script\b[\s\S]*?<\/script>/gi, '');
const handlerMatches = stripped.match(/\son\w+=/gi);
if (handlerMatches?.length) {
const unique = [...new Set(handlerMatches.map((m) => m.trim()))];
failures.push(`Unexpected inline event handler in ${file}:\n${unique.join('\n')}`);
}
}
if (failures.length > 0) {

View file

@ -6,16 +6,27 @@ import { chromium } from 'playwright';
const dist = path.resolve('dist');
const widths = [320, 390, 430, 768, 1024, 1440, 1920];
const MIME = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.webp': 'image/webp',
'.avif': 'image/avif',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.mp4': 'video/mp4',
'.webm': 'video/webm',
'.pdf': 'application/pdf',
};
function contentType(file) {
if (file.endsWith('.html')) return 'text/html; charset=utf-8';
if (file.endsWith('.css')) return 'text/css; charset=utf-8';
if (file.endsWith('.js')) return 'text/javascript; charset=utf-8';
if (file.endsWith('.svg')) return 'image/svg+xml';
if (file.endsWith('.png')) return 'image/png';
if (file.endsWith('.jpg') || file.endsWith('.jpeg')) return 'image/jpeg';
if (file.endsWith('.webp')) return 'image/webp';
if (file.endsWith('.woff2')) return 'font/woff2';
return 'application/octet-stream';
const ext = path.extname(file).toLowerCase();
return MIME[ext] ?? 'application/octet-stream';
}
async function walk(dir) {
@ -75,6 +86,12 @@ async function resolveFile(url) {
return path.join(dist, '404.html');
}
try {
await stat(dist);
} catch {
throw new Error('dist/ does not exist. Run npm run build first.');
}
const routes = await discoverRoutes();
const server = createServer(async (req, res) => {