Skip to content

Commit

Permalink
feat(argv): Load options from command line, env, package.json or conf…
Browse files Browse the repository at this point in the history
…ig file
  • Loading branch information
timkinnane committed Mar 26, 2018
1 parent f555520 commit e509c9b
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 7 deletions.
12 changes: 10 additions & 2 deletions package.json
Expand Up @@ -19,12 +19,16 @@
"typescript"
],
"files": [
"dist"
"dist",
"package.json"
],
"engines": {
"node": "> 8.0.0",
"npm": "> 5.0.0"
},
"bot": {
"alias": "bbot"
},
"scripts": {
"pretest": "tslint -p .",
"test": "nyc mocha './src/lib/**/*.spec.ts'",
Expand All @@ -43,12 +47,14 @@
"devDependencies": {
"@types/chai": "^4.1.2",
"@types/mocha": "^2.2.48",
"@types/mock-fs": "^3.6.30",
"chai": "^4.1.2",
"commitizen": "^2.9.6",
"cz-conventional-changelog": "^2.1.0",
"dotenv": "^5.0.1",
"husky": "^0.14.3",
"mocha": "^5.0.1",
"mock-fs": "^4.4.2",
"nyc": "^11.4.1",
"package-preview": "^1.0.5",
"rimraf": "^2.6.2",
Expand All @@ -64,7 +70,9 @@
"dependencies": {
"@types/node": "^9.4.6",
"@types/winston": "^2.3.8",
"winston": "^2.4.1"
"@types/yargs": "^11.0.0",
"winston": "^2.4.1",
"yargs": "^11.0.0"
},
"config": {
"commitizen": {
Expand Down
4 changes: 3 additions & 1 deletion src/config/botInterfaces.ts
@@ -1,3 +1,5 @@
export interface IOptions {
logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'silent'
logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'silent',
name?: string,
alias?: string
}
1 change: 1 addition & 0 deletions src/index.spec.ts
Expand Up @@ -3,6 +3,7 @@ import { expect } from 'chai'
describe('index', () => {
it('exports all lib modules', () => {
expect(Object.keys(require('bbot'))).to.eql([
'argv',
'bot',
'logger',
'middleware'
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Expand Up @@ -11,10 +11,16 @@
* bbot.bot.start()
*/

import * as argv from './lib/argv'
import * as bot from './lib/bot'
import * as logger from './lib/logger'
import * as middleware from './lib/middleware'

/** Use `dotenv` to load local env settings from `.env` file in development */
if (process.env.NODE_ENV === 'development') require('dotenv').config()

export {
argv,
bot,
logger,
middleware
Expand Down
39 changes: 39 additions & 0 deletions src/lib/argv.spec.ts
@@ -0,0 +1,39 @@
import 'mocha'
import { expect } from 'chai'
import * as yargs from 'yargs'
import mock from 'mock-fs'
import * as argv from './argv'

describe('argv', () => {
describe('.config', () => {
it('contains arguments collection, with defaults', () => {
expect(argv.config).to.have.property('name', 'bot')
})
})
describe('.getConfig', () => {
it('loads config from process.argv', () => {
yargs.parse(['--name', 'hao']) // overwrite argv
const config = argv.getConfig()
expect(config).to.have.property('name', 'hao')
yargs.parse(process.argv) // replace with actual argv
})
it('loads configs from ENV variables using prefix', () => {
process.env.BOT_NAME = 'henry'
expect(argv.getConfig()).to.have.property('name', 'henry')
delete process.env.BOT_NAME
})
it('loads config from package.json `bot` attribute', () => {
expect(argv.getConfig()).to.have.property('alias', 'bbot')
})
/** @todo restore config file testing without crashing wallaby */
// it('load config from a defined json file if given', () => {
// mock({
// '/mock/config.json': JSON.stringify({ name: 'harriet' })
// })
// yargs.parse(['--config', '/mock/config.json']) // overwrite argv
// console.log(argv.getConfig())
// mock.restore()
// yargs.parse(process.argv) // replace with actual argv
// })
})
})
69 changes: 69 additions & 0 deletions src/lib/argv.ts
@@ -0,0 +1,69 @@
import * as yargs from 'yargs'
import * as packageJSON from '../../package.json'
import { IOptions } from '../config/botInterfaces'

/**
* Used to trim argv into shape of interface
* @todo Needs to be updated with changes to interface, should be dynamic
*/
export const optionsFilterKeys = [
'logLevel',
'name',
'alias'
]

/**
* Combine and load config from command line, environment and JSON if provided.
*/
export function getConfig () {
const argv = yargs
.usage('\nUsage: $0 [args]')
.env('BOT')
.pkgConf('bot')
.option('log-level', {
type: 'string',
describe: 'The starting minimum level for logging events (silent|debug|info|warn|error).',
default: 'info'
})
.option('name', {
alias: 'n',
type: 'string',
describe: 'Name of the bot in chat. Prepending any command with the name will trigger respond listeners.\n',
default: 'bot'
})
.option('alias', {
type: 'string',
describe: 'Alternate name for the bot.\n',
default: false
})
.config()
.alias('config', 'c')
.example('config', 'bin/bbot -c bot-config.json')
.version(packageJSON.version)
.alias('version', 'v')
.help()
.alias('help', 'h')
.epilogue(
`All option can be provided as environment variables, with the prefix \`BOT_\`.
Config can also be declared in \`package.json\` with the key: "botConfig".
For more information, see https://amazebot.github.io/bbot'`
)
.fail((msg: string, err: Error) => {
console.error(msg, err)
console.info('Start with --help for config argument info.')
if (err) throw err
process.exit(1)
})
.argv
const config: any = []
for (let key of Object.keys(argv)) {
if (optionsFilterKeys.indexOf(key) !== -1) {
config[key] = argv[key]
}
}
return config
}

export const config = getConfig() as IOptions

if (process.platform !== 'win32') process.on('SIGTERM', () => process.exit(0))
4 changes: 3 additions & 1 deletion src/lib/bot.ts
Expand Up @@ -6,6 +6,7 @@
*/

import { EventEmitter } from 'events'
import { config } from './argv'
import { logger } from './logger'
import { IOptions } from '../config/botInterfaces'
export let started: boolean = false
Expand All @@ -30,7 +31,8 @@ export const events = new EventEmitter()
*/
export async function start (opts?: IOptions) {
logger.info('Bleep Bloop... starting up ~(O_O)~')
if (opts) logger.info('with options', opts)
Object.assign(config, opts)
logger.info('Using config:', config)
started = true
events.emit('ready')
return exports
Expand Down
4 changes: 3 additions & 1 deletion src/lib/logger.ts
Expand Up @@ -5,6 +5,7 @@
*/

import * as winston from 'winston'
import { config } from './argv'

/**
* Winston logger provides a logging interface common to many Node apps, with
Expand All @@ -22,9 +23,10 @@ import * as winston from 'winston'
*
* @todo Update to Winston v3 when typings complete
* https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20418
* @todo Add filter to prevent logging passwords etc
*/
export const logger = new (winston.Logger)({
level: process.env.LOG_LEVEL || 'info',
level: config.logLevel,
handleExceptions: true,
exitOnError: (err) => ((err as any).middleware === undefined),
transports: [
Expand Down
2 changes: 2 additions & 0 deletions src/start.spec.ts
@@ -1,4 +1,6 @@
import { expect } from 'chai'
import { config } from './lib/argv'
config.logLevel = 'silent'

describe('start', () => {
it('runs async bot startup', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/start.ts
Expand Up @@ -6,4 +6,4 @@
*/

import * as b from './index'
module.exports = b.bot.start().then(() => b)
export = b.bot.start().then(() => b)
3 changes: 3 additions & 0 deletions src/types/json.d.ts
@@ -0,0 +1,3 @@
declare module "*.json" {
export let version: string
}
3 changes: 2 additions & 1 deletion wallaby.js
Expand Up @@ -3,10 +3,11 @@ module.exports = function (wallaby) {
name: 'bbot',
files: [
"src/**/*.ts",
"package.json",
{ pattern: "src/**/*.spec.ts", ignore: true },
{ pattern: "src/**/*.d.ts", ignore: true },
],
tests: ["src/**/*.spec.ts"],
tests: ["src/lib/*.spec.ts"],
testFramework: 'mocha',
env: {
type: 'node'
Expand Down
31 changes: 31 additions & 0 deletions yarn.lock
Expand Up @@ -88,6 +88,12 @@
version "2.2.48"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.48.tgz#3523b126a0b049482e1c3c11877460f76622ffab"

"@types/mock-fs@^3.6.30":
version "3.6.30"
resolved "https://registry.yarnpkg.com/@types/mock-fs/-/mock-fs-3.6.30.tgz#4d812541e87b23577261a5aa95f704dd3d01e410"
dependencies:
"@types/node" "*"

"@types/mz@0.0.32", "@types/mz@^0.0.32":
version "0.0.32"
resolved "https://registry.yarnpkg.com/@types/mz/-/mz-0.0.32.tgz#e8248b4e41424c052edc1725dd33650c313a3659"
Expand Down Expand Up @@ -115,6 +121,10 @@
version "2.2.1"
resolved "https://registry.yarnpkg.com/@types/write-json-file/-/write-json-file-2.2.1.tgz#74155aaccbb0d532be21f9d66bebc4ea875a5a62"

"@types/yargs@^11.0.0":
version "11.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-11.0.0.tgz#124b9ed9c65b7091cc36da59ae12cbd47d8745ea"

align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
Expand Down Expand Up @@ -1833,6 +1843,10 @@ mocha@^5.0.1:
mkdirp "0.5.1"
supports-color "4.4.0"

mock-fs@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.4.2.tgz#09dec5313f97095a450be6aa2ad8ab6738d63d6b"

ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
Expand Down Expand Up @@ -3086,6 +3100,23 @@ yargs@11.1.0:
y18n "^3.2.1"
yargs-parser "^9.0.2"

yargs@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b"
dependencies:
cliui "^4.0.0"
decamelize "^1.1.1"
find-up "^2.1.0"
get-caller-file "^1.0.1"
os-locale "^2.0.0"
require-directory "^2.1.1"
require-main-filename "^1.0.1"
set-blocking "^2.0.0"
string-width "^2.0.0"
which-module "^2.0.0"
y18n "^3.2.1"
yargs-parser "^9.0.2"

yargs@~3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
Expand Down

0 comments on commit e509c9b

Please sign in to comment.