build/generate-contributors.js (115 lines of code) (raw):
const { exec } = require('child_process')
const fs = require('fs')
const fetch = require('node-fetch')
const cheerio = require('cheerio')
const dir = './contents'
const entries = {}
const paths = []
const loopDir = path => {
if (path.match(/\.DS_Store$/)) {
return
}
if (fs.lstatSync(path).isDirectory()) {
const children = fs.readdirSync(path)
children.forEach(child => loopDir([path, child].join('/')))
} else {
paths.push(path)
}
}
loopDir(dir)
function writeToFile() {
const text =
'export default ' + JSON.stringify(entries, null, ' ') + ';'
fs.writeFileSync('components/helper/contributors.ts', text)
}
/**
* Fetch contributors from local git log
*
* Drawback: local git username may be not consistent with GitHub username
*/
function local() {
for (let i = 0; i < paths.length; ++i) {
const cmd = `git log --follow --pretty=format:"%an%x09" ${paths[i]} | sort | uniq`
;(i => {
exec(cmd, (err, stdout) => {
if (err) {
console.error(err)
} else {
const key = paths[i].slice(2)
entries[key] = stdout
.trim()
.split('\n')
.map(name => name.trim())
}
if (i === paths.length - 1) {
writeToFile()
}
})
})(i)
}
}
/**
* Fetch contributors by GitHub rest API
*
* Drawback: rate limit
*/
async function ghAPI() {
const GH_COMMITS_URL = 'https://api.github.com/repos/apache/echarts-handbook/commits?path='
const total = paths.length
const tasks = new Array(total)
for (let i = 0; i < total; ++i) {
((i) => {
const path = paths[i].slice(2)
tasks[i] = fetch(GH_COMMITS_URL + path)
.then(response => response.json())
.then(commits => {
const contributors = {}
commits.forEach(({ author: { login } }) => {
contributors[login] = (contributors[login] || 0) + 1
})
entries[path] = Object.keys(contributors)
.sort((a, b) => contributors[b] - contributors[a])
})
.catch(e => {
console.error('failed to fetch contributors of path', path, e)
})
})(i)
}
await Promise.allSettled(tasks)
Object.keys(entries).length && writeToFile()
}
/**
* Fetch contributors from GitHub website
*
* Drawback: potential network/request error
*/
async function ghWeb() {
const GH_CONTRIBUTOR_URL = 'https://github.com/apache/echarts-handbook/contributors/master/'
// All contributors
const GH_CONTRIBUTOR_LIST_URL = 'https://github.com/apache/echarts-handbook/contributors-list/master/'
const total = paths.length
const tasks = new Array(total)
for (let i = 0; i < total; ++i) {
((i) => {
const path = paths[i].slice(2)
console.log('fetching contributors of path', path)
tasks[i] = fetch(GH_CONTRIBUTOR_LIST_URL + path)
.then(response => response.text())
.then(async html => {
let $ = cheerio.load(html)
let contributors
const links = $('li > a')
if (links.length) {
contributors = links.map((i, link) => $(link).attr('href').slice(1)).toArray()
} else {
// for special case, for example, ghost account
html = await (await fetch(GH_CONTRIBUTOR_URL + path)).text()
$ = cheerio.load(html)
let creator = $('span.Link--primary').text()
if (creator && (creator = creator.trim())) {
contributors = [creator]
}
}
entries[path] = contributors = contributors || []
console.log('fetched contributors of path', path, contributors)
})
.catch(e => {
console.error('failed to fetch contributors of path', path, e)
})
})(i)
}
const results = await Promise.allSettled(tasks)
const errorCount = results.filter(r => r.status === 'rejected').length
console.log(`
All Done!
${total} Total,
${total - errorCount} Successful,
${errorCount} Errors`
)
console.log('\nResults', entries)
Object.keys(entries).length && writeToFile()
}
ghWeb()