Symfony 7.4 / Twig / Doctrine DBAL auf herkömmlichem PHP-Webhosting. Versionierte Blockdokumente (v1-v3) mit unveränderlichen Revisionen, getrennten Entwürfen/Veröffentlichungen und optimistischer Konflikterkennung. Visueller Editor mit Strukturbaum, Live-Vorschau, responsiven Gerätewerten, Abschnittsvorlagen, Bildmediathek, Galerien und Effekten. Öffentliche Ausgabe ohne Editor-JavaScript. Geprüft: 41 PHP-Tests / 155 Assertions, 10 JavaScript-Modelltests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
50 lines
2.5 KiB
JavaScript
50 lines
2.5 KiB
JavaScript
import lighthouse from 'lighthouse';
|
|
import desktopConfig from 'lighthouse/core/config/desktop-config.js';
|
|
import { chromium } from 'playwright';
|
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
import net from 'node:net';
|
|
|
|
const origin = process.env.CMS_URL || 'http://127.0.0.1:8080';
|
|
const output = process.env.CMS_AUDIT_DIR || 'reports/lighthouse';
|
|
const preflight = await fetch(origin);
|
|
if (!preflight.ok || /noindex/i.test(preflight.headers.get('x-robots-tag') || '')) {
|
|
throw new Error('Audit requires an indexable HTTP 200 page in production mode. Check APP_ENV and APP_DEBUG.');
|
|
}
|
|
await mkdir(output, { recursive: true });
|
|
const portProbe = net.createServer();
|
|
await new Promise(resolve => portProbe.listen(0, '127.0.0.1', resolve));
|
|
const port = portProbe.address().port;
|
|
await new Promise(resolve => portProbe.close(resolve));
|
|
const chrome = await chromium.launch({ args: [`--remote-debugging-port=${port}`] });
|
|
const summary = [];
|
|
try {
|
|
for (const profile of ['mobile', 'desktop']) {
|
|
const runs = [];
|
|
for (let run = 1; run <= 3; run++) {
|
|
const result = await lighthouse(origin, {
|
|
port,
|
|
output: 'json',
|
|
logLevel: 'error',
|
|
}, profile === 'desktop' ? desktopConfig : undefined);
|
|
const lhr = result.lhr;
|
|
if (lhr.configSettings.formFactor !== profile) {
|
|
throw new Error(`Requested ${profile}, measured ${lhr.configSettings.formFactor}`);
|
|
}
|
|
await writeFile(`${output}/${profile}-${run}.json`, result.report);
|
|
runs.push(lhr);
|
|
console.log(profile, run, Object.fromEntries(Object.entries(lhr.categories).map(([key, value]) => [key, value.score])));
|
|
}
|
|
const scores = {};
|
|
for (const category of Object.keys(runs[0].categories)) {
|
|
const values = runs.map(r => r.categories[category].score).filter(v => v !== null).sort((a, b) => a - b);
|
|
scores[category] = values.length ? values[Math.floor(values.length / 2)] : null;
|
|
}
|
|
summary.push({ profile, scores, lighthouseVersion: runs[0].lighthouseVersion, settings: runs[0].configSettings, environment: runs[0].environment, note: 'Local prototype only. No customer-site acceptance or field-data claim.' });
|
|
}
|
|
await writeFile(`${output}/summary.json`, JSON.stringify(summary, null, 2));
|
|
const passed = summary.every(r => r.scores.performance >= .95 && ['accessibility', 'best-practices', 'seo'].every(c => r.scores[c] === 1) && (r.scores['agentic-browsing'] === undefined || r.scores['agentic-browsing'] === 1));
|
|
if (!passed) process.exitCode = 1;
|
|
} finally {
|
|
await chrome.close();
|
|
}
|