Kant CMS: Grundgerüst, visueller Editor, Mediathek

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>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-12 10:19:05 +02:00
commit 82b07820bc
87 files changed
+14463

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
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();
}