changelog.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. /* eslint-disable max-depth */
  2. const fs = require('fs-extra');
  3. const path = require('path');
  4. const marked = require('marked');
  5. const get = require('lodash/get');
  6. const isString = require('lodash/isString');
  7. const capitalize = require('lodash/capitalize');
  8. const { COMPONENT_LIST } = require('./componentList');
  9. const TYPE_MAP = {
  10. feat: 'feature',
  11. perf: 'performance',
  12. doc: 'docs',
  13. styles: 'style',
  14. };
  15. const SUPPORT_TYPES = new Set(['feat', 'feature', 'performance', 'style', 'new component', 'chore', 'docs', 'fix', 'refactor', 'breaking change', 'design token']);
  16. const UNKNOWN_COMPONENT_NAME = 'Other';
  17. const UNKNOWN_TYPE_NAME = 'chore';
  18. function resolve(dir) {
  19. return path.join(__dirname, dir);
  20. }
  21. function readFile(filename) {
  22. return fs.readFileSync(resolve(filename), { encoding: 'utf-8' });
  23. }
  24. function getVersion(str) {
  25. const versionReg = /.*((\d{1,2}\.){2}\d{1,2}(-\w+\.\d+)?)/;
  26. let result = versionReg.exec(str);
  27. return get(result, '1', '').trim().toLowerCase();
  28. }
  29. function getType(str) {
  30. const typeReg = /【([a-z\s]*)】|\[([a-z\s]*)\]/i;
  31. let result = typeReg.exec(str);
  32. let type;
  33. if (result) {
  34. type = result[1] || result[2];
  35. }
  36. type = isString(type) && type.trim().toLowerCase();
  37. if (!SUPPORT_TYPES.has(type)) {
  38. type = UNKNOWN_TYPE_NAME;
  39. }
  40. return type;
  41. }
  42. function getIndent(str) {
  43. return /(\s*).*/.exec(str)[1].length;
  44. }
  45. function getChangeLog(str) {
  46. const changeLogReg = /\s*-\s*(.*)/;
  47. const changeLog = changeLogReg.exec(str)[1];
  48. const indent = getIndent(str);
  49. return [changeLog.trim(), indent];
  50. }
  51. function getComponent(str) {
  52. const componentReg = new RegExp(`\\b(${COMPONENT_LIST.join('|')})\\b`, 'i');
  53. let result = componentReg.exec(str);
  54. if (result) {
  55. return capitalize(result[1]);
  56. } else {
  57. return null;
  58. }
  59. }
  60. function formatData(data) {
  61. // feat => feature
  62. // perf => performance
  63. // doc => docs
  64. const newData = data.map(item => {
  65. const { type, component } = item;
  66. if (type in TYPE_MAP) {
  67. // console.log(type);
  68. item.type = TYPE_MAP[type];
  69. }
  70. if (!SUPPORT_TYPES.has(item.type)) {
  71. console.warn('changelog not support type', item.type);
  72. }
  73. if (!component) {
  74. item.component = UNKNOWN_COMPONENT_NAME;
  75. }
  76. return item;
  77. });
  78. return newData;
  79. }
  80. function getChangeLogList(rawMarkdown) {
  81. const lines = rawMarkdown.split('\n');
  82. const output = [];
  83. let version, type, parentComponent, prevLevel, level, prevIndent;
  84. let parentIndent = 0;
  85. let intent2Level = new Map();
  86. for (let line of lines) {
  87. // is version line, eg. #### 🎉 1.24.4 (2021-06-21)
  88. if (line.startsWith('####')) {
  89. version = getVersion(line);
  90. intent2Level = new Map();
  91. continue;
  92. }
  93. // is type line, eg. - 【Fix】
  94. if (/-\s*[【】\[\]]/.test(line)) {
  95. type = getType(line);
  96. prevIndent = 0;
  97. level = 0;
  98. prevLevel = 0;
  99. continue;
  100. }
  101. // is changelog line
  102. if (version && /\s*-\s*.*/.test(line)) {
  103. let [content, indent] = getChangeLog(line);
  104. let component = getComponent(content);
  105. // process level
  106. const _level = intent2Level.get(indent); // align to same value indent
  107. if (_level) {
  108. level = _level;
  109. prevIndent = indent;
  110. prevLevel = level;
  111. } else {
  112. if (indent > prevIndent) {
  113. level = ++prevLevel;
  114. } else if (indent < prevIndent) {
  115. level = --prevLevel;
  116. } else {
  117. level = prevLevel;
  118. }
  119. intent2Level.set(indent, level);
  120. }
  121. // process component is none
  122. if ((prevIndent && (indent > prevIndent)) || ((indent === prevIndent) && level !== 1)) {
  123. if (!component) {
  124. component = indent > parentIndent ? parentComponent : UNKNOWN_COMPONENT_NAME;
  125. }
  126. }
  127. // update parent parameter
  128. if (level === 1 || indent < prevIndent) {
  129. parentComponent = component;
  130. parentIndent = indent;
  131. }
  132. prevIndent = indent; // record prev indent
  133. const contentToHTML = marked(content).replace(/(<p>)|(<\/p>)/g, '').trim();
  134. const currentChangeLog = { version, type, content: contentToHTML, component, level };
  135. output.push(currentChangeLog);
  136. }
  137. }
  138. return formatData(output);
  139. }
  140. function main() {
  141. // eslint-disable-next-line no-unused-vars
  142. const [_, __, savePath] = process.argv;
  143. const output = '../static/changeLog.json';
  144. try {
  145. const changelogZN = readFile('../content/start/changelog/index.md');
  146. const changelogEN = readFile('../content/start/changelog/index-en-US.md');
  147. const changelogZNList = getChangeLogList(changelogZN);
  148. const changelogENList = getChangeLogList(changelogEN);
  149. const changelogJSON = {
  150. 'zh-CN': changelogZNList,
  151. 'en-US': changelogENList,
  152. };
  153. fs.writeFileSync(savePath || resolve(output), JSON.stringify(changelogJSON));
  154. console.info(`🎉 save changeLog.json to ${savePath || resolve(output)}`);
  155. } catch (err) {
  156. console.error('changelog build error: ', err.message, err.stack);
  157. }
  158. }
  159. main();