hubot 8.1.0 → 9.0.1

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/bin/hubot.js CHANGED
@@ -153,5 +153,5 @@ function loadExternalScripts () {
153
153
 
154
154
  robot.adapter.once('connected', loadScripts)
155
155
 
156
- robot.run()
156
+ await robot.run()
157
157
  })()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hubot",
3
- "version": "8.1.0",
3
+ "version": "9.0.1",
4
4
  "author": "hubot",
5
5
  "keywords": [
6
6
  "github",
@@ -24,17 +24,12 @@
24
24
  "pino": "^8.11.0"
25
25
  },
26
26
  "devDependencies": {
27
- "chai": "^4.3.7",
28
- "is-circular": "^1.0.2",
29
- "mocha": "^10.2.0",
30
27
  "semantic-release": "^21.0.1",
31
- "sinon": "^15.0.4",
32
- "sinon-chai": "^3.7.0",
33
28
  "standard": "^17.1.0"
34
29
  },
35
30
  "engines": {
36
- "node": "> 16.20.2",
37
- "npm": "> 8.19.4"
31
+ "node": ">= 18",
32
+ "npm": ">= 9"
38
33
  },
39
34
  "main": "./index",
40
35
  "bin": {
@@ -43,7 +38,7 @@
43
38
  "scripts": {
44
39
  "start": "bin/hubot",
45
40
  "pretest": "standard",
46
- "test": "mocha --exit",
41
+ "test": "node --test",
47
42
  "test:smoke": "node src/**/*.js",
48
43
  "test:e2e": "bin/e2e-test.sh"
49
44
  },
package/src/adapter.js CHANGED
@@ -27,7 +27,7 @@ class Adapter extends EventEmitter {
27
27
  //
28
28
  // Returns results from adapter.
29
29
  async emote (envelope, ...strings) {
30
- return this.senda(envelope, ...strings)
30
+ return this.send(envelope, ...strings)
31
31
  }
32
32
 
33
33
  // Public: Raw method for building a reply and sending it back to the chat
@@ -57,13 +57,15 @@ class Adapter extends EventEmitter {
57
57
 
58
58
  // Public: Raw method for invoking the bot to run. Extend this.
59
59
  //
60
- // Returns nothing.
61
- run () {}
60
+ // Returns whatever the extended adapter returns.
61
+ async run () {}
62
62
 
63
63
  // Public: Raw method for shutting the bot down. Extend this.
64
64
  //
65
65
  // Returns nothing.
66
- close () {}
66
+ close () {
67
+ this.removeAllListeners()
68
+ }
67
69
 
68
70
  // Public: Dispatch a received message to the robot.
69
71
  //
@@ -75,24 +77,27 @@ class Adapter extends EventEmitter {
75
77
  // Public: Get an Array of User objects stored in the brain.
76
78
  //
77
79
  // Returns an Array of User objects.
80
+ // @deprecated Use @robot.brain
78
81
  users () {
79
- this.robot.logger.warning('@users() is going to be deprecated in 3.0.0 use @robot.brain.users()')
82
+ this.robot.logger.warning('@users() is going to be deprecated in 11.0.0 use @robot.brain.users()')
80
83
  return this.robot.brain.users()
81
84
  }
82
85
 
83
86
  // Public: Get a User object given a unique identifier.
84
87
  //
85
88
  // Returns a User instance of the specified user.
89
+ // @deprecated Use @robot.brain
86
90
  userForId (id, options) {
87
- this.robot.logger.warning('@userForId() is going to be deprecated in 3.0.0 use @robot.brain.userForId()')
91
+ this.robot.logger.warning('@userForId() is going to be deprecated in 11.0.0 use @robot.brain.userForId()')
88
92
  return this.robot.brain.userForId(id, options)
89
93
  }
90
94
 
91
95
  // Public: Get a User object given a name.
92
96
  //
93
97
  // Returns a User instance for the user with the specified name.
98
+ // @deprecated Use @robot.brain
94
99
  userForName (name) {
95
- this.robot.logger.warning('@userForName() is going to be deprecated in 3.0.0 use @robot.brain.userForName()')
100
+ this.robot.logger.warning('@userForName() is going to be deprecated in 11.0.0 use @robot.brain.userForName()')
96
101
  return this.robot.brain.userForName(name)
97
102
  }
98
103
 
@@ -101,8 +106,9 @@ class Adapter extends EventEmitter {
101
106
  // nicknames, etc.
102
107
  //
103
108
  // Returns an Array of User instances matching the fuzzy name.
109
+ // @deprecated Use @robot.brain
104
110
  usersForRawFuzzyName (fuzzyName) {
105
- this.robot.logger.warning('@userForRawFuzzyName() is going to be deprecated in 3.0.0 use @robot.brain.userForRawFuzzyName()')
111
+ this.robot.logger.warning('@userForRawFuzzyName() is going to be deprecated in 11.0.0 use @robot.brain.userForRawFuzzyName()')
106
112
  return this.robot.brain.usersForRawFuzzyName(fuzzyName)
107
113
  }
108
114
 
@@ -111,8 +117,9 @@ class Adapter extends EventEmitter {
111
117
  // fuzzyName is a raw fuzzy match (see usersForRawFuzzyName).
112
118
  //
113
119
  // Returns an Array of User instances matching the fuzzy name.
120
+ // @deprecated Use @robot.brain
114
121
  usersForFuzzyName (fuzzyName) {
115
- this.robot.logger.warning('@userForFuzzyName() is going to be deprecated in 3.0.0 use @robot.brain.userForFuzzyName()')
122
+ this.robot.logger.warning('@userForFuzzyName() is going to be deprecated in 11.0.0 use @robot.brain.userForFuzzyName()')
116
123
  return this.robot.brain.usersForFuzzyName(fuzzyName)
117
124
  }
118
125
 
@@ -122,8 +129,9 @@ class Adapter extends EventEmitter {
122
129
  // send the request.
123
130
  //
124
131
  // Returns a ScopedClient instance.
132
+ // @deprecated Use node.js fetch.
125
133
  http (url) {
126
- this.robot.logger.warning('@http() is going to be deprecated in 3.0.0 use @robot.http()')
134
+ this.robot.logger.warning('@http() is going to be deprecated in 11.0.0 use @robot.http()')
127
135
  return this.robot.http(url)
128
136
  }
129
137
  }
@@ -82,7 +82,7 @@ class Campfire extends Adapter {
82
82
  })
83
83
  }
84
84
 
85
- run () {
85
+ async run () {
86
86
  const self = this
87
87
 
88
88
  const options = {
@@ -17,6 +17,7 @@ const historyPath = '.hubot_history'
17
17
  const bold = str => `\x1b[1m${str}\x1b[22m`
18
18
 
19
19
  class Shell extends Adapter {
20
+ #rl = null
20
21
  constructor (robot) {
21
22
  super(robot)
22
23
  this.name = 'Shell'
@@ -35,27 +36,33 @@ class Shell extends Adapter {
35
36
  this.send(envelope, ...strings)
36
37
  }
37
38
 
38
- run () {
39
+ async run () {
39
40
  this.buildCli()
40
- loadHistory((error, history) => {
41
- if (error) {
42
- console.log(error.message)
43
- }
41
+ try {
42
+ const { readlineInterface, history } = await this.#loadHistory()
44
43
  this.cli.history(history)
45
- this.cli.interact(`${this.robot.name}> `)
46
- return this.emit('connected', this)
47
- })
44
+ this.cli.interact(`${this.robot.name ?? this.robot.alias}> `)
45
+ this.#rl = readlineInterface
46
+ this.emit('connected', this)
47
+ } catch (error) {
48
+ console.log(error)
49
+ }
48
50
  }
49
51
 
50
- shutdown () {
51
- this.robot.shutdown()
52
- return process.exit(0)
52
+ close () {
53
+ super.close()
54
+ // Getting an error message on GitHubt Actions: error: 'this[#rl].close is not a function'
55
+ if (this.#rl?.close) {
56
+ this.#rl.close()
57
+ }
58
+ this.cli.removeAllListeners()
59
+ this.cli.close()
53
60
  }
54
61
 
55
62
  buildCli () {
56
63
  this.cli = cline()
57
64
 
58
- this.cli.command('*', input => {
65
+ this.cli.command('*', async input => {
59
66
  let userId = process.env.HUBOT_SHELL_USER_ID || '1'
60
67
  if (userId.match(/A\d+z/)) {
61
68
  userId = parseInt(userId)
@@ -63,7 +70,7 @@ class Shell extends Adapter {
63
70
 
64
71
  const userName = process.env.HUBOT_SHELL_USER_NAME || 'Shell'
65
72
  const user = this.robot.brain.userForId(userId, { name: userName, room: 'Shell' })
66
- this.receive(new TextMessage(user, input, 'messageId'))
73
+ await this.receive(new TextMessage(user, input, 'messageId'))
67
74
  })
68
75
 
69
76
  this.cli.command('history', () => {
@@ -86,7 +93,7 @@ class Shell extends Adapter {
86
93
  history = this.cli.history()
87
94
 
88
95
  if (history.length <= historySize) {
89
- return this.shutdown()
96
+ return
90
97
  }
91
98
 
92
99
  const startIndex = history.length - historySize
@@ -96,11 +103,35 @@ class Shell extends Adapter {
96
103
  }
97
104
 
98
105
  const outstream = fs.createWriteStream(historyPath, fileOpts)
99
- outstream.on('end', this.shutdown.bind(this))
100
106
  for (i = 0, len = history.length; i < len; i++) {
101
107
  item = history[i]
102
108
  outstream.write(item + '\n')
103
109
  }
110
+ outstream.end()
111
+ })
112
+ }
113
+
114
+ async #loadHistory () {
115
+ if (!fs.existsSync(historyPath)) {
116
+ return new Error('No history available')
117
+ }
118
+ const instream = fs.createReadStream(historyPath)
119
+ const outstream = new Stream()
120
+ outstream.readable = true
121
+ outstream.writable = true
122
+ const history = []
123
+ const readlineInterface = readline.createInterface({ input: instream, output: outstream, terminal: false })
124
+ return new Promise((resolve, reject) => {
125
+ readlineInterface.on('line', line => {
126
+ line = line.trim()
127
+ if (line.length > 0) {
128
+ history.push(line)
129
+ }
130
+ })
131
+ readlineInterface.on('close', () => {
132
+ resolve({ readlineInterface, history })
133
+ })
134
+ readlineInterface.on('error', reject)
104
135
  })
105
136
  }
106
137
  }
@@ -108,29 +139,3 @@ class Shell extends Adapter {
108
139
  // Prevent output buffer "swallowing" every other character on OSX / Node version > 16.19.0.
109
140
  process.stdout._handle.setBlocking(false)
110
141
  exports.use = robot => new Shell(robot)
111
-
112
- // load history from .hubot_history.
113
- //
114
- // callback - A Function that is called with the loaded history items (or an empty array if there is no history)
115
- function loadHistory (callback) {
116
- if (!fs.existsSync(historyPath)) {
117
- return callback(new Error('No history available'))
118
- }
119
-
120
- const instream = fs.createReadStream(historyPath)
121
- const outstream = new Stream()
122
- outstream.readable = true
123
- outstream.writable = true
124
-
125
- const items = []
126
-
127
- readline.createInterface({ input: instream, output: outstream, terminal: false })
128
- .on('line', function (line) {
129
- line = line.trim()
130
- if (line.length > 0) {
131
- items.push(line)
132
- }
133
- })
134
- .on('close', () => callback(null, items))
135
- .on('error', callback)
136
- }
package/src/brain.js CHANGED
@@ -109,6 +109,7 @@ class Brain extends EventEmitter {
109
109
  clearInterval(this.saveInterval)
110
110
  this.save()
111
111
  this.emit('close')
112
+ this.removeAllListeners()
112
113
  }
113
114
 
114
115
  // Public: Enable or disable the automatic saving
package/src/datastore.js CHANGED
@@ -13,52 +13,49 @@ class DataStore {
13
13
  // write has completed.
14
14
  //
15
15
  // Value can be any JSON-serializable type.
16
- set (key, value) {
17
- return this._set(key, value, 'global')
16
+ async set (key, value) {
17
+ return await this._set(key, value, 'global')
18
18
  }
19
19
 
20
20
  // Public: Assuming `key` represents an object in the database,
21
21
  // sets its `objectKey` to `value`. If `key` isn't already
22
22
  // present, it's instantiated as an empty object.
23
- setObject (key, objectKey, value) {
24
- return this.get(key).then((object) => {
25
- const target = object || {}
26
- target[objectKey] = value
27
- return this.set(key, target)
28
- })
23
+ async setObject (key, objectKey, value) {
24
+ const object = await this.get(key)
25
+ const target = object || {}
26
+ target[objectKey] = value
27
+ return await this.set(key, target)
29
28
  }
30
29
 
31
30
  // Public: Adds the supplied value(s) to the end of the existing
32
31
  // array in the database marked by `key`. If `key` isn't already
33
32
  // present, it's instantiated as an empty array.
34
- setArray (key, value) {
35
- return this.get(key).then((object) => {
36
- const target = object || []
37
- // Extend the array if the value is also an array, otherwise
38
- // push the single value on the end.
39
- if (Array.isArray(value)) {
40
- return this.set(key, target.push.apply(target, value))
41
- } else {
42
- return this.set(key, target.concat(value))
43
- }
44
- })
33
+ async setArray (key, value) {
34
+ const object = await this.get(key)
35
+ const target = object ?? []
36
+ // Extend the array if the value is also an array, otherwise
37
+ // push the single value on the end.
38
+ if (Array.isArray(value)) {
39
+ return await this.set(key, target.concat(value))
40
+ } else {
41
+ return await this.set(key, target.concat([value]))
42
+ }
45
43
  }
46
44
 
47
45
  // Public: Get value by key if in the database or return `undefined`
48
46
  // if not found. Returns a promise which resolves to the
49
47
  // requested value.
50
- get (key) {
51
- return this._get(key, 'global')
48
+ async get (key) {
49
+ return await this._get(key, 'global')
52
50
  }
53
51
 
54
52
  // Public: Digs inside the object at `key` for a key named
55
53
  // `objectKey`. If `key` isn't already present, or if it doesn't
56
54
  // contain an `objectKey`, returns `undefined`.
57
- getObject (key, objectKey) {
58
- return this.get(key).then((object) => {
59
- const target = object || {}
60
- return target[objectKey]
61
- })
55
+ async getObject (key, objectKey) {
56
+ const object = await this.get(key)
57
+ const target = object || {}
58
+ return target[objectKey]
62
59
  }
63
60
 
64
61
  // Private: Implements the underlying `set` logic for the datastore.
@@ -70,7 +67,7 @@ class DataStore {
70
67
  // This returns a resolved promise when the `set` operation is
71
68
  // successful, and a rejected promise if the operation fails.
72
69
  _set (key, value, table) {
73
- return Promise.reject(new DataStoreUnavailable('Setter called on the abstract class.'))
70
+ throw new DataStoreUnavailable('Setter called on the abstract class.')
74
71
  }
75
72
 
76
73
  // Private: Implements the underlying `get` logic for the datastore.
@@ -82,7 +79,7 @@ class DataStore {
82
79
  // This returns a resolved promise containing the fetched value on
83
80
  // success, and a rejected promise if the operation fails.
84
81
  _get (key, table) {
85
- return Promise.reject(new DataStoreUnavailable('Getter called on the abstract class.'))
82
+ throw new DataStoreUnavailable('Getter called on the abstract class.')
86
83
  }
87
84
  }
88
85
 
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const DataStore = require('../datastore').DataStore
3
+ const DataStore = require('../datastore.js').DataStore
4
4
 
5
5
  class InMemoryDataStore extends DataStore {
6
6
  constructor (robot) {
@@ -11,11 +11,11 @@ class InMemoryDataStore extends DataStore {
11
11
  }
12
12
  }
13
13
 
14
- _get (key, table) {
14
+ async _get (key, table) {
15
15
  return Promise.resolve(this.data[table][key])
16
16
  }
17
17
 
18
- _set (key, value, table) {
18
+ async _set (key, value, table) {
19
19
  return Promise.resolve(this.data[table][key] = value)
20
20
  }
21
21
  }
package/src/response.js CHANGED
@@ -65,7 +65,7 @@ class Response {
65
65
  //
66
66
  // Returns result from middleware.
67
67
  async play (...strings) {
68
- return await this.#runWithMiddleware('play', ...strings)
68
+ return await this.#runWithMiddleware('play', {}, ...strings)
69
69
  }
70
70
 
71
71
  // Public: Posts a message in an unlogged room
package/src/robot.js CHANGED
@@ -50,11 +50,6 @@ class Robot {
50
50
  name,
51
51
  level: process.env.HUBOT_LOG_LEVEL || 'info'
52
52
  })
53
- Reflect.defineProperty(this.logger, 'warning', {
54
- value: this.logger.warn,
55
- enumerable: true,
56
- configurable: true
57
- })
58
53
 
59
54
  this.pingIntervalId = null
60
55
  this.globalHttpOptions = {}
@@ -72,10 +67,6 @@ class Robot {
72
67
  this.on('error', (err, res) => {
73
68
  return this.invokeErrorHandlers(err, res)
74
69
  })
75
- this.onUncaughtException = err => {
76
- return this.emit('error', err)
77
- }
78
- process.on('uncaughtException', this.onUncaughtException)
79
70
  }
80
71
 
81
72
  // Public: Adds a custom Listener with the provided matcher, options, and
@@ -232,9 +223,8 @@ class Robot {
232
223
  options = {}
233
224
  }
234
225
 
235
- this.listen(isCatchAllMessage, options, function listenCallback (msg) {
236
- msg.message = msg.message.message
237
- callback(msg)
226
+ this.listen(isCatchAllMessage, options, async msg => {
227
+ await callback(msg.message)
238
228
  })
239
229
  }
240
230
 
@@ -376,7 +366,7 @@ class Robot {
376
366
  this.parseHelp(full)
377
367
  } catch (error) {
378
368
  this.logger.error(`Unable to load ${full}: ${error.stack}`)
379
- process.exit(1)
369
+ throw error
380
370
  }
381
371
  }
382
372
 
@@ -411,7 +401,7 @@ class Robot {
411
401
  Object.keys(packages).forEach(key => require(key)(this, packages[key]))
412
402
  } catch (error) {
413
403
  this.logger.error(`Error loading scripts from npm package - ${error.stack}`)
414
- process.exit(1)
404
+ throw error
415
405
  }
416
406
  }
417
407
 
@@ -459,9 +449,8 @@ class Robot {
459
449
  this.server = app.listen(port, address)
460
450
  this.router = app
461
451
  } catch (error) {
462
- const err = error
463
- this.logger.error(`Error trying to start HTTP server: ${err}\n${err.stack}`)
464
- process.exit(1)
452
+ this.logger.error(`Error trying to start HTTP server: ${error}\n${error.stack}`)
453
+ throw error
465
454
  }
466
455
 
467
456
  let herokuUrl = process.env.HEROKU_URL
@@ -520,9 +509,9 @@ class Robot {
520
509
  }
521
510
  }
522
511
  }
523
- } catch (err) {
524
- this.logger.error(`Cannot load adapter ${adapterPath ?? '[no path set]'} ${this.adapterName} - ${err}`)
525
- process.exit(1)
512
+ } catch (error) {
513
+ this.logger.error(`Cannot load adapter ${adapterPath ?? '[no path set]'} ${this.adapterName} - ${error}`)
514
+ throw error
526
515
  }
527
516
  }
528
517
 
@@ -654,11 +643,11 @@ class Robot {
654
643
 
655
644
  // Public: Kick off the event loop for the adapter
656
645
  //
657
- // Returns nothing.
658
- run () {
646
+ // Returns whatever the adapter returns.
647
+ async run () {
659
648
  this.emit('running')
660
649
 
661
- this.adapter.run()
650
+ return await this.adapter.run()
662
651
  }
663
652
 
664
653
  // Public: Gracefully shutdown the robot process
@@ -668,13 +657,12 @@ class Robot {
668
657
  if (this.pingIntervalId != null) {
669
658
  clearInterval(this.pingIntervalId)
670
659
  }
671
- process.removeListener('uncaughtException', this.onUncaughtException)
672
- this.adapter.close()
660
+ this.adapter?.close()
673
661
  if (this.server) {
674
662
  this.server.close()
675
663
  }
676
-
677
664
  this.brain.close()
665
+ this.events.removeAllListeners()
678
666
  }
679
667
 
680
668
  // Public: The version of Hubot from npm