110 lines
2.9 KiB
TypeScript
110 lines
2.9 KiB
TypeScript
import { watch, type FSWatcher } from 'node:fs'
|
|
import path from 'node:path'
|
|
import type { DeckMetadata } from './content.ts'
|
|
import { formatStaticOverflowResult, staticOverflowFingerprint } from './overflow-report.ts'
|
|
import { StaticOverflowAuditor } from './static-overflow.ts'
|
|
|
|
interface DevOverflowAuditOptions {
|
|
cwd?: string
|
|
debounceMs?: number
|
|
deck: DeckMetadata
|
|
output?: (line: string) => void
|
|
}
|
|
|
|
export interface DevOverflowAuditController {
|
|
close: () => Promise<void>
|
|
ready: Promise<void>
|
|
}
|
|
|
|
export function startDevOverflowAudit(options: DevOverflowAuditOptions): DevOverflowAuditController {
|
|
const auditor = new StaticOverflowAuditor()
|
|
const cwd = options.cwd ?? process.cwd()
|
|
const debounceMs = options.debounceMs ?? 250
|
|
const output = options.output ?? console.log
|
|
let activeAudit: Promise<void> | undefined
|
|
let closed = false
|
|
let fingerprint = ''
|
|
let queued = false
|
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
let watcher: FSWatcher | undefined
|
|
|
|
function scheduleAudit(delay = debounceMs) {
|
|
if (closed)
|
|
return
|
|
if (timer)
|
|
clearTimeout(timer)
|
|
timer = setTimeout(() => {
|
|
timer = undefined
|
|
void runAudit()
|
|
}, delay)
|
|
}
|
|
|
|
async function runAudit() {
|
|
if (closed)
|
|
return
|
|
if (activeAudit) {
|
|
queued = true
|
|
return activeAudit
|
|
}
|
|
|
|
activeAudit = auditor.audit([options.deck])
|
|
.then((result) => {
|
|
if (closed)
|
|
return
|
|
const nextFingerprint = staticOverflowFingerprint(result)
|
|
if (nextFingerprint !== fingerprint) {
|
|
fingerprint = nextFingerprint
|
|
formatStaticOverflowResult(result, cwd).forEach(line => output(line))
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
if (closed)
|
|
return
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
const nextFingerprint = `error:${message}`
|
|
if (nextFingerprint !== fingerprint) {
|
|
fingerprint = nextFingerprint
|
|
output(`WARN ${path.relative(cwd, options.deck.entry)}: 无法完成静态内容检查:${message}`)
|
|
}
|
|
})
|
|
.finally(() => {
|
|
activeAudit = undefined
|
|
if (queued) {
|
|
queued = false
|
|
scheduleAudit(0)
|
|
}
|
|
})
|
|
return activeAudit
|
|
}
|
|
|
|
function startWatcher() {
|
|
const handleChange = () => scheduleAudit()
|
|
try {
|
|
watcher = watch(options.deck.directory, { recursive: true }, handleChange)
|
|
}
|
|
catch {
|
|
watcher = watch(options.deck.entry, handleChange)
|
|
}
|
|
}
|
|
|
|
const ready = (async () => {
|
|
output('\n检查开发内容中的静态溢出风险...')
|
|
startWatcher()
|
|
await runAudit()
|
|
})()
|
|
|
|
return {
|
|
ready,
|
|
async close() {
|
|
if (closed)
|
|
return
|
|
closed = true
|
|
watcher?.close()
|
|
if (timer)
|
|
clearTimeout(timer)
|
|
await ready.catch(() => undefined)
|
|
await activeAudit?.catch(() => undefined)
|
|
},
|
|
}
|
|
}
|