From 7fa3841497bd1158f97ff2f5981637a5c8a66137 Mon Sep 17 00:00:00 2001 From: Joey Guerra Date: Sat, 11 Nov 2023 23:15:47 -0600 Subject: [PATCH] fix: Revert back to 10.0.3 (#1700) fix: Accidently releasing the removal of CoffeeScript in a minor version. fix: --help and the Windows test for it. 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 | 3 +- 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, 183 insertions(+), 82 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..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", @@ -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 () => {