Browse Source

switch to using lws.. clean up

master
Lloyd Brookes 7 years ago
parent
commit
f18a07ba5a
  1. 4
      .travis.yml
  2. 2
      LICENSE
  3. 2
      README.md
  4. 3
      bin/cli.js
  5. 87
      lib/cli-data.js
  6. 47
      lib/feature.js
  7. 332
      lib/local-web-server.js
  8. 26
      package.json
  9. 20
      ssl/127.0.0.1.crt
  10. 27
      ssl/127.0.0.1.key

4
.travis.yml

@ -1,5 +1,3 @@
language: node_js
node_js:
- 4
- 5
- 6
- 7

2
LICENSE

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013-16 Lloyd Brookes <75pound@gmail.com>
Copyright (c) 2013-17 Lloyd Brookes <75pound@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

2
README.md

@ -102,4 +102,4 @@ $ npm install -g local-web-server
```
* * *
&copy; 2013-16 Lloyd Brookes <75pound@gmail.com>. Documented by [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown).
&copy; 2013-17 Lloyd Brookes <75pound@gmail.com>. Documented by [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown).

3
bin/cli.js

@ -1,4 +1,5 @@
#!/usr/bin/env node
'use strict'
const LocalWebServer = require('../')
new LocalWebServer()
const localWebServer = new LocalWebServer()
localWebServer.start()

87
lib/cli-data.js

@ -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

47
lib/feature.js

@ -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

332
lib/local-web-server.js

@ -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'))
})
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
class LocalWebServer extends Lws {
constructor () {
super({
stack: [ 'log', 'static' ]
})
}
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)
}
}

26
package.json

@ -30,32 +30,16 @@
"ssl"
],
"scripts": {
"test": "tape test/*.js",
"test": "test-runner test/*.js",
"docs": "jsdoc2md -t jsdoc2md/api.hbs -p list lib/*.js > doc/api.md; echo",
"cover": "istanbul cover ./node_modules/.bin/tape test/*.js && cat coverage/lcov.info | coveralls && rm -rf coverage; echo"
},
"repository": "https://github.com/75lb/local-web-server",
"author": "Lloyd Brookes <75pound@gmail.com>",
"dependencies": {
"ansi-escape-sequences": "^3.0.0",
"array-back": "^1.0.4",
"command-line-args": "^4.0.1",
"command-line-usage": "^4.0.0",
"config-master": "^3.0.0",
"koa": "next",
"koa-compose": "^3.1.0",
"koa-convert": "^1.2.0",
"local-web-server-default-stack": "github:local-web-server/default-stack",
"reduce-flatten": "^1.0.1",
"table-layout": "~0.4.0",
"typical": "^2.6.0",
"walk-back": "^2.0.1"
"local-web-server-log": "file:///Users/lloydb/Documents/lws/log",
"local-web-server-static": "file:///Users/lloydb/Documents/lws/static",
"lws": "^0.8.1"
},
"devDependencies": {
"jsdoc-to-markdown": "^3.0.0",
"koa-cache-control": "^1.0.0",
"koa-livereload": "~0.2.0",
"req-then": "~0.5.1",
"tape": "^4.6.3"
}
"devDependencies": {}
}

20
ssl/127.0.0.1.crt

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDLjCCAhYCCQC3MW7xH6DDyTANBgkqhkiG9w0BAQUFADBZMQswCQYDVQQGEwJH
QjETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMRIwEAYDVQQDEwkxMjcuMC4wLjEwHhcNMTYwMzEwMTAzMTMwWhcN
MTcwMzEwMTAzMTMwWjBZMQswCQYDVQQGEwJHQjETMBEGA1UECBMKU29tZS1TdGF0
ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDEwkx
MjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIWz+H5P3P
5/Uixviwbj88y112TBCCdhPizqVb8f7EgTgeIA0Jpqe2+RR9siawwUAX9nqRUB1g
vgLZE4NZS+5ICN3JqkC4EysDS6VtIVf2OAuem3kdKaHSLl4JabsmBprgf2Dtze0i
eX5+Pur5Pi2BEAYNCUKzC4OuVaP//3jNWD/Xp6eHBbC76L03EIGPxytYf5wkITbY
wCjIVQw0Mq+WsV9eJRuLT4bnoeefCK+zPeTEQ6o+3SFkTkhqfsTF83sHvgcy1T4u
7f+GZ9TYiaUi/1OVvfUg2FdGDAlKtVVH/t+pAg0M2hGr7vTClSVOg/qiY3ktEaYW
FvcxJa65DyQNAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAFwrxsqXwA6BTFTvRYi1
s4tqos8loaZxE4eug96mL7qRvYzhDY+nDluiDEjMapACQOQaGIV+uMraOBk9yCUo
BsYqLcBLUTKBZvIMEmYmlUKxZrtFLVo1y6p7CJM9luwUEpbPRivA/Vofk9zlq9B1
AeVjDtqK/iZbO05qN18sgp7VPZZc4zRLOYUGfiUfX6r+dvDAPx/NBFM3vAEyYSur
Jqa2CdsiUXo08CytgIaxGgF1DJxLqoA4SZagSUWWcuOlDzLSooNlcW/zfEfQfeMQ
h7SbUtD4IJuKNd0BCeWMyVN7rM91zp9tf7713l+skbo5wIJAsNQAa2o8uRIXLjNX
jy4=
-----END CERTIFICATE-----

27
ssl/127.0.0.1.key

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAyFs/h+T9z+f1Isb4sG4/PMtddkwQgnYT4s6lW/H+xIE4HiAN
CaantvkUfbImsMFAF/Z6kVAdYL4C2RODWUvuSAjdyapAuBMrA0ulbSFX9jgLnpt5
HSmh0i5eCWm7Jgaa4H9g7c3tInl+fj7q+T4tgRAGDQlCswuDrlWj//94zVg/16en
hwWwu+i9NxCBj8crWH+cJCE22MAoyFUMNDKvlrFfXiUbi0+G56Hnnwivsz3kxEOq
Pt0hZE5Ian7ExfN7B74HMtU+Lu3/hmfU2ImlIv9Tlb31INhXRgwJSrVVR/7fqQIN
DNoRq+70wpUlToP6omN5LRGmFhb3MSWuuQ8kDQIDAQABAoIBAQDFiBkBvQVzxegM
ColDQN597K5PpDyesxV2BnBHTzXzvMZ8BPN1sWYm4jmOl2bH2y96sJo0y/y61Xrv
U+qqzk61nHA1k/JMyTEeBaWqCzay3JywGe51jwcotmgl9aT6n4ZwkYUZz23dEFVi
2FtHskKgvRCKJ7gn19FSvsJ68P/Dyl7H3/XGucj/7S+0JK3tb7BJ/ce68XABF99x
hvvkaWtxv0WNX2LWDyLVwv3T5i+pq4sscd9dmxwwCb1N3Lm3SkAOqH7BINia/qud
BLLJwHamzToWH7NTSWqrM4X9I7mI3zcMfOGeH9yZEFhB3cVu63V4yHfnGGqEiUOk
21fA+iLBAoGBAPXwZskl+nM0Z7yadaOOCqjRMdvPIgHOvQvjKtQJ/E7I4sH3ZBfO
4YPU0pErV4rbOyv6TZcUQwmcHmepK5wcHjj52+vgDQMr+K1wjRai8WdapKgXi39n
5IgPD0y5Hgi7qUJI6w67ybkawgknL8hm6TwtxfbKtVoJ5BVgS1UmFMYRAoGBANCN
e3X685aGqsyuCVU3bXnZVGyromiCDQge3NGuUFqaSCA0uK9/Q4HuStktH7LiRoZo
UwBmdnF0Wa4hMcjBBONv1bc8S43CdoJC3LR6DdFL8j4YarUSXnTFRo+MnKIbNwQh
378E1ws+dsOGrJ+IIqQJHfzsnG+vvb9PUleXgtI9AoGAFOBKKUri/oJ1R8oosDBv
cTMIs2rarSKaY3bt/L+4PgvJS8OvKGI0PFeFZDM0pCHF3Q7LJUbgBeHNpujyPbcZ
TabP5y7Gi/1gh4BlSYWdTjOghHAzNCZifLYii1WvWfhr/qdn5IFGN0MxM0uzP6SU
qboM8sz0JedvB+17l4e6/bECgYBYI0MHJGyns/ghEngtRISG13tfhdXYVwYM5YYr
M4EQGV3cBov610z/b2bAi9p2rjxh91sEs0jhP+vatHqmvjRDrnLiwp+npISTHpDJ
0T9fsboJ1iXaqo2yyeC9MA7OT7QbkflOcEw1m0tz7MmtjkodiyDaUGD4rowBexNw
oz6NfQKBgQCbhTO6MNmdeQrJn/ojR6HipypKqpVXqqqraAgU5BapaH0ZZwXkXDAM
36ldQviX8UnPNFqHj7jzVSNyWsgmKHnXFmdTEBYTd+0b+WEyn9FR/8kBlxHFR7Nc
AcAF7XF79pkJM31e6GCwFymYPbFJEL4TkWSOnPkypGY6IXHp57bKzA==
-----END RSA PRIVATE KEY-----
Loading…
Cancel
Save