feat: update to es6 (#18)

* feat: use async/await

* fix: fix github api bug

* fix: fix auto release note bug

* fix: auto release node commit message handle bug

* fix: Notable changes list display bug

* fix: Notable changes sub list display bug

* feat: add eslint

* feat: use husky to handle git hooks
This commit is contained in:
Yuga Sun 2017-10-17 23:19:29 -05:00 committed by 前端小武
parent d7f7b22c8e
commit b447cbe173
13 changed files with 783 additions and 662 deletions

View File

@ -3,7 +3,7 @@ root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

19
.eslintrc.js Normal file
View File

@ -0,0 +1,19 @@
// http://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
env: {
'node': true
},
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
extends: 'standard',
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}

View File

@ -4,7 +4,10 @@
"version": "0.0.1",
"main": "src/app.js",
"scripts": {
"start": "NODE_ENV=development node src/app"
"start": "NODE_ENV=development node src/app",
"lint": "eslint src/**/* --quiet",
"precommit": "npm run lint",
"commitmsg": "validate-commit-msg"
},
"repository": {
"type": "git",
@ -27,12 +30,9 @@
"string-template": "^1.0.0"
},
"engines": {
"node": ">= 4"
"node": ">= 7.8.0"
},
"config": {
"ghooks": {
"commit-msg": "validate-commit-msg"
},
"validate-commit-msg": {
"types": [
"feat",
@ -53,10 +53,20 @@
}
}
},
"os": ["darwin", "linux"],
"os": [
"darwin",
"linux"
],
"devDependencies": {
"ghooks": "^2.0.0",
"eslint": "^4.9.0",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.2.0",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-standard": "^3.0.1",
"git-pull-or-clone": "xuexb/git-pull-or-clone",
"husky": "^0.14.3",
"validate-commit-msg": "^2.14.0"
}
}

View File

@ -3,52 +3,50 @@
* @author xuexb <fe.xiaowu@gmail.com>
*/
require('dotenv').config();
require('dotenv').config()
const EventEmitter = require('events');
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const requireDir = require('require-dir');
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();
const EventEmitter = require('events')
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const requireDir = require('require-dir')
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()
app.use(bodyParser());
app.use(bodyParser())
app.use(ctx => {
let eventName = ctx.request.headers['x-github-event'];
if (eventName && verifySignature(ctx.request)) {
const payload = ctx.request.body;
const action = payload.action || payload.ref_type;
let eventName = ctx.request.headers['x-github-event']
if (eventName && verifySignature(ctx.request)) {
const payload = ctx.request.body
const action = payload.action || payload.ref_type
if (action) {
eventName += `_${action}`;
}
console.log(`receive event: ${eventName}`);
githubEvent.emit(eventName, {
repo: payload.repository.name,
payload
});
ctx.body = 'Ok.';
if (action) {
eventName += `_${action}`
}
else {
ctx.body = 'Go away.';
}
});
console.log(`receive event: ${eventName}`)
const actions = Object.assign({}, issueActions, pullRequestActions, releasesActions);
githubEvent.emit(eventName, {
repo: payload.repository.name,
payload
})
ctx.body = 'Ok.'
} else {
ctx.body = 'Go away.'
}
})
const actions = Object.assign({}, issueActions, pullRequestActions, releasesActions)
Object.keys(actions).forEach((key) => {
actions[key](githubEvent.on.bind(githubEvent));
console.log(`bind ${key} success!`);
});
actions[key](githubEvent.on.bind(githubEvent))
console.log(`bind ${key} success!`)
})
const port = 8000;
app.listen(port);
console.log(`Listening on http://0.0.0.0:${port}`);
const port = 8000
app.listen(port)
console.log(`Listening on http://0.0.0.0:${port}`)

View File

@ -3,293 +3,378 @@
* @author xuexb <fe.xiaowu@gmail.com>
*/
const GitHub = require('github');
const {toArray} = require('./utils');
const GitHub = require('github')
const { toArray } = require('./utils')
const github = new GitHub({
debug: process.env.NODE_ENV === 'development'
});
debug: process.env.NODE_ENV === 'development'
})
github.authenticate({
type: 'token',
token: process.env.GITHUB_TOKEN
});
type: 'token',
token: process.env.GITHUB_TOKEN
})
module.exports = {
github,
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;
/**
* issue 是否包含某 label
*
* @param {Object} payload data
* @param {string} body 评论内容
*/
async 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);
});
},
try {
const res = github.issues.getIssueLabels({
owner,
repo,
number
})
/**
* 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;
if (res.data.map(v => v.name).indexOf(label) === -1) {
Promise.reject(new Error('issue no label'))
}
} catch (e) {
Promise.reject(e)
}
},
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 评论内容
*/
async pullRequestHasLabel (payload, label) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
const number = payload.pull_request.number
/**
* 评论 issue
*
* @param {Object} payload data
* @param {string} body 评论内容
*/
commentIssue(payload, body) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.issue.number;
try {
const res = await github.issues.getIssueLabels({
owner,
repo,
number
})
if (res.data.map(v => v.name).indexOf(label) === -1) {
Promise.reject(new Error('pull request no label'))
}
} catch (e) {
Promise.reject(e)
}
},
github.issues.createComment({
owner,
repo,
number,
body
});
},
/**
* 评论 issue
*
* @param {Object} payload data
* @param {string} body 评论内容
*/
async commentIssue (payload, body) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
const number = payload.issue.number
/**
* 评论 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;
try {
const res = await github.issues.createComment({
owner,
repo,
number,
body
})
return res
} catch (e) {
Promise.reject(e)
}
},
github.issues.createComment({
owner,
repo,
number,
body
});
},
/**
* 评论 PR
*
* @param {Object} payload data
* @param {string} body 评论内容
*/
async commentPullRequest (payload, body) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
const number = payload.pull_request.number
/**
* 关闭 issue
*
* @param {Object} payload data
*/
closeIssue(payload) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.issue.number;
try {
const res = await github.issues.createComment({
owner,
repo,
number,
body
})
return res
} catch (e) {
Promise.reject(e)
}
},
github.issues.edit({
owner,
repo,
number,
state: 'closed'
});
},
/**
* 关闭 issue
*
* @param {Object} payload data
*/
async closeIssue (payload) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
const number = payload.issue.number
/**
* 分派作者到 issues
*
* @param {Object} payload data
* @param {string | Array} assign 用户id
*/
addAssigneesToIssue(payload, assign) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.issue.number;
try {
const res = await github.issues.edit({
owner,
repo,
number,
state: 'closed'
})
return res
} catch (e) {
Promise.reject(e)
}
},
github.issues.edit({
owner,
repo,
number,
assignees: Array.isArray(assign) ? assign : [assign]
});
},
/**
* 分派作者到 issues
*
* @param {Object} payload data
* @param {string | Array} assign 用户id
*/
async addAssigneesToIssue (payload, assign) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
const number = payload.issue.number
/**
* 添加标签到 issue
*
* @param {Object} payload data
* @param {string | Array} labels 标签
*/
addLabelsToIssue(payload, labels) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.issue.number;
try {
const res = await github.issues.edit({
owner,
repo,
number,
assignees: Array.isArray(assign) ? assign : [assign]
})
return res
} catch (e) {
Promise.reject(e)
}
},
github.issues.addLabels({
owner,
repo,
number,
labels: Array.isArray(labels) ? labels : [labels]
});
},
/**
* 添加标签到 issue
*
* @param {Object} payload data
* @param {string | Array} labels 标签
*/
async addLabelsToIssue (payload, labels) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
const number = payload.issue.number
/**
* 添加标签到 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;
try {
const res = await github.issues.addLabels({
owner,
repo,
number,
labels: Array.isArray(labels) ? labels : [labels]
})
return res
} catch (e) {
Promise.reject(e)
}
},
github.issues.addLabels({
owner,
repo,
number,
labels: Array.isArray(labels) ? labels : [labels]
});
},
/**
* 添加标签到 PR
*
* @param {Object} payload data
* @param {string | Array} labels 标签
*/
async addLabelsToPullRequest (payload, labels) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
const number = payload.pull_request.number
/**
* 删除 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;
try {
const res = await github.issues.addLabels({
owner,
repo,
number,
labels: Array.isArray(labels) ? labels : [labels]
})
return res
} catch (e) {
Promise.reject(e)
}
},
github.issues.removeLabel({
owner,
repo,
number,
name
});
},
/**
* 删除 PR 标签
*
* @param {Object} payload data
* @param {string} name 标签名
*/
async removeLabelsToPullRequest (payload, name) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
const number = payload.pull_request.number
/**
* 删除 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;
try {
const res = await Fgithub.issues.removeLabel({
owner,
repo,
number,
name
})
return res
} catch (e) {
Promise.reject(e)
}
},
github.issues.removeLabel({
owner,
repo,
number,
name
});
},
/**
* 删除 issue 标签
*
* @param {Object} payload data
* @param {string} name 标签名
*/
async removeLabelsToIssue (payload, name) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
const number = payload.issues.number
try {
const res = await github.issues.removeLabel({
owner,
repo,
number,
name
})
return res
} catch (e) {
Promise.reject(e)
}
},
/**
* 创建发布
*
* @param {Object} payload data
* @param {string} options.tag_name tag名
* @param {string} options.target_commitish tag hash
* @param {string} options.name 标题
* @param {string} options.body 内容
* @param {boolean} options.draft 是否为草稿
* @param {boolean} options.prerelease 是否预发布
*/
createRelease(payload, {tag_name, target_commitish, name, body, draft, prerelease}) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
/**
* 创建发布
*
* @param {Object} payload data
* @param {string} options.tag_name tag名
* @param {string} options.target_commitish tag hash
* @param {string} options.name 标题
* @param {string} options.body 内容
* @param {boolean} options.draft 是否为草稿
* @param {boolean} options.prerelease 是否预发布
*/
async createRelease (payload, { tag_name, target_commitish, name, body, draft, prerelease }) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
try {
const res = await github.repos.createRelease({
owner,
repo,
tag_name,
target_commitish,
name,
body,
draft,
prerelease
})
return res
} catch (e) {
Promise.reject(e)
}
},
github.repos.createRelease({
owner,
repo,
tag_name,
target_commitish,
name,
body,
draft,
prerelease
});
},
/**
* 根据tag获取发布信息
*
* @param {Object} payload data
* @param {string} options.tag_name tag名
*
* @return {Promise}
*/
async getReleaseByTag (payload, { tag_name }) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
try {
const res = await github.repos.getReleaseByTag({
owner,
repo,
tag: tag_name
})
return res
} catch (e) {
return false
}
},
/**
* 根据tag获取发布信息
*
* @param {Object} payload data
* @param {string} options.tag_name tag名
*
* @return {Promise}
*/
getReleaseByTag(payload, {tag_name}) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
/**
* 创建 review 请求
*
* @param {Object} payload data
* @param {Array | string} options.reviewers reviewer
* @param {Array | string} options.team_reviewers team_reviewers
*
* @return {Promise}
*/
async createReviewRequest (payload, { reviewers, team_reviewers }) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
const number = payload.pull_request.number
try {
const res = await github.pullRequests.createReviewRequest({
owner,
repo,
number,
reviewers: toArray(reviewers),
team_reviewers: toArray(team_reviewers)
})
return res
} catch (e) {
Promise.reject(e)
}
},
return github.repos.getReleaseByTag({
owner,
repo,
tag: tag_name
});
},
/**
* 获得 repo 所有的tag
*
* @param {any} payload data
* @returns
*/
async getTags (payload) {
const owner = payload.repository.owner.login
const repo = payload.repository.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;
try {
const res = await github.repos.getTags({
owner,
repo
})
return res.data
} catch (e) {
Promise.reject(e)
}
},
return github.pullRequests.createReviewRequest({
owner,
repo,
number,
reviewers: toArray(reviewers),
team_reviewers: toArray(team_reviewers)
});
},
};
async compareCommits (payload, { base, head }) {
const owner = payload.repository.owner.login
const repo = payload.repository.name
try {
const res = await github.repos.compareCommits({
owner,
repo,
base,
head
})
return res.data
} catch (e) {
Promise.reject(e)
}
}
}

