diff --git a/example/mock-async/mocks/delayed.js b/example/mock-async/mocks/delayed.js
index 0b08823..ec6d325 100644
--- a/example/mock-async/mocks/delayed.js
+++ b/example/mock-async/mocks/delayed.js
@@ -1,4 +1,5 @@
module.exports = {
+ name: 'delayed response',
response: function (ctx) {
return new Promise((resolve, reject) => {
setTimeout(() => {
diff --git a/example/mock/.local-web-server.json b/example/mock/.local-web-server.json
index 736460e..51c31d8 100644
--- a/example/mock/.local-web-server.json
+++ b/example/mock/.local-web-server.json
@@ -39,7 +39,7 @@
]
},
{
- "route": "/four",
+ "route": "/stream",
"module": "/mocks/stream-self.js"
},
{
diff --git a/example/mock/mocks/five.js b/example/mock/mocks/five.js
index 091186b..5165d64 100644
--- a/example/mock/mocks/five.js
+++ b/example/mock/mocks/five.js
@@ -1,4 +1,5 @@
module.exports = {
+ name: '/five/:id?name=:name',
response: function (ctx, id) {
ctx.body = `
id: ${id}, name: ${ctx.query.name}
`
}
diff --git a/example/mock/mocks/stream-self.js b/example/mock/mocks/stream-self.js
index 981fe26..b80018d 100644
--- a/example/mock/mocks/stream-self.js
+++ b/example/mock/mocks/stream-self.js
@@ -1,6 +1,7 @@
const fs = require('fs')
module.exports = {
+ name: 'stream response',
response: {
body: fs.createReadStream(__filename)
}
diff --git a/example/mock/mocks/user.js b/example/mock/mocks/user.js
index 1998936..36f4d4a 100644
--- a/example/mock/mocks/user.js
+++ b/example/mock/mocks/user.js
@@ -7,6 +7,7 @@ const mockResponses = [
/* for GET requests, return a particular user */
{
+ name: 'GET user',
request: { method: 'GET' },
response: function (ctx, id) {
ctx.body = users.find(user => user.id === Number(id))
@@ -15,6 +16,7 @@ const mockResponses = [
/* for PUT requests, update the record */
{
+ name: 'PUT user',
request: { method: 'PUT' },
response: function (ctx, id) {
const updatedUser = ctx.request.body
@@ -26,6 +28,7 @@ const mockResponses = [
/* DELETE request: remove the record */
{
+ name: 'DELETE user',
request: { method: 'DELETE' },
response: function (ctx, id) {
const existingUserIndex = users.findIndex(user => user.id === Number(id))
diff --git a/example/spa/.local-web-server.json b/example/spa/.local-web-server.json
index 2c63606..d5db15c 100644
--- a/example/spa/.local-web-server.json
+++ b/example/spa/.local-web-server.json
@@ -1,3 +1,3 @@
{
- "spa": "index.html"
+ "spa": "index.html"
}
diff --git a/extend/cache-control.js b/extend/cache-control.js
index bd33057..d7fb67e 100644
--- a/extend/cache-control.js
+++ b/extend/cache-control.js
@@ -9,7 +9,7 @@ ws.addLogging('dev')
.add({
optionDefinitions: optionDefinitions,
middleware: function (options) {
- return cacheControl({ maxAge: options.middleware.maxage })
+ return cacheControl({ maxAge: options.maxage })
}
})
.addStatic()
diff --git a/lib/debug.js b/lib/debug.js
new file mode 100644
index 0000000..55eca0f
--- /dev/null
+++ b/lib/debug.js
@@ -0,0 +1,11 @@
+module.exports = debug
+
+let level = 0
+
+function debug () {
+ if (level) console.error.apply(console.error, arguments)
+}
+
+debug.setLevel = function () {
+ level = 1
+}
diff --git a/lib/local-web-server.js b/lib/local-web-server.js
index e54afac..297b98d 100644
--- a/lib/local-web-server.js
+++ b/lib/local-web-server.js
@@ -4,67 +4,82 @@ const ansi = require('ansi-escape-sequences')
const path = require('path')
const arrayify = require('array-back')
const t = require('typical')
-const Tool = require('command-line-tool')
+const CommandLineTool = require('command-line-tool')
const MiddlewareStack = require('./middleware-stack')
+const debug = require('./debug')
-const tool = new Tool()
+const tool = new CommandLineTool()
+
+class LocalWebServer extends MiddlewareStack {
+ getApplication () {
+ const Koa = require('koa')
+ const app = new Koa()
+ app.use(this.compose(this.options))
+ return app
+ }
+
+ getServer () {
+ const options = this.options
+ let key = options.key
+ let cert = options.cert
+
+ const app = this.getApplication()
+ app.on('error', err => {
+ if (options['log-format']) {
+ console.error(ansi.format(err.message, 'red'))
+ }
+ })
+
+ if (options.https) {
+ 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
+ }
-class Cli extends MiddlewareStack {
start () {
const options = collectOptions(this.getOptionDefinitions())
this.options = options
- if (options.misc.verbose) {
- process.env.DEBUG = '*'
+ if (options.verbose) {
+ debug.setLevel(1)
}
- if (options.misc.config) {
+ if (options.config) {
tool.stop(JSON.stringify(options, null, ' '), 0)
} else {
- const Koa = require('koa')
- const app = new Koa()
- app.on('error', err => {
- if (options.middleware['log-format']) {
- console.error(ansi.format(err.message, 'red'))
- }
- })
-
- app.use(this.compose(options))
-
- let key = options.server.key
- let cert = options.server.cert
- if (options.server.https) {
- key = path.resolve(__dirname, '..', 'ssl', '127.0.0.1.key')
- cert = path.resolve(__dirname, '..', 'ssl', '127.0.0.1.crt')
- }
-
- if (key && cert) {
- const https = require('https')
- const fs = require('fs')
-
- const serverOptions = {
- key: fs.readFileSync(key),
- cert: fs.readFileSync(cert)
- }
-
- const server = https.createServer(serverOptions, app.callback())
- server.listen(options.server.port, onServerUp.bind(null, options, true))
- } else {
- app.listen(options.server.port, onServerUp.bind(null, options))
- }
+ const server = this.getServer()
+ server.listen(options.port, onServerUp.bind(null, options, server.isHttps))
+ return server
}
}
}
function onServerUp (options, isHttps) {
const ipList = getIPList(isHttps)
- .map(iface => `[underline]{${isHttps ? 'https' : 'http'}://${iface.address}:${options.server.port}}`)
+ .map(iface => `[underline]{${isHttps ? 'https' : 'http'}://${iface.address}:${options.port}}`)
.join(', ')
console.error(ansi.format(
- path.resolve(options.middleware.directory) === process.cwd()
+ path.resolve(options.directory) === process.cwd()
? `serving at ${ipList}`
- : `serving [underline]{${options.middleware.directory}} at ${ipList}`
+ : `serving [underline]{${options.directory}} at ${ipList}`
))
}
@@ -90,18 +105,20 @@ function collectOptions (mwOptionDefinitions) {
/* parse command line args */
const definitions = cli.optionDefinitions.concat(arrayify(mwOptionDefinitions))
- let options = tool.getOptions(definitions, cli.usage(definitions))
+ let cliOptions = tool.getOptions(definitions, cli.usage(definitions))
- /* override built-in defaults with stored config and then command line args */
- options.server = Object.assign({ port: 8000 }, stored.server, options.server)
- options.middleware = Object.assign({ directory: process.cwd() }, stored.middleware || {}, options.middleware)
+ /* override built-in defaults with stored config and then command line options */
+ const options = Object.assign({
+ port: 8000,
+ directory: process.cwd()
+ }, stored, cliOptions.server, cliOptions.middleware, cliOptions.misc)
- if (options.middleware.rewrite) {
- options.middleware.rewrite = parseRewriteRules(options.middleware.rewrite)
+ if (options.rewrite) {
+ options.rewrite = parseRewriteRules(options.rewrite)
}
+ // console.error(require('util').inspect(options, { depth: 3, colors: true }))
validateOptions(options)
- // console.log(options)
return options
}
@@ -109,6 +126,7 @@ function parseRewriteRules (rules) {
return rules && rules.map(rule => {
if (t.isString(rule)) {
const matches = rule.match(/(\S*)\s*->\s*(\S*)/)
+ if (!(matches && matches.length >= 3)) throw new Error('Invalid rule: ' + rule)
return {
from: matches[1],
to: matches[2]
@@ -120,11 +138,15 @@ function parseRewriteRules (rules) {
}
function validateOptions (options) {
- if (!t.isNumber(options.server.port)) {
+ if (!t.isNumber(options.port)) {
tool.printError('--port must be numeric')
console.error(tool.usage)
tool.halt()
}
}
-module.exports = Cli
+module.exports = LocalWebServer
+
+process.on('unhandledRejection', (reason, p) => {
+ console.error('unhandledRejection', reason, p)
+})
diff --git a/lib/middleware-stack.js b/lib/middleware-stack.js
index a191d1e..47979c4 100644
--- a/lib/middleware-stack.js
+++ b/lib/middleware-stack.js
@@ -2,10 +2,11 @@
const arrayify = require('array-back')
const path = require('path')
const url = require('url')
-const debug = require('debug')('local-web-server')
+const debug = require('./debug')
const mw = require('./middleware')
const t = require('typical')
const compose = require('koa-compose')
+const flatten = require('reduce-flatten')
class MiddlewareStack extends Array {
add (middleware) {
@@ -36,9 +37,9 @@ class MiddlewareStack extends Array {
description: "A list of URL rewrite rules. For each rule, separate the 'from' and 'to' routes with '->'. Whitespace surrounded the routes is ignored. E.g. '/from -> /to'."
},
middleware: function (cliOptions) {
- const options = arrayify(cliOptions.middleware.rewrite || rewriteRules)
+ const options = arrayify(cliOptions.rewrite || rewriteRules)
if (options.length) {
- options.forEach(route => {
+ return options.map(route => {
if (route.to) {
/* `to` address is remote if the url specifies a host */
if (url.parse(route.to).host) {
@@ -75,7 +76,7 @@ class MiddlewareStack extends Array {
description: 'A list of forbidden routes.'
},
middleware: function (cliOptions) {
- forbidList = arrayify(cliOptions.middleware.forbid || forbidList)
+ forbidList = arrayify(cliOptions.forbid || forbidList)
if (forbidList.length) {
const pathToRegexp = require('path-to-regexp')
debug('forbid', forbidList.join(', '))
@@ -101,7 +102,7 @@ class MiddlewareStack extends Array {
description: 'Disable etag-based caching - forces loading from disk each request.'
},
middleware: function (cliOptions) {
- const noCache = cliOptions.middleware['no-cache']
+ const noCache = cliOptions['no-cache']
if (!noCache) {
return [
require('koa-conditional-get')(),
@@ -117,7 +118,7 @@ class MiddlewareStack extends Array {
addMimeType (mime) {
this.push({
middleware: function (cliOptions) {
- mime = cliOptions.middleware.mime || mime
+ mime = cliOptions.mime || mime
if (mime) {
debug('mime override', JSON.stringify(mime))
return mw.mime(mime)
@@ -135,8 +136,8 @@ class MiddlewareStack extends Array {
description: 'Serve gzip-compressed resources, where applicable.'
},
middleware: function (cliOptions) {
- compress = t.isDefined(cliOptions.middleware.compress)
- ? cliOptions.middleware.compress
+ compress = t.isDefined(cliOptions.compress)
+ ? cliOptions.compress
: compress
if (compress) {
debug('compression', 'enabled')
@@ -158,9 +159,9 @@ class MiddlewareStack extends Array {
description: "If a format is supplied an access log is written to stdout. If not, a dynamic statistics view is displayed. Use a preset ('none', 'dev','combined', 'short', 'tiny' or 'logstalgia') or supply a custom format (e.g. ':method -> :url')."
},
middleware: function (cliOptions) {
- format = cliOptions.middleware['log-format'] || format
+ format = cliOptions['log-format'] || format
- if (cliOptions.misc.verbose && !format) {
+ if (cliOptions.verbose && !format) {
format = 'none'
}
@@ -190,11 +191,11 @@ class MiddlewareStack extends Array {
addMockResponses (mocks) {
this.push({
middleware: function (cliOptions) {
- mocks = arrayify(cliOptions.middleware.mocks || mocks)
- mocks.forEach(mock => {
+ mocks = arrayify(cliOptions.mocks || mocks)
+ return mocks.map(mock => {
if (mock.module) {
- // TODO: ENSURE cliOptions.static.root is correct value
- mock.responses = require(path.resolve(path.join(cliOptions.static.root, mock.module)))
+ const modulePath = path.resolve(path.join(cliOptions.directory, mock.module))
+ mock.responses = require(modulePath)
}
if (mock.responses) {
@@ -220,13 +221,13 @@ class MiddlewareStack extends Array {
description: 'Path to a Single Page App, e.g. app.html.'
},
middleware: function (cliOptions) {
- spa = t.isDefined(cliOptions.middleware.spa) ? cliOptions.middleware.spa : spa
+ spa = t.isDefined(cliOptions.spa) ? cliOptions.spa : spa
if (spa) {
const historyApiFallback = require('koa-connect-history-api-fallback')
debug('SPA', spa)
return historyApiFallback({
index: spa,
- verbose: cliOptions.misc.verbose
+ verbose: cliOptions.verbose
})
}
}
@@ -242,12 +243,12 @@ class MiddlewareStack extends Array {
description: 'Root directory, defaults to the current directory.'
},
middleware: function (cliOptions) {
- root = cliOptions.middleware.directory || root || process.cwd()
+ cliOptions.directory = cliOptions.directory || root || process.cwd()
options = Object.assign({ hidden: true }, options)
// console.log(root, options, cliOptions)
- if (root) {
+ if (cliOptions.directory) {
const serve = require('koa-static')
- return serve(root, options)
+ return serve(cliOptions.directory, options)
}
}
})
@@ -258,7 +259,7 @@ class MiddlewareStack extends Array {
addIndex (path, options) {
this.push({
middleware: function (cliOptions) {
- path = cliOptions.middleware.directory || path || process.cwd()
+ path = cliOptions.directory || path || process.cwd()
options = Object.assign({ icons: true, hidden: true }, options)
if (path) {
const serveIndex = require('koa-serve-index')
@@ -270,7 +271,6 @@ class MiddlewareStack extends Array {
}
getOptionDefinitions () {
- const flatten = require('reduce-flatten')
return this
.filter(mw => mw.optionDefinitions)
.map(mw => mw.optionDefinitions)
@@ -283,8 +283,13 @@ class MiddlewareStack extends Array {
compose (options) {
const convert = require('koa-convert')
const middlewareStack = this
- .filter(mw => mw)
- .map(mw => compose(arrayify(mw.middleware(options)).map(convert)))
+ .filter(mw => mw.middleware)
+ .map(mw => mw.middleware)
+ .map(middleware => middleware(options))
+ .filter(middleware => middleware)
+ .reduce(flatten, [])
+ .map(convert)
+ // console.error(require('util').inspect(middlewareStack, { depth: 3, colors: true }))
return compose(middlewareStack)
}
}
diff --git a/lib/middleware.js b/lib/middleware.js
index 620780e..2ee450d 100644
--- a/lib/middleware.js
+++ b/lib/middleware.js
@@ -4,7 +4,7 @@ const url = require('url')
const arrayify = require('array-back')
const t = require('typical')
const pathToRegexp = require('path-to-regexp')
-const debug = require('debug')('local-web-server')
+const debug = require('./debug')
/**
* @module middleware
@@ -61,8 +61,8 @@ function mime (mimeTypes) {
function mockResponses (route, targets) {
targets = arrayify(targets)
- debug('mock route: %s, targets: %s', route, targets.length)
const pathRe = pathToRegexp(route)
+ debug('mock route: %s, targets: %s', route, targets.length)
return function mockResponse (ctx, next) {
if (pathRe.test(ctx.path)) {
@@ -83,6 +83,8 @@ function mockResponses (route, targets) {
target = targets.find(target => !target.request)
}
+ debug(`mock path: ${ctx.path} target: ${target.name || "unnamed"}`)
+
if (target) {
if (t.isFunction(target.response)) {
const pathMatches = ctx.path.match(pathRe).slice(1)
diff --git a/package.json b/package.json
index c0c7fe8..a88acf4 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,6 @@
"array-back": "^1.0.3",
"command-line-tool": "75lb/command-line-tool",
"config-master": "^2.0.2",
- "debug": "^2.2.0",
"http-proxy": "^1.13.3",
"kcors": "^1.2.1",
"koa": "^2.0.0",