diff --git a/README.en.md b/README.en.md index 2d330e177d9b4365644b3b84b05f99c63584d445..ea26485b1c9ca34f9e4c06d2cd3a245d97a3f2d6 100644 --- a/README.en.md +++ b/README.en.md @@ -1,4 +1,4 @@ -# git-webhook-ci [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] +# git-webhook-ci > A Git (github/gitee) webhook callback server to fetch new code (poor man CI) This little tool is born out of real projects. Keep having to deploy and setup demo site etc. Why bother if you own the git account? @@ -21,162 +21,35 @@ or Create a js file (normally on your project root directory). Let's call it `webhook.js`. ```js -const gitWebhook = require('git-webhook-ci'); +const gitWebhook = require('git-webhook-ci') const config = { + "provider": "github", // from version 2 you MUST provide this "secret": "your-github-webhook-secret", "path": "/webhook", "port": 8081, "branch": "refs/heads/master", // New in 0.4.1 you can pass * wildcard to listen to all branches - "cmd": "git pull origin master --no-edit" -}; - -gitWebhook(config); -``` - -*Important change, we no longer support github by default. Instead if you don't add the provider view, it will be gitlab!* - - -The minimum setup can be like this: - -```js - - // Default is gitlab - gitWebhook({secret: "your-gitlab-token"}); - - // For Gitee - gitWebhook({secret: 'your-gitee-password', provider: 'gitee'}); - - // For Github - gitWebhook({secret: 'your-github-webhook-secret', provider: 'github'}); - -``` - - -## New in 0.4.0 - cmd accept (String) command to run or (Function) callback - -The `cmd` config option now accept a function. - -The signature as follow - -```js -{ - secret: 'your-secret-between-you-and-gitlab', - cmd: (result, opt, ref) => { - // result has 3 properties - // 1. payload - // 2. host - // 3. event - from github / gitee - // opt is an environment variable that you can pass to the spawn - } -} -``` - -Example how to combine the wildcard branch option, and a function callback - -```js -const gitWebhook = require('git-webook-ci'); -const { spawn } = require('child_process'); - -const server = gitWebhook({ - secret: 'your-secret-between-you-and-github', - branch: '*', - cmd: (result, opt, ref) => { - switch (ref) { - case 'refs/heads/master': - const e1 = spawn('npm', ['run', 'something'], opt); - break; - case 'refs/heads/develop': - const e2 = spawn('npm', ['run', 'something-else'], opt); - break; - default: - // do special stuff using the result object - specialFunc(result.payload, opt); - } + "cmd": "git pull origin master --no-edit", + "error": (err) => { + // do thing with your error } -}); - -``` - -As you can see from the code example from above. The method `gitWebhook` actually return the -`server` instance from `http.createServer`. So you can make it to stop, restart etc easily. - -## New in 0.4.x - support 码云 Gitee.com - -You can now pass a new configuration option `provider`: -```js -{ - secret: 'your-password-between-you-and-gitee', - provider: 'gitee' } -``` - -## New in 0.5.x - support Gitlab.com -You just need to change the provider to `gitlab`: (*This is the default since V.0.9.x*) - -```js -{ - secret: 'your-gitlab-token', - provider: 'gitlab' -} -``` - -## New in 0.8.x - support 微信小程序消息服务 Wechat mini app callback - -We have added a new provider here - it's not a git repo. It supports the [Wechat callback](https://mp.weixin.qq.com/debug/wxadoc/dev/api/custommsg/callback_help.html). - -**There are several different between wechat callback and the other providers** - -There is a new property that you need to supply when you init your webhook with Wechat. -Because this is a two step process. Once your server is verify with Wechat server. -They will just push data over to the url. So you need to run this once like so. - -First you need to run with the `inited:false` (default) - -```js -{ - secret: 'the-token-you-setup-with-wechat', - provider: 'wechat', - inited: false // this is default -} -``` - -Then re-config your webhook to run normal operation: - -```js -{ - secret: 'the-token-you-setup-with-wechat', - provider: 'wechat', - inited: true // default: false -} -``` - -There is a complete example in the *Wiki coming back soon* to demonstrate how you can do this automatically, -with additional module `fs-extra`, `nodemon` and `node-config`. - ---- - -If you are using function as your `cmd` property, there will only be two parameters supply, when execute your callback. - -```js - { - cmd: (result, opt) => { - // there is no ref - } - } +gitWebhook(config) ``` ### Full configuration properties | Property name | Description | Default | Type | | ------------- | ------------- | ---------| -----| -| dir | Where the git root directory is, default to where it gets call | `process.cwd()` | String | +| cwd | Where the git root directory is, default to where it gets call | `process.cwd()` | String | +| env | The node environment variable | `process.env` | Object | | secret | A secret key pass to encrypt data between github and your server | '' | String | | path | The path where the web hook call to your server | `/webhook` | String | | port | The port number where this callback server running on | `8081` | Integer | | branch | The branch where you will trigger action when received event from github. You can pass `*` wildcard to listen to all the branches | `refs/heads/master` | String | | cmd | The command to execute when callback happens. You can also pass this as a function (see above for signature) and especially useful when you use `*` for branch | `git pull origin master --no-edit` | String | -| inited | only available with `wechat` provider | `false` | Boolean | +| error | expect a function and you can handle the error yourself. Or enable DEBUG=git-webhook-ci:error to see the error | `() => {}` | Function | +| inited | only available for `wechat` provider | `false` | Boolean | ### Debug option @@ -189,63 +62,25 @@ Internally we use `debug` to track what's going on. So you can just pass the env If you do that, you will see a huge amount of info. All our debug flags are prefixed with `git-webhook-ci`, and here is the list of all the keys we use in this npm. -- git-webhook-ci:main +- git-webhook-ci:error - This is the most likely you will use, to see the error message +- git-webhook-ci:main - You will see the configuration option being pass to the main method - git-webhook-ci:gitlab - git-webhook-ci:github - git-webhook-ci:gitee - git-webhook-ci:wechat -- git-webhook-ci:demo (only in test) -- git-webhook-ci:test For example: ```sh - DEBUG=git-webhook-ci:main,git-webhook-ci:wechat node ./webhook.js + DEBUG=git-webhook-ci:main,git-webhook-ci:error node ./webhook.js ``` Then you will only see the main (top interface) and the Wechat internal debug messages. ## CLI -You can install this tools globally. - -```sh - $ npm install git-webhook-ci --global -``` - -Then you can call it from command line like so - -```sh - $ git-webhook-ci /path/to/your/git --secret secret-you-setup - -``` - -Or in your `package.json` - -```js - { - "scripts": { - "webhook": "git-webhook-ci ./ --secret secret-you-setup" - } - } -``` - -Then just run it with `npm run webhook` - -## HOW TO - -Check our *Wiki will be back shortly* for more information about how to setup your app. +We drop the cli support since V.2 due to the large amount of configuration options. You can try to write your own follow [this example](docs/02-cli.md) ## License -MIT © [NEWBRAN.CH](newbran.ch) - - -[npm-image]: https://badge.fury.io/js/git-webhook-ci.svg -[npm-url]: https://npmjs.org/package/git-webhook-ci -[travis-image]: https://travis-ci.org/NewbranLTD/git-webhook-ci.svg?branch=master -[travis-url]: https://travis-ci.org/NewbranLTD/git-webhook-ci -[daviddm-image]: https://david-dm.org/NewbranLTD/git-webhook-ci.svg?theme=shields.io -[daviddm-url]: https://david-dm.org/NewbranLTD/git-webhook-ci - -Power by [generator-nodex](https://gitlab.com/newbranltd/generator-nodex). +WTFPL - Joel Chu (c) 2021 diff --git a/README.md b/README.md index 84a13acb0caa7714686b6633683dfcb262255759..ea26485b1c9ca34f9e4c06d2cd3a245d97a3f2d6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ or Create a js file (normally on your project root directory). Let's call it `webhook.js`. ```js -const gitWebhook = require('git-webhook-ci'); +const gitWebhook = require('git-webhook-ci') const config = { "provider": "github", // from version 2 you MUST provide this "secret": "your-github-webhook-secret", @@ -34,21 +34,22 @@ const config = { } } -gitWebhook(config); +gitWebhook(config) ``` ### Full configuration properties | Property name | Description | Default | Type | | ------------- | ------------- | ---------| -----| -| dir | Where the git root directory is, default to where it gets call | `process.cwd()` | String | +| cwd | Where the git root directory is, default to where it gets call | `process.cwd()` | String | +| env | The node environment variable | `process.env` | Object | | secret | A secret key pass to encrypt data between github and your server | '' | String | | path | The path where the web hook call to your server | `/webhook` | String | | port | The port number where this callback server running on | `8081` | Integer | | branch | The branch where you will trigger action when received event from github. You can pass `*` wildcard to listen to all the branches | `refs/heads/master` | String | | cmd | The command to execute when callback happens. You can also pass this as a function (see above for signature) and especially useful when you use `*` for branch | `git pull origin master --no-edit` | String | -| error | expect a function and you can handle the error yourself. Or enable DEBUG=git-webhook-ci:error to see the error | -| inited | only available with `wechat` provider | `false` | Boolean | +| error | expect a function and you can handle the error yourself. Or enable DEBUG=git-webhook-ci:error to see the error | `() => {}` | Function | +| inited | only available for `wechat` provider | `false` | Boolean | ### Debug option diff --git a/docs/02-cli.md b/docs/02-cli.md index 46a870fabb0cd9f2791e3068d30f0176456937bf..5de65867b058763f66882142869f77ac752b6bf0 100644 --- a/docs/02-cli.md +++ b/docs/02-cli.md @@ -30,7 +30,7 @@ const flags: any = { alias: 'pr', default: 'github' }, - dir: { + pwd: { type: 'string', alias: 'd' }, @@ -75,6 +75,7 @@ const serve = function(p: string, flags: any): any { return gitWebhookCi(config) } + // Run it serve(cli.input[0], cli.flags) diff --git a/package.json b/package.json index 50c68109033eda3accc8b77ca79039ad7456fa7e..45b192c2b43a06a4c878fda549b335595e929056 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test": "ava", "test:p": "ava --verbose ./tests/partial.test.ts", "test:gitee": "DEBUG=git-webhook-ci:* ava --verbose ./tests/gitee.test.ts", - "test:run": "npm run ts -- run.ts", + "test:gitlab": "DEBUG=git-webhook-ci:* ava --verbose ./tests/gitlab.test.ts", + "test:run": "DEBUG=git-webhook-ci:* npm run ts -- run.ts", "lint": "eslint src/ --ext .js,.jsx,.ts,.tsx", "init": "tsc --init", "build": "tsc -p tsconfig.json", diff --git a/run.ts b/run.ts index 7edab484457e42932387aa776c13f60b2fa10656..b1830ab08af038fc6b21f501f69d66dce49348e2 100644 --- a/run.ts +++ b/run.ts @@ -1,5 +1,20 @@ -// ts-run.js try to use esbuild-register to run this without the compilation - -// @TODO +// /run.ts example // Here we just run a github instance for testing purpose +import { gitWebhookCi } from './src/main' + +const config = { + port: 4567, + provider: 'github', + secret: process.env.SECRET, // so we only pass this when run it + cmd: (args: []): void => { + console.log("Got callback") + console.log(args) + }, + error: (args: []): void => { + console.error('ERROR!') + console.error(args) + } +} + +gitWebhookCi(config) \ No newline at end of file diff --git a/src/provider/gitee/gitee-handler.ts b/src/provider/gitee/gitee-handler.ts index 8ee77326c1190d2c6660f60d915019bee313a75d..8fe436814a8cd67c5de3f83116d2c48ba96ad472 100644 --- a/src/provider/gitee/gitee-handler.ts +++ b/src/provider/gitee/gitee-handler.ts @@ -25,9 +25,7 @@ export class GiteeHandler extends BaseTools { .then(result => { this.resSuccess(req, res, result) }) - .catch(err => { - return callback(err) - }) + .catch(callback) } /** diff --git a/src/provider/gitlab/gitlab-handler.ts b/src/provider/gitlab/gitlab-handler.ts index 76b5492ae0eb2790c1dac5fb4e8e3bda543a5e43..8e5b3015efe3e2e728db0af37426aa8239857793 100644 --- a/src/provider/gitlab/gitlab-handler.ts +++ b/src/provider/gitlab/gitlab-handler.ts @@ -1,6 +1,9 @@ // src/provider/gitlab/gitlab-handler.ts import { BaseTools, configOptionType } from '../../lib/base-tools' +import { debugFn } from '../../lib' + +const debug = debugFn('git-webhook-ci:gitlab') export class GitlabHandler extends BaseTools { @@ -17,20 +20,11 @@ export class GitlabHandler extends BaseTools { * @return {null} nothing */ public handler(req: any, res: any, callback: any): any { - if (req.url.split('?').shift() !== this.options.path || req.method !== 'POST') { - return callback() // The only time we use the callback - } - this.parsePayload(req).then(payload => { - const headers = req.headers // JSON.stringify(req.headers, replacer); - // cache = null; - this.verify(payload, headers) - .then(payload => { - this.resSuccess(res, req, payload) - }) - .catch((err: any) => { - this.resError(res, err) - }) - }) + return super.handler(req, res, this.verify) + .then(result => { + this.resSuccess(req, res, result) + }) + .catch(callback) } /** @@ -39,15 +33,17 @@ export class GitlabHandler extends BaseTools { * @param {object} headers headers looking for the X-Gitlab-Event: Push Hook * @return {object} promise */ - private verify(payload: any, headers: any): Promise { - const eventName = 'X-Gitlab-Event'.toLowerCase() - const token = 'X-Gitlab-Token'.toLowerCase() + private verify(obj: any, opt: any): Promise { + const eventName = 'X-Gitlab-Event' + const token = 'X-Gitlab-Token' + const { header, payload } = obj // Console.log('headers', headers, typeof headers); return new Promise((resolver, rejecter) => { - if (headers[eventName] === 'Push Hook' && headers[token] === this.options.secret) { + if (header[eventName] === 'Push Hook' && header[token] === opt.secret) { resolver(payload) } else { - rejecter(new Error('Verify failed')) + debug(header) + rejecter(new Error('Gitlab verify failed')) } }) } @@ -57,7 +53,7 @@ export class GitlabHandler extends BaseTools { * @param {object} result the payload * @return {null} nothing */ - private resSuccess(res: any, req: any, payload: any): void { + private resSuccess(req: any, res: any, payload: any): void { res.writeHead(200, { 'content-type': 'application/json' }) res.end('{"ok":true}') // Check the result if this is what we wanted diff --git a/src/provider/gitlab/index.ts b/src/provider/gitlab/index.ts index dde557ccbff0a6d4dad3325c557c7700fa31669c..51c467898875e41f1cc186c1af24a7c652a488dc 100644 --- a/src/provider/gitlab/index.ts +++ b/src/provider/gitlab/index.ts @@ -6,7 +6,7 @@ import { createServer, configOptionType, debugFn } from '../../lib' const debug = debugFn('git-webhook-ci:gitlab') // main method -function createGitlabServer(config: configOptionType, opt: any, callback: any, errorHandler: any = ()=>{}) { +function createGitlabServer(config: configOptionType, callback: any, errorHandler: any = ()=>{}) { const gitlab: GitlabHandler = new GitlabHandler(config) gitlab.on('error', (err: any) => { @@ -18,7 +18,8 @@ function createGitlabServer(config: configOptionType, opt: any, callback: any, e gitlab.on('push', (result: any) => { const ref = result.payload.ref if (config.branch === '*' || config.branch === ref) { - callback(result, opt, ref) + debug(`Gitlab Call success`) + callback(result, config, ref) } else { errorHandler(ref) debug('Gitee webhook is not expecting this branch', ref) diff --git a/src/provider/index.ts b/src/provider/index.ts index 3c2fdcc64b93a5fa1d7ab9ff1cbf88d28e3a2111..2882c3e627dbe138f30cba6dd40ba70f48d3974c 100644 --- a/src/provider/index.ts +++ b/src/provider/index.ts @@ -22,7 +22,8 @@ export function getProvider(provider: string): any { case 'github': return githubWebhook case 'gitee': - default: return giteeWebhook + default: + throw new Error(`Unknown provider`) } } diff --git a/tests/fixtures/fake-callback.ts b/tests/fixtures/fake-callback.ts index 26f92eca675b9d2528a466a332f483a719129ee9..d52076016c779a97b73ae5a8c424c4840f817901 100644 --- a/tests/fixtures/fake-callback.ts +++ b/tests/fixtures/fake-callback.ts @@ -1,6 +1,7 @@ // tests/fixtures/fake-callback.ts import { getToken as giteeGetToken } from '../../src/provider/gitee/verify' import { header as giteeHeader, payload as giteePayload } from './gitee' +import { header as gitlabHeader, payload as gitlabPayload } from './gitlab' import { SECRET_KEY } from './secret' // this will create a fake callback to the webhook listener with specific payload @@ -17,6 +18,11 @@ export function getFakeData(provider: string): any { }), payload: giteePayload } + case 'gitlab': + return { + header: gitlabHeader, + payload: gitlabPayload + } default: throw new Error(`Unknown provider ${provider}`) diff --git a/tests/fixtures/gitlab.ts b/tests/fixtures/gitlab.ts new file mode 100644 index 0000000000000000000000000000000000000000..322532d778e99ebd05f384cc38d5854abc663192 --- /dev/null +++ b/tests/fixtures/gitlab.ts @@ -0,0 +1,77 @@ +import { SECRET_KEY } from "./secret" + +export const header = { + 'X-Gitlab-Event': 'Push Hook', + 'X-Gitlab-Token': SECRET_KEY +} + +export const payload = { + "object_kind": "push", + "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", + "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "ref": "refs/heads/master", + "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "user_id": 4, + "user_name": "John Smith", + "user_username": "jsmith", + "user_email": "john@example.com", + "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80", + "project_id": 15, + "project":{ + "id": 15, + "name":"Diaspora", + "description":"", + "web_url":"http://example.com/mike/diaspora", + "avatar_url":null, + "git_ssh_url":"git@example.com:mike/diaspora.git", + "git_http_url":"http://example.com/mike/diaspora.git", + "namespace":"Mike", + "visibility_level":0, + "path_with_namespace":"mike/diaspora", + "default_branch":"master", + "homepage":"http://example.com/mike/diaspora", + "url":"git@example.com:mike/diaspora.git", + "ssh_url":"git@example.com:mike/diaspora.git", + "http_url":"http://example.com/mike/diaspora.git" + }, + "repository":{ + "name": "Diaspora", + "url": "git@example.com:mike/diaspora.git", + "description": "", + "homepage": "http://example.com/mike/diaspora", + "git_http_url":"http://example.com/mike/diaspora.git", + "git_ssh_url":"git@example.com:mike/diaspora.git", + "visibility_level":0 + }, + "commits": [ + { + "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "message": "Update Catalan translation to e38cb41.\n\nSee https://gitlab.com/gitlab-org/gitlab for more information", + "title": "Update Catalan translation to e38cb41.", + "timestamp": "2011-12-12T14:27:31+02:00", + "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "author": { + "name": "Jordi Mallach", + "email": "jordi@softcatala.org" + }, + "added": ["CHANGELOG"], + "modified": ["app/controller/application.rb"], + "removed": [] + }, + { + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "message": "fixed readme", + "title": "fixed readme", + "timestamp": "2012-01-03T23:36:29+02:00", + "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "author": { + "name": "GitLab dev user", + "email": "gitlabdev@dv6700.(none)" + }, + "added": ["CHANGELOG"], + "modified": ["app/controller/application.rb"], + "removed": [] + } + ], + "total_commits_count": 4 +} \ No newline at end of file diff --git a/tests/gitee.test.ts b/tests/gitee.test.ts index 01141ed32969a519919b94bc9f2e20947fb85f6a..867f1626f12423b5f16e08dcbde6a18e5f3096bf 100644 --- a/tests/gitee.test.ts +++ b/tests/gitee.test.ts @@ -15,6 +15,7 @@ test.cb(`Should able to use a gitee config to listen to the webhook event`, t => request( gitWebhookCi({ + provider: 'gitee', secret: SECRET_KEY, cmd: (...args) => { diff --git a/tests/gitlab.test.ts b/tests/gitlab.test.ts index 797e7c60c052e68654c0da61309d7292cd71b637..00976f0dccf88a92477b36dbc394f76999e39b34 100644 --- a/tests/gitlab.test.ts +++ b/tests/gitlab.test.ts @@ -1,6 +1,33 @@ // tests/gitee.test.ts import test from 'ava' +import request from 'supertest' +import { gitWebhookCi } from '../src/main' +import { SECRET_KEY } from './fixtures/secret' +import { getFakeData } from './fixtures/fake-callback' -test.todo(`Should able to use a gitlab config to connect`) +test.cb(`Should able to use a gitlab config to connect`, t => { + t.plan(1) + + const { header, payload } = getFakeData('gitlab') + + request( + gitWebhookCi({ + provider: 'gitlab', + secret: SECRET_KEY, + cmd: function gitLabCallback() { + console.log(`TEST CMD CALLED`) + t.pass() + t.end() + } + }) + ) + .post('/webhook') + .set(header) + .send(payload) + .expect(200, () => { + // t.pass() <-- this pass is never called? + //t.end() + }) +})