You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by br...@apache.org on 2013/09/23 20:00:11 UTC

[2/4] Refactor to use Q.js promises in place of callbacks everywhere.

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/emulate.js
----------------------------------------------------------------------
diff --git a/src/emulate.js b/src/emulate.js
index b37471a..5122182 100644
--- a/src/emulate.js
+++ b/src/emulate.js
@@ -18,36 +18,33 @@
 */
 var cordova_util      = require('./util'),
     path              = require('path'),
-    shell             = require('shelljs'),
-    platforms         = require('../platforms'),
-    platform          = require('./platform'),
+    child_process     = require('child_process'),
     events            = require('./events'),
-    fs                = require('fs'),
-    n                 = require('ncallbacks'),
     hooker            = require('./hooker'),
-    util              = require('util');
+    Q                 = require('q');
 
-function shell_out_to_emulate(root, platform, options, error_callback, done) {
+// Returns a promise.
+function shell_out_to_emulate(root, platform, options) {
     var cmd = '"' + path.join(root, 'platforms', platform, 'cordova', 'run') + '" ' + (options.length ? options.join(" ") : '--emulator');
     events.emit('log', 'Running on emulator for platform "' + platform + '" via command "' + cmd + '" (output to follow)...');
-    shell.exec(cmd, {silent:true, async:true}, function(code, output) {
-        events.emit('log', output);
-        if (code > 0) {
-            var err = new Error('An error occurred while emulating/deploying the ' + platform + ' project.' + output);
-            if (error_callback) return error_callback(err);
-            else throw err;
+    var d = Q.defer();
+    child_process.exec(cmd, function(err, stdout, stderr) {
+        events.emit('log', stdout);
+        if (err) {
+            d.reject(new Error('An error occurred while emulating/deploying the ' + platform + ' project.' + stdout + stderr));
         } else {
             events.emit('log', 'Platform "' + platform + '" deployed to emulator.');
-            done();
+            d.resolve();
         }
     });
+    return d.promise;
 }
 
-module.exports = function emulate (options, callback) {
+// Returns a promise.
+module.exports = function emulate (options) {
     var projectRoot = cordova_util.isCordova(process.cwd());
 
-    if (options instanceof Function && callback === undefined) {
-        callback = options;
+    if (!options) {
         options = {
             verbose: false,
             platforms: [],
@@ -57,43 +54,19 @@ module.exports = function emulate (options, callback) {
 
     options = cordova_util.preProcessOptions(options);
     if (options.constructor.name === "Error") {
-        if (callback) return callback(options)
-        else throw options;
+        return Q.reject(options);
     }
 
     var hooks = new hooker(projectRoot);
-    hooks.fire('before_emulate', options, function(err) {
-        if (err) {
-            if (callback) callback(err);
-            else throw err;
-        } else {
-            var end = n(options.platforms.length, function() {
-                hooks.fire('after_emulate', options, function(err) {
-                    if (err) {
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
-                        if (callback) callback();
-                    }
-                });
-            });
-
-            // Run a prepare first!
-            require('../cordova').prepare(options.platforms, function(err) {
-                if (err) {
-                    if (callback) callback(err);
-                    else throw err;
-                } else {
-                    options.platforms.forEach(function(platform) {
-                        try {
-                            shell_out_to_emulate(projectRoot, platform, options.options, callback, end);
-                        } catch(e) {
-                            if (callback) callback(e);
-                            else throw e;
-                        }
-                    });
-                }
-            });
-        }
+    return hooks.fire('before_emulate', options)
+    .then(function() {
+        // Run a prepare first!
+        return require('../cordova').raw.prepare(options.platforms);
+    }).then(function() {
+        return Q.all(options.platforms.map(function(platform) {
+            return shell_out_to_emulate(projectRoot, platform, options.options);
+        }));
+    }).then(function() {
+        return hooks.fire('after_emulate', options);
     });
 };

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/help.js
----------------------------------------------------------------------
diff --git a/src/help.js b/src/help.js
index d88711f..e6f7536 100644
--- a/src/help.js
+++ b/src/help.js
@@ -17,32 +17,10 @@
     under the License.
 */
 var fs = require('fs'),
-    colors = require('colors'),
     events = require('./events'),
     path = require('path');
 
 module.exports = function help () {
-    var raw = fs.readFileSync(path.join(__dirname, '..', 'doc', 'help.txt')).toString('utf8').split("\n");
-    events.emit('results', raw.map(function(line) {
-        if (line.match('    ')) {
-            var prompt = '    $ ',
-                isPromptLine = !(!(line.indexOf(prompt) != -1));
-            if (isPromptLine) {
-                return prompt.green + line.replace(prompt, '');
-            }
-            else {
-                return line.split(/\./g).map( function(char) { 
-                    if (char === '') {
-                        return '.'.grey;
-                    }
-                    else {
-                        return char;
-                    }
-                }).join('');
-            }
-        }
-        else {
-            return line.magenta;
-        }
-    }).join("\n"));
+    var raw = fs.readFileSync(path.join(__dirname, '..', 'doc', 'help.txt')).toString('utf8');
+    events.emit('results', raw);
 };

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/hooker.js
----------------------------------------------------------------------
diff --git a/src/hooker.js b/src/hooker.js
index eb27f50..a8ebbf8 100644
--- a/src/hooker.js
+++ b/src/hooker.js
@@ -16,10 +16,11 @@
     specific language governing permissions and limitations
     under the License.
 */
-var shell = require('shelljs'),
-    util  = require('./util'),
+var util  = require('./util'),
     fs    = require('fs'),
     events= require('./events'),
+    child_process = require('child_process'),
+    Q     = require('q'),
     path  = require('path');
 
 module.exports = function hooker(root) {
@@ -28,23 +29,17 @@ module.exports = function hooker(root) {
     else this.root = r;
 }
 
-module.exports.fire = function global_fire(hook, opts, callback) {
-    if (arguments.length == 2 && typeof opts == 'function') {
-        callback = opts;
-        opts = {};
-    }
+// Returns a promise.
+module.exports.fire = function global_fire(hook, opts) {
+    opts = opts || {};
     var handlers = events.listeners(hook);
-    execute_handlers_serially(handlers, opts, function() {
-        if (callback) callback();
-    });
+    return execute_handlers_serially(handlers, opts);
 };
 
 module.exports.prototype = {
-    fire:function fire(hook, opts, callback) {
-        if (arguments.length == 2) {
-            callback = opts;
-            opts = {};
-        }
+    // Returns a promise.
+    fire:function fire(hook, opts) {
+        opts = opts || {};
         var self = this;
         var dir = path.join(this.root, '.cordova', 'hooks', hook);
         opts.root = this.root;
@@ -52,61 +47,55 @@ module.exports.prototype = {
         // Fire JS hook for the event
         // These ones need to "serialize" events, that is, each handler attached to the event needs to finish processing (if it "opted in" to the callback) before the next one will fire.
         var handlers = events.listeners(hook);
-        execute_handlers_serially(handlers, opts, function() {
+        return execute_handlers_serially(handlers, opts)
+        .then(function() {
             // Fire script-based hooks
             if (!(fs.existsSync(dir))) {
-                callback(); // hooks directory got axed post-create; ignore.
+                return Q(); // hooks directory got axed post-create; ignore.
             } else {
                 var scripts = fs.readdirSync(dir).filter(function(s) {
                     return s[0] != '.';
                 });
-                execute_scripts_serially(scripts, self.root, dir, function(err) {
-                    if (err) {
-                        callback(err);
-                    } else {
-                        callback();
-                    }
-                });
+                return execute_scripts_serially(scripts, self.root, dir);
             }
         });
     }
 }
 
-function execute_scripts_serially(scripts, root, dir, callback) {
+// Returns a promise.
+function execute_scripts_serially(scripts, root, dir) {
     if (scripts.length) {
         var s = scripts.shift();
         var fullpath = path.join(dir, s);
         if (fs.statSync(fullpath).isDirectory()) {
-            execute_scripts_serially(scripts, root, dir, callback); // skip directories if they're in there.
+            return execute_scripts_serially(scripts, root, dir); // skip directories if they're in there.
         } else {
             var command = fullpath + ' "' + root + '"';
             events.emit('log', 'Executing hook "' + command + '" (output to follow)...');
-            shell.exec(command, {silent:true, async:true}, function(code, output) {
-                events.emit('log', output);
-                if (code !== 0) {
-                    callback(new Error('Script "' + fullpath + '" exited with non-zero status code. Aborting. Output: ' + output));
+            var d = Q.defer();
+            child_process.exec(command, function(err, stdout, stderr) {
+                events.emit('log', stdout);
+                if (err) {
+                    d.reject(new Error('Script "' + fullpath + '" exited with non-zero status code. Aborting. Output: ' + stdout + stderr));
                 } else {
-                    execute_scripts_serially(scripts, root, dir, callback);
+                    d.resolve(execute_scripts_serially(scripts, root, dir));
                 }
             });
+            return d.promise;
         }
     } else {
-        callback();
+        return Q(); // Nothing to do.
     }
 }
 
-function execute_handlers_serially(handlers, opts, callback) {
+// Returns a promise.
+function execute_handlers_serially(handlers, opts) {
     if (handlers.length) {
-        var h = handlers.shift();
-        if (h.length > 1) {
-            h(opts, function() {
-                execute_handlers_serially(handlers, opts, callback);
-            });
-        } else {
-            h(opts);
-            execute_handlers_serially(handlers, opts, callback);
-        }
+        // Chain the handlers in series.
+        return handlers.reduce(function(soFar, f) {
+            return soFar.then(function() { return f(opts) });
+        }, Q());
     } else {
-        callback();
+        return Q(); // Nothing to do.
     }
 }

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/lazy_load.js
----------------------------------------------------------------------
diff --git a/src/lazy_load.js b/src/lazy_load.js
index 038a172..e21c164 100644
--- a/src/lazy_load.js
+++ b/src/lazy_load.js
@@ -29,40 +29,34 @@ var path          = require('path'),
     zlib          = require('zlib'),
     tar           = require('tar'),
     URL           = require('url'),
+    Q             = require('q'),
     util          = require('./util');
 
 module.exports = {
-    cordova:function lazy_load(platform, callback) {
+    // Returns a promise.
+    cordova:function lazy_load(platform) {
         if (!(platform in platforms)) {
-            var err = new Error('Cordova library "' + platform + '" not recognized.');
-            if (callback) return callback(err);
-            else throw err;
+            return Q.reject(new Error('Cordova library "' + platform + '" not recognized.'));
         }
 
         var url = platforms[platform].url + ';a=snapshot;h=' + platforms[platform].version + ';sf=tgz';
-        module.exports.custom(url, 'cordova', platform, platforms[platform].version, function(err) {
-            if (err) {
-                if (callback) return callback(err);
-                else throw err;
-            } else {
-                if (callback) callback();
-            }
-        });
+        return module.exports.custom(url, 'cordova', platform, platforms[platform].version);
     },
-    custom:function(url, id, platform, version, callback) {
+    custom:function(url, id, platform, version) {
         var download_dir = (platform == 'wp7' || platform == 'wp8' ? path.join(util.libDirectory, 'wp', id, version) :
                                                                      path.join(util.libDirectory, platform, id, version));
         if (fs.existsSync(download_dir)) {
             events.emit('log', id + ' library for "' + platform + '" already exists. No need to download. Continuing.');
-            if (callback) return callback();
+            return Q();
         }
-        hooker.fire('before_library_download', {
+        return hooker.fire('before_library_download', {
             platform:platform,
             url:url,
             id:id,
             version:version
-        }, function() {
+        }).then(function() {
             var uri = URL.parse(url);
+            var d = Q.defer();
             if (uri.protocol && uri.protocol[1] != ':') { // second part of conditional is for awesome windows support. fuuu windows
                 npm.load(function() {
                     // Check if NPM proxy settings are set. If so, include them in the request() call.
@@ -80,21 +74,21 @@ module.exports = {
                         request_options.proxy = proxy;
                     }
                     events.emit('log', 'Requesting ' + JSON.stringify(request_options) + '...');
-                    request.get(request_options, function(err, req, body) { size = body.length; })
-                    .pipe(zlib.createUnzip())
+                    var req = request.get(request_options, function(err, req, body) { size = body.length; });
+                    req.pipe(zlib.createUnzip())
                     .pipe(tar.Extract({path:download_dir}))
                     .on('error', function(err) {
                         shell.rm('-rf', download_dir);
-                        if (callback) callback(err);
-                        else throw err;
+                        d.reject(err);
                     })
                     .on('end', function() {
+                        debugger;
                         events.emit('log', 'Downloaded, unzipped and extracted ' + size + ' byte response.');
                         var entries = fs.readdirSync(download_dir);
                         var entry = path.join(download_dir, entries[0]);
                         shell.mv('-f', path.join(entry, (platform=='blackberry10'?'blackberry10':''), '*'), download_dir);
                         shell.rm('-rf', entry);
-                        hooker.fire('after_library_download', {
+                        d.resolve(hooker.fire('after_library_download', {
                             platform:platform,
                             url:url,
                             id:id,
@@ -102,36 +96,35 @@ module.exports = {
                             path:download_dir,
                             size:size,
                             symlink:false
-                        }, function() {
-                            if (callback) callback();
-                        });
+                        }));
                     });
                 });
             } else {
                 // local path
                 // symlink instead of copying
+                // TODO: Unixy platforms only! We should fall back to copying on Windows.
                 shell.mkdir('-p', path.join(download_dir, '..'));
                 fs.symlinkSync((uri.protocol && uri.protocol[1] == ':' ? uri.href : uri.path), download_dir, 'dir');
-                hooker.fire('after_library_download', {
+                d.resolve(hooker.fire('after_library_download', {
                     platform:platform,
                     url:url,
                     id:id,
                     version:version,
                     path:download_dir,
                     symlink:true
-                }, function() {
-                    if (callback) callback();
-                });
+                }));
             }
+            return d.promise;
         });
     },
-    based_on_config:function(project_root, platform, callback) {
+    // Returns a promise.
+    based_on_config:function(project_root, platform) {
         var custom_path = config.has_custom_path(project_root, platform);
         if (custom_path) {
             var dot_file = config.read(project_root);
-            module.exports.custom(dot_file.lib[platform].uri, dot_file.lib[platform].id, platform, dot_file.lib[platform].version, callback);
+            return module.exports.custom(dot_file.lib[platform].uri, dot_file.lib[platform].id, platform, dot_file.lib[platform].version);
         } else {
-            module.exports.cordova(platform, callback);
+            return module.exports.cordova(platform);
         }
     }
 };

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/metadata/android_parser.js
----------------------------------------------------------------------
diff --git a/src/metadata/android_parser.js b/src/metadata/android_parser.js
index 6da778a..fde7e4b 100644
--- a/src/metadata/android_parser.js
+++ b/src/metadata/android_parser.js
@@ -18,12 +18,13 @@
 */
 var fs            = require('fs'),
     path          = require('path'),
-    et            = require('elementtree'),
     xml           = require('../xml-helpers'),
     util          = require('../util'),
     events        = require('../events'),
     shell         = require('shelljs'),
+    child_process = require('child_process'),
     project_config= require('../config'),
+    Q             = require('q'),
     config_parser = require('../config_parser');
 
 var default_prefs = {
@@ -41,17 +42,19 @@ module.exports = function android_parser(project) {
     this.android_config = path.join(this.path, 'res', 'xml', 'config.xml');
 };
 
-module.exports.check_requirements = function(project_root, callback) {
+// Returns a promise.
+module.exports.check_requirements = function(project_root) {
     events.emit('log', 'Checking Android requirements...');
     var command = 'android list target';
     events.emit('log', 'Running "' + command + '" (output to follow)');
-    shell.exec(command, {silent:true, async:true}, function(code, output) {
+    var d = Q.defer();
+    child_process.exec(command, function(err, output, stderr) {
         events.emit('log', output);
-        if (code != 0) {
-            callback('The command `android` failed. Make sure you have the latest Android SDK installed, and the `android` command (inside the tools/ folder) added to your path. Output: ' + output);
+        if (err) {
+            d.reject(new Error('The command `android` failed. Make sure you have the latest Android SDK installed, and the `android` command (inside the tools/ folder) added to your path. Output: ' + output));
         } else {
             if (output.indexOf('android-17') == -1) {
-                callback('Please install Android target 17 (the Android 4.2 SDK). Make sure you have the latest Android tools installed as well. Run `android` from your command-line to install/update any missing SDKs or tools.');
+                d.reject(new Error('Please install Android target 17 (the Android 4.2 SDK). Make sure you have the latest Android tools installed as well. Run `android` from your command-line to install/update any missing SDKs or tools.'));
             } else {
                 var custom_path = project_config.has_custom_path(project_root, 'android');
                 var framework_path;
@@ -62,17 +65,20 @@ module.exports.check_requirements = function(project_root, callback) {
                 }
                 var cmd = 'android update project -p "' + framework_path  + '" -t android-17';
                 events.emit('log', 'Running "' + cmd + '" (output to follow)...');
-                shell.exec(cmd, {silent:true, async:true}, function(code, output) {
+                var d2 = Q.defer();
+                child_process.exec(cmd, function(err, output, stderr) {
                     events.emit('log', output);
-                    if (code != 0) {
-                        callback('Error updating the Cordova library to work with your Android environment. Command run: "' + cmd + '", output: ' + output);
+                    if (err) {
+                        d2.reject(new Error('Error updating the Cordova library to work with your Android environment. Command run: "' + cmd + '", output: ' + output));
                     } else {
-                        callback(false);
+                        d2.resolve();
                     }
                 });
+                d.resolve(d2.promise);
             }
         }
     });
+    return d.promise;
 };
 
 module.exports.prototype = {
@@ -201,21 +207,20 @@ module.exports.prototype = {
         }
     },
 
-    update_project:function(cfg, callback) {
+    // Returns a promise.
+    update_project:function(cfg) {
         var platformWww = path.join(this.path, 'assets');
         try {
             this.update_from_config(cfg);
         } catch(e) {
-            if (callback) callback(e);
-            else throw e;
-            return;
+            return Q.reject(e);
         }
         this.update_www();
         this.update_overrides();
         this.update_staging();
         // delete any .svn folders copied over
         util.deleteSvnFolders(platformWww);
-        if (callback) callback();
+        return Q();
     }
 };
 

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/metadata/blackberry10_parser.js
----------------------------------------------------------------------
diff --git a/src/metadata/blackberry10_parser.js b/src/metadata/blackberry10_parser.js
index f4d617b..6b8476d 100644
--- a/src/metadata/blackberry10_parser.js
+++ b/src/metadata/blackberry10_parser.js
@@ -18,9 +18,10 @@
 */
 var fs            = require('fs'),
     path          = require('path'),
-    et            = require('elementtree'),
     shell         = require('shelljs'),
     util          = require('../util'),
+    Q             = require('q'),
+    child_process = require('child_process'),
     config_parser = require('../config_parser'),
     events        = require('../events'),
     config        = require('../config');
@@ -34,15 +35,18 @@ module.exports = function blackberry_parser(project) {
     this.xml = new util.config_parser(this.config_path);
 };
 
-module.exports.check_requirements = function(project_root, callback) {
+// Returns a promise.
+module.exports.check_requirements = function(project_root) {
     var lib_path = path.join(util.libDirectory, 'blackberry10', 'cordova', require('../../platforms').blackberry10.version);
-    shell.exec("\"" + path.join(lib_path, 'bin', 'check_reqs') + "\"", {silent:true, async:true}, function(code, output) {
-        if (code !== 0) {
-            callback(output);
+    var d = Q.defer();
+    child_process.exec("\"" + path.join(lib_path, 'bin', 'check_reqs') + "\"", function(err, output, stderr) {
+        if (err) {
+            d.reject(new Error('Error while checking requirements: ' + output + stderr));
         } else {
-            callback(false);
+            d.resolve();
         }
     });
+    return d.promise;
 };
 
 module.exports.prototype = {
@@ -68,20 +72,21 @@ module.exports.prototype = {
         });
         this.xml.content(config.content());
     },
-    update_project:function(cfg, callback) {
+
+    // Returns a promise.
+    update_project:function(cfg) {
         var self = this;
 
         try {
             self.update_from_config(cfg);
         } catch(e) {
-            if (callback) return callback(e);
-            else throw e;
+            return Q.reject(e);
         }
         self.update_www();
         self.update_overrides();
         self.update_staging();
         util.deleteSvnFolders(this.www_dir());
-        if (callback) callback();
+        return Q();
     },
 
     // Returns the platform-specific www directory.

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/metadata/firefoxos_parser.js
----------------------------------------------------------------------
diff --git a/src/metadata/firefoxos_parser.js b/src/metadata/firefoxos_parser.js
index 6dff17b..8115d4b 100644
--- a/src/metadata/firefoxos_parser.js
+++ b/src/metadata/firefoxos_parser.js
@@ -16,32 +16,29 @@
     specific language governing permissions and limitations
     under the License.
 */
-var fs = require('fs');
-var path = require('path');
-var shell = require('shelljs');
-var util = require('../util');
-var config_parser = require('../config_parser');
-var config = require('../config');
+var fs = require('fs'),
+    path = require('path'),
+    shell = require('shelljs'),
+    util = require('../util'),
+    Q = require('q'),
+    child_process = require('child_process'),
+    config_parser = require('../config_parser'),
+    config = require('../config');
 
 module.exports = function firefoxos_parser(project) {
     this.path = project;
 };
 
-module.exports.check_requirements = function(project_root, callback) {
-    callback(false);
+// Returns a promise.
+module.exports.check_requirements = function(project_root) {
+    return Q(); // Requirements always met.
 };
 
 module.exports.prototype = {
-    update_from_config: function(config, callback) {
+    // Returns a promise.
+    update_from_config: function(config) {
         if (!(config instanceof config_parser)) {
-            var err = new Error('update_from_config requires a config_parser object');
-            if (callback) {
-                callback(err);
-                return;
-            }
-            else {
-                throw err;
-            }
+            return Q.reject(new Error('update_from_config requires a config_parser object'));
         }
 
         var name = config.name();
@@ -66,9 +63,7 @@ module.exports.prototype = {
         manifest.pkgName = pkg;
         fs.writeFileSync(manifestPath, JSON.stringify(manifest));
 
-        if(callback) {
-            callback();
-        }
+        return Q();
     },
 
     www_dir: function() {
@@ -115,26 +110,15 @@ module.exports.prototype = {
         }
     },
 
-    update_project: function(cfg, callback) {
+    // Returns a promise.
+    update_project: function(cfg) {
         this.update_www();
 
-        this.update_from_config(cfg, function(err) {
-            if(err) {
-                if(callback) {
-                    callback(err);
-                }
-                else {
-                    throw err;
-                }
-            } else {
-                this.update_overrides();
-                this.update_staging();
-                util.deleteSvnFolders(this.www_dir());
-
-                if(callback) {
-                    callback();
-                }
-            }
-        }.bind(this));
+        return this.update_from_config(cfg)
+        .then(function() {
+            this.update_overrides();
+            this.update_staging();
+            util.deleteSvnFolders(this.www_dir());
+        });
     }
 };

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/metadata/ios_parser.js
----------------------------------------------------------------------
diff --git a/src/metadata/ios_parser.js b/src/metadata/ios_parser.js
index c023ef7..c1e1351 100644
--- a/src/metadata/ios_parser.js
+++ b/src/metadata/ios_parser.js
@@ -22,9 +22,10 @@ var fs            = require('fs'),
     util          = require('../util'),
     events        = require('../events'),
     shell         = require('shelljs'),
+    child_process = require('child_process'),
     plist         = require('plist'),
     semver        = require('semver'),
-    et            = require('elementtree'),
+    Q             = require('q'),
     config_parser = require('../config_parser'),
     config        = require('../config');
 
@@ -61,35 +62,36 @@ module.exports = function ios_parser(project) {
     this.config = new util.config_parser(this.config_path);
 };
 
-module.exports.check_requirements = function(project_root, callback) {
+// Returns a promise.
+module.exports.check_requirements = function(project_root) {
     events.emit('log', 'Checking iOS requirements...');
     // Check xcode + version.
     var command = 'xcodebuild -version';
     events.emit('log', 'Running "' + command + '" (output to follow)');
-    shell.exec(command, {silent:true, async:true}, function(code, output) {
+    var d = Q.defer();
+    child_process.exec(command, function(err, output, stderr) {
         events.emit('log', output);
-        if (code != 0) {
-            callback('Xcode is (probably) not installed, specifically the command `xcodebuild` is unavailable or erroring out. Output of `'+command+'` is: ' + output);
+        if (err) {
+            d.reject(new Error('Xcode is (probably) not installed, specifically the command `xcodebuild` is unavailable or erroring out. Output of `'+command+'` is: ' + output + stderr));
         } else {
             var xc_version = output.split('\n')[0].split(' ')[1];
             if(xc_version.split('.').length === 2){
                 xc_version += '.0';
             }
             if (!semver.satisfies(xc_version, MIN_XCODE_VERSION)) {
-                callback('Xcode version installed is too old. Minimum: ' + MIN_XCODE_VERSION + ', yours: ' + xc_version);
-            } else callback(false);
+                d.reject(new Error('Xcode version installed is too old. Minimum: ' + MIN_XCODE_VERSION + ', yours: ' + xc_version));
+            } else d.resolve();
         }
     });
+    return d.promise;
 };
 
 module.exports.prototype = {
-    update_from_config:function(config, callback) {
+    // Returns a promise.
+    update_from_config:function(config) {
         if (config instanceof config_parser) {
         } else {
-            var err = new Error('update_from_config requires a config_parser object');
-            if (callback) callback(err);
-            else throw err;
-            return;
+            return Q.reject(new Error('update_from_config requires a config_parser object'));
         }
         var name = config.name();
         var pkg = config.packageName();
@@ -147,11 +149,10 @@ module.exports.prototype = {
             // Update product name inside pbxproj file
             var proj = new xcode.project(this.pbxproj);
             var parser = this;
+            var d = Q.defer();
             proj.parse(function(err,hash) {
                 if (err) {
-                    var err = new Error('An error occured during parsing of project.pbxproj. Start weeping. Output: ' + err);
-                    if (callback) callback(err);
-                    else throw err;
+                    d.reject(new Error('An error occured during parsing of project.pbxproj. Start weeping. Output: ' + err));
                 } else {
                     proj.updateProductName(name);
                     fs.writeFileSync(parser.pbxproj, proj.writeSync(), 'utf-8');
@@ -168,12 +169,13 @@ module.exports.prototype = {
                     pbx_contents = pbx_contents.split(old_name).join(name);
                     fs.writeFileSync(parser.pbxproj, pbx_contents, 'utf-8');
                     events.emit('log', 'Wrote out iOS Product Name and updated XCode project file names from "'+old_name+'" to "' + name + '".');
-                    if (callback) callback();
+                    d.resolve();
                 }
             });
+            return d.promise;
         } else {
             events.emit('log', 'iOS Product Name has not changed (still "' + this.originalName + '")');
-            if (callback) callback();
+            return Q();
         }
     },
 
@@ -228,19 +230,15 @@ module.exports.prototype = {
         }
     },
 
-    update_project:function(cfg, callback) {
+    // Returns a promise.
+    update_project:function(cfg) {
         var self = this;
-        this.update_from_config(cfg, function(err) {
-            if (err) {
-                if (callback) callback(err);
-                else throw err;
-            } else {
-                self.update_www();
-                self.update_overrides();
-                self.update_staging();
-                util.deleteSvnFolders(self.www_dir());
-                if (callback) callback();
-            }
+        return this.update_from_config(cfg)
+        .then(function() {
+            self.update_www();
+            self.update_overrides();
+            self.update_staging();
+            util.deleteSvnFolders(self.www_dir());
         });
     }
 };

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/metadata/wp7_parser.js
----------------------------------------------------------------------
diff --git a/src/metadata/wp7_parser.js b/src/metadata/wp7_parser.js
index e72c1f6..7abbda1 100644
--- a/src/metadata/wp7_parser.js
+++ b/src/metadata/wp7_parser.js
@@ -23,7 +23,8 @@ var fs            = require('fs'),
     util          = require('../util'),
     events        = require('../events'),
     shell         = require('shelljs'),
-    events        = require('../events'),
+    child_process = require('child_process'),
+    Q             = require('q'),
     config_parser = require('../config_parser'),
     config        = require('../config');
 
@@ -43,21 +44,24 @@ module.exports = function wp7_parser(project) {
     this.config = new util.config_parser(this.config_path);
 };
 
-module.exports.check_requirements = function(project_root, callback) {
+// Returns a promise.
+module.exports.check_requirements = function(project_root) {
     events.emit('log', 'Checking wp7 requirements...');
     var lib_path = path.join(util.libDirectory, 'wp', 'cordova', require('../../platforms').wp7.version, 'wp7');
     var custom_path = config.has_custom_path(project_root, 'wp7');
     if (custom_path) lib_path = custom_path;
     var command = '"' + path.join(lib_path, 'bin', 'check_reqs') + '"';
     events.emit('log', 'Running "' + command + '" (output to follow)');
-    shell.exec(command, {silent:true, async:true}, function(code, output) {
+    var d = Q.defer();
+    child_process.exec(command, function(err, output, stderr) {
         events.emit('log', output);
-        if (code != 0) {
-            callback(output);
+        if (err) {
+            d.reject(new Error('Error while checking requirements: ' + output + stderr));
         } else {
-            callback(false);
+            d.resolve();
         }
     });
+    return d.promise;
 };
 
 module.exports.prototype = {
@@ -240,22 +244,17 @@ module.exports.prototype = {
     },
 
     // calls the nessesary functions to update the wp7 project 
-    update_project:function(cfg, callback) {
-        //console.log("Updating wp7 project...");
-
+    // Returns a promise.
+    update_project:function(cfg) {
         try {
             this.update_from_config(cfg);
         } catch(e) {
-            if (callback) return callback(e);
-            else throw e;
+            return Q.reject(e);
         }
         this.update_www();
         // TODO: Add overrides support? Why is this missing?
         this.update_staging();
         util.deleteSvnFolders(this.www_dir());
-
-        //console.log("Done updating.");
-
-        if (callback) callback();
+        return Q();
     }
 };

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/metadata/wp8_parser.js
----------------------------------------------------------------------
diff --git a/src/metadata/wp8_parser.js b/src/metadata/wp8_parser.js
index bfcb6ef..bb2f862 100644
--- a/src/metadata/wp8_parser.js
+++ b/src/metadata/wp8_parser.js
@@ -22,7 +22,8 @@ var fs            = require('fs'),
     util          = require('../util'),
     events        = require('../events'),
     shell         = require('shelljs'),
-    events        = require('../events'),
+    child_process = require('child_process'),
+    Q             = require('q'),
     config_parser = require('../config_parser'),
     xml           = require('../xml-helpers'),
     config        = require('../config');
@@ -43,21 +44,24 @@ module.exports = function wp8_parser(project) {
     this.config = new util.config_parser(this.config_path);
 };
 
-module.exports.check_requirements = function(project_root, callback) {
+// Returns a promise.
+module.exports.check_requirements = function(project_root) {
     events.emit('log', 'Checking wp8 requirements...');
     var lib_path = path.join(util.libDirectory, 'wp', 'cordova', require('../../platforms').wp8.version, 'wp8');
     var custom_path = config.has_custom_path(project_root, 'wp8');
     if (custom_path) lib_path = custom_path;
     var command = '"' + path.join(lib_path, 'bin', 'check_reqs') + '"';
     events.emit('log', 'Running "' + command + '" (output to follow)');
-    shell.exec(command, {silent:true, async:true}, function(code, output) {
+    var d = Q.defer();
+    child_process.exec(command, function(err, output, stderr) {
         events.emit('log', output);
-        if (code != 0) {
-            callback(output);
+        if (err) {
+            d.reject(new Error('Error while checking requirements: ' + output + stderr));
         } else {
-            callback(false);
+            d.resolve();
         }
     });
+    return d.promise;
 };
 
 module.exports.prototype = {
@@ -242,23 +246,18 @@ module.exports.prototype = {
         }
     },
 
-    // calls the nessesary functions to update the wp8 project 
-    update_project:function(cfg, callback) {
-        //console.log("Updating wp8 project...");
-
+    // calls the nessesary functions to update the wp8 project
+    // Returns a promise.
+    update_project:function(cfg) {
         try {
             this.update_from_config(cfg);
         } catch(e) {
-            if (callback) return callback(e);
-            else throw e;
+            return Q.reject(e);
         }
         // overrides (merges) are handled in update_www()
         this.update_www();
         this.update_staging();
         util.deleteSvnFolders(this.www_dir());
-
-        //console.log("Done updating.");
-
-        if (callback) callback();
+        return Q();
     }
 };

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/platform.js
----------------------------------------------------------------------
diff --git a/src/platform.js b/src/platform.js
index 3f8d45b..e5c5319 100644
--- a/src/platform.js
+++ b/src/platform.js
@@ -25,39 +25,34 @@ var config            = require('./config'),
     hooker            = require('./hooker'),
     events            = require('./events'),
     lazy_load         = require('./lazy_load'),
-    n                 = require('ncallbacks'),
+    Q                 = require('q'),
     platforms         = require('../platforms'),
-    plugman           = require('plugman'),
+    child_process     = require('child_process'),
     shell             = require('shelljs');
 
-module.exports = function platform(command, targets, callback) {
+// Returns a promise.
+module.exports = function platform(command, targets) {
     var projectRoot = cordova_util.isCordova(process.cwd());
 
     if (!projectRoot) {
-        var err = new Error('Current working directory is not a Cordova-based project.');
-        if (callback) callback(err);
-        else throw err;
-        return;
+        return Q.reject(new Error('Current working directory is not a Cordova-based project.'));
     }
 
     var hooks = new hooker(projectRoot);
 
-
     if (arguments.length === 0) command = 'ls';
     if (targets) {
         if (!(targets instanceof Array)) targets = [targets];
+        var err;
         targets.forEach(function(t) {
             if (!(t in platforms)) {
-                var err = new Error('Platform "' + t + '" not recognized as core cordova platform.');
-                if (callback) return callback(err);
-                else throw err;
+                err = new Error('Platform "' + t + '" not recognized as core cordova platform.');
             }
         });
+        if (err) return Q.reject(err);
     } else {
         if (command == 'add' || command == 'rm') {
-            var err = new Error('You need to qualify `add` or `remove` with one or more platforms!');
-            if (callback) return callback(err);
-            else throw err;
+            return Q.reject(new Error('You need to qualify `add` or `remove` with one or more platforms!'));
         }
     }
 
@@ -71,117 +66,76 @@ module.exports = function platform(command, targets, callback) {
         case 'add':
             var config_json = config.read(projectRoot);
 
-            var doInstall = function(index) {
-                if (index >= targets.length) {
-                    hooks.fire('after_platform_add', opts, function(err) {
-                        if (err) {
-                            if (callback) callback(err);
-                            else throw err;
-                        } else {
-                            if (callback) callback();
-                        }
-                    });
-                    return;
-                }
 
-                var t = targets[index];
-                lazy_load.based_on_config(projectRoot, t, function(err) {
-                    if (err) {
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
+            return hooks.fire('before_platform_add', opts)
+            .then(function() {
+                var promises = targets.map(function(t) {
+                    return lazy_load.based_on_config(projectRoot, t)
+                    .then(function() {
                         if (config_json.lib && config_json.lib[t]) {
-                            call_into_create(t, projectRoot, cfg, config_json.lib[t].id, config_json.lib[t].version, config_json.lib[t].template, callback, end(index));
+                            return call_into_create(t, projectRoot, cfg, config_json.lib[t].id, config_json.lib[t].version, config_json.lib[t].template);
                         } else {
-                            call_into_create(t, projectRoot, cfg, 'cordova', platforms[t].version, null, callback, end(index));
+                            return call_into_create(t, projectRoot, cfg, 'cordova', platforms[t].version, null);
                         }
-                    }
+                    });
                 });
-            };
-
-            var end = function(index) {
-                return function() {
-                        doInstall(index+1);
-                    };
-            };
 
-            hooks.fire('before_platform_add', opts, function(err) {
-                if (err) {
-                    if (callback) callback(err);
-                    else throw err;
-                } else {
-                    doInstall(0);
-                }
+                return promises.reduce(function(soFar, f) {
+                    return soFar.then(f);
+                }, Q());
+            })
+            .then(function() {
+                return hooks.fire('after_platform_add', opts);
             });
+
             break;
         case 'rm':
         case 'remove':
-            var end = n(targets.length, function() {
-                hooks.fire('after_platform_rm', opts, function(err) {
-                    if (err) {
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
-                        if (callback) callback();
-                    }
+            return hooks.fire('before_platform_rm', opts)
+            .then(function() {
+                targets.forEach(function(target) {
+                    shell.rm('-rf', path.join(projectRoot, 'platforms', target));
+                    shell.rm('-rf', path.join(cordova_util.appDir(projectRoot), 'merges', target));
+                    var plugins_json = path.join(projectRoot, 'plugins', target + '.json');
+                    if (fs.existsSync(plugins_json)) shell.rm(plugins_json);
                 });
+            }).then(function() {
+                return hooks.fire('after_platform_rm', opts);
             });
-            hooks.fire('before_platform_rm', opts, function(err) {
-                if (err) {
-                    if (callback) callback(err);
-                    else throw err;
-                } else {
-                    targets.forEach(function(target) {
-                        shell.rm('-rf', path.join(projectRoot, 'platforms', target));
-                        shell.rm('-rf', path.join(cordova_util.appDir(projectRoot), 'merges', target));
-                        var plugins_json = path.join(projectRoot, 'plugins', target + '.json');
-                        if (fs.existsSync(plugins_json)) shell.rm(plugins_json);
-                        end();
-                    });
-                }
-            });
+
             break;
         case 'update':
         case 'up':
             // Shell out to the update script provided by the named platform.
             if (!targets || !targets.length) {
-                var err = new Error('No platform provided. Please specify a platform to update.');
-                if (callback) callback(err);
-                else throw err;
+                return Q.reject(new Error('No platform provided. Please specify a platform to update.'));
             } else if (targets.length > 1) {
-                var err = new Error('Platform update can only be executed on one platform at a time.');
-                if (callback) callback(err);
-                else throw err;
+                return Q.reject(new Error('Platform update can only be executed on one platform at a time.'));
             } else {
                 var plat = targets[0];
                 var installed_platforms = cordova_util.listPlatforms(projectRoot);
                 if (installed_platforms.indexOf(plat) < 0) {
-                    var err = new Error('Platform "' + plat + '" is not installed.');
-                    if (callback) callback(err);
-                    else throw err;
-                    return;
+                    return Q.reject(new Error('Platform "' + plat + '" is not installed.'));
                 }
 
                 // First, lazy_load the latest version.
                 var config_json = config.read(projectRoot);
-                lazy_load.based_on_config(projectRoot, plat, function(err) {
-                    if (err) {
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
-                        var platDir = plat == 'wp7' || plat == 'wp8' ? 'wp' : plat;
-                        var script = path.join(cordova_util.libDirectory, platDir, 'cordova', platforms[plat].version, 'bin', 'update');
-                        shell.exec(script + ' "' + path.join(projectRoot, 'platforms', plat) + '"', { silent: false, async: true }, function(code, output) {
-                            if (code > 0) {
-                                var err = new Error('Error running update script.');
-                                if (callback) callback(err);
-                                else throw err;
-                            } else {
-                                events.emit('log', plat + ' updated to ' + platforms[plat].version);
-                                if (callback) callback();
-                            }
-                        });
-                    }
+                return hooks.fire('before_platform_update', opts)
+                .then(function() {
+                    return lazy_load.based_on_config(projectRoot, plat);
+                }).then(function() {
+                    var platDir = plat == 'wp7' || plat == 'wp8' ? 'wp' : plat;
+                    var script = path.join(cordova_util.libDirectory, platDir, 'cordova', platforms[plat].version, 'bin', 'update');
+                    var d = Q.defer();
+                    child_process.exec(script + ' "' + path.join(projectRoot, 'platforms', plat) + '"', function(err, stdout, stderr) {
+                        if (err) {
+                            d.reject(new Error('Error running update script: ' + err + stderr));
+                        } else {
+                            events.emit('log', plat + ' updated to ' + platforms[plat].version);
+                            d.resolve();
+                        }
+                    });
+                    return d.promise;
                 });
             }
             break;
@@ -189,45 +143,42 @@ module.exports = function platform(command, targets, callback) {
         case 'list':
         default:
             var platforms_on_fs = cordova_util.listPlatforms(projectRoot);
-            hooks.fire('before_platform_ls', function(err) {
-                if (err) {
-                    if (callback) callback(err);
-                    else throw err;
-                } else {
-                    // Acquire the version number of each platform we have installed, and output that too.
-                    var platformsText = platforms_on_fs.map(function(p) {
-                        var script = path.join(projectRoot, 'platforms', p, 'cordova', 'version');
-                        var result = shell.exec(script, { silent: true, async: false });
-                        if (result.code > 0 || !result.output) {
-                            return p; // Unknown version number, so output without it.
-                        } else {
-                            return p + ' ' + result.output.trim();
-                        }
-                    });
-
-                    var results = 'Installed platforms: ' + platformsText.join(', ') + '\n';
-                    var available = ['android', 'blackberry10', 'firefoxos'];
-                    if (os.platform() === 'darwin')
-                        available.push('ios');
-                    if (os.platform() === 'win32') {
-                        available.push('wp7');
-                        available.push('wp8');
-                    }
-
-                    available = available.filter(function(p) {
-                        return platforms_on_fs.indexOf(p) < 0; // Only those not already installed.
-                    });
-                    results += 'Available platforms: ' + available.join(', ');
 
-                    events.emit('results', results);
-                    hooks.fire('after_platform_ls', function(err) {
-                        if (err) {
-                            if (callback) callback(err);
-                            else throw err;
+            return hooks.fire('before_platform_ls')
+            .then(function() {
+                // Acquire the version number of each platform we have installed, and output that too.
+                return Q.all(platforms_on_fs.map(function(p) {
+                    var script = path.join(projectRoot, 'platforms', p, 'cordova', 'version');
+                    var d = Q.defer();
+                    child_process.exec(script, function(err, stdout, stderr) {
+                        if (err) d.resolve(p);
+                        else {
+                            if (stdout) d.resolve(p + ' ' + stdout.trim());
+                            else d.resolve(p);
                         }
                     });
+                    return d.promise;
+                }));
+            }).then(function(platformsText) {
+                var results = 'Installed platforms: ' + platformsText.join(', ') + '\n';
+                var available = ['android', 'blackberry10', 'firefoxos'];
+                if (os.platform() === 'darwin')
+                    available.push('ios');
+                if (os.platform() === 'win32') {
+                    available.push('wp7');
+                    available.push('wp8');
                 }
+
+                available = available.filter(function(p) {
+                    return platforms_on_fs.indexOf(p) < 0; // Only those not already installed.
+                });
+                results += 'Available platforms: ' + available.join(', ');
+
+                events.emit('results', results);
+            }).then(function() {
+                return hooks.fire('after_platform_ls');
             });
+
             break;
     }
 };
@@ -235,41 +186,29 @@ module.exports = function platform(command, targets, callback) {
 /**
  * Check Platform Support.
  *
- * Options:
- *
  *   - {String} `name` of the platform to test.
- *   - {Function} `callback` is triggered with the answer.
- *     - {Error} `e` null when a platform is supported otherwise describes error.
+ *   - Returns a promise, which shows any errors.
+ *
  */
 
-module.exports.supports = function(project_root, name, callback) {
+module.exports.supports = function(project_root, name) {
     // required parameters
-    if (!name) throw new Error('requires a platform name parameter');
-    if (!callback) throw new Error('requires a callback parameter');
+    if (!name) return Q.reject(new Error('requires a platform name parameter'));
 
     // check if platform exists
     var platform = platforms[name];
     if (!platform) {
-        callback(new Error(util.format('"%s" platform does not exist', name)));
-        return;
+        return Q.reject(new Error(util.format('"%s" platform does not exist', name)));
     }
 
     // look up platform meta-data parser
     var platformParser = platforms[name].parser;
     if (!platformParser) {
-        callback(new Error(util.format('"%s" platform parser does not exist', name)));
-        return;
+        return Q.reject(new Error(util.format('"%s" platform parser does not exist', name)));
     }
 
     // check for platform support
-    platformParser.check_requirements(project_root, function(e) {
-        // typecast String to Error
-        e = (typeof e == 'string') ? new Error(e) : e;
-        // typecast false Boolean to null
-        e = (e) ? e : null;
-
-        callback(e);
-    });
+    return platformParser.check_requirements(project_root);
 };
 
 // Expose the platform parsers on top of this command
@@ -280,63 +219,56 @@ function createOverrides(projectRoot, target) {
     shell.mkdir('-p', path.join(cordova_util.appDir(projectRoot), 'merges', target));
 };
 
-function call_into_create(target, projectRoot, cfg, id, version, template_dir, callback, end) {
+// Returns a promise.
+function call_into_create(target, projectRoot, cfg, id, version, template_dir) {
     var output = path.join(projectRoot, 'platforms', target);
 
     // Check if output directory already exists.
     if (fs.existsSync(output)) {
-        var err = new Error('Platform "' + target + '" already exists at "' + output + '"');
-        if (callback) callback(err);
-        else throw err;
+        return Q.reject(err);
     } else {
         // Make sure we have minimum requirements to work with specified platform
         events.emit('log', 'Checking if platform "' + target + '" passes minimum requirements...');
-        module.exports.supports(projectRoot, target, function(err) {
-            if (err) {
-                if (callback) callback(err);
-                else throw err;
-            } else {
-                // Create a platform app using the ./bin/create scripts that exist in each repo.
-                // Run platform's create script
-                var bin = path.join(cordova_util.libDirectory, target, id, version, 'bin', 'create');
-                if(target == 'wp7') bin = path.join(cordova_util.libDirectory, 'wp', id, version, 'wp7', 'bin', 'create');
-                if(target == 'wp8') bin = path.join(cordova_util.libDirectory, 'wp', id, version, 'wp8', 'bin', 'create');
-                var args = (target=='ios') ? '--arc' : '';
-                var pkg = cfg.packageName().replace(/[^\w.]/g,'_');
-                var name = cfg.name();
-                var command = util.format('"%s" %s "%s" "%s" "%s"', bin, args, output, pkg, name);
-                if (template_dir) {
-                    command += ' "' + template_dir + '"';
-                }
-                events.emit('log', 'Running bin/create for platform "' + target + '" with command: "' + command + '" (output to follow)');
+        return module.exports.supports(projectRoot, target)
+        .then(function() {
+            // Create a platform app using the ./bin/create scripts that exist in each repo.
+            // Run platform's create script
+            var bin = path.join(cordova_util.libDirectory, target, id, version, 'bin', 'create');
+            if(target == 'wp7') bin = path.join(cordova_util.libDirectory, 'wp', id, version, 'wp7', 'bin', 'create');
+            if(target == 'wp8') bin = path.join(cordova_util.libDirectory, 'wp', id, version, 'wp8', 'bin', 'create');
+            var args = (target=='ios') ? '--arc' : '';
+            var pkg = cfg.packageName().replace(/[^\w.]/g,'_');
+            var name = cfg.name();
+            var command = util.format('"%s" %s "%s" "%s" "%s"', bin, args, output, pkg, name);
+            if (template_dir) {
+                command += ' "' + template_dir + '"';
+            }
+            events.emit('log', 'Running bin/create for platform "' + target + '" with command: "' + command + '" (output to follow)');
 
-                shell.exec(command, {silent:true,async:true}, function(code, create_output) {
-                    events.emit('log', create_output);
-                    if (code > 0) {
-                        var err = new Error('An error occured during creation of ' + target + ' sub-project. ' + create_output);
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
-                        require('../cordova').prepare(target, function(err) {
-                            if (err) {
-                                if (callback) callback(err);
-                                else throw err;
-                            } else {
-                                createOverrides(projectRoot, target);
-                                end(); //platform add is done by now.
-                                // Install all currently installed plugins into this new platform.
-                                var plugins_dir = path.join(projectRoot, 'plugins');
-                                var plugins = cordova_util.findPlugins(plugins_dir);
-                                var parser = new platforms[target].parser(output);
-                                plugins && plugins.forEach(function(plugin) {
-                                    events.emit('log', 'Installing plugin "' + plugin + '" following successful platform add of ' + target);
-                                    plugman.install(target, output, path.basename(plugin), plugins_dir, { www_dir: parser.staging_dir() });
-                                });
-                            }
-                        });
-                    }
+            var d = Q.defer();
+            child_process.exec(command, function(err, create_output, stderr) {
+                events.emit('log', create_output);
+                if (err) {
+                    d.reject(new Error('An error occured during creation of ' + target + ' sub-project. ' + create_output));
+                } else {
+                    d.resolve(require('../cordova').raw.prepare(target));
+                }
+            });
+            return d.promise.then(function() {
+                createOverrides(projectRoot, target);
+                // Install all currently installed plugins into this new platform.
+                var plugins_dir = path.join(projectRoot, 'plugins');
+                var plugins = cordova_util.findPlugins(plugins_dir);
+                var parser = new platforms[target].parser(output);
+                if (!plugins) return Q();
+                var promises = plugins.map(function(plugin) {
+                    events.emit('log', 'Installing plugin "' + plugin + '" following successful platform add of ' + target);
+                    return require('plugman').install(target, output, path.basename(plugin), plugins_dir, { www_dir: parser.staging_dir() });
                 });
-            }
+                return promises.reduce(function(soFar, f) {
+                    return soFar.then(f);
+                }, Q());
+            });
         });
     }
 }

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/plugin.js
----------------------------------------------------------------------
diff --git a/src/plugin.js b/src/plugin.js
index 2c5cd33..e352ab8 100644
--- a/src/plugin.js
+++ b/src/plugin.js
@@ -17,34 +17,26 @@
     under the License.
 */
 
-module.exports = function plugin(command, targets, callback) {
+// Returns a promise.
+module.exports = function plugin(command, targets) {
     var cordova_util  = require('./util'),
         path          = require('path'),
-        n             = require('ncallbacks'),
         hooker        = require('./hooker'),
+        Q             = require('q'),
         events        = require('./events');
 
     var projectRoot = cordova_util.isCordova(process.cwd()),
         err;
 
     if (!projectRoot) {
-        err = new Error('Current working directory is not a Cordova-based project.');
-        if (callback) callback(err);
-        else throw err;
-        return;
+        return Q.reject(new Error('Current working directory is not a Cordova-based project.'));
     }
 
     if (arguments.length === 0){
         command = 'ls';
         targets = [];
-    } else if (arguments.length > 3) {
-        var args = Array.prototype.slice.call(arguments, 0);
-        if (typeof args[args.length - 1] === "function") {
-            callback = args.pop();
-        } else {
-            callback = undefined;
-            targets = args.slice(1);
-        }
+    } else {
+        targets = Array.prototype.slice.call(arguments, 1);
     }
 
     var hooks = new hooker(projectRoot);
@@ -54,15 +46,9 @@ module.exports = function plugin(command, targets, callback) {
     var pluginPath, plugins;
     pluginPath = path.join(projectRoot, 'plugins');
     plugins = cordova_util.findPlugins(pluginPath);
-    if (targets) {
-        if (!(targets instanceof Array)) {
-            targets = [targets];
-        }
-    } else {
+    if (!targets || !targets.length) {
         if (command == 'add' || command == 'rm') {
-            err = new Error('You need to qualify `add` or `remove` with one or more plugins!');
-            if (callback) return callback(err);
-            else throw err;
+            return Q.reject(new Error('You need to qualify `add` or `remove` with one or more plugins!'));
         } else {
             targets = [];
         }
@@ -73,6 +59,9 @@ module.exports = function plugin(command, targets, callback) {
         options: []
     };
 
+    if (targets.length == 1 && Array.isArray(targets[0]))
+        targets = targets[0];
+
     //Split targets between plugins and options
     //Assume everything after a token with a '-' is an option
     var i;
@@ -87,173 +76,110 @@ module.exports = function plugin(command, targets, callback) {
 
     switch(command) {
         case 'add':
-            var end = n(opts.plugins.length, function() {
-                hooks.fire('after_plugin_add', opts, function(err) {
-                    if (err) {
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
-                        if (callback) callback();
-                    }
-                });
-            });
-            hooks.fire('before_plugin_add', opts, function(err) {
-                if (err) {
-                    if (callback) callback(err);
-                    else throw err;
-                } else {
-                    opts.plugins.forEach(function(target, index) {
-                        var pluginsDir = path.join(projectRoot, 'plugins');
+            return hooks.fire('before_plugin_add', opts)
+            .then(function() {
+                return Q.all(opts.plugins.map(function(target, index) {
+                    var pluginsDir = path.join(projectRoot, 'plugins');
 
-                        if (target[target.length - 1] == path.sep) {
-                            target = target.substring(0, target.length - 1);
-                        }
-
-                        // Fetch the plugin first.
-                        events.emit('log', 'Calling plugman.fetch on plugin "' + target + '"');
-                        var plugman = require('plugman');
-                        plugman.fetch(target, pluginsDir, {}, function(err, dir) {
-                            if (err) {
-                                err = new Error('Error fetching plugin: ' + err);
-                                if (callback) callback(err);
-                                else throw err;
-                            } else {
-                                // Iterate (in serial!) over all platforms in the project and install the plugin.
-                                var doInstall = function(platformIndex) {
-                                    if (platformIndex >= platformList.length) {
-                                        end();
-                                        return;
-                                    }
+                    if (target[target.length - 1] == path.sep) {
+                        target = target.substring(0, target.length - 1);
+                    }
 
-                                    var platforms = require('../platforms');
-                                    var platform = platformList[platformIndex],
-                                        platformRoot = path.join(projectRoot, 'platforms', platform),
-                                        parser = new platforms[platform].parser(platformRoot),
-                                        options = {
-                                            www_dir: parser.staging_dir(),
-                                            cli_variables: {}
-                                        },
-                                        tokens,
-                                        key,
-                                        i;
-                                    //parse variables into cli_variables
-                                    for (i=0; i< opts.options.length; i++) {
-                                        if (opts.options[i] === "--variable" && typeof opts.options[++i] === "string") {
-                                            tokens = opts.options[i].split('=');
-                                            key = tokens.shift().toUpperCase();
-                                            if (/^[\w-_]+$/.test(key)) {
-                                                options.cli_variables[key] = tokens.join('=');
-                                            }
-                                        }
+                    // Fetch the plugin first.
+                    events.emit('log', 'Calling plugman.fetch on plugin "' + target + '"');
+                    var plugman = require('plugman');
+                    return plugman.raw.fetch(target, pluginsDir, {})
+                    .fail(function(err) {
+                        return Q.reject(new Error('Error fetching plugin: ' + err));
+                    })
+                    .then(function(dir) {
+                        // Iterate (in serial!) over all platforms in the project and install the plugin.
+                        return Q.all(platformList.map(function(platform) {
+                            var platforms = require('../platforms');
+                            var platformRoot = path.join(projectRoot, 'platforms', platform),
+                                parser = new platforms[platform].parser(platformRoot),
+                                options = {
+                                    www_dir: parser.staging_dir(),
+                                    cli_variables: {}
+                                },
+                                tokens,
+                                key,
+                                i;
+                            //parse variables into cli_variables
+                            for (i=0; i< opts.options.length; i++) {
+                                if (opts.options[i] === "--variable" && typeof opts.options[++i] === "string") {
+                                    tokens = opts.options[i].split('=');
+                                    key = tokens.shift().toUpperCase();
+                                    if (/^[\w-_]+$/.test(key)) {
+                                        options.cli_variables[key] = tokens.join('=');
                                     }
-
-                                    events.emit('log', 'Calling plugman.install on plugin "' + dir + '" for platform "' + platform + '" with options "' + JSON.stringify(options)  + '"');
-                                    plugman.install(platform, platformRoot, path.basename(dir), pluginsDir, options, function() {
-                                        doInstall(platformIndex+1);
-                                    });
-                                };
-                                doInstall(0);
+                                }
                             }
-                        });
+
+                            events.emit('log', 'Calling plugman.install on plugin "' + dir + '" for platform "' + platform + '" with options "' + JSON.stringify(options)  + '"');
+                            return plugman.raw.install(platform, platformRoot, path.basename(dir), pluginsDir, options);
+                        }));
                     });
-                }
+                })); // end Q.all
+            }).then(function() {
+                return hooks.fire('after_plugin_add', opts);
             });
             break;
         case 'rm':
         case 'remove':
-            var end = n(opts.plugins.length, function() {
-                hooks.fire('after_plugin_rm', opts, function(err) {
-                    if (err) {
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
-                        if (callback) callback();
-                    }
-                });
-            });
-            hooks.fire('before_plugin_rm', opts, function(err) {
-                if (err) {
-                    if (callback) callback(err);
-                    else throw err;
-                } else {
-                    opts.plugins.forEach(function(target, index) {
-                        // Check if we have the plugin.
-                        if (plugins.indexOf(target) > -1) {
-                            var targetPath = path.join(pluginPath, target);
-                            // Check if there is at least one match between plugin
-                            // supported platforms and app platforms
-                            var pluginXml = new cordova_util.plugin_parser(path.join(targetPath, 'plugin.xml'));
-                            var intersection = pluginXml.platforms.filter(function(e) {
-                                if (platformList.indexOf(e) == -1) return false;
-                                else return true;
-                            });
-
-                            // Iterate over all installed platforms and uninstall.
-                            // If this is a web-only or dependency-only plugin, then
-                            // there may be nothing to do here except remove the
-                            // reference from the platform's plugin config JSON.
-                            var plugman = require('plugman');
-                            platformList.forEach(function(platform) {
+            return hooks.fire('before_plugin_rm', opts)
+            .then(function() {
+                return Q.all(opts.plugins.map(function(target, index) {
+                    // Check if we have the plugin.
+                    if (plugins.indexOf(target) > -1) {
+                        var targetPath = path.join(pluginPath, target);
+                        // Iterate over all installed platforms and uninstall.
+                        // If this is a web-only or dependency-only plugin, then
+                        // there may be nothing to do here except remove the
+                        // reference from the platform's plugin config JSON.
+                        var plugman = require('plugman');
+                        return Q.all(
+                            platformList.map(function(platform) {
                                 var platformRoot = path.join(projectRoot, 'platforms', platform);
                                 var platforms = require('../platforms');
                                 var parser = new platforms[platform].parser(platformRoot);
                                 events.emit('log', 'Calling plugman.uninstall on plugin "' + target + '" for platform "' + platform + '"');
-                                plugman.uninstall.uninstallPlatform(platform, platformRoot, target, path.join(projectRoot, 'plugins'), { www_dir: parser.staging_dir() });
-                            });
-                            plugman.uninstall.uninstallPlugin(target, path.join(projectRoot, 'plugins'), end);
-                        } else {
-                            var err = new Error('Plugin "' + target + '" not added to project.');
-                            if (callback) callback(err);
-                            else throw err;
-                            return;
-                        }
-                    });
-                }
+                                return plugman.raw.uninstall.uninstallPlatform(platform, platformRoot, target, path.join(projectRoot, 'plugins'), { www_dir: parser.staging_dir() });
+                            })
+                        ).then(function() {
+                            return plugman.raw.uninstall.uninstallPlugin(target, path.join(projectRoot, 'plugins'));
+                        });
+                    } else {
+                        return Q.reject(new Error('Plugin "' + target + '" not added to project.'));
+                    }
+                }));
+            }).then(function() {
+                return hooks.fire('after_plugin_rm', opts);
             });
             break;
         case 'search':
-            hooks.fire('before_plugin_search', function(err) {
-                if (err) {
-                    if(callback) callback(err);
-                    else throw err;
-                } else {
-                    var plugman = require('plugman');
-                    plugman.search(opts.plugins, function(err, plugins) {
-                        if(err) return console.log(err);
-                        for(var plugin in plugins) {
-                            console.log();
-                            events.emit('results', plugins[plugin].name, '-', plugins[plugin].description || 'no description provided');
-                        }
-                        hooks.fire('after_plugin_search', function(err) {
-                            if(err) {
-                                if(callback) callback(err);
-                                else throw err;
-                            }
-                        });
-                    });
-
+            return hooks.fire('before_plugin_search')
+            .then(function() {
+                var plugman = require('plugman');
+                return plugman.raw.search(opts.plugins);
+            }).then(function(plugins) {
+                for(var plugin in plugins) {
+                    events.emit('results', plugins[plugin].name, '-', plugins[plugin].description || 'no description provided');
                 }
-
+            }).then(function() {
+                return hooks.fire('after_plugin_search');
             });
             break;
         case 'ls':
         case 'list':
         default:
-            hooks.fire('before_plugin_ls', function(err) {
-                if (err) {
-                    if (callback) callback(err);
-                    else throw err;
-                } else {
-                    events.emit('results', (plugins.length ? plugins : 'No plugins added. Use `cordova plugin add <plugin>`.'));
-                    hooks.fire('after_plugin_ls', function(err) {
-                        if (err) {
-                            if (callback) callback(err);
-                            else throw err;
-                        } else {
-                            if (callback) callback(undefined, plugins);
-                        }
-                    });
-                }
+            return hooks.fire('before_plugin_ls')
+            .then(function() {
+                events.emit('results', (plugins.length ? plugins : 'No plugins added. Use `cordova plugin add <plugin>`.'));
+                return hooks.fire('after_plugin_ls')
+                .then(function() {
+                    return plugins;
+                });
             });
             break;
     }

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/prepare.js
----------------------------------------------------------------------
diff --git a/src/prepare.js b/src/prepare.js
index fa53443..3927cea 100644
--- a/src/prepare.js
+++ b/src/prepare.js
@@ -28,15 +28,16 @@ var cordova_util      = require('./util'),
     config            = require('./config'),
     events            = require('./events'),
     n                 = require('ncallbacks'),
+    Q                 = require('q'),
     prompt            = require('prompt'),
     plugman           = require('plugman'),
     util              = require('util');
 
-module.exports = function prepare(options, callback) {
+// Returns a promise.
+module.exports = function prepare(options) {
     var projectRoot = cordova_util.isCordova(process.cwd());
 
-    if (options instanceof Function && callback === undefined) {
-        callback = options;
+    if (!options) {
         options = {
             verbose: false,
             platforms: [],
@@ -46,8 +47,7 @@ module.exports = function prepare(options, callback) {
 
     options = cordova_util.preProcessOptions(options);
     if (options.constructor.name === "Error") {
-        if (callback) return callback(options)
-        else throw options;
+        return Q.reject(options);
     }
 
     var xml = cordova_util.projectConfig(projectRoot);
@@ -59,55 +59,38 @@ module.exports = function prepare(options, callback) {
     options.paths = paths;
 
     var hooks = new hooker(projectRoot);
-    hooks.fire('before_prepare', options, function(err) {
-        if (err) {
-            if (callback) callback(err);
-            else throw err;
-        } else {
-            var cfg = new cordova_util.config_parser(xml);
-            var end = n(options.platforms.length, function() {
-                hooks.fire('after_prepare', options, function(err) {
-                    if (err) {
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
-                        if (callback) callback();
-                    }
-                });
-            });
+    return hooks.fire('before_prepare', options)
+    .then(function() {
+        var cfg = new cordova_util.config_parser(xml);
 
-            // Iterate over each added platform
-            options.platforms.forEach(function(platform) {
-                lazy_load.based_on_config(projectRoot, platform, function(err) {
-                    if (err) {
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
-                        var platformPath = path.join(projectRoot, 'platforms', platform);
-                        var parser = new platforms[platform].parser(platformPath);
-                        parser.update_project(cfg, function() {
-                            // Call plugman --prepare for this platform. sets up js-modules appropriately.
-                            var plugins_dir = path.join(projectRoot, 'plugins');
-                            events.emit('log', 'Calling plugman.prepare for platform "' + platform + '"');
-                            plugman.prepare(platformPath, platform, plugins_dir);
-                            // Make sure that config changes for each existing plugin is in place
-                            var plugins = cordova_util.findPlugins(plugins_dir);
-                            var platform_json = plugman.config_changes.get_platform_json(plugins_dir, platform);
-                            plugins && plugins.forEach(function(plugin_id) {
-                                if (platform_json.installed_plugins[plugin_id]) {
-                                    events.emit('log', 'Ensuring plugin "' + plugin_id + '" is installed correctly...');
-                                    plugman.config_changes.add_plugin_changes(platform, platformPath, plugins_dir, plugin_id, /* variables for plugin */ platform_json.installed_plugins[plugin_id], /* top level plugin? */ true, /* should increment config munge? cordova-cli never should, only plugman */ false);
-                                } else if (platform_json.dependent_plugins[plugin_id]) {
-                                    events.emit('log', 'Ensuring plugin "' + plugin_id + '" is installed correctly...');
-                                    plugman.config_changes.add_plugin_changes(platform, platformPath, plugins_dir, plugin_id, /* variables for plugin */ platform_json.dependent_plugins[plugin_id], /* top level plugin? */ false, /* should increment config munge? cordova-cli never should, only plugman */ false);
-                                }
-                                events.emit('log', 'Plugin "' + plugin_id + '" is good to go.');
-                            });
-                            end();
-                        });
+        // Iterate over each added platform
+        return Q.all(options.platforms.map(function(platform) {
+            var platformPath = path.join(projectRoot, 'platforms', platform);
+            return lazy_load.based_on_config(projectRoot, platform)
+            .then(function() {
+                var parser = new platforms[platform].parser(platformPath);
+                return parser.update_project(cfg);
+            }).then(function() {
+                // Call plugman --prepare for this platform. sets up js-modules appropriately.
+                var plugins_dir = path.join(projectRoot, 'plugins');
+                events.emit('log', 'Calling plugman.prepare for platform "' + platform + '"');
+                plugman.prepare(platformPath, platform, plugins_dir);
+                // Make sure that config changes for each existing plugin is in place
+                var plugins = cordova_util.findPlugins(plugins_dir);
+                var platform_json = plugman.config_changes.get_platform_json(plugins_dir, platform);
+                plugins && plugins.forEach(function(plugin_id) {
+                    if (platform_json.installed_plugins[plugin_id]) {
+                        events.emit('log', 'Ensuring plugin "' + plugin_id + '" is installed correctly...');
+                        plugman.config_changes.add_plugin_changes(platform, platformPath, plugins_dir, plugin_id, /* variables for plugin */ platform_json.installed_plugins[plugin_id], /* top level plugin? */ true, /* should increment config munge? cordova-cli never should, only plugman */ false);
+                    } else if (platform_json.dependent_plugins[plugin_id]) {
+                        events.emit('log', 'Ensuring plugin "' + plugin_id + '" is installed correctly...');
+                        plugman.config_changes.add_plugin_changes(platform, platformPath, plugins_dir, plugin_id, /* variables for plugin */ platform_json.dependent_plugins[plugin_id], /* top level plugin? */ false, /* should increment config munge? cordova-cli never should, only plugman */ false);
                     }
+                    events.emit('log', 'Plugin "' + plugin_id + '" is good to go.');
                 });
             });
-        }
+        })).then(function() {
+            return hooks.fire('after_prepare', options);
+        });
     });
 };

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/run.js
----------------------------------------------------------------------
diff --git a/src/run.js b/src/run.js
index 3d7053b..61ac0f3 100644
--- a/src/run.js
+++ b/src/run.js
@@ -18,14 +18,13 @@
 */
 var cordova_util      = require('./util'),
     path              = require('path'),
-    fs                = require('fs'),
-    shell             = require('shelljs'),
     hooker            = require('./hooker'),
-    platforms         = require('./../platforms'),
     events            = require('./events'),
-    n                 = require('ncallbacks');
+    Q                 = require('q'),
+    child_process     = require('child_process');
 
-function shell_out_to_run(projectRoot, platform, options, error_callback, done) {
+// Returns a promise.
+function shell_out_to_run(projectRoot, platform, options) {
     var cmd = '"' + path.join(projectRoot, 'platforms', platform, 'cordova', 'run') + '" ' + ( options.length ? options.join(" ") : '--device');
     // TODO: inconsistent API for BB10 run command
 /*    if (platform == 'blackberry') {
@@ -43,24 +42,24 @@ function shell_out_to_run(projectRoot, platform, options, error_callback, done)
     }
 */
     events.emit('log', 'Running app on platform "' + platform + '" with command "' + cmd + '" (output to follow)...');
-    shell.exec(cmd, {silent:true, async:true}, function(code, output) {
+    var d = Q.defer();
+    child_process.exec(cmd, function(err, output, stderr) {
         events.emit('log', output);
-        if (code > 0) {
-            var err = new Error('An error occurred while running the ' + platform + ' project. ' + output);
-            if (error_callback) error_callback(err);
-            else throw err;
+        if (err) {
+            d.reject(new Error('An error occurred while running the ' + platform + ' project. ' + output + stderr));
         } else {
             events.emit('log', 'Platform "' + platform + '" ran successfully.');
-            if (done) done();
+            d.resolve();
         }
     });
+    return d.promise;
 }
 
-module.exports = function run(options, callback) {
+// Returns a promise.
+module.exports = function run(options) {
     var projectRoot = cordova_util.isCordova(process.cwd());
 
-    if (options instanceof Function && callback === undefined) {
-        callback = options;
+    if (!options) {
         options = {
             verbose: false,
             platforms: [],
@@ -70,43 +69,20 @@ module.exports = function run(options, callback) {
 
     options = cordova_util.preProcessOptions(options);
     if (options.constructor.name === "Error") {
-        if (callback) return callback(options)
-        else throw options;
+        return Q.reject(options);
     }
 
     var hooks = new hooker(projectRoot);
-    hooks.fire('before_run', options, function(err) {
-        if (err) {
-            if (callback) callback(err);
-            else throw err;
-        } else {
-            var end = n(options.platforms.length, function() {
-                hooks.fire('after_run', options, function(err) {
-                    if (err) {
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
-                        if (callback) callback();
-                    }
-                });
-            });
-
-            // Run a prepare first, then shell out to run
-            require('../cordova').prepare(options.platforms, function(err) {
-                if (err) {
-                    if (callback) callback(err);
-                    else throw err;
-                } else {
-                    options.platforms.forEach(function(platform) {
-                        try {
-                            shell_out_to_run(projectRoot, platform, options.options, callback, end);
-                        } catch(e) {
-                            if (callback) callback(e);
-                            else throw e;
-                        }
-                    });
-                }
-            });
-        }
+    return hooks.fire('before_run', options)
+    .then(function() {
+        // Run a prepare first, then shell out to run
+        return require('../cordova').raw.prepare(options.platforms)
+        .then(function() {
+            return Q.all(options.platforms.map(function(platform) {
+                return shell_out_to_run(projectRoot, platform, options.options);
+            }));
+        }).then(function() {
+            return hooks.fire('after_run', options);
+        });
     });
 };