changelog.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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 = [
  9. 'anchor',
  10. 'autoComplete',
  11. 'avatar',
  12. 'backtop',
  13. 'badge',
  14. 'banner',
  15. 'breadcrumb',
  16. 'button',
  17. 'calendar',
  18. 'card',
  19. 'cascader',
  20. 'checkbox',
  21. 'collapse',
  22. 'collapsible',
  23. 'configProvider',
  24. 'datePicker',
  25. 'descriptions',
  26. 'dropdown',
  27. 'empty',
  28. 'form',
  29. 'grid',
  30. 'iconButton',
  31. 'icon',
  32. 'input',
  33. 'inputNumber',
  34. 'layout',
  35. 'list',
  36. 'localeProvider',
  37. 'modal',
  38. 'navigation',
  39. 'notification',
  40. 'overflowList',
  41. 'pagination',
  42. 'popconfirm',
  43. 'popover',
  44. 'progress',
  45. 'radio',
  46. 'rating',
  47. 'scrollList',
  48. 'select',
  49. 'sideSheet',
  50. 'skeleton',
  51. 'slider',
  52. 'space',
  53. 'spin',
  54. 'steps',
  55. 'switch',
  56. 'table',
  57. 'tabs',
  58. 'tag',
  59. 'tagInput',
  60. 'timePicker',
  61. 'timeline',
  62. 'toast',
  63. 'tooltip',
  64. 'transfer',
  65. 'tree',
  66. 'treeSelect',
  67. 'typography',
  68. 'upload'
  69. ];
  70. const TYPE_MAP = {
  71. feat: 'feature',
  72. perf: 'performance',
  73. doc: 'docs',
  74. styles: 'style',
  75. };
  76. const SUPPORT_TYPES = new Set(['feature', 'performance', 'style', 'new component', 'chore', 'docs', 'fix', 'refactor']);
  77. const UNKNOWN_COMPONENT_NAME = 'Other';
  78. const UNKNOWN_TYPE_NAME = 'chore';
  79. function resolve(dir) {
  80. return path.join(__dirname, dir);
  81. }
  82. function readFile(filename) {
  83. return fs.readFileSync(resolve(filename), { encoding: 'utf-8' });
  84. }
  85. function getVersion(str) {
  86. const versionReg = /.*((\d{1,2}\.){2}\d{1,2}(-\w+\.\d+)?)/;
  87. let result = versionReg.exec(str);
  88. return get(result, '1', '').trim().toLowerCase();
  89. }
  90. function getType(str) {
  91. const typeReg = /【([a-z\s]*)】|\[([a-z\s]*)\]/i;
  92. let result = typeReg.exec(str);
  93. let type;
  94. if (result) {
  95. type = result[1] || result[2];
  96. }
  97. type = isString(type) && type.trim().toLowerCase();
  98. if (!SUPPORT_TYPES.has(type)) {
  99. type = UNKNOWN_TYPE_NAME;
  100. }
  101. return type;
  102. }
  103. function getIndent(str) {
  104. return /(\s*).*/.exec(str)[1].length;
  105. }
  106. function getChangeLog(str) {
  107. const changeLogReg = /\s*-\s*(.*)/;
  108. const changeLog = changeLogReg.exec(str)[1];
  109. const indent = getIndent(str);
  110. return [changeLog.trim(), indent];
  111. }
  112. function getComponent(str) {
  113. const componentReg = new RegExp(`\\b(${COMPONENT_LIST.join('|')})\\b`, 'i');
  114. let result = componentReg.exec(str);
  115. if (result) {
  116. return capitalize(result[1]);
  117. } else {
  118. return null;
  119. }
  120. }
  121. function formatData(data) {
  122. // feat => feature
  123. // perf => performance
  124. // doc => docs
  125. const newData = data.map(item => {
  126. const { type, component } = item;
  127. if (type in TYPE_MAP) {
  128. // console.log(type);
  129. item.type = TYPE_MAP[type];
  130. }
  131. if (!SUPPORT_TYPES.has(item.type)) {
  132. console.warn('changelog not support type', item.type);
  133. }
  134. if (!component) {
  135. item.component = UNKNOWN_COMPONENT_NAME;
  136. }
  137. return item;
  138. });
  139. return newData;
  140. }
  141. function getChangeLogList(rawMarkdown) {
  142. const lines = rawMarkdown.split('\n');
  143. const output = [];
  144. let version, type, parentComponent, prevLevel, level, prevIndent;
  145. let parentIndent = 0;
  146. for (let line of lines) {
  147. // is version line, eg. #### 🎉 1.24.4 (2021-06-21)
  148. if (line.startsWith('####')) {
  149. version = getVersion(line);
  150. continue;
  151. }
  152. // is type line, eg. - 【Fix】
  153. if (/-\s*[【】\[\]]/.test(line)) {
  154. type = getType(line);
  155. prevIndent = 0;
  156. level = 0;
  157. prevLevel = 0;
  158. continue;
  159. }
  160. // is changelog line
  161. if (version && /\s*-\s*.*/.test(line)) {
  162. let [content, indent] = getChangeLog(line);
  163. let component = getComponent(content);
  164. // process level
  165. if (indent > prevIndent) {
  166. level = ++prevLevel;
  167. } else if (indent < prevIndent) {
  168. level = --prevLevel;
  169. } else {
  170. level = prevLevel;
  171. }
  172. // process component is none
  173. if ((prevIndent && (indent > prevIndent)) || ((indent === prevIndent) && level !== 1)) {
  174. if (!component) {
  175. component = indent > parentIndent ? parentComponent : UNKNOWN_COMPONENT_NAME;
  176. }
  177. }
  178. // update parent parameter
  179. if (level === 1 || indent < prevIndent) {
  180. parentComponent = component;
  181. parentIndent = indent;
  182. }
  183. prevIndent = indent; // record prev indent
  184. const contentToHTML = marked(content).replace(/(<p>)|(<\/p>)/g, '').trim();
  185. const currentChangeLog = { version, type, content: contentToHTML, component, level };
  186. output.push(currentChangeLog);
  187. }
  188. }
  189. return formatData(output);
  190. }
  191. function main() {
  192. // eslint-disable-next-line no-unused-vars
  193. const [_, __, savePath] = process.argv;
  194. const output = '../static/changeLog.json';
  195. try {
  196. const changelogZN = readFile('../content/start/changelog/index.md');
  197. const changelogEN = readFile('../content/start/changelog/index-en-US.md');
  198. const changelogZNList = getChangeLogList(changelogZN);
  199. const changelogENList = getChangeLogList(changelogEN);
  200. const changelogJSON = {
  201. 'zh-CN': changelogZNList,
  202. 'en-US': changelogENList,
  203. };
  204. fs.writeFileSync(savePath || resolve(output), JSON.stringify(changelogJSON));
  205. console.info(`🎉 save changeLog.json to ${savePath || resolve(output)}`);
  206. } catch (err) {
  207. console.error('changelog build error: ', err.message, err.stack);
  208. }
  209. }
  210. main();