Files
awoooi/apps/web/scripts/audit-ui-density.mjs
Your Name b97bc6f35e
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m1s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
feat(km): surface dynamic knowledge categories
2026-07-03 00:39:00 +08:00

131 lines
3.0 KiB
JavaScript

#!/usr/bin/env node
import { readdirSync, readFileSync, statSync } from 'node:fs'
import { join, relative } from 'node:path'
const appRoot = join(process.cwd(), 'src', 'app')
const textLikePatterns = [
/<p\b/g,
/<span\b/g,
/<li\b/g,
/<h[1-6]\b/g,
/<ReactMarkdown\b/g,
/prose\b/g,
/leading-\d/g,
]
const controlPatterns = [
/<button\b/g,
/<select\b/g,
/<input\b/g,
/<Link\b/g,
/role=["']tab/g,
/type=["']checkbox/g,
]
const layoutPatterns = [
/grid-cols-/g,
/flex\b/g,
/overflow-y-auto/g,
/sticky\b/g,
]
const textWallPatterns = [
/max-w-(?:2xl|3xl|4xl|5xl|6xl|7xl)/g,
/line-clamp-[4-9]/g,
/prose\b/g,
/<ReactMarkdown\b/g,
]
const manualTerms = [
/\u4eba\u5de5/g,
/\u624b\u52d5/g,
/manual/gi,
/needs_human/gi,
/human_/gi,
]
const fixedTaxonomyPatterns = [
/const\s+CATEGORIES\s*=/g,
]
function collectPages(dir, acc = []) {
for (const name of readdirSync(dir)) {
const fullPath = join(dir, name)
const stats = statSync(fullPath)
if (stats.isDirectory()) {
collectPages(fullPath, acc)
} else if (name === 'page.tsx') {
acc.push(fullPath)
}
}
return acc
}
function countMatches(source, patterns) {
return patterns.reduce((sum, pattern) => sum + (source.match(pattern) ?? []).length, 0)
}
function routeFromPage(filePath) {
return relative(appRoot, filePath)
.replace(/\/page\.tsx$/, '')
.replace(/\[locale\]/, ':locale')
}
const pages = collectPages(appRoot)
.map(filePath => {
const source = readFileSync(filePath, 'utf8')
const lineCount = source.split('\n').length
const textSignals = countMatches(source, textLikePatterns)
const controls = countMatches(source, controlPatterns)
const layoutSignals = countMatches(source, layoutPatterns)
const textWallSignals = countMatches(source, textWallPatterns)
const manualSignals = countMatches(source, manualTerms)
const fixedTaxonomySignals = countMatches(source, fixedTaxonomyPatterns)
const nestedCardSignals = (source.match(/rounded-(?:md|lg|xl)[^`'"]*border/g) ?? []).length
const score =
Math.round(
(textSignals * 1.2)
+ (textWallSignals * 8)
+ (manualSignals * 4)
+ (fixedTaxonomySignals * 10)
+ (nestedCardSignals * 0.7)
- (controls * 1.5)
- (layoutSignals * 0.4),
)
return {
route: routeFromPage(filePath),
file: relative(process.cwd(), filePath),
lines: lineCount,
score,
textSignals,
controls,
layoutSignals,
textWallSignals,
manualSignals,
fixedTaxonomySignals,
nestedCardSignals,
}
})
.sort((a, b) => b.score - a.score)
console.log('AWOOOI_UI_DENSITY_AUDIT')
console.table(
pages.slice(0, 20).map(page => ({
route: page.route,
score: page.score,
lines: page.lines,
text: page.textSignals,
controls: page.controls,
manual: page.manualSignals,
fixedTaxonomy: page.fixedTaxonomySignals,
})),
)
console.log(JSON.stringify({
page_count: pages.length,
top_routes: pages.slice(0, 20),
}, null, 2))