switch to using lws.. clean up
This commit is contained in:
@ -1,87 +0,0 @@
|
||||
exports.optionDefinitions = [
|
||||
{
|
||||
name: 'port', alias: 'p', type: Number, defaultOption: true,
|
||||
description: 'Web server port.', group: 'server'
|
||||
},
|
||||
{
|
||||
name: 'stack', type: String, multiple: true,
|
||||
description: 'Feature stack.', group: 'server'
|
||||
},
|
||||
{
|
||||
name: 'key', type: String, typeLabel: '[underline]{file}', group: 'server',
|
||||
description: 'SSL key. Supply along with --cert to launch a https server.'
|
||||
},
|
||||
{
|
||||
name: 'cert', type: String, typeLabel: '[underline]{file}', group: 'server',
|
||||
description: 'SSL cert. Supply along with --key to launch a https server.'
|
||||
},
|
||||
{
|
||||
name: 'https', type: Boolean, group: 'server',
|
||||
description: 'Enable HTTPS using a built-in key and cert, registered to the domain 127.0.0.1.'
|
||||
},
|
||||
{
|
||||
name: 'help', alias: 'h', type: Boolean,
|
||||
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'
|
||||
},
|
||||
{
|
||||
name: 'config-file', type: String,
|
||||
description: 'Config file to use', group: 'misc'
|
||||
},
|
||||
{
|
||||
name: 'verbose', type: Boolean, alias: 'v',
|
||||
description: 'Verbose output.', group: 'misc'
|
||||
},
|
||||
{
|
||||
name: 'debug', type: Boolean,
|
||||
description: 'Very verbose output, intended for debugging.', group: 'misc'
|
||||
},
|
||||
{
|
||||
name: 'version', type: Boolean,
|
||||
description: 'Print the version number.', group: 'misc'
|
||||
}
|
||||
]
|
||||
|
||||
function usage (middlewareDefinitions) {
|
||||
return [
|
||||
{
|
||||
header: 'local-web-server',
|
||||
content: 'A simple web-server for productive front-end development.'
|
||||
},
|
||||
{
|
||||
header: 'Synopsis',
|
||||
content: [
|
||||
'$ ws [--verbose] [<server options>] [<middleware options>]',
|
||||
'$ ws --config',
|
||||
'$ ws --help'
|
||||
]
|
||||
},
|
||||
{
|
||||
header: 'Server',
|
||||
optionList: exports.optionDefinitions,
|
||||
group: 'server'
|
||||
},
|
||||
{
|
||||
header: 'Middleware',
|
||||
optionList: middlewareDefinitions,
|
||||
group: 'middleware'
|
||||
},
|
||||
{
|
||||
header: 'Misc',
|
||||
optionList: exports.optionDefinitions,
|
||||
group: 'misc'
|
||||
},
|
||||
{
|
||||
content: 'Project home: [underline]{https://github.com/75lb/local-web-server}'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
exports.usage = usage
|
@ -1,47 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Feature interface.
|
||||
*/
|
||||
class Feature {
|
||||
/**
|
||||
* localWebServer instance passed to constructor in case feature needs access to http server instance.
|
||||
*/
|
||||
constructor (localWebServer) {}
|
||||
|
||||
/**
|
||||
* Return one or more options definitions to collect command-line input
|
||||
* @returns {OptionDefinition|OptionDefinition[]}
|
||||
*/
|
||||
optionDefinitions () {}
|
||||
|
||||
/**
|
||||
* Return one of more middleware functions with three args (req, res and next). Can be created by express, Koa or hand-rolled.
|
||||
*/
|
||||
middleware (options) {}
|
||||
|
||||
expandStack () {
|
||||
const flatten = require('reduce-flatten')
|
||||
|
||||
if (this.stack) {
|
||||
const featureStack = this.stack()
|
||||
.map(Feature => new Feature())
|
||||
|
||||
this.optionDefinitions = function () {
|
||||
return featureStack
|
||||
.map(feature => feature.optionDefinitions && feature.optionDefinitions())
|
||||
.filter(definitions => definitions)
|
||||
.reduce(flatten, [])
|
||||
}
|
||||
this.middleware = function (options, view) {
|
||||
return featureStack
|
||||
.map(feature => feature.middleware(options, view))
|
||||
.reduce(flatten, [])
|
||||
.filter(mw => mw)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Feature
|
@ -1,10 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const flatten = require('reduce-flatten')
|
||||
const arrayify = require('array-back')
|
||||
const ansi = require('ansi-escape-sequences')
|
||||
const Lws = require('lws')
|
||||
|
||||
/**
|
||||
* @module local-web-server
|
||||
@ -12,329 +7,12 @@ const ansi = require('ansi-escape-sequences')
|
||||
|
||||
/**
|
||||
* @alias module:local-web-server
|
||||
* @extends module:middleware-stack
|
||||
*/
|
||||
class LocalWebServer {
|
||||
|
||||
/**
|
||||
* @param [options] {object} - Server options
|
||||
* @param [options.port} {number} - Port
|
||||
* @param [options.stack} {string[]|Features[]} - Port
|
||||
*/
|
||||
constructor (initOptions) {
|
||||
initOptions = initOptions || {}
|
||||
const commandLineUsage = require('command-line-usage')
|
||||
const CliView = require('./cli-view')
|
||||
const cli = require('../lib/cli-data')
|
||||
|
||||
/* get stored config */
|
||||
const loadConfig = require('config-master')
|
||||
const stored = loadConfig('local-web-server')
|
||||
|
||||
/* read the config and command-line for feature paths */
|
||||
const featurePaths = parseFeaturePaths(initOptions.stack || stored.stack)
|
||||
|
||||
/**
|
||||
* Loaded feature modules
|
||||
* @type {Feature[]}
|
||||
*/
|
||||
this.features = this._buildFeatureStack(featurePaths)
|
||||
|
||||
/* gather feature optionDefinitions and parse the command line */
|
||||
const featureOptionDefinitions = gatherOptionDefinitions(this.features)
|
||||
const usage = commandLineUsage(cli.usage(featureOptionDefinitions))
|
||||
const allOptionDefinitions = cli.optionDefinitions.concat(featureOptionDefinitions)
|
||||
let options = initOptions.testMode ? {} : parseCommandLineOptions(allOptionDefinitions, this.view)
|
||||
|
||||
/* combine in stored config */
|
||||
options = Object.assign(
|
||||
{ port: 8000 },
|
||||
initOptions,
|
||||
stored,
|
||||
options.server,
|
||||
options.middleware,
|
||||
options.misc
|
||||
)
|
||||
|
||||
/**
|
||||
* Config
|
||||
* @type {object}
|
||||
*/
|
||||
this.options = options
|
||||
|
||||
/**
|
||||
* Current view.
|
||||
* @type {View}
|
||||
*/
|
||||
this.view = null
|
||||
|
||||
/* --config */
|
||||
if (options.config) {
|
||||
console.error(JSON.stringify(options, null, ' '))
|
||||
process.exit(0)
|
||||
|
||||
/* --version */
|
||||
} else if (options.version) {
|
||||
const pkg = require(path.resolve(__dirname, '..', 'package.json'))
|
||||
console.error(pkg.version)
|
||||
process.exit(0)
|
||||
|
||||
/* --help */
|
||||
} else if (options.help) {
|
||||
console.error(usage)
|
||||
process.exit(0)
|
||||
|
||||
} else {
|
||||
/**
|
||||
* Node.js server
|
||||
* @type {Server}
|
||||
*/
|
||||
this.server = this.getServer()
|
||||
|
||||
if (options.view) {
|
||||
const View = loadModule(options.view)
|
||||
this.view = new View(this)
|
||||
} else {
|
||||
this.view = new CliView(this)
|
||||
}
|
||||
|
||||
for (const feature of this.features) {
|
||||
if (feature.ready) {
|
||||
feature.ready(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
const compose = require('koa-compose')
|
||||
const convert = require('koa-convert')
|
||||
|
||||
const middlewareStack = this.features
|
||||
.filter(mw => mw.middleware)
|
||||
.map(mw => mw.middleware(this.options, this))
|
||||
.reduce(flatten, [])
|
||||
.filter(mw => mw)
|
||||
.map(convert)
|
||||
|
||||
app.use(compose(middlewareStack))
|
||||
app.on('error', err => {
|
||||
console.error(ansi.format(err.stack, 'red'))
|
||||
class LocalWebServer extends Lws {
|
||||
constructor () {
|
||||
super({
|
||||
stack: [ 'log', 'static' ]
|
||||
})
|
||||
return app.callback()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a listening server which processes requests using the middleware supplied.
|
||||
* @returns {Server}
|
||||
*/
|
||||
getServer () {
|
||||
const app = this.getApplication()
|
||||
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')
|
||||
}
|
||||
|
||||
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)
|
||||
server.isHttps = true
|
||||
} else {
|
||||
const http = require('http')
|
||||
server = http.createServer(app)
|
||||
}
|
||||
|
||||
server.listen(options.port)
|
||||
// if (onListening) server.on('listening', onListening)
|
||||
|
||||
/* on server-up message */
|
||||
if (!options.testMode) {
|
||||
server.on('listening', () => {
|
||||
const ipList = getIPList()
|
||||
.map(iface => `[underline]{${server.isHttps ? 'https' : 'http'}://${iface.address}:${options.port}}`)
|
||||
.join(', ')
|
||||
console.error('Serving at', ansi.format(ipList))
|
||||
})
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Feature instances, given their module paths/names.
|
||||
* @return {feature[]}
|
||||
*/
|
||||
_buildFeatureStack (featurePaths) {
|
||||
const FeatureBase = require('./feature')
|
||||
return featurePaths
|
||||
.map(featurePath => loadFeature(featurePath))
|
||||
.map(Feature => new Feature(this))
|
||||
.map(feature => FeatureBase.prototype.expandStack.call(feature))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
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 for: ${modulePath}`
|
||||
console.error(msg)
|
||||
process.exit(1)
|
||||
}
|
||||
return module
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return module
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of available IPv4 network interfaces
|
||||
* @example
|
||||
* [ { address: 'mbp.local' },
|
||||
* { address: '127.0.0.1',
|
||||
* netmask: '255.0.0.0',
|
||||
* family: 'IPv4',
|
||||
* mac: '00:00:00:00:00:00',
|
||||
* internal: true },
|
||||
* { address: '192.168.1.86',
|
||||
* netmask: '255.255.255.0',
|
||||
* family: 'IPv4',
|
||||
* mac: 'd0:a6:37:e9:86:49',
|
||||
* internal: false } ]
|
||||
*/
|
||||
function getIPList () {
|
||||
const flatten = require('reduce-flatten')
|
||||
const os = require('os')
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/* manually scan for any --stack passed, as we may need to display stack options */
|
||||
function parseFeaturePaths (configStack) {
|
||||
const featurePaths = arrayify(configStack)
|
||||
const featureIndex = process.argv.indexOf('--stack')
|
||||
if (featureIndex > -1) {
|
||||
for (var i = featureIndex + 1; i < process.argv.length; i++) {
|
||||
const featurePath = process.argv[i]
|
||||
if (/^-/.test(featurePath)) {
|
||||
break
|
||||
} else {
|
||||
featurePaths.push(featurePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if the user did not supply a stack, use the default */
|
||||
if (!featurePaths.length) featurePaths.push(path.resolve(__dirname, '..', 'node_modules', 'local-web-server-default-stack'))
|
||||
return featurePaths
|
||||
}
|
||||
|
||||
function gatherOptionDefinitions (features) {
|
||||
return features
|
||||
.filter(mw => mw.optionDefinitions)
|
||||
.map(mw => mw.optionDefinitions())
|
||||
.reduce(flatten, [])
|
||||
.filter(def => def)
|
||||
.map(def => {
|
||||
def.group = 'middleware'
|
||||
return def
|
||||
})
|
||||
}
|
||||
|
||||
function parseCommandLineOptions (allOptionDefinitions) {
|
||||
const commandLineArgs = require('command-line-args')
|
||||
try {
|
||||
return commandLineArgs(allOptionDefinitions)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
|
||||
/* handle duplicate option names */
|
||||
if (err.name === 'DUPLICATE_NAME') {
|
||||
console.error('\nOption Definitions:')
|
||||
console.error(allOptionDefinitions.map(def => {
|
||||
return `name: ${def.name}${def.alias ? ', alias: ' + def.alias : ''}`
|
||||
}).join('\n'))
|
||||
}
|
||||
console.error(usage)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user