1
0

changelog.js 6.4 KB

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