mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:41:09 +02:00
feat(manascore): add Tier 2 ecosystem metrics
5 new metrics: - Toast Consistency (100%) — all apps use shared toastStore - Store Pattern (95%) — 176 Runes stores vs 9 old writable/readable - Shared Types (62%) — shared-types imports vs local type files - Dep Freshness (80%) — avg 37 deps per app - Bundle Config (100%) — all apps have SvelteKit adapter Ecosystem Health Score: 74/100 (19 metrics total) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3b7b6c9761
commit
201819280e
3 changed files with 248 additions and 9 deletions
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue