From 2e609aa0ce28d0dc7fcc77f97e3418ecb3e6d763 Mon Sep 17 00:00:00 2001 From: Joey Guerra Date: Sat, 11 Nov 2023 22:43:16 -0600 Subject: [PATCH 1/3] Revert "fix(hubot): hubot --help wasn't working (#1693)" This reverts commit 5641d4c728891b5d8ea419ca5635b77aaf396343. --- bin/hubot.js | 2 +- src/OptParse.js | 5 ----- test/hubot_test.js | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/bin/hubot.js b/bin/hubot.js index 2f7ab841f..6b42721b7 100755 --- a/bin/hubot.js +++ b/bin/hubot.js @@ -33,7 +33,7 @@ const options = { } const Parser = new OptParse(switches) -Parser.banner = 'Usage: hubot [options]' +Parser.banner = 'Usage hubot [options]' Parser.on('adapter', (opt, value) => { options.adapter = value diff --git a/src/OptParse.js b/src/OptParse.js index 6344150b6..f5b2add1a 100644 --- a/src/OptParse.js +++ b/src/OptParse.js @@ -34,11 +34,6 @@ class OptParse extends EventEmitter { } return options } - - toString () { - return `${this.banner} -${this.switches.map(([key, description]) => ` ${key}, ${description}`).join('\n')}` - } } module.exports = OptParse diff --git a/test/hubot_test.js b/test/hubot_test.js index 2c8663ba7..8b0164136 100644 --- a/test/hubot_test.js +++ b/test/hubot_test.js @@ -10,7 +10,7 @@ const path = require('node:path') const { spawn } = require('child_process') describe('Running bin/hubot.js', () => { - it('should load adapter from HUBOT_FILE environment variable', async () => { + it('should load adapter from HUBOT_FILE environment variable', async function () { process.env.HUBOT_HTTPD = 'false' process.env.HUBOT_FILE = path.resolve(root, 'test', 'fixtures', 'MockAdapter.mjs') const hubot = require('../bin/hubot.js') From 852819c52ba53abe5fc523052cb988736c0119fe Mon Sep 17 00:00:00 2001 From: Joey Guerra Date: Sat, 11 Nov 2023 22:45:46 -0600 Subject: [PATCH 2/3] fix: Accidently releasing the removal of CoffeeScript in a minor version. Revert "BREAKING CHANGES: Removed CoffeeScript support; .coffee files will no longer load (#1694)" This reverts commit f11e5afd2154e2b5bd8dcdc1e1b27e28d66bb461. --- bin/e2e-test.sh | 2 +- bin/hubot | 10 +++- docs/adapters/development.md | 14 +++-- docs/deploying/azure.md | 7 +-- docs/scripting.md | 74 ++++++++++++-------------- es2015.js | 31 +++++++++++ index.js | 63 ++++++++++++---------- package-lock.json | 13 +++++ package.json | 1 + src/robot.js | 8 ++- test/{index_test.js => es2015_test.js} | 4 +- test/fixtures/MockAdapter.mjs | 2 +- test/fixtures/TestScript.coffee | 9 ++++ test/robot_test.js | 25 ++++++++- 14 files changed, 182 insertions(+), 81 deletions(-) create mode 100644 es2015.js rename test/{index_test.js => es2015_test.js} (98%) create mode 100644 test/fixtures/TestScript.coffee diff --git a/bin/e2e-test.sh b/bin/e2e-test.sh index dd3b4733e..36d7d2899 100755 --- a/bin/e2e-test.sh +++ b/bin/e2e-test.sh @@ -12,7 +12,7 @@ trap "{ CODE=$?; popd; rm -rf $TEMP_ROOT; exit $CODE; }" EXIT echo "$ create hubot in $TEMP_ROOT" echo "$ install Hubot from $HUBOT_FOLDER" npm init -y -npm i $HUBOT_FOLDER +npm i $HUBOT_FOLDER coffeescript ./node_modules/.bin/hubot --create myhubot cd myhubot diff --git a/bin/hubot b/bin/hubot index e7e2ac8bf..0467bb84d 100755 --- a/bin/hubot +++ b/bin/hubot @@ -1,3 +1,9 @@ -#!/usr/bin/env node +#!/usr/bin/env coffee -require('./hubot.js') \ No newline at end of file +# While all other files have been converted to JavaScript via https://github.com/github/hubot/pull/1347, +# we left the `bin/hubot` file to remain in CoffeeScript in order prevent +# breaking existing 3rd party adapters of which some are still written in +# CoffeeScript themselves. We will deprecate and eventually remove this file +# in a future version of hubot + +require './hubot.js' diff --git a/docs/adapters/development.md b/docs/adapters/development.md index 2261e5f9c..12c427fe9 100644 --- a/docs/adapters/development.md +++ b/docs/adapters/development.md @@ -10,8 +10,10 @@ permalink: /adapters/development.html 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: + ```javascript -const Adapter = require('hubot/index.js').Adapter; +const Adapter = require('hubot/es2015').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: @@ -58,7 +60,10 @@ exports.use = (robot) => new Sample(robot) "dependencies": { }, "peerDependencies": { - "hubot": ">= 11" + "hubot": ">=3.0" + }, + "devDependencies": { + "coffeescript": ">=1.2.0" } ``` @@ -112,7 +117,10 @@ Another option is to load the file from local disk. "dependencies": { }, "peerDependencies": { - "hubot": ">= 11" + "hubot": ">=9" + }, + "devDependencies": { + "coffeescript": ">=2.7.0" } ``` diff --git a/docs/deploying/azure.md b/docs/deploying/azure.md index 33c5c7a16..15f0efdde 100644 --- a/docs/deploying/azure.md +++ b/docs/deploying/azure.md @@ -37,12 +37,13 @@ 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 js extension - copy /Y "%DEPLOYMENT_TARGET%\node_modules\hubot\bin\hubot" "%DEPLOYMENT_TARGET%\node_modules\hubot\bin\hubot.js" + :: 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" Now, create a new file in the base directory of hubot called `server.js` and put these two lines into it: - module.exports = require('hubot/bin/hubot.js'); + require('coffeescript/register'); + module.exports = require('hubot/bin/hubot.coffee'); 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/scripting.md b/docs/scripting.md index 5218e2e2a..c9a7ed3de 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -839,7 +839,7 @@ Listener middleware inserts logic between the listener matching a message and th ## Listener Middleware Examples -A fully functioning example can be found in [hubot-rate-limit](https://github.com/michaelansel/hubot-rate-limit/blob/master/src/rate-limit.coffee) (Note: this is a coffee version, non-async/await, and will not work with the latest Hubot since CoffeeScript support was removed in version 11). +A fully functioning example can be found in [hubot-rate-limit](https://github.com/michaelansel/hubot-rate-limit/blob/master/src/rate-limit.coffee) (Note, this is a coffee version, non-async/await). A simple example of middleware logging command executions: @@ -1005,55 +1005,51 @@ You may also want to install: [Note: This section is still refering to Coffeescript, but we've update Hubot for Javascript. We'll have to replace this when we get a JavaScript example.] -Here is a sample script that tests the first couple of commands. +Here is a sample script that tests the first couple of commands in the [Hubot sample script](https://github.com/hubotio/generator-hubot/blob/master/generators/app/templates/scripts/example.coffee). This script uses *Mocha*, *chai*, *coffeescript*, and of course *hubot-test-helper*: -**test/example-test.mjs** +**test/example-test.coffee** -```javascript -import { describe, it } from 'node:test' -import assert from 'node:assert/strict' -import Helper from 'hubot-test-helper' +```coffeescript +Helper = require('hubot-test-helper') +chai = require 'chai' -const helper = new Helper('../scripts/example.mjs') +expect = chai.expect -describe('example script', () => { - let room = null - beforeEach(() => { - room = helper.createRoom() - }) +helper = new Helper('../scripts/example.coffee') - afterEach(() => - room.destroy() - )) +describe 'example script', -> + beforeEach -> + @room = helper.createRoom() - it("doesn't need badgers", async () => { - await room.user.say('alice', 'did someone call for a badger?') - assert.deepEqual(room.messages, [ - ['alice', 'did someone call for a badger?'] - ['hubot', 'Badgers? BADGERS? WE DON\'T NEED NO STINKIN BADGERS'] - ]) - }) - it("won't open the pod bay doors"), async () => { - await room.user.say('bob', '@hubot open the pod bay doors') - assert.deepEqual(room.messages, [ - ['bob', '@hubot open the pod bay doors'] - ['hubot', '@bob I\'m afraid I can\'t let you do that.'] - ]) - }) - it('will open the dutch doors'), async () => { - await room.user.say('bob', '@hubot open the dutch doors') - assert.deepEqual(room.messages, [ - ['bob', '@hubot open the dutch doors'] - ['hubot', '@bob Opening dutch doors'] - ]) - }) -} + afterEach -> + @room.destroy() + + it 'doesn\'t need badgers', -> + @room.user.say('alice', 'did someone call for a badger?').then => + expect(@room.messages).to.eql [ + ['alice', 'did someone call for a badger?'] + ['hubot', 'Badgers? BADGERS? WE DON\'T NEED NO STINKIN BADGERS'] + ] + + it 'won\'t open the pod bay doors', -> + @room.user.say('bob', '@hubot open the pod bay doors').then => + expect(@room.messages).to.eql [ + ['bob', '@hubot open the pod bay doors'] + ['hubot', '@bob I\'m afraid I can\'t let you do that.'] + ] + + it 'will open the dutch doors', -> + @room.user.say('bob', '@hubot open the dutch doors').then => + expect(@room.messages).to.eql [ + ['bob', '@hubot open the dutch doors'] + ['hubot', '@bob Opening dutch doors'] + ] ``` **sample output** ```sh -% node --test test/*.mjs +% mocha --require coffeescript/register test/*.coffee example script ✓ doesn't need badgers ✓ won't open the pod bay doors diff --git a/es2015.js b/es2015.js new file mode 100644 index 000000000..0c6f377ce --- /dev/null +++ b/es2015.js @@ -0,0 +1,31 @@ +'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 index 0c6f377ce..5c742efd4 100644 --- a/index.js +++ b/index.js @@ -1,31 +1,40 @@ 'use strict' +require('coffeescript/register') -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) +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/package-lock.json b/package-lock.json index a6156e3eb..e5553c666 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "9.1.0", "license": "MIT", "dependencies": { + "coffeescript": "^2.7.0", "connect-multiparty": "^2.2.0", "express": "^4.18.2", "express-basic-auth": "^1.2.1", @@ -669,6 +670,18 @@ "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 2de0daf95..3d49cf43c 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "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", diff --git a/src/robot.js b/src/robot.js index 9df585748..1882d1d06 100644 --- a/src/robot.js +++ b/src/robot.js @@ -328,6 +328,10 @@ class Robot { } } + async loadcoffee (filePath) { + return await this.loadjs(filePath) + } + async loadjs (filePath) { const script = require(filePath) if (typeof script === 'function') { @@ -348,7 +352,7 @@ class Robot { const full = path.join(filepath, path.basename(filename)) // see https://github.com/hubotio/hubot/issues/1355 - if (['js', 'mjs'].indexOf(ext) === -1) { + if (['js', 'mjs', 'coffee'].indexOf(ext) === -1) { this.logger.debug(`Skipping unsupported file type ${full}`) return } @@ -476,7 +480,7 @@ class Robot { 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'].includes(ext)) { + } else if (['.js', '.cjs', '.coffee'].includes(ext)) { this.adapter = this.requireAdapterFrom(path.resolve(adapterPath)) } else if (['.mjs'].includes(ext)) { this.adapter = await this.importAdapterFrom(pathToFileURL(path.resolve(adapterPath)).href) diff --git a/test/index_test.js b/test/es2015_test.js similarity index 98% rename from test/index_test.js rename to test/es2015_test.js index ac66b8061..a87ec62e6 100644 --- a/test/index_test.js +++ b/test/es2015_test.js @@ -8,7 +8,7 @@ const assert = require('assert/strict') const { hook, reset } = require('./fixtures/RequireMocker.js') // Hubot classes -const Hubot = require('../index.js') +const Hubot = require('../es2015.js') const User = Hubot.User const Brain = Hubot.Brain const Robot = Hubot.Robot @@ -23,7 +23,7 @@ const LeaveMessage = Hubot.LeaveMessage const TopicMessage = Hubot.TopicMessage const CatchAllMessage = Hubot.CatchAllMessage -describe('hubot/index', () => { +describe('hubot/es2015', () => { it('exports User class', () => { class MyUser extends User {} const user = new MyUser('id123', { foo: 'bar' }) diff --git a/test/fixtures/MockAdapter.mjs b/test/fixtures/MockAdapter.mjs index 5ebb9f4f9..5366ba072 100644 --- a/test/fixtures/MockAdapter.mjs +++ b/test/fixtures/MockAdapter.mjs @@ -1,6 +1,6 @@ 'use strict' -import { Adapter } from '../../index.js' +import { Adapter } from '../../es2015.js' // eslint-disable-line import/no-unresolved class MockAdapter extends Adapter { constructor (robot) { diff --git a/test/fixtures/TestScript.coffee b/test/fixtures/TestScript.coffee new file mode 100644 index 000000000..8aeaba1ce --- /dev/null +++ b/test/fixtures/TestScript.coffee @@ -0,0 +1,9 @@ +# 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/robot_test.js b/test/robot_test.js index df73e5677..0f2889783 100644 --- a/test/robot_test.js +++ b/test/robot_test.js @@ -1,7 +1,7 @@ 'use strict' /* eslint-disable no-unused-expressions */ - +require('coffeescript/register.js') const { describe, it, beforeEach, afterEach } = require('node:test') const assert = require('assert/strict') @@ -966,6 +966,29 @@ 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 () => { From 0b72a8c6592242c852b7373694c029e0dadaf94f Mon Sep 17 00:00:00 2001 From: Joey Guerra Date: Sat, 11 Nov 2023 23:11:08 -0600 Subject: [PATCH 3/3] fix: Revert back to 10.0.3 --- bin/hubot.js | 2 +- package.json | 2 +- src/OptParse.js | 5 +++++ test/hubot_test.js | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bin/hubot.js b/bin/hubot.js index 6b42721b7..2f7ab841f 100755 --- a/bin/hubot.js +++ b/bin/hubot.js @@ -33,7 +33,7 @@ const options = { } const Parser = new OptParse(switches) -Parser.banner = 'Usage hubot [options]' +Parser.banner = 'Usage: hubot [options]' Parser.on('adapter', (opt, value) => { options.adapter = value diff --git a/package.json b/package.json index 3d49cf43c..67702e236 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hubot", - "version": "9.1.0", + "version": "0.0.0-development", "author": "hubot", "keywords": [ "github", diff --git a/src/OptParse.js b/src/OptParse.js index f5b2add1a..6344150b6 100644 --- a/src/OptParse.js +++ b/src/OptParse.js @@ -34,6 +34,11 @@ class OptParse extends EventEmitter { } return options } + + toString () { + return `${this.banner} +${this.switches.map(([key, description]) => ` ${key}, ${description}`).join('\n')}` + } } module.exports = OptParse diff --git a/test/hubot_test.js b/test/hubot_test.js index 8b0164136..2c8663ba7 100644 --- a/test/hubot_test.js +++ b/test/hubot_test.js @@ -10,7 +10,7 @@ const path = require('node:path') const { spawn } = require('child_process') describe('Running bin/hubot.js', () => { - it('should load adapter from HUBOT_FILE environment variable', async function () { + 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')