/* eslint-disable no-await-in-loop, no-console */ const chalk = require('chalk'); const { spawn } = require('child_process'); const jsdom = require('jsdom'); const jQuery = require('jquery'); const fetch = require('node-fetch'); const open = require('open'); const fs = require('fs-extra'); const path = require('path'); const simpleGit = require('simple-git/promise'); const inquirer = require('inquirer'); const { JSDOM } = jsdom; const { window } = new JSDOM(); const { document } = new JSDOM('').window; global.document = document; const $ = jQuery(window); const QUERY_TITLE = '.gh-header-title .js-issue-title'; const QUERY_DESCRIPTION_LINES = '.comment-body table tbody tr'; const QUERY_AUTHOR = '.timeline-comment-header-text .author:first'; const MAINTAINERS = [ 'zombiej', 'afc163', 'chenshuai2144', 'shaodahong', 'xrkffgg', '07akioni', ].map(author => author.toLowerCase()); const cwd = process.cwd(); const git = simpleGit(cwd); function getDescription(entity) { if (!entity) { return ''; } const descEle = entity.element.find('td:last'); let htmlContent = descEle.html(); htmlContent = htmlContent.replace(/([^<]*)<\/code>/g, '`$1`'); return htmlContent.trim(); } async function printLog() { const tags = await git.tags(); const { fromVersion } = await inquirer.prompt([ { type: 'list', name: 'fromVersion', message: '🏷 Please choose tag to compare with current branch:', choices: tags.all.reverse().slice(0, 10), }, ]); let { toVersion } = await inquirer.prompt([ { type: 'list', name: 'toVersion', message: `🔀 Please choose branch to compare with ${chalk.magenta(fromVersion)}:`, choices: ['master', '3.x-stable', 'feature', 'custom input âŒĻïļ'], }, ]); if (toVersion.startsWith('custom input')) { const result = await inquirer.prompt([ { type: 'input', name: 'toVersion', message: `🔀 Please input custom git hash id or branch name to compare with ${chalk.magenta( fromVersion, )}:`, default: 'master', }, ]); toVersion = result.toVersion; } if (!/\d+\.\d+\.\d+/.test(fromVersion)) { console.log(chalk.red(`ðŸĪŠ tag (${chalk.magenta(fromVersion)}) is not valid.`)); } const logs = await git.log({ from: fromVersion, to: toVersion }); let prList = []; for (let i = 0; i < logs.all.length; i += 1) { const { message, body, hash, author_name: author } = logs.all[i]; const text = `${message} ${body}`; const match = text.match(/#\d+/g); const prs = (match || []).map(pr => pr.slice(1)); const validatePRs = []; console.log( `[${i + 1}/${logs.all.length}]`, hash.slice(0, 6), '-', prs.length ? prs.map(pr => `#${pr}`).join(',') : '?', ); for (let j = 0; j < prs.length; j += 1) { const pr = prs[j]; // Use jquery to get full html page since it don't need auth token const res = await fetch(`https://github.com/ant-design/ant-design/pull/${pr}`); if (res.url.includes('/issues/')) { continue; } const html = await res.text(); const $html = $(html); const prTitle = $html.find(QUERY_TITLE).text().trim(); const prAuthor = $html.find(QUERY_AUTHOR).text().trim(); const prLines = $html.find(QUERY_DESCRIPTION_LINES); const lines = []; prLines.each(function getDesc() { lines.push({ text: $(this).text().trim(), element: $(this), }); }); const english = getDescription(lines.find(line => line.text.includes('🇚ðŸ‡ļ English'))); const chinese = getDescription(lines.find(line => line.text.includes('ðŸ‡ĻðŸ‡ģ Chinese'))); if (english) { console.log(` ðŸ‡ĻðŸ‡ģ ${english}`); } if (chinese) { console.log(` 🇚ðŸ‡ļ ${chinese}`); } validatePRs.push({ pr, hash, title: prTitle, author: prAuthor, english: english || chinese || prTitle, chinese: chinese || english || prTitle, }); } if (validatePRs.length === 1) { console.log(chalk.cyan(' - Match PR:', `#${validatePRs[0].pr}`)); prList = prList.concat(validatePRs); } else if (message.includes('docs:')) { console.log(chalk.cyan(' - Skip document!')); } else { console.log(chalk.yellow(' - Miss match!')); prList.push({ hash, title: message, author, english: message, chinese: message, }); } } console.log('\n', chalk.green('Done. Here is the log:')); function printPR(lang, postLang) { prList.forEach(entity => { const { pr, author, hash, title } = entity; if (pr) { const str = postLang(entity[lang]); let icon = ''; if (str.toLowerCase().includes('fix') || str.includes('äŋŪåĪ')) { icon = '🐞'; } let authorText = ''; if (!MAINTAINERS.includes(author.toLowerCase())) { authorText = ` [@${author}](https://github.com/${author})`; } console.log( `- ${icon} ${str}[#${pr}](https://github.com/ant-design/ant-design/pull/${pr})${authorText}`, ); } else { console.log( `🆘 Miss Match: ${title} -> https://github.com/ant-design/ant-design/commit/${hash}`, ); } }); } // Chinese console.log('\n'); console.log(chalk.yellow('ðŸ‡ĻðŸ‡ģ Chinese changelog:')); console.log('\n'); printPR('chinese', chinese => { return chinese[chinese.length - 1] === '。' || !chinese ? chinese : `${chinese}。`; }); console.log('\n-----\n'); // English console.log(chalk.yellow('🇚ðŸ‡ļ English changelog:')); console.log('\n'); printPR('english', english => { english = english.trim(); if (english[english.length - 1] !== '.' || !english) { english = `${english}.`; } if (english) { return `${english} `; } return ''; }); // Preview editor generate // Web source: https://github.com/ant-design/antd-changelog-editor let html = fs.readFileSync(path.join(__dirname, 'previewEditor', 'template.html'), 'utf8'); html = html.replace('// [Replacement]', `window.changelog = ${JSON.stringify(prList)};`); fs.writeFileSync(path.join(__dirname, 'previewEditor', 'index.html'), html, 'utf8'); // Start preview const ls = spawn('npx', [ 'http-server', path.join(__dirname, 'previewEditor'), '-c-1', '-p', '2893', ]); ls.stdout.on('data', data => { console.log(data.toString()); }); console.log(chalk.green('Start changelog preview editor...')); setTimeout(() => { open('http://localhost:2893/'); }, 1000); } printLog();