| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 |
- #!/usr/bin/env node
- /**
- * D31: script 段维度色批量收编 BrandColors
- * 只替换「整段字符串就是 hex」的写法('#10B981' / "#10B981"),避免破坏模板字符串/渐变拼接。
- * 自动按文件深度计算相对 import 路径;已 import 的文件不重复注入。
- *
- * 用法: node scripts/d31-script-hex-harvest.js [--dry]
- */
- const fs = require('fs');
- const path = require('path');
- const ROOT = path.resolve(__dirname, '..');
- const DRY = process.argv.includes('--dry');
- const EXCLUDE_DIRS = new Set(['node_modules', 'dist', 'unpackage', '.git', 'scripts', '.tmp_equiv']);
- // hex -> BrandColors key(仅登记已存在于 utils/brand-colors.js 的键)
- const MAP = {
- '#10B981': 'success',
- '#F59E0B': 'wealth',
- '#FF6B9D': 'mind',
- '#FF8C42': 'body',
- '#F97316': 'primary',
- '#6366F1': 'wisdom',
- '#FFFFFF': 'white',
- '#3B82F6': 'infoBlue',
- '#EF4444': 'danger',
- '#94A3B8': 'appTextSubtle',
- '#8B5CF6': 'violet',
- '#EA580C': 'orangeDeep',
- '#0EA5E9': 'infoSky',
- '#EC4899': 'pink',
- '#6B7280': 'textSlate',
- '#4CAF50': 'matGreen',
- '#FF6B35': 'mindAccent',
- '#8D6E63': 'brownWarm',
- };
- const bcSrc = fs.readFileSync(path.join(ROOT, 'utils/brand-colors.js'), 'utf8');
- for (const key of Object.values(MAP)) {
- if (!new RegExp('^\\s*' + key + ':', 'm').test(bcSrc)) {
- console.error('缺少 BrandColors 键: ' + key);
- process.exit(1);
- }
- }
- 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 files = walk(ROOT);
- let changedFiles = 0;
- let replacements = 0;
- const log = [];
- for (const abs of files) {
- const src = fs.readFileSync(abs, 'utf8');
- const styleIdx = src.search(/<style/);
- if (styleIdx === -1) continue;
- let head = src.slice(0, styleIdx);
- const tail = src.slice(styleIdx);
- let count = 0;
- for (const [hex, key] of Object.entries(MAP)) {
- const re = new RegExp("(['\"])" + hex + "\\1", 'g');
- head = head.replace(re, () => {
- count++;
- return 'BrandColors.' + key;
- });
- }
- if (count === 0) continue;
- // 追加 import:已有 brand-colors 具名导入则并入 BrandColors,否则新增一行
- if (!/\bBrandColors\b[^}]*\}\s*from\s*['"][^'"]*brand-colors/.test(head)) {
- const existing = head.match(/import\s*\{([^}]*)\}\s*from\s*(['"][^'"]*brand-colors(?:\.js)?['"])/);
- if (existing) {
- head = head.replace(existing[0], 'import {' + existing[1].replace(/\s*$/, '') + (existing[1].trim().endsWith(',') ? ' ' : ', ') + 'BrandColors } from ' + existing[2]);
- } else {
- const rel = path.relative(path.dirname(abs), path.join(ROOT, 'utils/brand-colors.js')).split(path.sep).join('/');
- const importLine = "import { BrandColors } from '" + (rel.startsWith('.') ? rel : './' + rel) + "'";
- const m = head.match(/<script[^>]*>\r?\n/);
- if (!m) {
- console.warn('SKIP(no script tag): ' + path.relative(ROOT, abs));
- continue;
- }
- head = head.replace(m[0], m[0] + importLine + (m[0].includes('\r\n') ? '\r\n' : '\n'));
- }
- }
- if (!DRY) fs.writeFileSync(abs, head + tail, 'utf8');
- changedFiles++;
- replacements += count;
- log.push(String(count).padStart(3) + ' ' + path.relative(ROOT, abs).split(path.sep).join('/'));
- }
- log.sort((a, b) => parseInt(b) - parseInt(a));
- console.log((DRY ? '[DRY] ' : '') + '文件: ' + changedFiles + ' 替换: ' + replacements);
- log.slice(0, 40).forEach(l => console.log(l));
- if (log.length > 40) console.log('... 共 ' + log.length + ' 个文件');
|