feat: add PR auto label, reviewer, title

This commit is contained in:
xuexb 2017-10-15 12:43:42 +08:00
parent 221e16bc6e
commit 3ac7b7f4d1
7 changed files with 319 additions and 25 deletions

View File

@ -9,8 +9,10 @@ const EventEmitter = require('events');
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const requireDir = require('require-dir');
const {verifySignature, getRepo} = require('./utils');
const {verifySignature} = require('./utils');
const issueActions = requireDir('./modules/issues');
const pullRequestActions = requireDir('./modules/pull_request');
const releasesActions = requireDir('./modules/releases');
const app = new Koa();
const githubEvent = new EventEmitter();
@ -28,12 +30,10 @@ app.use(ctx => {
console.log(`receive event: ${eventName}`);
if (payload.sender.login !== process.env.GITHUB_BOT_NAME) {
githubEvent.emit(eventName, {
repo: getRepo(payload.repository.full_name),
payload
});
}
githubEvent.emit(eventName, {
repo: payload.repository.name,
payload
});
ctx.body = 'Ok.';
}
@ -42,8 +42,11 @@ app.use(ctx => {
}
});
Object.keys(issueActions).forEach((key) => {
issueActions[key](githubEvent.on.bind(githubEvent));
const actions = Object.assign({}, issueActions, pullRequestActions, releasesActions);
Object.keys(actions).forEach((key) => {
actions[key](githubEvent.on.bind(githubEvent));
console.log(`bind ${key} success!`);
});
const port = 8000;

View File

@ -4,9 +4,10 @@
*/
const GitHub = require('github');
const {toArray} = require('./utils');
const github = new GitHub({
debug: process.env.NODE_ENV === 'development'
debug: false//process.env.NODE_ENV === 'development'
});
github.authenticate({
@ -16,6 +17,62 @@ github.authenticate({
module.exports = {
github,
/**
* issue 是否包含某 label
*
* @param {Object} payload data
* @param {string} body 评论内容
*/
issueHasLabel(payload, label) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.issue.number;
return new Promise((resolve, reject) => {
github.issues.getIssueLabels({
owner,
repo,
number
}).then(res => {
if (res.data.map(v => v.name).indexOf(label) > -1) {
resolve();
}
else {
reject();
}
}, reject);
});
},
/**
* PR 是否包含某 label
*
* @param {Object} payload data
* @param {string} body 评论内容
*/
pullRequestHasLabel(payload, label) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.pull_request.number;
return new Promise((resolve, reject) => {
github.issues.getIssueLabels({
owner,
repo,
number
}).then(res => {
if (res.data.map(v => v.name).indexOf(label) > -1) {
resolve();
}
else {
reject();
}
}, reject);
});
},
/**
* 评论 issue
*
@ -35,6 +92,25 @@ module.exports = {
});
},
/**
* 评论 PR
*
* @param {Object} payload data
* @param {string} body 评论内容
*/
commentPullRequest(payload, body) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.pull_request.number;
github.issues.createComment({
owner,
repo,
number,
body
});
},
/**
* 关闭 issue
*
@ -91,6 +167,63 @@ module.exports = {
});
},
/**
* 添加标签到 PR
*
* @param {Object} payload data
* @param {string | Array} labels 标签
*/
addLabelsToPullRequest(payload, labels) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.pull_request.number;
github.issues.addLabels({
owner,
repo,
number,
labels: Array.isArray(labels) ? labels : [labels]
});
},
/**
* 删除 PR 标签
*
* @param {Object} payload data
* @param {string} name 标签名
*/
removeLabelsToPullRequest(payload, name) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.pull_request.number;
github.issues.removeLabel({
owner,
repo,
number,
name
});
},
/**
* 删除 issue 标签
*
* @param {Object} payload data
* @param {string} name 标签名
*/
removeLabelsToIssue(payload, name) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.issues.number;
github.issues.removeLabel({
owner,
repo,
number,
name
});
},
/**
* 创建发布
*
@ -135,5 +268,28 @@ module.exports = {
repo,
tag: tag_name
});
}
},
/**
* 创建 review 请求
*
* @param {Object} payload data
* @param {Array | string} options.reviewers reviewer
* @param {Array | string} options.team_reviewers team_reviewers
*
* @return {Promise}
*/
createReviewRequest(payload, {reviewers, team_reviewers}) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.pull_request.number;
return github.pullRequests.createReviewRequest({
owner,
repo,
number,
reviewers: toArray(reviewers),
team_reviewers: toArray(team_reviewers)
});
},
};

