| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 |
- #!/usr/bin/env node
- /* 等价回归验证(UniForge verify 工具)
- * 用法:node scripts/equiv-regression.js <页面路径(相对 cfc-frontend)>
- * 例:node scripts/equiv-regression.js pages/index-home/index.vue
- *
- * 原理:uni-app 将 uni.scss prepend 到每个页面 style。
- * 取 HEAD 版与工作区版分别拼接 uni.scss 编译,hex 大小写/3-6 位归一后逐字节比对。
- * 全部令牌同值映射时,产物应完全相同 → 证明零观感变化。
- */
- const { execSync } = require('child_process')
- const fs = require('fs')
- const path = require('path')
- const PAGE = process.argv[2]
- if (!PAGE) {
- console.error('用法: node scripts/equiv-regression.js <页面路径>')
- process.exit(2)
- }
- const ROOT = path.resolve(__dirname, '..')
- const REPO = path.resolve(ROOT, '..')
- const REL = path.relative(REPO, path.join(ROOT, PAGE)).split(path.sep).join('/')
- const TMP = path.join(ROOT, '.tmp_equiv')
- fs.mkdirSync(TMP, { recursive: true })
- function extractStyle(src) {
- const m = src.match(/<style[^>]*>([\s\S]*?)<\/style>/)
- if (!m) throw new Error('未找到 style 块: ' + PAGE)
- return m[1]
- }
- const uniScss = fs.readFileSync(path.join(ROOT, 'uni.scss'), 'utf8')
- const afterVue = fs.readFileSync(path.join(ROOT, PAGE), 'utf8')
- const beforeVue = execSync(`git -C "${REPO}" show HEAD:${REL}`, { encoding: 'utf8', maxBuffer: 1e8 })
- fs.writeFileSync(path.join(TMP, 'after.scss'), uniScss + '\n' + extractStyle(afterVue))
- fs.writeFileSync(path.join(TMP, 'before.scss'), uniScss + '\n' + extractStyle(beforeVue))
- const sass = path.join(ROOT, 'node_modules/.bin/sass.cmd')
- execSync(`"${sass}" --no-source-map "${TMP}/after.scss" "${TMP}/after.out.css"`, { stdio: 'pipe' })
- execSync(`"${sass}" --no-source-map "${TMP}/before.scss" "${TMP}/before.out.css"`, { stdio: 'pipe' })
- function norm(s) {
- return s
- .replace(/#([0-9a-fA-F]{3,8})\b/g, (m) => {
- let h = m.slice(1).toUpperCase()
- if (h.length === 3) h = h.split('').map((c) => c + c).join('')
- return '#' + h
- })
- .split('\n').map((l) => l.trim()).filter(Boolean).join('\n')
- }
- const before = norm(fs.readFileSync(path.join(TMP, 'before.out.css'), 'utf8'))
- const after = norm(fs.readFileSync(path.join(TMP, 'after.out.css'), 'utf8'))
- if (before === after) {
- console.log(`等价回归 PASS(${PAGE}):编译产物归一后逐字节相同,零观感变化`)
- } else {
- const bl = before.split('\n'), al = after.split('\n')
- console.log(`等价回归 DIFF(${PAGE}):before ${bl.length} 行 / after ${al.length} 行`)
- let shown = 0
- for (let i = 0; i < Math.max(bl.length, al.length) && shown < 40; i++) {
- if (bl[i] !== al[i]) {
- console.log(`L${i + 1}\n before: ${bl[i]}\n after: ${al[i]}`)
- shown++
- }
- }
- if (shown >= 40) console.log('...(仅显示前 40 处差异)')
- process.exit(1)
- }
|