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.

281 lines
7.7 KiB

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