const path = require('path'); const fs = require('fs'); const { execSync } = require('child_process'); const semver = require('semver'); const _ = require('underscore'); const historyDataFile = path.join(__dirname, '../.github/history.json'); const historyManualDataFile = path.join(__dirname, '../.github/history-manual.json'); const historyFile = path.join(__dirname, '../HISTORY.md'); // TODO: Get from github team members const nonContributors = [ 'web-flow', 'Hudell', 'MarcosSpessatto', 'bernardoetrevisan', 'ggazzo', 'graywolf336', 'sampaiodiego', 'alexbrazier', 'engelgabriel', 'gdelavald', 'geekgonecrazy', 'ggazzo', 'graywolf336', 'karlprieb', 'marceloschmidt', 'MartinSchoeler', 'rafaelks', 'rodrigok', 'renatobecker', 'sampaiodiego', 'SeanPackham', ]; const GroupNames = { FIX: '### ๐Ÿ› Bug fixes', NEW: '### ๐ŸŽ‰ New features', BREAK: '### โš ๏ธ BREAKING CHANGES', MINOR: '๐Ÿ” Minor changes', }; const SummaryNameEmoticons = { FIX: '๐Ÿ›', NEW: '๐ŸŽ‰', BREAK: '๏ธ๏ธ๏ธโš ๏ธ', NOGROUP: '๐Ÿ”', contributor: '๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป', }; const historyData = (() => { try { return require(historyDataFile); // eslint-disable-line import/no-dynamic-require } catch (error) { return {}; } })(); const historyManualData = (() => { try { return require(historyManualDataFile); // eslint-disable-line import/no-dynamic-require } catch (error) { return {}; } })(); Object.keys(historyManualData).forEach((tag) => { historyData[tag] = historyData[tag] || []; historyData[tag].unshift(...historyManualData[tag]); }); function groupPRs(prs) { const groups = { BREAK: [], NEW: [], FIX: [], NOGROUP: [], }; prs.forEach((pr) => { const match = pr.title.match(/\[(FIX|NEW|BREAK)\]\s*(.+)/); if (match) { pr.title = match[2]; groups[match[1]].push(pr); } else { groups.NOGROUP.push(pr); } }); return groups; } function getTagDate(tag) { return execSync(`git tag -l --format="%(creatordate:short)" ${ tag }`).toString().replace(/\n/, ''); } function getLatestCommitDate() { return execSync('git log --date=short --format=\'%ad\' -1').toString().replace(/\n/, ''); } Object.keys(historyData).forEach((tag) => { historyData[tag] = { prs: historyData[tag], rcs: [], }; }); Object.keys(historyData).forEach((tag) => { if (/-rc/.test(tag)) { const mainTag = tag.replace(/-rc.*/, ''); historyData[mainTag] = historyData[mainTag] || { noMainRelease: true, prs: [], rcs: [], }; if (historyData[mainTag].noMainRelease) { historyData[mainTag].rcs.push({ tag, prs: historyData[tag].prs, }); } else { historyData[mainTag].prs.push(...historyData[tag].prs); } delete historyData[tag]; } }); const file = []; function getSummary(contributors, groupedPRs) { const summary = []; Object.keys(groupedPRs).forEach((group) => { if (groupedPRs[group].length) { summary.push(`${ groupedPRs[group].length } ${ SummaryNameEmoticons[group] }`); } }); if (contributors.length) { summary.push(`${ contributors.length } ${ SummaryNameEmoticons.contributor }`); } if (summary.length) { return ` ยท ${ summary.join(' ยท ') }`; } return ''; } function renderPRs(prs) { const data = []; const groupedPRs = groupPRs(prs); Object.keys(groupedPRs).forEach((group) => { const prs = groupedPRs[group]; if (!prs.length) { return; } const groupName = GroupNames[group]; if (group === 'NOGROUP') { data.push(`\n
\n${ GroupNames.MINOR }\n`); } else { data.push(`\n${ groupName }\n`); } prs.forEach((pr) => { let contributors = _.compact(_.difference(pr.contributors, nonContributors)) .sort() .map((contributor) => `[@${ contributor }](https://github.com/${ contributor })`) .join(' & '); if (contributors) { contributors = ` by ${ contributors }`; } const prInfo = pr.pr ? ` ([#${ pr.pr }](https://github.com/RocketChat/Rocket.Chat/pull/${ pr.pr })${ contributors })` : ''; data.push(`- ${ pr.title }${ prInfo }`); }); if (group === 'NOGROUP') { data.push('\n
'); } }); const contributors = _.compact(_.difference(prs.reduce((value, pr) => _.unique(value.concat(pr.contributors)), []), nonContributors)); if (contributors.length) { // TODO: Improve list like https://gist.github.com/paulmillr/2657075/ data.push('\n### ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป Contributors ๐Ÿ˜\n'); contributors.sort().forEach((contributor) => { data.push(`- [@${ contributor }](https://github.com/${ contributor })`); }); } return { data, summary: getSummary(contributors, groupedPRs), }; } function sort(a, b) { if (a === 'HEAD') { return -1; } if (b === 'HEAD') { return 1; } if (semver.gt(a, b)) { return -1; } if (semver.lt(a, b)) { return 1; } return 0; } Object.keys(historyData).sort(sort).forEach((tag) => { const { prs, rcs } = historyData[tag]; if (!prs.length && !rcs.length) { return; } const tagDate = tag === 'HEAD' ? getLatestCommitDate() : getTagDate(tag); const { data, summary } = renderPRs(prs); const tagText = tag === 'HEAD' ? 'Next' : tag; if (historyData[tag].noMainRelease) { file.push(`\n# ${ tagText } (Under Release Candidate Process)`); } else { file.push(`\n# ${ tagText }`); file.push(`\`${ tagDate }${ summary }\``); } file.push(...data); if (Array.isArray(rcs)) { rcs.reverse().forEach((rc) => { const { data, summary } = renderPRs(rc.prs); if (historyData[tag].noMainRelease) { const tagDate = getTagDate(rc.tag); file.push(`\n## ${ rc.tag }`); file.push(`\`${ tagDate }${ summary }\``); } file.push(...data); }); } }); fs.writeFileSync(historyFile, file.join('\n'));