import fs from 'node:fs' import path from 'node:path' import process from 'node:process' import { fileURLToPath } from 'node:url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const repoRoot = path.resolve(__dirname, '..') const decoder = new TextDecoder('utf-8', { fatal: true }) const textExtensions = new Set([ '.js', '.cjs', '.mjs', '.ts', '.tsx', '.json', '.jsonc', '.css', '.less', '.scss', '.vue', '.html', '.htm', '.md', '.mdx', '.yml', '.yaml', '.svg' ]) const textFileNames = new Set([ 'Dockerfile', 'AGENTS.md', 'CLAUDE.md', 'LICENSE', '.env', '.env.development', '.env.production', '.gitignore', '.gitattributes', '.prettierignore', '.prettierrc', '.stylelintignore', '.stylelintrc.cjs' ]) const ignoredDirs = new Set(['.git', 'node_modules', 'dist']) function isTextFile(filePath) { const baseName = path.basename(filePath) if (textFileNames.has(baseName)) { return true } return textExtensions.has(path.extname(baseName).toLowerCase()) } function collectFiles(dirPath) { const files = [] for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) { if (entry.isDirectory()) { if (ignoredDirs.has(entry.name)) { continue } files.push(...collectFiles(path.join(dirPath, entry.name))) continue } const fullPath = path.join(dirPath, entry.name) if (isTextFile(fullPath)) { files.push(fullPath) } } return files } function toAbsolutePath(targetPath) { if (path.isAbsolute(targetPath)) { return targetPath } return path.resolve(process.cwd(), targetPath) } function describeIssue(relativePath, message) { return `${relativePath}: ${message}` } function checkFile(filePath) { const relativePath = path.relative(repoRoot, filePath) const buffer = fs.readFileSync(filePath) try { const text = decoder.decode(buffer) if (text.includes('\uFFFD')) { return describeIssue(relativePath, '包含 Unicode 替换字符,疑似已出现乱码') } return null } catch { return describeIssue(relativePath, '不是有效的 UTF-8 编码') } } const inputFiles = process.argv.slice(2) const filesToCheck = inputFiles.length > 0 ? inputFiles .map(toAbsolutePath) .filter((filePath) => fs.existsSync(filePath) && fs.statSync(filePath).isFile()) : collectFiles(repoRoot) const issues = filesToCheck .filter(isTextFile) .map(checkFile) .filter(Boolean) if (issues.length > 0) { console.error('检测到编码问题:') for (const issue of issues) { console.error(`- ${issue}`) } process.exit(1) } console.log(`编码检查通过,共检查 ${filesToCheck.length} 个文件。`)