UNPKG

ember-cli-ajh

Version:

Command line tool for developing ambitious ember.js apps

232 lines (201 loc) 7.23 kB
'use strict'; var path = require('path'); var EventEmitter = require('events').EventEmitter; var chalk = require('chalk'); var fs = require('fs'); var existsSync = require('exists-sync'); var debounce = require('lodash/function/debounce'); var mapSeries = require('promise-map-series'); var Promise = require('../../ext/promise'); var Task = require('../../models/task'); var SilentError = require('silent-error'); var cleanBaseURL = require('clean-base-url'); module.exports = Task.extend({ init: function() { this.emitter = new EventEmitter(); this.express = this.express || require('express'); this.http = this.http || require('http'); this.https = this.https || require('https'); var serverRestartDelayTime = this.serverRestartDelayTime || 100; this.scheduleServerRestart = debounce(function(){ this.restartHttpServer(); }, serverRestartDelayTime); }, on: function() { this.emitter.on.apply(this.emitter, arguments); }, off: function() { this.emitter.off.apply(this.emitter, arguments); }, emit: function() { this.emitter.emit.apply(this.emitter, arguments); }, displayHost: function(specifiedHost) { return specifiedHost || 'localhost'; }, setupHttpServer: function() { if (this.startOptions.ssl) { this.httpServer = this.createHttpsServer(); } else { this.httpServer = this.http.createServer(this.app); } // We have to keep track of sockets so that we can close them // when we need to restart. this.sockets = {}; this.nextSocketId = 0; this.httpServer.on('connection', function(socket) { var socketId = this.nextSocketId++; this.sockets[socketId] = socket; socket.on('close', function() { delete this.sockets[socketId]; }.bind(this)); }.bind(this)); }, createHttpsServer: function() { if(!existsSync(this.startOptions.sslKey)) { throw new TypeError('SSL key couldn\'t be found in "' + this.startOptions.sslKey + '", please provide a path to an existing ssl key file with --ssl-key'); } if(!existsSync(this.startOptions.sslCert)) { throw new TypeError('SSL certificate couldn\'t be found in "' + this.startOptions.sslCert + '", please provide a path to an existing ssl certificate file with --ssl-cert'); } var options = { key: fs.readFileSync(this.startOptions.sslKey), cert: fs.readFileSync(this.startOptions.sslCert) }; return this.https.createServer(options, this.app); }, listen: function(port, host) { var server = this.httpServer; return new Promise(function(resolve, reject) { server.listen(port, host); server.on('listening', function() { resolve(); this.emit('listening'); }.bind(this)); server.on('error', reject); }.bind(this)); }, processAddonMiddlewares: function(options) { this.project.initializeAddons(); return mapSeries(this.project.addons, function(addon) { if (addon.serverMiddleware) { return addon.serverMiddleware({ app: this.app, options: options }); } }, this); }, processAppMiddlewares: function(options) { if (this.project.has(this.serverRoot)) { var server = this.project.require(this.serverRoot); if (typeof server !== 'function') { throw new TypeError('ember-cli expected ./server/index.js to be the entry for your mock or proxy server'); } if (server.length === 3) { // express app is function of form req, res, next return this.app.use(server); } return server(this.app, options); } }, start: function(options) { options.project = this.project; options.watcher = this.watcher; options.serverWatcher = this.serverWatcher; options.ui = this.ui; this.startOptions = options; if (this.serverWatcher) { this.serverWatcher.on('change', this.serverWatcherDidChange.bind(this)); this.serverWatcher.on('add', this.serverWatcherDidChange.bind(this)); this.serverWatcher.on('delete', this.serverWatcherDidChange.bind(this)); } return this.startHttpServer() .then(function () { var baseURL = cleanBaseURL(options.baseURL); options.ui.writeLine('Serving on http' + (options.ssl ? 's' : '') + '://' + this.displayHost(options.host) + ':' + options.port + baseURL); }.bind(this)); }, serverWatcherDidChange: function() { this.scheduleServerRestart(); }, restartHttpServer: function() { if (!this.serverRestartPromise) { this.serverRestartPromise = this.stopHttpServer() .then(function () { this.invalidateCache(this.serverRoot); return this.startHttpServer(); }.bind(this)) .then(function () { this.emit('restart'); this.ui.writeLine(''); this.ui.writeLine(chalk.green('Server restarted.')); this.ui.writeLine(''); }.bind(this)) .catch(function (err) { this.ui.writeError(err); }.bind(this)) .finally(function () { this.serverRestartPromise = null; }.bind(this)); return this.serverRestartPromise; } else { return this.serverRestartPromise.then(function () { return this.restartHttpServer(); }.bind(this)); } }, stopHttpServer: function() { return new Promise(function (resolve, reject) { if (!this.httpServer) { return resolve(); } this.httpServer.close(function (err) { if (err) { reject(err); return; } this.httpServer = null; resolve(); }.bind(this)); // We have to force close all sockets in order to get an fast restart var sockets = this.sockets; for (var socketId in sockets) { sockets[socketId].destroy(); } }.bind(this)); }, startHttpServer: function() { this.app = this.express(); this.app.use(require('compression')()); this.setupHttpServer(); var options = this.startOptions; options.httpServer = this.httpServer; return Promise.resolve() .then(function(){ return this.processAppMiddlewares(options); }.bind(this)) .then(function(){ return this.processAddonMiddlewares(options); }.bind(this)) .then(function(){ return this.listen(options.port, options.host) .catch(function() { throw new SilentError('Could not serve on http://' + this.displayHost(options.host) + ':' + options.port + '. It is either in use or you do not have permission.'); }.bind(this)); }.bind(this)); }, invalidateCache: function (serverRoot) { var absoluteServerRoot = path.resolve(serverRoot); if (absoluteServerRoot[absoluteServerRoot.length - 1] !== path.sep) { absoluteServerRoot += path.sep; } var allKeys = Object.keys(require.cache); for (var i = 0; i < allKeys.length; i++) { if (allKeys[i].indexOf(absoluteServerRoot) === 0) { delete require.cache[allKeys[i]]; } } } });