UNPKG

bricks-cli

Version:

Command line tool for developing ambitious ember.js apps

340 lines (317 loc) 9.95 kB
/* dev_mode_app.js =============== This is the entry point for development(TDD) mode. */ var EventEmitter = require('events').EventEmitter var Backbone = require('backbone') var async = require('async') var Server = require('../server') var fs = require('fs') var log = require('npmlog') var AppView = require('./ui/appview') var HookRunner = require('../hook_runner') var child_process = require('child_process') var BrowserRunner = require('../browser_runner') var Path = require('path') var yaml = require('js-yaml') var fireworm = require('fireworm') var Config = require('../config') var browser_launcher = require('../browser_launcher') var Launcher = require('../launcher') var StyledString = require('styled_string') function App(config, finalizer){ var self = this this.config = config this.runners = new Backbone.Collection this .on('all-test-results', function () { var allRunnersComplete = self.runners.all(function (runner) { var results = runner.get('results') return results && !!results.get('all') }) if (allRunnersComplete) { self.emit('all-runners-complete') } }) this.exited = false this.finalizer = finalizer || process.exit this.fileWatcher = fireworm('./', { ignoreInitial: true }) var onFileChanged = this.onFileChanged.bind(this) this.fileWatcher.on('change', onFileChanged) this.fileWatcher.on('add', onFileChanged) this.fileWatcher.on('remove', onFileChanged) this.fileWatcher.on('emfile', this.onEMFILE.bind(this)) // a list of connected browser clients this.runners.on('remove', function(runner){ runner.unbind() }) this.view = new AppView({ app: this }) this.view.on('ctrl-c', function(){ self.quit() }) this.configure(function(){ this.server = new Server(config) this.server.on('server-start', this.initView.bind(this)) this.server.on('file-requested', this.onFileRequested.bind(this)) this.server.on('browser-login', this.onBrowserLogin.bind(this)) this.server.on('server-error', this.onServerError.bind(this)) this.server.start() }) process.on('uncaughtException', function(err){ self.quit(1, err) }) } App.prototype = { __proto__: EventEmitter.prototype , start: function(){} , initView: function(){ var self = this var view = this.view if (this.view.on) this.view.on('inputChar', this.onInputChar.bind(this)) this.on('all-runners-complete', function(){ self.runPostprocessors() }) self.startOnStartHook(function(err){ if (err){ var titleText = 'Error running on_start hook' var title = StyledString(titleText + '\n ').foreground('red') var errMsgs = StyledString('\n' + err.name).foreground('white') .concat(StyledString('\n' + err.stdout).foreground('yellow')) .concat(StyledString('\n' + err.stderr).foreground('red')) view.setErrorPopupMessage(title.concat(errMsgs)) log.log( 'warn' , titleText , { name: err.name , stdout: err.stdout , stderr: err.stderr } ) return } else { self.startTests(function(){ self.initLaunchers() }) } }) } , initLaunchers: function(){ var config = this.config var launch_in_dev = config.get('launch_in_dev') var self = this config.getLaunchers(function(launchers){ self.launchers = launchers launchers.forEach(function(launcher){ log.info('Launching ' + launcher.name) launcher.start() if (launcher.runner){ self.runners.push(launcher.runner) } }) }) } , configure: function(cb){ var self = this , fileWatcher = self.fileWatcher , config = self.config config.read(function(){ var watchFiles = config.get('watch_files') fileWatcher.clear() var confFile = config.get('file') if (confFile){ fileWatcher.add(confFile) } if (config.isCwdMode()){ fileWatcher.add('*.js') } if (watchFiles) { fileWatcher.add(watchFiles) } var srcFiles = config.get('src_files') || '*.js' fileWatcher.add(srcFiles) var ignoreFiles = config.get('src_files_ignore') if (ignoreFiles){ fileWatcher.ignore(ignoreFiles) } if (cb) cb.call(self) }) } , onFileRequested: function(filepath){ if (!this.config.get('serve_files')){ this.fileWatcher.add(filepath) } } , onFileChanged: function(filepath){ log.info(filepath + ' changed ('+(this.disableFileWatch ? 'disabled' : 'enabled')+').') if (this.disableFileWatch) return var configFile = this.config.get('file') if ((configFile && filepath === Path.resolve(configFile)) || (this.config.isCwdMode() && filepath === process.cwd())){ // config changed this.configure(this.startTests.bind(this)) }else{ this.runHook('on_change', {file: filepath}, this.startTests.bind(this)) } } , onEMFILE: function(){ var view = this.view var text = [ 'The file watcher received a EMFILE system error, which means that ', 'it has hit the maximum number of files that can be open at a time. ', 'Luckily, you can increase this limit as a workaround. See the directions below \n \n', 'Linux: http://stackoverflow.com/a/34645/5304\n', 'Mac OS: http://serverfault.com/a/15575/47234' ].join('') view.setErrorPopupMessage(StyledString(text + '\n ').foreground('megenta')) } , onServerError: function(err){ this.quit(1, err) } , onGeneralWatcherError: function(message){ log.error('Error from fireworm: ' + message) } , onBrowserLogin: function(browserName, id, client){ this.connectBrowser(browserName, id, client) } , quit: function(code, err){ if (this.exited) return var self = this this.emit('exit') this.cleanUpLaunchers(function(){ self.runExitHook(function(){ if (self.view) self.view.cleanup(die) else die() function die(){ if (err) console.error(err.stack) self.finalizer(code) self.exited = true } }) }) } , onInputChar: function(chr, i) { var self = this if (chr === 'q') { log.info('Got keyboard Quit command') this.quit() } else if (i === 13){ // ENTER log.info('Got keyboard Start Tests command') this.startTests() } } , startTests: function(callback){ try{ var view = this.view var runners = this.runners this.runPreprocessors(function(err){ if (err){ var titleText = 'Error running before_tests hook' var title = StyledString(titleText + '\n ').foreground('red') var errMsgs = StyledString('\n' + err.name).foreground('white') .concat(StyledString('\n' + err.stdout).foreground('yellow')) .concat(StyledString('\n' + err.stderr).foreground('red')) view.setErrorPopupMessage(title.concat(errMsgs)) log.log( 'warn' , titleText , { name: err.name , stdout: err.stdout , stderr: err.stderr } ) return }else{ view.clearErrorPopupMessage() runners.forEach(function(runner){ runner.startTests() }) if (callback) callback() } }) }catch(e){ log.info(e.message) log.info(e.stack) } } , runPreprocessors: function(callback){ this.runHook('before_tests', callback) } , runPostprocessors: function(callback){ this.runHook('after_tests', callback) } , startOnStartHook: function(callback){ this.onStartProcess = new HookRunner(this.config) this.onStartProcess.run('on_start', [], callback) } , runExitHook: function (callback) { if(this.onStartProcess) { this.onStartProcess.stop() } this.runHook('on_exit', callback) } , runHook: function(/*hook, data..., callback*/){ var hook = arguments[0] var callback = arguments[arguments.length-1] var data = arguments.length > 2 ? arguments[1] : {} var runner = new HookRunner(this.config) var self = this log.info("Hook "+hook+" started") this.disableFileWatch = true runner.run(hook, data, function(err){ log.info("Hook "+hook+" finished") self.disableFileWatch = false if (callback) { callback(err) } }) } , removeBrowser: function(browser){ this.runners.remove(browser) } , connectBrowser: function(browserName, id, client){ var existing = this.runners.find(function(runner){ return runner.pending && runner.get('name') === browserName }) if (existing){ clearTimeout(existing.pending) existing.set('socket', client) return existing }else{ var browser = new BrowserRunner({ name: browserName , socket: client }) var self = this browser.on('disconnect', function(){ browser.pending = setTimeout(function(){ self.removeBrowser(browser) }, 1000) }) browser.on('all-test-results', function(results, browser){ self.emit('all-test-results', results, browser) }) browser.on('top-level-error', function(msg, url, line){ self.emit('top-level-error', msg, url, line) }) this.runners.push(browser) return browser } } , cleanUpLaunchers: function(callback){ if (!this.launchers){ if (callback) callback() return } async.forEach(this.launchers, function(launcher, done){ if (launcher && launcher.process){ launcher.kill('SIGTERM', done) }else{ done() } }, callback) } } module.exports = App