Files
hiring-test-one/lib/local-web-server.js

269 lines
7.8 KiB
JavaScript
Raw Normal View History

2016-06-08 23:06:41 +01:00
#!/usr/bin/env node
2015-11-08 22:09:07 +00:00
'use strict'
2016-06-08 23:06:41 +01:00
const ansi = require('ansi-escape-sequences')
2015-11-13 11:26:02 +00:00
const path = require('path')
2015-11-20 13:50:49 +00:00
const arrayify = require('array-back')
2016-06-08 23:06:41 +01:00
const t = require('typical')
const CommandLineTool = require('command-line-tool')
2016-06-27 00:03:11 +01:00
const flatten = require('reduce-flatten')
2016-06-16 23:00:07 +01:00
2016-06-20 22:27:55 +01:00
/**
* @module local-web-server
*/
const tool = new CommandLineTool()
2016-06-20 22:27:55 +01:00
/**
* @alias module:local-web-server
* @extends module:middleware-stack
*/
class LocalWebServer {
2016-06-29 22:40:34 +01:00
constructor (initOptions) {
initOptions = initOptions || {}
2016-06-24 21:18:23 +01:00
const commandLineArgs = require('command-line-args')
const commandLineUsage = require('command-line-usage')
const cli = require('../lib/cli-data')
2016-06-27 00:03:11 +01:00
/* manually scan for any --stack passed, as we may need to display stack options */
2016-06-29 22:40:34 +01:00
const stackPaths = initOptions.stack || []
2016-06-24 21:18:23 +01:00
const stackIndex = process.argv.indexOf('--stack')
if (stackIndex > -1) {
2016-06-27 00:03:11 +01:00
for (var i = stackIndex + 1; i < process.argv.length; i++) {
const stackPath = process.argv[i]
if (/^-/.test(stackPath)) {
break
} else {
stackPaths.push(stackPath)
}
}
2016-06-24 21:18:23 +01:00
}
2016-06-27 00:03:11 +01:00
/* load the stack */
2016-06-29 22:40:34 +01:00
if (!stackPaths.length) stackPaths.push(path.resolve(__dirname, '..', 'node_modules', 'local-web-server-default-stack'))
2016-06-27 00:03:11 +01:00
const stackModules = stackPaths
.map(stackPath => loadStack(stackPath))
.map(Middleware => new Middleware())
2016-06-29 22:40:34 +01:00
.map(module => {
if (module.stack) {
const featureStack = module.stack()
module.optionDefinitions = function () {
return featureStack
.map(Feature => new Feature())
.map(feature => feature.optionDefinitions && feature.optionDefinitions())
.filter(definitions => definitions)
.reduce(flatten, [])
}
module.middleware = function (options) {
return featureStack
.map(Feature => new Feature())
.map(feature => feature.middleware(options))
.reduce(flatten, [])
.filter(mw => mw)
}
}
return module
})
2016-06-27 00:03:11 +01:00
/* gather stack option definitions and parse the command line */
const middlewareOptionDefinitions = stackModules
.filter(mw => mw.optionDefinitions)
.map(mw => mw.optionDefinitions())
.reduce(flatten, [])
.map(def => {
def.group = 'middleware'
return def
})
2016-06-24 21:18:23 +01:00
const usage = commandLineUsage(cli.usage(middlewareOptionDefinitions))
let options = {}
2016-07-01 20:53:10 +01:00
const allOptionDefinitions = cli.optionDefinitions.concat(middlewareOptionDefinitions)
2016-06-24 21:18:23 +01:00
try {
2016-07-01 20:53:10 +01:00
options = commandLineArgs(allOptionDefinitions)
2016-06-24 21:18:23 +01:00
} catch (err) {
2016-07-01 20:53:10 +01:00
tool.printError(err)
tool.printError(allOptionDefinitions.map(def => {
return `name: ${def.name}${def.alias ? ', alias: ' + def.alias : ''}`
}).join('\n'))
2016-06-24 21:18:23 +01:00
console.error(usage)
2016-07-01 20:53:10 +01:00
tool.halt()
2016-06-24 21:18:23 +01:00
}
2016-06-27 00:03:11 +01:00
/* combine in stored config */
2016-06-24 21:18:23 +01:00
const loadConfig = require('config-master')
const stored = loadConfig('local-web-server')
2016-06-29 22:40:34 +01:00
options = Object.assign({ port: 8000 }, initOptions, stored, options.server, options.middleware, options.misc)
2016-06-24 21:18:23 +01:00
this.options = options
if (options.verbose) {
// debug.setLevel(1)
}
2016-06-27 00:03:11 +01:00
/* --config */
2016-06-24 21:18:23 +01:00
if (options.config) {
tool.stop(JSON.stringify(options, null, ' '), 0)
2016-06-27 00:03:11 +01:00
/* --version */
2016-06-24 21:18:23 +01:00
} else if (options.version) {
const pkg = require(path.resolve(__dirname, '..', 'package.json'))
tool.stop(pkg.version)
2016-06-27 00:03:11 +01:00
/* --help */
} else if (options.help) {
tool.stop(usage)
2016-06-24 21:18:23 +01:00
} else {
2016-06-27 00:03:11 +01:00
const compose = require('koa-compose')
const convert = require('koa-convert')
const middlewareStack = stackModules
.filter(mw => mw.middleware)
.map(mw => mw.middleware)
.map(middleware => middleware(options))
.reduce(flatten, [])
2016-06-29 22:40:34 +01:00
.filter(middleware => middleware)
2016-06-27 00:03:11 +01:00
.map(convert)
this.stack = compose(middlewareStack)
2016-06-24 21:18:23 +01:00
}
}
2016-06-24 21:18:23 +01:00
2016-06-29 22:40:34 +01:00
getApplication () {
const Koa = require('koa')
const app = new Koa()
2016-06-27 00:03:11 +01:00
app.use(this.stack)
2016-06-29 22:40:34 +01:00
app.on('error', err => {
2016-07-01 20:53:10 +01:00
if (this.options['log.format']) {
2016-06-29 22:40:34 +01:00
console.error(ansi.format(err.stack, 'red'))
}
})
return app
}
2016-06-29 22:40:34 +01:00
getServer () {
const app = this.getApplication()
const options = this.options
let key = options.key
let cert = options.cert
2016-06-29 22:40:34 +01:00
if (options.https && !(key && cert)) {
key = path.resolve(__dirname, '..', 'ssl', '127.0.0.1.key')
cert = path.resolve(__dirname, '..', 'ssl', '127.0.0.1.crt')
}
let server = null
if (key && cert) {
const fs = require('fs')
const serverOptions = {
key: fs.readFileSync(key),
cert: fs.readFileSync(cert)
}
const https = require('https')
server = https.createServer(serverOptions, app.callback())
server.isHttps = true
} else {
const http = require('http')
server = http.createServer(app.callback())
}
return server
}
2015-11-08 22:09:07 +00:00
2016-06-29 22:40:34 +01:00
listen () {
const options = this.options
const server = this._server = this.getServer()
return new Promise ((resolve, reject) => {
server.listen(options.port, () => {
onServerUp(options.port, options.directory, server.isHttps)
resolve(server)
})
2016-06-24 21:18:23 +01:00
})
2016-06-29 22:40:34 +01:00
}
close () {
this._server.close()
2015-11-13 19:55:52 +00:00
}
2016-06-08 23:06:41 +01:00
}
2015-11-13 15:33:19 +00:00
2016-06-18 21:17:20 +01:00
function onServerUp (port, directory, isHttps) {
2016-06-20 22:27:55 +01:00
const ipList = getIPList()
2016-06-18 21:17:20 +01:00
.map(iface => `[underline]{${isHttps ? 'https' : 'http'}://${iface.address}:${port}}`)
2016-06-08 23:06:41 +01:00
.join(', ')
2015-11-13 15:33:19 +00:00
2016-06-08 23:06:41 +01:00
console.error(ansi.format(
2016-06-18 21:17:20 +01:00
path.resolve(directory || '') === process.cwd()
2016-06-08 23:06:41 +01:00
? `serving at ${ipList}`
2016-06-18 21:17:20 +01:00
: `serving [underline]{${directory}} at ${ipList}`
2016-06-08 23:06:41 +01:00
))
}
2016-06-20 22:27:55 +01:00
function getIPList () {
2016-06-08 23:06:41 +01:00
const flatten = require('reduce-flatten')
const os = require('os')
2015-11-12 23:02:38 +00:00
2016-06-08 23:06:41 +01:00
let ipList = Object.keys(os.networkInterfaces())
.map(key => os.networkInterfaces()[key])
.reduce(flatten, [])
.filter(iface => iface.family === 'IPv4')
ipList.unshift({ address: os.hostname() })
return ipList
}
2015-11-12 23:02:38 +00:00
2016-06-08 23:06:41 +01:00
/**
* Return default, stored and command-line options combined
*/
2016-06-18 21:17:20 +01:00
function collectUserOptions (mwOptionDefinitions) {
2016-06-08 23:06:41 +01:00
const loadConfig = require('config-master')
const stored = loadConfig('local-web-server')
const cli = require('../lib/cli-data')
2015-11-24 11:46:47 +00:00
2016-06-08 23:06:41 +01:00
/* parse command line args */
2016-06-24 21:18:23 +01:00
const definitions = mwOptionDefinitions
? cli.optionDefinitions.concat(arrayify(mwOptionDefinitions))
: cli.optionDefinitions
let cliOptions = tool.getOptions(definitions, cli.usage(definitions))
2015-11-18 16:37:16 +00:00
2016-06-18 21:17:20 +01:00
/* override stored config with command line options */
const options = Object.assign(stored, cliOptions.server, cliOptions.middleware, cliOptions.misc)
2016-06-08 23:06:41 +01:00
return options
2015-11-08 22:09:07 +00:00
}
2016-06-27 00:03:11 +01:00
/**
* Loads a module by either path or name.
* @returns {object}
*/
2016-06-24 21:18:23 +01:00
function loadStack (modulePath) {
let module
2016-07-01 20:53:10 +01:00
const tried = []
2016-06-24 21:18:23 +01:00
if (modulePath) {
const fs = require('fs')
try {
2016-07-01 20:53:10 +01:00
tried.push(path.resolve(modulePath))
2016-06-24 21:18:23 +01:00
module = require(path.resolve(modulePath))
} catch (err) {
const walkBack = require('walk-back')
2016-06-27 00:03:11 +01:00
const foundPath = walkBack(process.cwd(), path.join('node_modules', 'local-web-server-' + modulePath))
2016-07-01 20:53:10 +01:00
tried.push('local-web-server-' + modulePath)
2016-06-24 21:18:23 +01:00
if (foundPath) {
module = require(foundPath)
2016-06-27 00:03:11 +01:00
} else {
const foundPath2 = walkBack(process.cwd(), path.join('node_modules', modulePath))
2016-07-01 20:53:10 +01:00
tried.push(modulePath)
2016-06-27 00:03:11 +01:00
if (foundPath2) {
module = require(foundPath2)
}
2016-06-24 21:18:23 +01:00
}
}
}
2016-07-01 20:53:10 +01:00
if (module) {
if (!(module.prototype.middleware || module.prototype.stack)) {
const insp = require('util').inspect(module, { depth: 3, colors: true })
const msg = `Not valid Middleware at: ${insp}`
tool.halt(new Error(msg))
}
} else {
const msg = `No module found at: \n${tried.join('\n')}`
tool.halt(new Error(msg))
2016-06-27 00:03:11 +01:00
}
2016-06-24 21:18:23 +01:00
return module
}
module.exports = LocalWebServer