diff --git a/src/cli.js b/src/cli.js index 1c52e96..434da67 100755 --- a/src/cli.js +++ b/src/cli.js @@ -11,6 +11,8 @@ const generate = require('./generate') const util = require('./util') const repo = require('./repo') const updateContributors = require('./contributors') +const {getContributors} = require('./discover') +const learner = require('./discover/learner') const cwd = process.cwd() const defaultRCFile = path.join(cwd, '.all-contributorsrc') @@ -64,16 +66,48 @@ function startGeneration(argv) { function addContribution(argv) { util.configFile.readConfig(argv.config) // ensure the config file exists const username = argv._[1] === undefined ? undefined : String(argv._[1]) + /* Example: (for clarity & debugging purposes) + { + _: [ 'add' ], + projectName: 'cz-cli', + projectOwner: 'commitizen', + repoType: 'github', + repoHost: 'https://github.com', + files: [ 'AC.md' ], + imageSize: 100, + commit: false, + commitConvention: 'angular', + contributors: [], + contributorsPerLine: 7, + 'contributors-per-line': 7, + config: '/mnt/c/Users/max/Projects/cz-cli/.all-contributorsrc', + '$0': '../all-contributors-cli/src/cli.js' + } + */ const contributions = argv._[2] // Add or update contributor in the config file - return updateContributors(argv, username, contributions).then(data => { - argv.contributors = data.contributors - return startGeneration(argv).then(() => { - if (argv.commit) { - return util.git.commit(argv, data) - } - }) - }) + return updateContributors(argv, username, contributions).then( + data => { + argv.contributors = data.contributors + /* Example + [ { login: 'Berkmann18', + name: 'Maximilian Berkmann', + avatar_url: 'https://avatars0.githubusercontent.com/u/8260834?v=4', + profile: 'http://maxcubing.wordpress.com', + contributions: [ 'code', 'ideas' ] }, + { already in argv.contributors } ] + */ + return startGeneration(argv).then( + () => { + if (argv.commit) { + return util.git.commit(argv, data) + } + }, + err => console.error('Generation fail:', err), + ) + }, + err => console.error('Contributor Update fail:', err), + ) } function checkContributors(argv) { @@ -87,6 +121,7 @@ function checkContributors(argv) { configData.repoHost, ) .then(repoContributors => { + // console.dir(repoContributors) //['jfmengels', 'jakebolam', ...] const checkKey = repo.getCheckKey(configData.repoType) const knownContributions = configData.contributors.reduce((obj, item) => { obj[item[checkKey]] = item.contributions @@ -123,6 +158,122 @@ function checkContributors(argv) { }) } +function fetchContributors(argv) { + // console.log('argv=', argv); + // const configData = util.configFile.readConfig(argv.config) + // console.log('configData') + // console.dir(configData) + + return getContributors(argv.projectOwner, argv.projectName).then( + repoContributors => { + // repoContributors = {prCreators, prCommentators, issueCreators, issueCommentators, reviewers, commitAuthors, commitCommentators} + // console.dir(repoContributors) + + // const checkKey = repo.getCheckKey(configData.repoType) + // const knownContributions = configData.contributors.reduce((obj, item) => { + // obj[item[checkKey]] = item.contributions + // return obj + // }, {}) + // console.log('knownContributions', knownContributions) //{ jfmengels: ['code', 'test', 'doc'], ...} + // const knownContributors = configData.contributors.map( + // contributor => contributor[checkKey], + // ) + // console.log('knownContributors', knownContributors) //['kentcdodds', 'ben-eb', ...] + + // let contributors = new Set( + // repoContributors.prCreators.map(usr => usr.login), + // ) + + // repoContributors.issueCreators.forEach(usr => contributors.add(usr.login)) + // repoContributors.reviewers.forEach(usr => contributors.add(usr.login)) + // repoContributors.commitAuthors.forEach(usr => contributors.add(usr.login)) + // contributors = Array.from(contributors) + + // console.log('ctbs=', contributors); + + //~1. Auto-add reviewers for review~ + //~2. Auto-add issue creators for any categories found~ + //~3. Auto-add commit authors~ + //4. Roll onto other contribution categories following https://www.draw.io/#G1uL9saIuZl3rj8sOo9xsLOPByAe28qhwa + + const args = {...argv, _: []} + const contributorsToAdd = [] + repoContributors.reviewers.forEach(usr => { + // args._ = ['add', usr.login, 'review'] + // addContribution(args) + contributorsToAdd.push({login: usr.login, contributions: ['review']}) + // console.log( + // `Adding ${chalk.underline('Reviewer')} ${chalk.blue(usr.login)}`, + // ) + }) + + repoContributors.issueCreators.forEach(usr => { + // console.log('usr=', usr.login, 'labels=', usr.labels) + const contributor = { + login: usr.login, + contributions: [], + } + usr.labels.forEach(lbl => { + const guesses = learner.classify(lbl).filter(c => c && c !== 'null') + if (guesses.length) { + const category = guesses[0] + // args._ = ['', usr.login, category] + // addContribution(args) + if (!contributor.contributions.includes(category)) + contributor.contributions.push(category) + // console.log( + // `Adding ${chalk.blue(usr.login)} for ${chalk.underline(category)}`, + // ) + } //else console.warn(`Oops, I couldn't find any category for the "${lbl}" label`) + }) + const existingContributor = contributorsToAdd.filter( + ctrb => ctrb.login === usr.login, + ) + if (existingContributor.length) { + existingContributor[0].contributions = [ + ...new Set( + existingContributor[0].contributions.concat( + contributor.contributions, + ), + ), + ] + } else contributorsToAdd.push(contributor) + }) + + repoContributors.commitAuthors.forEach(usr => { + // const contributor = { + // login: usr.login, + // contributions: [], + // } + // console.log('commit auth:', usr) + const existingContributor = contributorsToAdd.filter( + ctrb => ctrb.login === usr.login, + ) + if (existingContributor.length) { + //there's no label or commit message info so use only code for now + if (!existingContributor[0].contributions.includes('code')) { + existingContributor[0].contributions.push('code') + } + } else + contributorsToAdd.push({login: usr.login, contributions: ['code']}) + }) + + // console.log('contributorsToAdd=', contributorsToAdd) + contributorsToAdd.forEach(contributor => { + console.log( + `Adding ${chalk.blue(contributor.login)} for ${chalk.underline( + contributor.contributions.join('/'), + )}`, + ) + args._ = ['', contributor.login, contributor.contributions.join(',')] + // if (contributor.contributions.length) addContribution(args) + // else console.log('Skipping', contributor.login) + }) + }, + err => console.error('fetch error:', err), + ) +} + function onError(error) { if (error) { console.error(error.stack || error.message || error) @@ -151,6 +302,10 @@ function promptForCommand(argv) { 'Compare contributors from the repository with the credited ones', value: 'check', }, + { + name: 'Fetch contributors from the repository', + value: 'fetch', + }, ], when: !argv._[0], default: 0, @@ -173,6 +328,8 @@ promptForCommand(yargv) return addContribution(yargv) case 'check': return checkContributors(yargv) + case 'fetch': + return fetchContributors(yargv) default: throw new Error(`Unknown command ${command}`) } diff --git a/src/contributors/index.js b/src/contributors/index.js index 3e3544c..fea31c1 100644 --- a/src/contributors/index.js +++ b/src/contributors/index.js @@ -10,13 +10,20 @@ function isNewContributor(contributorList, username) { module.exports = function addContributor(options, username, contributions) { const answersP = prompt(options, username, contributions) - const contributorsP = answersP.then(answers => - add(options, answers.username, answers.contributions, repo.getUserInfo), - ) + const contributorsP = answersP + .then(answers => + add(options, answers.username, answers.contributions, repo.getUserInfo), + ) + //eslint-disable-next-line no-console + .catch(err => console.error('contributorsP error:', err)) - const writeContributorsP = contributorsP.then(contributors => - util.configFile.writeContributors(options.config, contributors), - ) + const writeContributorsP = contributorsP + .then(contributors => { + // console.log('opts.config=', options.config, 'contributors=', contributors) + return util.configFile.writeContributors(options.config, contributors) + }) + //eslint-disable-next-line no-console + .catch(err => console.error('writeContributorsP error:', err)) return Promise.all([answersP, contributorsP, writeContributorsP]).then( res => { @@ -32,5 +39,7 @@ module.exports = function addContributor(options, username, contributions) { ), } }, + //eslint-disable-next-line no-console + err => console.error('contributors fail: ', err), ) } diff --git a/src/discover/index.js b/src/discover/index.js new file mode 100644 index 0000000..6c4ee39 --- /dev/null +++ b/src/discover/index.js @@ -0,0 +1,19 @@ +const nyc = require('name-your-contributors') +const {Spinner} = require('clui') + +const privateToken = (process.env && process.env.PRIVATE_TOKEN) || '' +const loader = new Spinner('Loading...') + +const getContributors = function(owner, name, token = privateToken) { + loader.start() + const contributors = nyc.repoContributors({ + token, + user: owner, + repo: name, + commits: true, + }) + loader.stop() + return contributors +} + +module.exports = {getContributors} diff --git a/src/discover/learner.js b/src/discover/learner.js new file mode 100644 index 0000000..5afe212 --- /dev/null +++ b/src/discover/learner.js @@ -0,0 +1,23 @@ +const {existsSync} = require('fs') +const Learner = require('ac-learn') + +const JSON_PATH = `${__dirname}/learner.json` + +//@TODO: Use the JSON methods from `ac-learn` to get the whole thing saveable +const learner = new Learner() +/* eslint-disable no-console */ +if (existsSync(JSON_PATH)) { + learner.loadAndDeserializeClassifier(JSON_PATH).then(classifier => { + learner.classifier = classifier + // console.log('Re-using existing classifier') + }, console.error) +} else { + learner.crossValidate(6) + learner.eval() + learner.serializeAndSaveClassifier(JSON_PATH).then(_ => { + // console.log('Classifier saved', classifier) + }, console.error) +} +/* eslint-enable no-console */ + +module.exports = learner diff --git a/src/discover/learner.json b/src/discover/learner.json new file mode 100644 index 0000000..e69de29