diff --git a/apps/manacore/apps/landing/src/data/ecosystem-health.json b/apps/manacore/apps/landing/src/data/ecosystem-health.json index f02bc5a93..e1dae839b 100644 --- a/apps/manacore/apps/landing/src/data/ecosystem-health.json +++ b/apps/manacore/apps/landing/src/data/ecosystem-health.json @@ -1,6 +1,6 @@ { - "generatedAt": "2026-03-31T12:12:26.680Z", - "overallScore": 72, + "generatedAt": "2026-03-31T12:18:59.699Z", + "overallScore": 74, "scores": { "sharedPackages": 90, "iconConsistency": 89, @@ -15,7 +15,12 @@ "pwaSupport": 2, "maintainability": 0, "securityHeaders": 76, - "skeletonLoading": 76 + "skeletonLoading": 76, + "toastConsistency": 100, + "storePattern": 95, + "sharedTypes": 62, + "depFreshness": 80, + "bundleConfig": 100 }, "weights": { "sharedPackages": 20, @@ -31,7 +36,12 @@ "pwaSupport": 4, "maintainability": 4, "securityHeaders": 5, - "skeletonLoading": 3 + "skeletonLoading": 3, + "toastConsistency": 3, + "storePattern": 4, + "sharedTypes": 3, + "depFreshness": 2, + "bundleConfig": 2 }, "details": { "icons": { @@ -357,16 +367,16 @@ "file": "lib/components/NewContactModal.svelte", "lines": 1130 }, - { - "app": "calendar", - "file": "lib/components/calendar/WeekView.svelte", - "lines": 959 - }, { "app": "zitare", "file": "routes/(app)/lists/[id]/+page.svelte", "lines": 958 }, + { + "app": "calendar", + "file": "lib/components/calendar/WeekView.svelte", + "lines": 953 + }, { "app": "calendar", "file": "routes/(app)/settings/sync/+page.svelte", @@ -398,6 +408,32 @@ "adoption": 76, "appsWithSkeletons": 22, "missing": ["citycorners", "manadeck", "manavoxel", "planta", "presi", "times", "zitare"] + }, + "toasts": { + "adoption": 100, + "sharedToast": 204, + "customToast": 0 + }, + "storePattern": { + "adoption": 95, + "totalRunesStores": 176, + "totalOldStores": 9, + "appsWithRunesStores": 24, + "appsWithOldStores": 4 + }, + "sharedTypes": { + "adoption": 62, + "sharedTypeImports": 8, + "localTypeFiles": 5 + }, + "depFreshness": { + "adoption": 80, + "totalDeps": 1068, + "avgDepsPerApp": 37 + }, + "bundleSize": { + "adoption": 100, + "appsWithBundleConfig": 29 } }, "apps": [ diff --git a/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro b/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro index 63453d877..3904c0c18 100644 --- a/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro +++ b/apps/manacore/apps/landing/src/pages/manascore/ecosystem.astro @@ -118,6 +118,36 @@ const categories = [ icon: 'πŸ’€', description: 'Skeleton-Komponenten fΓΌr Loading States', }, + { + key: 'toastConsistency', + label: 'Toast Consistency', + icon: 'πŸ””', + description: 'shared-ui toastStore vs window.alert', + }, + { + key: 'storePattern', + label: 'Store Pattern', + icon: '⚑', + description: 'Svelte 5 Runes (.svelte.ts) vs alte Stores', + }, + { + key: 'sharedTypes', + label: 'Shared Types', + icon: 'πŸ”—', + description: 'shared-types Imports vs lokale Type-Definitionen', + }, + { + key: 'depFreshness', + label: 'Dep Freshness', + icon: 'πŸ“¦', + description: 'Durchschnittliche Dependencies pro App', + }, + { + key: 'bundleConfig', + label: 'Bundle Config', + icon: 'βš™οΈ', + description: 'SvelteKit Adapter konfiguriert', + }, ]; const packageDetails = details.packages.perPackage; diff --git a/scripts/ecosystem-audit.mjs b/scripts/ecosystem-audit.mjs index 7628740d2..f1f57a68e 100644 --- a/scripts/ecosystem-audit.mjs +++ b/scripts/ecosystem-audit.mjs @@ -601,6 +601,159 @@ function measureSkeletonLoading() { return { adoption, appsWithSkeletons, missing }; } +function measureToastConsistency() { + console.log('πŸ“Š Measuring Toast Consistency...'); + let sharedToast = 0; + let customToast = 0; + + for (const app of WEB_APPS) { + const webSrc = join(APPS_DIR, app, 'apps/web/src'); + if (!existsSync(webSrc)) continue; + + const shared = + grepOccurrences('toastStore', webSrc, '*.svelte') + + grepOccurrences('toastStore', webSrc, '*.ts'); + const custom = + grepOccurrences('window.alert', webSrc, '*.svelte') + + grepOccurrences('window.alert', webSrc, '*.ts'); + + sharedToast += shared; + customToast += custom; + } + + const total = sharedToast + customToast; + const adoption = total > 0 ? Math.round((sharedToast / total) * 100) : 100; + + console.log(` toastStore usage: ${sharedToast}`); + console.log(` window.alert usage: ${customToast}`); + console.log(` Adoption: ${adoption}%\n`); + + return { adoption, sharedToast, customToast }; +} + +function measureStorePattern() { + console.log('πŸ“Š Measuring Store Pattern (Svelte 5 Runes)...'); + let appsWithRunesStores = 0; + let appsWithOldStores = 0; + let totalRunesStores = 0; + let totalOldStores = 0; + + for (const app of WEB_APPS) { + const webSrc = join(APPS_DIR, app, 'apps/web/src'); + if (!existsSync(webSrc)) continue; + + const runesStores = fileCount('*.svelte.ts', join(webSrc, 'lib/stores')); + const oldStores = + grepCount('writable(', join(webSrc, 'lib/stores'), '*.ts') + + grepCount('readable(', join(webSrc, 'lib/stores'), '*.ts'); + + totalRunesStores += runesStores; + totalOldStores += oldStores; + + if (runesStores > 0 && oldStores === 0) appsWithRunesStores++; + else if (oldStores > 0) appsWithOldStores++; + } + + const total = totalRunesStores + totalOldStores; + const adoption = total > 0 ? Math.round((totalRunesStores / total) * 100) : 100; + + console.log(` Runes stores (.svelte.ts): ${totalRunesStores}`); + console.log(` Old stores (writable/readable): ${totalOldStores}`); + console.log(` Adoption: ${adoption}%\n`); + + return { adoption, totalRunesStores, totalOldStores, appsWithRunesStores, appsWithOldStores }; +} + +function measureSharedTypeUsage() { + console.log('πŸ“Š Measuring Shared Type Usage...'); + let sharedTypeImports = 0; + let localTypeFiles = 0; + + for (const app of WEB_APPS) { + const webSrc = join(APPS_DIR, app, 'apps/web/src'); + if (!existsSync(webSrc)) continue; + + sharedTypeImports += + grepOccurrences('shared-types', webSrc, '*.ts') + + grepOccurrences('shared-types', webSrc, '*.svelte'); + + localTypeFiles += fileCount('*.types.ts', webSrc) + fileCount('types.ts', webSrc); + } + + // Higher shared usage relative to local types = better + const total = sharedTypeImports + localTypeFiles; + const adoption = total > 0 ? Math.round((sharedTypeImports / total) * 100) : 50; + + console.log(` shared-types imports: ${sharedTypeImports}`); + console.log(` Local type files: ${localTypeFiles}`); + console.log(` Adoption: ${adoption}%\n`); + + return { adoption, sharedTypeImports, localTypeFiles }; +} + +function measureDependencyFreshness() { + console.log('πŸ“Š Measuring Dependency Freshness...'); + let totalDeps = 0; + + for (const app of WEB_APPS) { + const pkgPath = join(APPS_DIR, app, 'apps/web/package.json'); + try { + const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')); + const deps = Object.keys(pkg.dependencies || {}).length; + const devDeps = Object.keys(pkg.devDependencies || {}).length; + totalDeps += deps + devDeps; + } catch {} + } + + // Use npm outdated on the workspace β€” too slow per-app, just count total deps + // Simple heuristic: more deps = more risk of outdated + // Real measurement would need `pnpm outdated` which is slow + const avgDepsPerApp = Math.round(totalDeps / WEB_APPS.length); + + console.log(` Total dependencies: ${totalDeps}`); + console.log(` Average per app: ${avgDepsPerApp}`); + + // Score based on average deps (fewer = better maintained) + // <20 = 100%, 20-40 = 80%, 40-60 = 60%, >60 = 40% + let adoption; + if (avgDepsPerApp < 20) adoption = 100; + else if (avgDepsPerApp < 40) adoption = 80; + else if (avgDepsPerApp < 60) adoption = 60; + else adoption = 40; + + console.log(` Score: ${adoption}%\n`); + + return { adoption, totalDeps, avgDepsPerApp }; +} + +function measureBundleSize() { + console.log('πŸ“Š Measuring Bundle Size Awareness...'); + // Check which apps have build output analysis tools or bundle size monitoring + let appsWithBundleConfig = 0; + + for (const app of WEB_APPS) { + const webDir = join(APPS_DIR, app, 'apps/web'); + + // Check for bundle analysis indicators + const hasAnalysis = + grepCount('analyzeBundle', webDir, '*.config.*') > 0 || + grepCount('vite-plugin-inspect', webDir, '*.config.*') > 0 || + existsSync(join(webDir, '.svelte-kit/output')) || + // SvelteKit apps with adapter-node or adapter-auto are well-configured + grepCount('adapter', webDir, 'svelte.config.js') > 0; + + if (hasAnalysis) appsWithBundleConfig++; + } + + const adoption = Math.round((appsWithBundleConfig / WEB_APPS.length) * 100); + + console.log( + ` Apps with build config: ${appsWithBundleConfig}/${WEB_APPS.length} (${adoption}%)\n` + ); + + return { adoption, appsWithBundleConfig }; +} + // ============================================================ // Main // ============================================================ @@ -623,6 +776,11 @@ const fileSizes = measureFileSizes(); const todos = measureTodoFixmeCount(); const securityHeaders = measureSecurityHeaders(); const skeletons = measureSkeletonLoading(); +const toasts = measureToastConsistency(); +const storePattern = measureStorePattern(); +const sharedTypes = measureSharedTypeUsage(); +const depFreshness = measureDependencyFreshness(); +const bundleSize = measureBundleSize(); // Calculate overall scores const scores = { @@ -640,6 +798,11 @@ const scores = { maintainability: fileSizes.adoption, securityHeaders: securityHeaders.adoption, skeletonLoading: skeletons.adoption, + toastConsistency: toasts.adoption, + storePattern: storePattern.adoption, + sharedTypes: sharedTypes.adoption, + depFreshness: depFreshness.adoption, + bundleConfig: bundleSize.adoption, }; // Weighted overall score @@ -658,6 +821,11 @@ const weights = { maintainability: 4, securityHeaders: 5, skeletonLoading: 3, + toastConsistency: 3, + storePattern: 4, + sharedTypes: 3, + depFreshness: 2, + bundleConfig: 2, }; let totalWeight = 0; @@ -714,6 +882,11 @@ const output = { todos, securityHeaders, skeletons, + toasts, + storePattern, + sharedTypes, + depFreshness, + bundleSize, }, apps: WEB_APPS, };