Comparing version 1.0.0-pre.4 to 1.0.0-pre.5
#!/usr/bin/env node | ||
'use strict' | ||
const Lws = require('../') | ||
const lws = new Lws() | ||
lws.start() | ||
require('../lib/cli-app').run() |
'use strict' | ||
const ansi = require('ansi-escape-sequences') | ||
const t = require('typical') | ||
class CliView { | ||
log (msg) { | ||
process.stdout.write(msg) | ||
constructor (options) { | ||
this.options = options | ||
} | ||
start (config) { | ||
// console.error(`START: ${this.constructor.name}`) | ||
// console.error(require('util').inspect(config, { depth: 1, colors: true })) | ||
write (name, value) { | ||
if (this.options.verbose) { | ||
const output = { name } | ||
if (t.isDefined(value)) output.value = value | ||
console.log(output) | ||
} else { | ||
if (name === 'log') { | ||
console.log(value.trim()) | ||
} else if (name === 'server-listening') { | ||
const ansi = require('ansi-escape-sequences') | ||
const ipList = value | ||
.map(iface => ansi.format(iface, 'underline')) | ||
.join(', ') | ||
console.error(`Serving at ${ipList}`) | ||
} | ||
} | ||
} | ||
verbose (title, msg) { | ||
msg = require('util').inspect(msg, { depth: 1, colors: true }) | ||
console.error(`${title} ${msg}`) | ||
} | ||
end () {} | ||
error (err) { | ||
console.error(ansi.format(`Error: ${err.message}`, 'red')) | ||
} | ||
} | ||
module.exports = CliView |
303
lib/lws.js
@@ -1,26 +0,31 @@ | ||
'use strict' | ||
const path = require('path') | ||
const arrayify = require('array-back') | ||
const ansi = require('ansi-escape-sequences') | ||
const util = require('./util') | ||
const t = require('typical') | ||
/** | ||
*Creating command-line web servers suitable for full-stack javascript development. | ||
* @module lws | ||
*/ | ||
class Lws { | ||
constructor (options) { | ||
this.options = options | ||
/** | ||
* @alias module:lws | ||
* @example | ||
* const Lws = require('lws') | ||
* const lws = new Lws() | ||
* lws.start({ https: true}) | ||
*/ | ||
class Lws { | ||
/* validate stack */ | ||
const Stack = require('./stack') | ||
if (!(options.stack instanceof Stack)) { | ||
options.stack = Stack.create(options.stack) | ||
} | ||
/* create server */ | ||
this.server = this.createServer(options) | ||
if (t.isDefined(options.maxConnections)) this.server.maxConnections = options.maxConnections | ||
if (t.isDefined(options.keepaliveTimeout)) this.server.keepAliveTimeout = options.keepaliveTimeout | ||
const Koa = require('koa') | ||
this.app = new Koa() | ||
} | ||
/** | ||
* Create server, | ||
* @param [options] {object} - Server options | ||
* @param [options.port] {number} - Port | ||
* @param [options.hostname] {string} -The hostname (or IP address) to listen on. Defaults to 0.0.0.0. | ||
* @param [options.config-file] {string} - Config file, defaults to 'lws.config.js'. | ||
* @param [options.configFile] {string} - Config file, defaults to 'lws.config.js'. | ||
* @param [options.stack] {string[]|Features[]} - Array of feature classes, or filenames of modules exporting a feature class. | ||
* @param [options.moduleDir] {string[]} - One or more directories to search for feature modules. | ||
* @param [options.https] {boolean} - Enable HTTPS using a built-in key and cert, registered to the domain 127.0.0.1. | ||
@@ -30,211 +35,109 @@ * @param [options.key] {string} - SSL key. Supply along with --cert to launch a https server. | ||
*/ | ||
constructor (options) { | ||
this.options = Object.assign({}, options) | ||
if (this.options.stack) this.options.stack = arrayify(this.options.stack) | ||
launch () { | ||
this.attachView() | ||
const options = this.options | ||
/** | ||
* The [Koa application](https://github.com/koajs/koa/blob/master/docs/api/index.md#application) instance in use. | ||
* @type {Koa} | ||
*/ | ||
this.app = null | ||
/* build Koa application, add it to server */ | ||
const middlewares = options.stack.getMiddlewareFunctions(options) | ||
middlewares.forEach(middleware => this.app.use(middleware)) | ||
this.server.on('request', this.app.callback()) | ||
/** | ||
* The node server in use, an instance of either [http.Server](https://nodejs.org/dist/latest-v7.x/docs/api/http.html#http_class_http_server) or [https.Server](https://nodejs.org/dist/latest-v7.x/docs/api/https.html#https_class_https_server). | ||
* @type {http.Server|https.Server} | ||
*/ | ||
this.server = null | ||
/** | ||
* Feature instances | ||
* @type {Feature[]} | ||
*/ | ||
this.features = null | ||
/* start server */ | ||
this.server.listen(options.port, options.hostname) | ||
} | ||
/** | ||
* Start the app. | ||
*/ | ||
start () { | ||
this.options.stack = util.expandStack(this.options.stack) | ||
let cli | ||
try { | ||
cli = util.parseCommandLineOptions(util.getOptionDefinitions(this.options.stack)) | ||
} catch (err) { | ||
if (err.name === 'UNKNOWN_OPTION') { | ||
halt(`${err.message}. Use --help/-h to see all options.`) | ||
} else { | ||
halt(err.stack) | ||
} | ||
} | ||
const builtInDefaults = { port: 8000 } | ||
const optionsSoFar = util.deepMerge({}, builtInDefaults, this.options, cli.options._all) | ||
attachView () { | ||
const options = this.options | ||
/* load view */ | ||
const View = require('./view') | ||
let ActiveView = View.load(options.view) || require('./cli-view') | ||
this.view = new ActiveView(options) | ||
const storedOptions = this.getStoredConfig(optionsSoFar['config-file']) | ||
storedOptions.stack = util.expandStack(storedOptions.stack) | ||
this.options = util.deepMerge({}, builtInDefaults, this.options, storedOptions, cli.options._all) | ||
/* attach view to middlewares */ | ||
options.stack.attachView(this.view) | ||
/* --config */ | ||
if (this.options.config) { | ||
console.error(require('util').inspect(this.options, { depth: 6, colors: true })) | ||
process.exit(0) | ||
/* attach view to Koa */ | ||
this.app.on('error', err => { | ||
this.view.write('koa-error', err.stack) | ||
util.printError(err, 'Middleware error') | ||
}) | ||
/* --version */ | ||
} else if (this.options.version) { | ||
console.error(this.getVersion()) | ||
process.exit(0) | ||
/* --help */ | ||
} else if (this.options.help) { | ||
console.error(this.getUsage()) | ||
process.exit(0) | ||
/* launch server and listen */ | ||
} else { | ||
const Koa = require('koa') | ||
this.app = new Koa() | ||
this.server = this.getServer() | ||
/* Features should be a collection class with `getMiddlewares`, `expandStack`, | ||
getOptionDefinitions etc on it */ | ||
this.features = this.options.stack.map(Feature => new Feature(this)) | ||
this.attachView() | ||
const middlewares = this.getMiddlewares(this.features) | ||
middlewares.forEach(middleware => this.app.use(middleware)) | ||
this.server.on('request', this.app.callback()) | ||
this.server.listen(this.options.port, this.options.hostname) | ||
for (const feature of this.features) { | ||
if (feature.ready) { | ||
feature.ready(this) | ||
} | ||
} | ||
} | ||
/* attach to view to server */ | ||
attachServerToView(this.server, this.view, options) | ||
} | ||
/** | ||
* Returns and array of middleware functions from a given stack. | ||
* @returns {middleware[]} | ||
* @ignore | ||
*/ | ||
getMiddlewares (features) { | ||
const flatten = require('reduce-flatten') | ||
return features | ||
.filter(feature => feature.middleware) | ||
.map(feature => feature.middleware(Object.assign({}, this.options))) | ||
.reduce(flatten, []) | ||
.filter(mw => mw) | ||
} | ||
/** | ||
* Returns a listening server which processes requests using the middleware supplied. | ||
* Returns a nodejs Server instance. | ||
* @returns {Server} | ||
* @ignore | ||
*/ | ||
getServer () { | ||
const options = this.options | ||
let key = options.key | ||
let cert = options.cert | ||
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') | ||
createServer (options) { | ||
let ServerFactory = require('./server-factory') | ||
const loadModule = require('load-module') | ||
if (options.https) { | ||
ServerFactory = require('./server-factory-https')(ServerFactory) | ||
} else if (options.server) { | ||
ServerFactory = loadModule(options.server)(ServerFactory) | ||
} | ||
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) | ||
server.isHttps = true | ||
} else { | ||
const http = require('http') | ||
server = http.createServer() | ||
} | ||
/* on server-up message */ | ||
if (!options.testMode) { | ||
server.on('listening', () => { | ||
const ipList = util.getIPList() | ||
.map(iface => `[underline]{${server.isHttps ? 'https' : 'http'}://${iface.address}:${options.port}}`) | ||
.join(', ') | ||
console.error('Serving at', ansi.format(ipList)) | ||
}) | ||
} | ||
return server | ||
const factory = new ServerFactory() | ||
return factory.create(options) | ||
} | ||
} | ||
/** | ||
* Returns version number | ||
* @returns {string} | ||
* @ignore | ||
*/ | ||
getVersion () { | ||
const pkg = require(path.resolve(__dirname, '..', 'package.json')) | ||
return pkg.version | ||
} | ||
attachView () { | ||
const View = require('./cli-view') | ||
const view = new View() | ||
for (const feature of this.features) { | ||
if (feature.on) { | ||
feature.on('log', view.log) | ||
feature.on('error', view.error) | ||
if (this.options.verbose) { | ||
feature.on('start', view.start) | ||
feature.on('verbose', view.verbose) | ||
} | ||
} | ||
function attachServerToView (server, view, options) { | ||
function write (name, value) { | ||
return function () { | ||
view.write(name, value) | ||
} | ||
this.server.on('error', err => halt(`Failed to start server: ${err.message}`)) | ||
this.app.on('error', view.error) | ||
} | ||
/** | ||
* Return stored config object. | ||
* @return {object} | ||
* @ignore | ||
*/ | ||
getStoredConfig (configFilePath) { | ||
const walkBack = require('walk-back') | ||
const configFile = walkBack(process.cwd(), configFilePath || 'lws.config.js') | ||
if (configFile) { | ||
return require(configFile) | ||
} else { | ||
return {} | ||
} | ||
} | ||
getUsage () { | ||
const commandLineUsage = require('command-line-usage') | ||
const optionDefinitions = util.getOptionDefinitions(this.options.stack) | ||
const usageSections = require('./cli-data').usage(optionDefinitions) | ||
usageSections.unshift(this.getUsageHeader()) | ||
usageSections.push(this.getUsageFooter()) | ||
return commandLineUsage(usageSections) | ||
} | ||
getUsageHeader () { | ||
function socketProperties (socket) { | ||
return { | ||
header: 'lws', | ||
content: 'A modular server application shell for creating a personalised local web server to support productive, full-stack Javascript development.' | ||
id: socket.id, | ||
bytesRead: socket.bytesRead, | ||
bytesWritten: socket.bytesWritten | ||
} | ||
} | ||
getUsageFooter () { | ||
return { | ||
content: 'Project home: [underline]{https://github.com/lwsjs/lws}' | ||
} | ||
} | ||
} | ||
let cId = 1 | ||
server.on('connection', (socket) => { | ||
socket.id = cId++ | ||
view.write('socket-new', socketProperties(socket)) | ||
socket.on('connect', write('socket-connect', socketProperties(socket, cId))) | ||
socket.on('data', function () { | ||
view.write('socket-data', socketProperties(this)) | ||
}) | ||
socket.on('drain', function () { | ||
view.write('socket-drain', socketProperties(this)) | ||
}) | ||
socket.on('timeout', function () { | ||
view.write('socket-timeout', socketProperties(this)) | ||
}) | ||
socket.on('close', function () { | ||
view.write('socket-close', socketProperties(this)) | ||
}) | ||
socket.on('end', write('socket-end', socketProperties(socket, cId))) | ||
socket.on('lookup', write('socket-connect', socketProperties(socket, cId))) | ||
}) | ||
server.on('close', write('server-close')) | ||
server.on('error', err => { | ||
view.write('server-error', err.stack) | ||
util.printError(err, 'Server error') | ||
}) | ||
server.on('request', write('server-request')) | ||
server.on('checkContinue', write('server-checkContinue')) | ||
server.on('checkExpectation', write('server-checkExpectation')) | ||
server.on('clientError', write('server-clientError')) | ||
server.on('connect', write('server-connect')) | ||
server.on('upgrade', write('server-upgrade')) | ||
function halt (msg) { | ||
console.error(ansi.format(msg, 'red')) | ||
process.exit(1) | ||
/* on server-up message */ | ||
server.on('listening', () => { | ||
const isSecure = t.isDefined(server.addContext) | ||
const ipList = util.getIPList() | ||
.map(iface => `${isSecure ? 'https' : 'http'}://${iface.address}:${options.port}`) | ||
view.write('server-listening', ipList) | ||
}) | ||
} | ||
module.exports = Lws |
177
lib/util.js
'use strict' | ||
const path = require('path') | ||
const flatten = require('reduce-flatten') | ||
const arrayify = require('array-back') | ||
@@ -10,100 +7,49 @@ /** | ||
exports.parseCommandLineOptions = parseCommandLineOptions | ||
exports.loadFeature = loadFeature | ||
exports.loadModule = loadModule | ||
exports.deepMerge = deepMerge | ||
exports.camelCaseObject = camelCaseObject | ||
exports.printError = printError | ||
exports.getIPList = getIPList | ||
exports.getOptionDefinitions = getOptionDefinitions | ||
exports.expandStack = expandStack | ||
exports.deepMerge = deepMerge | ||
/** | ||
* Return commandLineArgs output. Expand stack too. | ||
*/ | ||
function parseCommandLineOptions (defs) { | ||
/* pass in the optionDefinitions supplied in the constructor options */ | ||
const commandLineArgs = require('command-line-args') | ||
const cli = require('./cli-data') | ||
let optionDefinitions = [ ...cli.optionDefinitions, ...defs || [] ] | ||
let options = commandLineArgs(optionDefinitions, { partial: true }) | ||
if (options._all.stack && options._all.stack.length) { | ||
options._all.stack = expandStack(options._all.stack) | ||
const stackDefinitions = getOptionDefinitions(options._all.stack) | ||
optionDefinitions = [ ...optionDefinitions, ...stackDefinitions ] | ||
options = commandLineArgs(optionDefinitions) | ||
options._all.stack = options._all.stack ? expandStack(options._all.stack) : [] | ||
} else { | ||
options = commandLineArgs(optionDefinitions) | ||
} | ||
return { options, optionDefinitions } | ||
} | ||
function deepMerge (...args) { | ||
const assignWith = require('lodash.assignwith') | ||
const t = require('typical') | ||
/** | ||
* Load a module and verify it's of the correct type | ||
* @returns {Feature} | ||
*/ | ||
function loadFeature (modulePath) { | ||
const isModule = module => module.prototype && (module.prototype.middleware || module.prototype.stack || module.prototype.ready || true) | ||
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) | ||
function customiser (previousValue, newValue, key, object, source) { | ||
/* deep merge plain objects */ | ||
if (t.isPlainObject(previousValue) && t.isPlainObject(newValue)) { | ||
return assignWith(previousValue, newValue, customiser) | ||
/* overwrite arrays if the new array has items */ | ||
} else if (Array.isArray(previousValue) && Array.isArray(newValue) && newValue.length) { | ||
return newValue.slice() | ||
/* ignore incoming arrays if empty */ | ||
} else if (Array.isArray(newValue) && !newValue.length) { | ||
return Array.isArray(previousValue) ? previousValue.slice() : previousValue | ||
} else if (!t.isDefined(previousValue) && Array.isArray(newValue)) { | ||
return newValue.slice() | ||
} | ||
} else { | ||
const msg = `No module found for: ${modulePath}` | ||
console.error(msg) | ||
process.exit(1) | ||
} | ||
return module | ||
return assignWith(...args, customiser) | ||
} | ||
/** | ||
* Returns a module, loaded by the first to succeed from | ||
* - direct path | ||
* - 'node_modules/local-web-server-' + path, from current folder upward | ||
* - 'node_modules/' + path, from current folder upward | ||
* - also search local-web-server project node_modules? (e.g. to search for a feature module without need installing it locally) | ||
* @returns {object} | ||
*/ | ||
function loadModule (modulePath) { | ||
let module | ||
const tried = [] | ||
if (modulePath) { | ||
try { | ||
tried.push(path.resolve(modulePath)) | ||
module = require(path.resolve(modulePath)) | ||
} catch (err) { | ||
if (!(err && err.code === 'MODULE_NOT_FOUND')) { | ||
throw err | ||
} | ||
const walkBack = require('walk-back') | ||
const foundPath = walkBack(process.cwd(), path.join('node_modules', 'local-web-server-' + modulePath)) | ||
tried.push('local-web-server-' + modulePath) | ||
if (foundPath) { | ||
module = require(foundPath) | ||
} else { | ||
const foundPath2 = walkBack(process.cwd(), path.join('node_modules', modulePath)) | ||
tried.push(modulePath) | ||
if (foundPath2) { | ||
module = require(foundPath2) | ||
} else { | ||
const foundPath3 = walkBack(path.resolve(__filename, '..'), path.join('node_modules', 'local-web-server-' + modulePath)) | ||
if (foundPath3) { | ||
return require(foundPath3) | ||
} else { | ||
const foundPath4 = walkBack(path.resolve(__filename, '..'), path.join('node_modules', modulePath)) | ||
if (foundPath4) { | ||
return require(foundPath4) | ||
} | ||
} | ||
} | ||
} | ||
function camelCaseObject (object) { | ||
const camelCase = require('lodash.camelcase') | ||
for (const prop of Object.keys(object)) { | ||
const converted = camelCase(prop) | ||
if (converted !== prop) { | ||
object[converted] = object[prop] | ||
delete object[prop] | ||
} | ||
} | ||
return module | ||
return object | ||
} | ||
function printError (err, title) { | ||
const ansi = require('ansi-escape-sequences') | ||
const now = new Date() | ||
const time = now.toLocaleTimeString() | ||
if (title) console.error(ansi.format(`${time}: [underline red]{${title}}`)) | ||
console.error(ansi.format(err.stack, 'red')) | ||
} | ||
/** | ||
@@ -126,2 +72,3 @@ * Returns an array of available IPv4 network interfaces | ||
const os = require('os') | ||
const flatten = require('reduce-flatten') | ||
@@ -135,51 +82,1 @@ let ipList = Object.keys(os.networkInterfaces()) | ||
} | ||
/** | ||
* Grouped 'middleware'. | ||
* @return {OptionDefinition[]} | ||
*/ | ||
function getOptionDefinitions (stack) { | ||
return arrayify(stack) | ||
.map(Feature => new Feature(this)) | ||
.filter(feature => feature.optionDefinitions) | ||
.map(feature => feature.optionDefinitions()) | ||
.reduce(flatten, []) | ||
.filter(def => def) | ||
.map(def => { | ||
def.group = 'middleware' | ||
return def | ||
}) | ||
} | ||
function expandStack (stack) { | ||
if (stack && stack.length) { | ||
stack.forEach((featurePath, index) => { | ||
if (typeof featurePath === 'string') { | ||
stack[index] = loadFeature(featurePath) | ||
} | ||
}) | ||
return stack | ||
} else { | ||
return [] | ||
} | ||
} | ||
function deepMerge (...args) { | ||
const assignWith = require('lodash.assignwith') | ||
const t = require('typical') | ||
function customiser (objValue, srcValue, key, object, source) { | ||
if (t.isPlainObject(objValue) && t.isPlainObject(srcValue)) { | ||
return assignWith(objValue, srcValue, customiser) | ||
} else if (Array.isArray(objValue) && Array.isArray(srcValue)) { | ||
for (const value of srcValue) { | ||
objValue.push(value) | ||
} | ||
return objValue | ||
} else if (!t.isDefined(objValue) && Array.isArray(srcValue)) { | ||
return srcValue.slice() | ||
} | ||
} | ||
return assignWith(...args, customiser) | ||
} |
{ | ||
"name": "lws", | ||
"author": "Lloyd Brookes <75pound@gmail.com>", | ||
"version": "1.0.0-pre.4", | ||
"version": "1.0.0-pre.5", | ||
"description": "A modular, server application shell for creating a personalised local web server to support productive full-stack Javascript development.", | ||
@@ -18,3 +18,4 @@ "repository": "https://github.com/lwsjs/lws.git", | ||
"test": "test-runner test/*.js", | ||
"docs": "jsdoc2md -t jsdoc2md/README.hbs lib/*.js > README.md; echo" | ||
"docs": "jsdoc2md -t jsdoc2md/README.hbs lib/*.js > README.md; echo", | ||
"cover": "istanbul cover ./node_modules/.bin/test-runner test/*.js || cat coverage/lcov.info | ./node_modules/.bin/coveralls" | ||
}, | ||
@@ -24,14 +25,21 @@ "dependencies": { | ||
"array-back": "^1.0.4", | ||
"command-line-args": "^4.0.2", | ||
"array-base": "^1.0.1", | ||
"byte-size": "^3.0.0", | ||
"cli-commands": "^0.3.2", | ||
"command-line-args": "^4.0.6", | ||
"command-line-usage": "^4.0.0", | ||
"koa": "^2.2.0", | ||
"load-module": "^0.2.3", | ||
"lodash.assignwith": "^4.2.0", | ||
"lodash.camelcase": "^4.3.0", | ||
"readable-stream": "^2.2.11", | ||
"reduce-flatten": "^1.0.1", | ||
"typical": "^2.6.0", | ||
"typical": "^2.6.1", | ||
"walk-back": "^3.0.0" | ||
}, | ||
"devDependencies": { | ||
"coveralls": "^2.13.1", | ||
"req-then": "^0.6.1", | ||
"test-runner": "^0.3.0" | ||
"test-runner": "^0.4.0" | ||
} | ||
} |
260
README.md
@@ -1,5 +0,6 @@ | ||
[![view on npm](http://img.shields.io/npm/v/lws.svg)](https://www.npmjs.org/package/lws) | ||
[![npm module downloads](http://img.shields.io/npm/dt/lws.svg)](https://www.npmjs.org/package/lws) | ||
[![view on npm](https://img.shields.io/npm/v/lws.svg)](https://www.npmjs.org/package/lws) | ||
[![npm module downloads](https://img.shields.io/npm/dt/lws.svg)](https://www.npmjs.org/package/lws) | ||
[![Build Status](https://travis-ci.org/lwsjs/lws.svg?branch=master)](https://travis-ci.org/lwsjs/lws) | ||
[![Dependency Status](https://david-dm.org/lwsjs/lws.svg)](https://david-dm.org/lwsjs/lws) | ||
[![Coverage Status](https://coveralls.io/repos/github/lwsjs/lws/badge.svg)](https://coveralls.io/github/lwsjs/lws) | ||
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](https://github.com/feross/standard) | ||
@@ -9,259 +10,6 @@ | ||
A modular server application shell for creating a personalised local web server to support productive, full-stack Javascript development. | ||
**Documentation work in progress.** | ||
## Synopsis | ||
`lws` is a command-line tool, install it by running `npm install -g lws`. Here's a quick look at the usage: | ||
``` | ||
$ lws --help | ||
lws | ||
A modular server application shell for creating a personalised local web server to support productive, full-stack Javascript development. | ||
Synopsis | ||
$ ws [--verbose] [--config-file file] [<server options>] [<middleware options>] | ||
$ ws --config | ||
$ ws --help | ||
$ ws --version | ||
General | ||
-h, --help Print these usage instructions. | ||
--config Print the active config. | ||
-c, --config-file file Config filename to use, defaults to "lws.config.js". | ||
-v, --verbose Verbose output. | ||
--version Print the version number. | ||
Server | ||
-p, --port number Web server port. | ||
--hostname string The hostname (or IP address) to listen on. Defaults to 0.0.0.0. | ||
--stack feature ... Feature stack. | ||
--key file SSL key. Supply along with --cert to launch a https server. | ||
--cert file SSL cert. Supply along with --key to launch a https server. | ||
--https Enable HTTPS using a built-in key and cert, registered to the domain | ||
127.0.0.1. | ||
Middleware | ||
No middleware specified. | ||
Project home: https://github.com/lwsjs/lws | ||
``` | ||
Running `lws` launches a web server. | ||
``` | ||
$ lws | ||
Serving at http://mbp.local:8000, http://127.0.0.1:8000, http://192.168.0.32:8000 | ||
``` | ||
However, by default it's empty. Any resource requested will return `404 Not Found`. | ||
``` | ||
$ curl -I http://127.0.0.1:8000/README.md | ||
HTTP/1.1 404 Not Found | ||
Date: Wed, 22 Mar 2017 20:41:07 GMT | ||
Connection: keep-alive | ||
``` | ||
So, install one or more features and pass their names to `--stack`. Notice the "Middleware" section of the usage guide now contains additional options added by the `lws-static` feature: | ||
``` | ||
$ npm install lws-static | ||
$ npm --stack lws-static --help | ||
... | ||
Middleware | ||
-d, --directory path Root directory, defaults to the current directory. | ||
--static.maxage number Browser cache max-age in milliseconds. | ||
--static.defer If true, serves after `yield next`, allowing any downstream middleware to | ||
respond first. | ||
--static.index path Default file name, defaults to `index.html`. | ||
``` | ||
By launching `lws` with this feature we're now able to serve static files. | ||
``` | ||
$ lws --stack lws-static | ||
Serving at http://mbp.local:8000, http://127.0.0.1:8000, http://192.168.0.32:8000 | ||
$ curl -I http://127.0.0.1:8000/README.md | ||
HTTP/1.1 200 OK | ||
Content-Length: 3286 | ||
Last-Modified: Wed, 22 Mar 2017 00:22:21 GMT | ||
Cache-Control: max-age=0 | ||
Content-Type: text/x-markdown; charset=utf-8 | ||
Date: Wed, 22 Mar 2017 20:39:18 GMT | ||
Connection: keep-alive | ||
``` | ||
You can use pre-built features or make your own. | ||
```js | ||
class Feature { | ||
optionDefinitions () { | ||
return [ | ||
{ name: 'team', type: String } | ||
] | ||
} | ||
middleware (options) { | ||
return (ctx, next) => { | ||
ctx.body = `Your team: ${options.team}` | ||
await next() | ||
} | ||
} | ||
} | ||
module.exports = Feature | ||
``` | ||
If you want a server with all the common features pre-installed, look at [local-web-server](https://github.com/75lb/local-web-server). | ||
## Install | ||
Command-line tool: | ||
``` | ||
$ npm install -g lws | ||
``` | ||
Install the API for use in nodejs: | ||
``` | ||
$ npm install lws --save | ||
``` | ||
# API Reference | ||
<a name="module_lws"></a> | ||
## lws | ||
Creating command-line web servers suitable for full-stack javascript development. | ||
* [lws](#module_lws) | ||
* [Lws](#exp_module_lws--Lws) ⏏ | ||
* [new Lws([options])](#new_module_lws--Lws_new) | ||
* [.app](#module_lws--Lws.Lws+app) : <code>Koa</code> | ||
* [.server](#module_lws--Lws.Lws+server) : <code>http.Server</code> \| <code>https.Server</code> | ||
* [.features](#module_lws--Lws.Lws+features) : <code>Array.<Feature></code> | ||
* [.start()](#module_lws--Lws+start) | ||
<a name="exp_module_lws--Lws"></a> | ||
### Lws ⏏ | ||
**Kind**: Exported class | ||
<a name="new_module_lws--Lws_new"></a> | ||
#### new Lws([options]) | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| [options] | <code>object</code> | Server options | | ||
| [options.port] | <code>number</code> | Port | | ||
| [options.hostname] | <code>string</code> | The hostname (or IP address) to listen on. Defaults to 0.0.0.0. | | ||
| [options.config-file] | <code>string</code> | Config file, defaults to 'lws.config.js'. | | ||
| [options.stack] | <code>Array.<string></code> \| <code>Array.<Features></code> | Array of feature classes, or filenames of modules exporting a feature class. | | ||
| [options.https] | <code>boolean</code> | Enable HTTPS using a built-in key and cert, registered to the domain 127.0.0.1. | | ||
| [options.key] | <code>string</code> | SSL key. Supply along with --cert to launch a https server. | | ||
| [options.cert] | <code>string</code> | SSL cert. Supply along with --key to launch a https server. | | ||
**Example** | ||
```js | ||
const Lws = require('lws') | ||
const lws = new Lws() | ||
lws.start({ https: true}) | ||
``` | ||
<a name="module_lws--Lws.Lws+app"></a> | ||
#### lws.app : <code>Koa</code> | ||
The [Koa application](https://github.com/koajs/koa/blob/master/docs/api/index.md#application) instance in use. | ||
**Kind**: instance property of <code>[Lws](#exp_module_lws--Lws)</code> | ||
<a name="module_lws--Lws.Lws+server"></a> | ||
#### lws.server : <code>http.Server</code> \| <code>https.Server</code> | ||
The node server in use, an instance of either [http.Server](https://nodejs.org/dist/latest-v7.x/docs/api/http.html#http_class_http_server) or [https.Server](https://nodejs.org/dist/latest-v7.x/docs/api/https.html#https_class_https_server). | ||
**Kind**: instance property of <code>[Lws](#exp_module_lws--Lws)</code> | ||
<a name="module_lws--Lws.Lws+features"></a> | ||
#### lws.features : <code>Array.<Feature></code> | ||
Feature instances | ||
**Kind**: instance property of <code>[Lws](#exp_module_lws--Lws)</code> | ||
<a name="module_lws--Lws+start"></a> | ||
#### lws.start() | ||
Start the app. | ||
**Kind**: instance method of <code>[Lws](#exp_module_lws--Lws)</code> | ||
# Feature interface | ||
<a name="module_feature"></a> | ||
## feature | ||
* [feature](#module_feature) | ||
* [Feature](#exp_module_feature--Feature) ⏏ | ||
* [new Feature(lws)](#new_module_feature--Feature_new) | ||
* [.optionDefinitions()](#module_feature--Feature+optionDefinitions) ⇒ <code>OptionDefinition</code> \| <code>Array.<OptionDefinition></code> | ||
* [.middleware()](#module_feature--Feature+middleware) ⇒ <code>KoaMiddleware</code> | ||
* [.ready(lws)](#module_feature--Feature+ready) | ||
<a name="exp_module_feature--Feature"></a> | ||
### Feature ⏏ | ||
Feature interface. | ||
**Kind**: Exported class | ||
<a name="new_module_feature--Feature_new"></a> | ||
#### new Feature(lws) | ||
localWebServer instance passed to constructor in case feature needs access to http server instance. | ||
| Param | Type | | ||
| --- | --- | | ||
| lws | <code>Lws</code> | | ||
<a name="module_feature--Feature+optionDefinitions"></a> | ||
#### feature.optionDefinitions() ⇒ <code>OptionDefinition</code> \| <code>Array.<OptionDefinition></code> | ||
Return one or more options definitions to collect command-line input | ||
**Kind**: instance method of <code>[Feature](#exp_module_feature--Feature)</code> | ||
<a name="module_feature--Feature+middleware"></a> | ||
#### feature.middleware() ⇒ <code>KoaMiddleware</code> | ||
Return one of more middleware functions with three args (req, res and next). Can be created by express, Koa or hand-rolled. | ||
**Kind**: instance method of <code>[Feature](#exp_module_feature--Feature)</code> | ||
<a name="module_feature--Feature+ready"></a> | ||
#### feature.ready(lws) | ||
Called once the server is launched and ready to accept connections. | ||
**Kind**: instance method of <code>[Feature](#exp_module_feature--Feature)</code> | ||
| Param | Type | | ||
| --- | --- | | ||
| lws | <code>Lws</code> | | ||
* * * | ||
© 2016-17 Lloyd Brookes \<75pound@gmail.com\>. Documented by [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown). |
@@ -5,4 +5,5 @@ class Feature { | ||
} | ||
ready () {} | ||
} | ||
module.exports = Feature |
@@ -1,9 +0,12 @@ | ||
class Feature { | ||
class Two { | ||
middleware (options) { | ||
return (ctx, next) => { | ||
ctx.body += 'two' | ||
ctx.body = (ctx.body || '') + 'two' | ||
} | ||
} | ||
optionDefinitions () { | ||
return [ { name: 'something' }] | ||
} | ||
} | ||
module.exports = Feature | ||
module.exports = Two |
114
test/lws.js
@@ -9,28 +9,8 @@ 'use strict' | ||
runner.test('stack: one feature', async function () { | ||
const port = 9000 + this.index | ||
class Feature { | ||
runner.test('http', async function () { | ||
const port = 9100 + this.index | ||
class One { | ||
middleware (options) { | ||
return (ctx, next) => { | ||
ctx.body = 'one' | ||
} | ||
} | ||
} | ||
const lws = new Lws({ | ||
stack: Feature, | ||
port: port | ||
}) | ||
lws.start() | ||
const response = await request(`http://localhost:${port}`) | ||
lws.server.close() | ||
a.strictEqual(response.data.toString(), 'one') | ||
a.strictEqual(lws.options.stack.length, 1) | ||
}) | ||
runner.test('stack: one feature, one feature path', async function () { | ||
const port = 9000 + this.index | ||
class Feature { | ||
middleware (options) { | ||
return (ctx, next) => { | ||
ctx.body = 'one' | ||
next() | ||
@@ -41,90 +21,8 @@ } | ||
const lws = new Lws({ | ||
stack: [ Feature, 'test/fixture/two.js' ], | ||
stack: [ One ], | ||
port: port | ||
}) | ||
lws.start() | ||
const response = await request(`http://localhost:${port}`) | ||
lws.server.close() | ||
a.strictEqual(response.data.toString(), 'onetwo') | ||
a.strictEqual(lws.options.stack.length, 2) | ||
}) | ||
runner.test('stack: one feature, one cli feature path', async function () { | ||
const port = 9000 + this.index | ||
class Feature { | ||
middleware (options) { | ||
return (ctx, next) => { | ||
ctx.body = 'one' | ||
next() | ||
} | ||
} | ||
} | ||
const lws = new Lws({ | ||
stack: [ Feature ], | ||
port: port | ||
}) | ||
process.argv = [ 'node', 'example.js', '--stack', 'test/fixture/two.js' ] | ||
lws.start() | ||
process.argv = [ 'node', 'example.js' ] // reset as process.argv is global | ||
const response = await request(`http://localhost:${port}`) | ||
lws.server.close() | ||
a.strictEqual(lws.options.stack.length, 2) | ||
a.strictEqual(response.data.toString(), 'onetwo') | ||
}) | ||
runner.test('stack: Two features', async function () { | ||
const port = 9000 + this.index | ||
class Feature { | ||
middleware (options) { | ||
return (ctx, next) => { | ||
ctx.body = 'one' | ||
next() | ||
} | ||
} | ||
} | ||
class Feature2 { | ||
middleware (options) { | ||
return (ctx, next) => { | ||
ctx.body += 'two' | ||
next() | ||
} | ||
} | ||
} | ||
const lws = new Lws({ | ||
stack: [ Feature, Feature2 ], | ||
port: port | ||
}) | ||
lws.start() | ||
const response = await request(`http://localhost:${port}`) | ||
lws.server.close() | ||
a.strictEqual(response.data.toString(), 'onetwo') | ||
a.strictEqual(lws.options.stack.length, 2) | ||
}) | ||
runner.skip('--help', function () { | ||
const lws = new Lws() | ||
process.argv = [ 'node', 'script.js', '--help' ] | ||
// need to be able to pass in the output stream in order to test it | ||
}) | ||
runner.test('https', async function () { | ||
const port = 9000 + this.index | ||
class Feature { | ||
middleware (options) { | ||
return (ctx, next) => { | ||
ctx.body = 'one' | ||
next() | ||
} | ||
} | ||
} | ||
const lws = new Lws({ | ||
stack: [ Feature ], | ||
https: true, | ||
port: port | ||
}) | ||
lws.start() | ||
lws.launch() | ||
const url = require('url') | ||
const reqOptions = url.parse(`https://127.0.0.1:${port}`) | ||
reqOptions.rejectUnauthorized = false | ||
const response = await request(reqOptions) | ||
const response = await request(`http://127.0.0.1:${port}`) | ||
lws.server.close() | ||
@@ -131,0 +29,0 @@ a.strictEqual(response.res.statusCode, 200) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
67271
31
961
4
15
3
15
3
+ Addedarray-base@^1.0.1
+ Addedbyte-size@^3.0.0
+ Addedcli-commands@^0.3.2
+ Addedload-module@^0.2.3
+ Addedlodash.camelcase@^4.3.0
+ Addedreadable-stream@^2.2.11
+ Addedarray-base@1.0.1(transitive)
+ Addedbyte-size@3.0.0(transitive)
+ Addedcli-commands@0.3.3(transitive)
+ Addedcommand-line-commands@2.0.1(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedload-module@0.2.5(transitive)
+ Addedlodash.camelcase@4.3.0(transitive)
+ Addedprocess-nextick-args@2.0.1(transitive)
+ Addedreadable-stream@2.3.8(transitive)
+ Addedsafe-buffer@5.1.2(transitive)
+ Addedstring_decoder@1.1.1(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
Updatedcommand-line-args@^4.0.6
Updatedtypical@^2.6.1