View File

@ -3,21 +3,21 @@
* @author xuexb <fe.xiaowu@gmail.com>
*/
const {getPkgConfig} = require('../../utils');
const {addAssigneesToIssue} = require('../../github');
const { getPkgConfig } = require('../../utils')
const { addAssigneesToIssue } = require('../../github')
const config = getPkgConfig();
const assignMap = config.labelToAuthor || {};
const config = getPkgConfig()
const assignMap = config.labelToAuthor || {}
function autoAssign(on) {
on('issues_labeled', ({payload, repo}) => {
if (assignMap[payload.label.name]) {
addAssigneesToIssue(
payload,
assignMap[payload.label.name]
);
}
});
function autoAssign (on) {
on('issues_labeled', ({ payload, repo }) => {
if (assignMap[payload.label.name]) {
addAssigneesToIssue(
payload,
assignMap[payload.label.name]
)
}
})
}
module.exports = autoAssign;
module.exports = autoAssign

View File

@ -3,39 +3,39 @@
* @author xuexb <fe.xiaowu@gmail.com>
*/
const format = require('string-template');
const format = require('string-template')
const {
commentIssue,
closeIssue,
addLabelsToIssue
} = require('../../github');
commentIssue,
closeIssue,
addLabelsToIssue
} = require('../../github')
const comment = [
'hi @{user},非常感谢您的反馈,',
'但是由于您没有使用 [规范的issue](https://github.com/xuexb/github-bot#issue-规则) 格式, 将直接被关闭, 谢谢!'
].join('');
'hi @{user},非常感谢您的反馈,',
'但是由于您没有使用 [规范的issue](https://github.com/xuexb/github-bot#issue-规则) 格式, 将直接被关闭, 谢谢!'
].join('')
const match = str => {
return /node version:\s*[vV]?(\d\.?)+/.test(str);
};
function replyInvalid(on) {
on('issues_opened', ({payload}) => {
const issue = payload.issue;
const opener = issue.user.login;
if (!match(issue.body)) {
commentIssue(
payload,
format(comment, {
user: opener
})
);
closeIssue(payload);
addLabelsToIssue(payload, 'invalid');
}
});
return /node version:\s*[vV]?(\d\.?)+/.test(str)
}
module.exports = replyInvalid;
function replyInvalid (on) {
on('issues_opened', ({ payload }) => {
const issue = payload.issue
const opener = issue.user.login
if (!match(issue.body)) {
commentIssue(
payload,
format(comment, {
user: opener
})
)
closeIssue(payload)
addLabelsToIssue(payload, 'invalid')
}
})
}
module.exports = replyInvalid

