feat(km): surface dynamic knowledge categories
This commit is contained in:
130
apps/web/scripts/audit-ui-density.mjs
Normal file
130
apps/web/scripts/audit-ui-density.mjs
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/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))
|
||||
Reference in New Issue
Block a user