import { readFileSync, readdirSync, statSync } from 'node:fs'; import { join, relative } from 'node:path'; const roots = ['src', 'scripts', 'drizzle']; const explicitFiles = ['drizzle.config.ts']; const extensions = new Set([ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json', '.sql' ]); const suspiciousChars = [ '\u0081', '\u0087', '\u008d', '\u0099', '\u009e', '\u00a0', '\u201d', '\u20ac' ]; function walk(dir) { const entries = readdirSync(dir, { withFileTypes: true }); const files = []; for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { files.push(...walk(fullPath)); continue; } if ([...extensions].some((extension) => entry.name.endsWith(extension))) { files.push(fullPath); } } return files; } function hasUtf8Bom(buffer) { return buffer.length >= 3 && buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf; } function findSuspiciousChars(text) { return suspiciousChars.filter((char) => text.includes(char)); } const filesToCheck = [ ...roots.flatMap((root) => (statSync(root, { throwIfNoEntry: false })?.isDirectory() ? walk(root) : [])), ...explicitFiles.filter((file) => statSync(file, { throwIfNoEntry: false })?.isFile()) ]; const issues = []; for (const file of filesToCheck) { const buffer = readFileSync(file); if (hasUtf8Bom(buffer)) { issues.push(`${relative(process.cwd(), file)}: UTF-8 BOM detected`); } let text; try { text = buffer.toString('utf8'); } catch (error) { issues.push(`${relative(process.cwd(), file)}: unable to decode as UTF-8 (${error.message})`); continue; } const suspicious = findSuspiciousChars(text); if (suspicious.length > 0) { const labels = suspicious.map((char) => `U+${char.codePointAt(0).toString(16).toUpperCase().padStart(4, '0')}`); issues.push(`${relative(process.cwd(), file)}: suspicious mojibake markers found (${labels.join(', ')})`); } } if (issues.length > 0) { console.error('Encoding verification failed:'); for (const issue of issues) { console.error(`- ${issue}`); } process.exit(1); } console.log(`Encoding verification passed for ${filesToCheck.length} files.`);