From 3fb1eddc04c9ca25d30d9ba997f34e2a3656ebab Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 31 Mar 2026 14:13:04 +0200 Subject: [PATCH] feat(manascore): add security headers, skeleton loading, TODO count metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New metrics: - Security Headers (76%) β€” apps with setSecurityHeaders() in hooks - Skeleton Loading (76%) β€” apps with skeleton/loading state components - TODO/FIXME Count (22 total) β€” technical debt info metric (not weighted) Ecosystem Health Score: 72/100 (15 metrics total) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../landing/src/data/ecosystem-health.json | 56 +++++++--- .../src/pages/manascore/ecosystem.astro | 40 +++++++ scripts/ecosystem-audit.mjs | 102 +++++++++++++++++- 3 files changed, 180 insertions(+), 18 deletions(-) diff --git a/apps/manacore/apps/landing/src/data/ecosystem-health.json b/apps/manacore/apps/landing/src/data/ecosystem-health.json index 24e029ede..f02bc5a93 100644 --- a/apps/manacore/apps/landing/src/data/ecosystem-health.json +++ b/apps/manacore/apps/landing/src/data/ecosystem-health.json @@ -1,11 +1,11 @@ { - "generatedAt": "2026-03-31T12:04:45.568Z", - "overallScore": 70, + "generatedAt": "2026-03-31T12:12:26.680Z", + "overallScore": 72, "scores": { "sharedPackages": 90, "iconConsistency": 89, "modalConsistency": 19, - "errorHandling": 20, + "errorHandling": 17, "i18nCoverage": 86, "localFirst": 93, "styleConsistency": 87, @@ -13,7 +13,9 @@ "typescriptStrict": 100, "testCoverage": 72, "pwaSupport": 2, - "maintainability": 0 + "maintainability": 0, + "securityHeaders": 76, + "skeletonLoading": 76 }, "weights": { "sharedPackages": 20, @@ -26,13 +28,15 @@ "errorBoundaries": 8, "typescriptStrict": 7, "testCoverage": 7, - "pwaSupport": 5, - "maintainability": 5 + "pwaSupport": 4, + "maintainability": 4, + "securityHeaders": 5, + "skeletonLoading": 3 }, "details": { "icons": { "adoption": 89, - "phosphorFiles": 354, + "phosphorFiles": 347, "inlineSvgFiles": 45, "perApp": { "calc": { @@ -40,7 +44,7 @@ "inlineSvg": 0 }, "calendar": { - "phosphor": 35, + "phosphor": 28, "inlineSvg": 0 }, "chat": { @@ -155,9 +159,9 @@ }, "modals": { "adoption": 19, - "total": 64, + "total": 63, "sharedUsage": 12, - "focusTrapUsage": 7 + "focusTrapUsage": 6 }, "packages": { "coreAdoption": 90, @@ -206,9 +210,9 @@ } }, "errors": { - "adoption": 20, + "adoption": 17, "inline": 193, - "shared": 47 + "shared": 40 }, "i18n": { "adoption": 86, @@ -266,7 +270,7 @@ "e2eAdoption": 14, "appsWithTests": 21, "appsWithE2e": 4, - "totalTestFiles": 111, + "totalTestFiles": 110, "noTests": [ "calc", "inventar", @@ -341,12 +345,12 @@ { "app": "todo", "file": "lib/components/TaskItem.svelte", - "lines": 1217 + "lines": 1194 }, { "app": "todo", "file": "lib/components/board-views/ViewEditorModal.svelte", - "lines": 1190 + "lines": 1187 }, { "app": "contacts", @@ -356,7 +360,7 @@ { "app": "calendar", "file": "lib/components/calendar/WeekView.svelte", - "lines": 1043 + "lines": 959 }, { "app": "zitare", @@ -374,6 +378,26 @@ "lines": 849 } ] + }, + "todos": { + "totalCount": 22, + "perApp": { + "manacore": 13, + "contacts": 5, + "todo": 2, + "chat": 1, + "picture": 1 + } + }, + "securityHeaders": { + "adoption": 76, + "appsWithHeaders": 22, + "missing": ["manavoxel", "moodlit", "news", "playground", "times", "uload", "wisekeep"] + }, + "skeletons": { + "adoption": 76, + "appsWithSkeletons": 22, + "missing": ["citycorners", "manadeck", "manavoxel", "planta", "presi", "times", "zitare"] } }, "apps": [ diff --git a/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro b/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro index d83ca35f0..63453d877 100644 --- a/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro +++ b/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro @@ -106,6 +106,18 @@ const categories = [ icon: 'πŸ“', description: 'Dateien unter 500 Zeilen', }, + { + key: 'securityHeaders', + label: 'Security Headers', + icon: 'πŸ”', + description: 'CSP, X-Frame-Options via setSecurityHeaders()', + }, + { + key: 'skeletonLoading', + label: 'Skeleton Loading', + icon: 'πŸ’€', + description: 'Skeleton-Komponenten fΓΌr Loading States', + }, ]; const packageDetails = details.packages.perPackage; @@ -268,6 +280,34 @@ const generatedDate = new Date(ecosystemData.generatedAt).toLocaleDateString('de + + { + details.todos && details.todos.totalCount > 0 && ( +
+

+ Technical Debt (TODO/FIXME/HACK) +

+
+
+ Gesamt im Codebase + 50 ? 'text-red-500' : details.todos.totalCount > 20 ? 'text-orange-500' : 'text-yellow-500'}`} + > + {details.todos.totalCount} + +
+
+ {Object.entries(details.todos.perApp).map(([app, count]: [string, any]) => ( + + {app}: {count} + + ))} +
+
+
+ ) + } + { details.fileSizes?.topOffenders?.length > 0 && ( diff --git a/scripts/ecosystem-audit.mjs b/scripts/ecosystem-audit.mjs index b13f595c3..7628740d2 100644 --- a/scripts/ecosystem-audit.mjs +++ b/scripts/ecosystem-audit.mjs @@ -513,6 +513,94 @@ function measureFileSizes() { }; } +function measureTodoFixmeCount() { + console.log('πŸ“Š Measuring TODO/FIXME Count...'); + let totalCount = 0; + const perApp = {}; + + for (const app of WEB_APPS) { + const webSrc = join(APPS_DIR, app, 'apps/web/src'); + if (!existsSync(webSrc)) continue; + + const count = + grepOccurrences('TODO', webSrc, '*.svelte') + + grepOccurrences('TODO', webSrc, '*.ts') + + grepOccurrences('FIXME', webSrc, '*.svelte') + + grepOccurrences('FIXME', webSrc, '*.ts') + + grepOccurrences('HACK', webSrc, '*.svelte') + + grepOccurrences('HACK', webSrc, '*.ts'); + + if (count > 0) { + perApp[app] = count; + totalCount += count; + } + } + + const sorted = Object.entries(perApp).sort(([, a], [, b]) => b - a); + console.log(` Total TODO/FIXME/HACK: ${totalCount}`); + if (sorted.length > 0) { + sorted.slice(0, 5).forEach(([app, count]) => console.log(` ${app}: ${count}`)); + } + console.log(''); + + return { totalCount, perApp: Object.fromEntries(sorted) }; +} + +function measureSecurityHeaders() { + console.log('πŸ“Š Measuring Security Headers...'); + let appsWithHeaders = 0; + const missing = []; + + for (const app of WEB_APPS) { + const webSrc = join(APPS_DIR, app, 'apps/web/src'); + if (!existsSync(webSrc)) continue; + + const hasHeaders = + grepCount('setSecurityHeaders', webSrc, '*.ts') > 0 || + grepCount('Content-Security-Policy', webSrc, '*.ts') > 0 || + grepCount('X-Frame-Options', webSrc, '*.ts') > 0; + + if (hasHeaders) appsWithHeaders++; + else missing.push(app); + } + + const adoption = Math.round((appsWithHeaders / WEB_APPS.length) * 100); + console.log(` Apps with security headers: ${appsWithHeaders}/${WEB_APPS.length} (${adoption}%)`); + if (missing.length > 0 && missing.length <= 10) console.log(` Missing: ${missing.join(', ')}`); + console.log(''); + + return { adoption, appsWithHeaders, missing }; +} + +function measureSkeletonLoading() { + console.log('πŸ“Š Measuring Skeleton Loading States...'); + let appsWithSkeletons = 0; + const missing = []; + + for (const app of WEB_APPS) { + const webSrc = join(APPS_DIR, app, 'apps/web/src'); + if (!existsSync(webSrc)) continue; + + const hasSkeletons = + fileCount('*Skeleton*', webSrc) > 0 || + fileCount('*skeleton*', webSrc) > 0 || + grepCount('Skeleton', webSrc, '*.svelte') > 0 || + grepCount('animate-pulse', webSrc, '*.svelte') > 0; + + if (hasSkeletons) appsWithSkeletons++; + else missing.push(app); + } + + const adoption = Math.round((appsWithSkeletons / WEB_APPS.length) * 100); + console.log( + ` Apps with skeleton loading: ${appsWithSkeletons}/${WEB_APPS.length} (${adoption}%)` + ); + if (missing.length > 0 && missing.length <= 10) console.log(` Missing: ${missing.join(', ')}`); + console.log(''); + + return { adoption, appsWithSkeletons, missing }; +} + // ============================================================ // Main // ============================================================ @@ -532,6 +620,9 @@ const typescript = measureTypeScriptStrictness(); const tests = measureTestCoverage(); const pwa = measurePwaSupport(); const fileSizes = measureFileSizes(); +const todos = measureTodoFixmeCount(); +const securityHeaders = measureSecurityHeaders(); +const skeletons = measureSkeletonLoading(); // Calculate overall scores const scores = { @@ -547,6 +638,8 @@ const scores = { testCoverage: tests.adoption, pwaSupport: pwa.adoption, maintainability: fileSizes.adoption, + securityHeaders: securityHeaders.adoption, + skeletonLoading: skeletons.adoption, }; // Weighted overall score @@ -561,8 +654,10 @@ const weights = { errorBoundaries: 8, typescriptStrict: 7, testCoverage: 7, - pwaSupport: 5, - maintainability: 5, + pwaSupport: 4, + maintainability: 4, + securityHeaders: 5, + skeletonLoading: 3, }; let totalWeight = 0; @@ -616,6 +711,9 @@ const output = { tests, pwa, fileSizes, + todos, + securityHeaders, + skeletons, }, apps: WEB_APPS, };