diff --git a/apps/manacore/apps/landing/src/data/ecosystem-health.json b/apps/manacore/apps/landing/src/data/ecosystem-health.json index e1dae839b..05f5159df 100644 --- a/apps/manacore/apps/landing/src/data/ecosystem-health.json +++ b/apps/manacore/apps/landing/src/data/ecosystem-health.json @@ -1,26 +1,30 @@ { - "generatedAt": "2026-03-31T12:18:59.699Z", - "overallScore": 74, + "generatedAt": "2026-03-31T14:32:40.747Z", + "overallScore": 72, "scores": { "sharedPackages": 90, - "iconConsistency": 89, + "iconConsistency": 78, "modalConsistency": 19, - "errorHandling": 17, - "i18nCoverage": 86, + "errorHandling": 16, + "i18nCoverage": 87, "localFirst": 93, "styleConsistency": 87, - "errorBoundaries": 54, + "errorBoundaries": 52, "typescriptStrict": 100, - "testCoverage": 72, - "pwaSupport": 2, + "testCoverage": 73, + "pwaSupport": 4, "maintainability": 0, - "securityHeaders": 76, - "skeletonLoading": 76, + "securityHeaders": 73, + "skeletonLoading": 77, "toastConsistency": 100, - "storePattern": 95, - "sharedTypes": 62, + "storePattern": 94, + "sharedTypes": 53, "depFreshness": 80, - "bundleConfig": 100 + "bundleConfig": 100, + "gitActivity": 97, + "a11yIndicators": 36, + "authGuardCoverage": 83, + "dockerReadiness": 80 }, "weights": { "sharedPackages": 20, @@ -41,13 +45,17 @@ "storePattern": 4, "sharedTypes": 3, "depFreshness": 2, - "bundleConfig": 2 + "bundleConfig": 2, + "gitActivity": 3, + "a11yIndicators": 4, + "authGuardCoverage": 5, + "dockerReadiness": 3 }, "details": { "icons": { - "adoption": 89, + "adoption": 78, "phosphorFiles": 347, - "inlineSvgFiles": 45, + "inlineSvgFiles": 98, "perApp": { "calc": { "phosphor": 1, @@ -97,6 +105,10 @@ "phosphor": 26, "inlineSvg": 0 }, + "memoro": { + "phosphor": 0, + "inlineSvg": 53 + }, "moodlit": { "phosphor": 5, "inlineSvg": 0 @@ -175,72 +187,72 @@ }, "packages": { "coreAdoption": 90, - "totalApps": 29, + "totalApps": 30, "perPackage": { "Auth": { - "count": 29, - "total": 29, + "count": 30, + "total": 30, "adoption": 100 }, "UI": { - "count": 28, - "total": 29, + "count": 29, + "total": 30, "adoption": 97 }, "Theme": { - "count": 24, - "total": 29, + "count": 25, + "total": 30, "adoption": 83 }, "Branding": { - "count": 28, - "total": 29, + "count": 29, + "total": 30, "adoption": 97 }, "i18n": { - "count": 23, - "total": 29, - "adoption": 79 + "count": 24, + "total": 30, + "adoption": 80 }, "Error Tracking": { - "count": 24, - "total": 29, + "count": 25, + "total": 30, "adoption": 83 }, "Icons": { - "count": 25, - "total": 29, - "adoption": 86 + "count": 26, + "total": 30, + "adoption": 87 }, "Local Store": { - "count": 27, - "total": 29, + "count": 28, + "total": 30, "adoption": 93 } } }, "errors": { - "adoption": 17, - "inline": 193, + "adoption": 16, + "inline": 217, "shared": 40 }, "i18n": { - "adoption": 86, - "withI18n": 25, + "adoption": 87, + "withI18n": 26, "without": 4 }, "localFirst": { "adoption": 93, - "count": 27 + "count": 28 }, "styles": { "themeAdoption": 83, "tailwindAdoption": 90 }, "errorBoundaries": { - "adoption": 54, - "errorAdoption": 28, - "offlineAdoption": 79, + "adoption": 52, + "errorAdoption": 27, + "offlineAdoption": 77, "appsWithErrorPage": 8, "appsWithOfflinePage": 23, "missing": { @@ -252,6 +264,7 @@ "context", "manadeck", "manavoxel", + "memoro", "moodlit", "mukke", "news", @@ -267,20 +280,20 @@ "wisekeep", "zitare" ], - "offline": ["manavoxel", "moodlit", "news", "playground", "uload", "wisekeep"] + "offline": ["manavoxel", "memoro", "moodlit", "news", "playground", "uload", "wisekeep"] } }, "typescript": { "adoption": 100, - "strictApps": 29, + "strictApps": 30, "nonStrict": [] }, "tests": { - "adoption": 72, - "e2eAdoption": 14, - "appsWithTests": 21, + "adoption": 73, + "e2eAdoption": 13, + "appsWithTests": 22, "appsWithE2e": 4, - "totalTestFiles": 110, + "totalTestFiles": 122, "noTests": [ "calc", "inventar", @@ -293,10 +306,10 @@ ] }, "pwa": { - "adoption": 2, - "manifestAdoption": 3, + "adoption": 4, + "manifestAdoption": 7, "swAdoption": 0, - "appsWithManifest": 1, + "appsWithManifest": 2, "appsWithServiceWorker": 0, "noPwa": [ "calc", @@ -331,7 +344,7 @@ }, "fileSizes": { "adoption": 0, - "totalLargeFiles": 38, + "totalLargeFiles": 42, "largestFile": { "path": "matrix: lib/matrix/store.svelte.ts", "lines": 2019 @@ -350,7 +363,7 @@ { "app": "contacts", "file": "lib/components/ContactDetailModal.svelte", - "lines": 1511 + "lines": 1502 }, { "app": "todo", @@ -375,7 +388,7 @@ { "app": "calendar", "file": "lib/components/calendar/WeekView.svelte", - "lines": 953 + "lines": 946 }, { "app": "calendar", @@ -390,9 +403,10 @@ ] }, "todos": { - "totalCount": 22, + "totalCount": 33, "perApp": { "manacore": 13, + "memoro": 11, "contacts": 5, "todo": 2, "chat": 1, @@ -400,13 +414,22 @@ } }, "securityHeaders": { - "adoption": 76, + "adoption": 73, "appsWithHeaders": 22, - "missing": ["manavoxel", "moodlit", "news", "playground", "times", "uload", "wisekeep"] + "missing": [ + "manavoxel", + "memoro", + "moodlit", + "news", + "playground", + "times", + "uload", + "wisekeep" + ] }, "skeletons": { - "adoption": 76, - "appsWithSkeletons": 22, + "adoption": 77, + "appsWithSkeletons": 23, "missing": ["citycorners", "manadeck", "manavoxel", "planta", "presi", "times", "zitare"] }, "toasts": { @@ -415,25 +438,79 @@ "customToast": 0 }, "storePattern": { - "adoption": 95, - "totalRunesStores": 176, - "totalOldStores": 9, + "adoption": 94, + "totalRunesStores": 177, + "totalOldStores": 12, "appsWithRunesStores": 24, - "appsWithOldStores": 4 + "appsWithOldStores": 5 }, "sharedTypes": { - "adoption": 62, + "adoption": 53, "sharedTypeImports": 8, - "localTypeFiles": 5 + "localTypeFiles": 7 }, "depFreshness": { "adoption": 80, - "totalDeps": 1068, + "totalDeps": 1106, "avgDepsPerApp": 37 }, "bundleSize": { "adoption": 100, - "appsWithBundleConfig": 29 + "appsWithBundleConfig": 30 + }, + "gitActivity": { + "adoption": 97, + "activeApps": 29, + "perApp": { + "manacore": 166, + "todo": 135, + "calendar": 125, + "contacts": 95, + "mukke": 90, + "storage": 87, + "zitare": 80, + "chat": 73, + "picture": 70, + "presi": 68, + "clock": 64, + "manadeck": 63, + "citycorners": 61, + "nutriphi": 56, + "photos": 56, + "planta": 55, + "context": 48, + "matrix": 48, + "skilltree": 46, + "questions": 39, + "inventar": 19, + "playground": 18, + "manavoxel": 11, + "uload": 10, + "calc": 6, + "moodlit": 5, + "times": 5, + "news": 4, + "wisekeep": 4, + "memoro": 0 + } + }, + "a11y": { + "adoption": 36, + "altAdoption": 100, + "dialogAdoption": 0, + "trapAdoption": 7, + "totalImgFiles": 21, + "totalImgWithAlt": 21 + }, + "authGuard": { + "adoption": 83, + "appsWithAuthGuard": 25, + "missing": ["manacore", "manavoxel", "matrix", "memoro", "playground"] + }, + "docker": { + "adoption": 80, + "appsWithDockerfile": 24, + "missing": ["context", "moodlit", "news", "planta", "questions", "wisekeep"] } }, "apps": [ @@ -449,6 +526,7 @@ "manadeck", "manavoxel", "matrix", + "memoro", "moodlit", "mukke", "news", diff --git a/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro b/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro index 3904c0c18..8d4e0de9d 100644 --- a/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro +++ b/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro @@ -148,6 +148,30 @@ const categories = [ icon: '⚙️', description: 'SvelteKit Adapter konfiguriert', }, + { + key: 'gitActivity', + label: 'Git Activity', + icon: '🔄', + description: 'Apps mit Commits in den letzten 30 Tagen', + }, + { + key: 'a11yIndicators', + label: 'A11y Indicators', + icon: '♿', + description: 'Alt-Texte, role=dialog, focusTrap', + }, + { + key: 'authGuardCoverage', + label: 'Auth Guard', + icon: '🔑', + description: 'AuthGate / authGuard in allen Apps', + }, + { + key: 'dockerReadiness', + label: 'Docker Readiness', + icon: '🐳', + description: 'Dockerfile pro App vorhanden', + }, ]; const packageDetails = details.packages.perPackage; diff --git a/scripts/ecosystem-audit.mjs b/scripts/ecosystem-audit.mjs index f1f57a68e..06308ec0f 100644 --- a/scripts/ecosystem-audit.mjs +++ b/scripts/ecosystem-audit.mjs @@ -754,6 +754,123 @@ function measureBundleSize() { return { adoption, appsWithBundleConfig }; } +function measureGitActivity() { + console.log('📊 Measuring Git Activity (last 30 days)...'); + let activeApps = 0; + const perApp = {}; + + for (const app of WEB_APPS) { + const appDir = join(APPS_DIR, app); + try { + const result = execSync( + `git log --since="30 days ago" --oneline -- "${appDir}" 2>/dev/null | wc -l`, + { encoding: 'utf-8' } + ); + const commits = parseInt(result.trim()) || 0; + perApp[app] = commits; + if (commits > 0) activeApps++; + } catch { + perApp[app] = 0; + } + } + + const adoption = Math.round((activeApps / WEB_APPS.length) * 100); + const sorted = Object.entries(perApp).sort(([, a], [, b]) => b - a); + console.log(` Active apps (≥1 commit): ${activeApps}/${WEB_APPS.length} (${adoption}%)`); + sorted.slice(0, 5).forEach(([a, c]) => console.log(` ${a}: ${c} commits`)); + console.log(''); + + return { adoption, activeApps, perApp: Object.fromEntries(sorted) }; +} + +function measureA11yIndicators() { + console.log('📊 Measuring Accessibility Indicators...'); + let totalImgFiles = 0; + let totalImgWithAlt = 0; + let appsWithDialogRole = 0; + let appsWithFocusTrap = 0; + + for (const app of WEB_APPS) { + const webSrc = join(APPS_DIR, app, 'apps/web/src'); + if (!existsSync(webSrc)) continue; + + // img tags with and without alt + const imgWithAlt = grepOccurrences(']*alt=', webSrc, '*.svelte'); + const imgWithoutAlt = grepOccurrences(']*alt=)', webSrc, '*.svelte'); + totalImgWithAlt += imgWithAlt; + totalImgFiles += imgWithAlt + imgWithoutAlt; + + if (grepCount('role="dialog"', webSrc, '*.svelte') > 0) appsWithDialogRole++; + if (grepCount('use:focusTrap', webSrc, '*.svelte') > 0) appsWithFocusTrap++; + } + + // Score: alt text coverage + dialog/focusTrap presence + const altAdoption = totalImgFiles > 0 ? Math.round((totalImgWithAlt / totalImgFiles) * 100) : 100; + const dialogAdoption = Math.round((appsWithDialogRole / WEB_APPS.length) * 100); + const trapAdoption = Math.round((appsWithFocusTrap / WEB_APPS.length) * 100); + const adoption = Math.round((altAdoption + dialogAdoption + trapAdoption) / 3); + + console.log(` img with alt: ${totalImgWithAlt}/${totalImgFiles} (${altAdoption}%)`); + console.log( + ` Apps with role=dialog: ${appsWithDialogRole}/${WEB_APPS.length} (${dialogAdoption}%)` + ); + console.log(` Apps with focusTrap: ${appsWithFocusTrap}/${WEB_APPS.length} (${trapAdoption}%)`); + console.log(` Combined: ${adoption}%\n`); + + return { adoption, altAdoption, dialogAdoption, trapAdoption, totalImgFiles, totalImgWithAlt }; +} + +function measureAuthGuardCoverage() { + console.log('📊 Measuring Auth Guard Coverage...'); + let appsWithAuthGuard = 0; + const missing = []; + + for (const app of WEB_APPS) { + const webSrc = join(APPS_DIR, app, 'apps/web/src'); + if (!existsSync(webSrc)) continue; + + const hasAuthGuard = + grepCount('AuthGate', webSrc, '*.svelte') > 0 || + grepCount('authGuard', webSrc, '*.ts') > 0 || + grepCount('authGuard', webSrc, '*.server.ts') > 0 || + grepCount('requireAuth', webSrc, '*.ts') > 0; + + if (hasAuthGuard) appsWithAuthGuard++; + else missing.push(app); + } + + const adoption = Math.round((appsWithAuthGuard / WEB_APPS.length) * 100); + console.log(` Apps with auth guard: ${appsWithAuthGuard}/${WEB_APPS.length} (${adoption}%)`); + if (missing.length > 0 && missing.length <= 10) console.log(` Missing: ${missing.join(', ')}`); + console.log(''); + + return { adoption, appsWithAuthGuard, missing }; +} + +function measureDockerReadiness() { + console.log('📊 Measuring Docker Readiness...'); + let appsWithDockerfile = 0; + const missing = []; + + for (const app of WEB_APPS) { + const appDir = join(APPS_DIR, app); + const hasDockerfile = + existsSync(join(appDir, 'apps/web/Dockerfile')) || + existsSync(join(appDir, 'Dockerfile')) || + fileCount('Dockerfile', appDir) > 0; + + if (hasDockerfile) appsWithDockerfile++; + else missing.push(app); + } + + const adoption = Math.round((appsWithDockerfile / WEB_APPS.length) * 100); + console.log(` Apps with Dockerfile: ${appsWithDockerfile}/${WEB_APPS.length} (${adoption}%)`); + if (missing.length > 0 && missing.length <= 10) console.log(` Missing: ${missing.join(', ')}`); + console.log(''); + + return { adoption, appsWithDockerfile, missing }; +} + // ============================================================ // Main // ============================================================ @@ -781,6 +898,10 @@ const storePattern = measureStorePattern(); const sharedTypes = measureSharedTypeUsage(); const depFreshness = measureDependencyFreshness(); const bundleSize = measureBundleSize(); +const gitActivity = measureGitActivity(); +const a11y = measureA11yIndicators(); +const authGuard = measureAuthGuardCoverage(); +const docker = measureDockerReadiness(); // Calculate overall scores const scores = { @@ -803,6 +924,10 @@ const scores = { sharedTypes: sharedTypes.adoption, depFreshness: depFreshness.adoption, bundleConfig: bundleSize.adoption, + gitActivity: gitActivity.adoption, + a11yIndicators: a11y.adoption, + authGuardCoverage: authGuard.adoption, + dockerReadiness: docker.adoption, }; // Weighted overall score @@ -826,6 +951,10 @@ const weights = { sharedTypes: 3, depFreshness: 2, bundleConfig: 2, + gitActivity: 3, + a11yIndicators: 4, + authGuardCoverage: 5, + dockerReadiness: 3, }; let totalWeight = 0; @@ -887,6 +1016,10 @@ const output = { sharedTypes, depFreshness, bundleSize, + gitActivity, + a11y, + authGuard, + docker, }, apps: WEB_APPS, };