View File

@ -3,23 +3,22 @@
* @author xuexb <fe.xiaowu@gmail.com>
*/
const format = require('string-template');
const {commentIssue} = require('../../github');
const format = require('string-template')
const { commentIssue } = require('../../github')
const comment = 'hi @{user},请提供一个可预览的链接,如: <https://codepen.io/pen?template=KgPZrE&editors=0010>';
const comment = 'hi @{user},请提供一个可预览的链接,如: <https://codepen.io/pen?template=KgPZrE&editors=0010>'
function replyNeedDemo(on) {
on('issues_labeled', ({payload, repo}) => {
if (payload.label.name === 'need demo') {
commentIssue(
payload,
format(comment, {
user: payload.issue.user.login
})
);
}
});
function replyNeedDemo (on) {
on('issues_labeled', ({ payload, repo }) => {
if (payload.label.name === 'need demo') {
commentIssue(
payload,
format(comment, {
user: payload.issue.user.login
})
)
}
})
}
module.exports = replyNeedDemo;
module.exports = replyNeedDemo

View File

@ -3,21 +3,21 @@
* @author xuexb <fe.xiaowu@gmail.com>
*/
const {getPkgConfig} = require('../../utils');
const {createReviewRequest} = require('../../github');
const { getPkgConfig } = require('../../utils')
const { createReviewRequest } = require('../../github')
const config = getPkgConfig();
const assignMap = config.labelToAuthor || {};
const config = getPkgConfig()
const assignMap = config.labelToAuthor || {}
module.exports = on => {
on('pull_request_labeled', ({payload, repo}) => {
if (assignMap[payload.label.name]) {
createReviewRequest(
payload,
{
reviewers: assignMap[payload.label.name]
}
);
on('pull_request_labeled', ({ payload, repo }) => {
if (assignMap[payload.label.name]) {
createReviewRequest(
payload,
{
reviewers: assignMap[payload.label.name]
}
});
)
}
})
}

View File

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

View File

@ -4,31 +4,31 @@
*/
const {
addLabelsToPullRequest,
pullRequestHasLabel
} = require('../../github');
addLabelsToPullRequest,
pullRequestHasLabel
} = require('../../github')
const getAction = title => {
return (title.match(/^(\w+?):/) || [])[1];
};
return (title.match(/^(\w+?):/) || [])[1]
}
const ACTION_TO_LABEL_MAP = {
feat: 'enhancement',
fix: 'bug',
docs: 'document'
};
feat: 'enhancement',
fix: 'bug',
docs: 'document'
}
const handle = ({payload, repo}) => {
const action = getAction(payload.pull_request.title);
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]);
});
}
};
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);
};
on('pull_request_edited', handle)
on('pull_request_opened', handle)
}

