mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-23 18:50:06 +08:00
863f61d908
Co-authored-by: afc163 <afc163@gmail.com>
314 lines
8.6 KiB
TypeScript
314 lines
8.6 KiB
TypeScript
import { spawn } from 'child_process';
|
|
import path from 'path';
|
|
import { input, select } from '@inquirer/prompts';
|
|
import chalk from 'chalk';
|
|
import fs from 'fs-extra';
|
|
import fetch from 'isomorphic-fetch';
|
|
import jQuery from 'jquery';
|
|
import jsdom from 'jsdom';
|
|
import openWindow from 'open';
|
|
import simpleGit from 'simple-git';
|
|
|
|
const { JSDOM } = jsdom;
|
|
const { window } = new JSDOM();
|
|
const { document } = new JSDOM('').window;
|
|
|
|
global.document = document;
|
|
|
|
const $ = jQuery<jsdom.DOMWindow>(window) as unknown as JQueryStatic;
|
|
|
|
const QUERY_TITLE = '.gh-header-title .js-issue-title';
|
|
const QUERY_DESCRIPTION_LINES = '.comment-body table tbody tr';
|
|
const QUERY_AUTHOR = '.pull-discussion-timeline .TimelineItem:first .author:first';
|
|
// https://github.com/orgs/ant-design/teams/ant-design-collaborators/members
|
|
// no need filter: https://github.com/ant-design/ant-design/pull/49878#issuecomment-2227853899
|
|
const MAINTAINERS = [
|
|
// 'zombiej',
|
|
// 'afc163',
|
|
// 'chenshuai2144',
|
|
// 'shaodahong',
|
|
// 'xrkffgg',
|
|
// 'AshoneA',
|
|
// 'yesmeck',
|
|
// 'bang88',
|
|
// 'yoyo837',
|
|
// 'hengkx',
|
|
// 'Rustin-Liu',
|
|
// 'fireairforce',
|
|
// 'kerm1it',
|
|
// 'madccc',
|
|
// 'MadCcc',
|
|
// 'li-jia-nan',
|
|
// 'kiner-tang',
|
|
// 'Wxh16144',
|
|
].map((author: string) => author.toLowerCase());
|
|
|
|
const cwd = process.cwd();
|
|
const git = simpleGit(cwd);
|
|
|
|
interface Line {
|
|
text: string;
|
|
element: JQuery<HTMLElement>;
|
|
}
|
|
|
|
interface PR {
|
|
pr?: string;
|
|
hash: string;
|
|
title: string;
|
|
author: string;
|
|
english: string;
|
|
chinese: string;
|
|
}
|
|
|
|
const getDescription = (entity?: Line): string => {
|
|
if (!entity) {
|
|
return '';
|
|
}
|
|
const descEle = entity.element.find('td:last');
|
|
let htmlContent = descEle.html();
|
|
htmlContent = htmlContent.replace(/<code class="notranslate">([^<]*)<\/code>/g, '`$1`');
|
|
return htmlContent.trim();
|
|
};
|
|
|
|
async function printLog() {
|
|
const tags = await git.tags();
|
|
const fromVersion = await select({
|
|
message: '🏷 Please choose tag to compare with current branch:',
|
|
choices: tags.all
|
|
.filter((item) => !item.includes('experimental'))
|
|
.filter((item) => !item.includes('alpha'))
|
|
.filter((item) => !item.includes('resource'))
|
|
.reverse()
|
|
.slice(0, 50)
|
|
.map((item) => ({ name: item, value: item })),
|
|
});
|
|
|
|
let toVersion = await select({
|
|
message: `🔀 Please choose branch to compare with ${chalk.magenta(fromVersion)}:`,
|
|
choices: ['master', '4.x-stable', '3.x-stable', 'feature', 'custom input ⌨️'].map((i) => ({
|
|
name: i,
|
|
value: i,
|
|
})),
|
|
});
|
|
|
|
if (toVersion.startsWith('custom input')) {
|
|
toVersion = await input({
|
|
default: 'master',
|
|
message: `🔀 Please input custom git hash id or branch name to compare with ${chalk.magenta(
|
|
fromVersion,
|
|
)}:`,
|
|
});
|
|
}
|
|
|
|
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: PR[] = [];
|
|
|
|
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: PR[] = [];
|
|
|
|
console.log(
|
|
chalk.green(
|
|
`[${i + 1}/${logs.all.length}]`,
|
|
hash.slice(0, 6),
|
|
'-',
|
|
prs.length
|
|
? prs.map((pr) => `https://github.com/ant-design/ant-design/pull/${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
|
|
let res: Response | undefined;
|
|
let html: string | undefined;
|
|
let tryTimes = 0;
|
|
const timeout = 30000;
|
|
const fetchPullRequest = async () => {
|
|
try {
|
|
res = await new Promise<Response>((resolve, reject) => {
|
|
setTimeout(() => {
|
|
reject(new Error(`Fetch timeout of ${timeout}ms exceeded`));
|
|
}, timeout);
|
|
fetch(`https://github.com/ant-design/ant-design/pull/${pr}`)
|
|
.then((response) => {
|
|
response.text().then((htmlRes) => {
|
|
html = htmlRes;
|
|
resolve(response);
|
|
});
|
|
})
|
|
.catch(reject);
|
|
});
|
|
} catch (err) {
|
|
tryTimes++;
|
|
if (tryTimes < 100) {
|
|
console.log(chalk.red(`❌ Fetch error, reason: ${err}`));
|
|
console.log(chalk.red(`⌛️ Retrying...(Retry times: ${tryTimes})`));
|
|
await fetchPullRequest();
|
|
}
|
|
}
|
|
};
|
|
await fetchPullRequest();
|
|
if (res?.url.includes('/issues/')) {
|
|
continue;
|
|
}
|
|
|
|
const $html = $(html!);
|
|
|
|
const prTitle: string = $html.find(QUERY_TITLE).text().trim();
|
|
const prAuthor: string = $html.find(QUERY_AUTHOR).text().trim();
|
|
const prLines: JQuery<HTMLElement> = $html.find(QUERY_DESCRIPTION_LINES);
|
|
|
|
const lines: Line[] = [];
|
|
|
|
prLines.each(function getDesc() {
|
|
lines.push({
|
|
text: $(this).text().trim(),
|
|
element: $(this),
|
|
});
|
|
});
|
|
|
|
let english = getDescription(lines.find((line) => line.text.includes('🇺🇸 English')));
|
|
let chinese = getDescription(lines.find((line) => line.text.includes('🇨🇳 Chinese')));
|
|
|
|
if (/^-*$/.test(english)) {
|
|
english = prTitle;
|
|
} else {
|
|
english = english || chinese || prTitle;
|
|
}
|
|
|
|
if (/^-*$/.test(chinese)) {
|
|
chinese = prTitle;
|
|
} else {
|
|
chinese = chinese || english || prTitle;
|
|
}
|
|
|
|
if (english) {
|
|
console.log(` 🇺🇸 ${english}`);
|
|
}
|
|
if (chinese) {
|
|
console.log(` 🇨🇳 ${chinese}`);
|
|
}
|
|
|
|
validatePRs.push({
|
|
pr,
|
|
hash,
|
|
title: prTitle,
|
|
author: prAuthor,
|
|
english,
|
|
chinese,
|
|
});
|
|
}
|
|
|
|
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: string, postLang: (str: string) => string) {
|
|
prList.forEach((entity) => {
|
|
const { pr, author, hash, title } = entity;
|
|
if (pr) {
|
|
const str = postLang(entity[lang as keyof PR]!);
|
|
let icon = '';
|
|
if (str.toLowerCase().includes('fix') || str.includes('修复')) {
|
|
icon = '🐞';
|
|
}
|
|
if (str.toLowerCase().includes('feat')) {
|
|
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: string) =>
|
|
chinese[chinese.length - 1] === '。' || !chinese ? chinese : `${chinese}。`,
|
|
);
|
|
|
|
console.log('\n-----\n');
|
|
|
|
// English
|
|
console.log(chalk.yellow('🇺🇸 English changelog:'));
|
|
console.log('\n');
|
|
printPR('english', (english: string) => {
|
|
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'],
|
|
{ shell: true },
|
|
);
|
|
|
|
ls.stdout.on('data', (data) => {
|
|
console.log(data.toString());
|
|
});
|
|
|
|
console.log(chalk.green('Start changelog preview editor...'));
|
|
|
|
setTimeout(() => {
|
|
openWindow('http://localhost:2893/');
|
|
}, 1000);
|
|
}
|
|
|
|
printLog();
|