diff --git a/README.md b/README.md index 5f1ceb3..1b8a224 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ***Requires node v4.0.0 or higher. Install the [previous release](https://github.com/75lb/local-web-server/tree/prev) for older node support.*** # local-web-server -At its core, local-web-server is an application shell for building a specialised command-line web server to support productive Web Platform engineers. It comes bundled with a middleware stack covering common requirements but any arbitrary stack can be specified from the command line or config. +At its core, local-web-server is an application shell for building a specialised command-line web server to support productive Web Platform engineers. When combined with built-in and custom features it's in intended to by a powerful tool in helping build and debug Web applications. It comes bundled with a middleware stack covering common requirements but any arbitrary stack can be specified from the command line or config. Being an npm module, it is trivial is bundle and distribute/deploy with your web application. diff --git a/doc/api.md b/doc/api.md index fe731a8..b476b8d 100644 --- a/doc/api.md +++ b/doc/api.md @@ -2,29 +2,71 @@ * [local-web-server](#module_local-web-server) - * [LocalWebServer](#exp_module_local-web-server--LocalWebServer) ⇐ [middleware-stack](#module_middleware-stack) ⏏ + * [LocalWebServer](#exp_module_local-web-server--LocalWebServer) ⇐ module:middleware-stack ⏏ + * [new LocalWebServer([options])](#new_module_local-web-server--LocalWebServer_new) * _instance_ - * [.add(middleware)](#) ↩︎ + * [.view](#module_local-web-server--LocalWebServer.LocalWebServer+view) : View + * [.features](#module_local-web-server--LocalWebServer.LocalWebServer+features) : Array.<Feature> + * [.options](#module_local-web-server--LocalWebServer.LocalWebServer+options) : object + * [.server](#module_local-web-server--LocalWebServer+server) : Server + * [.getApplication()](#module_local-web-server--LocalWebServer+getApplication) ⇒ function + * [.getServer()](#module_local-web-server--LocalWebServer+getServer) ⇒ Server * _inner_ - * [~collectUserOptions()](#module_local-web-server--LocalWebServer..collectUserOptions) + * [~loadStack()](#module_local-web-server--LocalWebServer..loadStack) ⇒ object -### LocalWebServer ⇐ [middleware-stack](#module_middleware-stack) ⏏ +### LocalWebServer ⇐ module:middleware-stack ⏏ **Kind**: Exported class -**Extends:** [middleware-stack](#module_middleware-stack) - +**Extends:** module:middleware-stack + -#### localWebServer.add(middleware) ↩︎ -**Kind**: instance method of [LocalWebServer](#exp_module_local-web-server--LocalWebServer) -**Chainable** +#### new LocalWebServer([options]) **Params** -- middleware [middleware](#module_middleware-stack--MiddlewareStack..middleware) +- [options] object - Server options + - .port} number - Port + - .stack} Array.<string> | Array.<Features> - Port + + + +#### localWebServer.view : View +Current view. + +**Kind**: instance property of [LocalWebServer](#exp_module_local-web-server--LocalWebServer) + + +#### localWebServer.features : Array.<Feature> +Loaded feature modules + +**Kind**: instance property of [LocalWebServer](#exp_module_local-web-server--LocalWebServer) + + +#### localWebServer.options : object +Config + +**Kind**: instance property of [LocalWebServer](#exp_module_local-web-server--LocalWebServer) + - +#### localWebServer.server : Server +Node.js server + +**Kind**: instance property of [LocalWebServer](#exp_module_local-web-server--LocalWebServer) + + +#### localWebServer.getApplication() ⇒ function +Returns a middleware application suitable for passing to `http.createServer`. The application is a function with three args (req, res and next) which can be created by express, Koa or hand-rolled. + +**Kind**: instance method of [LocalWebServer](#exp_module_local-web-server--LocalWebServer) + + +#### localWebServer.getServer() ⇒ Server +Returns a listening server which processes requests using the middleware supplied. + +**Kind**: instance method of [LocalWebServer](#exp_module_local-web-server--LocalWebServer) + -#### LocalWebServer~collectUserOptions() -Return default, stored and command-line options combined +#### LocalWebServer~loadStack() ⇒ object +Loads a module by either path or name. **Kind**: inner method of [LocalWebServer](#exp_module_local-web-server--LocalWebServer) diff --git a/lib/cli-data.js b/lib/cli-data.js index bbe4605..8385504 100644 --- a/lib/cli-data.js +++ b/lib/cli-data.js @@ -24,6 +24,10 @@ exports.optionDefinitions = [ description: 'Print these usage instructions.', group: 'misc' }, { + name: 'view', type: String, + description: 'Custom view', group: 'misc' + }, + { name: 'config', type: Boolean, description: 'Print the stored config.', group: 'misc' }, diff --git a/lib/cli-view.js b/lib/cli-view.js new file mode 100644 index 0000000..21fabaa --- /dev/null +++ b/lib/cli-view.js @@ -0,0 +1,31 @@ +class CliView { + constructor (localWebServer) { + this.options = localWebServer.options + } + info (key, value) { + if (key && value) { + const ansi = require('ansi-escape-sequences') + const tableLayout = require('table-layout') + const output = tableLayout({ key: ansi.format(key, 'bold'), value: value}, { + padding: { left: '', right: ' ' }, + columns: [ + { name: 'key', width: 18 }, + { name: 'value', nowrap: true } + ] + }) + process.stderr.write(output) + } else { + console.error(key) + } + } + verbose (key, value) { + if (this.options.verbose) { + this.info(key, value) + } + } + error (msg) { + console.error(ansi.format(msg, 'red')) + } +} + +module.exports = CliView diff --git a/lib/local-web-server.js b/lib/local-web-server.js index 7672894..f43b21e 100644 --- a/lib/local-web-server.js +++ b/lib/local-web-server.js @@ -9,37 +9,6 @@ const ansi = require('ansi-escape-sequences') * @module local-web-server */ - -class CliView { - constructor (options) { - this.options = options || {} - } - show (key, value) { - if (key && value) { - const ansi = require('ansi-escape-sequences') - const tableLayout = require('table-layout') - const output = tableLayout({ key: ansi.format(key, 'bold'), value: value}, { - padding: { left: '', right: ' ' }, - columns: [ - { name: 'key', width: 18 }, - { name: 'value', nowrap: true } - ] - }) - process.stderr.write(output) - } else { - console.error(key) - } - } - verbose (key, value) { - if (this.options.verbose) { - this.show(key, value) - } - } - error (msg) { - console.error(ansi.format(msg, 'red')) - } -} - /** * @alias module:local-web-server * @extends module:middleware-stack @@ -55,9 +24,14 @@ class LocalWebServer { initOptions = initOptions || {} const commandLineArgs = require('command-line-args') const commandLineUsage = require('command-line-usage') + const CliView = require('./cli-view') const cli = require('../lib/cli-data') - this.view = new CliView() + /** + * Current view. + * @type {View} + */ + this.view = new CliView(this) /* get stored config */ const loadConfig = require('config-master') @@ -66,11 +40,14 @@ class LocalWebServer { /* read the config and command-line for feature paths */ const featurePaths = parseFeaturePaths(initOptions.stack || stored.stack) - /* load features and build the middleware stack */ - const features = this._buildFeatureStack(featurePaths) + /** + * Loaded feature modules + * @type {Feature[]} + */ + this.features = this._buildFeatureStack(featurePaths) /* gather feature optionDefinitions and parse the command line */ - const featureOptionDefinitions = features + const featureOptionDefinitions = this.features .filter(mw => mw.optionDefinitions) .map(mw => mw.optionDefinitions()) .reduce(flatten, []) @@ -95,7 +72,7 @@ class LocalWebServer { return `name: ${def.name}${def.alias ? ', alias: ' + def.alias : ''}` }).join('\n')) } - this.view.show(usage) + this.view.info(usage) process.exit(1) } } @@ -110,41 +87,39 @@ class LocalWebServer { options.misc ) - this.view.options.verbose = options.verbose - /** * Config * @type {object} */ this.options = options - this.features = features - - features - .filter(mw => mw.on) - .forEach(mw => { - mw.on('verbose', this.view.verbose.bind(this.view)) - mw.on('debug', this.view.verbose.bind(this.view)) - }) + if (options.view) { + const View = loadModule(options.view) + this.view = new View(this) + } /* --config */ if (options.config) { - this.view.show(JSON.stringify(options, null, ' ')) + this.view.info(JSON.stringify(options, null, ' ')) process.exit(0) /* --version */ } else if (options.version) { const pkg = require(path.resolve(__dirname, '..', 'package.json')) - this.view.show(pkg.version) + this.view.info(pkg.version) process.exit(0) /* --help */ } else if (options.help) { - this.view.show(usage) + this.view.info(usage) process.exit(0) } } + /** + * Returns a middleware application suitable for passing to `http.createServer`. The application is a function with three args (req, res and next) which can be created by express, Koa or hand-rolled. + * @returns {function} + */ getApplication () { const Koa = require('koa') const app = new Koa() @@ -153,7 +128,7 @@ class LocalWebServer { const middlewareStack = this.features .filter(mw => mw.middleware) - .map(mw => mw.middleware(this.options)) + .map(mw => mw.middleware(this.options, this)) .reduce(flatten, []) .filter(mw => mw) .map(convert) @@ -162,9 +137,13 @@ class LocalWebServer { app.on('error', err => { console.error(ansi.format(err.stack, 'red')) }) - return app + return app.callback() } + /** + * Returns a listening server which processes requests using the middleware supplied. + * @returns {Server} + */ getServer (onListening) { const app = this.getApplication() const options = this.options @@ -186,11 +165,11 @@ class LocalWebServer { } const https = require('https') - server = https.createServer(serverOptions, app.callback()) + server = https.createServer(serverOptions, app) server.isHttps = true } else { const http = require('http') - server = http.createServer(app.callback()) + server = http.createServer(app) } server.listen(options.port) @@ -200,10 +179,15 @@ class LocalWebServer { const ipList = getIPList() .map(iface => `[underline]{${server.isHttps ? 'https' : 'http'}://${iface.address}:${options.port}}`) .join(', ') - this.view.show('Serving at', ansi.format(ipList)) + this.view.info('Serving at', ansi.format(ipList)) }) } + /** + * Node.js server + * @type {Server} + */ + this.server = server return server } @@ -211,32 +195,25 @@ class LocalWebServer { return featurePaths .map(featurePath => loadStack(featurePath)) .map(Feature => new Feature()) - .map(module => { - if (module.stack) { - const featureStack = module.stack() + .map(feature => { + if (feature.stack) { + const featureStack = feature.stack() .map(Feature => new Feature()) - .map(feature => { - if (feature.on) { - feature.on('verbose', this.view.verbose.bind(this.view)) - feature.on('debug', this.view.verbose.bind(this.view)) - } - return feature - }) - - module.optionDefinitions = function () { + + feature.optionDefinitions = function () { return featureStack .map(feature => feature.optionDefinitions && feature.optionDefinitions()) .filter(definitions => definitions) .reduce(flatten, []) } - module.middleware = function (options) { + feature.middleware = function (options, view) { return featureStack - .map(feature => feature.middleware(options)) + .map(feature => feature.middleware(options, view)) .reduce(flatten, []) .filter(mw => mw) } } - return module + return feature }) } } @@ -246,8 +223,26 @@ class LocalWebServer { * @returns {object} */ function loadStack (modulePath) { - let module + const isModule = module => module.prototype && (module.prototype.middleware || module.prototype.stack) if (isModule(modulePath)) return modulePath + const module = loadModule(modulePath) + if (module) { + if (!isModule(module)) { + const insp = require('util').inspect(module, { depth: 3, colors: true }) + const msg = `Not valid Middleware at: ${insp}` + console.error(msg) + process.exit(1) + } + } else { + const msg = `No module found at: \n${tried.join('\n')}` + console.error(msg) + process.exit(1) + } + return module +} + +function loadModule (modulePath) { + let module const tried = [] if (modulePath) { try { @@ -268,22 +263,9 @@ function loadStack (modulePath) { } } } - if (module) { - if (!isModule(module)) { - 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)) - } return module } -function isModule (module) { - return module.prototype && (module.prototype.middleware || module.prototype.stack) -} function getIPList () { const flatten = require('reduce-flatten')