d31-script-hex-harvest.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. #!/usr/bin/env node
  2. /**
  3. * D31: script 段维度色批量收编 BrandColors
  4. * 只替换「整段字符串就是 hex」的写法('#10B981' / "#10B981"),避免破坏模板字符串/渐变拼接。
  5. * 自动按文件深度计算相对 import 路径;已 import 的文件不重复注入。
  6. *
  7. * 用法: node scripts/d31-script-hex-harvest.js [--dry]
  8. */
  9. const fs = require('fs');
  10. const path = require('path');
  11. const ROOT = path.resolve(__dirname, '..');
  12. const DRY = process.argv.includes('--dry');
  13. const EXCLUDE_DIRS = new Set(['node_modules', 'dist', 'unpackage', '.git', 'scripts', '.tmp_equiv']);
  14. // hex -> BrandColors key(仅登记已存在于 utils/brand-colors.js 的键)
  15. const MAP = {
  16. '#10B981': 'success',
  17. '#F59E0B': 'wealth',
  18. '#FF6B9D': 'mind',
  19. '#FF8C42': 'body',
  20. '#F97316': 'primary',
  21. '#6366F1': 'wisdom',
  22. '#FFFFFF': 'white',
  23. '#3B82F6': 'infoBlue',
  24. '#EF4444': 'danger',
  25. '#94A3B8': 'appTextSubtle',
  26. '#8B5CF6': 'violet',
  27. '#EA580C': 'orangeDeep',
  28. '#0EA5E9': 'infoSky',
  29. '#EC4899': 'pink',
  30. '#6B7280': 'textSlate',
  31. '#4CAF50': 'matGreen',
  32. '#FF6B35': 'mindAccent',
  33. '#8D6E63': 'brownWarm',
  34. };
  35. const bcSrc = fs.readFileSync(path.join(ROOT, 'utils/brand-colors.js'), 'utf8');
  36. for (const key of Object.values(MAP)) {
  37. if (!new RegExp('^\\s*' + key + ':', 'm').test(bcSrc)) {
  38. console.error('缺少 BrandColors 键: ' + key);
  39. process.exit(1);
  40. }
  41. }
  42. function walk(dir, out = []) {
  43. for (const name of fs.readdirSync(dir)) {
  44. if (EXCLUDE_DIRS.has(name)) continue;
  45. const p = path.join(dir, name);
  46. const st = fs.statSync(p);
  47. if (st.isDirectory()) walk(p, out);
  48. else if (name.endsWith('.vue')) out.push(p);
  49. }
  50. return out;
  51. }
  52. const files = walk(ROOT);
  53. let changedFiles = 0;
  54. let replacements = 0;
  55. const log = [];
  56. for (const abs of files) {
  57. const src = fs.readFileSync(abs, 'utf8');
  58. const styleIdx = src.search(/<style/);
  59. if (styleIdx === -1) continue;
  60. let head = src.slice(0, styleIdx);
  61. const tail = src.slice(styleIdx);
  62. let count = 0;
  63. for (const [hex, key] of Object.entries(MAP)) {
  64. const re = new RegExp("(['\"])" + hex + "\\1", 'g');
  65. head = head.replace(re, () => {
  66. count++;
  67. return 'BrandColors.' + key;
  68. });
  69. }
  70. if (count === 0) continue;
  71. // 追加 import:已有 brand-colors 具名导入则并入 BrandColors,否则新增一行
  72. if (!/\bBrandColors\b[^}]*\}\s*from\s*['"][^'"]*brand-colors/.test(head)) {
  73. const existing = head.match(/import\s*\{([^}]*)\}\s*from\s*(['"][^'"]*brand-colors(?:\.js)?['"])/);
  74. if (existing) {
  75. head = head.replace(existing[0], 'import {' + existing[1].replace(/\s*$/, '') + (existing[1].trim().endsWith(',') ? ' ' : ', ') + 'BrandColors } from ' + existing[2]);
  76. } else {
  77. const rel = path.relative(path.dirname(abs), path.join(ROOT, 'utils/brand-colors.js')).split(path.sep).join('/');
  78. const importLine = "import { BrandColors } from '" + (rel.startsWith('.') ? rel : './' + rel) + "'";
  79. const m = head.match(/<script[^>]*>\r?\n/);
  80. if (!m) {
  81. console.warn('SKIP(no script tag): ' + path.relative(ROOT, abs));
  82. continue;
  83. }
  84. head = head.replace(m[0], m[0] + importLine + (m[0].includes('\r\n') ? '\r\n' : '\n'));
  85. }
  86. }
  87. if (!DRY) fs.writeFileSync(abs, head + tail, 'utf8');
  88. changedFiles++;
  89. replacements += count;
  90. log.push(String(count).padStart(3) + ' ' + path.relative(ROOT, abs).split(path.sep).join('/'));
  91. }
  92. log.sort((a, b) => parseInt(b) - parseInt(a));
  93. console.log((DRY ? '[DRY] ' : '') + '文件: ' + changedFiles + ' 替换: ' + replacements);
  94. log.slice(0, 40).forEach(l => console.log(l));
  95. if (log.length > 40) console.log('... 共 ' + log.length + ' 个文件');