github-webhook-handler 0.7.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/dependabot.yml +20 -0
- package/.github/workflows/test-and-release.yml +56 -0
- package/CHANGELOG.md +9 -0
- package/README.md +23 -18
- package/github-webhook-handler.d.ts +13 -9
- package/github-webhook-handler.js +82 -44
- package/package.json +101 -8
- package/test.js +249 -232
- package/.jshintrc +0 -59
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: 'github-actions'
|
|
4
|
+
directory: '/'
|
|
5
|
+
schedule:
|
|
6
|
+
interval: 'weekly'
|
|
7
|
+
commit-message:
|
|
8
|
+
prefix: 'chore'
|
|
9
|
+
include: 'scope'
|
|
10
|
+
cooldown:
|
|
11
|
+
default-days: 5
|
|
12
|
+
- package-ecosystem: 'npm'
|
|
13
|
+
directory: '/'
|
|
14
|
+
schedule:
|
|
15
|
+
interval: 'weekly'
|
|
16
|
+
commit-message:
|
|
17
|
+
prefix: 'chore'
|
|
18
|
+
include: 'scope'
|
|
19
|
+
cooldown:
|
|
20
|
+
default-days: 5
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Test & Maybe Release
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
|
|
4
|
+
jobs:
|
|
5
|
+
test:
|
|
6
|
+
strategy:
|
|
7
|
+
fail-fast: false
|
|
8
|
+
matrix:
|
|
9
|
+
node: [lts/*, current]
|
|
10
|
+
os: [macos-latest, ubuntu-latest, windows-latest]
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout Repository
|
|
14
|
+
uses: actions/checkout@v6
|
|
15
|
+
- name: Use Node.js ${{ matrix.node }}
|
|
16
|
+
uses: actions/setup-node@v6
|
|
17
|
+
with:
|
|
18
|
+
node-version: ${{ matrix.node }}
|
|
19
|
+
- name: Install Dependencies
|
|
20
|
+
run: npm install --no-progress
|
|
21
|
+
- name: Check build is up to date
|
|
22
|
+
run: |
|
|
23
|
+
npm run build
|
|
24
|
+
git diff --exit-code || (echo "::error::Build artifacts not committed. Run 'npm run build' and commit the changes." && exit 1)
|
|
25
|
+
- name: Run tests
|
|
26
|
+
run: npm test
|
|
27
|
+
|
|
28
|
+
release:
|
|
29
|
+
name: Release
|
|
30
|
+
needs: test
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
33
|
+
permissions:
|
|
34
|
+
contents: write
|
|
35
|
+
issues: write
|
|
36
|
+
pull-requests: write
|
|
37
|
+
id-token: write
|
|
38
|
+
steps:
|
|
39
|
+
- name: Checkout
|
|
40
|
+
uses: actions/checkout@v6
|
|
41
|
+
with:
|
|
42
|
+
fetch-depth: 0
|
|
43
|
+
- name: Setup Node.js
|
|
44
|
+
uses: actions/setup-node@v6
|
|
45
|
+
with:
|
|
46
|
+
node-version: lts/*
|
|
47
|
+
registry-url: 'https://registry.npmjs.org'
|
|
48
|
+
- name: Install dependencies
|
|
49
|
+
run: npm install --no-progress --no-package-lock --no-save
|
|
50
|
+
- name: Build
|
|
51
|
+
run: npm run build
|
|
52
|
+
- name: Release
|
|
53
|
+
env:
|
|
54
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
55
|
+
NPM_CONFIG_PROVENANCE: true
|
|
56
|
+
run: npx semantic-release
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
## [2.0.0](https://github.com/rvagg/github-webhook-handler/compare/v1.0.0...v2.0.0) (2026-01-28)
|
|
2
|
+
|
|
3
|
+
### ⚠ BREAKING CHANGES
|
|
4
|
+
|
|
5
|
+
* modernise, ESM, promises, update deps, GHA, auto-release (#58)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* modernise, ESM, promises, update deps, GHA, auto-release ([#58](https://github.com/rvagg/github-webhook-handler/issues/58)) ([bc84845](https://github.com/rvagg/github-webhook-handler/commit/bc848457d5c28110ac24335af520ec3b921355fc))
|
package/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# github-webhook-handler
|
|
2
2
|
|
|
3
|
-
[](https://nodei.co/npm/github-webhook-handler/)
|
|
3
|
+
[](https://nodei.co/npm/github-webhook-handler/)
|
|
5
4
|
|
|
6
5
|
GitHub allows you to register **[Webhooks](https://developer.github.com/webhooks/)** for your repositories. Each time an event occurs on your repository, whether it be pushing code, filling issues or creating pull requests, the webhook address you register can be configured to be pinged with details.
|
|
7
6
|
|
|
8
7
|
This library is a small handler (or "middleware" if you must) for Node.js web servers that handles all the logic of receiving and verifying webhook requests from GitHub.
|
|
9
8
|
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
Node.js >= 20
|
|
12
|
+
|
|
10
13
|
## Tips
|
|
11
14
|
|
|
12
15
|
In Github Webhooks settings, Content type must be `application/json`.
|
|
@@ -16,28 +19,29 @@ In Github Webhooks settings, Content type must be `application/json`.
|
|
|
16
19
|
## Example
|
|
17
20
|
|
|
18
21
|
```js
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
import http from 'node:http'
|
|
23
|
+
import createHandler from 'github-webhook-handler'
|
|
24
|
+
|
|
25
|
+
const handler = createHandler({ path: '/webhook', secret: 'myhashsecret' })
|
|
22
26
|
|
|
23
|
-
http.createServer(
|
|
24
|
-
handler(req, res,
|
|
27
|
+
http.createServer((req, res) => {
|
|
28
|
+
handler(req, res, (err) => {
|
|
25
29
|
res.statusCode = 404
|
|
26
30
|
res.end('no such location')
|
|
27
31
|
})
|
|
28
32
|
}).listen(7777)
|
|
29
33
|
|
|
30
|
-
handler.on('error',
|
|
34
|
+
handler.on('error', (err) => {
|
|
31
35
|
console.error('Error:', err.message)
|
|
32
36
|
})
|
|
33
37
|
|
|
34
|
-
handler.on('push',
|
|
38
|
+
handler.on('push', (event) => {
|
|
35
39
|
console.log('Received a push event for %s to %s',
|
|
36
40
|
event.payload.repository.name,
|
|
37
41
|
event.payload.ref)
|
|
38
42
|
})
|
|
39
43
|
|
|
40
|
-
handler.on('issues',
|
|
44
|
+
handler.on('issues', (event) => {
|
|
41
45
|
console.log('Received an issue event for %s action=%s: #%d %s',
|
|
42
46
|
event.payload.repository.name,
|
|
43
47
|
event.payload.action,
|
|
@@ -46,11 +50,11 @@ handler.on('issues', function (event) {
|
|
|
46
50
|
})
|
|
47
51
|
```
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
For multiple handlers, see [multiple-handlers-issue](https://github.com/rvagg/github-webhook-handler/pull/22#issuecomment-274301907).
|
|
50
54
|
|
|
51
55
|
## API
|
|
52
56
|
|
|
53
|
-
github-webhook-handler exports a single function
|
|
57
|
+
github-webhook-handler exports a single function. Use this function to *create* a webhook handler by passing in an *options* object. Your options object should contain:
|
|
54
58
|
|
|
55
59
|
* `"path"`: the complete case sensitive path/route to match when looking at `req.url` for incoming requests. Any request not matching this path will cause the callback function to the handler to be called (sometimes called the `next` handler).
|
|
56
60
|
* `"secret"`: this is a hash key used for creating the SHA-1 HMAC signature of the JSON blob sent by GitHub. You should register the same secret key with GitHub. Any request not delivering a `X-Hub-Signature` that matches the signature generated using this key against the blob will be rejected and cause an `'error'` event (also the callback will be called with an `Error` object).
|
|
@@ -65,14 +69,15 @@ See the [GitHub Webhooks documentation](https://developer.github.com/webhooks/)
|
|
|
65
69
|
Included in the distribution is an *events.json* file which maps the event names to descriptions taken from the API:
|
|
66
70
|
|
|
67
71
|
```js
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
import events from 'github-webhook-handler/events.json' with { type: 'json' }
|
|
73
|
+
|
|
74
|
+
for (const [event, description] of Object.entries(events)) {
|
|
75
|
+
console.log(event, '=', description)
|
|
76
|
+
}
|
|
72
77
|
```
|
|
73
78
|
|
|
74
|
-
Additionally, there is a special `'*'`
|
|
79
|
+
Additionally, there is a special `'*'` event you can listen to in order to receive _everything_.
|
|
75
80
|
|
|
76
81
|
## License
|
|
77
82
|
|
|
78
|
-
**github-webhook-handler** is Copyright (c) 2014 Rod Vagg
|
|
83
|
+
**github-webhook-handler** is Copyright (c) 2014 Rod Vagg and licensed under the MIT License. All rights not explicitly granted in the MIT License are reserved. See the included [LICENSE.md](./LICENSE.md) file for more details.
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
/// <reference types="node" />
|
|
2
2
|
|
|
3
|
-
import { IncomingMessage, ServerResponse } from
|
|
4
|
-
import { EventEmitter } from
|
|
3
|
+
import { IncomingMessage, ServerResponse } from 'node:http'
|
|
4
|
+
import { EventEmitter } from 'node:events'
|
|
5
5
|
|
|
6
6
|
interface CreateHandlerOptions {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
path: string
|
|
8
|
+
secret: string
|
|
9
|
+
events?: string | string[]
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
interface
|
|
13
|
-
|
|
12
|
+
interface Handler extends EventEmitter {
|
|
13
|
+
(req: IncomingMessage, res: ServerResponse, callback: (err?: Error) => void): void
|
|
14
|
+
sign(data: string | Buffer): string
|
|
15
|
+
verify(signature: string, data: string | Buffer): boolean
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
declare function createHandler (options: CreateHandlerOptions | CreateHandlerOptions[]): Handler
|
|
19
|
+
|
|
20
|
+
export default createHandler
|
|
@@ -1,29 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
throw new TypeError('must provide an options object')
|
|
1
|
+
import { EventEmitter } from 'node:events'
|
|
2
|
+
import crypto from 'node:crypto'
|
|
3
|
+
import bl from 'bl'
|
|
4
|
+
|
|
5
|
+
function findHandler (url, arr) {
|
|
6
|
+
if (!Array.isArray(arr)) {
|
|
7
|
+
return arr
|
|
8
|
+
}
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
let ret = arr[0]
|
|
11
|
+
for (let i = 0; i < arr.length; i++) {
|
|
12
|
+
if (url === arr[i].path) {
|
|
13
|
+
ret = arr[i]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return ret
|
|
18
|
+
}
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
function checkType (options) {
|
|
21
|
+
if (typeof options !== 'object') {
|
|
22
|
+
throw new TypeError('must provide an options object')
|
|
23
|
+
}
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
if (typeof options.path !== 'string') {
|
|
26
|
+
throw new TypeError("must provide a 'path' option")
|
|
27
|
+
}
|
|
18
28
|
|
|
19
|
-
if (typeof options.
|
|
20
|
-
|
|
29
|
+
if (typeof options.secret !== 'string') {
|
|
30
|
+
throw new TypeError("must provide a 'secret' option")
|
|
31
|
+
}
|
|
32
|
+
}
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
function create (initOptions) {
|
|
35
|
+
let options
|
|
36
|
+
if (Array.isArray(initOptions)) {
|
|
37
|
+
for (let i = 0; i < initOptions.length; i++) {
|
|
38
|
+
checkType(initOptions[i])
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
checkType(initOptions)
|
|
42
|
+
}
|
|
24
43
|
|
|
25
|
-
|
|
26
|
-
handler.__proto__ = EventEmitter.prototype
|
|
44
|
+
Object.setPrototypeOf(handler, EventEmitter.prototype)
|
|
27
45
|
EventEmitter.call(handler)
|
|
28
46
|
|
|
29
47
|
handler.sign = sign
|
|
@@ -31,54 +49,74 @@ function create (options) {
|
|
|
31
49
|
|
|
32
50
|
return handler
|
|
33
51
|
|
|
34
|
-
|
|
35
52
|
function sign (data) {
|
|
36
|
-
return
|
|
53
|
+
return `sha1=${crypto.createHmac('sha1', options.secret).update(data).digest('hex')}`
|
|
37
54
|
}
|
|
38
55
|
|
|
39
56
|
function verify (signature, data) {
|
|
40
|
-
|
|
57
|
+
const sig = Buffer.from(signature)
|
|
58
|
+
const signed = Buffer.from(sign(data))
|
|
59
|
+
if (sig.length !== signed.length) {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
return crypto.timingSafeEqual(sig, signed)
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
function handler (req, res, callback) {
|
|
44
|
-
|
|
66
|
+
let events
|
|
67
|
+
|
|
68
|
+
options = findHandler(req.url, initOptions)
|
|
69
|
+
|
|
70
|
+
if (typeof options.events === 'string' && options.events !== '*') {
|
|
71
|
+
events = [options.events]
|
|
72
|
+
} else if (Array.isArray(options.events) && options.events.indexOf('*') === -1) {
|
|
73
|
+
events = options.events
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (req.url !== options.path || req.method !== 'POST') {
|
|
45
77
|
return callback()
|
|
78
|
+
}
|
|
46
79
|
|
|
47
80
|
function hasError (msg) {
|
|
48
81
|
res.writeHead(400, { 'content-type': 'application/json' })
|
|
49
82
|
res.end(JSON.stringify({ error: msg }))
|
|
50
83
|
|
|
51
|
-
|
|
84
|
+
const err = new Error(msg)
|
|
52
85
|
|
|
53
86
|
handler.emit('error', err, req)
|
|
54
87
|
callback(err)
|
|
55
88
|
}
|
|
56
89
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
90
|
+
const sig = req.headers['x-hub-signature']
|
|
91
|
+
const event = req.headers['x-github-event']
|
|
92
|
+
const id = req.headers['x-github-delivery']
|
|
60
93
|
|
|
61
|
-
if (!sig)
|
|
94
|
+
if (!sig) {
|
|
62
95
|
return hasError('No X-Hub-Signature found on request')
|
|
96
|
+
}
|
|
63
97
|
|
|
64
|
-
if (!event)
|
|
98
|
+
if (!event) {
|
|
65
99
|
return hasError('No X-Github-Event found on request')
|
|
100
|
+
}
|
|
66
101
|
|
|
67
|
-
if (!id)
|
|
102
|
+
if (!id) {
|
|
68
103
|
return hasError('No X-Github-Delivery found on request')
|
|
104
|
+
}
|
|
69
105
|
|
|
70
|
-
if (events && events.indexOf(event)
|
|
106
|
+
if (events && events.indexOf(event) === -1) {
|
|
71
107
|
return hasError('X-Github-Event is not acceptable')
|
|
108
|
+
}
|
|
72
109
|
|
|
73
|
-
req.pipe(bl(
|
|
110
|
+
req.pipe(bl((err, data) => {
|
|
74
111
|
if (err) {
|
|
75
112
|
return hasError(err.message)
|
|
76
113
|
}
|
|
77
114
|
|
|
78
|
-
|
|
115
|
+
let obj
|
|
79
116
|
|
|
80
|
-
if (!verify(sig, data))
|
|
117
|
+
if (!verify(sig, data)) {
|
|
81
118
|
return hasError('X-Hub-Signature does not match blob signature')
|
|
119
|
+
}
|
|
82
120
|
|
|
83
121
|
try {
|
|
84
122
|
obj = JSON.parse(data.toString())
|
|
@@ -89,13 +127,14 @@ function create (options) {
|
|
|
89
127
|
res.writeHead(200, { 'content-type': 'application/json' })
|
|
90
128
|
res.end('{"ok":true}')
|
|
91
129
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
130
|
+
const emitData = {
|
|
131
|
+
event,
|
|
132
|
+
id,
|
|
133
|
+
payload: obj,
|
|
134
|
+
protocol: req.protocol,
|
|
135
|
+
host: req.headers.host,
|
|
136
|
+
url: req.url,
|
|
137
|
+
path: options.path
|
|
99
138
|
}
|
|
100
139
|
|
|
101
140
|
handler.emit(event, emitData)
|
|
@@ -104,5 +143,4 @@ function create (options) {
|
|
|
104
143
|
}
|
|
105
144
|
}
|
|
106
145
|
|
|
107
|
-
|
|
108
|
-
module.exports = create
|
|
146
|
+
export default create
|
package/package.json
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-webhook-handler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Web handler / middleware for processing GitHub Webhooks",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "github-webhook-handler.js",
|
|
7
|
+
"exports": "./github-webhook-handler.js",
|
|
6
8
|
"types": "github-webhook-handler.d.ts",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20"
|
|
11
|
+
},
|
|
7
12
|
"scripts": {
|
|
8
|
-
"
|
|
13
|
+
"lint": "standard",
|
|
14
|
+
"build": "true",
|
|
15
|
+
"test:unit": "node --test test.js",
|
|
16
|
+
"test": "npm run lint && npm run test:unit"
|
|
9
17
|
},
|
|
10
18
|
"keywords": [
|
|
11
19
|
"github",
|
|
@@ -18,13 +26,98 @@
|
|
|
18
26
|
},
|
|
19
27
|
"license": "MIT",
|
|
20
28
|
"dependencies": {
|
|
21
|
-
"bl": "
|
|
22
|
-
"buffer-equal-constant-time": "~1.0.1"
|
|
29
|
+
"bl": "^6.1.6"
|
|
23
30
|
},
|
|
24
31
|
"devDependencies": {
|
|
25
|
-
"@
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
32
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
33
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
34
|
+
"@semantic-release/git": "^10.0.1",
|
|
35
|
+
"@semantic-release/github": "^12.0.2",
|
|
36
|
+
"@semantic-release/npm": "^13.1.3",
|
|
37
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
38
|
+
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
39
|
+
"semantic-release": "^25.0.2",
|
|
40
|
+
"standard": "^17.1.2"
|
|
41
|
+
},
|
|
42
|
+
"release": {
|
|
43
|
+
"branches": [
|
|
44
|
+
"master"
|
|
45
|
+
],
|
|
46
|
+
"plugins": [
|
|
47
|
+
[
|
|
48
|
+
"@semantic-release/commit-analyzer",
|
|
49
|
+
{
|
|
50
|
+
"preset": "conventionalcommits",
|
|
51
|
+
"releaseRules": [
|
|
52
|
+
{
|
|
53
|
+
"breaking": true,
|
|
54
|
+
"release": "major"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"revert": true,
|
|
58
|
+
"release": "patch"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "feat",
|
|
62
|
+
"release": "minor"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"type": "fix",
|
|
66
|
+
"release": "patch"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"type": "chore",
|
|
70
|
+
"release": "patch"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"type": "docs",
|
|
74
|
+
"release": "patch"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"type": "test",
|
|
78
|
+
"release": "patch"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"scope": "no-release",
|
|
82
|
+
"release": false
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
"@semantic-release/release-notes-generator",
|
|
89
|
+
{
|
|
90
|
+
"preset": "conventionalcommits",
|
|
91
|
+
"presetConfig": {
|
|
92
|
+
"types": [
|
|
93
|
+
{
|
|
94
|
+
"type": "feat",
|
|
95
|
+
"section": "Features"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"type": "fix",
|
|
99
|
+
"section": "Bug Fixes"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"type": "chore",
|
|
103
|
+
"section": "Trivial Changes"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"type": "docs",
|
|
107
|
+
"section": "Trivial Changes"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"type": "test",
|
|
111
|
+
"section": "Tests"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"@semantic-release/changelog",
|
|
118
|
+
"@semantic-release/npm",
|
|
119
|
+
"@semantic-release/github",
|
|
120
|
+
"@semantic-release/git"
|
|
121
|
+
]
|
|
29
122
|
}
|
|
30
123
|
}
|
package/test.js
CHANGED
|
@@ -1,333 +1,350 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
import { PassThrough } from 'node:stream'
|
|
5
|
+
import handler from './github-webhook-handler.js'
|
|
7
6
|
|
|
8
7
|
function signBlob (key, blob) {
|
|
9
|
-
return 'sha1
|
|
10
|
-
crypto.createHmac('sha1', key).update(blob).digest('hex')
|
|
8
|
+
return `sha1=${crypto.createHmac('sha1', key).update(blob).digest('hex')}`
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
|
|
14
11
|
function mkReq (url, method) {
|
|
15
|
-
|
|
12
|
+
const req = new PassThrough()
|
|
16
13
|
req.method = method || 'POST'
|
|
17
14
|
req.url = url
|
|
18
15
|
req.headers = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
'x-hub-signature': 'bogus',
|
|
17
|
+
'x-github-event': 'bogus',
|
|
18
|
+
'x-github-delivery': 'bogus'
|
|
22
19
|
}
|
|
23
20
|
return req
|
|
24
21
|
}
|
|
25
22
|
|
|
26
|
-
|
|
27
23
|
function mkRes () {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
24
|
+
const res = {
|
|
25
|
+
writeHead (statusCode, headers) {
|
|
26
|
+
res.$statusCode = statusCode
|
|
27
|
+
res.$headers = headers
|
|
28
|
+
},
|
|
29
|
+
end (content) {
|
|
30
|
+
res.$end = content
|
|
31
|
+
}
|
|
37
32
|
}
|
|
38
|
-
|
|
39
33
|
return res
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
t.throws(handler, /must provide an options object/, 'throws if no options')
|
|
49
|
-
|
|
50
|
-
t.throws(handler.bind(null, {}), /must provide a 'path' option/, 'throws if no path option')
|
|
51
|
-
|
|
52
|
-
t.throws(handler.bind(null, { path: '/' }), /must provide a 'secret' option/, 'throws if no secret option')
|
|
36
|
+
test('handler without full options throws', () => {
|
|
37
|
+
assert.strictEqual(typeof handler, 'function', 'handler exports a function')
|
|
38
|
+
assert.throws(() => handler(), /must provide an options object/, 'throws if no options')
|
|
39
|
+
assert.throws(() => handler({}), /must provide a 'path' option/, 'throws if no path option')
|
|
40
|
+
assert.throws(() => handler({ path: '/' }), /must provide a 'secret' option/, 'throws if no secret option')
|
|
53
41
|
})
|
|
54
42
|
|
|
43
|
+
test('handler without full options throws in array', () => {
|
|
44
|
+
assert.throws(() => handler([{}]), /must provide a 'path' option/, 'throws if no path option')
|
|
45
|
+
assert.throws(() => handler([{ path: '/' }]), /must provide a 'secret' option/, 'throws if no secret option')
|
|
46
|
+
})
|
|
55
47
|
|
|
56
|
-
test('handler ignores invalid urls',
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
t.plan(6)
|
|
48
|
+
test('handler ignores invalid urls', async () => {
|
|
49
|
+
const options = { path: '/some/url', secret: 'bogus' }
|
|
50
|
+
const h = handler(options)
|
|
61
51
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
await new Promise((resolve) => {
|
|
53
|
+
h(mkReq('/'), mkRes(), (err) => {
|
|
54
|
+
assert.ifError(err)
|
|
55
|
+
resolve()
|
|
56
|
+
})
|
|
65
57
|
})
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
await new Promise((resolve) => {
|
|
60
|
+
h(mkReq('/some/url/'), mkRes(), (err) => {
|
|
61
|
+
assert.ifError(err)
|
|
62
|
+
resolve()
|
|
63
|
+
})
|
|
71
64
|
})
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
await new Promise((resolve) => {
|
|
67
|
+
h(mkReq('/some'), mkRes(), (err) => {
|
|
68
|
+
assert.ifError(err)
|
|
69
|
+
resolve()
|
|
70
|
+
})
|
|
77
71
|
})
|
|
78
72
|
})
|
|
79
73
|
|
|
80
|
-
test('handler
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
test('handler ignores non-POST requests', async () => {
|
|
75
|
+
const options = { path: '/some/url', secret: 'bogus' }
|
|
76
|
+
const h = handler(options)
|
|
83
77
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
await new Promise((resolve) => {
|
|
79
|
+
h(mkReq('/some/url', 'GET'), mkRes(), (err) => {
|
|
80
|
+
assert.ifError(err)
|
|
81
|
+
resolve()
|
|
82
|
+
})
|
|
89
83
|
})
|
|
90
84
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
await new Promise((resolve) => {
|
|
86
|
+
h(mkReq('/some/url?test=param', 'GET'), mkRes(), (err) => {
|
|
87
|
+
assert.ifError(err)
|
|
88
|
+
resolve()
|
|
89
|
+
})
|
|
94
90
|
})
|
|
95
91
|
})
|
|
96
92
|
|
|
97
|
-
test('handler accepts valid urls',
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
t.plan(1)
|
|
102
|
-
|
|
103
|
-
h(mkReq('/some/url'), mkRes(), function (err) {
|
|
104
|
-
t.error(err)
|
|
105
|
-
t.fail(false, 'should not call')
|
|
106
|
-
})
|
|
93
|
+
test('handler accepts valid urls', async () => {
|
|
94
|
+
const options = { path: '/some/url', secret: 'bogus' }
|
|
95
|
+
const h = handler(options)
|
|
107
96
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
97
|
+
let callbackCalled = false
|
|
98
|
+
h(mkReq('/some/url'), mkRes(), () => {
|
|
99
|
+
callbackCalled = true
|
|
111
100
|
})
|
|
112
101
|
|
|
113
|
-
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
103
|
+
assert.strictEqual(callbackCalled, false, 'callback should not be called for valid POST')
|
|
114
104
|
})
|
|
115
105
|
|
|
106
|
+
test('handler accepts valid urls in Array', async () => {
|
|
107
|
+
const options = [{ path: '/some/url', secret: 'bogus' }, { path: '/someOther/url', secret: 'bogus' }]
|
|
108
|
+
const h = handler(options)
|
|
116
109
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
, 'a string equal to the event' : 'bogus'
|
|
121
|
-
, 'a string equal to *' : '*'
|
|
122
|
-
, 'an array containing the event' : ['bogus']
|
|
123
|
-
, 'an array containing *' : ['not-bogus', '*']
|
|
124
|
-
}
|
|
125
|
-
, unacceptableEvents = {
|
|
126
|
-
'a string not equal to the event or *' : 'not-bogus'
|
|
127
|
-
, 'an array not containing the event or *' : ['not-bogus']
|
|
128
|
-
}
|
|
129
|
-
, acceptable = Object.keys(acceptableEvents)
|
|
130
|
-
, unacceptable = Object.keys(unacceptableEvents)
|
|
131
|
-
, acceptableTests = acceptable.map(function (events) {
|
|
132
|
-
return acceptableReq.bind(null, events)
|
|
133
|
-
})
|
|
134
|
-
, unacceptableTests = unacceptable.map(function (events) {
|
|
135
|
-
return unacceptableReq.bind(null, events)
|
|
136
|
-
})
|
|
110
|
+
let callbackCalled = false
|
|
111
|
+
h(mkReq('/some/url'), mkRes(), () => { callbackCalled = true })
|
|
112
|
+
h(mkReq('/someOther/url'), mkRes(), () => { callbackCalled = true })
|
|
137
113
|
|
|
138
|
-
|
|
139
|
-
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
115
|
+
assert.strictEqual(callbackCalled, false, 'callback should not be called for valid POSTs')
|
|
116
|
+
})
|
|
140
117
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
118
|
+
test('handler can reject events', async () => {
|
|
119
|
+
const acceptableEvents = {
|
|
120
|
+
undefined,
|
|
121
|
+
'a string equal to the event': 'bogus',
|
|
122
|
+
'a string equal to *': '*',
|
|
123
|
+
'an array containing the event': ['bogus'],
|
|
124
|
+
'an array containing *': ['not-bogus', '*']
|
|
125
|
+
}
|
|
126
|
+
const unacceptableEvents = {
|
|
127
|
+
'a string not equal to the event or *': 'not-bogus',
|
|
128
|
+
'an array not containing the event or *': ['not-bogus']
|
|
129
|
+
}
|
|
147
130
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
131
|
+
for (const [desc, events] of Object.entries(acceptableEvents)) {
|
|
132
|
+
const h = handler({
|
|
133
|
+
path: '/some/url',
|
|
134
|
+
secret: 'bogus',
|
|
135
|
+
events
|
|
151
136
|
})
|
|
152
137
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
138
|
+
let callbackCalled = false
|
|
139
|
+
h(mkReq('/some/url'), mkRes(), () => { callbackCalled = true })
|
|
140
|
+
|
|
141
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
142
|
+
assert.strictEqual(callbackCalled, false, `accepted because options.events was ${desc}`)
|
|
157
143
|
}
|
|
158
144
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
145
|
+
for (const [desc, events] of Object.entries(unacceptableEvents)) {
|
|
146
|
+
const h = handler({
|
|
147
|
+
path: '/some/url',
|
|
148
|
+
secret: 'bogus',
|
|
149
|
+
events
|
|
164
150
|
})
|
|
165
151
|
|
|
166
|
-
h.on('error',
|
|
152
|
+
h.on('error', () => {})
|
|
167
153
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
154
|
+
await new Promise((resolve) => {
|
|
155
|
+
h(mkReq('/some/url'), mkRes(), (err) => {
|
|
156
|
+
assert.ok(err, `rejected because options.events was ${desc}`)
|
|
157
|
+
resolve()
|
|
158
|
+
})
|
|
171
159
|
})
|
|
172
160
|
}
|
|
173
161
|
})
|
|
174
162
|
|
|
163
|
+
test('handler is an EventEmitter', () => {
|
|
164
|
+
const h = handler({ path: '/', secret: 'bogus' })
|
|
175
165
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
var h = handler({ path: '/', secret: 'bogus' })
|
|
181
|
-
|
|
182
|
-
t.equal(typeof h.on, 'function', 'has h.on()')
|
|
183
|
-
t.equal(typeof h.emit, 'function', 'has h.emit()')
|
|
184
|
-
t.equal(typeof h.removeListener, 'function', 'has h.removeListener()')
|
|
185
|
-
|
|
186
|
-
h.on('ping', function (pong) {
|
|
187
|
-
t.equal(pong, 'pong', 'got event')
|
|
188
|
-
})
|
|
166
|
+
assert.strictEqual(typeof h.on, 'function', 'has h.on()')
|
|
167
|
+
assert.strictEqual(typeof h.emit, 'function', 'has h.emit()')
|
|
168
|
+
assert.strictEqual(typeof h.removeListener, 'function', 'has h.removeListener()')
|
|
189
169
|
|
|
170
|
+
let received
|
|
171
|
+
h.on('ping', (pong) => { received = pong })
|
|
190
172
|
h.emit('ping', 'pong')
|
|
173
|
+
assert.strictEqual(received, 'pong', 'got event')
|
|
191
174
|
|
|
192
|
-
|
|
175
|
+
assert.throws(() => h.emit('error', new Error('threw an error')), /threw an error/, 'acts like an EE')
|
|
193
176
|
})
|
|
194
177
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
, h = handler({ path: '/', secret: 'bogus' })
|
|
202
|
-
, req = mkReq('/')
|
|
203
|
-
, res = mkRes()
|
|
178
|
+
test('handler accepts a signed blob', async () => {
|
|
179
|
+
const obj = { some: 'github', object: 'with', properties: true }
|
|
180
|
+
const json = JSON.stringify(obj)
|
|
181
|
+
const h = handler({ path: '/', secret: 'bogus' })
|
|
182
|
+
const req = mkReq('/')
|
|
183
|
+
const res = mkRes()
|
|
204
184
|
|
|
205
185
|
req.headers['x-hub-signature'] = signBlob('bogus', json)
|
|
206
|
-
req.headers['x-github-event']
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
186
|
+
req.headers['x-github-event'] = 'push'
|
|
187
|
+
|
|
188
|
+
const eventPromise = new Promise((resolve) => {
|
|
189
|
+
h.on('push', (event) => {
|
|
190
|
+
assert.deepStrictEqual(event, {
|
|
191
|
+
event: 'push',
|
|
192
|
+
id: 'bogus',
|
|
193
|
+
payload: obj,
|
|
194
|
+
url: '/',
|
|
195
|
+
host: undefined,
|
|
196
|
+
protocol: undefined,
|
|
197
|
+
path: '/'
|
|
198
|
+
})
|
|
199
|
+
assert.strictEqual(res.$statusCode, 200, 'correct status code')
|
|
200
|
+
assert.deepStrictEqual(res.$headers, { 'content-type': 'application/json' })
|
|
201
|
+
assert.strictEqual(res.$end, '{"ok":true}', 'got correct content')
|
|
202
|
+
resolve()
|
|
203
|
+
})
|
|
213
204
|
})
|
|
214
205
|
|
|
215
|
-
h(req, res,
|
|
216
|
-
|
|
217
|
-
t.fail(true, 'should not get here!')
|
|
206
|
+
h(req, res, () => {
|
|
207
|
+
assert.fail('should not get here')
|
|
218
208
|
})
|
|
219
209
|
|
|
220
|
-
process.nextTick(
|
|
221
|
-
|
|
222
|
-
})
|
|
210
|
+
process.nextTick(() => req.end(json))
|
|
211
|
+
await eventPromise
|
|
223
212
|
})
|
|
224
213
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
, h = handler({ path: '/', secret: 'bogus' })
|
|
232
|
-
, req = mkReq('/')
|
|
233
|
-
, res = mkRes()
|
|
214
|
+
test('handler accepts multi blob in Array', async () => {
|
|
215
|
+
const obj = { some: 'github', object: 'with', properties: true }
|
|
216
|
+
const json = JSON.stringify(obj)
|
|
217
|
+
const h = handler([{ path: '/', secret: 'bogus' }, { path: '/some/url', secret: 'bogus' }])
|
|
218
|
+
const req = mkReq('/some/url')
|
|
219
|
+
const res = mkRes()
|
|
234
220
|
|
|
235
221
|
req.headers['x-hub-signature'] = signBlob('bogus', json)
|
|
236
|
-
req.headers['x-github-event']
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
222
|
+
req.headers['x-github-event'] = 'push'
|
|
223
|
+
|
|
224
|
+
const eventPromise = new Promise((resolve) => {
|
|
225
|
+
h.on('push', (event) => {
|
|
226
|
+
assert.deepStrictEqual(event, {
|
|
227
|
+
event: 'push',
|
|
228
|
+
id: 'bogus',
|
|
229
|
+
payload: obj,
|
|
230
|
+
url: '/some/url',
|
|
231
|
+
host: undefined,
|
|
232
|
+
protocol: undefined,
|
|
233
|
+
path: '/some/url'
|
|
234
|
+
})
|
|
235
|
+
assert.strictEqual(res.$statusCode, 200, 'correct status code')
|
|
236
|
+
assert.deepStrictEqual(res.$headers, { 'content-type': 'application/json' })
|
|
237
|
+
assert.strictEqual(res.$end, '{"ok":true}', 'got correct content')
|
|
238
|
+
resolve()
|
|
239
|
+
})
|
|
240
240
|
})
|
|
241
241
|
|
|
242
|
-
h
|
|
243
|
-
|
|
244
|
-
t.equal(res.$statusCode, 200, 'correct status code')
|
|
245
|
-
t.deepEqual(res.$headers, { 'content-type': 'application/json' })
|
|
246
|
-
t.equal(res.$end, '{"ok":true}', 'got correct content')
|
|
242
|
+
h(req, res, () => {
|
|
243
|
+
assert.fail('should not get here')
|
|
247
244
|
})
|
|
248
245
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
})
|
|
246
|
+
process.nextTick(() => req.end(json))
|
|
247
|
+
await eventPromise
|
|
248
|
+
})
|
|
253
249
|
|
|
254
|
-
|
|
255
|
-
|
|
250
|
+
test('handler accepts a signed blob with alt event', async () => {
|
|
251
|
+
const obj = { some: 'github', object: 'with', properties: true }
|
|
252
|
+
const json = JSON.stringify(obj)
|
|
253
|
+
const h = handler({ path: '/', secret: 'bogus' })
|
|
254
|
+
const req = mkReq('/')
|
|
255
|
+
const res = mkRes()
|
|
256
|
+
|
|
257
|
+
req.headers['x-hub-signature'] = signBlob('bogus', json)
|
|
258
|
+
req.headers['x-github-event'] = 'issue'
|
|
259
|
+
|
|
260
|
+
h.on('push', () => assert.fail('should not get here'))
|
|
261
|
+
|
|
262
|
+
const eventPromise = new Promise((resolve) => {
|
|
263
|
+
h.on('issue', (event) => {
|
|
264
|
+
assert.deepStrictEqual(event, {
|
|
265
|
+
event: 'issue',
|
|
266
|
+
id: 'bogus',
|
|
267
|
+
payload: obj,
|
|
268
|
+
url: '/',
|
|
269
|
+
host: undefined,
|
|
270
|
+
protocol: undefined,
|
|
271
|
+
path: '/'
|
|
272
|
+
})
|
|
273
|
+
assert.strictEqual(res.$statusCode, 200, 'correct status code')
|
|
274
|
+
assert.deepStrictEqual(res.$headers, { 'content-type': 'application/json' })
|
|
275
|
+
assert.strictEqual(res.$end, '{"ok":true}', 'got correct content')
|
|
276
|
+
resolve()
|
|
277
|
+
})
|
|
256
278
|
})
|
|
257
|
-
})
|
|
258
279
|
|
|
280
|
+
h(req, res, () => {
|
|
281
|
+
assert.fail('should not get here')
|
|
282
|
+
})
|
|
259
283
|
|
|
260
|
-
|
|
261
|
-
|
|
284
|
+
process.nextTick(() => req.end(json))
|
|
285
|
+
await eventPromise
|
|
286
|
+
})
|
|
262
287
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
288
|
+
test('handler rejects a badly signed blob', async () => {
|
|
289
|
+
const obj = { some: 'github', object: 'with', properties: true }
|
|
290
|
+
const json = JSON.stringify(obj)
|
|
291
|
+
const h = handler({ path: '/', secret: 'bogus' })
|
|
292
|
+
const req = mkReq('/')
|
|
293
|
+
const res = mkRes()
|
|
268
294
|
|
|
269
295
|
req.headers['x-hub-signature'] = signBlob('bogus', json)
|
|
270
|
-
// break signage by a tiny bit
|
|
271
296
|
req.headers['x-hub-signature'] = '0' + req.headers['x-hub-signature'].substring(1)
|
|
272
297
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
298
|
+
const errorPromise = new Promise((resolve) => {
|
|
299
|
+
h.on('error', (err, _req) => {
|
|
300
|
+
assert.ok(err, 'got an error')
|
|
301
|
+
assert.strictEqual(_req, req, 'was given original request object')
|
|
302
|
+
assert.strictEqual(res.$statusCode, 400, 'correct status code')
|
|
303
|
+
assert.deepStrictEqual(res.$headers, { 'content-type': 'application/json' })
|
|
304
|
+
assert.strictEqual(res.$end, '{"error":"X-Hub-Signature does not match blob signature"}', 'got correct content')
|
|
305
|
+
resolve()
|
|
306
|
+
})
|
|
279
307
|
})
|
|
280
308
|
|
|
281
|
-
h.on('push',
|
|
282
|
-
t.fail(true, 'should not get here!')
|
|
283
|
-
})
|
|
309
|
+
h.on('push', () => assert.fail('should not get here'))
|
|
284
310
|
|
|
285
|
-
h(req, res,
|
|
286
|
-
|
|
311
|
+
h(req, res, (err) => {
|
|
312
|
+
assert.ok(err, 'got error on callback')
|
|
287
313
|
})
|
|
288
314
|
|
|
289
|
-
process.nextTick(
|
|
290
|
-
|
|
291
|
-
})
|
|
315
|
+
process.nextTick(() => req.end(json))
|
|
316
|
+
await errorPromise
|
|
292
317
|
})
|
|
293
318
|
|
|
294
|
-
test('handler responds on a
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
, req = mkReq('/')
|
|
301
|
-
, res = mkRes()
|
|
319
|
+
test('handler responds on a stream error', async () => {
|
|
320
|
+
const obj = { some: 'github', object: 'with', properties: true }
|
|
321
|
+
const json = JSON.stringify(obj)
|
|
322
|
+
const h = handler({ path: '/', secret: 'bogus' })
|
|
323
|
+
const req = mkReq('/')
|
|
324
|
+
const res = mkRes()
|
|
302
325
|
|
|
303
326
|
req.headers['x-hub-signature'] = signBlob('bogus', json)
|
|
304
|
-
req.headers['x-github-event']
|
|
327
|
+
req.headers['x-github-event'] = 'issue'
|
|
305
328
|
|
|
306
|
-
h.on('push',
|
|
307
|
-
|
|
308
|
-
})
|
|
329
|
+
h.on('push', () => assert.fail('should not get here'))
|
|
330
|
+
h.on('issue', () => assert.fail('should never get here'))
|
|
309
331
|
|
|
310
|
-
|
|
311
|
-
|
|
332
|
+
const errorPromise = new Promise((resolve) => {
|
|
333
|
+
h.on('error', (err) => {
|
|
334
|
+
assert.ok(err, 'got an error')
|
|
335
|
+
assert.strictEqual(res.$statusCode, 400, 'correct status code')
|
|
336
|
+
resolve()
|
|
337
|
+
})
|
|
312
338
|
})
|
|
313
339
|
|
|
314
|
-
h
|
|
315
|
-
|
|
316
|
-
t.equal(res.$statusCode, 400, 'correct status code')
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
h(req, res, function (err) {
|
|
320
|
-
t.ok(err)
|
|
340
|
+
h(req, res, (err) => {
|
|
341
|
+
assert.ok(err)
|
|
321
342
|
})
|
|
322
343
|
|
|
323
|
-
var end = res.end
|
|
324
|
-
res.end = function () {
|
|
325
|
-
t.equal(res.$statusCode, 400, 'correct status code')
|
|
326
|
-
}
|
|
327
|
-
|
|
328
344
|
req.write('{')
|
|
329
|
-
process.nextTick(
|
|
330
|
-
req.
|
|
331
|
-
})
|
|
345
|
+
process.nextTick(() => {
|
|
346
|
+
req.destroy(new Error('simulated explosion'))
|
|
347
|
+
})
|
|
332
348
|
|
|
349
|
+
await errorPromise
|
|
333
350
|
})
|
package/.jshintrc
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"predef": [ ]
|
|
3
|
-
, "bitwise": false
|
|
4
|
-
, "camelcase": false
|
|
5
|
-
, "curly": false
|
|
6
|
-
, "eqeqeq": false
|
|
7
|
-
, "forin": false
|
|
8
|
-
, "immed": false
|
|
9
|
-
, "latedef": false
|
|
10
|
-
, "noarg": true
|
|
11
|
-
, "noempty": true
|
|
12
|
-
, "nonew": true
|
|
13
|
-
, "plusplus": false
|
|
14
|
-
, "quotmark": true
|
|
15
|
-
, "regexp": false
|
|
16
|
-
, "undef": true
|
|
17
|
-
, "unused": true
|
|
18
|
-
, "strict": false
|
|
19
|
-
, "trailing": true
|
|
20
|
-
, "maxlen": 120
|
|
21
|
-
, "asi": true
|
|
22
|
-
, "boss": true
|
|
23
|
-
, "debug": true
|
|
24
|
-
, "eqnull": true
|
|
25
|
-
, "esnext": true
|
|
26
|
-
, "evil": true
|
|
27
|
-
, "expr": true
|
|
28
|
-
, "funcscope": false
|
|
29
|
-
, "globalstrict": false
|
|
30
|
-
, "iterator": false
|
|
31
|
-
, "lastsemic": true
|
|
32
|
-
, "laxbreak": true
|
|
33
|
-
, "laxcomma": true
|
|
34
|
-
, "loopfunc": true
|
|
35
|
-
, "multistr": false
|
|
36
|
-
, "onecase": false
|
|
37
|
-
, "proto": false
|
|
38
|
-
, "regexdash": false
|
|
39
|
-
, "scripturl": true
|
|
40
|
-
, "smarttabs": false
|
|
41
|
-
, "shadow": false
|
|
42
|
-
, "sub": true
|
|
43
|
-
, "supernew": false
|
|
44
|
-
, "validthis": true
|
|
45
|
-
, "browser": true
|
|
46
|
-
, "couch": false
|
|
47
|
-
, "devel": false
|
|
48
|
-
, "dojo": false
|
|
49
|
-
, "mootools": false
|
|
50
|
-
, "node": true
|
|
51
|
-
, "nonstandard": true
|
|
52
|
-
, "prototypejs": false
|
|
53
|
-
, "rhino": false
|
|
54
|
-
, "worker": true
|
|
55
|
-
, "wsh": false
|
|
56
|
-
, "nomen": false
|
|
57
|
-
, "onevar": false
|
|
58
|
-
, "passfail": false
|
|
59
|
-
}
|