This commit is contained in:
133
scripts/check-encoding.mjs
Normal file
133
scripts/check-encoding.mjs
Normal file
@@ -0,0 +1,133 @@
|
||||
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} 个文件。`)
|
||||
Reference in New Issue
Block a user