Browse Source

mock names.. debug.. refactor.. options

master
Lloyd Brookes 9 years ago
parent
commit
aee47bc599
  1. 1
      example/mock-async/mocks/delayed.js
  2. 2
      example/mock/.local-web-server.json
  3. 1
      example/mock/mocks/five.js
  4. 1
      example/mock/mocks/stream-self.js
  5. 3
      example/mock/mocks/user.js
  6. 2
      example/spa/.local-web-server.json
  7. 2
      extend/cache-control.js
  8. 11
      lib/debug.js
  9. 120
      lib/local-web-server.js
  10. 51
      lib/middleware-stack.js
  11. 6
      lib/middleware.js
  12. 1
      package.json

1
example/mock-async/mocks/delayed.js

@ -1,4 +1,5 @@
module.exports = { module.exports = {
name: 'delayed response',
response: function (ctx) { response: function (ctx) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setTimeout(() => { setTimeout(() => {

2
example/mock/.local-web-server.json

@ -39,7 +39,7 @@
] ]
}, },
{ {
"route": "/four",
"route": "/stream",
"module": "/mocks/stream-self.js" "module": "/mocks/stream-self.js"
}, },
{ {

1
example/mock/mocks/five.js

@ -1,4 +1,5 @@
module.exports = { module.exports = {
name: '/five/:id?name=:name',
response: function (ctx, id) { response: function (ctx, id) {
ctx.body = `<h1>id: ${id}, name: ${ctx.query.name}</h1>` ctx.body = `<h1>id: ${id}, name: ${ctx.query.name}</h1>`
} }

1
example/mock/mocks/stream-self.js

@ -1,6 +1,7 @@
const fs = require('fs') const fs = require('fs')
module.exports = { module.exports = {
name: 'stream response',
response: { response: {
body: fs.createReadStream(__filename) body: fs.createReadStream(__filename)
} }

3
example/mock/mocks/user.js

@ -7,6 +7,7 @@ const mockResponses = [
/* for GET requests, return a particular user */ /* for GET requests, return a particular user */
{ {
name: 'GET user',
request: { method: 'GET' }, request: { method: 'GET' },
response: function (ctx, id) { response: function (ctx, id) {
ctx.body = users.find(user => user.id === Number(id)) ctx.body = users.find(user => user.id === Number(id))
@ -15,6 +16,7 @@ const mockResponses = [
/* for PUT requests, update the record */ /* for PUT requests, update the record */
{ {
name: 'PUT user',
request: { method: 'PUT' }, request: { method: 'PUT' },
response: function (ctx, id) { response: function (ctx, id) {
const updatedUser = ctx.request.body const updatedUser = ctx.request.body
@ -26,6 +28,7 @@ const mockResponses = [
/* DELETE request: remove the record */ /* DELETE request: remove the record */
{ {
name: 'DELETE user',
request: { method: 'DELETE' }, request: { method: 'DELETE' },
response: function (ctx, id) { response: function (ctx, id) {
const existingUserIndex = users.findIndex(user => user.id === Number(id)) const existingUserIndex = users.findIndex(user => user.id === Number(id))

2
example/spa/.local-web-server.json

@ -1,3 +1,3 @@
{ {
"spa": "index.html"
"spa": "index.html"
} }

2
extend/cache-control.js

@ -9,7 +9,7 @@ ws.addLogging('dev')
.add({ .add({
optionDefinitions: optionDefinitions, optionDefinitions: optionDefinitions,
middleware: function (options) { middleware: function (options) {
return cacheControl({ maxAge: options.middleware.maxage })
return cacheControl({ maxAge: options.maxage })
} }
}) })
.addStatic() .addStatic()

11
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
}

120
lib/local-web-server.js

@ -4,67 +4,82 @@ const ansi = require('ansi-escape-sequences')
const path = require('path') const path = require('path')
const arrayify = require('array-back') const arrayify = require('array-back')
const t = require('typical') const t = require('typical')
const Tool = require('command-line-tool')
const CommandLineTool = require('command-line-tool')
const MiddlewareStack = require('./middleware-stack') 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 () { start () {
const options = collectOptions(this.getOptionDefinitions()) const options = collectOptions(this.getOptionDefinitions())
this.options = options 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) tool.stop(JSON.stringify(options, null, ' '), 0)
} else { } 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) { function onServerUp (options, isHttps) {
const ipList = getIPList(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(', ') .join(', ')
console.error(ansi.format( console.error(ansi.format(
path.resolve(options.middleware.directory) === process.cwd()
path.resolve(options.directory) === process.cwd()
? `serving at ${ipList}` ? `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 */ /* parse command line args */
const definitions = cli.optionDefinitions.concat(arrayify(mwOptionDefinitions)) 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) validateOptions(options)
// console.log(options)
return options return options
} }
@ -109,6 +126,7 @@ function parseRewriteRules (rules) {
return rules && rules.map(rule => { return rules && rules.map(rule => {
if (t.isString(rule)) { if (t.isString(rule)) {
const matches = rule.match(/(\S*)\s*->\s*(\S*)/) const matches = rule.match(/(\S*)\s*->\s*(\S*)/)
if (!(matches && matches.length >= 3)) throw new Error('Invalid rule: ' + rule)
return { return {
from: matches[1], from: matches[1],
to: matches[2] to: matches[2]
@ -120,11 +138,15 @@ function parseRewriteRules (rules) {
} }
function validateOptions (options) { function validateOptions (options) {
if (!t.isNumber(options.server.port)) {
if (!t.isNumber(options.port)) {
tool.printError('--port must be numeric') tool.printError('--port must be numeric')
console.error(tool.usage) console.error(tool.usage)
tool.halt() tool.halt()
} }
} }
module.exports = Cli
module.exports = LocalWebServer
process.on('unhandledRejection', (reason, p) => {
console.error('unhandledRejection', reason, p)
})

51
lib/middleware-stack.js

@ -2,10 +2,11 @@
const arrayify = require('array-back') const arrayify = require('array-back')
const path = require('path') const path = require('path')
const url = require('url') const url = require('url')
const debug = require('debug')('local-web-server')
const debug = require('./debug')
const mw = require('./middleware') const mw = require('./middleware')
const t = require('typical') const t = require('typical')
const compose = require('koa-compose') const compose = require('koa-compose')
const flatten = require('reduce-flatten')
class MiddlewareStack extends Array { class MiddlewareStack extends Array {
add (middleware) { 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'." 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) { middleware: function (cliOptions) {
const options = arrayify(cliOptions.middleware.rewrite || rewriteRules)
const options = arrayify(cliOptions.rewrite || rewriteRules)
if (options.length) { if (options.length) {
options.forEach(route => {
return options.map(route => {
if (route.to) { if (route.to) {
/* `to` address is remote if the url specifies a host */ /* `to` address is remote if the url specifies a host */
if (url.parse(route.to).host) { if (url.parse(route.to).host) {
@ -75,7 +76,7 @@ class MiddlewareStack extends Array {
description: 'A list of forbidden routes.' description: 'A list of forbidden routes.'
}, },
middleware: function (cliOptions) { middleware: function (cliOptions) {
forbidList = arrayify(cliOptions.middleware.forbid || forbidList)
forbidList = arrayify(cliOptions.forbid || forbidList)
if (forbidList.length) { if (forbidList.length) {
const pathToRegexp = require('path-to-regexp') const pathToRegexp = require('path-to-regexp')
debug('forbid', forbidList.join(', ')) debug('forbid', forbidList.join(', '))
@ -101,7 +102,7 @@ class MiddlewareStack extends Array {
description: 'Disable etag-based caching - forces loading from disk each request.' description: 'Disable etag-based caching - forces loading from disk each request.'
}, },
middleware: function (cliOptions) { middleware: function (cliOptions) {
const noCache = cliOptions.middleware['no-cache']
const noCache = cliOptions['no-cache']
if (!noCache) { if (!noCache) {
return [ return [
require('koa-conditional-get')(), require('koa-conditional-get')(),
@ -117,7 +118,7 @@ class MiddlewareStack extends Array {
addMimeType (mime) { addMimeType (mime) {
this.push({ this.push({
middleware: function (cliOptions) { middleware: function (cliOptions) {
mime = cliOptions.middleware.mime || mime
mime = cliOptions.mime || mime
if (mime) { if (mime) {
debug('mime override', JSON.stringify(mime)) debug('mime override', JSON.stringify(mime))
return mw.mime(mime) return mw.mime(mime)
@ -135,8 +136,8 @@ class MiddlewareStack extends Array {
description: 'Serve gzip-compressed resources, where applicable.' description: 'Serve gzip-compressed resources, where applicable.'
}, },
middleware: function (cliOptions) { middleware: function (cliOptions) {
compress = t.isDefined(cliOptions.middleware.compress)
? cliOptions.middleware.compress
compress = t.isDefined(cliOptions.compress)
? cliOptions.compress
: compress : compress
if (compress) { if (compress) {
debug('compression', 'enabled') 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')." 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) { 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' format = 'none'
} }
@ -190,11 +191,11 @@ class MiddlewareStack extends Array {
addMockResponses (mocks) { addMockResponses (mocks) {
this.push({ this.push({
middleware: function (cliOptions) { middleware: function (cliOptions) {
mocks = arrayify(cliOptions.middleware.mocks || mocks)
mocks.forEach(mock => {
mocks = arrayify(cliOptions.mocks || mocks)
return mocks.map(mock => {
if (mock.module) { 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) { if (mock.responses) {
@ -220,13 +221,13 @@ class MiddlewareStack extends Array {
description: 'Path to a Single Page App, e.g. app.html.' description: 'Path to a Single Page App, e.g. app.html.'
}, },
middleware: function (cliOptions) { middleware: function (cliOptions) {
spa = t.isDefined(cliOptions.middleware.spa) ? cliOptions.middleware.spa : spa
spa = t.isDefined(cliOptions.spa) ? cliOptions.spa : spa
if (spa) { if (spa) {
const historyApiFallback = require('koa-connect-history-api-fallback') const historyApiFallback = require('koa-connect-history-api-fallback')
debug('SPA', spa) debug('SPA', spa)
return historyApiFallback({ return historyApiFallback({
index: spa, 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.' description: 'Root directory, defaults to the current directory.'
}, },
middleware: function (cliOptions) { middleware: function (cliOptions) {
root = cliOptions.middleware.directory || root || process.cwd()
cliOptions.directory = cliOptions.directory || root || process.cwd()
options = Object.assign({ hidden: true }, options) options = Object.assign({ hidden: true }, options)
// console.log(root, options, cliOptions) // console.log(root, options, cliOptions)
if (root) {
if (cliOptions.directory) {
const serve = require('koa-static') 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) { addIndex (path, options) {
this.push({ this.push({
middleware: function (cliOptions) { middleware: function (cliOptions) {
path = cliOptions.middleware.directory || path || process.cwd()
path = cliOptions.directory || path || process.cwd()
options = Object.assign({ icons: true, hidden: true }, options) options = Object.assign({ icons: true, hidden: true }, options)
if (path) { if (path) {
const serveIndex = require('koa-serve-index') const serveIndex = require('koa-serve-index')
@ -270,7 +271,6 @@ class MiddlewareStack extends Array {
} }
getOptionDefinitions () { getOptionDefinitions () {
const flatten = require('reduce-flatten')
return this return this
.filter(mw => mw.optionDefinitions) .filter(mw => mw.optionDefinitions)
.map(mw => mw.optionDefinitions) .map(mw => mw.optionDefinitions)
@ -283,8 +283,13 @@ class MiddlewareStack extends Array {
compose (options) { compose (options) {
const convert = require('koa-convert') const convert = require('koa-convert')
const middlewareStack = this 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) return compose(middlewareStack)
} }
} }

6
lib/middleware.js

@ -4,7 +4,7 @@ const url = require('url')
const arrayify = require('array-back') const arrayify = require('array-back')
const t = require('typical') const t = require('typical')
const pathToRegexp = require('path-to-regexp') const pathToRegexp = require('path-to-regexp')
const debug = require('debug')('local-web-server')
const debug = require('./debug')
/** /**
* @module middleware * @module middleware
@ -61,8 +61,8 @@ function mime (mimeTypes) {
function mockResponses (route, targets) { function mockResponses (route, targets) {
targets = arrayify(targets) targets = arrayify(targets)
debug('mock route: %s, targets: %s', route, targets.length)
const pathRe = pathToRegexp(route) const pathRe = pathToRegexp(route)
debug('mock route: %s, targets: %s', route, targets.length)
return function mockResponse (ctx, next) { return function mockResponse (ctx, next) {
if (pathRe.test(ctx.path)) { if (pathRe.test(ctx.path)) {
@ -83,6 +83,8 @@ function mockResponses (route, targets) {
target = targets.find(target => !target.request) target = targets.find(target => !target.request)
} }
debug(`mock path: ${ctx.path} target: ${target.name || "unnamed"}`)
if (target) { if (target) {
if (t.isFunction(target.response)) { if (t.isFunction(target.response)) {
const pathMatches = ctx.path.match(pathRe).slice(1) const pathMatches = ctx.path.match(pathRe).slice(1)

1
package.json

@ -33,7 +33,6 @@
"array-back": "^1.0.3", "array-back": "^1.0.3",
"command-line-tool": "75lb/command-line-tool", "command-line-tool": "75lb/command-line-tool",
"config-master": "^2.0.2", "config-master": "^2.0.2",
"debug": "^2.2.0",
"http-proxy": "^1.13.3", "http-proxy": "^1.13.3",
"kcors": "^1.2.1", "kcors": "^1.2.1",
"koa": "^2.0.0", "koa": "^2.0.0",

Loading…
Cancel
Save