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 ready: Promise } 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 | undefined let closed = false let fingerprint = '' let queued = false let timer: ReturnType | 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) }, } }