mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:21:10 +02:00
feat(ecosystem-audit): add Tier 3 metrics (git activity, a11y, auth guard, docker)
Adds 4 new Tier 3 metrics to the ecosystem health audit script: - Git Activity: % of apps with commits in the last 30 days (97%) - A11y Indicators: alt-text coverage, role=dialog, focusTrap (36%) - Auth Guard Coverage: AuthGate/authGuard presence per app (83%) - Docker Readiness: Dockerfile present per app (80%) Overall score updated from 74 → 72 (23 metrics, 135 total weight). Dashboard at /manascore/ecosystem updated with new category rows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
df1cd4bfa0
commit
d460c9ec07
3 changed files with 303 additions and 68 deletions
|
|
@ -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('<img[^>]*alt=', webSrc, '*.svelte');
|
||||
const imgWithoutAlt = grepOccurrences('<img(?![^>]*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,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue