From 7891f41f8ae73d73fd080f5395c7f424006fc281 Mon Sep 17 00:00:00 2001 From: yofreke Date: Sat, 18 Jul 2015 18:06:54 -0700 Subject: [PATCH 01/11] add routes for app modules and src directories --- src/serve/appRoutes.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/serve/appRoutes.js b/src/serve/appRoutes.js index 805dacdf..bda0e42d 100644 --- a/src/serve/appRoutes.js +++ b/src/serve/appRoutes.js @@ -142,7 +142,12 @@ exports.addToAPI = function (opts, api) { var simulatorApp = express(); baseApp.use('/apps/' + routeId, simulatorApp); + // Special case src directories + simulatorApp.use('/modules', express.static(path.join(appPath, 'modules'))); + simulatorApp.use('/src', express.static(path.join(appPath, 'src'))); + // Static serve builds simulatorApp.use('/', express.static(buildPath)); + addSimulatorAPI(simulatorApp); var modules = app.getModules(); From 359645d403addd1931347b862df36b34d4fb0e72 Mon Sep 17 00:00:00 2001 From: yofreke Date: Mon, 20 Jul 2015 08:30:26 -0700 Subject: [PATCH 02/11] better static content serving --- modules/devkit-simulator-client/client.js | 4 ++++ src/serve/index.js | 2 ++ src/serve/static/API.js | 1 + src/serve/static/Simulator.js | 13 +++++++++++-- src/serve/static/ui/Chrome.js | 7 ++++++- src/serve/static/util/Channel.js | 7 ++++--- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/modules/devkit-simulator-client/client.js b/modules/devkit-simulator-client/client.js index 8b5266bc..81422874 100644 --- a/modules/devkit-simulator-client/client.js +++ b/modules/devkit-simulator-client/client.js @@ -45,6 +45,10 @@ exports.onLaunch = function () { GLOBAL.NATIVE.onBackButton && GLOBAL.NATIVE.onBackButton(evt); }); + channel.on('reload', function (evt) { + debugger + }); + channel.on('screenshot', function (data, req) { devkit.debugging.screenshot(function (err, res) { if (err) { diff --git a/src/serve/index.js b/src/serve/index.js index 03d07001..2764aecf 100644 --- a/src/serve/index.js +++ b/src/serve/index.js @@ -54,6 +54,8 @@ exports.serveWeb = function (opts, cb) { app.use('/compile/', importMiddleware(getPath('static/'))); // serve static files + var devkitPath = path.resolve(__dirname, '..', '..'); + app.use('/devkit/', express.static(devkitPath)); app.use('/', express.static(getPath('static'))); // Serve diff --git a/src/serve/static/API.js b/src/serve/static/API.js index 649d6ca5..ce2e6949 100644 --- a/src/serve/static/API.js +++ b/src/serve/static/API.js @@ -9,6 +9,7 @@ exports = Class(ChannelAPI, function (supr) { this._simulator = simulator; devkitConn.getTransport().bind(this).then(function (socket) { + debugger this.devkit.setTransport(socket); }); }; diff --git a/src/serve/static/Simulator.js b/src/serve/static/Simulator.js index 10a17a3d..c55f8808 100644 --- a/src/serve/static/Simulator.js +++ b/src/serve/static/Simulator.js @@ -61,8 +61,11 @@ exports = Class(function () { }, this); }; - this.rebuild = function (cb) { + this.rebuild = function (cb, softReload) { this._ui.setBuilding(true); + // This will cause the iframe to reload. jsio will wait for the all clear before initilizing the app + // this.setURL(res.url); + // get or update a simulator port with the following options return util.ajax .get({ @@ -79,7 +82,13 @@ exports = Class(function () { .then(function (res) { var res = res[0]; this._ui.setBuilding(false); - this.setURL(res.url); + // if (softReload) { + // This will tell jsio that it should reload the javascript it has, using the old url resolutions (if possible) + // this._ui.softReload(); + // } else { + // This will cause the iframe to reload + this.setURL(res.url); + // } this.loadModules(res.debuggerURLs); }, function (err) { logger.error('Unable to simulate', this._app); diff --git a/src/serve/static/ui/Chrome.js b/src/serve/static/ui/Chrome.js index 4be5790a..9091ad2e 100644 --- a/src/serve/static/ui/Chrome.js +++ b/src/serve/static/ui/Chrome.js @@ -54,6 +54,7 @@ exports = Class(CenterLayout, function (supr) { this.init = function (simulator) { this._simulator = simulator; this._channel = simulator.api.getChannel('devkit-simulator'); + this._channel.connect(); this._channel.on('hideSplash', bind(this, 'hideSplash')); this._channel.on('connect', bind(this, '_onConnect')); @@ -494,7 +495,7 @@ exports = Class(CenterLayout, function (supr) { }; this.reload = function () { - this._simulator.rebuild(); + this._simulator.rebuild(null, true); // this._frame.contentWindow.reload(); }; @@ -608,6 +609,10 @@ exports = Class(CenterLayout, function (supr) { } }; + this.softReload = function() { + this._channel.emit('reload'); + }; + this.takeScreenshot = function () { var win = window.open('', '', 'width=' + (this._screenWidth + 2) + ',height=' + (this._screenHeight + 2)); this._channel.request('screenshot').then(function (res) { diff --git a/src/serve/static/util/Channel.js b/src/serve/static/util/Channel.js index 8b8ef6a2..c23d28aa 100644 --- a/src/serve/static/util/Channel.js +++ b/src/serve/static/util/Channel.js @@ -38,7 +38,6 @@ exports = Class(lib.PubSub, function (supr) { // internal: set an underlying transport this.setTransport = function (transport) { - if (this._transport && this._transport != transport) { // tear-down an old transport this._transport @@ -94,14 +93,16 @@ exports = Class(lib.PubSub, function (supr) { switch (msg) { case 'connect': // complete the channel connection + console.log('Channel connected:', this._name); this._sendInternalMessage('connectConfirmed'); - // fall-through case 'connectConfirmed': + console.log('Channel connection confirmed:', this._name); this._isConnected = true; this._emit('connect'); break; case 'disconnect': + console.log('Channel disconnect:', this._name); this._isConnected = false; this._emit('disconnect'); break; @@ -132,7 +133,7 @@ exports = Class(lib.PubSub, function (supr) { if (this._transport) { this._transport.emit(this._name, data); } else { - logger.warn(this._name, 'failed to send', data); + logger.warn(this._name, 'transport not set, failed to send', data); } }; From 67b6c75587a9c2949b283c733d04de8526e10a8c Mon Sep 17 00:00:00 2001 From: yofreke Date: Thu, 23 Jul 2015 03:08:59 -0700 Subject: [PATCH 03/11] parallelize preloading sources and devkit rebuild for soft reloads --- modules/devkit-simulator-client/client.js | 24 ++++++++++- src/serve/appRoutes.js | 8 +++- src/serve/static/API.js | 1 - src/serve/static/Simulator.js | 44 +++++++++++++------ src/serve/static/devkitConn.js | 52 ----------------------- src/serve/static/ui/Chrome.js | 40 ++++++++++++++++- 6 files changed, 97 insertions(+), 72 deletions(-) diff --git a/modules/devkit-simulator-client/client.js b/modules/devkit-simulator-client/client.js index 81422874..54d12a72 100644 --- a/modules/devkit-simulator-client/client.js +++ b/modules/devkit-simulator-client/client.js @@ -45,8 +45,28 @@ exports.onLaunch = function () { GLOBAL.NATIVE.onBackButton && GLOBAL.NATIVE.onBackButton(evt); }); - channel.on('reload', function (evt) { - debugger + /** Puts preserveCache on localStorage so that jsio knows to preserve (and then request) + suggestions next load. Optionally also sets partialLoad, which causes jsio to wait for + a partialLoadContinue signal to actually load the app, after preloading suggestions. */ + channel.on('reload', function (data, req) { + localStorage.setItem(jsio.__env.getNamespace('preserveCache'), true); + + if (data) { + if (data.partialLoad) { + localStorage.setItem(jsio.__env.getNamespace('partialLoad'), true); + } + } + + req.send(true); + }); + + /** partialLoadContinue is to be used in conjunction with partialLoad */ + channel.on('partialLoadContinue', function (data, req) { + var def = window._continueLoadDefer; + if (def) { + def.resolve(); + } + req.send(true); }); channel.on('screenshot', function (data, req) { diff --git a/src/serve/appRoutes.js b/src/serve/appRoutes.js index bda0e42d..0e51109c 100644 --- a/src/serve/appRoutes.js +++ b/src/serve/appRoutes.js @@ -143,8 +143,12 @@ exports.addToAPI = function (opts, api) { baseApp.use('/apps/' + routeId, simulatorApp); // Special case src directories - simulatorApp.use('/modules', express.static(path.join(appPath, 'modules'))); - simulatorApp.use('/src', express.static(path.join(appPath, 'src'))); + simulatorApp.use('/modules', + express.static(path.join(appPath, 'modules')) + ); + simulatorApp.use('/src', + express.static(path.join(appPath, 'src')) + ); // Static serve builds simulatorApp.use('/', express.static(buildPath)); diff --git a/src/serve/static/API.js b/src/serve/static/API.js index ce2e6949..649d6ca5 100644 --- a/src/serve/static/API.js +++ b/src/serve/static/API.js @@ -9,7 +9,6 @@ exports = Class(ChannelAPI, function (supr) { this._simulator = simulator; devkitConn.getTransport().bind(this).then(function (socket) { - debugger this.devkit.setTransport(socket); }); }; diff --git a/src/serve/static/Simulator.js b/src/serve/static/Simulator.js index c55f8808..e0f12d0c 100644 --- a/src/serve/static/Simulator.js +++ b/src/serve/static/Simulator.js @@ -62,13 +62,24 @@ exports = Class(function () { }; this.rebuild = function (cb, softReload) { - this._ui.setBuilding(true); - // This will cause the iframe to reload. jsio will wait for the all clear before initilizing the app - // this.setURL(res.url); + var ui = this._ui; + var tasks = []; + + ui.setBuilding(true); + + if (softReload) { + tasks.push(ui.softReload() + .then(ui.refresh.bind(ui)) + .catch(function (e) { + logger.log("Error with soft reload", e); + }) + ); + } + // else { // get or update a simulator port with the following options - return util.ajax - .get({ + tasks.push( + util.ajax.get({ url: '/api/simulate/', query: { app: this._app, @@ -76,25 +87,30 @@ exports = Class(function () { deviceId: this.id, scheme: 'debug', target: this._buildTarget - } + }, + async: true }) .bind(this) .then(function (res) { var res = res[0]; - this._ui.setBuilding(false); - // if (softReload) { - // This will tell jsio that it should reload the javascript it has, using the old url resolutions (if possible) - // this._ui.softReload(); - // } else { - // This will cause the iframe to reload + if (!softReload) { this.setURL(res.url); - // } + } this.loadModules(res.debuggerURLs); }, function (err) { logger.error('Unable to simulate', this._app); console.error(err); }) - .nodeify(cb); + ); +// } + + // Some final clean up + var promise = Promise.all(tasks); + if (softReload) { + promise = promise.then(ui.continueLoad.bind(ui)); + } + prommise = promise.then(function() { setTimeout(function() { ui.setBuilding(false); }, 200); }); + return promise.nodeify(cb); }; this.setURL = function (url) { diff --git a/src/serve/static/devkitConn.js b/src/serve/static/devkitConn.js index 0797f852..47042b2d 100644 --- a/src/serve/static/devkitConn.js +++ b/src/serve/static/devkitConn.js @@ -44,58 +44,6 @@ function connect(namespace) { }); } -/* -exports = Class(function () { - this.init = function (controller) { - this._controller = controller; - - connect('/devkit-simulator/', bind(this, '_onSocket')); - } - - this._onSocket = function (err, socket) { - socket.emit('request:devices'); - socket.on('device', function (info) { - this._controller.onDeviceConn({ - isLocal: false, - deviceId: info.deviceId, - type: info.deviceType, - userAgent: info.userAgent, - screen: info.screen, - conn: new ConnectionWrapper(info.deviceId, socket), - }); - }); - - socket.on('liveedit', function (data) { - console.log(data); - }); - } -}); -*/ - exports.getTransport = function (namespace) { return connect(namespace); } - -// multiplex TargetCuppa into a single socket.io connection -// all instances can share the same socket, split by deviceId -// var ConnectionWrapper = Class(TargetCuppa, function () { -// this.init = function (deviceId, socket) { -// supr(this, 'init'); - -// // handle write calls -// this.transport = { -// write: function (data) { -// socket.emit('send', { -// deviceId: deviceId -// }); -// }, -// close: function () {} -// }; - -// // handle read calls -// socket.on('device:' + deviceId, bind(this, 'dataReceived')); - -// // notify protocol of connection -// this.connectionMade(); -// } -// }); diff --git a/src/serve/static/ui/Chrome.js b/src/serve/static/ui/Chrome.js index 9091ad2e..77efd06e 100644 --- a/src/serve/static/ui/Chrome.js +++ b/src/serve/static/ui/Chrome.js @@ -444,7 +444,22 @@ exports = Class(CenterLayout, function (supr) { className: 'frame' }); + var def = this._newIframeLoadDefer(); + + // Listen for bootstrapping + // TODO: use the proper channel stuff for this + window.addEventListener('message', function(event) { + if (event.data === 'bootstrapping') { + if (this._iframeLoadDefer) { + this._iframeLoadDefer.resolve(); + this._iframeLoadDefer = undefined; + } + } + }.bind(this)); + this.update(); + + return def.promise; }; this.getDevicePixelRatio = function () { @@ -601,18 +616,41 @@ exports = Class(CenterLayout, function (supr) { this.update(); }; + /** Reject any old deferred, make a new one. */ + this._newIframeLoadDefer = function() { + if (this._iframeLoadDefer) { + this._iframeLoadDefer.reject('another load has been called'); + } + var def = Promise.defer(); + this._iframeLoadDefer = def; + return def; + }; + // restart without rebuilding this.restart = this.refresh = function () { if (this._frame) { + var def = this._newIframeLoadDefer(); this._frame.src = this._frame.src; + return def.promise; } + return Promise.resolve(); }; this.softReload = function() { - this._channel.emit('reload'); + return this._channel.request('reload', { partialLoad: true }); }; + /** Send a partialLoadContinue signal to the inner window, return the promise */ + this.continueLoad = function() { + if (this._frame) { + this._frame.contentWindow.postMessage('partialLoadContinue', '*'); + return Promise.resolve(); + } + return Promise.reject('no iframe set'); + // return this._channel.request('partialLoadContinue'); + } + this.takeScreenshot = function () { var win = window.open('', '', 'width=' + (this._screenWidth + 2) + ',height=' + (this._screenHeight + 2)); this._channel.request('screenshot').then(function (res) { From 05aecea1ba70f74ca7d3cffe8dee690cc242518c Mon Sep 17 00:00:00 2001 From: yofreke Date: Thu, 23 Jul 2015 18:04:40 -0700 Subject: [PATCH 04/11] added routes for static serving resources --- src/build/index.js | 3 +-- src/serve/appRoutes.js | 35 +++++++++++++++++++++++++++++------ src/serve/static/Simulator.js | 4 +--- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/build/index.js b/src/build/index.js index 956a613d..ab9c9d34 100644 --- a/src/build/index.js +++ b/src/build/index.js @@ -72,9 +72,8 @@ exports.build = function (appPath, argv, cb) { // ONLY print config to stdout process.stdout.write(JSON.stringify(merge({title: app.manifest.title}, config))); process.exit(0); - } else { - require('./steps/logConfig').log(app, config, f()); } + require('./steps/logConfig').log(app, config, f()); }, function () { require('./steps/executeTargetBuild').build(app, config, f()); }, function () { diff --git a/src/serve/appRoutes.js b/src/serve/appRoutes.js index 0e51109c..51b63837 100644 --- a/src/serve/appRoutes.js +++ b/src/serve/appRoutes.js @@ -143,12 +143,19 @@ exports.addToAPI = function (opts, api) { baseApp.use('/apps/' + routeId, simulatorApp); // Special case src directories - simulatorApp.use('/modules', - express.static(path.join(appPath, 'modules')) - ); - simulatorApp.use('/src', - express.static(path.join(appPath, 'src')) - ); + simulatorApp.use( + '/modules', + express.static(path.join(appPath, 'modules')) + ); + simulatorApp.use( + '/src', + express.static(path.join(appPath, 'src')) + ); + simulatorApp.use( + '/resources', + express.static(path.join(appPath, 'resources')) + ); + // Static serve builds simulatorApp.use('/', express.static(buildPath)); @@ -158,8 +165,24 @@ exports.addToAPI = function (opts, api) { var debuggerURLs = {}; var simulatorURLs = {}; var loadExtension = function (module) { + // All modules get a chance to add "resources" routes + var buildExtension = module.loadExtension('build'); + if (buildExtension) { + var resources = buildExtension.getResourceDirectories(); + if (resources) { + var prefix = '/modules/' + module.name; + resources.forEach(function(resource) { + simulatorApp.use( + path.join(prefix, resource.target), + express.static(resource.src) + ); + }); + } + } + var extension = module.loadExtension('debugger'); if (!extension || !extension.getMiddleware) { return; } + try { var routes = extension.getMiddleware(require('../api'), app); if (!routes) { return; } diff --git a/src/serve/static/Simulator.js b/src/serve/static/Simulator.js index e0f12d0c..ea2b08d7 100644 --- a/src/serve/static/Simulator.js +++ b/src/serve/static/Simulator.js @@ -75,7 +75,6 @@ exports = Class(function () { }) ); } - // else { // get or update a simulator port with the following options tasks.push( @@ -102,14 +101,13 @@ exports = Class(function () { console.error(err); }) ); -// } // Some final clean up var promise = Promise.all(tasks); if (softReload) { promise = promise.then(ui.continueLoad.bind(ui)); } - prommise = promise.then(function() { setTimeout(function() { ui.setBuilding(false); }, 200); }); + prommise = promise.then(function() { ui.setBuilding(false); }); return promise.nodeify(cb); }; From b90f1315c4a0678cd3be1d26501bc6a8626d5006 Mon Sep 17 00:00:00 2001 From: yofreke Date: Fri, 24 Jul 2015 01:35:28 -0700 Subject: [PATCH 05/11] move app routing into custom handler, clean up routes after 15 minutes --- src/serve/appRoutes.js | 57 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/src/serve/appRoutes.js b/src/serve/appRoutes.js index 51b63837..d8e3150c 100644 --- a/src/serve/appRoutes.js +++ b/src/serve/appRoutes.js @@ -70,13 +70,13 @@ exports.addToAPI = function (opts, api) { return mountAppFromRequest(opts) .then(function (mountInfo) { var buildOpts = { - target: opts.target, - scheme: opts.scheme, - simulated: true, - simulateDeviceId: opts.deviceId, - simulateDeviceType: opts.deviceType, - output: mountInfo.buildPath - }; + target: opts.target, + scheme: opts.scheme, + simulated: true, + simulateDeviceId: opts.deviceId, + simulateDeviceType: opts.deviceType, + output: mountInfo.buildPath + }; return buildQueue .add(mountInfo.appPath, buildOpts) @@ -133,14 +133,27 @@ exports.addToAPI = function (opts, api) { }); } + // Promises that resolve with mount info once ready var _mountedApps = {}; + // Map of appId -> app + var _availableSimulatorApps = {}; + + baseApp.use('/apps/:appId', function(req, res, next) { + var app = _availableSimulatorApps[req.params.appId]; + if (app) { + app(req, res, next); + } else { + next(); + } + }); + function mountApp(appPath, buildPath) { + var routeId = generateRouteId(appPath); + if (!_mountedApps[appPath]) { _mountedApps[appPath] = apps.get(appPath) .then(function mountExtensions(app) { - var routeId = generateRouteId(appPath); var simulatorApp = express(); - baseApp.use('/apps/' + routeId, simulatorApp); // Special case src directories simulatorApp.use( @@ -213,6 +226,8 @@ exports.addToAPI = function (opts, api) { baseModules.forEach(function (module) { loadExtension(module); }); + // Add to available routes, return info + _availableSimulatorApps[routeId] = simulatorApp; return { id: routeId, url: '/apps/' + routeId + '/', @@ -224,7 +239,29 @@ exports.addToAPI = function (opts, api) { }); } - return Promise.resolve(_mountedApps[appPath]); + // Remove and clean up the routes after a bit of inactivity + var mountedApp = _mountedApps[appPath]; + // remove the old timeout + if (mountedApp.cleanupTimeout) { + clearTimeout(mountedApp.cleanupTimeout); + } + mountedApp.cleanupTimeout = setTimeout( + unmountApp.bind(null, appPath, routeId), + 15 * 60 * 1000 + ); + + return Promise.resolve(mountedApp); + } + + function unmountApp(appPath, routeId) { + logger.info('Shutting down route for: ' + appPath + ' (' + routeId + ')'); + + // Remove watchers + // .... + + // Remove app routes + delete _availableSimulatorApps[routeId]; + delete _mountedApps[appPath]; } // tracks used route uuids From 1b4e977859cf65c44a9d87f02a112cbe6e12caf3 Mon Sep 17 00:00:00 2001 From: yofreke Date: Fri, 24 Jul 2015 18:05:00 -0700 Subject: [PATCH 06/11] added socket support (for predictive rebuilds) --- package.json | 1 + src/serve/appRoutes.js | 70 +++++++++++++++++++- src/serve/index.js | 4 -- src/serve/static/Simulator.js | 81 +++++++++++++++++++----- src/serve/static/index.html | 1 + src/serve/static/util/socket.io-1.3.5.js | 3 + 6 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 src/serve/static/util/socket.io-1.3.5.js diff --git a/package.json b/package.json index 95ecb635..a332fe13 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "bluebird": "^2.9.24", "body-parser": "1.x", "chalk": "^1.0.0", + "chokidar": "^1.0.5", "compression": "^1.4.3", "express": "4.x", "ff": "^0.2.1", diff --git a/src/serve/appRoutes.js b/src/serve/appRoutes.js index d8e3150c..ed5cc7ee 100644 --- a/src/serve/appRoutes.js +++ b/src/serve/appRoutes.js @@ -13,11 +13,14 @@ var jvmtools = require('../jvmtools'); var logging = require('../util/logging'); var buildQueue = require('./buildQueue'); +var chokidar = require('chokidar'); + var logger = logging.get('routes'); var HOME = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; +var ROUTE_INACTIVE_TIME_LIMIT = 30 * 60 * 1000; exports.addToAPI = function (opts, api) { @@ -53,6 +56,7 @@ exports.addToAPI = function (opts, api) { res.json(mountInfo); }) .catch(function (e) { + logger.error('Error mounting app', e.stack); res.status(500).send({ message: e.message, stack: e.stack @@ -137,6 +141,15 @@ exports.addToAPI = function (opts, api) { var _mountedApps = {}; // Map of appId -> app var _availableSimulatorApps = {}; + var getAppByPath = function(appPath) { + for (var appId in _availableSimulatorApps) { + var app = _availableSimulatorApps[appId]; + if (app.appPath === appPath) { + return app; + } + } + return null; + }; baseApp.use('/apps/:appId', function(req, res, next) { var app = _availableSimulatorApps[req.params.appId]; @@ -147,6 +160,23 @@ exports.addToAPI = function (opts, api) { } }); + baseApp.io.on('connection', function(socket) { + logger.info('socket connected'); + + socket.on('watch', function(appPath) { + // Add this socket to the app + var app = getAppByPath(appPath); + if (app) { + app.sockets.push(socket); + } + }); + + socket.on('disconnect', function() { + logger.info('socket disconnect'); + }); + + }); + function mountApp(appPath, buildPath) { var routeId = generateRouteId(appPath); @@ -226,6 +256,33 @@ exports.addToAPI = function (opts, api) { baseModules.forEach(function (module) { loadExtension(module); }); + // Add socket connection + simulatorApp.sockets = []; + simulatorApp.socketEmit = function(name, data) { + simulatorApp.sockets.forEach(function(socket) { + socket.emit(name, data); + }); + }; + + // Everything is set, add some file watchers + simulatorApp.watchers = []; + simulatorApp.watchers.push( + chokidar.watch( + [ + path.join(appPath, 'manifest.json'), + path.join(appPath, 'src'), + path.join(appPath, 'resources') + ], + { recursive: true, followSymLinks: false, persistent: true, ignoreInitial: true } + ).on('all', function(event, path) { + logger.info(routeId + ': changed ' + path); + simulatorApp.socketEmit('watch:changed', path); + }) + ); + + // Meta data + simulatorApp.appPath = appPath; + // Add to available routes, return info _availableSimulatorApps[routeId] = simulatorApp; return { @@ -247,7 +304,7 @@ exports.addToAPI = function (opts, api) { } mountedApp.cleanupTimeout = setTimeout( unmountApp.bind(null, appPath, routeId), - 15 * 60 * 1000 + ROUTE_INACTIVE_TIME_LIMIT ); return Promise.resolve(mountedApp); @@ -256,8 +313,17 @@ exports.addToAPI = function (opts, api) { function unmountApp(appPath, routeId) { logger.info('Shutting down route for: ' + appPath + ' (' + routeId + ')'); + var app = _availableSimulatorApps[routeId]; + + // Close sockets + app.sockets.forEach(function(socket) { + socket.disconnect(); + }); + // Remove watchers - // .... + app.watchers.forEach(function(watcher) { + watcher.close(); + }); // Remove app routes delete _availableSimulatorApps[routeId]; diff --git a/src/serve/index.js b/src/serve/index.js index 2764aecf..8ec09ccc 100644 --- a/src/serve/index.js +++ b/src/serve/index.js @@ -26,15 +26,11 @@ var Z_BEST_COMPRESSION = 9; exports.serveWeb = function (opts, cb) { var port = opts.port; - // common.track("BasilServe"); var app = express(); var server = http.Server(app); app.io = require('socket.io')(server); - // var deviceManager = require('./deviceManager').get(); - // deviceManager.init(app.io); - app.use(compression({level: Z_BEST_COMPRESSION})); app.use(function noCacheControl(req, res, next) { diff --git a/src/serve/static/Simulator.js b/src/serve/static/Simulator.js index ea2b08d7..577122b2 100644 --- a/src/serve/static/Simulator.js +++ b/src/serve/static/Simulator.js @@ -33,11 +33,41 @@ exports = Class(function () { // DOM simulator this._ui = new ui.Chrome(this); + this._hasSocket = false; + this.socket = this._makeSocket(); + + /** @type {Promise} */ + this._requestSimulatePromise = null; + this._modules = {}; this.loadModules(opts.modules); this.rebuild(); }; + this._makeSocket = function() { + var socket = new io(); + + socket.on('connect', function() { + this._hasSocket = true; + + socket.emit('watch', this._app); + + // Do this right off the bat to make sure that the routes are all set up right + this._requestSimulate(true, true); + }.bind(this)); + + socket.on('disconnect', function() { + this._hasSocket = false; + }.bind(this)); + + socket.on('watch:changed', function(data) { + // reload + this._requestSimulate(true, true); + }.bind(this)); + + return socket; + }; + this.getUI = function () { return this._ui; }; @@ -61,24 +91,16 @@ exports = Class(function () { }, this); }; - this.rebuild = function (cb, softReload) { - var ui = this._ui; - var tasks = []; - - ui.setBuilding(true); - - if (softReload) { - tasks.push(ui.softReload() - .then(ui.refresh.bind(ui)) - .catch(function (e) { - logger.log("Error with soft reload", e); - }) - ); + /** Make a request to /simulate and return a promise */ + this._requestSimulate = function(softReload, ignoreSocket) { + if (this._hasSocket && !ignoreSocket) { + // Use the socket connection to make a simulate request as soon as a change occurs + return this._requestSimulatePromise || Promise.resolve(); } - // get or update a simulator port with the following options - tasks.push( - util.ajax.get({ + // Is there an existing promise or do we need a new one + if (!this._requestSimulatePromise) { + this._requestSimulatePromise = util.ajax.get({ url: '/api/simulate/', query: { app: this._app, @@ -100,7 +122,32 @@ exports = Class(function () { logger.error('Unable to simulate', this._app); console.error(err); }) - ); + .finally(function() { + // Clear the promise so that we run it next time + this._requestSimulatePromise = null; + }); + } + + return this._requestSimulatePromise; + }; + + this.rebuild = function (cb, softReload) { + var ui = this._ui; + var tasks = []; + + ui.setBuilding(true); + + if (softReload) { + tasks.push(ui.softReload() + .then(ui.refresh.bind(ui)) + .catch(function (e) { + logger.log("Error with soft reload", e); + }) + ); + } + + // get or update a simulator port with the following options + tasks.push(this._requestSimulate(softReload)); // Some final clean up var promise = Promise.all(tasks); diff --git a/src/serve/static/index.html b/src/serve/static/index.html index 7b67cc63..95802279 100755 --- a/src/serve/static/index.html +++ b/src/serve/static/index.html @@ -19,6 +19,7 @@ + Game Closure SDK diff --git a/src/serve/static/util/socket.io-1.3.5.js b/src/serve/static/util/socket.io-1.3.5.js new file mode 100644 index 00000000..1d1ad7f9 --- /dev/null +++ b/src/serve/static/util/socket.io-1.3.5.js @@ -0,0 +1,3 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.io=e()}}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0&&!this.encoding){var pack=this.packetBuffer.shift();this.packet(pack)}};Manager.prototype.cleanup=function(){var sub;while(sub=this.subs.shift())sub.destroy();this.packetBuffer=[];this.encoding=false;this.decoder.destroy()};Manager.prototype.close=Manager.prototype.disconnect=function(){this.skipReconnect=true;this.backoff.reset();this.readyState="closed";this.engine&&this.engine.close()};Manager.prototype.onclose=function(reason){debug("close");this.cleanup();this.backoff.reset();this.readyState="closed";this.emit("close",reason);if(this._reconnection&&!this.skipReconnect){this.reconnect()}};Manager.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var self=this;if(this.backoff.attempts>=this._reconnectionAttempts){debug("reconnect failed");this.backoff.reset();this.emitAll("reconnect_failed");this.reconnecting=false}else{var delay=this.backoff.duration();debug("will wait %dms before reconnect attempt",delay);this.reconnecting=true;var timer=setTimeout(function(){if(self.skipReconnect)return;debug("attempting reconnect");self.emitAll("reconnect_attempt",self.backoff.attempts);self.emitAll("reconnecting",self.backoff.attempts);if(self.skipReconnect)return;self.open(function(err){if(err){debug("reconnect attempt error");self.reconnecting=false;self.reconnect();self.emitAll("reconnect_error",err.data)}else{debug("reconnect success");self.onreconnect()}})},delay);this.subs.push({destroy:function(){clearTimeout(timer)}})}};Manager.prototype.onreconnect=function(){var attempt=this.backoff.attempts;this.reconnecting=false;this.backoff.reset();this.updateSocketIds();this.emitAll("reconnect",attempt)}},{"./on":4,"./socket":5,"./url":6,backo2:7,"component-bind":8,"component-emitter":9,debug:10,"engine.io-client":11,indexof:42,"object-component":43,"socket.io-parser":46}],4:[function(_dereq_,module,exports){module.exports=on;function on(obj,ev,fn){obj.on(ev,fn);return{destroy:function(){obj.removeListener(ev,fn)}}}},{}],5:[function(_dereq_,module,exports){var parser=_dereq_("socket.io-parser");var Emitter=_dereq_("component-emitter");var toArray=_dereq_("to-array");var on=_dereq_("./on");var bind=_dereq_("component-bind");var debug=_dereq_("debug")("socket.io-client:socket");var hasBin=_dereq_("has-binary");module.exports=exports=Socket;var events={connect:1,connect_error:1,connect_timeout:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1};var emit=Emitter.prototype.emit;function Socket(io,nsp){this.io=io;this.nsp=nsp;this.json=this;this.ids=0;this.acks={};if(this.io.autoConnect)this.open();this.receiveBuffer=[];this.sendBuffer=[];this.connected=false;this.disconnected=true}Emitter(Socket.prototype);Socket.prototype.subEvents=function(){if(this.subs)return;var io=this.io;this.subs=[on(io,"open",bind(this,"onopen")),on(io,"packet",bind(this,"onpacket")),on(io,"close",bind(this,"onclose"))]};Socket.prototype.open=Socket.prototype.connect=function(){if(this.connected)return this;this.subEvents();this.io.open();if("open"==this.io.readyState)this.onopen();return this};Socket.prototype.send=function(){var args=toArray(arguments);args.unshift("message");this.emit.apply(this,args);return this};Socket.prototype.emit=function(ev){if(events.hasOwnProperty(ev)){emit.apply(this,arguments);return this}var args=toArray(arguments);var parserType=parser.EVENT;if(hasBin(args)){parserType=parser.BINARY_EVENT}var packet={type:parserType,data:args};if("function"==typeof args[args.length-1]){debug("emitting packet with ack id %d",this.ids);this.acks[this.ids]=args.pop();packet.id=this.ids++}if(this.connected){this.packet(packet)}else{this.sendBuffer.push(packet)}return this};Socket.prototype.packet=function(packet){packet.nsp=this.nsp;this.io.packet(packet)};Socket.prototype.onopen=function(){debug("transport is open - connecting");if("/"!=this.nsp){this.packet({type:parser.CONNECT})}};Socket.prototype.onclose=function(reason){debug("close (%s)",reason);this.connected=false;this.disconnected=true;delete this.id;this.emit("disconnect",reason)};Socket.prototype.onpacket=function(packet){if(packet.nsp!=this.nsp)return;switch(packet.type){case parser.CONNECT:this.onconnect();break;case parser.EVENT:this.onevent(packet);break;case parser.BINARY_EVENT:this.onevent(packet);break;case parser.ACK:this.onack(packet);break;case parser.BINARY_ACK:this.onack(packet);break;case parser.DISCONNECT:this.ondisconnect();break;case parser.ERROR:this.emit("error",packet.data);break}};Socket.prototype.onevent=function(packet){var args=packet.data||[];debug("emitting event %j",args);if(null!=packet.id){debug("attaching ack callback to event");args.push(this.ack(packet.id))}if(this.connected){emit.apply(this,args)}else{this.receiveBuffer.push(args)}};Socket.prototype.ack=function(id){var self=this;var sent=false;return function(){if(sent)return;sent=true;var args=toArray(arguments);debug("sending ack %j",args);var type=hasBin(args)?parser.BINARY_ACK:parser.ACK;self.packet({type:type,id:id,data:args})}};Socket.prototype.onack=function(packet){debug("calling ack %s with %j",packet.id,packet.data);var fn=this.acks[packet.id];fn.apply(this,packet.data);delete this.acks[packet.id]};Socket.prototype.onconnect=function(){this.connected=true;this.disconnected=false;this.emit("connect");this.emitBuffered()};Socket.prototype.emitBuffered=function(){var i;for(i=0;i0&&opts.jitter<=1?opts.jitter:0;this.attempts=0}Backoff.prototype.duration=function(){var ms=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var rand=Math.random();var deviation=Math.floor(rand*this.jitter*ms);ms=(Math.floor(rand*10)&1)==0?ms-deviation:ms+deviation}return Math.min(ms,this.max)|0};Backoff.prototype.reset=function(){this.attempts=0};Backoff.prototype.setMin=function(min){this.ms=min};Backoff.prototype.setMax=function(max){this.max=max};Backoff.prototype.setJitter=function(jitter){this.jitter=jitter}},{}],8:[function(_dereq_,module,exports){var slice=[].slice;module.exports=function(obj,fn){if("string"==typeof fn)fn=obj[fn];if("function"!=typeof fn)throw new Error("bind() requires a function");var args=slice.call(arguments,2);return function(){return fn.apply(obj,args.concat(slice.call(arguments)))}}},{}],9:[function(_dereq_,module,exports){module.exports=Emitter;function Emitter(obj){if(obj)return mixin(obj)}function mixin(obj){for(var key in Emitter.prototype){obj[key]=Emitter.prototype[key]}return obj}Emitter.prototype.on=Emitter.prototype.addEventListener=function(event,fn){this._callbacks=this._callbacks||{};(this._callbacks[event]=this._callbacks[event]||[]).push(fn);return this};Emitter.prototype.once=function(event,fn){var self=this;this._callbacks=this._callbacks||{};function on(){self.off(event,on);fn.apply(this,arguments)}on.fn=fn;this.on(event,on);return this};Emitter.prototype.off=Emitter.prototype.removeListener=Emitter.prototype.removeAllListeners=Emitter.prototype.removeEventListener=function(event,fn){this._callbacks=this._callbacks||{};if(0==arguments.length){this._callbacks={};return this}var callbacks=this._callbacks[event];if(!callbacks)return this;if(1==arguments.length){delete this._callbacks[event];return this}var cb;for(var i=0;i=hour)return(ms/hour).toFixed(1)+"h";if(ms>=min)return(ms/min).toFixed(1)+"m";if(ms>=sec)return(ms/sec|0)+"s";return ms+"ms"};debug.enabled=function(name){for(var i=0,len=debug.skips.length;i';iframe=document.createElement(html)}catch(e){iframe=document.createElement("iframe");iframe.name=self.iframeId;iframe.src="javascript:0"}iframe.id=self.iframeId;self.form.appendChild(iframe);self.iframe=iframe}initIframe();data=data.replace(rEscapedNewline,"\\\n");this.area.value=data.replace(rNewline,"\\n");try{this.form.submit()}catch(e){}if(this.iframe.attachEvent){this.iframe.onreadystatechange=function(){if(self.iframe.readyState=="complete"){complete()}}}else{this.iframe.onload=complete}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./polling":18,"component-inherit":21}],17:[function(_dereq_,module,exports){(function(global){var XMLHttpRequest=_dereq_("xmlhttprequest");var Polling=_dereq_("./polling");var Emitter=_dereq_("component-emitter");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:polling-xhr");module.exports=XHR;module.exports.Request=Request;function empty(){}function XHR(opts){Polling.call(this,opts);if(global.location){var isSSL="https:"==location.protocol;var port=location.port;if(!port){port=isSSL?443:80}this.xd=opts.hostname!=global.location.hostname||port!=opts.port;this.xs=opts.secure!=isSSL}}inherit(XHR,Polling);XHR.prototype.supportsBinary=true;XHR.prototype.request=function(opts){opts=opts||{};opts.uri=this.uri();opts.xd=this.xd;opts.xs=this.xs;opts.agent=this.agent||false;opts.supportsBinary=this.supportsBinary;opts.enablesXDR=this.enablesXDR;opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;return new Request(opts)};XHR.prototype.doWrite=function(data,fn){var isBinary=typeof data!=="string"&&data!==undefined;var req=this.request({method:"POST",data:data,isBinary:isBinary});var self=this;req.on("success",fn);req.on("error",function(err){self.onError("xhr post error",err)});this.sendXhr=req};XHR.prototype.doPoll=function(){debug("xhr poll");var req=this.request();var self=this;req.on("data",function(data){self.onData(data)});req.on("error",function(err){self.onError("xhr poll error",err)});this.pollXhr=req};function Request(opts){this.method=opts.method||"GET";this.uri=opts.uri;this.xd=!!opts.xd;this.xs=!!opts.xs;this.async=false!==opts.async;this.data=undefined!=opts.data?opts.data:null;this.agent=opts.agent;this.isBinary=opts.isBinary;this.supportsBinary=opts.supportsBinary;this.enablesXDR=opts.enablesXDR;this.pfx=opts.pfx;this.key=opts.key;this.passphrase=opts.passphrase;this.cert=opts.cert;this.ca=opts.ca;this.ciphers=opts.ciphers;this.rejectUnauthorized=opts.rejectUnauthorized;this.create()}Emitter(Request.prototype);Request.prototype.create=function(){var opts={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;var xhr=this.xhr=new XMLHttpRequest(opts);var self=this;try{debug("xhr open %s: %s",this.method,this.uri);xhr.open(this.method,this.uri,this.async);if(this.supportsBinary){xhr.responseType="arraybuffer"}if("POST"==this.method){try{if(this.isBinary){xhr.setRequestHeader("Content-type","application/octet-stream")}else{xhr.setRequestHeader("Content-type","text/plain;charset=UTF-8")}}catch(e){}}if("withCredentials"in xhr){xhr.withCredentials=true}if(this.hasXDR()){xhr.onload=function(){self.onLoad()};xhr.onerror=function(){self.onError(xhr.responseText)}}else{xhr.onreadystatechange=function(){if(4!=xhr.readyState)return;if(200==xhr.status||1223==xhr.status){self.onLoad()}else{setTimeout(function(){self.onError(xhr.status)},0)}}}debug("xhr data %s",this.data);xhr.send(this.data)}catch(e){setTimeout(function(){self.onError(e)},0);return}if(global.document){this.index=Request.requestsCount++;Request.requests[this.index]=this}};Request.prototype.onSuccess=function(){this.emit("success");this.cleanup()};Request.prototype.onData=function(data){this.emit("data",data);this.onSuccess()};Request.prototype.onError=function(err){this.emit("error",err);this.cleanup(true)};Request.prototype.cleanup=function(fromError){if("undefined"==typeof this.xhr||null===this.xhr){return}if(this.hasXDR()){this.xhr.onload=this.xhr.onerror=empty}else{this.xhr.onreadystatechange=empty}if(fromError){try{this.xhr.abort()}catch(e){}}if(global.document){delete Request.requests[this.index]}this.xhr=null};Request.prototype.onLoad=function(){var data;try{var contentType;try{contentType=this.xhr.getResponseHeader("Content-Type").split(";")[0]}catch(e){}if(contentType==="application/octet-stream"){data=this.xhr.response}else{if(!this.supportsBinary){data=this.xhr.responseText}else{data="ok"}}}catch(e){this.onError(e)}if(null!=data){this.onData(data)}};Request.prototype.hasXDR=function(){return"undefined"!==typeof global.XDomainRequest&&!this.xs&&this.enablesXDR};Request.prototype.abort=function(){this.cleanup()};if(global.document){Request.requestsCount=0;Request.requests={};if(global.attachEvent){global.attachEvent("onunload",unloadHandler)}else if(global.addEventListener){global.addEventListener("beforeunload",unloadHandler,false)}}function unloadHandler(){for(var i in Request.requests){if(Request.requests.hasOwnProperty(i)){Request.requests[i].abort()}}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./polling":18,"component-emitter":9,"component-inherit":21,debug:22,xmlhttprequest:20}],18:[function(_dereq_,module,exports){var Transport=_dereq_("../transport");var parseqs=_dereq_("parseqs");var parser=_dereq_("engine.io-parser");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:polling");module.exports=Polling;var hasXHR2=function(){var XMLHttpRequest=_dereq_("xmlhttprequest");var xhr=new XMLHttpRequest({xdomain:false});return null!=xhr.responseType}();function Polling(opts){var forceBase64=opts&&opts.forceBase64;if(!hasXHR2||forceBase64){this.supportsBinary=false}Transport.call(this,opts)}inherit(Polling,Transport);Polling.prototype.name="polling";Polling.prototype.doOpen=function(){this.poll()};Polling.prototype.pause=function(onPause){var pending=0;var self=this;this.readyState="pausing";function pause(){debug("paused");self.readyState="paused";onPause()}if(this.polling||!this.writable){var total=0;if(this.polling){debug("we are currently polling - waiting to pause");total++;this.once("pollComplete",function(){debug("pre-pause polling complete");--total||pause()})}if(!this.writable){debug("we are currently writing - waiting to pause");total++;this.once("drain",function(){debug("pre-pause writing complete");--total||pause()})}}else{pause()}};Polling.prototype.poll=function(){debug("polling");this.polling=true;this.doPoll();this.emit("poll")};Polling.prototype.onData=function(data){var self=this;debug("polling got data %s",data);var callback=function(packet,index,total){if("opening"==self.readyState){self.onOpen()}if("close"==packet.type){self.onClose();return false}self.onPacket(packet)};parser.decodePayload(data,this.socket.binaryType,callback);if("closed"!=this.readyState){this.polling=false;this.emit("pollComplete");if("open"==this.readyState){this.poll()}else{debug('ignoring poll - transport state "%s"',this.readyState)}}};Polling.prototype.doClose=function(){var self=this;function close(){debug("writing close packet");self.write([{type:"close"}])}if("open"==this.readyState){debug("transport open - closing");close()}else{debug("transport not open - deferring close");this.once("open",close)}};Polling.prototype.write=function(packets){var self=this;this.writable=false;var callbackfn=function(){self.writable=true;self.emit("drain")};var self=this;parser.encodePayload(packets,this.supportsBinary,function(data){self.doWrite(data,callbackfn)})};Polling.prototype.uri=function(){var query=this.query||{};var schema=this.secure?"https":"http";var port="";if(false!==this.timestampRequests){query[this.timestampParam]=+new Date+"-"+Transport.timestamps++}if(!this.supportsBinary&&!query.sid){query.b64=1}query=parseqs.encode(query);if(this.port&&("https"==schema&&this.port!=443||"http"==schema&&this.port!=80)){port=":"+this.port}if(query.length){query="?"+query}return schema+"://"+this.hostname+port+this.path+query}},{"../transport":14,"component-inherit":21,debug:22,"engine.io-parser":25,parseqs:35,xmlhttprequest:20}],19:[function(_dereq_,module,exports){var Transport=_dereq_("../transport");var parser=_dereq_("engine.io-parser");var parseqs=_dereq_("parseqs");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:websocket");var WebSocket=_dereq_("ws");module.exports=WS;function WS(opts){var forceBase64=opts&&opts.forceBase64;if(forceBase64){this.supportsBinary=false}Transport.call(this,opts)}inherit(WS,Transport);WS.prototype.name="websocket";WS.prototype.supportsBinary=true;WS.prototype.doOpen=function(){if(!this.check()){return}var self=this;var uri=this.uri();var protocols=void 0;var opts={agent:this.agent};opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;this.ws=new WebSocket(uri,protocols,opts);if(this.ws.binaryType===undefined){this.supportsBinary=false}this.ws.binaryType="arraybuffer";this.addEventListeners()};WS.prototype.addEventListeners=function(){var self=this;this.ws.onopen=function(){self.onOpen()};this.ws.onclose=function(){self.onClose()};this.ws.onmessage=function(ev){self.onData(ev.data)};this.ws.onerror=function(e){self.onError("websocket error",e)}};if("undefined"!=typeof navigator&&/iPad|iPhone|iPod/i.test(navigator.userAgent)){WS.prototype.onData=function(data){var self=this;setTimeout(function(){Transport.prototype.onData.call(self,data)},0)}}WS.prototype.write=function(packets){var self=this;this.writable=false;for(var i=0,l=packets.length;i=31}exports.formatters.j=function(v){return JSON.stringify(v)};function formatArgs(){var args=arguments;var useColors=this.useColors;args[0]=(useColors?"%c":"")+this.namespace+(useColors?" %c":" ")+args[0]+(useColors?"%c ":" ")+"+"+exports.humanize(this.diff);if(!useColors)return args;var c="color: "+this.color;args=[args[0],c,"color: inherit"].concat(Array.prototype.slice.call(args,1));var index=0;var lastC=0;args[0].replace(/%[a-z%]/g,function(match){if("%"===match)return;index++;if("%c"===match){lastC=index}});args.splice(lastC,0,c);return args}function log(){return"object"==typeof console&&"function"==typeof console.log&&Function.prototype.apply.call(console.log,console,arguments)}function save(namespaces){try{if(null==namespaces){localStorage.removeItem("debug")}else{localStorage.debug=namespaces}}catch(e){}}function load(){var r;try{r=localStorage.debug}catch(e){}return r}exports.enable(load())},{"./debug":23}],23:[function(_dereq_,module,exports){exports=module.exports=debug;exports.coerce=coerce;exports.disable=disable;exports.enable=enable;exports.enabled=enabled;exports.humanize=_dereq_("ms");exports.names=[];exports.skips=[];exports.formatters={};var prevColor=0;var prevTime;function selectColor(){return exports.colors[prevColor++%exports.colors.length]}function debug(namespace){function disabled(){}disabled.enabled=false;function enabled(){var self=enabled;var curr=+new Date;var ms=curr-(prevTime||curr);self.diff=ms;self.prev=prevTime;self.curr=curr;prevTime=curr;if(null==self.useColors)self.useColors=exports.useColors();if(null==self.color&&self.useColors)self.color=selectColor();var args=Array.prototype.slice.call(arguments);args[0]=exports.coerce(args[0]);if("string"!==typeof args[0]){args=["%o"].concat(args)}var index=0;args[0]=args[0].replace(/%([a-z%])/g,function(match,format){if(match==="%")return match;index++;var formatter=exports.formatters[format];if("function"===typeof formatter){var val=args[index];match=formatter.call(self,val);args.splice(index,1);index--}return match});if("function"===typeof exports.formatArgs){args=exports.formatArgs.apply(self,args)}var logFn=enabled.log||exports.log||console.log.bind(console);logFn.apply(self,args)}enabled.enabled=true;var fn=exports.enabled(namespace)?enabled:disabled;fn.namespace=namespace;return fn}function enable(namespaces){exports.save(namespaces);var split=(namespaces||"").split(/[\s,]+/);var len=split.length;for(var i=0;i=d)return Math.round(ms/d)+"d";if(ms>=h)return Math.round(ms/h)+"h";if(ms>=m)return Math.round(ms/m)+"m";if(ms>=s)return Math.round(ms/s)+"s";return ms+"ms"}function long(ms){return plural(ms,d,"day")||plural(ms,h,"hour")||plural(ms,m,"minute")||plural(ms,s,"second")||ms+" ms"}function plural(ms,n,name){if(ms1){return{type:packetslist[type],data:data.substring(1)}}else{return{type:packetslist[type]}}}var asArray=new Uint8Array(data);var type=asArray[0];var rest=sliceBuffer(data,1);if(Blob&&binaryType==="blob"){rest=new Blob([rest])}return{type:packetslist[type],data:rest}};exports.decodeBase64Packet=function(msg,binaryType){var type=packetslist[msg.charAt(0)];if(!global.ArrayBuffer){return{type:type,data:{base64:true,data:msg.substr(1)}}}var data=base64encoder.decode(msg.substr(1));if(binaryType==="blob"&&Blob){data=new Blob([data])}return{type:type,data:data}};exports.encodePayload=function(packets,supportsBinary,callback){if(typeof supportsBinary=="function"){callback=supportsBinary;supportsBinary=null}var isBinary=hasBinary(packets);if(supportsBinary&&isBinary){if(Blob&&!dontSendBlobs){return exports.encodePayloadAsBlob(packets,callback)}return exports.encodePayloadAsArrayBuffer(packets,callback)}if(!packets.length){return callback("0:")}function setLengthHeader(message){return message.length+":"+message}function encodeOne(packet,doneCallback){exports.encodePacket(packet,!isBinary?false:supportsBinary,true,function(message){doneCallback(null,setLengthHeader(message))})}map(packets,encodeOne,function(err,results){return callback(results.join(""))})};function map(ary,each,done){var result=new Array(ary.length);var next=after(ary.length,done);var eachWithIndex=function(i,el,cb){each(el,function(error,msg){result[i]=msg;cb(error,result)})};for(var i=0;i0){var tailArray=new Uint8Array(bufferTail);var isString=tailArray[0]===0;var msgLength="";for(var i=1;;i++){if(tailArray[i]==255)break;if(msgLength.length>310){numberTooLong=true;break}msgLength+=tailArray[i]}if(numberTooLong)return callback(err,0,1);bufferTail=sliceBuffer(bufferTail,2+msgLength.length);msgLength=parseInt(msgLength);var msg=sliceBuffer(bufferTail,0,msgLength);if(isString){try{msg=String.fromCharCode.apply(null,new Uint8Array(msg))}catch(e){var typed=new Uint8Array(msg);msg="";for(var i=0;ibytes){end=bytes}if(start>=bytes||start>=end||bytes===0){return new ArrayBuffer(0)}var abv=new Uint8Array(arraybuffer);var result=new Uint8Array(end-start);for(var i=start,ii=0;i>2];base64+=chars[(bytes[i]&3)<<4|bytes[i+1]>>4];base64+=chars[(bytes[i+1]&15)<<2|bytes[i+2]>>6];base64+=chars[bytes[i+2]&63]}if(len%3===2){base64=base64.substring(0,base64.length-1)+"="}else if(len%3===1){base64=base64.substring(0,base64.length-2)+"=="}return base64};exports.decode=function(base64){var bufferLength=base64.length*.75,len=base64.length,i,p=0,encoded1,encoded2,encoded3,encoded4;if(base64[base64.length-1]==="="){bufferLength--;if(base64[base64.length-2]==="="){bufferLength--}}var arraybuffer=new ArrayBuffer(bufferLength),bytes=new Uint8Array(arraybuffer);for(i=0;i>4;bytes[p++]=(encoded2&15)<<4|encoded3>>2;bytes[p++]=(encoded3&3)<<6|encoded4&63}return arraybuffer}})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")},{}],30:[function(_dereq_,module,exports){(function(global){var BlobBuilder=global.BlobBuilder||global.WebKitBlobBuilder||global.MSBlobBuilder||global.MozBlobBuilder;var blobSupported=function(){try{var b=new Blob(["hi"]);return b.size==2}catch(e){return false}}();var blobBuilderSupported=BlobBuilder&&BlobBuilder.prototype.append&&BlobBuilder.prototype.getBlob;function BlobBuilderConstructor(ary,options){options=options||{};var bb=new BlobBuilder;for(var i=0;i=55296&&value<=56319&&counter65535){value-=65536; +output+=stringFromCharCode(value>>>10&1023|55296);value=56320|value&1023}output+=stringFromCharCode(value)}return output}function createByte(codePoint,shift){return stringFromCharCode(codePoint>>shift&63|128)}function encodeCodePoint(codePoint){if((codePoint&4294967168)==0){return stringFromCharCode(codePoint)}var symbol="";if((codePoint&4294965248)==0){symbol=stringFromCharCode(codePoint>>6&31|192)}else if((codePoint&4294901760)==0){symbol=stringFromCharCode(codePoint>>12&15|224);symbol+=createByte(codePoint,6)}else if((codePoint&4292870144)==0){symbol=stringFromCharCode(codePoint>>18&7|240);symbol+=createByte(codePoint,12);symbol+=createByte(codePoint,6)}symbol+=stringFromCharCode(codePoint&63|128);return symbol}function utf8encode(string){var codePoints=ucs2decode(string);var length=codePoints.length;var index=-1;var codePoint;var byteString="";while(++index=byteCount){throw Error("Invalid byte index")}var continuationByte=byteArray[byteIndex]&255;byteIndex++;if((continuationByte&192)==128){return continuationByte&63}throw Error("Invalid continuation byte")}function decodeSymbol(){var byte1;var byte2;var byte3;var byte4;var codePoint;if(byteIndex>byteCount){throw Error("Invalid byte index")}if(byteIndex==byteCount){return false}byte1=byteArray[byteIndex]&255;byteIndex++;if((byte1&128)==0){return byte1}if((byte1&224)==192){var byte2=readContinuationByte();codePoint=(byte1&31)<<6|byte2;if(codePoint>=128){return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&240)==224){byte2=readContinuationByte();byte3=readContinuationByte();codePoint=(byte1&15)<<12|byte2<<6|byte3;if(codePoint>=2048){return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&248)==240){byte2=readContinuationByte();byte3=readContinuationByte();byte4=readContinuationByte();codePoint=(byte1&15)<<18|byte2<<12|byte3<<6|byte4;if(codePoint>=65536&&codePoint<=1114111){return codePoint}}throw Error("Invalid UTF-8 detected")}var byteArray;var byteCount;var byteIndex;function utf8decode(byteString){byteArray=ucs2decode(byteString);byteCount=byteArray.length;byteIndex=0;var codePoints=[];var tmp;while((tmp=decodeSymbol())!==false){codePoints.push(tmp)}return ucs2encode(codePoints)}var utf8={version:"2.0.0",encode:utf8encode,decode:utf8decode};if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){define(function(){return utf8})}else if(freeExports&&!freeExports.nodeType){if(freeModule){freeModule.exports=utf8}else{var object={};var hasOwnProperty=object.hasOwnProperty;for(var key in utf8){hasOwnProperty.call(utf8,key)&&(freeExports[key]=utf8[key])}}}else{root.utf8=utf8}})(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],34:[function(_dereq_,module,exports){(function(global){var rvalidchars=/^[\],:{}\s]*$/;var rvalidescape=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;var rvalidtokens=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;var rvalidbraces=/(?:^|:|,)(?:\s*\[)+/g;var rtrimLeft=/^\s+/;var rtrimRight=/\s+$/;module.exports=function parsejson(data){if("string"!=typeof data||!data){return null}data=data.replace(rtrimLeft,"").replace(rtrimRight,"");if(global.JSON&&JSON.parse){return JSON.parse(data)}if(rvalidchars.test(data.replace(rvalidescape,"@").replace(rvalidtokens,"]").replace(rvalidbraces,""))){return new Function("return "+data)()}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],35:[function(_dereq_,module,exports){exports.encode=function(obj){var str="";for(var i in obj){if(obj.hasOwnProperty(i)){if(str.length)str+="&";str+=encodeURIComponent(i)+"="+encodeURIComponent(obj[i])}}return str};exports.decode=function(qs){var qry={};var pairs=qs.split("&");for(var i=0,l=pairs.length;i1)))/4)-floor((year-1901+month)/100)+floor((year-1601+month)/400)}}if(!(isProperty={}.hasOwnProperty)){isProperty=function(property){var members={},constructor;if((members.__proto__=null,members.__proto__={toString:1},members).toString!=getClass){isProperty=function(property){var original=this.__proto__,result=property in(this.__proto__=null,this);this.__proto__=original;return result}}else{constructor=members.constructor;isProperty=function(property){var parent=(this.constructor||constructor).prototype;return property in this&&!(property in parent&&this[property]===parent[property])}}members=null;return isProperty.call(this,property)}}var PrimitiveTypes={"boolean":1,number:1,string:1,undefined:1};var isHostType=function(object,property){var type=typeof object[property];return type=="object"?!!object[property]:!PrimitiveTypes[type]};forEach=function(object,callback){var size=0,Properties,members,property;(Properties=function(){this.valueOf=0}).prototype.valueOf=0;members=new Properties;for(property in members){if(isProperty.call(members,property)){size++}}Properties=members=null;if(!size){members=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,length;var hasProperty=!isFunction&&typeof object.constructor!="function"&&isHostType(object,"hasOwnProperty")?object.hasOwnProperty:isProperty;for(property in object){if(!(isFunction&&property=="prototype")&&hasProperty.call(object,property)){callback(property)}}for(length=members.length;property=members[--length];hasProperty.call(object,property)&&callback(property));}}else if(size==2){forEach=function(object,callback){var members={},isFunction=getClass.call(object)==functionClass,property;for(property in object){if(!(isFunction&&property=="prototype")&&!isProperty.call(members,property)&&(members[property]=1)&&isProperty.call(object,property)){callback(property)}}}}else{forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,isConstructor;for(property in object){if(!(isFunction&&property=="prototype")&&isProperty.call(object,property)&&!(isConstructor=property==="constructor")){callback(property)}}if(isConstructor||isProperty.call(object,property="constructor")){callback(property)}}}return forEach(object,callback)};if(!has("json-stringify")){var Escapes={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var leadingZeroes="000000";var toPaddedString=function(width,value){return(leadingZeroes+(value||0)).slice(-width)};var unicodePrefix="\\u00";var quote=function(value){var result='"',index=0,length=value.length,isLarge=length>10&&charIndexBuggy,symbols;if(isLarge){symbols=value.split("")}for(;index-1/0&&value<1/0){if(getDay){date=floor(value/864e5);for(year=floor(date/365.2425)+1970-1;getDay(year+1,0)<=date;year++);for(month=floor((date-getDay(year,0))/30.42);getDay(year,month+1)<=date;month++);date=1+date-getDay(year,month);time=(value%864e5+864e5)%864e5;hours=floor(time/36e5)%24;minutes=floor(time/6e4)%60;seconds=floor(time/1e3)%60;milliseconds=time%1e3}else{year=value.getUTCFullYear();month=value.getUTCMonth();date=value.getUTCDate();hours=value.getUTCHours();minutes=value.getUTCMinutes();seconds=value.getUTCSeconds();milliseconds=value.getUTCMilliseconds()}value=(year<=0||year>=1e4?(year<0?"-":"+")+toPaddedString(6,year<0?-year:year):toPaddedString(4,year))+"-"+toPaddedString(2,month+1)+"-"+toPaddedString(2,date)+"T"+toPaddedString(2,hours)+":"+toPaddedString(2,minutes)+":"+toPaddedString(2,seconds)+"."+toPaddedString(3,milliseconds)+"Z"}else{value=null}}else if(typeof value.toJSON=="function"&&(className!=numberClass&&className!=stringClass&&className!=arrayClass||isProperty.call(value,"toJSON"))){value=value.toJSON(property)}}if(callback){value=callback.call(object,property,value)}if(value===null){return"null"}className=getClass.call(value);if(className==booleanClass){return""+value}else if(className==numberClass){return value>-1/0&&value<1/0?""+value:"null"}else if(className==stringClass){return quote(""+value)}if(typeof value=="object"){for(length=stack.length;length--;){if(stack[length]===value){throw TypeError()}}stack.push(value);results=[];prefix=indentation;indentation+=whitespace;if(className==arrayClass){for(index=0,length=value.length;index0){for(whitespace="",width>10&&(width=10);whitespace.length=48&&charCode<=57||charCode>=97&&charCode<=102||charCode>=65&&charCode<=70)){abort()}}value+=fromCharCode("0x"+source.slice(begin,Index));break;default:abort()}}else{if(charCode==34){break}charCode=source.charCodeAt(Index);begin=Index;while(charCode>=32&&charCode!=92&&charCode!=34){charCode=source.charCodeAt(++Index)}value+=source.slice(begin,Index)}}if(source.charCodeAt(Index)==34){Index++;return value}abort();default:begin=Index;if(charCode==45){isSigned=true;charCode=source.charCodeAt(++Index)}if(charCode>=48&&charCode<=57){if(charCode==48&&(charCode=source.charCodeAt(Index+1),charCode>=48&&charCode<=57)){abort()}isSigned=false;for(;Index=48&&charCode<=57);Index++);if(source.charCodeAt(Index)==46){position=++Index;for(;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}charCode=source.charCodeAt(Index);if(charCode==101||charCode==69){charCode=source.charCodeAt(++Index);if(charCode==43||charCode==45){Index++}for(position=Index;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}return+source.slice(begin,Index)}if(isSigned){abort()}if(source.slice(Index,Index+4)=="true"){Index+=4;return true}else if(source.slice(Index,Index+5)=="false"){Index+=5;return false}else if(source.slice(Index,Index+4)=="null"){Index+=4;return null}abort()}}return"$"};var get=function(value){var results,hasMembers;if(value=="$"){abort()}if(typeof value=="string"){if((charIndexBuggy?value.charAt(0):value[0])=="@"){return value.slice(1)}if(value=="["){results=[];for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="]"){break}if(hasMembers){if(value==","){value=lex();if(value=="]"){abort()}}else{abort()}}if(value==","){abort()}results.push(get(value))}return results}else if(value=="{"){results={};for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="}"){break}if(hasMembers){if(value==","){value=lex();if(value=="}"){abort()}}else{abort()}}if(value==","||typeof value!="string"||(charIndexBuggy?value.charAt(0):value[0])!="@"||lex()!=":"){abort()}results[value.slice(1)]=get(lex())}return results}abort()}return value};var update=function(source,property,callback){var element=walk(source,property,callback);if(element===undef){delete source[property]}else{source[property]=element}};var walk=function(source,property,callback){var value=source[property],length;if(typeof value=="object"&&value){if(getClass.call(value)==arrayClass){for(length=value.length;length--;){update(value,length,callback)}}else{forEach(value,function(property){update(value,property,callback)})}}return callback.call(source,property,value)};JSON3.parse=function(source,callback){var result,value;Index=0;Source=""+source;result=get(lex());if(lex()!="$"){abort()}Index=Source=null;return callback&&getClass.call(callback)==functionClass?walk((value={},value[""]=result,value),"",callback):result}}}if(isLoader){define(function(){return JSON3})}})(this)},{}],50:[function(_dereq_,module,exports){module.exports=toArray;function toArray(list,index){var array=[];index=index||0;for(var i=index||0;i Date: Fri, 31 Jul 2015 14:28:02 -0700 Subject: [PATCH 07/11] removed channel connection logs --- src/serve/static/util/Channel.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/serve/static/util/Channel.js b/src/serve/static/util/Channel.js index c23d28aa..3a2da216 100644 --- a/src/serve/static/util/Channel.js +++ b/src/serve/static/util/Channel.js @@ -93,16 +93,13 @@ exports = Class(lib.PubSub, function (supr) { switch (msg) { case 'connect': // complete the channel connection - console.log('Channel connected:', this._name); this._sendInternalMessage('connectConfirmed'); // fall-through case 'connectConfirmed': - console.log('Channel connection confirmed:', this._name); this._isConnected = true; this._emit('connect'); break; case 'disconnect': - console.log('Channel disconnect:', this._name); this._isConnected = false; this._emit('disconnect'); break; From d3c1290aeef9c347335e612c72c27bfa79fd0c8f Mon Sep 17 00:00:00 2001 From: yofreke Date: Wed, 5 Aug 2015 10:55:18 +0000 Subject: [PATCH 08/11] use the new callback instead of promise --- modules/devkit-simulator-client/client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/devkit-simulator-client/client.js b/modules/devkit-simulator-client/client.js index 54d12a72..beb84617 100644 --- a/modules/devkit-simulator-client/client.js +++ b/modules/devkit-simulator-client/client.js @@ -62,9 +62,9 @@ exports.onLaunch = function () { /** partialLoadContinue is to be used in conjunction with partialLoad */ channel.on('partialLoadContinue', function (data, req) { - var def = window._continueLoadDefer; - if (def) { - def.resolve(); + var cb = window._continueLoadCallback; + if (cb) { + cb(); } req.send(true); }); From 319215a50eb7705269a71a7f7c264d87849db232 Mon Sep 17 00:00:00 2001 From: Martin Hunt Date: Thu, 20 Aug 2015 02:12:40 -0700 Subject: [PATCH 09/11] add getResourceDirectories as a build hook - copied from devkit-core - build hooks now return all responses and which module they came from - build hooks now accept hooks returning promises rather than calling a callback - fix bug returning build result from buildQueue --- src/build/DirectoryBuilder.js | 41 ++++++++++++ src/build/index.js | 26 ++++++-- src/build/steps/buildHooks.js | 94 ++++++++++++++++++++++----- src/build/steps/executeTargetBuild.js | 1 - src/serve/appRoutes.js | 9 +-- src/serve/buildQueue.js | 2 +- 6 files changed, 146 insertions(+), 27 deletions(-) create mode 100644 src/build/DirectoryBuilder.js diff --git a/src/build/DirectoryBuilder.js b/src/build/DirectoryBuilder.js new file mode 100644 index 00000000..a574ed3b --- /dev/null +++ b/src/build/DirectoryBuilder.js @@ -0,0 +1,41 @@ +var path = require('path'); +var fs = require('fs'); +var logger = require('../util/logging').get('build-directories'); + +// class for representing a list of resource directories +function DirectoryBuilder(base) { + this._base = base; + this._directories = []; +} + +DirectoryBuilder.prototype.add = function (src, target) { + var directory; + if (arguments.length === 1) { + directory = { + src: path.join(this._base, src), + target: src + }; + } else { + directory = { + src: src, + target: target + }; + } + + if (fs.existsSync(directory.src)) { + this._directories.push(directory); + } else { + logger.warn('Directory does not exist, ignoring files from', + directory); + } +}; + +DirectoryBuilder.prototype.getPaths = function () { + return this._directories.map(function (dir) { return dir.src; }); +}; + +DirectoryBuilder.prototype.getDirectories = function () { + return this._directories; +}; + +module.exports = DirectoryBuilder; diff --git a/src/build/index.js b/src/build/index.js index ab9c9d34..480f188f 100644 --- a/src/build/index.js +++ b/src/build/index.js @@ -55,15 +55,31 @@ exports.build = function (appPath, argv, cb) { require('./steps/createDirectories').createDirectories(app, config, f()); }, function () { require('./steps/buildHooks').getDependencies(app, config, f()); - }, function (deps) { - // deps is an array of objects, merge them into one object and get all keys with false values - deps = merge.apply(this, deps); - var toRemove = Object.keys(deps).filter(function (name) { return deps[name] === false; }); - app.removeModules(toRemove); + }, function (res) { + var modules = res.reduce(function (modules, res) { + var toRemove = Object.keys(res.data) + .filter(function (name) { + return res.data[name] === false; + }); + + if (toRemove.length) { + logger.log('module', res.module.name, 'getDependencies:'); + toRemove.forEach(function (module) { + logger.log(' removing module', module); + }); + } + return modules.concat(toRemove); + }, []); + + app.removeModules(modules); }, function () { require('./steps/addDebugCode')(app, config, f()); }, function () { require('./steps/moduleConfig').getConfig(app, config, f()); + }, function () { + require('./steps/buildHooks').getResourceDirectories(app, config, f()); + }, function (directories) { + config.directories = directories; }, function () { require('./steps/buildHooks').onBeforeBuild(app, config, f()); }, function () { diff --git a/src/build/steps/buildHooks.js b/src/build/steps/buildHooks.js index 27d834a8..55fe1925 100644 --- a/src/build/steps/buildHooks.js +++ b/src/build/steps/buildHooks.js @@ -1,32 +1,94 @@ -var ff = require('ff'); +var path = require('path'); var Promise = require('bluebird'); var api = require('../../api'); +var DirectoryBuilder = require('../DirectoryBuilder'); +var fs = require('fs'); + +var readDir = Promise.promisify(fs.readdir); +var stat = Promise.promisify(fs.stat); exports.getDependencies = function (app, config, cb) { app.reloadModules(); // allows modules to disable other modules - executeHook('getDependencies', app, config, cb); -} + executeHook('getDependencies', app, config) + .nodeify(cb); +}; exports.onBeforeBuild = function (app, config, cb) { - executeHook('onBeforeBuild', app, config, cb); -} + executeHook('onBeforeBuild', app, config) + .nodeify(cb); +}; exports.onAfterBuild = function (app, config, cb) { - executeHook('onAfterBuild', app, config, cb); -} + executeHook('onAfterBuild', app, config) + .nodeify(cb); +}; + +exports.getResourceDirectories = function (app, config, cb) { + var builder = new DirectoryBuilder(app.paths.root); + builder.add('resources'); + executeHook('getResourceDirectories', app, config) + .map(function (res) { + var module = res.module; + var directories = res.data; + directories.forEach(function (directory) { + var target = path.join('modules', module.name, directory.target); + builder.add(directory.src, target); + }); + console.log(res); + }) + .then(function () { + // add any localized resource directories + return readDir(app.paths.root); + }) + .filter(function (filename) { + if (/^resources-/.test(filename)) { + return stat(path.join(app.paths.root, filename)).then(function (info) { + return info.isDirectory(); + }, function onStatFail() { + return false; + }); + } + + return false; + }) + .map(function (filename) { + builder.add(filename); + }) + .then(function () { + return builder.getDirectories(); + }) + .nodeify(cb); +}; -function executeHook(buildHook, app, config, cb) { +function executeHook(buildHook, app, config) { var modules = app.getModules(); - Promise.all(Object.keys(modules).map(function (moduleName) { - var module = modules[moduleName]; - var buildExtension = module.loadExtension('build'); - if (!buildExtension || !buildExtension[buildHook]) { - return; - } + return Promise.resolve(Object.keys(modules)) + .map(function (moduleName) { + var module = modules[moduleName]; + var buildExtension = module.loadExtension('build'); + if (!buildExtension || !buildExtension[buildHook]) { + return; + } + return new Promise(function (resolve, reject) { + var retVal = buildExtension[buildHook](api, app.toJSON(), config, function (err, res) { + if (err) { + reject(err); + } else { + resolve(res); + } + }); - return Promise.fromNode(buildExtension[buildHook].bind(buildExtension, api, app.toJSON(), config)); - })).nodeify(cb); + if (retVal) { resolve(retVal); } + }) + .then(function (data) { + return { + module: module, + data: data + }; + }); + }) + .filter(function (res) { return res; }); } diff --git a/src/build/steps/executeTargetBuild.js b/src/build/steps/executeTargetBuild.js index abbd9592..68f38796 100644 --- a/src/build/steps/executeTargetBuild.js +++ b/src/build/steps/executeTargetBuild.js @@ -12,7 +12,6 @@ exports.build = function (app, config, cb) { } moduleKeys.forEach(function (moduleName) { - console.log(moduleName) if (!buildModule) { var module = modules[moduleName]; buildModule = module.loadBuildTarget(config.target); diff --git a/src/serve/appRoutes.js b/src/serve/appRoutes.js index ed5cc7ee..bbed9843 100644 --- a/src/serve/appRoutes.js +++ b/src/serve/appRoutes.js @@ -52,7 +52,7 @@ exports.addToAPI = function (opts, api) { } buildFromRequest(opts) - .then(function (mountInfo) { + .spread(function (mountInfo, buildResult) { res.json(mountInfo); }) .catch(function (e) { @@ -82,9 +82,10 @@ exports.addToAPI = function (opts, api) { output: mountInfo.buildPath }; - return buildQueue - .add(mountInfo.appPath, buildOpts) - .return(mountInfo); + return buildQueue.add(mountInfo.appPath, buildOpts) + .then(function (buildResult) { + return [mountInfo, buildResult]; + }); }); } diff --git a/src/serve/buildQueue.js b/src/serve/buildQueue.js index 9477fff7..cbdd44f6 100644 --- a/src/serve/buildQueue.js +++ b/src/serve/buildQueue.js @@ -107,7 +107,7 @@ var BuildItem = Class(function () { this.error = msg.err; this._reject(this.error); } else { - this._resolve(); + this._resolve(msg.res); } checkQueue(); From c8ecbdfdf99afc313bf7c126152877db9111e86b Mon Sep 17 00:00:00 2001 From: yofreke Date: Thu, 20 Aug 2015 10:05:16 +0000 Subject: [PATCH 10/11] make use of new buildResult to add resources routes --- src/serve/appRoutes.js | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/serve/appRoutes.js b/src/serve/appRoutes.js index bbed9843..5551de2c 100644 --- a/src/serve/appRoutes.js +++ b/src/serve/appRoutes.js @@ -54,6 +54,17 @@ exports.addToAPI = function (opts, api) { buildFromRequest(opts) .spread(function (mountInfo, buildResult) { res.json(mountInfo); + // Finally, add all of the resource routes (some are dynamically added at build by devkit modules) + var simulatorApp = getAppByPath(mountInfo.appPath); + if (!simulatorApp._areResourcesMounted) { + simulatorApp._areResourcesMounted = true; + buildResult.config.directories.forEach(function(resource) { + simulatorApp.use( + resource.target, + express.static(resource.src) + ); + }); + } }) .catch(function (e) { logger.error('Error mounting app', e.stack); @@ -195,10 +206,7 @@ exports.addToAPI = function (opts, api) { '/src', express.static(path.join(appPath, 'src')) ); - simulatorApp.use( - '/resources', - express.static(path.join(appPath, 'resources')) - ); + simulatorApp._areResourcesMounted = false; // Static serve builds simulatorApp.use('/', express.static(buildPath)); @@ -209,21 +217,6 @@ exports.addToAPI = function (opts, api) { var debuggerURLs = {}; var simulatorURLs = {}; var loadExtension = function (module) { - // All modules get a chance to add "resources" routes - var buildExtension = module.loadExtension('build'); - if (buildExtension) { - var resources = buildExtension.getResourceDirectories(); - if (resources) { - var prefix = '/modules/' + module.name; - resources.forEach(function(resource) { - simulatorApp.use( - path.join(prefix, resource.target), - express.static(resource.src) - ); - }); - } - } - var extension = module.loadExtension('debugger'); if (!extension || !extension.getMiddleware) { return; } From 71bbfd38c4de0d5378b9c8facdb812c0bb6527f8 Mon Sep 17 00:00:00 2001 From: yofreke Date: Thu, 20 Aug 2015 10:05:16 +0000 Subject: [PATCH 11/11] make use of new buildResult to add resources routes --- src/serve/appRoutes.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/serve/appRoutes.js b/src/serve/appRoutes.js index 5551de2c..219622d0 100644 --- a/src/serve/appRoutes.js +++ b/src/serve/appRoutes.js @@ -60,7 +60,7 @@ exports.addToAPI = function (opts, api) { simulatorApp._areResourcesMounted = true; buildResult.config.directories.forEach(function(resource) { simulatorApp.use( - resource.target, + path.join('/' + resource.target), express.static(resource.src) ); }); @@ -206,6 +206,11 @@ exports.addToAPI = function (opts, api) { '/src', express.static(path.join(appPath, 'src')) ); + simulatorApp.use( + '/lib', + express.static(path.join(appPath, 'lib')) + ); + simulatorApp._areResourcesMounted = false; // Static serve builds