View File

@ -3,79 +3,92 @@
* @author xuexb <fe.xiaowu@gmail.com>
*/
const {getReleaseByTag, createRelease} = require('../../github');
const {updateRepo, getTags, getFirstCommitHash, getCommitLog} = require('../../utils');
const { getTags, compareCommits, getReleaseByTag, createRelease } = require('../../github')
const RELEASE_CHANGE_MAP = {
document: 'docs',
feature: 'feat',
bugfix: 'fix',
close: 'close'
};
document: 'docs',
feature: 'feat',
bugfix: 'fix',
close: 'close'
}
module.exports = on => {
on('create_tag', ({payload, repo}) => {
getReleaseByTag(payload, {
tag_name: payload.ref
}).then(() => {}, () => {
updateRepo({
url: payload.repository.clone_url,
repo
}).then(repoDir => {
const tags = getTags({
dir: repoDir
});
const after = tags[0];
const before = tags.length > 1 ? tags[1] : getFirstCommitHash({
dir: repoDir
});
const log = getCommitLog({
dir: repoDir,
before,
after
});
on('create_tag', async ({ payload, repo }) => {
const tag = await getReleaseByTag(payload, {
tag_name: payload.ref
})
// 如果该 tag 存在则直接返回
if (tag !== false) {
return
}
const hash = getCommitLog({
dir: repoDir,
before,
after,
html_url: payload.repository.html_url,
hash: true
});
// 创建 release note
try {
const tags = await getTags(payload)
const head = tags[0].name
const base = tags.length > 1 ? tags[1].name : tags[0].name
const changes = Object.keys(RELEASE_CHANGE_MAP).map(title => {
return {
title,
data: log.filter(log => log.indexOf(`- ${RELEASE_CHANGE_MAP[title]}:`) === 0)
}
}).filter(v => v.data.length);
const commitsLog = await compareCommits(payload, {
base,
head
})
let body = [];
const commits = commitsLog.commits
const changes = Object.keys(RELEASE_CHANGE_MAP).map(title => {
let data = []
commits.map((commit) => {
if (commit.commit.message.indexOf(`${RELEASE_CHANGE_MAP[title]}:`) === 0) {
let message = commit.commit.message
// 处理 squash merge 的 commit message
// 后期看看有没有更好的解决办法?
if (message.indexOf('\n') !== -1) {
message = message.substr(0, message.indexOf('\n'))
}
data.push(`- ${message}, by @${commit.commit.author.name} <<${commit.commit.author.email}>>`)
}
})
return {
title,
data
}
}).filter(v => v.data.length)
if (changes.length) {
body.push('## Notable changes\n');
changes.forEach(v => {
body.push([
`- ${v.title}`
]);
const hashChanges = commits.map((commit) => {
let message = commit.commit.message
// 处理 squash merge 的 commit message
if (message.indexOf('\n') !== -1) {
message = message.substr(0, message.indexOf('\n'))
}
return `- [${commit.sha.substr(0, 7)}](${commit.html_url}) - ${message}, by @${commit.commit.author.name} <<${commit.commit.author.email}>>`
})
v.data.forEach(line => body.push(' ' + line));
});
}
let body = []
if (hash.length) {
body.push('\n## Commits\n');
body = body.concat(hash);
}
if (changes.length) {
body.push('## Notable changes\n')
changes.forEach(v => {
body.push([
`- ${v.title}`
])
if (body.length) {
createRelease(payload, {
tag_name: payload.ref,
name: `${payload.ref} @${payload.repository.owner.login}`,
body: body.join('\n')
});
}
}).catch(err => console.error(err));
});
});
v.data.forEach(line => body.push(' ' + line))
})
}
if (hashChanges.length) {
body.push('\n## Commits\n')
body = body.concat(hashChanges)
}
if (body.length) {
createRelease(payload, {
tag_name: payload.ref,
name: `${payload.ref} @${payload.repository.owner.login}`,
body: body.join('\n')
})
}
} catch (err) {
console.error(err)
}
})
}

