hubot 3.4.0 → 4.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/workflows/release.yml +36 -0
- package/README.md +4 -1
- package/bin/hubot +1 -1
- package/docs/adapters/development.md +35 -36
- package/docs/implementation.md +1 -3
- package/docs/index.md +5 -10
- package/docs/patterns.md +132 -101
- package/docs/scripting.md +493 -411
- package/package.json +21 -19
- package/src/adapters/shell.js +4 -9
- package/src/robot.js +7 -4
- package/test/datastore_test.js +4 -0
- package/test/robot_test.js +16 -6
- package/test/shell_test.js +72 -0
- package/.env.example +0 -2
- package/.envrc +0 -5
- package/.travis.yml +0 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hubot",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"author": "hubot",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github",
|
|
@@ -15,26 +15,24 @@
|
|
|
15
15
|
"url": "https://github.com/hubotio/hubot.git"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"async": "
|
|
19
|
-
"chalk": "^1.0.0",
|
|
18
|
+
"async": "^3.2.4",
|
|
20
19
|
"cline": "^0.8.2",
|
|
21
|
-
"coffeescript": "
|
|
22
|
-
"connect-multiparty": "^2.
|
|
23
|
-
"express": "^4.
|
|
24
|
-
"express-basic-auth": "1.1
|
|
25
|
-
"log": "
|
|
26
|
-
"
|
|
20
|
+
"coffeescript": "^2.7.0",
|
|
21
|
+
"connect-multiparty": "^2.2.0",
|
|
22
|
+
"express": "^4.18.2",
|
|
23
|
+
"express-basic-auth": "^1.2.1",
|
|
24
|
+
"log": "^6.3.1",
|
|
25
|
+
"log-node": "^8.0.3",
|
|
26
|
+
"optparse": "^1.0.5"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"chai": "
|
|
30
|
-
"coveralls": "^3.0.2",
|
|
29
|
+
"chai": "^4.3.7",
|
|
31
30
|
"is-circular": "^1.0.2",
|
|
32
31
|
"mocha": "^10.2.0",
|
|
33
|
-
"mockery": "^1.
|
|
34
|
-
"nyc": "^15.1.0",
|
|
32
|
+
"mockery": "^2.1.0",
|
|
35
33
|
"semantic-release": "^21.0.1",
|
|
36
|
-
"sinon": "
|
|
37
|
-
"sinon-chai": "^
|
|
34
|
+
"sinon": "^15.0.4",
|
|
35
|
+
"sinon-chai": "^3.7.0",
|
|
38
36
|
"standard": "^17.0.0"
|
|
39
37
|
},
|
|
40
38
|
"engines": {
|
|
@@ -48,9 +46,13 @@
|
|
|
48
46
|
"scripts": {
|
|
49
47
|
"start": "bin/hubot",
|
|
50
48
|
"pretest": "standard",
|
|
51
|
-
"test": "
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
"test": "mocha --exit",
|
|
50
|
+
"test:smoke": "node src/**/*.js"
|
|
51
|
+
},
|
|
52
|
+
"release": {
|
|
53
|
+
"branches": [
|
|
54
|
+
"master"
|
|
55
|
+
],
|
|
56
|
+
"dryRun": false
|
|
55
57
|
}
|
|
56
58
|
}
|
package/src/adapters/shell.js
CHANGED
|
@@ -4,7 +4,6 @@ const fs = require('fs')
|
|
|
4
4
|
const readline = require('readline')
|
|
5
5
|
const Stream = require('stream')
|
|
6
6
|
const cline = require('cline')
|
|
7
|
-
const chalk = require('chalk')
|
|
8
7
|
|
|
9
8
|
const Adapter = require('../adapter')
|
|
10
9
|
|
|
@@ -15,12 +14,13 @@ const TextMessage = _require.TextMessage
|
|
|
15
14
|
const historySize = process.env.HUBOT_SHELL_HISTSIZE != null ? parseInt(process.env.HUBOT_SHELL_HISTSIZE) : 1024
|
|
16
15
|
|
|
17
16
|
const historyPath = '.hubot_history'
|
|
17
|
+
const bold = str => `\x1b[1m${str}\x1b[22m`
|
|
18
18
|
|
|
19
19
|
class Shell extends Adapter {
|
|
20
20
|
send (envelope/* , ...strings */) {
|
|
21
21
|
const strings = [].slice.call(arguments, 1)
|
|
22
22
|
|
|
23
|
-
Array.from(strings).forEach(str => console.log(
|
|
23
|
+
Array.from(strings).forEach(str => console.log(bold(str)))
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
emote (envelope/* , ...strings */) {
|
|
@@ -36,15 +36,13 @@ class Shell extends Adapter {
|
|
|
36
36
|
|
|
37
37
|
run () {
|
|
38
38
|
this.buildCli()
|
|
39
|
-
|
|
40
39
|
loadHistory((error, history) => {
|
|
41
40
|
if (error) {
|
|
42
41
|
console.log(error.message)
|
|
43
42
|
}
|
|
44
|
-
|
|
45
43
|
this.cli.history(history)
|
|
46
44
|
this.cli.interact(`${this.robot.name}> `)
|
|
47
|
-
return this.emit('connected')
|
|
45
|
+
return this.emit('connected', this)
|
|
48
46
|
})
|
|
49
47
|
}
|
|
50
48
|
|
|
@@ -97,14 +95,11 @@ class Shell extends Adapter {
|
|
|
97
95
|
}
|
|
98
96
|
|
|
99
97
|
const outstream = fs.createWriteStream(historyPath, fileOpts)
|
|
100
|
-
outstream.on('
|
|
101
|
-
|
|
98
|
+
outstream.on('end', this.shutdown.bind(this))
|
|
102
99
|
for (i = 0, len = history.length; i < len; i++) {
|
|
103
100
|
item = history[i]
|
|
104
101
|
outstream.write(item + '\n')
|
|
105
102
|
}
|
|
106
|
-
|
|
107
|
-
outstream.end(this.shutdown.bind(this))
|
|
108
103
|
})
|
|
109
104
|
}
|
|
110
105
|
}
|
package/src/robot.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
+
require('log-node')()
|
|
2
3
|
|
|
3
4
|
const EventEmitter = require('events').EventEmitter
|
|
4
5
|
const fs = require('fs')
|
|
5
6
|
const path = require('path')
|
|
6
7
|
|
|
7
8
|
const async = require('async')
|
|
8
|
-
const
|
|
9
|
+
const log = require('log')
|
|
9
10
|
const HttpClient = require('./httpclient')
|
|
10
11
|
|
|
11
12
|
const Brain = require('./brain')
|
|
@@ -49,7 +50,9 @@ class Robot {
|
|
|
49
50
|
response: new Middleware(this),
|
|
50
51
|
receive: new Middleware(this)
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
+
process.env.LOG_LEVEL = process.env.LOG_LEVEL || process.env.HUBOT_LOG_LEVEL || 'info'
|
|
54
|
+
this.logger = log.get('robot')
|
|
55
|
+
|
|
53
56
|
this.pingIntervalId = null
|
|
54
57
|
this.globalHttpOptions = {}
|
|
55
58
|
|
|
@@ -317,13 +320,13 @@ class Robot {
|
|
|
317
320
|
// stack doesn't get too big
|
|
318
321
|
process.nextTick(() =>
|
|
319
322
|
// Stop processing when message.done == true
|
|
320
|
-
done(context.response.message.done)
|
|
323
|
+
done(null, context.response.message.done)
|
|
321
324
|
)
|
|
322
325
|
})
|
|
323
326
|
} catch (err) {
|
|
324
327
|
this.emit('error', err, new this.Response(this, context.response.message, []))
|
|
325
328
|
// Continue to next listener when there is an error
|
|
326
|
-
done(false)
|
|
329
|
+
done(null, false)
|
|
327
330
|
}
|
|
328
331
|
},
|
|
329
332
|
// Ignore the result ( == the listener that set message.done = true)
|
package/test/datastore_test.js
CHANGED
|
@@ -30,6 +30,10 @@ describe('Datastore', function () {
|
|
|
30
30
|
this.robot.brain.userForId('2', { name: 'User Two' })
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
+
this.afterEach(function () {
|
|
34
|
+
this.clock.restore()
|
|
35
|
+
})
|
|
36
|
+
|
|
33
37
|
describe('global scope', function () {
|
|
34
38
|
it('returns undefined for values not in the datastore', function () {
|
|
35
39
|
return this.robot.datastore.get('blah').then(function (value) {
|
package/test/robot_test.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// Assertions and Stubbing
|
|
7
7
|
const chai = require('chai')
|
|
8
8
|
const sinon = require('sinon')
|
|
9
|
+
const emitter = require('log/lib/emitter')
|
|
9
10
|
chai.use(require('sinon-chai'))
|
|
10
11
|
|
|
11
12
|
const expect = chai.expect
|
|
@@ -374,7 +375,7 @@ describe('Robot', function () {
|
|
|
374
375
|
|
|
375
376
|
describe('#loadFile', function () {
|
|
376
377
|
beforeEach(function () {
|
|
377
|
-
this.sandbox = sinon.
|
|
378
|
+
this.sandbox = sinon.createSandbox()
|
|
378
379
|
})
|
|
379
380
|
|
|
380
381
|
afterEach(function () {
|
|
@@ -422,15 +423,24 @@ describe('Robot', function () {
|
|
|
422
423
|
})
|
|
423
424
|
|
|
424
425
|
it('logs a warning for a .js file', function () {
|
|
425
|
-
|
|
426
|
+
let wasCalled = false
|
|
427
|
+
const listener = e => {
|
|
428
|
+
wasCalled = e.messageTokens.some(t => t.indexOf('Expected scripts/test-script') > -1)
|
|
429
|
+
}
|
|
430
|
+
emitter.on('log', listener)
|
|
426
431
|
this.robot.loadFile('./scripts', 'test-script.js')
|
|
427
|
-
expect(
|
|
432
|
+
expect(wasCalled).to.be.true
|
|
433
|
+
emitter.off('log', listener)
|
|
428
434
|
})
|
|
429
435
|
|
|
430
436
|
it('logs a warning for a .mjs file', function () {
|
|
431
|
-
|
|
437
|
+
let wasCalled = false
|
|
438
|
+
const listener = e => {
|
|
439
|
+
wasCalled = e.messageTokens.some(t => t.indexOf('Expected scripts/test-script') > -1)
|
|
440
|
+
}
|
|
441
|
+
emitter.on('log', listener)
|
|
432
442
|
this.robot.loadFile('./scripts', 'test-script.mjs')
|
|
433
|
-
expect(
|
|
443
|
+
expect(wasCalled).to.be.true
|
|
434
444
|
})
|
|
435
445
|
})
|
|
436
446
|
|
|
@@ -724,7 +734,7 @@ describe('Robot', function () {
|
|
|
724
734
|
this.robot.catchAll(catchAllCallback)
|
|
725
735
|
|
|
726
736
|
this.robot.receive(testMessage, function () {
|
|
727
|
-
expect(listenerCallback).to.have.been.
|
|
737
|
+
expect(listenerCallback).to.have.been.calledOnce
|
|
728
738
|
expect(catchAllCallback).to.not.have.been.called
|
|
729
739
|
done()
|
|
730
740
|
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* global describe, beforeEach, it */
|
|
4
|
+
|
|
5
|
+
const chai = require('chai')
|
|
6
|
+
const sinon = require('sinon')
|
|
7
|
+
chai.use(require('sinon-chai'))
|
|
8
|
+
|
|
9
|
+
const expect = chai.expect
|
|
10
|
+
|
|
11
|
+
const Robot = require('../src/robot')
|
|
12
|
+
|
|
13
|
+
describe('Shell Adapter', function () {
|
|
14
|
+
beforeEach(function () {
|
|
15
|
+
this.robot = new Robot(null, 'shell', false, 'TestHubot')
|
|
16
|
+
this.robot.run()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
this.afterEach(function () {
|
|
20
|
+
this.robot.shutdown()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('Public API', function () {
|
|
24
|
+
beforeEach(function () {
|
|
25
|
+
this.adapter = this.robot.adapter
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('assigns robot', function () {
|
|
29
|
+
expect(this.adapter.robot).to.equal(this.robot)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('sends a message', function () {
|
|
33
|
+
this.adapter.send = sinon.spy()
|
|
34
|
+
this.adapter.send({ room: 'general' }, 'hello')
|
|
35
|
+
|
|
36
|
+
expect(this.adapter.send).to.have.been.calledWith({ room: 'general' }, 'hello')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('emotes a message', function () {
|
|
40
|
+
this.adapter.send = sinon.spy()
|
|
41
|
+
this.adapter.emote({ room: 'general' }, 'hello')
|
|
42
|
+
|
|
43
|
+
expect(this.adapter.send).to.have.been.calledWith({ room: 'general' }, '* hello')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('replies to a message', function () {
|
|
47
|
+
this.adapter.send = sinon.spy()
|
|
48
|
+
this.adapter.reply({ room: 'general', user: { name: 'mocha' } }, 'hello')
|
|
49
|
+
|
|
50
|
+
expect(this.adapter.send).to.have.been.calledWith({ room: 'general', user: { name: 'mocha' } }, 'mocha: hello')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('runs the adapter and emits connected', function (done) {
|
|
54
|
+
const connected = () => {
|
|
55
|
+
this.adapter.off('connected', connected)
|
|
56
|
+
done()
|
|
57
|
+
}
|
|
58
|
+
this.adapter.on('connected', connected)
|
|
59
|
+
this.adapter.run()
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('dispatches received messages to the robot', function () {
|
|
64
|
+
this.robot.receive = sinon.spy()
|
|
65
|
+
this.adapter = this.robot.adapter
|
|
66
|
+
this.message = sinon.spy()
|
|
67
|
+
|
|
68
|
+
this.adapter.receive(this.message)
|
|
69
|
+
|
|
70
|
+
expect(this.robot.receive).to.have.been.calledWith(this.message)
|
|
71
|
+
})
|
|
72
|
+
})
|
package/.env.example
DELETED
package/.envrc
DELETED
package/.travis.yml
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
language: node_js
|
|
2
|
-
node_js:
|
|
3
|
-
- "10"
|
|
4
|
-
- "8"
|
|
5
|
-
- "6"
|
|
6
|
-
notifications:
|
|
7
|
-
email: false
|
|
8
|
-
sudo: false
|
|
9
|
-
addons:
|
|
10
|
-
apt:
|
|
11
|
-
packages:
|
|
12
|
-
- expect
|
|
13
|
-
before_install:
|
|
14
|
-
- npm install -g npm@latest
|
|
15
|
-
before_script:
|
|
16
|
-
- npm prune
|
|
17
|
-
- bin/e2e-test.sh
|
|
18
|
-
after_success:
|
|
19
|
-
- npm run coverage
|
|
20
|
-
branches:
|
|
21
|
-
except:
|
|
22
|
-
- /^v\d+\.\d+\.\d+$/
|
|
23
|
-
deploy:
|
|
24
|
-
provider: script
|
|
25
|
-
skip_cleanup: true
|
|
26
|
-
script:
|
|
27
|
-
- npx travis-deploy-once "npx semantic-release"
|