hubot
Version:
A simple helpful robot for your Company
222 lines (191 loc) • 7.52 kB
JavaScript
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'
console.log('creating hubot directory', hubotDirectory)
try {
File.mkdirSync(hubotDirectory, { recursive: true })
} catch (error) {
console.log(`${hubotDirectory} exists, continuing to the next operation.`)
}
const envFilePath = path.resolve(process.cwd(), '.env')
process.chdir(hubotDirectory)
let output = spawnSync('npm', ['init', '-y'], { shell: true, stdio: 'inherit' })
console.log('npm init', output.stderr?.toString() ?? '')
if (options.hubotInstallationPath !== 'hubot') {
output = spawnSync('npm', ['pack', `${options.hubotInstallationPath}`], { shell: true, stdio: 'inherit' })
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`], { shell: true, stdio: 'inherit' })
console.log(`npm i ${customHubotPackage.name}-${customHubotPackage.version}.tgz`, output.stderr?.toString() ?? '', output.stdout?.toString() ?? '')
} else {
output = spawnSync('npm', ['i', 'hubot@latest'], { shell: true, stdio: 'inherit' })
}
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() ?? '')
File.mkdirSync(path.join('tests', 'doubles'), { recursive: true })
const externalScriptsPath = path.resolve('./', 'external-scripts.json')
if (!File.existsSync(externalScriptsPath)) {
File.writeFileSync(externalScriptsPath, '[]')
}
let escripts = File.readFileSync(externalScriptsPath, 'utf8')
if (escripts.length === 0) escripts = '[]'
const externalScripts = JSON.parse(escripts)
externalScripts.push('hubot-help')
externalScripts.push('hubot-rules')
externalScripts.push('hubot-diagnostics')
File.writeFileSync(externalScriptsPath, JSON.stringify(externalScripts, null, 2))
File.mkdirSync(path.join('scripts'), { recursive: true })
File.writeFileSync('./scripts/Xample.mjs', `// Description:
// Test script
//
// Commands:
// hubot helo - Responds with Hello World!.
//
// Notes:
// This is a test script.
//
export default async (robot) => {
robot.respond(/helo$/, async res => {
await res.reply("HELO World! I'm Dumbotheelephant.")
})
robot.respond(/helo room/, async res => {
await res.send('Hello World!')
})
robot.router.get('/helo', async (req, res) => {
res.send("HELO World! I'm Dumbotheelephant.")
})
}`)
File.writeFileSync('./tests/doubles/DummyAdapter.mjs', `
'use strict'
import { Adapter, TextMessage } from 'hubot'
export class DummyAdapter extends Adapter {
constructor (robot) {
super(robot)
this.name = 'DummyAdapter'
this.messages = new Set()
}
async send (envelope, ...strings) {
this.emit('send', envelope, ...strings)
this.robot.emit('send', envelope, ...strings)
}
async reply (envelope, ...strings) {
this.emit('reply', envelope, ...strings)
this.robot.emit('reply', envelope, ...strings)
}
async topic (envelope, ...strings) {
this.emit('topic', envelope, ...strings)
this.robot.emit('topic', envelope, ...strings)
}
async play (envelope, ...strings) {
this.emit('play', envelope, ...strings)
this.robot.emit('play', envelope, ...strings)
}
async run () {
// This is required to get the scripts loaded
this.emit('connected')
}
close () {
this.emit('closed')
}
async say (user, message, room) {
this.messages.add(message)
user.room = room
await this.robot.receive(new TextMessage(user, message))
}
}
export default {
async use (robot) {
return new DummyAdapter(robot)
}
}
`)
File.writeFileSync('./tests/XampleTest.mjs', `
import { describe, it, beforeEach, afterEach } from 'node:test'
import assert from 'node:assert/strict'
import { Robot } from 'hubot'
// You need a dummy adapter to test scripts
import dummyRobot from './doubles/DummyAdapter.mjs'
// Mocks Aren't Stubs
// https://www.martinfowler.com/articles/mocksArentStubs.html
describe('Xample testing Hubot scripts', () => {
let robot = null
beforeEach(async () => {
process.env.EXPRESS_PORT = 0
robot = new Robot(dummyRobot, true, 'Dumbotheelephant')
await robot.loadAdapter()
await robot.run()
await robot.loadFile('./scripts', 'Xample.mjs')
})
afterEach(() => {
delete process.env.EXPRESS_PORT
robot.shutdown()
})
it('should handle /helo request', async () => {
const expected = "HELO World! I'm Dumbotheelephant."
const url = 'http://localhost:' + robot.server.address().port + '/helo'
const response = await fetch(url)
const actual = await response.text()
assert.strictEqual(actual, expected)
})
it('should reply with expected message', async () => {
const expected = "HELO World! I'm Dumbotheelephant."
const user = robot.brain.userForId('test-user', { name: 'test user' })
let actual = ''
robot.on('reply', (envelope, ...strings) => {
actual = strings.join('')
})
await robot.adapter.say(user, '@Dumbotheelephant helo', 'test-room')
assert.strictEqual(actual, expected)
})
it('should send message to the #general room', async () => {
const expected = 'general'
const user = robot.brain.userForId('test-user', { name: 'test user' })
let actual = ''
robot.on('send', (envelope, ...strings) => {
actual = envelope.room
})
await robot.adapter.say(user, '@Dumbotheelephant helo room', 'general')
assert.strictEqual(actual, expected)
})
})
`)
const packageJsonPath = path.resolve(process.cwd(), 'package.json')
const packageJson = JSON.parse(File.readFileSync(packageJsonPath, 'utf8'))
packageJson.scripts = {
start: 'hubot',
test: 'node --test'
}
packageJson.description = 'A simple helpful robot for your Company'
if (options.adapter) {
packageJson.scripts.start += ` --adapter ${options.adapter}`
}
File.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
console.log('package.json updated successfully.')
const hubotEnvFilePath = path.resolve('.env')
try {
File.accessSync(envFilePath)
File.copyFileSync(envFilePath, hubotEnvFilePath)
console.log('.env file copied successfully.')
const envContent = File.readFileSync(hubotEnvFilePath, 'utf8')
const envLines = envContent.split('\n')
for (const line of envLines) {
const trimmedLine = line.trim()
if (trimmedLine && !trimmedLine.startsWith('#')) {
const [key, ...values] = trimmedLine.split('=')
const value = values.join('=')
process.env[key] = value
}
}
} catch (error) {
console.log('.env file not found, continuing to the next operation.')
}
}
export default (hubotDirectory, options) => {
try {
runCommands(hubotDirectory, options)
} catch (error) {
console.error('An error occurred:', error)
}
}