1
0

changelog.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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', 'breaking change']);
  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. let intent2Level = new Map();
  147. for (let line of lines) {
  148. // is version line, eg. #### 🎉 1.24.4 (2021-06-21)
  149. if (line.startsWith('####')) {
  150. version = getVersion(line);
  151. intent2Level = new Map();
  152. continue;
  153. }
  154. // is type line, eg. - 【Fix】
  155. if (/-\s*[【】\[\]]/.test(line)) {
  156. type = getType(line);
  157. prevIndent = 0;
  158. level = 0;
  159. prevLevel = 0;
  160. continue;
  161. }
  162. // is changelog line
  163. if (version && /\s*-\s*.*/.test(line)) {
  164. let [content, indent] = getChangeLog(line);
  165. let component = getComponent(content);
  166. // process level
  167. const _level = intent2Level.get(indent); // align to same value indent
  168. if (_level) {
  169. level = _level;
  170. prevIndent = indent;
  171. prevLevel = level;
  172. } else {
  173. if (indent > prevIndent) {
  174. level = ++prevLevel;
  175. } else if (indent < prevIndent) {
  176. level = --prevLevel;
  177. } else {
  178. level = prevLevel;
  179. }
  180. intent2Level.set(indent, level);
  181. }
  182. // process component is none
  183. if ((prevIndent && (indent > prevIndent)) || ((indent === prevIndent) && level !== 1)) {
  184. if (!component) {
  185. component = indent > parentIndent ? parentComponent : UNKNOWN_COMPONENT_NAME;
  186. }
  187. }
  188. // update parent parameter
  189. if (level === 1 || indent < prevIndent) {
  190. parentComponent = component;
  191. parentIndent = indent;
  192. }
  193. prevIndent = indent; // record prev indent
  194. const contentToHTML = marked(content).replace(/(<p>)|(<\/p>)/g, '').trim();
  195. const currentChangeLog = { version, type, content: contentToHTML, component, level };
  196. output.push(currentChangeLog);
  197. }
  198. }
  199. return formatData(output);
  200. }
  201. function main() {
  202. // eslint-disable-next-line no-unused-vars
  203. const [_, __, savePath] = process.argv;
  204. const output = '../static/changeLog.json';
  205. try {
  206. const changelogZN = readFile('../content/start/changelog/index.md');
  207. const changelogEN = readFile('../content/start/changelog/index-en-US.md');
  208. const changelogZNList = getChangeLogList(changelogZN);
  209. const changelogENList = getChangeLogList(changelogEN);
  210. const changelogJSON = {
  211. 'zh-CN': changelogZNList,
  212. 'en-US': changelogENList,
  213. };
  214. fs.writeFileSync(savePath || resolve(output), JSON.stringify(changelogJSON));
  215. console.info(`🎉 save changeLog.json to ${savePath || resolve(output)}`);
  216. } catch (err) {
  217. console.error('changelog build error: ', err.message, err.stack);
  218. }
  219. }
  220. main();