You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
6.6 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  1. #!/usr/bin/env node
  2. 'use strict'
  3. const path = require('path')
  4. const CommandLineTool = require('command-line-tool')
  5. const flatten = require('reduce-flatten')
  6. const arrayify = require('array-back')
  7. /**
  8. * @module local-web-server
  9. */
  10. const tool = new CommandLineTool()
  11. /**
  12. * @alias module:local-web-server
  13. * @extends module:middleware-stack
  14. */
  15. class LocalWebServer {
  16. constructor (initOptions) {
  17. initOptions = initOptions || {}
  18. const commandLineArgs = require('command-line-args')
  19. const commandLineUsage = require('command-line-usage')
  20. const cli = require('../lib/cli-data')
  21. const loadConfig = require('config-master')
  22. const stored = loadConfig('local-web-server')
  23. /* manually scan for any --stack passed, as we may need to display stack options */
  24. const stackPaths = arrayify(initOptions.stack || stored.stack) || []
  25. const stackIndex = process.argv.indexOf('--stack')
  26. if (stackIndex > -1) {
  27. for (var i = stackIndex + 1; i < process.argv.length; i++) {
  28. const stackPath = process.argv[i]
  29. if (/^-/.test(stackPath)) {
  30. break
  31. } else {
  32. stackPaths.push(stackPath)
  33. }
  34. }
  35. }
  36. /* if the user did not supply a stack, use the default */
  37. if (!stackPaths.length) stackPaths.push(path.resolve(__dirname, '..', 'node_modules', 'local-web-server-default-stack'))
  38. /* load the stack */
  39. const stackModules = stackPaths
  40. .map(stackPath => loadStack(stackPath))
  41. .map(Middleware => new Middleware())
  42. .map(module => {
  43. if (module.stack) {
  44. const featureStack = module.stack()
  45. module.optionDefinitions = function () {
  46. return featureStack
  47. .map(Feature => new Feature())
  48. .map(feature => feature.optionDefinitions && feature.optionDefinitions())
  49. .filter(definitions => definitions)
  50. .reduce(flatten, [])
  51. }
  52. module.middleware = function (options) {
  53. return featureStack
  54. .map(Feature => new Feature())
  55. .map(feature => feature.middleware(options))
  56. .reduce(flatten, [])
  57. .filter(mw => mw)
  58. }
  59. }
  60. return module
  61. })
  62. /* gather stack option definitions and parse the command line */
  63. const middlewareOptionDefinitions = stackModules
  64. .filter(mw => mw.optionDefinitions)
  65. .map(mw => mw.optionDefinitions())
  66. .reduce(flatten, [])
  67. .filter(def => def)
  68. .map(def => {
  69. def.group = 'middleware'
  70. return def
  71. })
  72. const usage = commandLineUsage(cli.usage(middlewareOptionDefinitions))
  73. let options = {}
  74. const allOptionDefinitions = cli.optionDefinitions.concat(middlewareOptionDefinitions)
  75. if (!initOptions.ignoreCli) {
  76. try {
  77. options = commandLineArgs(allOptionDefinitions)
  78. } catch (err) {
  79. tool.printError(err)
  80. tool.printError(allOptionDefinitions.map(def => {
  81. return `name: ${def.name}${def.alias ? ', alias: ' + def.alias : ''}`
  82. }).join('\n'))
  83. console.error(usage)
  84. tool.halt()
  85. }
  86. }
  87. /* combine in stored config */
  88. options = Object.assign(
  89. { port: 8000 },
  90. initOptions,
  91. stored,
  92. options.server,
  93. options.middleware,
  94. options.misc
  95. )
  96. /**
  97. * Config
  98. * @type {object}
  99. */
  100. this.options = options
  101. if (options.verbose) {
  102. // debug.setLevel(1)
  103. }
  104. /* --config */
  105. if (options.config) {
  106. tool.stop(JSON.stringify(options, null, ' '), 0)
  107. /* --version */
  108. } else if (options.version) {
  109. const pkg = require(path.resolve(__dirname, '..', 'package.json'))
  110. tool.stop(pkg.version)
  111. /* --help */
  112. } else if (options.help) {
  113. tool.stop(usage)
  114. } else {
  115. this.stack = stackModules
  116. }
  117. }
  118. getApplication () {
  119. const Koa = require('koa')
  120. const app = new Koa()
  121. const compose = require('koa-compose')
  122. const convert = require('koa-convert')
  123. const middlewareStack = this.stack
  124. .filter(mw => mw.middleware)
  125. .map(mw => mw.middleware(this.options))
  126. .reduce(flatten, [])
  127. .filter(mw => mw)
  128. .map(convert)
  129. app.use(compose(middlewareStack))
  130. app.on('error', err => {
  131. const defaultLogInUse = this.stack.some(mw => mw.constructor.name === 'Log')
  132. if (defaultLogInUse) {
  133. if (this.options['log.format']) console.error(ansi.format(err.stack, 'red'))
  134. } else {
  135. console.error(ansi.format(err.stack, 'red'))
  136. }
  137. })
  138. return app
  139. }
  140. getServer (onListening) {
  141. const app = this.getApplication()
  142. const options = this.options
  143. let key = options.key
  144. let cert = options.cert
  145. if (options.https && !(key && cert)) {
  146. key = path.resolve(__dirname, '..', 'ssl', '127.0.0.1.key')
  147. cert = path.resolve(__dirname, '..', 'ssl', '127.0.0.1.crt')
  148. }
  149. let server = null
  150. if (key && cert) {
  151. const fs = require('fs')
  152. const serverOptions = {
  153. key: fs.readFileSync(key),
  154. cert: fs.readFileSync(cert)
  155. }
  156. const https = require('https')
  157. server = https.createServer(serverOptions, app.callback())
  158. server.isHttps = true
  159. } else {
  160. const http = require('http')
  161. server = http.createServer(app.callback())
  162. }
  163. server.listen(options.port, onListening)
  164. return server
  165. }
  166. }
  167. /**
  168. * Loads a module by either path or name.
  169. * @returns {object}
  170. */
  171. function loadStack (modulePath) {
  172. let module
  173. if (isModule(modulePath)) return modulePath
  174. const tried = []
  175. if (modulePath) {
  176. try {
  177. tried.push(path.resolve(modulePath))
  178. module = require(path.resolve(modulePath))
  179. } catch (err) {
  180. const walkBack = require('walk-back')
  181. const foundPath = walkBack(process.cwd(), path.join('node_modules', 'local-web-server-' + modulePath))
  182. tried.push('local-web-server-' + modulePath)
  183. if (foundPath) {
  184. module = require(foundPath)
  185. } else {
  186. const foundPath2 = walkBack(process.cwd(), path.join('node_modules', modulePath))
  187. tried.push(modulePath)
  188. if (foundPath2) {
  189. module = require(foundPath2)
  190. }
  191. }
  192. }
  193. }
  194. if (module) {
  195. if (!isModule(module)) {
  196. const insp = require('util').inspect(module, { depth: 3, colors: true })
  197. const msg = `Not valid Middleware at: ${insp}`
  198. tool.halt(new Error(msg))
  199. }
  200. } else {
  201. const msg = `No module found at: \n${tried.join('\n')}`
  202. tool.halt(new Error(msg))
  203. }
  204. return module
  205. }
  206. function isModule (module) {
  207. return module.prototype && (module.prototype.middleware || module.prototype.stack)
  208. }
  209. module.exports = LocalWebServer