mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +00:00
Counts come from the linter; a rule at zero loses its key; a rule over its allowance stops the script with the lint's own list. --check fails when the file is higher than the tree owes. Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
69 lines
2.9 KiB
TypeScript
69 lines
2.9 KiB
TypeScript
/**
|
|
* Lowers hanzo-design.allow.json to what the tree now owes, and never raises it.
|
|
*
|
|
* `pnpm lint:design` fails when a rule's count is over its allowance and says
|
|
* "lower it to N" when it is under. This does the lowering, so a fix and its
|
|
* allowance move in one commit and nobody types a number: the counts come from
|
|
* the linter itself, a rule at zero loses its key (a rule with no entry is
|
|
* allowed zero), and a rule over its allowance stops the script with the lint's
|
|
* own list, so a ratchet is never loosened to make a build pass.
|
|
*
|
|
* pnpm ratchet write the lower allowance
|
|
* pnpm ratchet --check exit 1 when the file is higher than the tree owes
|
|
*/
|
|
|
|
import { spawnSync } from 'node:child_process'
|
|
import { readFileSync, writeFileSync } from 'node:fs'
|
|
|
|
const FILE = 'hanzo-design.allow.json'
|
|
const TARGETS = ['components', 'app', 'lib']
|
|
const check = process.argv.includes('--check')
|
|
|
|
const lint = spawnSync('pnpm', ['exec', 'hanzo-design-lint', ...TARGETS], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
|
|
const out = `${lint.stdout ?? ''}${lint.stderr ?? ''}`
|
|
|
|
/** The linter's own count per rule: every rule with a finding prints `rule (N, …)` once. */
|
|
const found: Record<string, number> = {}
|
|
for (const m of out.matchAll(/^([a-z][a-z-]*)\s{2}\((\d+),/gm)) found[m[1]] = Number(m[2])
|
|
|
|
if (!/hanzo-design-lint: \d+ files|violation\(s\) across \d+ files/.test(out)) {
|
|
console.error(out.trim() || 'hanzo-design-lint printed nothing')
|
|
console.error('\nratchet: the linter did not finish, so nothing was written')
|
|
process.exit(1)
|
|
}
|
|
|
|
const allow = JSON.parse(readFileSync(FILE, 'utf8')) as Record<string, string | number>
|
|
const rules = Object.keys(allow).filter((k) => !k.startsWith('_'))
|
|
const next: Record<string, string | number> = {}
|
|
const over: string[] = []
|
|
const lowered: string[] = []
|
|
|
|
for (const [key, value] of Object.entries(allow)) {
|
|
if (key.startsWith('_')) {
|
|
next[key] = value
|
|
continue
|
|
}
|
|
const owes = found[key] ?? 0
|
|
if (owes > Number(value)) over.push(`${key}: ${owes} found, ${value} allowed`)
|
|
if (owes < Number(value)) lowered.push(`${key}: ${value} -> ${owes}`)
|
|
if (owes > 0) next[key] = Math.min(owes, Number(value))
|
|
}
|
|
// A rule the file does not name is allowed zero; one that has findings and no key is over.
|
|
for (const [rule, n] of Object.entries(found)) if (!rules.includes(rule)) over.push(`${rule}: ${n} found, 0 allowed`)
|
|
|
|
if (over.length) {
|
|
console.error(out.trim())
|
|
console.error(`\nratchet: over the allowance, not loosening it:\n ${over.join('\n ')}`)
|
|
process.exit(1)
|
|
}
|
|
if (!lowered.length) {
|
|
console.log('ratchet: the allowance is what the tree owes')
|
|
process.exit(0)
|
|
}
|
|
if (check) {
|
|
console.error(`ratchet: the allowance is higher than the tree owes; run \`pnpm ratchet\`:\n ${lowered.join('\n ')}`)
|
|
process.exit(1)
|
|
}
|
|
writeFileSync(FILE, `${JSON.stringify(next, null, 2)}\n`)
|
|
console.log(`ratchet: lowered ${FILE}:\n ${lowered.join('\n ')}`)
|