mirror of
https://github.com/xuexb/github-bot.git
synced 2026-01-18 13:56:38 +00:00
feat: add PR auto label, reviewer, title
This commit is contained in:
parent
221e16bc6e
commit
3ac7b7f4d1
21
src/app.js
21
src/app.js
@ -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;
|
||||
|
||||
160
src/github.js
160
src/github.js
@ -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)
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
23
src/modules/pull_request/autoReviewRequest.js
Normal file
23
src/modules/pull_request/autoReviewRequest.js
Normal 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]
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
61
src/modules/pull_request/replyInvalidTitle.js
Normal file
61
src/modules/pull_request/replyInvalidTitle.js
Normal 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');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
33
src/modules/pull_request/titlePrefixToLabel.js
Normal file
33
src/modules/pull_request/titlePrefixToLabel.js
Normal 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);
|
||||
};
|
||||
@ -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;
|
||||
42
src/utils.js
42
src/utils.js
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user