diff --git a/README.md b/README.md index 94f02836b..9304aee7d 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,8 @@ # Hubot -**Note: v10.0.4 contains the removal of CoffeeScript** - -Semver is looking for **BREAKING CHANGE** singular, not **BREAKING CHANGES**. As a result, the removal of CoffeeScript was marked as the `v10.0.4` release. +**Note: v10.0.4 accidentaly contains the removal of CoffeeScript; v10.0.5 puts it back in** +**Note: v11 removes CoffeeScript and converts this codebase to ESM** Hubot is a framework to build chat bots, modeled after GitHub's Campfire bot of the same name, hubot. He's pretty cool. He's [extendable with scripts](https://hubotio.github.io/hubot/docs#scripts) and can work diff --git a/bin/hubot.js b/bin/Hubot.mjs similarity index 85% rename from bin/hubot.js rename to bin/Hubot.mjs index 2f7ab841f..01f9965f1 100755 --- a/bin/hubot.js +++ b/bin/Hubot.mjs @@ -1,15 +1,13 @@ 'use strict' -const fs = require('fs') -const pathResolve = require('path').resolve - -const OptParse = require('../src/OptParse.js') - -const Hubot = require('..') -const create = require('../src/GenHubot.js') +import fs from 'node:fs' +import { resolve as pathResolve } from 'node:path' +import OptParse from '../src/OptParse.mjs' +import Hubot from '../index.mjs' +import create from '../src/GenHubot.mjs' const switches = [ - ['-a', '--adapter HUBOT_ADAPTER', 'The Adapter to use, e.g. "shell" (to load the default hubot shell adapter)'], + ['-a', '--adapter HUBOT_ADAPTER', 'The Adapter to use, e.g. "Shell" (to load the default hubot Shell adapter)'], ['-f', '--file HUBOT_FILE', 'Path to adapter file, e.g. "./adapters/CustomAdapter.mjs"'], ['-c', '--create HUBOT_CREATE', 'Create a deployable hubot'], ['-d', '--disable-httpd HUBOT_HTTPD', 'Disable the HTTP server'], @@ -97,13 +95,13 @@ if (options.file) { } const robot = Hubot.loadBot(options.adapter, options.enableHttpd, options.name, options.alias) -module.exports = robot +export default robot async function loadScripts () { await robot.load(pathResolve('.', 'scripts')) await robot.load(pathResolve('.', 'src', 'scripts')) - loadExternalScripts() + await loadExternalScripts() const tasks = options.scripts.map((scriptPath) => { if (scriptPath[0] === '/') { @@ -115,25 +113,19 @@ async function loadScripts () { await Promise.all(tasks) } -function loadExternalScripts () { +async function loadExternalScripts () { const externalScripts = pathResolve('.', 'external-scripts.json') - - if (!fs.existsSync(externalScripts)) { - return - } - - fs.readFile(externalScripts, function (error, data) { - if (error) { - throw error - } - + try { + const data = await fs.promises.readFile(externalScripts) try { robot.loadExternalScripts(JSON.parse(data)) } catch (error) { console.error(`Error parsing JSON data from external-scripts.json: ${error}`) process.exit(1) } - }) + } catch (e) { + robot.logger.info('No external-scripts.json found. Skipping.') + } } (async () => { diff --git a/bin/e2e-test.sh b/bin/e2e-test.sh index 36d7d2899..4eacb1e54 100755 --- a/bin/e2e-test.sh +++ b/bin/e2e-test.sh @@ -9,22 +9,20 @@ trap "{ CODE=$?; popd; rm -rf $TEMP_ROOT; exit $CODE; }" EXIT ## https://github.com/hubotio/hubot/blob/main/docs/index.md ## use this hubot version -echo "$ create hubot in $TEMP_ROOT" -echo "$ install Hubot from $HUBOT_FOLDER" +echo "Creating hubot in $TEMP_ROOT" +echo " and installing Hubot from $HUBOT_FOLDER" npm init -y -npm i $HUBOT_FOLDER coffeescript +npm i $HUBOT_FOLDER -./node_modules/.bin/hubot --create myhubot -cd myhubot +export HUBOT_INSTALLATION_PATH=$HUBOT_FOLDER +./node_modules/.bin/hubot --create . # npm install /path/to/hubot will create a symlink in npm 5+ (http://blog.npmjs.org/post/161081169345/v500). # As the require calls for app-specific scripts happen inside hubot, we have to # set NODE_PATH to the app’s node_modules path so they can be found echo "$ Update NODE_PATH=$TEMP_ROOT/node_modules so everything can be found correctly." -export NODE_PATH=$TEMP_ROOT/node_modules:$TEMP_ROOT/myhubot/node_modules -export PATH=$PATH:$TEMP_ROOT/node_modules/.bin:$TEMP_ROOT/myhubot/node_modules/.bin -export HUBOT_INSTALLATION_PATH=$HUBOT_FOLDER -echo $HUBOT_INSTALLATION_PATH +export NODE_PATH=$TEMP_ROOT/node_modules +export PATH=$PATH:$TEMP_ROOT/node_modules/.bin ## start, but have to sleep 1 second to wait for hubot to start and the scripts to load expect < {}) \ No newline at end of file diff --git a/docs/adapters/campfire.md b/docs/adapters/campfire.md index 92cd83416..4dc54223f 100644 --- a/docs/adapters/campfire.md +++ b/docs/adapters/campfire.md @@ -14,11 +14,11 @@ You will need a Campfire account to start. Next, you will need to create a user on your Campfire account for your Hubot, then give it access so it can join to your rooms. You will need to create a room if you haven't already. -Hubot defaults to using its [shell](./shell.html), so to use Campfire instead, you can run hubot with `-a campfire`: +Hubot defaults to using its [Shell](./shell.html), so to use Campfire instead, you can run hubot with `-a Campfire`: % bin/hubot -a campfire -If you are deploying to Heroku or using foreman, you need to make sure the hubot is called with `-a campfire` in the `Procfile`: +If you are deploying to Heroku or using foreman, you need to make sure the hubot is called with `-a Campfire` in the `Procfile`: web: bin/hubot -a campfire -n Hubot diff --git a/docs/adapters/development.md b/docs/adapters/development.md index 12c427fe9..cd33dc5bb 100644 --- a/docs/adapters/development.md +++ b/docs/adapters/development.md @@ -8,12 +8,10 @@ permalink: /adapters/development.html ## Adapter Basics -All adapters inherit from the Adapter class in the `src/adapter.js` file. - -If you're writing your adapter in ES2015, you must require the ES2015 entrypoint instead: +All adapters inherit from the Adapter class in the `src/Adapter.mjs` file. ```javascript -const Adapter = require('hubot/es2015').Adapter; +const Adapter = require('hubot/index.mjs').Adapter; ``` There are certain methods that you will want to override. Here is a basic stub of what an extended Adapter class would look like: @@ -60,10 +58,7 @@ exports.use = (robot) => new Sample(robot) "dependencies": { }, "peerDependencies": { - "hubot": ">=3.0" - }, - "devDependencies": { - "coffeescript": ">=1.2.0" + "hubot": ">=11" } ``` @@ -117,10 +112,7 @@ Another option is to load the file from local disk. "dependencies": { }, "peerDependencies": { - "hubot": ">=9" - }, - "devDependencies": { - "coffeescript": ">=2.7.0" + "hubot": ">=11" } ``` diff --git a/docs/adapters/shell.md b/docs/adapters/shell.md index b413f3ef8..2fc4930d2 100644 --- a/docs/adapters/shell.md +++ b/docs/adapters/shell.md @@ -11,8 +11,8 @@ It can be useful for testing scripts before using them on a live hubot. ## Getting Started -To use the shell adapter you can simply omit the `-a` option when running -hubot as it will use the shell adapter by default. +To use the Shell adapter you can simply omit the `-a` option when running +hubot as it will use the Shell adapter by default. % bin/hubot diff --git a/docs/deploying/azure.md b/docs/deploying/azure.md index 15f0efdde..61bcf25b1 100644 --- a/docs/deploying/azure.md +++ b/docs/deploying/azure.md @@ -37,13 +37,12 @@ First, run the follow command to add `deploy.cmd` to your hubot directory. This Then, edit this file and look for the sections that give you steps 1, 2 and 3. You're going to add a 4th step: - :: 4. Create Hubot file with a coffee extension - copy /Y "%DEPLOYMENT_TARGET%\node_modules\hubot\bin\hubot" "%DEPLOYMENT_TARGET%\node_modules\hubot\bin\hubot.coffee" + :: 4. Create Hubot file with a js extension + copy /Y "%DEPLOYMENT_TARGET%\node_modules\hubot\bin\hubot" "%DEPLOYMENT_TARGET%\node_modules\hubot\bin\Hubot.mjs" Now, create a new file in the base directory of hubot called `server.js` and put these two lines into it: - require('coffeescript/register'); - module.exports = require('hubot/bin/hubot.coffee'); + module.exports = await import('hubot/bin/Hubot.mjs'); Finally you will need to add the environment variables to the website to make sure it runs properly. You can either do it through the GUI (under configuration) or you can use the Azure PowerShell command line, as follows (example is showing slack as an adapter and mynewhubot as the website name). diff --git a/docs/docs.md b/docs/docs.md index 5f6702f87..036bd443b 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -25,7 +25,7 @@ Now open `package.json` in your code editor and add a `start` property to the `s } ``` -Start your Hubot instance by executing `npm start`. It will start with the built in [shell adapter](/adapters/shell.html), which starts a [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) where you can type commands. +Start your Hubot instance by executing `npm start`. It will start with the built in [Shell adapter](/adapters/shell.html), which starts a [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) where you can type commands. Your terminal should look like: @@ -39,7 +39,7 @@ Typing `help` will list some default commands that Hubot's default adapter, Shel Hubot> help usage: history -exit, \q - close shell and exit +exit, \q - close Shell and exit help, \? - print this usage clear, \c - clear the terminal screen Hubot> diff --git a/es2015.js b/es2015.js deleted file mode 100644 index 0c6f377ce..000000000 --- a/es2015.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict' - -const User = require('./src/user') -const Brain = require('./src/brain') -const Robot = require('./src/robot') -const Adapter = require('./src/adapter') -const Response = require('./src/response') -const Listener = require('./src/listener') -const Message = require('./src/message') -const DataStore = require('./src/datastore') - -module.exports = { - User, - Brain, - Robot, - Adapter, - Response, - Listener: Listener.Listener, - TextListener: Listener.TextListener, - Message: Message.Message, - TextMessage: Message.TextMessage, - EnterMessage: Message.EnterMessage, - LeaveMessage: Message.LeaveMessage, - TopicMessage: Message.TopicMessage, - CatchAllMessage: Message.CatchAllMessage, - DataStore: DataStore.DataStore, - DataStoreUnavailable: DataStore.DataStoreUnavailable, - loadBot (adapter, enableHttpd, name, alias) { - return new module.exports.Robot(adapter, enableHttpd, name, alias) - } -} diff --git a/index.js b/index.js deleted file mode 100644 index 5c742efd4..000000000 --- a/index.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict' -require('coffeescript/register') - -const inherits = require('util').inherits - -const hubotExport = require('./es2015') - -// make all es2015 class declarations compatible with CoffeeScript’s extend -// see https://github.com/hubotio/evolution/pull/4#issuecomment-306437501 -module.exports = Object.keys(hubotExport).reduce((map, current) => { - if (current !== 'loadBot') { - map[current] = makeClassCoffeeScriptCompatible(hubotExport[current]) - } else { - map[current] = hubotExport[current] - } - return map -}, {}) - -function makeClassCoffeeScriptCompatible (klass) { - function CoffeeScriptCompatibleClass () { - const Hack = Function.prototype.bind.apply(klass, [null].concat([].slice.call(arguments))) - const instance = new Hack() - - // pass methods from child to returned instance - for (const key in this) { - instance[key] = this[key] - } - - // support for constructor methods which call super() - // in which this.* properties are set - for (const key in instance) { - this[key] = instance[key] - } - - return instance - } - inherits(CoffeeScriptCompatibleClass, klass) - - return CoffeeScriptCompatibleClass -} diff --git a/index.mjs b/index.mjs new file mode 100644 index 000000000..3da194a7a --- /dev/null +++ b/index.mjs @@ -0,0 +1,52 @@ +'use strict' + +import User from './src/User.mjs' +import Brain from './src/Brain.mjs' +import Robot from './src/Robot.mjs' +import Adapter from './src/Adapter.mjs' +import Response from './src/Response.mjs' +import Middleware from './src/Middleware.mjs' +import { Listener, TextListener } from './src/Listener.mjs' +import { TextMessage, EnterMessage, LeaveMessage, TopicMessage, CatchAllMessage, Message } from './src/Message.mjs' +import { DataStore, DataStoreUnavailable } from './src/DataStore.mjs' + +const loadBot = (adapter, enableHttpd, name, alias) => new Robot(adapter, enableHttpd, name, alias) +export { + Adapter, + User, + Brain, + Robot, + Response, + Listener, + TextListener, + Message, + TextMessage, + EnterMessage, + LeaveMessage, + TopicMessage, + CatchAllMessage, + DataStore, + DataStoreUnavailable, + Middleware, + loadBot +} + +export default { + Adapter, + User, + Brain, + Robot, + Response, + Listener, + TextListener, + Message, + TextMessage, + EnterMessage, + LeaveMessage, + TopicMessage, + CatchAllMessage, + DataStore, + DataStoreUnavailable, + Middleware, + loadBot +} diff --git a/package-lock.json b/package-lock.json index e5553c666..028322749 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,14 @@ { "name": "hubot", - "version": "9.1.0", + "version": "0.0.0-development", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hubot", - "version": "9.1.0", + "version": "0.0.0-development", "license": "MIT", "dependencies": { - "coffeescript": "^2.7.0", "connect-multiparty": "^2.2.0", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -670,18 +669,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/coffeescript": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.7.0.tgz", - "integrity": "sha512-hzWp6TUE2d/jCcN67LrW1eh5b/rSDKQK6oD6VMLlggYVUUFexgTH9z3dNYihzX4RMhze5FTUsUmOXViJKFQR/A==", - "bin": { - "cake": "bin/cake", - "coffee": "bin/coffee" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", diff --git a/package.json b/package.json index 67702e236..197fc879f 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "url": "https://github.com/hubotio/hubot.git" }, "dependencies": { - "coffeescript": "^2.7.0", "connect-multiparty": "^2.2.0", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -28,7 +27,7 @@ "node": ">= 18", "npm": ">= 9" }, - "main": "./index", + "main": "./index.mjs", "bin": { "hubot": "./bin/hubot" }, diff --git a/src/adapter.js b/src/Adapter.mjs similarity index 98% rename from src/adapter.js rename to src/Adapter.mjs index 85746a257..4c8a0ad11 100644 --- a/src/adapter.js +++ b/src/Adapter.mjs @@ -1,6 +1,6 @@ 'use strict' -const EventEmitter = require('events').EventEmitter +import EventEmitter from 'node:events' class Adapter extends EventEmitter { // An adapter is a specific interface to a chat source for robots. @@ -136,4 +136,4 @@ class Adapter extends EventEmitter { } } -module.exports = Adapter +export default Adapter diff --git a/src/brain.js b/src/Brain.mjs similarity index 98% rename from src/brain.js rename to src/Brain.mjs index c063977c7..4d3c32ee0 100644 --- a/src/brain.js +++ b/src/Brain.mjs @@ -1,8 +1,7 @@ 'use strict' -const EventEmitter = require('events').EventEmitter - -const User = require('./user') +import EventEmitter from 'node:events' +import User from './User.mjs' // If necessary, reconstructs a User object. Returns either: // @@ -239,4 +238,4 @@ class Brain extends EventEmitter { } } -module.exports = Brain +export default Brain diff --git a/src/datastore.js b/src/DataStore.mjs similarity index 97% rename from src/datastore.js rename to src/DataStore.mjs index 57fab9ab5..6cb4cde75 100644 --- a/src/datastore.js +++ b/src/DataStore.mjs @@ -1,6 +1,6 @@ 'use strict' -class DataStore { +export class DataStore { // Represents a persistent, database-backed storage for the robot. Extend this. // // Returns a new Datastore with no storage. @@ -83,9 +83,9 @@ class DataStore { } } -class DataStoreUnavailable extends Error {} +export class DataStoreUnavailable extends Error {} -module.exports = { +export default { DataStore, DataStoreUnavailable } diff --git a/src/GenHubot.js b/src/GenHubot.mjs similarity index 67% rename from src/GenHubot.js rename to src/GenHubot.mjs index ed35a7f25..048d90c01 100755 --- a/src/GenHubot.js +++ b/src/GenHubot.mjs @@ -1,6 +1,6 @@ -const { spawnSync } = require('child_process') -const File = require('fs') -const path = require('path') +import { spawnSync } from 'node:child_process' +import File from 'node:fs' +import path from 'node:path' function runCommands (hubotDirectory, options) { options.hubotInstallationPath = options?.hubotInstallationPath ?? 'hubot' @@ -13,8 +13,19 @@ function runCommands (hubotDirectory, options) { const envFilePath = path.resolve(process.cwd(), '.env') process.chdir(hubotDirectory) - spawnSync('npm', ['init', '-y']) - spawnSync('npm', ['i', options.hubotInstallationPath].concat(options.adapter, 'hubot-help', 'hubot-rules', 'hubot-diagnostics')) + let output = spawnSync('npm', ['init', '-y']) + console.log('npm init', output.stderr.toString()) + if (options.hubotInstallationPath !== 'hubot') { + output = spawnSync('npm', ['pack', `${options.hubotInstallationPath}`]) + console.log('npm pack', output.stderr.toString(), output.stdout.toString()) + const customHubotPackage = JSON.parse(File.readFileSync(`${options.hubotInstallationPath}/package.json`, 'utf8')) + output = spawnSync('npm', ['i', `${customHubotPackage.name}-${customHubotPackage.version}.tgz`]) + console.log(`npm i ${customHubotPackage.name}-${customHubotPackage.version}.tgz`, output.stderr.toString(), output.stdout.toString()) + } else { + output = spawnSync('npm', ['i', 'hubot@latest']) + } + output = spawnSync('npm', ['i', 'hubot-help@latest', 'hubot-rules@latest', 'hubot-diagnostics@latest'].concat([options.adapter]).filter(Boolean)) + console.log('npm i', output.stderr.toString(), output.stdout.toString()) spawnSync('mkdir', ['scripts']) spawnSync('touch', ['external-scripts.json']) @@ -50,6 +61,7 @@ export default (robot) => { packageJson.scripts = { start: 'hubot' } + packageJson.description = 'A simple helpful robot for your Company' if (options.adapter) { packageJson.scripts.start += ` --adapter ${options.adapter}` } @@ -79,7 +91,7 @@ export default (robot) => { console.log('.env file not found, continuing to the next operation.') } } -module.exports = (hubotDirectory, options) => { +export default (hubotDirectory, options) => { try { runCommands(hubotDirectory, options) } catch (error) { diff --git a/src/httpclient.js b/src/HttpClient.mjs similarity index 97% rename from src/httpclient.js rename to src/HttpClient.mjs index cb8b14afc..7a0eddc90 100644 --- a/src/httpclient.js +++ b/src/HttpClient.mjs @@ -33,10 +33,10 @@ Implement a phased approach to deprecate `robot.http` all together in favor of ` 2. Add a deprecation warning to `robot.http` 3. Remove `robot.http` in a future release */ -const path = require('path') -const http = require('http') -const https = require('https') -const qs = require('querystring') +import path from 'node:path' +import http from 'node:http' +import https from 'node:https' +import qs from 'node:querystring' const nonPassThroughOptions = [ 'headers', 'hostname', 'encoding', 'auth', 'port', @@ -308,5 +308,8 @@ const reduce = function (a, b) { } return a } - -exports.create = (url, options) => new ScopedClient(url, options) +export default { + create (url, options) { + return new ScopedClient(url, options) + } +} diff --git a/src/listener.js b/src/Listener.mjs similarity index 95% rename from src/listener.js rename to src/Listener.mjs index f31ed6717..d58b1ff05 100644 --- a/src/listener.js +++ b/src/Listener.mjs @@ -1,9 +1,8 @@ 'use strict' -const inspect = require('util').inspect - -const TextMessage = require('./message').TextMessage -const Middleware = require('./middleware') +import { inspect } from 'node:util' +import { TextMessage } from './Message.mjs' +import Middleware from './Middleware.mjs' class Listener { // Listeners receive every message from the chat source and decide if they @@ -108,7 +107,7 @@ class TextListener extends Listener { } } -module.exports = { +export { Listener, TextListener } diff --git a/src/message.js b/src/Message.mjs similarity index 86% rename from src/message.js rename to src/Message.mjs index 9ad0d4492..b3eb04fb2 100644 --- a/src/message.js +++ b/src/Message.mjs @@ -1,6 +1,6 @@ 'use strict' -class Message { +export class Message { // Represents an incoming message from the chat. // // user - A User instance that sent the message. @@ -18,7 +18,7 @@ class Message { } } -class TextMessage extends Message { +export class TextMessage extends Message { // Represents an incoming message from the chat. // // user - A User instance that sent the message. @@ -52,23 +52,23 @@ class TextMessage extends Message { // user - A User instance for the user who entered. // text - Always null. // id - A String of the message ID. -class EnterMessage extends Message {} +export class EnterMessage extends Message {} // Represents an incoming user exit notification. // // user - A User instance for the user who left. // text - Always null. // id - A String of the message ID. -class LeaveMessage extends Message {} +export class LeaveMessage extends Message {} // Represents an incoming topic change notification. // // user - A User instance for the user who changed the topic. // text - A String of the new topic // id - A String of the message ID. -class TopicMessage extends TextMessage {} +export class TopicMessage extends TextMessage {} -class CatchAllMessage extends Message { +export class CatchAllMessage extends Message { // Represents a message that no matchers matched. // // message - The original message. @@ -78,7 +78,7 @@ class CatchAllMessage extends Message { } } -module.exports = { +export default { Message, TextMessage, EnterMessage, diff --git a/src/middleware.js b/src/Middleware.mjs similarity index 98% rename from src/middleware.js rename to src/Middleware.mjs index cd0f1cfcb..fd218762a 100644 --- a/src/middleware.js +++ b/src/Middleware.mjs @@ -42,4 +42,4 @@ class Middleware { } } -module.exports = Middleware +export default Middleware diff --git a/src/OptParse.js b/src/OptParse.mjs similarity index 93% rename from src/OptParse.js rename to src/OptParse.mjs index 6344150b6..5c940a34e 100644 --- a/src/OptParse.js +++ b/src/OptParse.mjs @@ -1,4 +1,4 @@ -const EventEmitter = require('node:events') +import EventEmitter from 'node:events' class OptParse extends EventEmitter { constructor (switches) { super() @@ -41,4 +41,4 @@ ${this.switches.map(([key, description]) => ` ${key}, ${description}`).join('\n } } -module.exports = OptParse +export default OptParse diff --git a/src/response.js b/src/Response.mjs similarity index 99% rename from src/response.js rename to src/Response.mjs index 3f0f1fc7b..d1af23297 100644 --- a/src/response.js +++ b/src/Response.mjs @@ -123,4 +123,4 @@ class Response { } } -module.exports = Response +export default Response diff --git a/src/robot.js b/src/Robot.mjs similarity index 90% rename from src/robot.js rename to src/Robot.mjs index 1882d1d06..ecd0141a6 100644 --- a/src/robot.js +++ b/src/Robot.mjs @@ -1,21 +1,23 @@ 'use strict' -const EventEmitter = require('events').EventEmitter -const fs = require('fs') -const path = require('path') -const pathToFileURL = require('url').pathToFileURL -const pino = require('pino') -const HttpClient = require('./httpclient') - -const Brain = require('./brain') -const Response = require('./response') -const Listener = require('./listener') -const Message = require('./message') -const Middleware = require('./middleware') - -const HUBOT_DEFAULT_ADAPTERS = ['campfire', 'shell'] +import EventEmitter from 'node:events' +import fs from 'node:fs' +import path from 'node:path' +import { pathToFileURL, fileURLToPath } from 'node:url' +import pino from 'pino' +import HttpClient from './HttpClient.mjs' +import Brain from './Brain.mjs' +import Response from './Response.mjs' +import { Listener, TextListener } from './Listener.mjs' +import Message from './Message.mjs' +import Middleware from './Middleware.mjs' + +const HUBOT_DEFAULT_ADAPTERS = ['Campfire', 'Shell'] const HUBOT_DOCUMENTATION_SECTIONS = ['description', 'dependencies', 'configuration', 'commands', 'notes', 'author', 'authors', 'examples', 'tags', 'urls'] +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + class Robot { // Robots receive messages from a chat source (Campfire, irc, etc), and // dispatch them to matching listeners. @@ -37,6 +39,14 @@ class Robot { this.brain = new Brain(this) this.alias = alias this.adapter = null + this.adaptername = 'Shell' + if (adapter && typeof (adapter) === 'object') { + this.adapter = adapter + this.adapterName = adapter.name ?? adapter.constructor.name + } else { + this.adapterName = adapter ?? this.adaptername + } + this.shouldEnableHttpd = httpd ?? true this.datastore = null this.Response = Response @@ -56,7 +66,6 @@ class Robot { this.globalHttpOptions = {} this.parseVersion() - this.adapterName = adapter ?? 'shell' this.errorHandlers = [] this.on('error', (err, res) => { @@ -78,7 +87,7 @@ class Robot { // // Returns nothing. listen (matcher, options, callback) { - this.listeners.push(new Listener.Listener(this, matcher, options, callback)) + this.listeners.push(new Listener(this, matcher, options, callback)) } // Public: Adds a Listener that attempts to match incoming messages based on @@ -91,7 +100,7 @@ class Robot { // // Returns nothing. hear (regex, options, callback) { - this.listeners.push(new Listener.TextListener(this, regex, options, callback)) + this.listeners.push(new TextListener(this, regex, options, callback)) } // Public: Adds a Listener that attempts to match incoming messages directed @@ -328,12 +337,8 @@ class Robot { } } - async loadcoffee (filePath) { - return await this.loadjs(filePath) - } - async loadjs (filePath) { - const script = require(filePath) + const script = (await import(filePath)).default if (typeof script === 'function') { script(this) } else { @@ -352,7 +357,7 @@ class Robot { const full = path.join(filepath, path.basename(filename)) // see https://github.com/hubotio/hubot/issues/1355 - if (['js', 'mjs', 'coffee'].indexOf(ext) === -1) { + if (['js', 'mjs'].indexOf(ext) === -1) { this.logger.debug(`Skipping unsupported file type ${full}`) return } @@ -386,15 +391,19 @@ class Robot { // packages - An Array of packages containing hubot scripts to load. // // Returns nothing. - loadExternalScripts (packages) { + async loadExternalScripts (packages) { this.logger.debug('Loading external-scripts from npm packages') try { if (Array.isArray(packages)) { - return packages.forEach(pkg => require(pkg)(this)) + for await (const pkg of packages) { + (await import(pkg)).default(this) + } + return + } + for await (const key of Object.keys(packages)) { + (await import(key)).default(this, packages[key]) } - - Object.keys(packages).forEach(key => require(key)(this, packages[key])) } catch (error) { this.logger.error(`Error loading scripts from npm package - ${error.stack}`) throw error @@ -413,9 +422,9 @@ class Robot { const limit = process.env.EXPRESS_LIMIT || '100kb' const paramLimit = parseInt(process.env.EXPRESS_PARAMETER_LIMIT) || 1000 - const express = require('express') - const basicAuth = require('express-basic-auth') - const multipart = require('connect-multiparty') + const express = (await import('express')).default + const basicAuth = (await import('express-basic-auth')).default + const multipart = (await import('connect-multiparty')).default const app = express() @@ -475,19 +484,23 @@ class Robot { // // Returns nothing. async loadAdapter (adapterPath = null) { + if (this.adapter) { + this.adapter = await this.adapter.use(this) + return + } this.logger.debug(`Loading adapter ${adapterPath ?? 'from npmjs:'} ${this.adapterName}`) - const ext = path.extname(adapterPath ?? '') ?? '.js' + const ext = path.extname(adapterPath ?? '') ?? '.mjs' try { if (Array.from(HUBOT_DEFAULT_ADAPTERS).indexOf(this.adapterName) > -1) { - this.adapter = this.requireAdapterFrom(path.resolve(path.join(__dirname, 'adapters', this.adapterName))) - } else if (['.js', '.cjs', '.coffee'].includes(ext)) { - this.adapter = this.requireAdapterFrom(path.resolve(adapterPath)) + this.adapter = await this.requireAdapterFrom(path.resolve(path.join(__dirname, 'adapters', `${this.adapterName}.mjs`))) + } else if (['.js', '.cjs'].includes(ext)) { + this.adapter = await this.requireAdapterFrom(path.resolve(adapterPath)) } else if (['.mjs'].includes(ext)) { this.adapter = await this.importAdapterFrom(pathToFileURL(path.resolve(adapterPath)).href) } else { const adapterPathInCurrentWorkingDirectory = this.adapterName try { - this.adapter = this.requireAdapterFrom(adapterPathInCurrentWorkingDirectory) + this.adapter = await this.requireAdapterFrom(adapterPathInCurrentWorkingDirectory) } catch (err) { if (err.name === 'SyntaxError') { this.adapter = await this.importAdapterFrom(adapterPathInCurrentWorkingDirectory) @@ -502,8 +515,8 @@ class Robot { } } - requireAdapterFrom (adapaterPath) { - return require(adapaterPath).use(this) + async requireAdapterFrom (adapaterPath) { + return await this.importAdapterFrom(adapaterPath) } async importAdapterFrom (adapterPath) { @@ -519,12 +532,12 @@ class Robot { // Private: load help info from a loaded script. // - // path - A String path to the file on disk. + // filePath - A String path to the file on disk. // // Returns nothing. - parseHelp (path) { + parseHelp (filePath) { const scriptDocumentation = {} - const body = fs.readFileSync(require.resolve(path), 'utf-8') + const body = fs.readFileSync(path.resolve(filePath), 'utf-8') const useStrictHeaderRegex = /^["']use strict['"];?\s+/ const lines = body.replace(useStrictHeaderRegex, '').split(/(?:\n|\r\n|\r)/) @@ -661,7 +674,7 @@ class Robot { // // Returns a String of the version number. parseVersion () { - const pkg = require(path.join(__dirname, '..', 'package.json')) + const pkg = fs.readFileSync(path.join(__dirname, '..', 'package.json')) this.version = pkg.version return this.version @@ -720,8 +733,6 @@ class Robot { } } -module.exports = Robot - function isCatchAllMessage (message) { return message instanceof Message.CatchAllMessage } @@ -748,9 +759,7 @@ function removeCommentPrefix (line) { return line.replace(/^[#/]+\s*/, '') } -function extend (obj/* , ...sources */) { - const sources = [].slice.call(arguments, 1) - +function extend (obj, ...sources) { sources.forEach((source) => { if (typeof source !== 'object') { return @@ -763,3 +772,5 @@ function extend (obj/* , ...sources */) { return obj } + +export default Robot diff --git a/src/user.js b/src/User.mjs similarity index 93% rename from src/user.js rename to src/User.mjs index 7b383a234..9836e23fd 100644 --- a/src/user.js +++ b/src/User.mjs @@ -1,6 +1,6 @@ 'use strict' -const DataStoreUnavailable = require('./datastore').DataStoreUnavailable +import { DataStoreUnavailable } from './DataStore.mjs' class User { // Represents a participating user in the chat. @@ -62,4 +62,4 @@ class User { } } -module.exports = User +export default User diff --git a/src/adapters/campfire.js b/src/adapters/Campfire.mjs similarity index 96% rename from src/adapters/campfire.js rename to src/adapters/Campfire.mjs index be15effb6..49d02cc51 100644 --- a/src/adapters/campfire.js +++ b/src/adapters/Campfire.mjs @@ -1,16 +1,9 @@ 'use strict' -const HTTPS = require('https') -const EventEmitter = require('events').EventEmitter - -const Adapter = require('../adapter') - -const Message = require('../message') - -const TextMessage = Message.TextMessage -const EnterMessage = Message.EnterMessage -const LeaveMessage = Message.LeaveMessage -const TopicMessage = Message.TopicMessage +import HTTPS from 'node:https' +import EventEmitter from 'node:events' +import Adapter from '../Adapter.mjs' +import { TextMessage, EnterMessage, LeaveMessage, TopicMessage } from '../Message.mjs' class Campfire extends Adapter { send (envelope/* , ...strings */) { @@ -157,8 +150,6 @@ class Campfire extends Adapter { } } -exports.use = robot => new Campfire(robot) - class CampfireStreaming extends EventEmitter { constructor (options, robot) { super() @@ -392,3 +383,9 @@ class CampfireStreaming extends EventEmitter { return request.on('error', err => logger.error(`Campfire request error: ${err}`)) } } + +export default { + use (robot) { + return new Campfire(robot) + } +} diff --git a/src/adapters/shell.js b/src/adapters/Shell.mjs similarity index 91% rename from src/adapters/shell.js rename to src/adapters/Shell.mjs index bd9233ece..dadfc9056 100644 --- a/src/adapters/shell.js +++ b/src/adapters/Shell.mjs @@ -1,9 +1,9 @@ 'use strict' -const fs = require('fs') -const readline = require('node:readline') -const Adapter = require('../adapter') -const { TextMessage } = require('../message') +import fs from 'node:fs' +import readline from 'node:readline' +import Adapter from '../Adapter.mjs' +import { TextMessage } from '../Message.mjs' const historySize = process.env.HUBOT_SHELL_HISTSIZE != null ? parseInt(process.env.HUBOT_SHELL_HISTSIZE) : 1024 const historyPath = '.hubot_history' @@ -16,7 +16,7 @@ const completer = line => { } const showHelp = () => { console.log('usage:') - console.log('\\q, exit - close shell and exit') + console.log('\\q, exit - close Shell and exit') console.log('\\?, help - show this help') console.log('\\c, clear - clear screen') } @@ -112,4 +112,8 @@ class Shell extends Adapter { // Prevent output buffer "swallowing" every other character on OSX / Node version > 16.19.0. process.stdout._handle.setBlocking(false) -exports.use = robot => new Shell(robot) +export default { + use (robot) { + return new Shell(robot) + } +} diff --git a/src/datastores/memory.js b/src/datastores/Memory.mjs similarity index 79% rename from src/datastores/memory.js rename to src/datastores/Memory.mjs index 43268ec26..b3c456d7d 100644 --- a/src/datastores/memory.js +++ b/src/datastores/Memory.mjs @@ -1,6 +1,6 @@ 'use strict' -const DataStore = require('../datastore.js').DataStore +import { DataStore } from '../DataStore.mjs' class InMemoryDataStore extends DataStore { constructor (robot) { @@ -20,4 +20,4 @@ class InMemoryDataStore extends DataStore { } } -module.exports = InMemoryDataStore +export default InMemoryDataStore diff --git a/test/adapter_test.js b/test/Adapter_test.mjs similarity index 91% rename from test/adapter_test.js rename to test/Adapter_test.mjs index dd4f0eab1..b902727cc 100644 --- a/test/adapter_test.js +++ b/test/Adapter_test.mjs @@ -1,9 +1,10 @@ 'use strict' -const { describe, it, beforeEach, afterEach } = require('node:test') -const assert = require('assert/strict') -const Adapter = require('../src/adapter') -const { TextMessage } = require('../src/message.js') -const User = require('../src/user.js') + +import { describe, it, beforeEach, afterEach } from 'node:test' +import assert from 'node:assert/strict' +import Adapter from '../src/Adapter.mjs' +import { TextMessage } from '../src/Message.mjs' +import User from '../src/User.mjs' describe('Adapter', () => { let robot = null diff --git a/test/brain_test.js b/test/Brain_test.mjs similarity index 95% rename from test/brain_test.js rename to test/Brain_test.mjs index c2074223a..c285791d1 100644 --- a/test/brain_test.js +++ b/test/Brain_test.mjs @@ -1,16 +1,9 @@ 'use strict' -/* eslint-disable no-unused-expressions */ - -const { describe, it, beforeEach, afterEach } = require('node:test') -const assert = require('assert/strict') - -// Hubot classes -const User = require('../src/user.js') -const Robot = require('../src/robot.js') -const Brain = require('../src/brain.js') -const { hook, reset } = require('./fixtures/RequireMocker.js') -const mockAdapter = require('./fixtures/mock-adapter.js') +import { describe, it, beforeEach, afterEach } from 'node:test' +import assert from 'node:assert/strict' +import { User, Robot, Brain } from '../index.mjs' +import mockAdapter from './fixtures/MockAdapter.mjs' describe('Brain', () => { let mockRobot = null @@ -18,9 +11,8 @@ describe('Brain', () => { let user2 = null let user3 = null beforeEach(async () => { - hook('hubot-mock-adapter', mockAdapter) - mockRobot = new Robot('hubot-mock-adapter', false, 'TestHubot') - await mockRobot.loadAdapter('hubot-mock-adapter') + mockRobot = new Robot(mockAdapter, false, 'TestHubot') + await mockRobot.loadAdapter() await mockRobot.run() user1 = mockRobot.brain.userForId('1', { name: 'Guy One' }) user2 = mockRobot.brain.userForId('2', { name: 'Guy One Two' }) @@ -28,7 +20,6 @@ describe('Brain', () => { }) afterEach(() => { mockRobot.shutdown() - reset() process.removeAllListeners() }) describe('Unit Tests', () => { diff --git a/test/datastore_test.js b/test/DataStore_test.mjs similarity index 94% rename from test/datastore_test.js rename to test/DataStore_test.mjs index 497672c31..9a4531d1d 100644 --- a/test/datastore_test.js +++ b/test/DataStore_test.mjs @@ -1,10 +1,9 @@ 'use strict' -const { describe, it, beforeEach, afterEach } = require('node:test') -const assert = require('assert/strict') - -const Brain = require('../src/brain.js') -const InMemoryDataStore = require('../src/datastores/memory.js') +import { describe, it, beforeEach, afterEach } from 'node:test' +import assert from 'node:assert/strict' +import InMemoryDataStore from '../src/datastores/Memory.mjs' +import Brain from '../src/Brain.mjs' describe('Datastore', () => { let robot = null diff --git a/test/hubot_test.js b/test/Hubot_test.mjs similarity index 76% rename from test/hubot_test.js rename to test/Hubot_test.mjs index 2c8663ba7..8f711b3a0 100644 --- a/test/hubot_test.js +++ b/test/Hubot_test.mjs @@ -1,19 +1,21 @@ 'use strict' -/* eslint-disable no-unused-expressions */ +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import path from 'node:path' +import { spawn } from 'node:child_process' +import { TextMessage, User } from '../index.mjs' +import { fileURLToPath } from 'node:url' -const { describe, it } = require('node:test') -const assert = require('assert/strict') +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) const root = __dirname.replace(/test$/, '') -const { TextMessage, User } = require('../index.js') -const path = require('node:path') -const { spawn } = require('child_process') -describe('Running bin/hubot.js', () => { +describe('Running bin/Hubot.mjs', () => { it('should load adapter from HUBOT_FILE environment variable', async () => { process.env.HUBOT_HTTPD = 'false' process.env.HUBOT_FILE = path.resolve(root, 'test', 'fixtures', 'MockAdapter.mjs') - const hubot = require('../bin/hubot.js') + const hubot = (await import('../bin/Hubot.mjs')).default await hubot.loadFile(path.resolve(root, 'test', 'fixtures'), 'TestScript.mjs') while (!hubot.adapter) { await new Promise(resolve => setTimeout(resolve, 100)) @@ -34,7 +36,7 @@ describe('Running bin/hubot.js', () => { }) it('should output a help message when run with --help', (t, done) => { - const hubot = process.platform === 'win32' ? spawn('node', ['./bin/hubot.js', '--help']) : spawn('./bin/hubot', ['--help']) + const hubot = process.platform === 'win32' ? spawn('node', ['./bin/Hubot.mjs', '--help']) : spawn('./bin/hubot', ['--help']) const expected = `Usage: hubot [options] -a, --adapter HUBOT_ADAPTER -f, --file HUBOT_FILE diff --git a/test/listener_test.js b/test/Listener_test.mjs similarity index 94% rename from test/listener_test.js rename to test/Listener_test.mjs index 4f6ac220e..e2a69c27c 100644 --- a/test/listener_test.js +++ b/test/Listener_test.mjs @@ -1,18 +1,8 @@ 'use strict' -/* eslint-disable no-unused-expressions */ - -const { describe, it } = require('node:test') -const assert = require('node:assert/strict') - -// Hubot classes -const EnterMessage = require('../src/message.js').EnterMessage -const TextMessage = require('../src/message.js').TextMessage -const Listener = require('../src/listener.js').Listener -const TextListener = require('../src/listener.js').TextListener -const Response = require('../src/response.js') -const User = require('../src/user.js') -const Middleware = require('../src/middleware.js') +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import { EnterMessage, TextMessage, Listener, TextListener, Response, User, Middleware } from '../index.mjs' describe('Listener', () => { const robot = { diff --git a/test/message_test.js b/test/Message_test.mjs similarity index 72% rename from test/message_test.js rename to test/Message_test.mjs index 9a6dbba91..075bdc307 100644 --- a/test/message_test.js +++ b/test/Message_test.mjs @@ -1,14 +1,8 @@ 'use strict' -/* eslint-disable no-unused-expressions */ - -const { describe, it } = require('node:test') -const assert = require('node:assert/strict') - -// Hubot classes -const User = require('../src/user') -const Message = require('../src/message').Message -const TextMessage = require('../src/message').TextMessage +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import { User, Message, TextMessage } from '../index.mjs' describe('Message', () => { const user = new User({ diff --git a/test/middleware_test.js b/test/Middleware_test.mjs similarity index 88% rename from test/middleware_test.js rename to test/Middleware_test.mjs index 79945031a..48ea178b1 100644 --- a/test/middleware_test.js +++ b/test/Middleware_test.mjs @@ -1,17 +1,9 @@ 'use strict' -/* eslint-disable no-unused-expressions */ - -const { describe, it, beforeEach, afterEach } = require('node:test') -const assert = require('node:assert/strict') - -// Hubot classes -const Robot = require('../src/robot') -const TextMessage = require('../src/message').TextMessage -const Response = require('../src/response') -const Middleware = require('../src/middleware') - -const { hook, reset } = require('./fixtures/RequireMocker.js') +import { describe, it, beforeEach, afterEach } from 'node:test' +import assert from 'node:assert/strict' +import { Robot, TextMessage, Response, Middleware } from '../index.mjs' +import mockAdapter from './fixtures/MockAdapter.mjs' describe('Middleware', () => { describe('Unit Tests', () => { @@ -98,8 +90,7 @@ describe('Middleware', () => { let testListener = null let testMessage = null beforeEach(async () => { - hook('hubot-mock-adapter', require('./fixtures/mock-adapter.js')) - robot = new Robot('hubot-mock-adapter', false, 'TestHubot') + robot = new Robot(mockAdapter, false, 'TestHubot') await robot.loadAdapter() await robot.run @@ -122,7 +113,6 @@ describe('Middleware', () => { }) afterEach(() => { - reset() robot.shutdown() }) @@ -170,5 +160,5 @@ describe('Middleware', () => { }) function __guard__ (value, transform) { - (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined } diff --git a/test/OptParse-test.js b/test/OptParse-test.mjs similarity index 82% rename from test/OptParse-test.js rename to test/OptParse-test.mjs index 5782a413d..d7d1538e5 100644 --- a/test/OptParse-test.js +++ b/test/OptParse-test.mjs @@ -1,11 +1,11 @@ -const { describe, it } = require('node:test') -const assert = require('node:assert/strict') -const OptParse = require('../src/OptParse.js') +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import OptParse from '../src/OptParse.mjs' describe('CLI Argument Parsing', () => { it('should parse arguments into options', () => { const switches = [ - ['-a', '--adapter HUBOT_ADAPTER', 'The Adapter to use, e.g. "shell" (to load the default hubot shell adapter)'], + ['-a', '--adapter HUBOT_ADAPTER', 'The Adapter to use, e.g. "Shell" (to load the default hubot Shell adapter)'], ['-f', '--file HUBOT_FILE', 'Path to adapter file, e.g. "./adapters/CustomAdapter.mjs"'], ['-c', '--create HUBOT_CREATE', 'Create a deployable hubot'], ['-d', '--disable-httpd HUBOT_HTTPD', 'Disable the HTTP server'], @@ -38,8 +38,8 @@ describe('CLI Argument Parsing', () => { Parser.on('alias', (opt, value) => { options.alias = value }) - Parser.parse(['-a', 'shell', '-d', '--alias', 'bot']) - assert.deepEqual(options.adapter, 'shell') + Parser.parse(['-a', 'Shell', '-d', '--alias', 'bot']) + assert.deepEqual(options.adapter, 'Shell') assert.deepEqual(options.enableHttpd, false) assert.deepEqual(options.alias, 'bot') }) diff --git a/test/robot_test.js b/test/Robot_test.mjs similarity index 92% rename from test/robot_test.js rename to test/Robot_test.mjs index 0f2889783..5b1464cc3 100644 --- a/test/robot_test.js +++ b/test/Robot_test.mjs @@ -1,21 +1,10 @@ 'use strict' -/* eslint-disable no-unused-expressions */ -require('coffeescript/register.js') -const { describe, it, beforeEach, afterEach } = require('node:test') -const assert = require('assert/strict') - -// Hubot classes -const Robot = require('../src/robot.js') -const CatchAllMessage = require('../src/message.js').CatchAllMessage -const EnterMessage = require('../src/message.js').EnterMessage -const LeaveMessage = require('../src/message.js').LeaveMessage -const TextMessage = require('../src/message.js').TextMessage -const TopicMessage = require('../src/message.js').TopicMessage -const User = require('../src/user.js') -const path = require('path') -const { hook, reset } = require('./fixtures/RequireMocker.js') -const mockAdapter = require('./fixtures/mock-adapter.js') +import { describe, it, beforeEach, afterEach } from 'node:test' +import assert from 'node:assert/strict' +import path from 'node:path' +import { Robot, CatchAllMessage, EnterMessage, LeaveMessage, TextMessage, TopicMessage, User } from '../index.mjs' +import mockAdapter from './fixtures/MockAdapter.mjs' describe('Robot', () => { describe('#http', () => { let robot = null @@ -65,8 +54,10 @@ describe('Robot', () => { describe('#respondPattern', () => { let robot = null - beforeEach(() => { - robot = new Robot('hubot-mock-adapter', false, 'TestHubot', 't-bot') + beforeEach(async () => { + robot = new Robot(mockAdapter, false, 'TestHubot', 't-bot') + await robot.loadAdapter() + await robot.run() }) afterEach(() => { robot.shutdown() @@ -186,8 +177,10 @@ describe('Robot', () => { describe('#receive', () => { let robot = null let user = null - beforeEach(() => { - robot = new Robot('hubot-mock-adapter', false, 'TestHubot') + beforeEach(async () => { + robot = new Robot(mockAdapter, false, 'TestHubot') + await robot.loadAdapter() + await robot.run() user = new User('1', { name: 'node', room: '#test' }) }) afterEach(() => { @@ -272,8 +265,10 @@ describe('Robot', () => { }) describe('#loadFile', () => { let robot = null - beforeEach(() => { - robot = new Robot('hubot-mock-adapter', false, 'TestHubot') + beforeEach(async () => { + robot = new Robot(mockAdapter, false, 'TestHubot') + await robot.loadAdapter() + await robot.run() }) afterEach(() => { robot.shutdown() @@ -334,14 +329,12 @@ describe('Robot', () => { describe('Sending API', () => { let robot = null beforeEach(async () => { - hook('hubot-mock-adapter', mockAdapter) - robot = new Robot('hubot-mock-adapter', false, 'TestHubot') + robot = new Robot(mockAdapter, false, 'TestHubot') await robot.loadAdapter() await robot.run() }) afterEach(() => { robot.shutdown() - reset() }) it('#send: delegates to adapter "send" with proper context', async () => { @@ -378,8 +371,10 @@ describe('Robot', () => { describe('Listener Registration', () => { let robot = null let user = null - beforeEach(() => { - robot = new Robot('hubot-mock-adapter', false, 'TestHubot') + beforeEach(async () => { + robot = new Robot(mockAdapter, false, 'TestHubot') + await robot.loadAdapter() + await robot.run() user = new User('1', { name: 'node', room: '#test' }) }) afterEach(() => { @@ -535,8 +530,10 @@ describe('Robot', () => { describe('Message Processing', () => { let robot = null let user = null - beforeEach(() => { - robot = new Robot('hubot-mock-adapter', false, 'TestHubot') + beforeEach(async () => { + robot = new Robot(mockAdapter, false, 'TestHubot') + await robot.loadAdapter() + await robot.run() user = new User('1', { name: 'node', room: '#test' }) }) afterEach(() => { @@ -647,8 +644,10 @@ describe('Robot', () => { describe('Listener Middleware', () => { let robot = null let user = null - beforeEach(() => { - robot = new Robot('hubot-mock-adapter', false, 'TestHubot') + beforeEach(async () => { + robot = new Robot(mockAdapter, false, 'TestHubot') + await robot.loadAdapter() + await robot.run() user = new User('1', { name: 'node', room: '#test' }) }) afterEach(() => { @@ -726,8 +725,10 @@ describe('Robot', () => { describe('Receive Middleware', () => { let robot = null let user = null - beforeEach(() => { - robot = new Robot('hubot-mock-adapter', false, 'TestHubot') + beforeEach(async () => { + robot = new Robot(mockAdapter, false, 'TestHubot') + await robot.loadAdapter() + await robot.run() user = new User('1', { name: 'node', room: '#test' }) }) afterEach(() => { @@ -841,8 +842,7 @@ describe('Robot', () => { let robot = null let user = null beforeEach(async () => { - hook('hubot-mock-adapter', mockAdapter) - robot = new Robot('hubot-mock-adapter', false, 'TestHubot') + robot = new Robot(mockAdapter, false, 'TestHubot') user = new User('1', { name: 'node', room: '#test' }) robot.alias = 'Hubot' await robot.loadAdapter() @@ -966,29 +966,6 @@ describe('Robot', () => { await robot.receive(new TextMessage('tester', 'hubot test')) }) }) - describe('Robot Coffeescript', () => { - let robot = null - beforeEach(async () => { - robot = new Robot('MockAdapter', false, 'TestHubot') - robot.alias = 'Hubot' - await robot.loadAdapter('./test/fixtures/MockAdapter.coffee') - await robot.loadFile(path.resolve('./test/fixtures/'), 'TestScript.coffee') - await robot.run() - }) - afterEach(() => { - robot.shutdown() - }) - it('should load a CoffeeScript adapter from a file', async () => { - assert.equal(robot.adapter.name, 'MockAdapter') - }) - it('should load a coffeescript file and respond to a message', async () => { - const sent = async (envelop, strings) => { - assert.deepEqual(strings, ['test response from coffeescript']) - } - robot.adapter.on('send', sent) - await robot.receive(new TextMessage('tester', 'hubot test')) - }) - }) describe('Robot Defaults', () => { let robot = null beforeEach(async () => { @@ -1001,15 +978,14 @@ describe('Robot', () => { robot.shutdown() process.removeAllListeners() }) - it('should load the builtin shell adapter by default', async () => { + it('should load the builtin Shell adapter by default', async () => { assert.equal(robot.adapter.name, 'Shell') }) }) describe('Robot HTTP Service', () => { it('should start a web service', async () => { process.env.PORT = 0 - hook('hubot-mock-adapter', mockAdapter) - const robot = new Robot('hubot-mock-adapter', true, 'TestHubot') + const robot = new Robot(mockAdapter, true, 'TestHubot') await robot.loadAdapter() await robot.run() const port = robot.server.address().port @@ -1017,7 +993,6 @@ describe('Robot', () => { assert.equal(res.status, 404) assert.match(await res.text(), /Cannot GET \/hubot\/version/ig) robot.shutdown() - reset() delete process.env.PORT }) }) diff --git a/test/shell_test.js b/test/Shell_test.mjs similarity index 89% rename from test/shell_test.js rename to test/Shell_test.mjs index 65fca53d8..6568ac95c 100644 --- a/test/shell_test.js +++ b/test/Shell_test.mjs @@ -1,16 +1,13 @@ 'use strict' -const { describe, it, beforeEach } = require('node:test') -const assert = require('assert/strict') - -const Robot = require('../src/robot') -const { TextMessage } = require('../src/message.js') -const User = require('../src/user.js') +import { describe, it, beforeEach } from 'node:test' +import assert from 'node:assert/strict' +import { Robot, TextMessage, User } from '../index.mjs' describe('Shell Adapter', () => { let robot = null beforeEach(async () => { - robot = new Robot('shell', false, 'TestHubot') + robot = new Robot('Shell', false, 'TestHubot') await robot.loadAdapter() }) diff --git a/test/user_test.js b/test/User_test.mjs similarity index 88% rename from test/user_test.js rename to test/User_test.mjs index 2fb7587dc..d27cb90f5 100644 --- a/test/user_test.js +++ b/test/User_test.mjs @@ -1,8 +1,8 @@ 'use strict' -const { describe, it } = require('node:test') -const assert = require('assert/strict') -const User = require('../src/user') +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import { User } from '../index.mjs' describe('User', () => describe('new', function () { diff --git a/test/fixtures/MockAdapter.coffee b/test/fixtures/MockAdapter.coffee deleted file mode 100644 index 525561fd1..000000000 --- a/test/fixtures/MockAdapter.coffee +++ /dev/null @@ -1,10 +0,0 @@ -{ Adapter } = require "../../index.js" -class MockAdapter extends Adapter - constructor: (robot, @options) -> - super robot - @name = "MockAdapter" - run: -> - @emit "connected" - -module.exports.use = (robot) -> - new MockAdapter robot \ No newline at end of file diff --git a/test/fixtures/MockAdapter.mjs b/test/fixtures/MockAdapter.mjs index 5366ba072..894c9a4f5 100644 --- a/test/fixtures/MockAdapter.mjs +++ b/test/fixtures/MockAdapter.mjs @@ -1,8 +1,8 @@ 'use strict' -import { Adapter } from '../../es2015.js' // eslint-disable-line import/no-unresolved +import { Adapter } from '../../index.mjs' -class MockAdapter extends Adapter { +export class MockAdapter extends Adapter { constructor (robot) { super(robot) this.name = 'MockAdapter' @@ -33,9 +33,6 @@ class MockAdapter extends Adapter { this.emit('closed') } } -export { - MockAdapter -} export default { use (robot) { return new MockAdapter(robot) diff --git a/test/fixtures/RequireMocker.js b/test/fixtures/RequireMocker.js deleted file mode 100644 index 291d1d3b9..000000000 --- a/test/fixtures/RequireMocker.js +++ /dev/null @@ -1,19 +0,0 @@ -const Module = require('module') -const originalRequire = Module.prototype.require -const hookModuleToReturnMockFromRequire = (module, mock) => { - Module.prototype.require = function () { - if (arguments[0] === module) { - return mock - } - return originalRequire.apply(this, arguments) - } -} - -const resetModuleMocks = () => { - Module.prototype.require = originalRequire -} - -module.exports = { - hook: hookModuleToReturnMockFromRequire, - reset: resetModuleMocks -} diff --git a/test/fixtures/TestScript.coffee b/test/fixtures/TestScript.coffee deleted file mode 100644 index 8aeaba1ce..000000000 --- a/test/fixtures/TestScript.coffee +++ /dev/null @@ -1,9 +0,0 @@ -# Description: A test script for the robot to load -# -# Commands: -# hubot test - Responds with a test response -# - -module.exports = (robot) -> - robot.respond 'test', (res) -> - res.send 'test response from coffeescript' diff --git a/test/fixtures/mock-adapter.js b/test/fixtures/mock-adapter.js deleted file mode 100644 index e9d947bda..000000000 --- a/test/fixtures/mock-adapter.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict' - -const Adapter = require('../..').Adapter - -class MockAdapter extends Adapter { - async send (envelope, ...strings) { - this.emit('send', envelope, ...strings) - } - - async reply (envelope, ...strings) { - this.emit('reply', envelope, ...strings) - } - - async topic (envelope, ...strings) { - this.emit('topic', envelope, ...strings) - } - - async play (envelope, ...strings) { - this.emit('play', envelope, ...strings) - } - - run () { - this.emit('connected') - } - - close () { - this.emit('closed') - } -} - -module.exports.use = robot => new MockAdapter(robot) diff --git a/test/es2015_test.js b/test/index_test.mjs similarity index 81% rename from test/es2015_test.js rename to test/index_test.mjs index a87ec62e6..da4de9668 100644 --- a/test/es2015_test.js +++ b/test/index_test.mjs @@ -1,29 +1,14 @@ 'use strict' -/* eslint-disable no-unused-expressions */ - -const { describe, it } = require('node:test') -const assert = require('assert/strict') - -const { hook, reset } = require('./fixtures/RequireMocker.js') - -// Hubot classes -const Hubot = require('../es2015.js') -const User = Hubot.User -const Brain = Hubot.Brain -const Robot = Hubot.Robot -const Adapter = Hubot.Adapter -const Response = Hubot.Response -const Listener = Hubot.Listener -const TextListener = Hubot.TextListener -const Message = Hubot.Message -const TextMessage = Hubot.TextMessage -const EnterMessage = Hubot.EnterMessage -const LeaveMessage = Hubot.LeaveMessage -const TopicMessage = Hubot.TopicMessage -const CatchAllMessage = Hubot.CatchAllMessage - -describe('hubot/es2015', () => { +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import { + Adapter, User, Brain, Robot, Response, Listener, TextListener, + Message, TextMessage, EnterMessage, LeaveMessage, TopicMessage, CatchAllMessage, loadBot +} from '../index.mjs' +import mockAdapter from './fixtures/MockAdapter.mjs' + +describe('hubot/index', () => { it('exports User class', () => { class MyUser extends User {} const user = new MyUser('id123', { foo: 'bar' }) @@ -48,15 +33,12 @@ describe('hubot/es2015', () => { }) it('exports Robot class', async () => { - hook('hubot-mock-adapter', require('./fixtures/mock-adapter.js')) - class MyRobot extends Robot {} - const robot = new MyRobot('hubot-mock-adapter', false, 'TestHubot') + const robot = new MyRobot(mockAdapter, false, 'TestHubot') await robot.loadAdapter() assert.ok(robot instanceof Robot) assert.equal(robot.name, 'TestHubot') robot.shutdown() - reset() }) it('exports Adapter class', () => { @@ -179,8 +161,8 @@ describe('hubot/es2015', () => { }) it('exports loadBot function', () => { - assert.ok(Hubot.loadBot && typeof Hubot.loadBot === 'function') - const robot = Hubot.loadBot('adapter', false, 'botName', 'botAlias') + assert.ok(loadBot && typeof loadBot === 'function') + const robot = loadBot('adapter', false, 'botName', 'botAlias') assert.equal(robot.name, 'botName') assert.equal(robot.alias, 'botAlias') robot.shutdown()