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.

263 lines
7.5 KiB

9 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
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
9 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
8 years ago
9 years ago
9 years ago
9 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. const arrayify = require('array-back')
  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. /* load the stack */
  40. const stackModules = stackPaths
  41. .map(stackPath => loadStack(stackPath))
  42. .map(Middleware => new Middleware())
  43. .map(module => {
  44. if (module.stack) {
  45. const featureStack = module.stack()
  46. module.optionDefinitions = function () {
  47. return featureStack
  48. .map(Feature => new Feature())
  49. .map(feature => feature.optionDefinitions && feature.optionDefinitions())
  50. .filter(definitions => definitions)
  51. .reduce(flatten, [])
  52. }
  53. module.middleware = function (options) {
  54. return featureStack
  55. .map(Feature => new Feature())
  56. .map(feature => feature.middleware(options))
  57. .reduce(flatten, [])
  58. .filter(mw => mw)
  59. }
  60. }
  61. return module
  62. })
  63. /* gather stack option definitions and parse the command line */
  64. const middlewareOptionDefinitions = stackModules
  65. .filter(mw => mw.optionDefinitions)
  66. .map(mw => mw.optionDefinitions())
  67. .reduce(flatten, [])
  68. .filter(def => def)
  69. .map(def => {
  70. def.group = 'middleware'
  71. return def
  72. })
  73. const usage = commandLineUsage(cli.usage(middlewareOptionDefinitions))
  74. let options = {}
  75. const allOptionDefinitions = cli.optionDefinitions.concat(middlewareOptionDefinitions)
  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. /* combine in stored config */
  87. options = Object.assign({ port: 8000 }, initOptions, stored || {}, options.server, options.middleware, options.misc)
  88. this.options = options
  89. // console.log(initOptions, stored, options)
  90. if (options.verbose) {
  91. // debug.setLevel(1)
  92. }
  93. /* --config */
  94. if (options.config) {
  95. tool.stop(JSON.stringify(options, null, ' '), 0)
  96. /* --version */
  97. } else if (options.version) {
  98. const pkg = require(path.resolve(__dirname, '..', 'package.json'))
  99. tool.stop(pkg.version)
  100. /* --help */
  101. } else if (options.help) {
  102. tool.stop(usage)
  103. } else {
  104. this.stack = stackModules
  105. }
  106. }
  107. getApplication () {
  108. const Koa = require('koa')
  109. const app = new Koa()
  110. const compose = require('koa-compose')
  111. const convert = require('koa-convert')
  112. const middlewareStack = this.stack
  113. .filter(mw => mw.middleware)
  114. .map(mw => mw.middleware(this.options))
  115. .reduce(flatten, [])
  116. .filter(mw => mw)
  117. .map(convert)
  118. app.use(compose(middlewareStack))
  119. app.on('error', err => {
  120. const defaultLogInUse = this.stack.some(mw => mw.constructor.name === 'Log')
  121. if (defaultLogInUse) {
  122. if (this.options['log.format']) console.error(ansi.format(err.stack, 'red'))
  123. } else {
  124. console.error(ansi.format(err.stack, 'red'))
  125. }
  126. })
  127. return app
  128. }
  129. getServer () {
  130. const app = this.getApplication()
  131. const options = this.options
  132. let key = options.key
  133. let cert = options.cert
  134. if (options.https && !(key && cert)) {
  135. key = path.resolve(__dirname, '..', 'ssl', '127.0.0.1.key')
  136. cert = path.resolve(__dirname, '..', 'ssl', '127.0.0.1.crt')
  137. }
  138. let server = null
  139. if (key && cert) {
  140. const fs = require('fs')
  141. const serverOptions = {
  142. key: fs.readFileSync(key),
  143. cert: fs.readFileSync(cert)
  144. }
  145. const https = require('https')
  146. server = https.createServer(serverOptions, app.callback())
  147. server.isHttps = true
  148. } else {
  149. const http = require('http')
  150. server = http.createServer(app.callback())
  151. }
  152. return server
  153. }
  154. listen () {
  155. const options = this.options
  156. const server = this._server = this.getServer()
  157. // console.log(options)
  158. return new Promise((resolve, reject) => {
  159. server.listen(options.port, () => {
  160. onServerUp(options.port, options['static.root'], server.isHttps)
  161. resolve(server)
  162. })
  163. })
  164. }
  165. close () {
  166. this._server.close()
  167. }
  168. }
  169. function onServerUp (port, directory, isHttps) {
  170. const ipList = getIPList()
  171. .map(iface => `[underline]{${isHttps ? 'https' : 'http'}://${iface.address}:${port}}`)
  172. .join(', ')
  173. console.error(ansi.format(
  174. path.resolve(directory || '') === process.cwd()
  175. ? `serving at ${ipList}`
  176. : `serving [underline]{${directory}} at ${ipList}`
  177. ))
  178. }
  179. function getIPList () {
  180. const flatten = require('reduce-flatten')
  181. const os = require('os')
  182. let ipList = Object.keys(os.networkInterfaces())
  183. .map(key => os.networkInterfaces()[key])
  184. .reduce(flatten, [])
  185. .filter(iface => iface.family === 'IPv4')
  186. ipList.unshift({ address: os.hostname() })
  187. return ipList
  188. }
  189. /**
  190. * Loads a module by either path or name.
  191. * @returns {object}
  192. */
  193. function loadStack (modulePath) {
  194. let module
  195. if (isModule(modulePath)) return modulePath
  196. const tried = []
  197. if (modulePath) {
  198. try {
  199. tried.push(path.resolve(modulePath))
  200. module = require(path.resolve(modulePath))
  201. } catch (err) {
  202. const walkBack = require('walk-back')
  203. const foundPath = walkBack(process.cwd(), path.join('node_modules', 'local-web-server-' + modulePath))
  204. tried.push('local-web-server-' + modulePath)
  205. if (foundPath) {
  206. module = require(foundPath)
  207. } else {
  208. const foundPath2 = walkBack(process.cwd(), path.join('node_modules', modulePath))
  209. tried.push(modulePath)
  210. if (foundPath2) {
  211. module = require(foundPath2)
  212. }
  213. }
  214. }
  215. }
  216. if (module) {
  217. if (!isModule(module)) {
  218. const insp = require('util').inspect(module, { depth: 3, colors: true })
  219. const msg = `Not valid Middleware at: ${insp}`
  220. tool.halt(new Error(msg))
  221. }
  222. } else {
  223. const msg = `No module found at: \n${tried.join('\n')}`
  224. tool.halt(new Error(msg))
  225. }
  226. return module
  227. }
  228. function isModule (module) {
  229. return module.prototype && (module.prototype.middleware || module.prototype.stack)
  230. }
  231. module.exports = LocalWebServer