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.

251 lines
7.2 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
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
8 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 ansi = require('ansi-escape-sequences')
  4. const path = require('path')
  5. const CommandLineTool = require('command-line-tool')
  6. const flatten = require('reduce-flatten')
  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. /* manually scan for any --stack passed, as we may need to display stack options */
  22. const stackPaths = initOptions.stack || []
  23. const stackIndex = process.argv.indexOf('--stack')
  24. if (stackIndex > -1) {
  25. for (var i = stackIndex + 1; i < process.argv.length; i++) {
  26. const stackPath = process.argv[i]
  27. if (/^-/.test(stackPath)) {
  28. break
  29. } else {
  30. stackPaths.push(stackPath)
  31. }
  32. }
  33. }
  34. /* load the stack */
  35. if (!stackPaths.length) stackPaths.push(path.resolve(__dirname, '..', 'node_modules', 'local-web-server-default-stack'))
  36. const stackModules = stackPaths
  37. .map(stackPath => loadStack(stackPath))
  38. .map(Middleware => new Middleware())
  39. .map(module => {
  40. if (module.stack) {
  41. const featureStack = module.stack()
  42. module.optionDefinitions = function () {
  43. return featureStack
  44. .map(Feature => new Feature())
  45. .map(feature => feature.optionDefinitions && feature.optionDefinitions())
  46. .filter(definitions => definitions)
  47. .reduce(flatten, [])
  48. }
  49. module.middleware = function (options) {
  50. return featureStack
  51. .map(Feature => new Feature())
  52. .map(feature => feature.middleware(options))
  53. .reduce(flatten, [])
  54. .filter(mw => mw)
  55. }
  56. }
  57. return module
  58. })
  59. /* gather stack option definitions and parse the command line */
  60. const middlewareOptionDefinitions = stackModules
  61. .filter(mw => mw.optionDefinitions)
  62. .map(mw => mw.optionDefinitions())
  63. .reduce(flatten, [])
  64. .filter(def => def)
  65. .map(def => {
  66. def.group = 'middleware'
  67. return def
  68. })
  69. const usage = commandLineUsage(cli.usage(middlewareOptionDefinitions))
  70. let options = {}
  71. const allOptionDefinitions = cli.optionDefinitions.concat(middlewareOptionDefinitions)
  72. try {
  73. options = commandLineArgs(allOptionDefinitions)
  74. } catch (err) {
  75. tool.printError(err)
  76. tool.printError(allOptionDefinitions.map(def => {
  77. return `name: ${def.name}${def.alias ? ', alias: ' + def.alias : ''}`
  78. }).join('\n'))
  79. console.error(usage)
  80. tool.halt()
  81. }
  82. /* combine in stored config */
  83. const loadConfig = require('config-master')
  84. const stored = loadConfig('local-web-server')
  85. options = Object.assign({ port: 8000 }, initOptions, stored, options.server, options.middleware, options.misc)
  86. this.options = options
  87. if (options.verbose) {
  88. // debug.setLevel(1)
  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. const defaultLogInUse = this.stack.some(mw => mw.constructor.name === 'Log')
  118. if (defaultLogInUse) {
  119. if (this.options['log.format']) console.error(ansi.format(err.stack, 'red'))
  120. } else {
  121. console.error(ansi.format(err.stack, 'red'))
  122. }
  123. })
  124. return app
  125. }
  126. getServer () {
  127. const app = this.getApplication()
  128. const options = this.options
  129. let key = options.key
  130. let cert = options.cert
  131. if (options.https && !(key && cert)) {
  132. key = path.resolve(__dirname, '..', 'ssl', '127.0.0.1.key')
  133. cert = path.resolve(__dirname, '..', 'ssl', '127.0.0.1.crt')
  134. }
  135. let server = null
  136. if (key && cert) {
  137. const fs = require('fs')
  138. const serverOptions = {
  139. key: fs.readFileSync(key),
  140. cert: fs.readFileSync(cert)
  141. }
  142. const https = require('https')
  143. server = https.createServer(serverOptions, app.callback())
  144. server.isHttps = true
  145. } else {
  146. const http = require('http')
  147. server = http.createServer(app.callback())
  148. }
  149. return server
  150. }
  151. listen () {
  152. const options = this.options
  153. const server = this._server = this.getServer()
  154. return new Promise((resolve, reject) => {
  155. server.listen(options.port, () => {
  156. onServerUp(options.port, options['static.root'], server.isHttps)
  157. resolve(server)
  158. })
  159. })
  160. }
  161. close () {
  162. this._server.close()
  163. }
  164. }
  165. function onServerUp (port, directory, isHttps) {
  166. const ipList = getIPList()
  167. .map(iface => `[underline]{${isHttps ? 'https' : 'http'}://${iface.address}:${port}}`)
  168. .join(', ')
  169. console.error(ansi.format(
  170. path.resolve(directory || '') === process.cwd()
  171. ? `serving at ${ipList}`
  172. : `serving [underline]{${directory}} at ${ipList}`
  173. ))
  174. }
  175. function getIPList () {
  176. const flatten = require('reduce-flatten')
  177. const os = require('os')
  178. let ipList = Object.keys(os.networkInterfaces())
  179. .map(key => os.networkInterfaces()[key])
  180. .reduce(flatten, [])
  181. .filter(iface => iface.family === 'IPv4')
  182. ipList.unshift({ address: os.hostname() })
  183. return ipList
  184. }
  185. /**
  186. * Loads a module by either path or name.
  187. * @returns {object}
  188. */
  189. function loadStack (modulePath) {
  190. let module
  191. const tried = []
  192. if (modulePath) {
  193. try {
  194. tried.push(path.resolve(modulePath))
  195. module = require(path.resolve(modulePath))
  196. } catch (err) {
  197. const walkBack = require('walk-back')
  198. const foundPath = walkBack(process.cwd(), path.join('node_modules', 'local-web-server-' + modulePath))
  199. tried.push('local-web-server-' + modulePath)
  200. if (foundPath) {
  201. module = require(foundPath)
  202. } else {
  203. const foundPath2 = walkBack(process.cwd(), path.join('node_modules', modulePath))
  204. tried.push(modulePath)
  205. if (foundPath2) {
  206. module = require(foundPath2)
  207. }
  208. }
  209. }
  210. }
  211. if (module) {
  212. if (!(module.prototype.middleware || module.prototype.stack)) {
  213. const insp = require('util').inspect(module, { depth: 3, colors: true })
  214. const msg = `Not valid Middleware at: ${insp}`
  215. tool.halt(new Error(msg))
  216. }
  217. } else {
  218. const msg = `No module found at: \n${tried.join('\n')}`
  219. tool.halt(new Error(msg))
  220. }
  221. return module
  222. }
  223. module.exports = LocalWebServer