| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 |
- #!/usr/bin/env node
- /**
- * 品牌色/令牌引用完整性审计
- *
- * 检查美化治理过程中容易出现的四类回归(均为真实踩过的坑):
- * 1) 'BrandColors.xxx' 字符串字面量 —— 字符串不是变量,作为颜色值无效
- * 2) BrandColors.xxx 使用但未 import —— 运行时 ReferenceError / undefined
- * 3) import 落在 </script> 之后 —— 模块作用域外,运行时标识符未定义
- * 4) 从 utils/brand-colors.js 具名导入不存在的导出 —— 值为 undefined
- *
- * 用法: node scripts/audit-brandcolor-integrity.js
- * 退出码: 0 通过 / 1 存在问题
- */
- const fs = require('fs')
- const path = require('path')
- const ROOT = path.resolve(__dirname, '..')
- const EXCLUDE_DIRS = new Set(['node_modules', 'dist', 'unpackage', '.git', 'scripts', 'docs', '.tmp_equiv'])
- const SKIP_FILES = new Set(['components/mp-html/node/node.vue']) // mp-html 源码保持原样
- function walk(dir, out = []) {
- for (const name of fs.readdirSync(dir)) {
- if (EXCLUDE_DIRS.has(name)) continue
- const p = path.join(dir, name)
- const st = fs.statSync(p)
- if (st.isDirectory()) walk(p, out)
- else if (name.endsWith('.vue')) out.push(p)
- }
- return out
- }
- const bcSrc = fs.readFileSync(path.join(ROOT, 'utils/brand-colors.js'), 'utf8')
- const exportsSet = new Set()
- for (const m of bcSrc.matchAll(/export\s+const\s+(\w+)/g)) exportsSet.add(m[1])
- for (const m of bcSrc.matchAll(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*'/gm)) exportsSet.add(m[1])
- exportsSet.add('default')
- const issues = { stringLiteral: [], missingImport: [], strayImport: [], badNamedImport: [] }
- for (const abs of walk(ROOT)) {
- const rel = path.relative(ROOT, abs).split(path.sep).join('/')
- if (SKIP_FILES.has(rel)) continue
- const src = fs.readFileSync(abs, 'utf8')
- const scriptIdx = src.search(/<script/)
- const closeIdx = src.indexOf('</script>')
- const styleIdx = src.search(/<style/)
- const head = scriptIdx === -1 ? src : src.slice(0, scriptIdx)
- const scriptBody = scriptIdx === -1 || closeIdx === -1 ? '' : src.slice(scriptIdx, closeIdx)
- if (/['"]BrandColors\.\w+['"]/.test(src)) issues.stringLiteral.push(rel)
- const usesBrandColors = /\bBrandColors\./.test(scriptBody) || /\bBrandColors\./.test(head)
- const importedBrandColors = /import\s*\{[^}]*\bBrandColors\b[^}]*\}\s*from\s*['"][^'"]*brand-colors/.test(src)
- if (usesBrandColors && !importedBrandColors) issues.missingImport.push(rel)
- if (closeIdx !== -1 && styleIdx > closeIdx) {
- const between = src.slice(closeIdx + 9, styleIdx)
- if (/^\s*import\s/m.test(between)) issues.strayImport.push(rel)
- }
- for (const m of src.matchAll(/import\s*\{([^}]*)\}\s*from\s*['"][^'"]*brand-colors(?:\.js)?['"]/g)) {
- for (const raw of m[1].split(',')) {
- const name = raw.trim()
- if (!name) continue
- if (!exportsSet.has(name)) issues.badNamedImport.push(rel + ' -> ' + name)
- }
- }
- }
- const labels = {
- stringLiteral: "'BrandColors.xxx' 字符串字面量(颜色值无效)",
- missingImport: 'BrandColors 已使用但未 import',
- strayImport: 'import 落在 </script> 之后',
- badNamedImport: 'brand-colors 具名导入不存在',
- }
- let failed = 0
- for (const [key, list] of Object.entries(issues)) {
- if (list.length) failed += list.length
- console.log(labels[key] + ': ' + list.length)
- list.slice(0, 20).forEach(x => console.log(' - ' + x))
- }
- console.log(failed === 0 ? 'PASS' : 'FAIL(' + failed + ')')
- process.exit(failed === 0 ? 0 : 1)
|