View File

@ -3,168 +3,167 @@
* @author xuexb <fe.xiaowu@gmail.com>
*/
const format = require('string-template');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const {fixedTimeComparison} = require('cryptiles');
const {execSync, exec} = require('child_process');
const gitPullOrClone = require('git-pull-or-clone');
const format = require('string-template')
const path = require('path')
const fs = require('fs')
const crypto = require('crypto')
const { fixedTimeComparison } = require('cryptiles')
const { execSync } = require('child_process')
const gitPullOrClone = require('git-pull-or-clone')
const utils = {
/**
* 验证请求
*
* @param {Object} request req
*
* @return {boolean}
*/
verifySignature(request) {
let signature = crypto.createHmac('sha1', process.env.GITHUB_SECRET_TOKEN)
.update(request.rawBody)
.digest('hex');
signature = `sha1=${signature}`;
return fixedTimeComparison(signature, request.headers['x-hub-signature']);
},
/**
* 验证请求
*
* @param {Object} request req
*
* @return {boolean}
*/
verifySignature (request) {
let signature = crypto.createHmac('sha1', process.env.GITHUB_SECRET_TOKEN)
.update(request.rawBody)
.digest('hex')
signature = `sha1=${signature}`
return fixedTimeComparison(signature, request.headers['x-hub-signature'])
},
/**
* 目录是否存在
*
* @param {string} file 路径
*
* @return {boolean}
*/
isDirectory(file) {
try {
return fs.statSync(file).isDirectory();
}
catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
/**
* 目录是否存在
*
* @param {string} file 路径
*
* @return {boolean}
*/
isDirectory (file) {
try {
return fs.statSync(file).isDirectory()
} catch (e) {
if (e.code !== 'ENOENT') {
throw e
}
return false;
}
},
/**
* 获取本地 git 目录的第一个提交主要处理当第一次给项目打标签时不能用2个标签去 ..
*
* @param {string} options.dir git 目录
*
* @return {string}
*/
getFirstCommitHash({dir}) {
return execSync(`cd ${dir} && git log --oneline --pretty=format:"%h"`).toString()
.split(/\n+/).slice(-1)[0];
},
/**
* 获取本地 git 目录的日志
*
* @param {Object} options 配置数据
* @param {string} options.dir git 目录
* @param {string} options.before 开始版本号
* @param {string} options.after 结束版本号
* @param {string} options.html_url 预览的html地址 用来拼 hash commit
* @param {boolean} [options.hash=false] 是否携带 commit hash log
*
* @return {Array}
*/
getCommitLog(options) {
const shell = [
'cd {dir}',
options.hash
? 'git log {before}..{after} --no-merges --pretty=format:"- [%h]({html_url}/commit/%H) - %s, by @%aN <<%ae>>"'
: 'git log {before}..{after} --no-merges --pretty=format:"- %s, by @%aN <<%ae>>"'
].join(' && ');
return execSync(format(shell, options)).toString().split(/\n+/);
},
/**
* 更新 github 项目
*
* @param {string} options.repo 项目名
*
* @return {Promise}
*/
updateRepo({url, repo}) {
const repoDir = path.resolve(__dirname, '../github/', repo);
return new Promise((resolve, reject) => {
gitPullOrClone(url, repoDir, err => {
if (err) {
return reject(err);
}
resolve(repoDir);
});
});
},
/**
* 获取本地 git 目录的 tag 列表
*
* @param {Object} options 配置
* @param {string} options.dir git 目录
*
* @return {Array}
*/
getTags(options) {
const shell = [
'cd {dir}',
'git describe --tags `git rev-list --tags --abbrev=0` --abbrev=0 | uniq'
].join(' && ');
return execSync(format(shell, options)).toString().split(/\n+/).filter(tag => !!tag);
},
/**
* 获取 package.json 里的 config.github-bot
*
* @return {Object}
*/
getPkgConfig() {
const pkg = require('../package.json');
const config = Object.assign({
'github-bot': {}
}, 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;
return false
}
};
},
module.exports = utils;
/**
* 获取本地 git 目录的第一个提交主要处理当第一次给项目打标签时不能用2个标签去 ..
*
* @param {string} options.dir git 目录
*
* @return {string}
*/
getFirstCommitHash ({ dir }) {
return execSync(`cd ${dir} && git log --oneline --pretty=format:"%h"`).toString()
.split(/\n+/).slice(-1)[0]
},
/**
* 获取本地 git 目录的日志
*
* @param {Object} options 配置数据
* @param {string} options.dir git 目录
* @param {string} options.before 开始版本号
* @param {string} options.after 结束版本号
* @param {string} options.html_url 预览的html地址 用来拼 hash commit
* @param {boolean} [options.hash=false] 是否携带 commit hash log
*
* @return {Array}
*/
getCommitLog (options) {
const shell = [
'cd {dir}',
options.hash
? 'git log {before}..{after} --no-merges --pretty=format:"- [%h]({html_url}/commit/%H) - %s, by @%aN <<%ae>>"'
: 'git log {before}..{after} --no-merges --pretty=format:"- %s, by @%aN <<%ae>>"'
].join(' && ')
return execSync(format(shell, options)).toString().split(/\n+/)
},
/**
* 更新 github 项目
*
* @param {string} options.repo 项目名
*
* @return {Promise}
*/
updateRepo ({ url, repo }) {
const repoDir = path.resolve(__dirname, '../github/', repo)
return new Promise((resolve, reject) => {
gitPullOrClone(url, repoDir, err => {
if (err) {
return reject(err)
}
resolve(repoDir)
})
})
},
/**
* 获取本地 git 目录的 tag 列表
*
* @param {Object} options 配置
* @param {string} options.dir git 目录
*
* @return {Array}
*/
getTags (options) {
const shell = [
'cd {dir}',
'git describe --tags `git rev-list --tags --abbrev=0` --abbrev=0 | uniq'
].join(' && ')
return execSync(format(shell, options)).toString().split(/\n+/).filter(tag => !!tag)
},
/**
* 获取 package.json 里的 config.github-bot
*
* @return {Object}
*/
getPkgConfig () {
const pkg = require('../package.json')
const config = Object.assign({
'github-bot': {}
}, 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
}
}
module.exports = utils