View File

@ -0,0 +1,23 @@
/**
* @file PR 自动根据 tag 去添加 reviewer
* @author xuexb <fe.xiaowu@gmail.com>
*/
const {getPkgConfig} = require('../../utils');
const {createReviewRequest} = require('../../github');
const config = getPkgConfig();
const assignMap = config.lableToAuthor || {};
module.exports = on => {
on('pull_request_labeled', ({payload, repo}) => {
if (assignMap[payload.label.name]) {
createReviewRequest(
payload,
{
reviewers: assignMap[payload.label.name]
}
);
}
});
}

View File

@ -0,0 +1,61 @@
/**
* @file PR 提示标题正确性
* @author xuexb <fe.xiaowu@gmail.com>
*/
const format = require('string-template');
const {getPkgCommitPrefix} = require('../../utils');
const {
commentPullRequest,
addLabelsToPullRequest,
removeLabelsToPullRequest,
pullRequestHasLabel
} = require('../../github');
const actions = getPkgCommitPrefix();
const match = title => {
return actions.some(action => title.indexOf(`${action}:`) === 0);
};
const commentSuccess = [
'hi @{user},非常感谢您及时修正标题格式,祝您玩的开心!'
].join('');
const commentError = [
'hi @{user},非常感谢您的 PR ',
'但是您没有使用 [PR 标题规则](https://github.com/xuexb/github-bot#commit-log-和-pr-标题规则) 格式,',
'请及时修改, 谢谢!'
].join('');
module.exports = on => {
if (actions.length) {
on('pull_request_opened', ({payload, repo}) => {
if (!match(payload.pull_request.title)) {
commentPullRequest(
payload,
format(commentError, {
user: payload.pull_request.user.login
})
);
addLabelsToPullRequest(payload, 'invalid');
}
});
on('pull_request_edited', ({payload, repo}) => {
if (match(payload.pull_request.title)) {
pullRequestHasLabel(payload, 'invalid').then(() => {
commentPullRequest(
payload,
format(commentSuccess, {
user: payload.pull_request.user.login
})
);
removeLabelsToPullRequest(payload, 'invalid');
});
}
});
}
};

View File

@ -0,0 +1,33 @@
/**
* @file PR 标题自动打标签
* @author xuexb <fe.xiaowu@gmail.com>
*/
const {
addLabelsToPullRequest,
pullRequestHasLabel
} = require('../../github');
const getAction = title => {
return (title.match(/^(\w+?):/) || [])[1];
};
const ACTION_TO_LABEL_MAP = {
feat: 'enhancement',
fix: 'bug'
};
const handle = ({payload, repo}) => {
const action = getAction(payload.pull_request.title);
if (action && ACTION_TO_LABEL_MAP[action]) {
pullRequestHasLabel(payload, ACTION_TO_LABEL_MAP[action]).catch(() => {
addLabelsToPullRequest(payload, ACTION_TO_LABEL_MAP[action]);
});
}
};
module.exports = on => {
on('pull_request_edited', handle);
on('pull_request_opened', handle);
};

View File

@ -13,7 +13,7 @@ const RELEASE_CHANGE_MAP = {
close: 'close'
};
function autoReleaseNote(on) {
module.exports = on => {
on('create_tag', ({payload, repo}) => {
getReleaseByTag(payload, {
tag_name: payload.ref
@ -78,5 +78,3 @@ function autoReleaseNote(on) {
});
});
}
module.exports = autoReleaseNote;

View File

@ -23,17 +23,6 @@ const utils = {
return fixedTimeComparison(signature, request.headers['x-hub-signature']);
},
/**
* 获取项目名
*
* @param {string} url xuexb/repo
*
* @return {string} repo
*/
getRepo(url) {
return url.split('/')[1];
},
/**
* 目录是否存在
*
@ -124,6 +113,37 @@ const utils = {
}, pkg.config);
return config['github-bot'];
},
/**
* 获取 commit log 前缀白名单
*
* @return {Array}
*/
getPkgCommitPrefix() {
const pkg = require('../package.json');
const config = Object.assign({
'validate-commit-msg': {
'types': []
}
}, pkg.config);
return config['validate-commit-msg'].types;
},
/**
* 转化成 Array
*
* @param {string | Array} str 目标值
*
* @return {Array}
*/
toArray(str) {
if (str) {
return Array.isArray(str) ? str : [str];
}
return str;
}
};