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:10 UTC

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

Updated Branches:
  refs/heads/master f8494a2be -> 017b7d9b6


http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/util.js
----------------------------------------------------------------------
diff --git a/src/util.js b/src/util.js
index 1a4e4d5..83c3284 100644
--- a/src/util.js
+++ b/src/util.js
@@ -134,12 +134,36 @@ exports = module.exports = {
     }
 };
 
-function addModuleProperty(module, symbol, modulePath, opt_obj) {
+// opt_wrap is a boolean: True means that a callback-based wrapper for the promise-based function
+// should be created.
+function addModuleProperty(module, symbol, modulePath, opt_wrap, opt_obj) {
     var val = null;
-    Object.defineProperty(opt_obj || module.exports, symbol, {
-        get : function() { return val = val || module.require(modulePath); },
-        set : function(v) { val = v; }
-    });
+    if (opt_wrap) {
+        module.exports[symbol] = function() {
+            val = val || module.require(modulePath);
+            if (arguments.length && typeof arguments[arguments.length - 1] === 'function') {
+                // If args exist and the last one is a function, it's the callback.
+                var args = Array.prototype.slice.call(arguments);
+                var cb = args.pop();
+                val.apply(module.exports, args).done(cb, cb);
+            } else {
+                val.apply(module.exports, arguments).done(null, function(err) { throw err; });
+            }
+        };
+    } else {
+        Object.defineProperty(opt_obj || module.exports, symbol, {
+            get : function() { return val = val || module.require(modulePath); },
+            set : function(v) { val = v; }
+        });
+    }
+
+    // Add the module.raw.foo as well.
+    if(module.exports.raw) {
+        Object.defineProperty(module.exports.raw, symbol, {
+            get : function() { return val = val || module.require(modulePath); },
+            set : function(v) { val = v; }
+        });
+    }
 }
 
 addModuleProperty(module, 'config_parser', './config_parser');

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/xml-helpers.js
----------------------------------------------------------------------
diff --git a/src/xml-helpers.js b/src/xml-helpers.js
index 1d2a36a..b1fb459 100644
--- a/src/xml-helpers.js
+++ b/src/xml-helpers.js
@@ -26,13 +26,17 @@ var fs = require('fs')
   , et = require('elementtree');
 
 module.exports = {
-    moveProjFile: function(origFile, projPath, callback) {
+    // Returns a promise.
+    moveProjFile: function(origFile, projPath) {
         var src = path.resolve(projPath, origFile)
           , dest = src.replace('.orig', '');
 
+        var d = Q.defer();
         fs.createReadStream(src)
             .pipe(fs.createWriteStream(dest))
-            .on('close', callback);
+            .on('close', d.resolve)
+            .on('error', d.reject);
+        return d.promise;
     },
 
     // compare two et.XML nodes, see if they match


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

Posted by br...@apache.org.
Refactor to use Q.js promises in place of callbacks everywhere.

This significantly cleans up many parts of the code, and generally
shortens it by 25%, by dropping redundant error checks.

NOTE: This DOES NOT change the "public" API of CLI, in the sense
that cordova.foo still takes a callback. Under the hood, it wraps a call
to cordova.raw.foo. If you downstream CLI and require modules like
platform.js directly, (a) stop, and (b) you'll have to port to promises.

I created the cordova-3.1.x branch for all bugfixes for the 3.1.0
release, since this code should probably bake for a while before being
released to npm.

Mostly fixed tests.

Fixed all tests (expect the broken BB10 ones)

Cleaning up debugging output

First pass over async refactoring.

build spec passing

CLI and Compile specs passing.

Create tests repaired.

Fixed tests for emulate, hooker, and lazy_load.

Fixed platform tests.

Fixed plugin tests.

Prepare tests fixed.

Run tests passing.

Fixing some of the parser tests.

Platform parser tests all fixed.

And the final burst of tests fixed.

Fix a few error messages and other problems.

Refactor to preserve top-level semantics.

Add tests for the cordova.foo -> cordova.raw.foo wrappers


Project: http://git-wip-us.apache.org/repos/asf/cordova-cli/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-cli/commit/017b7d9b
Tree: http://git-wip-us.apache.org/repos/asf/cordova-cli/tree/017b7d9b
Diff: http://git-wip-us.apache.org/repos/asf/cordova-cli/diff/017b7d9b

Branch: refs/heads/master
Commit: 017b7d9b6bfa4510f4ada608da3aefe597d710cb
Parents: f8494a2
Author: Braden Shepherdson <br...@gmail.com>
Authored: Fri Sep 13 09:16:00 2013 -0400
Committer: Braden Shepherdson <br...@gmail.com>
Committed: Mon Sep 23 13:59:17 2013 -0400

----------------------------------------------------------------------
 cordova.js                              |  21 +-
 package.json                            |   1 +
 platforms.js                            |   2 +-
 spec/build.spec.js                      |  78 ++++---
 spec/cli.spec.js                        |  19 +-
 spec/compile.spec.js                    |  62 ++---
 spec/create.spec.js                     |  33 ++-
 spec/emulate.spec.js                    |  69 +++---
 spec/hooker.spec.js                     | 130 +++++------
 spec/lazy_load.spec.js                  | 155 ++++++++-----
 spec/metadata/android_parser.spec.js    |  50 ++--
 spec/metadata/blackberry_parser.spec.js |  80 ++++---
 spec/metadata/ios_parser.spec.js        | 111 +++++----
 spec/metadata/wp7_parser.spec.js        |  64 +++---
 spec/metadata/wp8_parser.spec.js        |  64 +++---
 spec/platform.spec.js                   | 275 ++++++++++++----------
 spec/plugin.spec.js                     | 240 +++++++++++--------
 spec/prepare.spec.js                    | 159 +++++++------
 spec/run.spec.js                        |  88 ++++---
 spec/serve.spec.js                      |   6 +-
 spec/wrappers.spec.js                   |  39 ++++
 src/build.js                            |  52 ++---
 src/cli.js                              |   8 +-
 src/compile.js                          |  66 ++----
 src/config.js                           |   4 +-
 src/create.js                           |  40 ++--
 src/emulate.js                          |  77 ++-----
 src/help.js                             |  26 +--
 src/hooker.js                           |  75 +++---
 src/lazy_load.js                        |  55 ++---
 src/metadata/android_parser.js          |  35 +--
 src/metadata/blackberry10_parser.js     |  25 +-
 src/metadata/firefoxos_parser.js        |  62 ++---
 src/metadata/ios_parser.js              |  56 +++--
 src/metadata/wp7_parser.js              |  29 ++-
 src/metadata/wp8_parser.js              |  31 ++-
 src/platform.js                         | 332 +++++++++++----------------
 src/plugin.js                           | 260 ++++++++-------------
 src/prepare.js                          |  85 +++----
 src/run.js                              |  74 ++----
 src/util.js                             |  34 ++-
 src/xml-helpers.js                      |   8 +-
 42 files changed, 1586 insertions(+), 1594 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/cordova.js
----------------------------------------------------------------------
diff --git a/cordova.js b/cordova.js
index 277c12a..bca8981 100644
--- a/cordova.js
+++ b/cordova.js
@@ -38,21 +38,22 @@ exports = module.exports = {
     },
     emit:      emit,
     trigger:   emit,
+    raw: {}
 };
 
-addModuleProperty(module, 'prepare', './src/prepare');
-addModuleProperty(module, 'build', './src/build');
+addModuleProperty(module, 'prepare', './src/prepare', true);
+addModuleProperty(module, 'build', './src/build', true);
 addModuleProperty(module, 'help', './src/help');
 addModuleProperty(module, 'config', './src/config');
-addModuleProperty(module, 'create', './src/create');
+addModuleProperty(module, 'create', './src/create', true);
 addModuleProperty(module, 'ripple', './src/ripple');
-addModuleProperty(module, 'emulate', './src/emulate');
-addModuleProperty(module, 'plugin', './src/plugin');
-addModuleProperty(module, 'plugins', './src/plugin');
+addModuleProperty(module, 'emulate', './src/emulate', true);
+addModuleProperty(module, 'plugin', './src/plugin', true);
+addModuleProperty(module, 'plugins', './src/plugin', true);
 addModuleProperty(module, 'serve', './src/serve');
-addModuleProperty(module, 'platform', './src/platform');
-addModuleProperty(module, 'platforms', './src/platform');
-addModuleProperty(module, 'compile', './src/compile');
-addModuleProperty(module, 'run', './src/run');
+addModuleProperty(module, 'platform', './src/platform', true);
+addModuleProperty(module, 'platforms', './src/platform', true);
+addModuleProperty(module, 'compile', './src/compile', true);
+addModuleProperty(module, 'run', './src/run', true);
 
 

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 1c8ab4f..9f8ff2d 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
     "tar": "0.1.x",
     "open": "0.0.3",
     "npm": "1.3.x",
+    "q": "~0.9",
     "optimist": "0.6.0",
     "mime": "~1.2.11"
   },

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/platforms.js
----------------------------------------------------------------------
diff --git a/platforms.js b/platforms.js
index d34c112..82ba8c8 100644
--- a/platforms.js
+++ b/platforms.js
@@ -58,6 +58,6 @@ var addModuleProperty = require('./src/util').addModuleProperty;
 Object.keys(module.exports).forEach(function(key) {
     var obj = module.exports[key];
     if (obj.parser) {
-        addModuleProperty(module, 'parser', obj.parser, obj);
+        addModuleProperty(module, 'parser', obj.parser, false, obj);
     }
 });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/build.spec.js
----------------------------------------------------------------------
diff --git a/spec/build.spec.js b/spec/build.spec.js
index 801fc1b..a3159d4 100644
--- a/spec/build.spec.js
+++ b/spec/build.spec.js
@@ -22,6 +22,7 @@ var cordova = require('../cordova'),
     path = require('path'),
     fs = require('fs'),
     hooker = require('../src/hooker'),
+    Q = require('q'),
     util = require('../src/util');
 
 var supported_platforms = Object.keys(platforms).filter(function(p) { return p != 'www'; });
@@ -30,46 +31,59 @@ describe('build command', function() {
     var is_cordova, list_platforms, fire;
     var project_dir = '/some/path';
     var prepare_spy, compile_spy;
+    var result;
+
+    function buildPromise(f) {
+        f.then(function() { result = true; }, function(err) { result = err; });
+    }
+
+    function wrapper(f, post) {
+        runs(function() {
+            buildPromise(f);
+        });
+        waitsFor(function() { return result; }, 'promise never resolved', 500);
+        runs(post);
+    }
+
     beforeEach(function() {
         is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
-        fire = spyOn(hooker.prototype, 'fire').andCallFake(function(e, opts, cb) {
-            cb(false);
-        });
-        prepare_spy = spyOn(cordova, 'prepare').andCallFake(function(platforms, cb) {
-            cb();
-        });
-        compile_spy = spyOn(cordova, 'compile').andCallFake(function(platforms, cb) {
-            cb();
-        });
+        fire = spyOn(hooker.prototype, 'fire').andReturn(Q());
+        prepare_spy = spyOn(cordova.raw, 'prepare').andReturn(Q());
+        compile_spy = spyOn(cordova.raw, 'compile').andReturn(Q());
     });
-    describe('failure', function() {
+    describe('failure', function(done) {
         it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function() {
             list_platforms.andReturn([]);
-            expect(function() {
-                cordova.build();
-            }).toThrow('No platforms added to this project. Please use `cordova platform add <platform>`.');
+            runs(function() {
+                buildPromise(cordova.raw.build());
+            });
+            waitsFor(function() { return result; }, 'promise never resolved', 500);
+            runs(function() {
+                expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+            });
         });
         it('should not run outside of a Cordova-based project', function() {
             is_cordova.andReturn(false);
-            expect(function() {
-                cordova.build();
-            }).toThrow('Current working directory is not a Cordova-based project.');
+            wrapper(cordova.raw.build(), function() {
+                expect(result).toEqual(new Error('Current working directory is not a Cordova-based project.'));
+            });
         });
     });
 
     describe('success', function() {
         it('should run inside a Cordova-based project with at least one added platform and call both prepare and compile', function(done) {
-            cordova.build(['android','ios'], function(err) {
-                expect(prepare_spy).toHaveBeenCalledWith({verbose: false, platforms: ['android', 'ios'], options: []}, jasmine.any(Function));
-                expect(compile_spy).toHaveBeenCalledWith({verbose: false, platforms: ['android', 'ios'], options: []}, jasmine.any(Function));
+            debugger;
+            cordova.raw.build(['android','ios']).then(function() {
+                expect(prepare_spy).toHaveBeenCalledWith({verbose: false, platforms: ['android', 'ios'], options: []});
+                expect(compile_spy).toHaveBeenCalledWith({verbose: false, platforms: ['android', 'ios'], options: []});
                 done();
             });
         });
         it('should pass down options', function(done) {
-            cordova.build({platforms: ['android'], options: ['--release']}, function(err) {
-                expect(prepare_spy).toHaveBeenCalledWith({platforms: ['android'], options: ["--release"]}, jasmine.any(Function));
-                expect(compile_spy).toHaveBeenCalledWith({platforms: ['android'], options: ["--release"]}, jasmine.any(Function));
+            cordova.raw.build({platforms: ['android'], options: ['--release']}).then(function() {
+                expect(prepare_spy).toHaveBeenCalledWith({platforms: ['android'], options: ["--release"]});
+                expect(compile_spy).toHaveBeenCalledWith({platforms: ['android'], options: ["--release"]});
                 done();
             });
         });
@@ -77,13 +91,15 @@ describe('build command', function() {
 
     describe('hooks', function() {
         describe('when platforms are added', function() {
-            it('should fire before hooks through the hooker module', function() {
-                cordova.build(['android', 'ios']);
-                expect(fire).toHaveBeenCalledWith('before_build', {verbose: false, platforms:['android', 'ios'], options: []}, jasmine.any(Function));
+            it('should fire before hooks through the hooker module', function(done) {
+                cordova.raw.build(['android', 'ios']).then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_build', {verbose: false, platforms:['android', 'ios'], options: []});
+                    done();
+                });
             });
             it('should fire after hooks through the hooker module', function(done) {
-                cordova.build('android', function() {
-                     expect(fire).toHaveBeenCalledWith('after_build', {verbose: false, platforms:['android'], options: []}, jasmine.any(Function));
+                cordova.raw.build('android').then(function() {
+                     expect(fire).toHaveBeenCalledWith('after_build', {verbose: false, platforms:['android'], options: []});
                      done();
                 });
             });
@@ -92,10 +108,10 @@ describe('build command', function() {
         describe('with no platforms added', function() {
             it('should not fire the hooker', function() {
                 list_platforms.andReturn([]);
-                expect(function() {
-                    cordova.build();
-                }).toThrow();
-                expect(fire).not.toHaveBeenCalled();
+                wrapper(cordova.raw.build(), function() {
+                    expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+                    expect(fire).not.toHaveBeenCalled();
+                });
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/cli.spec.js
----------------------------------------------------------------------
diff --git a/spec/cli.spec.js b/spec/cli.spec.js
index 33c40cd..999ce62 100644
--- a/spec/cli.spec.js
+++ b/spec/cli.spec.js
@@ -16,7 +16,8 @@
     specific language governing permissions and limitations
     under the License.
 */
-var CLI = require("../src/cli");
+var CLI = require("../src/cli"),
+    Q = require('q'),
     cordova = require("../cordova");
 
 describe("cordova cli", function () {
@@ -47,7 +48,7 @@ describe("cordova cli", function () {
 
     describe("project commands other than plugin and platform", function () {
         beforeEach(function () {
-            spyOn(cordova, "build");
+            spyOn(cordova.raw, "build").andReturn(Q());
         });
 
         afterEach(function () {
@@ -56,33 +57,33 @@ describe("cordova cli", function () {
 
         it("will call command with all arguments passed through", function () {
             new CLI(["node", "cordova", "build", "blackberry10", "-k", "abcd1234"]);
-            expect(cordova.build).toHaveBeenCalledWith({verbose: false, platforms: ["blackberry10"], options: ["-k", "abcd1234"]});
+            expect(cordova.raw.build).toHaveBeenCalledWith({verbose: false, platforms: ["blackberry10"], options: ["-k", "abcd1234"]});
         });
 
         it("will consume the first instance of -d", function () {
             new CLI(["node", "cordova", "-d", "build", "blackberry10", "-k", "abcd1234", "-d"]);
-            expect(cordova.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "-d"]});
+            expect(cordova.raw.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "-d"]});
         });
 
         it("will consume the first instance of --verbose", function () {
             new CLI(["node", "cordova", "--verbose", "build", "blackberry10", "-k", "abcd1234", "--verbose"]);
-            expect(cordova.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "--verbose"]});
+            expect(cordova.raw.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "--verbose"]});
         });
 
         it("will consume the first instance of either --verbose of -d", function () {
             new CLI(["node", "cordova", "--verbose", "build", "blackberry10", "-k", "abcd1234", "-d"]);
-            expect(cordova.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "-d"]});
+            expect(cordova.raw.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "-d"]});
         });
 
         it("will consume the first instance of either --verbose of -d", function () {
             new CLI(["node", "cordova", "-d", "build", "blackberry10", "-k", "abcd1234", "--verbose"]);
-            expect(cordova.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "--verbose"]});
+            expect(cordova.raw.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "--verbose"]});
         });
     });
 
     describe("plugin", function () {
         beforeEach(function () {
-            spyOn(cordova, "plugin");
+            spyOn(cordova.raw, "plugin").andReturn(Q());
         });
 
         afterEach(function () {
@@ -91,7 +92,7 @@ describe("cordova cli", function () {
 
         it("will call command with all arguments passed through", function () {
             new CLI(["node", "cordova", "plugin", "add", "facebook", "--variable", "FOO=foo"]);
-            expect(cordova.plugin).toHaveBeenCalledWith("add", ["facebook", "--variable", "FOO=foo"]);
+            expect(cordova.raw.plugin).toHaveBeenCalledWith("add", ["facebook", "--variable", "FOO=foo"]);
         });
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/compile.spec.js
----------------------------------------------------------------------
diff --git a/spec/compile.spec.js b/spec/compile.spec.js
index 975fcdc..3b0fe0d 100644
--- a/spec/compile.spec.js
+++ b/spec/compile.spec.js
@@ -18,53 +18,61 @@
 */
 var cordova = require('../cordova'),
     platforms = require('../platforms'),
-    shell = require('shelljs'),
+    child_process = require('child_process'),
     path = require('path'),
     fs = require('fs'),
     hooker = require('../src/hooker'),
+    Q = require('q'),
     util = require('../src/util');
 
 var supported_platforms = Object.keys(platforms).filter(function(p) { return p != 'www'; });
 
 describe('compile command', function() {
-    var is_cordova, list_platforms, fire, exec;
+    var is_cordova, list_platforms, fire, exec, result;
     var project_dir = '/some/path';
+
+    function wrapper(f, post) {
+        runs(function() {
+            f.then(function() { result = true; }, function(err) { result = err; });
+        });
+        waitsFor(function() { return result; }, 'promise never resolved', 500);
+        runs(post);
+    }
     beforeEach(function() {
         is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
-        fire = spyOn(hooker.prototype, 'fire').andCallFake(function(e, opts, cb) {
-            cb(false);
-        });
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, '');
+        fire = spyOn(hooker.prototype, 'fire').andReturn(Q());
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(null, '', '');
         });
     });
     describe('failure', function() {
         it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function() {
             list_platforms.andReturn([]);
-            expect(function() {
-                cordova.compile();
-            }).toThrow('No platforms added to this project. Please use `cordova platform add <platform>`.');
+            wrapper(cordova.raw.compile(), function() {
+                expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+            });
         });
         it('should not run outside of a Cordova-based project', function() {
             is_cordova.andReturn(false);
-            expect(function() {
-                cordova.compile();
-            }).toThrow('Current working directory is not a Cordova-based project.');
+            wrapper(cordova.raw.compile(), function() {
+                expect(result).toEqual(new Error('Current working directory is not a Cordova-based project.'));
+            });
         });
     });
 
     describe('success', function() {
         it('should run inside a Cordova-based project with at least one added platform and shell out to build', function(done) {
-            cordova.compile(['android','ios'], function(err) {
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'android', 'cordova', 'build') + '"', jasmine.any(Object), jasmine.any(Function));
+            cordova.raw.compile(['android','ios']).then(function() {
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'android', 'cordova', 'build') + '"', jasmine.any(Function));
                 done();
             });
         });
         it('should pass down optional parameters', function (done) {
-            cordova.compile({platforms:["blackberry10"], options:["--release"]}, function (err) {
+            cordova.raw.compile({platforms:["blackberry10"], options:["--release"]}).then(function () {
                 var buildCommand = path.join(project_dir, 'platforms', 'blackberry10', 'cordova', 'build');
-                expect(exec).toHaveBeenCalledWith('"' + buildCommand + '" --release', jasmine.any(Object), jasmine.any(Function));
+                expect(exec).toHaveBeenCalledWith('"' + buildCommand + '" --release', jasmine.any(Function));
                 done();
             });
         });
@@ -72,13 +80,15 @@ describe('compile command', function() {
 
     describe('hooks', function() {
         describe('when platforms are added', function() {
-            it('should fire before hooks through the hooker module', function() {
-                cordova.compile(['android', 'ios']);
-                expect(fire).toHaveBeenCalledWith('before_compile', {verbose: false, platforms:['android', 'ios'], options: []}, jasmine.any(Function));
+            it('should fire before hooks through the hooker module', function(done) {
+                cordova.raw.compile(['android', 'ios']).then(function() {;
+                    expect(fire).toHaveBeenCalledWith('before_compile', {verbose: false, platforms:['android', 'ios'], options: []});
+                    done();
+                });
             });
             it('should fire after hooks through the hooker module', function(done) {
-                cordova.compile('android', function() {
-                     expect(fire).toHaveBeenCalledWith('after_compile', {verbose: false, platforms:['android'], options: []}, jasmine.any(Function));
+                cordova.raw.compile('android').then(function() {
+                     expect(fire).toHaveBeenCalledWith('after_compile', {verbose: false, platforms:['android'], options: []});
                      done();
                 });
             });
@@ -87,10 +97,10 @@ describe('compile command', function() {
         describe('with no platforms added', function() {
             it('should not fire the hooker', function() {
                 list_platforms.andReturn([]);
-                expect(function() {
-                    cordova.compile();
-                }).toThrow();
-                expect(fire).not.toHaveBeenCalled();
+                wrapper(cordova.raw.compile(), function() {
+                    expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+                    expect(fire).not.toHaveBeenCalled();
+                });
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/create.spec.js
----------------------------------------------------------------------
diff --git a/spec/create.spec.js b/spec/create.spec.js
index b56d624..e46569d 100644
--- a/spec/create.spec.js
+++ b/spec/create.spec.js
@@ -23,6 +23,7 @@ var cordova = require('../cordova'),
     util    = require('../src/util'),
     config    = require('../src/config'),
     lazy_load = require('../src/lazy_load'),
+    Q = require('q'),
     tempDir = path.join(__dirname, '..', 'temp');
 
 describe('create command', function () {
@@ -34,12 +35,8 @@ describe('create command', function () {
         config_spy = spyOn(cordova, 'config');
         config_read = spyOn(config, 'read').andReturn({});
         exists = spyOn(fs, 'existsSync').andReturn(false);
-        load_cordova = spyOn(lazy_load, 'cordova').andCallFake(function(platform, cb) {
-            cb();
-        });
-        load_custom = spyOn(lazy_load, 'custom').andCallFake(function(url, id, platform, version, cb) {
-            cb();
-        });
+        load_cordova = spyOn(lazy_load, 'cordova').andReturn(Q());
+        load_custom = spyOn(lazy_load, 'custom').andReturn(Q());
         package = jasmine.createSpy('config.packageName');
         name = jasmine.createSpy('config.name');
         parser = spyOn(util, 'config_parser').andReturn({
@@ -57,13 +54,13 @@ describe('create command', function () {
                 expect(h).toMatch(/synopsis/gi);
                 done();
             });
-            cordova.create();
+            cordova.raw.create();
         });
     });
 
     describe('success', function() {
         it('should create a default project if only directory is specified', function(done) {
-            cordova.create(tempDir, function() {
+            cordova.raw.create(tempDir).then(function() {
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, '.cordova'));
                 expect(package).toHaveBeenCalledWith('io.cordova.hellocordova');
                 expect(name).toHaveBeenCalledWith('HelloCordova');
@@ -71,7 +68,7 @@ describe('create command', function () {
             });
         });
         it('should create a default project if only directory and id is specified', function(done) {
-            cordova.create(tempDir, 'ca.filmaj.canucks', function() {
+            cordova.raw.create(tempDir, 'ca.filmaj.canucks').then(function() {
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, '.cordova'));
                 expect(package).toHaveBeenCalledWith('ca.filmaj.canucks');
                 expect(name).toHaveBeenCalledWith('HelloCordova');
@@ -79,7 +76,7 @@ describe('create command', function () {
             });
         });
         it('should create a project in specified directory with specified name and id', function(done) {
-            cordova.create(tempDir, 'ca.filmaj.canucks', 'IHateTheBruins', function() {
+            cordova.raw.create(tempDir, 'ca.filmaj.canucks', 'IHateTheBruins').then(function() {
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, '.cordova'));
                 expect(package).toHaveBeenCalledWith('ca.filmaj.canucks');
                 expect(name).toHaveBeenCalledWith('IHateTheBruins');
@@ -87,7 +84,7 @@ describe('create command', function () {
             });
         });
         it('should create top-level directory structure appropriate for a cordova-cli project', function(done) {
-            cordova.create(tempDir, function() {
+            cordova.raw.create(tempDir).then(function() {
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, 'platforms'));
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, 'merges'));
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, 'plugins'));
@@ -97,7 +94,7 @@ describe('create command', function () {
         });
         it('should create appropriate directories for hooks', function(done) {
             var hooks_dir = path.join(tempDir, '.cordova', 'hooks');
-            cordova.create(tempDir, function() {
+            cordova.raw.create(tempDir).then(function() {
                 expect(mkdir).toHaveBeenCalledWith('-p', hooks_dir);
                 expect(mkdir).toHaveBeenCalledWith( (path.join(hooks_dir, 'after_build')));
                 expect(mkdir).toHaveBeenCalledWith( (path.join(hooks_dir, 'after_compile')));
@@ -127,8 +124,8 @@ describe('create command', function () {
             });
         });
         it('should by default use cordova-app-hello-world as www assets', function(done) {
-            cordova.create(tempDir, function() {
-                expect(load_cordova).toHaveBeenCalledWith('www', jasmine.any(Function));
+            cordova.raw.create(tempDir).then(function() {
+                expect(load_cordova).toHaveBeenCalledWith('www');
                 done();
             });
         });
@@ -143,13 +140,13 @@ describe('create command', function () {
                 }
             };
             config_read.andReturn(fake_config);
-            cordova.create(tempDir, function() {
-                expect(load_custom).toHaveBeenCalledWith(fake_config.lib.www.uri, fake_config.lib.www.id, 'www', fake_config.lib.www.version, jasmine.any(Function));
+            cordova.raw.create(tempDir).then(function() {
+                expect(load_custom).toHaveBeenCalledWith(fake_config.lib.www.uri, fake_config.lib.www.id, 'www', fake_config.lib.www.version);
                 done();
             });
         });
         it('should add a missing www/config.xml', function(done) {
-            cordova.create(tempDir, function() {
+            cordova.raw.create(tempDir).then(function() {
                 expect(shell.cp).toHaveBeenCalledWith(
                     path.resolve(__dirname, '..', 'templates', 'config.xml'),
                     jasmine.any(String)
@@ -162,7 +159,7 @@ describe('create command', function () {
                 if (p.indexOf('config.xml') > -1) return true;
                 return false;
             });
-            cordova.create(tempDir, function() {
+            cordova.raw.create(tempDir).then(function() {
                 expect(shell.cp).not.toHaveBeenCalledWith(
                     path.resolve(__dirname, '..', 'templates', 'config.xml'),
                     jasmine.any(String)

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/emulate.spec.js
----------------------------------------------------------------------
diff --git a/spec/emulate.spec.js b/spec/emulate.spec.js
index 807553c..6dd05ca 100644
--- a/spec/emulate.spec.js
+++ b/spec/emulate.spec.js
@@ -18,57 +18,66 @@
 */
 var cordova = require('../cordova'),
     platforms = require('../platforms'),
-    shell = require('shelljs'),
+    child_process = require('child_process'),
     path = require('path'),
     fs = require('fs'),
     hooker = require('../src/hooker'),
+    Q = require('q'),
     util = require('../src/util');
 
 var supported_platforms = Object.keys(platforms).filter(function(p) { return p != 'www'; });
 
 describe('emulate command', function() {
-    var is_cordova, list_platforms, fire, exec;
+    var is_cordova, list_platforms, fire, exec, result;
     var project_dir = '/some/path';
     var prepare_spy;
+
+    function wrapper(f, post) {
+        runs(function() {
+            f.then(function() { result = true; }, function(err) { result = err; });
+        });
+        waitsFor(function() { return result; }, 'promise never resolved', 500);
+        runs(post);
+    }
+
     beforeEach(function() {
         is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
-        fire = spyOn(hooker.prototype, 'fire').andCallFake(function(e, opts, cb) {
-            cb(false);
-        });
-        prepare_spy = spyOn(cordova, 'prepare').andCallFake(function(platforms, cb) {
-            cb();
+        fire = spyOn(hooker.prototype, 'fire').andReturn(Q());
+        prepare_spy = spyOn(cordova.raw, 'prepare').andReturn(Q());
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(null, '', '');
         });
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) { cb(0, ''); });
     });
     describe('failure', function() {
         it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function() {
             list_platforms.andReturn([]);
-            expect(function() {
-                cordova.emulate();
-            }).toThrow('No platforms added to this project. Please use `cordova platform add <platform>`.');
+            wrapper(cordova.raw.emulate(), function() {
+                expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+            });
         });
         it('should not run outside of a Cordova-based project', function() {
             is_cordova.andReturn(false);
-            expect(function() {
-                cordova.emulate();
-            }).toThrow('Current working directory is not a Cordova-based project.');
+            wrapper(cordova.raw.emulate(), function() {
+                expect(result).toEqual(new Error('Current working directory is not a Cordova-based project.'));
+            });
         });
     });
 
     describe('success', function() {
         it('should run inside a Cordova-based project with at least one added platform and call prepare and shell out to the emulate script', function(done) {
-            cordova.emulate(['android','ios'], function(err) {
-                expect(prepare_spy).toHaveBeenCalledWith(['android', 'ios'], jasmine.any(Function));
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'android', 'cordova', 'run') + '" --emulator', jasmine.any(Object), jasmine.any(Function));
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'ios', 'cordova', 'run') + '" --emulator', jasmine.any(Object), jasmine.any(Function));
+            cordova.raw.emulate(['android','ios']).then(function(err) {
+                expect(prepare_spy).toHaveBeenCalledWith(['android', 'ios']);
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'android', 'cordova', 'run') + '" --emulator', jasmine.any(Function));
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'ios', 'cordova', 'run') + '" --emulator', jasmine.any(Function));
                 done();
             });
         });
         it('should pass down options', function(done) {
-            cordova.emulate({platforms: ['ios'], options:["--optionTastic"]}, function(err) {
-                expect(prepare_spy).toHaveBeenCalledWith(['ios'], jasmine.any(Function));
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'ios', 'cordova', 'run') + '" --optionTastic', jasmine.any(Object), jasmine.any(Function));
+            cordova.raw.emulate({platforms: ['ios'], options:["--optionTastic"]}).then(function(err) {
+                expect(prepare_spy).toHaveBeenCalledWith(['ios']);
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'ios', 'cordova', 'run') + '" --optionTastic', jasmine.any(Function));
                 done();
             });
         });
@@ -76,13 +85,15 @@ describe('emulate command', function() {
 
     describe('hooks', function() {
         describe('when platforms are added', function() {
-            it('should fire before hooks through the hooker module', function() {
-                cordova.emulate(['android', 'ios']);
-                expect(fire).toHaveBeenCalledWith('before_emulate', {verbose: false, platforms:['android', 'ios'], options: []}, jasmine.any(Function));
+            it('should fire before hooks through the hooker module', function(done) {
+                cordova.raw.emulate(['android', 'ios']).then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_emulate', {verbose: false, platforms:['android', 'ios'], options: []});
+                    done();
+                });
             });
             it('should fire after hooks through the hooker module', function(done) {
-                cordova.emulate('android', function() {
-                     expect(fire).toHaveBeenCalledWith('after_emulate', {verbose: false, platforms:['android'], options: []}, jasmine.any(Function));
+                cordova.raw.emulate('android').then(function() {
+                     expect(fire).toHaveBeenCalledWith('after_emulate', {verbose: false, platforms:['android'], options: []});
                      done();
                 });
             });
@@ -91,9 +102,9 @@ describe('emulate command', function() {
         describe('with no platforms added', function() {
             it('should not fire the hooker', function() {
                 list_platforms.andReturn([]);
-                expect(function() {
-                    cordova.emulate();
-                }).toThrow();
+                wrapper(cordova.raw.emulate(), function() {
+                    expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+                });
                 expect(fire).not.toHaveBeenCalled();
             });
         });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/hooker.spec.js
----------------------------------------------------------------------
diff --git a/spec/hooker.spec.js b/spec/hooker.spec.js
index 4711b17..c303e25 100644
--- a/spec/hooker.spec.js
+++ b/spec/hooker.spec.js
@@ -19,9 +19,11 @@
 var hooker = require('../src/hooker'),
     util   = require('../src/util'),
     shell  = require('shelljs'),
+    child_process = require('child_process'),
     path   = require('path'),
     fs     = require('fs'),
     os     = require('os'),
+    Q      = require('q'),
     tempDir= path.join(__dirname, '..', 'temp'),
     hooks  = path.join(__dirname, 'fixtures', 'hooks'),
     cordova= require('../cordova');
@@ -48,28 +50,26 @@ describe('hooker', function() {
     describe('global (static) fire method', function() {
         it('should execute listeners serially', function(done) {
             var timeout = 20;
-            var test_event = 'poop';
+            var test_event = 'foo';
             var h1_fired = false;
-            var h1 = function(root, cb) {
+            var h1 = function() {
                 h1_fired = true;
-                setTimeout(cb, timeout);
+                expect(h2_fired).toBe(false);
+                return Q();
             };
             var h2_fired = false;
             var h2 = function() {
                 h2_fired = true;
+                expect(h1_fired).toBe(true);
+                return Q();
             };
-            runs(function() {
-                cordova.on(test_event, h1);
-                cordova.on(test_event, h2);
-                hooker.fire(test_event, function(err) {
-                    done();
-                });
+
+            cordova.on(test_event, h1);
+            cordova.on(test_event, h2);
+            hooker.fire(test_event).then(function() {
                 expect(h1_fired).toBe(true);
-                expect(h2_fired).toBe(false);
-            });
-            waits(timeout);
-            runs(function() {
                 expect(h2_fired).toBe(true);
+                done();
             });
         });
     });
@@ -81,10 +81,14 @@ describe('hooker', function() {
         });
 
         describe('failure', function() {
-            it('should not error if the hook is unrecognized', function(done) {
-                h.fire('CLEAN YOUR SHORTS GODDAMNIT LIKE A BIG BOY!', function(err){
-                    expect(err).not.toBeDefined();
-                    done();
+            it('should not error if the hook is unrecognized', function() {
+                var p;
+                runs(function() {
+                    p = h.fire('CLEAN YOUR SHORTS GODDAMNIT LIKE A BIG BOY!');
+                });
+                waitsFor(function() { return !p.isPending() }, 'promise not resolved', 500);
+                runs(function() {
+                    expect(p.isFulfilled()).toBe(true);
                 });
             });
             it('should error if any script exits with non-zero code', function(done) {
@@ -99,10 +103,11 @@ describe('hooker', function() {
                     shell.cp(path.join(hooks, 'fail', 'fail.sh'), script);
                 }
                 fs.chmodSync(script, '754');
-                h.fire('before_build', function(err){
+                h.fire('before_build').then(function() {
+                    expect('the call').toBe('a failure');
+                }, function(err) {
                     expect(err).toBeDefined();
-                    done();
-                });
+                }).fin(done);
             });
         });
 
@@ -122,16 +127,16 @@ describe('hooker', function() {
                     fs.readdirSync(hook).forEach(function(script) {
                         fs.chmodSync(path.join(hook, script), '754');
                     });
-                    s = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-                        cb(0, '');
+                    s = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+                        if (!cb) cb = opts;
+                        cb(0, '', '');
                     });
                 });
                 afterEach(function() {
                     shell.rm('-rf', tempDir);
                 });
                 it('should execute all scripts in order and fire callback', function(done) {
-                    h.fire('before_build', function(err) {
-                        expect(err).not.toBeDefined();
+                    h.fire('before_build').then(function() {
                         if (platform.match(/(win32|win64)/)) {
                             expect(s.calls[0].args[0]).toMatch(/0.bat/);
                             expect(s.calls[1].args[0]).toMatch(/1.bat/);
@@ -139,12 +144,12 @@ describe('hooker', function() {
                             expect(s.calls[0].args[0]).toMatch(/0.sh/);
                             expect(s.calls[1].args[0]).toMatch(/1.sh/);
                         }
-                        done();
-                    });
+                    }, function(err) {
+                        expect(err).not.toBeDefined();
+                    }).fin(done);
                 });
                 it('should pass the project root folder as parameter into the project-level hooks', function(done) {
-                    h.fire('before_build', function(err) {
-                        expect(err).not.toBeDefined();
+                    h.fire('before_build').then(function() {
                         var param_str;
                         if (platform.match(/(win32|win64)/)) {
                             param_str = '0.bat "'+tempDir+'"';
@@ -152,20 +157,21 @@ describe('hooker', function() {
                             param_str = '0.sh "'+tempDir+'"'; 
                         }
                         expect(s.calls[0].args[0].indexOf(param_str)).not.toEqual(-1);
-                        done();
-                    });
+                    }, function(err) {
+                        expect(err).toBeUndefined();
+                    }).fin(done);
                 });
                 it('should skip any files starting with a dot on the scripts', function(done) {
                     shell.cp(path.join(hooks, 'test', '0.bat'), path.join(hook, '.swp.file'));
-                    h.fire('before_build', function(err) {
-                        expect(err).not.toBeDefined();
+                    h.fire('before_build').then(function() {
                         expect(s).not.toHaveBeenCalledWith(path.join(tempDir, '.cordova', 'hooks', 'before_build', '.swp.file') + ' "' + tempDir + '"', jasmine.any(Object), jasmine.any(Function));
-                        done();
-                    });
+                    }, function(err) {
+                        expect(err).not.toBeDefined();
+                    }).fin(done);
                 });
             });
             describe('module-level hooks', function() {
-                var handler = jasmine.createSpy();
+                var handler = jasmine.createSpy().andReturn(Q());
                 var test_event = 'before_build';
                 afterEach(function() {
                     cordova.removeAllListeners(test_event);
@@ -174,51 +180,49 @@ describe('hooker', function() {
 
                 it('should fire handlers using cordova.on', function(done) {
                     cordova.on(test_event, handler);
-                    h.fire(test_event, function(err) {
+                    h.fire(test_event).then(function() {
                         expect(handler).toHaveBeenCalled();
+                    }, function(err) {
                         expect(err).not.toBeDefined();
-                        done();
-                    });
+                    }).fin(done);
                 });
                 it('should pass the project root folder as parameter into the module-level handlers', function(done) {
                     cordova.on(test_event, handler);
-                    h.fire(test_event, function(err) {
+                    h.fire(test_event).then(function() {
                         expect(handler).toHaveBeenCalledWith({root:tempDir});
+                    }, function(err) {
                         expect(err).not.toBeDefined();
-                        done();
-                    });
+                    }).fin(done);
                 });
                 it('should be able to stop listening to events using cordova.off', function(done) {
                     cordova.on(test_event, handler);
                     cordova.off(test_event, handler);
-                    h.fire(test_event, function(err) {
+                    h.fire(test_event).then(function() {
                         expect(handler).not.toHaveBeenCalled();
-                        done();
-                    });
+                    }, function(err) {
+                        expect(err).toBeUndefined();
+                    }).fin(done);
                 });
                 it('should allow for hook to opt into asynchronous execution and block further hooks from firing using the done callback', function(done) {
                     var h1_fired = false;
-                    var timeout = 19;
-                    var h1 = function(root, cb) {
+                    var h1 = function() {
                         h1_fired = true;
-                        setTimeout(cb, timeout);
+                        expect(h2_fired).toBe(false);
+                        return Q();
                     };
                     var h2_fired = false;
                     var h2 = function() {
                         h2_fired = true;
+                        expect(h1_fired).toBe(true);
+                        return Q();
                     };
-                    runs(function() {
-                        cordova.on(test_event, h1);
-                        cordova.on(test_event, h2);
-                        h.fire(test_event, function(err) {
-                            done();
-                        });
+
+                    cordova.on(test_event, h1);
+                    cordova.on(test_event, h2);
+                    h.fire(test_event).then(function() {
                         expect(h1_fired).toBe(true);
-                        expect(h2_fired).toBe(false);
-                    });
-                    waits(timeout);
-                    runs(function() {
                         expect(h2_fired).toBe(true);
+                        done();
                     });
                 });
                 it('should pass data object that fire calls into async handlers', function(done) {
@@ -226,15 +230,13 @@ describe('hooker', function() {
                         "hi":"ho",
                         "offtowork":"wego"
                     };
-                    var async = function(opts, cb) {
+                    var async = function(opts) {
                         data.root = tempDir;
                         expect(opts).toEqual(data);
-                        cb();
+                        return Q();
                     };
                     cordova.on(test_event, async);
-                    h.fire(test_event, data, function() {
-                        done();
-                    });
+                    h.fire(test_event, data).then(done);
                 });
                 it('should pass data object that fire calls into sync handlers', function(done) {
                     var data = {
@@ -246,9 +248,7 @@ describe('hooker', function() {
                         expect(opts).toEqual(data);
                     };
                     cordova.on(test_event, async);
-                    h.fire(test_event, data, function() {
-                        done();
-                    });
+                    h.fire(test_event, data).then(done);
                 });
             });
         });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/lazy_load.spec.js
----------------------------------------------------------------------
diff --git a/spec/lazy_load.spec.js b/spec/lazy_load.spec.js
index f909400..f77ab9d 100644
--- a/spec/lazy_load.spec.js
+++ b/spec/lazy_load.spec.js
@@ -25,6 +25,7 @@ var lazy_load = require('../src/lazy_load'),
     hooker = require('../src/hooker'),
     request = require('request'),
     fs = require('fs'),
+    Q = require('q'),
     platforms = require('../platforms');
 
 describe('lazy_load module', function() {
@@ -35,16 +36,20 @@ describe('lazy_load module', function() {
     describe('cordova method (loads stock cordova libs)', function() {
         var custom;
         beforeEach(function() {
-            custom = spyOn(lazy_load, 'custom');
+            custom = spyOn(lazy_load, 'custom').andReturn(Q());
         });
-        it('should throw if platform is not a stock cordova platform', function() {
-            expect(function() {
-                lazy_load.cordova('atari');
-            }).toThrow('Cordova library "atari" not recognized.');
+        it('should throw if platform is not a stock cordova platform', function(done) {
+            lazy_load.cordova('atari').then(function() {
+                expect('this call').toEqual('to fail');
+            }, function(err) {
+                expect(err).toEqual(new Error('Cordova library "atari" not recognized.'));
+            }).fin(done);
         });
-        it('should invoke lazy_load.custom with appropriate url, platform, and version as specified in platforms manifest', function() {
-            lazy_load.cordova('android');
-            expect(custom).toHaveBeenCalledWith(platforms.android.url + ';a=snapshot;h=' + platforms.android.version + ';sf=tgz', 'cordova', 'android', platforms.android.version, jasmine.any(Function));
+        it('should invoke lazy_load.custom with appropriate url, platform, and version as specified in platforms manifest', function(done) {
+            lazy_load.cordova('android').then(function() {
+                expect(custom).toHaveBeenCalledWith(platforms.android.url + ';a=snapshot;h=' + platforms.android.version + ';sf=tgz', 'cordova', 'android', platforms.android.version);
+                done();
+            });
         });
     });
 
@@ -53,82 +58,108 @@ describe('lazy_load module', function() {
         beforeEach(function() {
             mkdir = spyOn(shell, 'mkdir');
             rm = spyOn(shell, 'rm');
+            mv = spyOn(shell, 'mv');
             sym = spyOn(fs, 'symlinkSync');
             exists = spyOn(fs, 'existsSync').andReturn(false);
-            fire = spyOn(hooker, 'fire').andCallFake(function(evt, data, cb) {
-                cb();
-            });
+            readdir = spyOn(fs, 'readdirSync').andReturn(['somefile.txt']);
+            fire = spyOn(hooker, 'fire').andReturn(Q());
         });
 
         it('should callback with no errors and not fire event hooks if library already exists', function(done) {
             exists.andReturn(true);
-            lazy_load.custom('some url', 'some id', 'platform X', 'three point five', function(err) {
-                expect(err).not.toBeDefined();
+            lazy_load.custom('some url', 'some id', 'platform X', 'three point five').then(function() {
                 expect(fire).not.toHaveBeenCalled()
-                done();
-            });
+            }, function(err) {
+                expect(err).not.toBeDefined();
+            }).fin(done);
         });
-        it('should fire a before_library_download event before it starts downloading a library', function() {
-            lazy_load.custom('some url', 'some id', 'platform X', 'three point five');
-            expect(fire).toHaveBeenCalledWith('before_library_download', {platform:'platform X', url:'some url', id:'some id', version:'three point five'}, jasmine.any(Function));
+        it('should fire a before_library_download event before it starts downloading a library', function(done) {
+            lazy_load.custom('some url', 'some id', 'platform X', 'three point five').then(function() {
+                expect(fire).toHaveBeenCalledWith('before_library_download', {platform:'platform X', url:'some url', id:'some id', version:'three point five'});
+            }, function(err) {
+                expect(err).not.toBeDefined();
+            }).fin(done);
         });
 
         describe('remote URLs for libraries', function() {
             var req,
                 load_spy,
-                p1 = jasmine.createSpy().andReturn({
-                    on:function() {
-                        return {
-                            on:function(){}
-                        }
-                    }
-                });
-            var p2 = jasmine.createSpy().andReturn({pipe:p1});
+                events = {},
+                fakeRequest = {
+                    on: jasmine.createSpy().andCallFake(function(event, cb) {
+                        events[event] = cb;
+                        return fakeRequest;
+                    }),
+                    pipe: jasmine.createSpy().andCallFake(function() { return fakeRequest; })
+                };
             beforeEach(function() {
-                req = spyOn(request, 'get').andReturn({
-                    pipe:p2
+                events = {};
+                fakeRequest.on.reset();
+                fakeRequest.pipe.reset();
+                req = spyOn(request, 'get').andCallFake(function() {
+                    // Fire the 'end' event shortly.
+                    setTimeout(function() {
+                        events['end']();
+                    }, 10);
+                    return fakeRequest;
                 });
                 load_spy = spyOn(npm, 'load').andCallFake(function(cb) { cb(); });
                 npm.config.get = function() { return null; };
             });
 
-            it('should call request with appopriate url params', function() {
+            it('should call request with appopriate url params', function(done) {
                 var url = 'https://github.com/apache/someplugin';
-                lazy_load.custom(url, 'random', 'android', '1.0');
-                expect(req).toHaveBeenCalledWith({
-                    uri:url
-                }, jasmine.any(Function));
+                lazy_load.custom(url, 'random', 'android', '1.0').then(function() {
+                    expect(req).toHaveBeenCalledWith({
+                        uri:url
+                    }, jasmine.any(Function));
+                }, function(err) {
+                    console.log(err);
+                    expect(err).not.toBeDefined();
+                }).fin(done);
             });
-            it('should take into account https-proxy npm configuration var if exists for https:// calls', function() {
+            it('should take into account https-proxy npm configuration var if exists for https:// calls', function(done) {
                 var proxy = 'https://somelocalproxy.com';
                 npm.config.get = function() { return proxy; };
                 var url = 'https://github.com/apache/someplugin';
-                lazy_load.custom(url, 'random', 'android', '1.0');
-                expect(req).toHaveBeenCalledWith({
-                    uri:url,
-                    proxy:proxy
-                }, jasmine.any(Function));
+                lazy_load.custom(url, 'random', 'android', '1.0').then(function() {
+                    expect(req).toHaveBeenCalledWith({
+                        uri:url,
+                        proxy:proxy
+                    }, jasmine.any(Function));
+                }, function(err) {
+                    expect(err).not.toBeDefined();
+                }).fin(done);
             });
-            it('should take into account proxy npm config var if exists for http:// calls', function() {
+            it('should take into account proxy npm config var if exists for http:// calls', function(done) {
                 var proxy = 'http://somelocalproxy.com';
                 npm.config.get = function() { return proxy; };
                 var url = 'http://github.com/apache/someplugin';
-                lazy_load.custom(url, 'random', 'android', '1.0');
-                expect(req).toHaveBeenCalledWith({
-                    uri:url,
-                    proxy:proxy
-                }, jasmine.any(Function));
+                lazy_load.custom(url, 'random', 'android', '1.0').then(function() {
+                    expect(req).toHaveBeenCalledWith({
+                        uri:url,
+                        proxy:proxy
+                    }, jasmine.any(Function));
+                }, function(err) {
+                    expect(err).not.toBeDefined();
+                }).fin(done);
             });
         });
 
         describe('local paths for libraries', function() {
-            it('should symlink to local path', function() {
-                lazy_load.custom('/some/random/lib', 'id', 'X', 'three point five')
-                expect(sym).toHaveBeenCalledWith('/some/random/lib', path.join(util.libDirectory, 'X', 'id', 'three point five'), 'dir');
+            it('should symlink to local path', function(done) {
+                lazy_load.custom('/some/random/lib', 'id', 'X', 'three point five').then(function() {
+                    expect(sym).toHaveBeenCalledWith('/some/random/lib', path.join(util.libDirectory, 'X', 'id', 'three point five'), 'dir');
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
-            it('should fire after hook once done', function() {
-                lazy_load.custom('/some/random/lib', 'id', 'X', 'three point five')
-                expect(fire).toHaveBeenCalledWith('after_library_download', {platform:'X',url:'/some/random/lib',id:'id',version:'three point five',path:path.join(util.libDirectory, 'X', 'id', 'three point five'), symlink:true}, jasmine.any(Function));
+            it('should fire after hook once done', function(done) {
+                lazy_load.custom('/some/random/lib', 'id', 'X', 'three point five').then(function() {
+                    expect(fire).toHaveBeenCalledWith('after_library_download', {platform:'X',url:'/some/random/lib',id:'id',version:'three point five',path:path.join(util.libDirectory, 'X', 'id', 'three point five'), symlink:true});
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
     });
@@ -136,10 +167,10 @@ describe('lazy_load module', function() {
     describe('based_on_config method', function() {
         var cordova, custom;
         beforeEach(function() {
-            cordova = spyOn(lazy_load, 'cordova');
-            custom = spyOn(lazy_load, 'custom');
+            cordova = spyOn(lazy_load, 'cordova').andReturn(Q());
+            custom = spyOn(lazy_load, 'custom').andReturn(Q());
         });
-        it('should invoke custom if a custom lib is specified', function() {
+        it('should invoke custom if a custom lib is specified', function(done) {
             var read = spyOn(config, 'read').andReturn({
                 lib:{
                     maybe:{
@@ -151,12 +182,18 @@ describe('lazy_load module', function() {
             });
             var p = '/some/random/custom/path';
             custom_path.andReturn(p);
-            lazy_load.based_on_config('yup', 'maybe');
-            expect(custom).toHaveBeenCalledWith('you or eye?', 'eye dee', 'maybe', 'four point twenty', undefined);
+            lazy_load.based_on_config('yup', 'maybe').then(function() {
+                expect(custom).toHaveBeenCalledWith('you or eye?', 'eye dee', 'maybe', 'four point twenty');
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
-        it('should invoke cordova if no custom lib is specified', function() {
-            lazy_load.based_on_config('yup', 'ios');
-            expect(cordova).toHaveBeenCalledWith('ios', undefined);
+        it('should invoke cordova if no custom lib is specified', function(done) {
+            lazy_load.based_on_config('yup', 'ios').then(function() {
+                expect(cordova).toHaveBeenCalledWith('ios');
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/metadata/android_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/metadata/android_parser.spec.js b/spec/metadata/android_parser.spec.js
index 228a11b..71479ae 100644
--- a/spec/metadata/android_parser.spec.js
+++ b/spec/metadata/android_parser.spec.js
@@ -22,6 +22,8 @@ var platforms = require('../../platforms'),
     shell = require('shelljs'),
     fs = require('fs'),
     ET = require('elementtree'),
+    Q = require('q'),
+    child_process = require('child_process'),
     config = require('../../src/config'),
     config_parser = require('../../src/config_parser'),
     cordova = require('../../cordova');
@@ -31,12 +33,25 @@ describe('android project parser', function() {
     var exists, exec, custom;
     beforeEach(function() {
         exists = spyOn(fs, 'existsSync').andReturn(true);
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, 'android-17');
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(null, 'android-17', '');
         });
         custom = spyOn(config, 'has_custom_path').andReturn(false);
     });
 
+    function wrapper(p, done, post) {
+        p.then(post, function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    }
+
+    function errorWrapper(p, done, post) {
+        p.then(function() {
+            expect('this call').toBe('fail');
+        }, post).fin(done);
+    }
+
     describe('constructions', function() {
         it('should throw if provided directory does not contain an AndroidManifest.xml', function() {
             exists.andReturn(false);
@@ -58,45 +73,39 @@ describe('android project parser', function() {
     describe('check_requirements', function() {
         it('should fire a callback if there is an error during shelling out', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(50, 'there was an errorz!');
+                if (!cb) cb = opts;
+                cb(50, 'there was an errorz!', '');
             });
-            platforms.android.parser.check_requirements(proj, function(err) {
+            errorWrapper(platforms.android.parser.check_requirements(proj), done, function(err) {
                 expect(err).toContain('there was an errorz!');
-                done();
             });
         });
         it('should fire a callback if `android list target` does not return anything containing "android-17"', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(0, 'android-15');
+                if (!cb) cb = opts;
+                cb(0, 'android-15', '');
             });
-            platforms.android.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual('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.');
-                done();
+            errorWrapper(platforms.android.parser.check_requirements(proj), done, function(err) {
+                expect(err).toEqual(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.'));
             });
         });
         it('should check that `android` is on the path by calling `android list target`', function(done) {
-            platforms.android.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
-                expect(exec).toHaveBeenCalledWith('android list target', jasmine.any(Object), jasmine.any(Function));
-                done();
+            wrapper(platforms.android.parser.check_requirements(proj), done, function() {
+                expect(exec).toHaveBeenCalledWith('android list target', jasmine.any(Function));
             });
         });
         it('should check that we can update an android project by calling `android update project` on stock android path', function(done) {
-            platforms.android.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
+            wrapper(platforms.android.parser.check_requirements(proj), done, function() {
                 expect(exec.mostRecentCall.args[0]).toMatch(/^android update project -p .* -t android-17$/gi);
                 expect(exec.mostRecentCall.args[0]).toContain(util.libDirectory);
-                done();
             });
         });
         it('should check that we can update an android project by calling `android update project` on a custom path if it is so defined', function(done) {
             var custom_path = path.join('some', 'custom', 'path', 'to', 'android', 'lib');
             custom.andReturn(custom_path);
-            platforms.android.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
+            wrapper(platforms.android.parser.check_requirements(proj), done, function() {
                 expect(exec.mostRecentCall.args[0]).toMatch(/^android update project -p .* -t android-17$/gi);
                 expect(exec.mostRecentCall.args[0]).toContain(custom_path);
-                done();
             });
         });
     });
@@ -277,9 +286,8 @@ describe('android project parser', function() {
             it('should throw if update_from_config throws', function(done) {
                 var err = new Error('uh oh!');
                 config.andCallFake(function() { throw err; });
-                p.update_project({}, function(err) {
+                errorWrapper(p.update_project({}), done, function(err) {
                     expect(err).toEqual(err);
-                    done();
                 });
             });
             it('should call update_www', function() {

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/metadata/blackberry_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/metadata/blackberry_parser.spec.js b/spec/metadata/blackberry_parser.spec.js
index 354109e..97d8c7d 100644
--- a/spec/metadata/blackberry_parser.spec.js
+++ b/spec/metadata/blackberry_parser.spec.js
@@ -23,6 +23,8 @@ var platforms = require('../../platforms'),
     shell = require('shelljs'),
     fs = require('fs'),
     ET = require('elementtree'),
+    Q = require('q'),
+    child_process = require('child_process'),
     config = require('../../src/config'),
     prompt = require('prompt'),
     config_parser = require('../../src/config_parser'),
@@ -35,11 +37,23 @@ describe('blackberry10 project parser', function() {
         exists = spyOn(fs, 'existsSync').andReturn(true);
         custom = spyOn(config, 'has_custom_path').andReturn(false);
         config_p = spyOn(util, 'config_parser');
-        sh = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, '');
+        sh = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            (cb || opts)(0, '', '');
         });
     });
 
+    function wrapper(p, done, post) {
+        p.then(post, function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    }
+
+    function errorWrapper(p, done, post) {
+        p.then(function() {
+            expect('this call').toBe('fail');
+        }, post).fin(done);
+    }
+
     describe('constructions', function() {
         it('should throw an exception with a path that is not a native blackberry project', function() {
             exists.andReturn(false);
@@ -59,17 +73,15 @@ describe('blackberry10 project parser', function() {
     describe('check_requirements', function() {
         it('should fire a callback if the blackberry-deploy shell-out fails', function(done) {
             sh.andCallFake(function(cmd, opts, cb) {
-                cb(1, 'no bb-deploy dewd!');
+                (cb || opts)(1, 'no bb-deploy dewd!');
             });
-            platforms.blackberry10.parser.check_requirements(proj, function(err) {
+            errorWrapper(platforms.blackberry10.parser.check_requirements(proj), done, function(err) {
                 expect(err).toContain('no bb-deploy dewd');
-                done();
             });
         });
         it('should fire a callback with no error if shell out is successful', function(done) {
-            platforms.blackberry10.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
-                done();
+            wrapper(platforms.blackberry10.parser.check_requirements(proj), done, function() {
+                expect(1).toBe(1);
             });
         });
     });
@@ -97,6 +109,8 @@ describe('blackberry10 project parser', function() {
                 xml_access_add = jasmine.createSpy('xml access add');
                 xml_update = jasmine.createSpy('xml update');
                 xml_append = jasmine.createSpy('xml append');
+                xml_preference_remove = jasmine.createSpy('xml preference rm');
+                xml_preference_add = jasmine.createSpy('xml preference add');
                 p.xml.name = xml_name;
                 p.xml.packageName = xml_pkg;
                 p.xml.version = xml_version;
@@ -109,6 +123,10 @@ describe('blackberry10 project parser', function() {
                 p.xml.doc = {
                     getroot:function() { return { append:xml_append}; }
                 };
+                p.xml.preference = {
+                    add: xml_preference_add,
+                    remove: xml_preference_remove
+                };
                 find_obj = {
                     text:'hi'
                 };
@@ -131,7 +149,9 @@ describe('blackberry10 project parser', function() {
                 cfg.content = function() { return 'index.html'; };
                 cfg.packageName = function() { return 'testpkg'; };
                 cfg.version = function() { return 'one point oh'; };
+                cfg.access = {};
                 cfg.access.getAttributes = function() { return []; };
+                cfg.access.remove = function() { };
                 cfg.preference.get = function() { return []; };
             });
 
@@ -178,6 +198,10 @@ describe('blackberry10 project parser', function() {
             });
         });
         describe('update_www method', function() {
+            beforeEach(function() {
+                p.xml.update = jasmine.createSpy('xml update');
+            });
+
             it('should rm project-level www and cp in platform agnostic www', function() {
                 p.update_www();
                 expect(rm).toHaveBeenCalled();
@@ -226,33 +250,37 @@ describe('blackberry10 project parser', function() {
                 svn = spyOn(util, 'deleteSvnFolders');
                 parse = spyOn(JSON, 'parse').andReturn({blackberry:{qnx:{}}});
             });
-            it('should call update_from_config', function() {
-                p.update_project();
-                expect(config).toHaveBeenCalled();
+            it('should call update_from_config', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(config).toHaveBeenCalled();
+                });
             });
             it('should throw if update_from_config throws', function(done) {
                 var err = new Error('uh oh!');
                 config.andCallFake(function() { throw err; });
-                p.update_project({}, function(err) {
-                    expect(err).toEqual(err);
-                    done();
+                errorWrapper(p.update_project({}), done, function(e) {
+                    expect(e).toEqual(err);
                 });
             });
-            it('should call update_www', function() {
-                p.update_project();
-                expect(www).toHaveBeenCalled();
+            it('should call update_www', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(www).toHaveBeenCalled();
+                });
             });
-            it('should call update_overrides', function() {
-                p.update_project();
-                expect(overrides).toHaveBeenCalled();
+            it('should call update_overrides', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(overrides).toHaveBeenCalled();
+                });
             });
-            it('should call update_staging', function() {
-                p.update_project();
-                expect(staging).toHaveBeenCalled();
+            it('should call update_staging', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(staging).toHaveBeenCalled();
+                });
             });
-            it('should call deleteSvnFolders', function() {
-                p.update_project();
-                expect(svn).toHaveBeenCalled();
+            it('should call deleteSvnFolders', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(svn).toHaveBeenCalled();
+                });
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/metadata/ios_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/metadata/ios_parser.spec.js b/spec/metadata/ios_parser.spec.js
index 509cf26..f3e57be 100644
--- a/spec/metadata/ios_parser.spec.js
+++ b/spec/metadata/ios_parser.spec.js
@@ -24,6 +24,8 @@ var platforms = require('../../platforms'),
     xcode = require('xcode'),
     ET = require('elementtree'),
     fs = require('fs'),
+    Q = require('q'),
+    child_process = require('child_process'),
     config = require('../../src/config'),
     config_parser = require('../../src/config_parser'),
     cordova = require('../../cordova');
@@ -32,13 +34,27 @@ describe('ios project parser', function () {
     var proj = path.join('some', 'path');
     var exec, custom, readdir, cfg_parser;
     beforeEach(function() {
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, '');
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(null, '', '');
         });
         custom = spyOn(config, 'has_custom_path').andReturn(false);
         readdir = spyOn(fs, 'readdirSync').andReturn(['test.xcodeproj']);
         cfg_parser = spyOn(util, 'config_parser');
     });
+
+    function wrapper(p, done, post) {
+        p.then(post, function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    }
+
+    function errorWrapper(p, done, post) {
+        p.then(function() {
+            expect('this call').toBe('fail');
+        }, post).fin(done);
+    }
+
     describe('constructions', function() {
         it('should throw if provided directory does not contain an xcodeproj file', function() {
             readdir.andReturn(['noxcodehere']);
@@ -58,29 +74,29 @@ describe('ios project parser', function () {
     describe('check_requirements', function() {
         it('should fire a callback if there is an error during shelling out', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(50, 'there was an errorz!');
+                if (!cb) cb = opts;
+                cb(50, 'there was an errorz!', '');
             });
-            platforms.ios.parser.check_requirements(proj, function(err) {
+            errorWrapper(platforms.ios.parser.check_requirements(proj), done, function(err) {
                 expect(err).toContain('there was an errorz!');
-                done();
             });
         });
         it('should fire a callback if the xcodebuild version is less than 4.5.x', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(0, 'version 4.4.9');
+                if (!cb) cb = opts;
+                cb(0, 'version 4.4.9', '');
             });
-            platforms.ios.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual('Xcode version installed is too old. Minimum: >=4.5.x, yours: 4.4.9');
-                done();
+            errorWrapper(platforms.ios.parser.check_requirements(proj), done, function(err) {
+                expect(err).toEqual(new Error('Xcode version installed is too old. Minimum: >=4.5.x, yours: 4.4.9'));
             });
         });
         it('should not return an error if the xcodebuild version 2 digits and not proper semver (eg: 5.0), but still satisfies the MIN_XCODE_VERSION', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(0, 'version 5.0');
+                if (!cb) cb = opts;
+                cb(0, 'version 5.0', '');
             });
-            platforms.ios.parser.check_requirements(proj, function(err) {
-                expect(err).toBe(false);
-                done();
+            wrapper(platforms.ios.parser.check_requirements(proj), done, function() {
+                expect(1).toBe(1);
             });
         });
     });
@@ -162,64 +178,56 @@ describe('ios project parser', function () {
             it('should update the app name in pbxproj by calling xcode.updateProductName, and move the ios native files to match the new name', function(done) {
                 var test_path = path.join(proj, 'platforms', 'ios', 'test');
                 var testname_path = path.join(proj, 'platforms', 'ios', 'testname');
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(update_name).toHaveBeenCalledWith('testname');
                     expect(mv).toHaveBeenCalledWith(path.join(test_path, 'test-Info.plist'), path.join(test_path, 'testname-Info.plist'));
                     expect(mv).toHaveBeenCalledWith(path.join(test_path, 'test-Prefix.pch'), path.join(test_path, 'testname-Prefix.pch'));
                     expect(mv).toHaveBeenCalledWith(test_path + '.xcodeproj', testname_path + '.xcodeproj');
                     expect(mv).toHaveBeenCalledWith(test_path, testname_path);
-                    done();
                 });
             });
             it('should write out the app id to info plist as CFBundleIdentifier', function(done) {
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(plist_build.mostRecentCall.args[0].CFBundleIdentifier).toEqual('testpkg');
-                    done();
                 });
             });
             it('should write out the app version to info plist as CFBundleVersion', function(done) {
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(plist_build.mostRecentCall.args[0].CFBundleVersion).toEqual('one point oh');
-                    done();
                 });
             });
             it('should wipe out the ios whitelist every time', function(done) {
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_access_rm).toHaveBeenCalled();
-                    done();
                 });
             });
             it('should update the whitelist', function(done) {
                 cfg.access.get = function() { return ['one'] };
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_access_add).toHaveBeenCalledWith('one');
-                    done();
                 });
             });
             it('should update preferences', function(done) {
                 var sample_pref = {name:'pref',value:'yes'};
                 cfg.preference.get = function() { return [sample_pref] };
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_pref_add).toHaveBeenCalledWith(sample_pref);
-                    done();
                 });
             });
             it('should update the content tag / start page', function(done) {
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_content).toHaveBeenCalledWith('index.html');
-                    done();
                 });
             });
             it('should wipe out the ios preferences every time', function(done) {
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_pref_rm).toHaveBeenCalled();
-                    done();
                 });
             });
             it('should write out default preferences every time', function(done) {
                 var sample_pref = {name:'preftwo',value:'false'};
                 cfg.preference.get = function() { return [sample_pref] };
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"KeyboardDisplayRequiresUserAction",value:"true"});
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"SuppressesIncrementalRendering",value:"false"});
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"UIWebViewBounce",value:"true"});
@@ -232,7 +240,6 @@ describe('ios project parser', function () {
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"AllowInlineMediaPlayback",value:"false"});
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"OpenAllWhitelistURLsInWebView",value:"false"});
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"BackupWebStorage",value:"cloud"});
-                    done();
                 });
             });
         });
@@ -301,41 +308,43 @@ describe('ios project parser', function () {
         describe('update_project method', function() {
             var config, www, overrides, staging, svn;
             beforeEach(function() {
-                config = spyOn(p, 'update_from_config').andCallFake(function(cfg, cb) { cb() });
+                config = spyOn(p, 'update_from_config').andReturn(Q());
                 www = spyOn(p, 'update_www');
                 overrides = spyOn(p, 'update_overrides');
                 staging = spyOn(p, 'update_staging');
                 svn = spyOn(util, 'deleteSvnFolders');
             });
-            it('should call update_from_config', function() {
-                p.update_project();
-                expect(config).toHaveBeenCalled();
+            it('should call update_from_config', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(config).toHaveBeenCalled();
+                });
             });
             it('should throw if update_from_config errors', function(done) {
-                var err = new Error('uh oh!');
-                config.andCallFake(function(cfg, cb) { cb(err); });
-                p.update_project({}, function(err) {
-                    expect(err).toEqual(err);
-                    done();
+                var e = new Error('uh oh!');
+                config.andReturn(Q.reject(e));
+                errorWrapper(p.update_project({}), done, function(err) {
+                    expect(err).toEqual(e);
                 });
             });
             it('should call update_www', function(done) {
-                p.update_project({}, function() {
+                wrapper(p.update_project({}), done, function() {
                     expect(www).toHaveBeenCalled();
-                    done();
                 });
             });
-            it('should call update_overrides', function() {
-                p.update_project();
-                expect(overrides).toHaveBeenCalled();
+            it('should call update_overrides', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(overrides).toHaveBeenCalled();
+                });
             });
-            it('should call update_staging', function() {
-                p.update_project();
-                expect(staging).toHaveBeenCalled();
+            it('should call update_staging', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(staging).toHaveBeenCalled();
+                });
             });
-            it('should call deleteSvnFolders', function() {
-                p.update_project();
-                expect(svn).toHaveBeenCalled();
+            it('should call deleteSvnFolders', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(svn).toHaveBeenCalled();
+                });
             });
         });
     });


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

Posted by br...@apache.org.
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/metadata/wp7_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/metadata/wp7_parser.spec.js b/spec/metadata/wp7_parser.spec.js
index c7a74df..d7ac782 100644
--- a/spec/metadata/wp7_parser.spec.js
+++ b/spec/metadata/wp7_parser.spec.js
@@ -23,6 +23,8 @@ var platforms = require('../../platforms'),
     shell = require('shelljs'),
     fs = require('fs'),
     ET = require('elementtree'),
+    Q = require('q'),
+    child_process = require('child_process'),
     config = require('../../src/config'),
     config_parser = require('../../src/config_parser'),
     cordova = require('../../cordova');
@@ -32,14 +34,26 @@ describe('wp7 project parser', function() {
     var exists, exec, custom, readdir, cfg_parser;
     beforeEach(function() {
         exists = spyOn(fs, 'existsSync').andReturn(true);
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, '');
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            (cb || opts)(0, '', '');
         });
         custom = spyOn(config, 'has_custom_path').andReturn(false);
         readdir = spyOn(fs, 'readdirSync').andReturn(['test.csproj']);
         cfg_parser = spyOn(util, 'config_parser');
     });
 
+    function wrapper(p, done, post) {
+        p.then(post, function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    }
+
+    function errorWrapper(p, done, post) {
+        p.then(function() {
+            expect('this call').toBe('fail');
+        }, post).fin(done);
+    }
+
     describe('constructions', function() {
         it('should throw if provided directory does not contain a csproj file', function() {
             readdir.andReturn([]);
@@ -59,29 +73,24 @@ describe('wp7 project parser', function() {
     describe('check_requirements', function() {
         it('should fire a callback if there is an error during shelling out', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(50, 'there was an errorz!');
+                (cb || opts)(50, 'there was an errorz!');
             });
-            platforms.wp7.parser.check_requirements(proj, function(err) {
+            errorWrapper(platforms.wp7.parser.check_requirements(proj), done, function(err) {
                 expect(err).toContain('there was an errorz!');
-                done();
             });
         });
         it('should check by calling check_reqs on the stock lib path if no custom path is defined', function(done) {
-            platforms.wp7.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
+            wrapper(platforms.wp7.parser.check_requirements(proj), done, function(err) {
                 expect(exec.mostRecentCall.args[0]).toContain(util.libDirectory);
                 expect(exec.mostRecentCall.args[0]).toMatch(/check_reqs"$/);
-                done();
             });
         });
         it('should check by calling check_reqs on a custom path if it is so defined', function(done) {
             var custom_path = path.join('some','custom','path','to','wp7','lib');
             custom.andReturn(custom_path);
-            platforms.wp7.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
+            wrapper(platforms.wp7.parser.check_requirements(proj), done, function() {
                 expect(exec.mostRecentCall.args[0]).toContain(custom_path);
                 expect(exec.mostRecentCall.args[0]).toMatch(/check_reqs"$/);
-                done();
             });
         });
     });
@@ -217,29 +226,32 @@ describe('wp7 project parser', function() {
                 staging = spyOn(p, 'update_staging');
                 svn = spyOn(util, 'deleteSvnFolders');
             });
-            it('should call update_from_config', function() {
-                p.update_project();
-                expect(config).toHaveBeenCalled();
+            it('should call update_from_config', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(config).toHaveBeenCalled();
+                });
             });
             it('should throw if update_from_config throws', function(done) {
                 var err = new Error('uh oh!');
                 config.andCallFake(function() { throw err; });
-                p.update_project({}, function(err) {
-                    expect(err).toEqual(err);
-                    done();
+                errorWrapper(p.update_project({}), done, function(e) {
+                    expect(e).toEqual(err);
                 });
             });
-            it('should call update_www', function() {
-                p.update_project();
-                expect(www).toHaveBeenCalled();
+            it('should call update_www', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(www).toHaveBeenCalled();
+                });
             });
-            it('should call update_staging', function() {
-                p.update_project();
-                expect(staging).toHaveBeenCalled();
+            it('should call update_staging', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(staging).toHaveBeenCalled();
+                });
             });
-            it('should call deleteSvnFolders', function() {
-                p.update_project();
-                expect(svn).toHaveBeenCalled();
+            it('should call deleteSvnFolders', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(svn).toHaveBeenCalled();
+                });
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/metadata/wp8_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/metadata/wp8_parser.spec.js b/spec/metadata/wp8_parser.spec.js
index 67a1c89..fc90d8a 100644
--- a/spec/metadata/wp8_parser.spec.js
+++ b/spec/metadata/wp8_parser.spec.js
@@ -23,6 +23,8 @@ var platforms = require('../../platforms'),
     shell = require('shelljs'),
     fs = require('fs'),
     ET = require('elementtree'),
+    Q = require('q'),
+    child_process = require('child_process'),
     config = require('../../src/config'),
     config_parser = require('../../src/config_parser'),
     cordova = require('../../cordova');
@@ -32,14 +34,26 @@ describe('wp8 project parser', function() {
     var exists, exec, custom, readdir, cfg_parser;
     beforeEach(function() {
         exists = spyOn(fs, 'existsSync').andReturn(true);
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, '');
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            (cb || opts)(0, '', '');
         });
         custom = spyOn(config, 'has_custom_path').andReturn(false);
         readdir = spyOn(fs, 'readdirSync').andReturn(['test.csproj']);
         cfg_parser = spyOn(util, 'config_parser');
     });
 
+    function wrapper(p, done, post) {
+        p.then(post, function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    }
+
+    function errorWrapper(p, done, post) {
+        p.then(function() {
+            expect('this call').toBe('fail');
+        }, post).fin(done);
+    }
+
     describe('constructions', function() {
         it('should throw if provided directory does not contain a csproj file', function() {
             readdir.andReturn([]);
@@ -59,29 +73,24 @@ describe('wp8 project parser', function() {
     describe('check_requirements', function() {
         it('should fire a callback if there is an error during shelling out', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(50, 'there was an errorz!');
+                (cb || opts)(50, 'there was an errorz!');
             });
-            platforms.wp8.parser.check_requirements(proj, function(err) {
+            errorWrapper(platforms.wp8.parser.check_requirements(proj), done, function(err) {
                 expect(err).toContain('there was an errorz!');
-                done();
             });
         });
         it('should check by calling check_reqs on the stock lib path if no custom path is defined', function(done) {
-            platforms.wp8.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
+            wrapper(platforms.wp8.parser.check_requirements(proj), done, function() {
                 expect(exec.mostRecentCall.args[0]).toContain(util.libDirectory);
                 expect(exec.mostRecentCall.args[0]).toMatch(/check_reqs"$/);
-                done();
             });
         });
         it('should check by calling check_reqs on a custom path if it is so defined', function(done) {
             var custom_path = path.join('some','custom','path','to','wp8','lib');
             custom.andReturn(custom_path);
-            platforms.wp8.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
+            wrapper(platforms.wp8.parser.check_requirements(proj), done, function(err) {
                 expect(exec.mostRecentCall.args[0]).toContain(custom_path);
                 expect(exec.mostRecentCall.args[0]).toMatch(/check_reqs"$/);
-                done();
             });
         });
     });
@@ -217,29 +226,32 @@ describe('wp8 project parser', function() {
                 staging = spyOn(p, 'update_staging');
                 svn = spyOn(util, 'deleteSvnFolders');
             });
-            it('should call update_from_config', function() {
-                p.update_project();
-                expect(config).toHaveBeenCalled();
+            it('should call update_from_config', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(config).toHaveBeenCalled();
+                });
             });
             it('should throw if update_from_config throws', function(done) {
                 var err = new Error('uh oh!');
                 config.andCallFake(function() { throw err; });
-                p.update_project({}, function(err) {
-                    expect(err).toEqual(err);
-                    done();
+                errorWrapper(p.update_project({}), done, function(e) {
+                    expect(e).toEqual(err);
                 });
             });
-            it('should call update_www', function() {
-                p.update_project();
-                expect(www).toHaveBeenCalled();
+            it('should call update_www', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(www).toHaveBeenCalled();
+                });
             });
-            it('should call update_staging', function() {
-                p.update_project();
-                expect(staging).toHaveBeenCalled();
+            it('should call update_staging', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(staging).toHaveBeenCalled();
+                });
             });
-            it('should call deleteSvnFolders', function() {
-                p.update_project();
-                expect(svn).toHaveBeenCalled();
+            it('should call deleteSvnFolders', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(svn).toHaveBeenCalled();
+                });
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/platform.spec.js
----------------------------------------------------------------------
diff --git a/spec/platform.spec.js b/spec/platform.spec.js
index b167b20..a4ae605 100644
--- a/spec/platform.spec.js
+++ b/spec/platform.spec.js
@@ -19,12 +19,14 @@
 var cordova = require('../cordova'),
     path = require('path'),
     shell = require('shelljs'),
+    child_process = require('child_process'),
     plugman = require('plugman'),
     fs = require('fs'),
     util = require('../src/util'),
     config = require('../src/config'),
     hooker = require('../src/hooker'),
     lazy_load = require('../src/lazy_load'),
+    Q = require('q'),
     platform = require('../src/platform'),
     platforms = require('../platforms');
 
@@ -41,10 +43,7 @@ describe('platform command', function() {
             });
         });
         is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
-        fire = spyOn(hooker.prototype, 'fire').andCallFake(function(e, opts, cb) {
-            if (cb === undefined) cb = opts;
-            cb(false);
-        });
+        fire = spyOn(hooker.prototype, 'fire').andReturn(Q());
         name = jasmine.createSpy('config name').andReturn('magical mystery tour');
         pkg = jasmine.createSpy('config packageName').andReturn('ca.filmaj.id');
         config_parser = spyOn(util, 'config_parser').andReturn({
@@ -55,52 +54,52 @@ describe('platform command', function() {
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
         util.libDirectory = path.join('HOMEDIR', '.cordova', 'lib');
         config_read = spyOn(config, 'read').andReturn({});
-        load = spyOn(lazy_load, 'based_on_config').andCallFake(function(root, platform, cb) {
-            cb();
-        });
-        load_custom = spyOn(lazy_load, 'custom').andCallFake(function(url, id, p, v, cb) {
-            cb();
-        });
+        load = spyOn(lazy_load, 'based_on_config').andReturn(Q());
+        load_custom = spyOn(lazy_load, 'custom').andReturn(Q());
         rm = spyOn(shell, 'rm');
         mkdir = spyOn(shell, 'mkdir');
         existsSync = spyOn(fs, 'existsSync').andReturn(false);
-        supports = spyOn(platform, 'supports').andCallFake(function(project_root, name, cb) {
-            cb();
-        });
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            if (cb) cb(0, '');
-            else return { code: 0, output: '' };
+        supports = spyOn(platform, 'supports').andReturn(Q());
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(null, '', '');
         });
-        prep_spy = spyOn(cordova, 'prepare').andCallFake(function(t, cb) {
-            cb();
-        });
-        plugman_install = spyOn(plugman, 'install');
+        prep_spy = spyOn(cordova.raw, 'prepare').andReturn(Q());
+        plugman_install = spyOn(plugman, 'install').andReturn(Q());
     });
 
     describe('failure', function() {
-        it('should not run outside of a Cordova-based project by calling util.isCordova', function() {
+        function expectFailure(p, done, post) {
+            p.then(function() {
+                expect('this call').toBe('fail');
+            }, post).fin(done);
+        }
+
+        it('should not run outside of a Cordova-based project by calling util.isCordova', function(done) {
             is_cordova.andReturn(false);
-            expect(function() {
-                cordova.platform();
+            expectFailure(cordova.raw.platform(), done, function(err) {
                 expect(is_cordova).toHaveBeenCalled();
-            }).toThrow('Current working directory is not a Cordova-based project.');
+                expect(err).toEqual(new Error('Current working directory is not a Cordova-based project.'));
+            });
         });
-        it('should report back an error if used with `add` and no platform is specified', function() {
-            expect(function() {
-               cordova.platform('add');
-            }).toThrow('You need to qualify `add` or `remove` with one or more platforms!');
+        it('should report back an error if used with `add` and no platform is specified', function(done) {
+            expectFailure(cordova.raw.platform('add'), done, function(err) {
+                expect(err).toEqual(new Error('You need to qualify `add` or `remove` with one or more platforms!'));
+            });
         });
-        it('should report back an error if used with `rm` and no platform is specified', function() {
-            expect(function() {
-               cordova.platform('rm');
-            }).toThrow('You need to qualify `add` or `remove` with one or more platforms!');
+        it('should report back an error if used with `rm` and no platform is specified', function(done) {
+            expectFailure(cordova.raw.platform('rm'), done, function(err) {
+                expect(err).toEqual(new Error('You need to qualify `add` or `remove` with one or more platforms!'));
+            });
         });
     });
 
     describe('success', function() {
-        it('should run inside a Cordova-based project by calling util.isCordova', function() {
-            cordova.platform();
-            expect(is_cordova).toHaveBeenCalled();
+        it('should run inside a Cordova-based project by calling util.isCordova', function(done) {
+            cordova.raw.platform().then(function() {
+                expect(is_cordova).toHaveBeenCalled();
+                done();
+            });
         });
 
         describe('`ls`', function() {
@@ -113,7 +112,7 @@ describe('platform command', function() {
                     expect(res).toMatch(/^Installed platforms:\s*Available platforms:.*$/);
                     done();
                 });
-                cordova.platform('list');
+                cordova.raw.platform('list');
             });
 
             it('should list out added platforms in a project', function(done) {
@@ -121,19 +120,23 @@ describe('platform command', function() {
                     expect(res).toMatch(/^Installed platforms: ios, android, wp7, wp8, blackberry10, firefoxos\s*Available platforms:\s*$/);
                     done();
                 });
-                cordova.platform('list');
+                cordova.raw.platform('list');
             });
         });
         describe('`add`', function() {
-            it('should shell out to specified platform\'s bin/create, using the version that is specified in platforms manifest', function() {
-                cordova.platform('add', 'android');
-                expect(exec.mostRecentCall.args[0]).toMatch(/lib.android.cordova.\d.\d.\d[\d\w]*.bin.create/gi);
-                expect(exec.mostRecentCall.args[0]).toContain(project_dir);
-                cordova.platform('add', 'wp8');
-                expect(exec.mostRecentCall.args[0]).toMatch(/lib.wp.cordova.\d.\d.\d[\d\w]*.wp8.bin.create/gi);
-                expect(exec.mostRecentCall.args[0]).toContain(project_dir);
+            it('should shell out to specified platform\'s bin/create, using the version that is specified in platforms manifest', function(done) {
+                cordova.raw.platform('add', 'android').then(function() {
+                    expect(exec.mostRecentCall.args[0]).toMatch(/lib.android.cordova.\d.\d.\d[\d\-\w]*.bin.create/gi);
+                    expect(exec.mostRecentCall.args[0]).toContain(project_dir);
+                }).then(function() {
+                    return cordova.raw.platform('add', 'wp8');
+                }).then(function() {
+                    expect(exec.mostRecentCall.args[0]).toMatch(/lib.wp.cordova.\d.\d.\d[\d\w\-]*.wp8.bin.create/gi);
+                    expect(exec.mostRecentCall.args[0]).toContain(project_dir);
+                    done();
+                });
             });
-            it('should call into lazy_load.custom if there is a user-specified configruation for consuming custom libraries', function() {
+            it('should call into lazy_load.custom if there is a user-specified configruation for consuming custom libraries', function(done) {
                 load.andCallThrough();
                 config_read.andReturn({
                     lib:{
@@ -144,12 +147,14 @@ describe('platform command', function() {
                         }
                     }
                 });
-                cordova.platform('add', 'wp8');
-                expect(load_custom).toHaveBeenCalledWith('haha', 'phonegap', 'wp8', 'bleeding edge', jasmine.any(Function));
-                expect(exec.mostRecentCall.args[0]).toMatch(/lib.wp.phonegap.bleeding edge.wp8.bin.create/gi);
-                expect(exec.mostRecentCall.args[0]).toContain(project_dir);
+                cordova.raw.platform('add', 'wp8').then(function() {
+                    expect(load_custom).toHaveBeenCalledWith('haha', 'phonegap', 'wp8', 'bleeding edge');
+                    expect(exec.mostRecentCall.args[0]).toMatch(/lib.wp.phonegap.bleeding edge.wp8.bin.create/gi);
+                    expect(exec.mostRecentCall.args[0]).toContain(project_dir);
+                    done();
+                });
             });
-            it('should use a custom template directory if there is one specified in the configuration', function() {
+            it('should use a custom template directory if there is one specified in the configuration', function(done) {
                 var template_dir = "/tmp/custom-template"
                 load.andCallThrough();
                 config_read.andReturn({
@@ -162,12 +167,14 @@ describe('platform command', function() {
                         }
                     }
                 });
-                cordova.platform('add', 'android');
-                expect(exec.mostRecentCall.args[0]).toMatch(/^"[^ ]*" +"[^"]*" +"[^"]*" +"[^"]*" +"[^"]*"$/g);
-                expect(exec.mostRecentCall.args[0]).toContain(project_dir);
-                expect(exec.mostRecentCall.args[0]).toContain(template_dir);
+                cordova.raw.platform('add', 'android').then(function() {
+                    expect(exec.mostRecentCall.args[0]).toMatch(/^"[^ ]*" +"[^"]*" +"[^"]*" +"[^"]*" +"[^"]*"$/g);
+                    expect(exec.mostRecentCall.args[0]).toContain(project_dir);
+                    expect(exec.mostRecentCall.args[0]).toContain(template_dir);
+                    done();
+                });
             });
-            it('should not use a custom template directory if there is not one specified in the configuration', function() {
+            it('should not use a custom template directory if there is not one specified in the configuration', function(done) {
                 load.andCallThrough();
                 config_read.andReturn({
                     lib: {
@@ -178,44 +185,54 @@ describe('platform command', function() {
                         }
                     }
                 });
-                cordova.platform('add', 'android');
-                expect(exec.mostRecentCall.args[0]).toMatch(/^"[^ ]*" +"[^"]*" +"[^"]*" +"[^"]*"$/g);
-                expect(exec.mostRecentCall.args[0]).toContain(project_dir);
+                cordova.raw.platform('add', 'android').then(function() {
+                    expect(exec.mostRecentCall.args[0]).toMatch(/^"[^ ]*" +"[^"]*" +"[^"]*" +"[^"]*"$/g);
+                    expect(exec.mostRecentCall.args[0]).toContain(project_dir);
+                    done();
+                });
             });
-            it('should not use a custom template directory if there is no user-defined configuration', function() {
-                cordova.platform('add', 'android');
-                expect(exec.mostRecentCall.args[0]).toMatch(/^"[^ ]*" +"[^"]*" +"[^"]*" +"[^"]*"$/g);
-                expect(exec.mostRecentCall.args[0]).toContain(project_dir);
+            it('should not use a custom template directory if there is no user-defined configuration', function(done) {
+                cordova.raw.platform('add', 'android').then(function() {
+                    expect(exec.mostRecentCall.args[0]).toMatch(/^"[^ ]*" +"[^"]*" +"[^"]*" +"[^"]*"$/g);
+                    expect(exec.mostRecentCall.args[0]).toContain(project_dir);
+                    done();
+                });
             });
         });
         describe('`remove`',function() {
-            it('should remove a supported and added platform', function() {
-                cordova.platform('remove', 'android');
-                expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'platforms', 'android'));
-                expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'merges', 'android'));
+            it('should remove a supported and added platform', function(done) {
+                cordova.raw.platform('remove', 'android').then(function() {
+                    expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'platforms', 'android'));
+                    expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'merges', 'android'));
+                    done();
+                });
             });
 
-            it('should be able to remove multiple platforms', function() {
-                cordova.platform('remove', ['android', 'blackberry10']);
-                expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'platforms', 'android'));
-                expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'merges', 'android'));
-                expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'platforms', 'blackberry10'));
-                expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'merges', 'blackberry10'));
+            it('should be able to remove multiple platforms', function(done) {
+                cordova.raw.platform('remove', ['android', 'blackberry10']).then(function() {
+                    expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'platforms', 'android'));
+                    expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'merges', 'android'));
+                    expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'platforms', 'blackberry10'));
+                    expect(rm).toHaveBeenCalledWith('-rf', path.join(project_dir, 'merges', 'blackberry10'));
+                    done();
+                });
             });
         });
         describe('`update`', function() {
             describe('failure', function() {
                 it('should fail if no platform is specified', function(done) {
-                    cordova.platform('update', [], function(err) {
+                    cordova.raw.platform('update', []).then(function() {
+                        expect('this call').toBe('fail');
+                    }, function(err) {
                         expect(err).toEqual(new Error('No platform provided. Please specify a platform to update.'));
-                        done();
-                    });
+                    }).fin(done);
                 });
                 it('should fail if more than one platform is specified', function(done) {
-                    cordova.platform('update', ['android', 'ios'], function(err) {
+                    cordova.raw.platform('update', ['android', 'ios']).then(function() {
+                        expect('this call').toBe('fail');
+                    }, function(err) {
                         expect(err).toEqual(new Error('Platform update can only be executed on one platform at a time.'));
-                        done();
-                    });
+                    }).fin(done);
                 });
             });
 
@@ -223,10 +240,11 @@ describe('platform command', function() {
                 it('should shell out to the platform update script', function(done) {
                     var oldVersion = platforms['ios'].version;
                     platforms['ios'].version = '1.0.0';
-                    cordova.platform('update', ['ios'], function(err) {
+                    cordova.raw.platform('update', ['ios']).then(function() {
+                        expect(exec).toHaveBeenCalledWith('HOMEDIR/.cordova/lib/ios/cordova/1.0.0/bin/update "some/path/platforms/ios"', jasmine.any(Function));
+                    }, function(err) {
                         expect(err).toBeUndefined();
-                        expect(exec).toHaveBeenCalledWith('HOMEDIR/.cordova/lib/ios/cordova/1.0.0/bin/update "some/path/platforms/ios"', jasmine.any(Object), jasmine.any(Function));
-
+                    }).fin(function() {
                         platforms['ios'].version = oldVersion;
                         done();
                     });
@@ -235,81 +253,92 @@ describe('platform command', function() {
         });
     });
     describe('hooks', function() {
-        describe('list (ls) hooks', function() {
+        describe('list (ls) hooks', function(done) {
             it('should fire before hooks through the hooker module', function() {
-                cordova.platform();
-                expect(fire).toHaveBeenCalledWith('before_platform_ls', jasmine.any(Function));
+                cordova.raw.platform().then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_platform_ls');
+                    done();
+                });
             });
-            it('should fire after hooks through the hooker module', function() {
-                cordova.platform();
-                expect(fire).toHaveBeenCalledWith('after_platform_ls', jasmine.any(Function));
+            it('should fire after hooks through the hooker module', function(done) {
+                cordova.raw.platform().then(function() {
+                    expect(fire).toHaveBeenCalledWith('after_platform_ls');
+                    done();
+                });
             });
         });
         describe('remove (rm) hooks', function() {
-            it('should fire before hooks through the hooker module', function() {
-                cordova.platform('rm', 'android');
-                expect(fire).toHaveBeenCalledWith('before_platform_rm', {platforms:['android']}, jasmine.any(Function));
+            it('should fire before hooks through the hooker module', function(done) {
+                cordova.raw.platform('rm', 'android').then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_platform_rm', {platforms:['android']});
+                    done();
+                });
             });
-            it('should fire after hooks through the hooker module', function() {
-                cordova.platform('rm', 'android');
-                expect(fire).toHaveBeenCalledWith('after_platform_rm', {platforms:['android']}, jasmine.any(Function));
+            it('should fire after hooks through the hooker module', function(done) {
+                cordova.raw.platform('rm', 'android').then(function() {
+                    expect(fire).toHaveBeenCalledWith('after_platform_rm', {platforms:['android']});
+                    done();
+                });
             });
         });
         describe('add hooks', function() {
-            it('should fire before and after hooks through the hooker module', function() {
-                cordova.platform('add', 'android');
-                expect(fire).toHaveBeenCalledWith('before_platform_add', {platforms:['android']}, jasmine.any(Function));
-                expect(fire).toHaveBeenCalledWith('after_platform_add', {platforms:['android']}, jasmine.any(Function));
+            it('should fire before and after hooks through the hooker module', function(done) {
+                cordova.raw.platform('add', 'android').then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_platform_add', {platforms:['android']});
+                    expect(fire).toHaveBeenCalledWith('after_platform_add', {platforms:['android']});
+                    done();
+                });
             });
         });
     });
 });
 
-describe('platform.supports(name, callback)', function() {
+describe('platform.supports(name)', function() {
     var supports = {};
     beforeEach(function() {
         supported_platforms.forEach(function(p) {
-            supports[p] = spyOn(platforms[p].parser, 'check_requirements').andCallFake(function(project, cb) { cb(); });
+            supports[p] = spyOn(platforms[p].parser, 'check_requirements').andReturn(Q());
         });
     });
-    it('should require a platform name', function() {
-        expect(function() {
-            cordova.platform.supports(project_dir, undefined, function(e){});
-        }).toThrow();
-    });
 
-    it('should require a callback function', function() {
-        expect(function() {
-            cordova.platform.supports(project_dir, 'android', undefined);
-        }).toThrow();
+    function expectFailure(p, done, post) {
+        p.then(function() {
+            expect('this call').toBe('fail');
+        }, post).fin(done);
+    }
+
+    it('should require a platform name', function(done) {
+        expectFailure(cordova.raw.platform.supports(project_dir, undefined), done, function(err) {
+            expect(err).toEqual(jasmine.any(Error));
+        });
     });
 
     describe('when platform is unknown', function() {
-        it('should trigger callback with false', function(done) {
-            cordova.platform.supports(project_dir, 'windows-3.1', function(e) {
-                expect(e).toEqual(jasmine.any(Error));
+        it('should reject', function(done) {
+            expectFailure(cordova.raw.platform.supports(project_dir, 'windows-3.1'), done, function(err) {
+                expect(err).toEqual(jasmine.any(Error));
                 done();
             });
         });
     });
 
     describe('when platform is supported', function() {
-        it('should trigger callback without error', function(done) {
-            cordova.platform.supports(project_dir, 'android', function(e) {
-                expect(e).toBeNull();
-                done();
-            });
+        it('should resolve', function(done) {
+            cordova.raw.platform.supports(project_dir, 'android').then(function() {
+                expect(1).toBe(1);
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
     });
 
     describe('when platform is unsupported', function() {
-        it('should trigger callback with error', function(done) {
+        it('should reject', function(done) {
             supported_platforms.forEach(function(p) {
-                supports[p].andCallFake(function(project, cb) { cb(new Error('no sdk')); });
+                supports[p].andReturn(Q.reject(new Error('no sdk')));
             });
-            cordova.platform.supports(project_dir, 'android', function(e) {
-                expect(e).toEqual(jasmine.any(Error));
-                done();
+            expectFailure(cordova.raw.platform.supports(project_dir, 'android'), done, function(err) {
+                expect(err).toEqual(jasmine.any(Error));
             });
         });
     });
@@ -318,9 +347,9 @@ describe('platform.supports(name, callback)', function() {
 describe('platform parsers', function() {
     it('should be exposed on the platform module', function() {
         for (var platform in platforms) {
-            expect(cordova.platform[platform]).toBeDefined();
+            expect(cordova.raw.platform[platform]).toBeDefined();
             for (var prop in platforms[platform]) {
-                expect(cordova.platform[platform][prop]).toBeDefined();
+                expect(cordova.raw.platform[platform][prop]).toBeDefined();
             }
         }
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/plugin.spec.js
----------------------------------------------------------------------
diff --git a/spec/plugin.spec.js b/spec/plugin.spec.js
index 5e021e2..1f4bf97 100644
--- a/spec/plugin.spec.js
+++ b/spec/plugin.spec.js
@@ -19,11 +19,13 @@
 var cordova = require('../cordova'),
     path = require('path'),
     shell = require('shelljs'),
+    child_process = require('child_process'),
     plugman = require('plugman'),
     fs = require('fs'),
     util = require('../src/util'),
     config = require('../src/config'),
     hooker = require('../src/hooker'),
+    Q = require('q'),
     platforms = require('../platforms');
 
 var cwd = process.cwd();
@@ -36,14 +38,9 @@ describe('plugin command', function() {
     var is_cordova, list_platforms, fire, find_plugins, rm, mkdir, existsSync, exec, prep_spy, plugman_install, plugman_fetch, parsers = {}, uninstallPlatform, uninstallPlugin;
     beforeEach(function() {
         is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
-        fire = spyOn(hooker.prototype, 'fire').andCallFake(function(e, opts, cb) {
-            if (cb === undefined) cb = opts;
-            cb(false);
-        });
+        fire = spyOn(hooker.prototype, 'fire').andReturn(Q());
         supported_platforms.forEach(function(p) {
-            parsers[p] = jasmine.createSpy(p + ' update_project').andCallFake(function(cfg, cb) {
-                cb();
-            });
+            parsers[p] = jasmine.createSpy(p + ' update_project').andReturn(Q());
             spyOn(platforms[p], 'parser').andReturn({
                 staging_dir:function(){return ''}
             });
@@ -53,47 +50,49 @@ describe('plugin command', function() {
         rm = spyOn(shell, 'rm');
         mkdir = spyOn(shell, 'mkdir');
         existsSync = spyOn(fs, 'existsSync').andReturn(false);
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, '');
-        });
-        prep_spy = spyOn(cordova, 'prepare').andCallFake(function(t, cb) {
-            cb();
-        });
-        plugman_install = spyOn(plugman, 'install').andCallFake(function(platform, platform_dir, plugin, plugins_dir, options, callback) {
-            callback();
-        });
-        plugman_fetch = spyOn(plugman, 'fetch').andCallFake(function(target, plugins_dir, opts, cb) { cb(false, path.join(plugins_dir, target)); });
-        plugman_search = spyOn(plugman, 'search').andCallFake(function(params, cb) { cb(); });
-        uninstallPlatform = spyOn(plugman.uninstall, 'uninstallPlatform');
-        uninstallPlugin = spyOn(plugman.uninstall, 'uninstallPlugin').andCallFake(function(target, plugins_dir, cb) {
-            cb && cb();
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(0, '', '');
         });
+        prep_spy = spyOn(cordova.raw, 'prepare').andReturn(Q());
+        plugman_install = spyOn(plugman.raw, 'install').andReturn(Q());
+        plugman_fetch = spyOn(plugman.raw, 'fetch').andCallFake(function(target, plugins_dir, opts) { return Q(path.join(plugins_dir, target)); });
+        plugman_search = spyOn(plugman.raw, 'search').andReturn(Q());
+        uninstallPlatform = spyOn(plugman.raw.uninstall, 'uninstallPlatform').andReturn(Q());
+        uninstallPlugin = spyOn(plugman.raw.uninstall, 'uninstallPlugin').andReturn(Q());
     });
 
     describe('failure', function() {
-        it('should not run outside of a Cordova-based project by calling util.isCordova', function() {
+        function expectFailure(p, done, post) {
+            p.then(function() {
+                expect('this call').toBe('fail');
+            }, post).fin(done);
+        }
+
+        it('should not run outside of a Cordova-based project by calling util.isCordova', function(done) {
             is_cordova.andReturn(false);
-            expect(function() {
-                cordova.plugin();
-                expect(is_cordova).toHaveBeenCalled();
-            }).toThrow('Current working directory is not a Cordova-based project.');
+            expectFailure(cordova.raw.plugin(), done, function(err) {
+                expect(err).toEqual(new Error('Current working directory is not a Cordova-based project.'));
+            });
         });
-        it('should report back an error if used with `add` and no plugin is specified', function() {
-            expect(function() {
-               cordova.plugin('add');
-            }).toThrow('You need to qualify `add` or `remove` with one or more plugins!');
+        it('should report back an error if used with `add` and no plugin is specified', function(done) {
+            expectFailure(cordova.raw.plugin('add'), done, function(err) {
+                expect(err).toEqual(new Error('You need to qualify `add` or `remove` with one or more plugins!'));
+            });
         });
-        it('should report back an error if used with `rm` and no plugin is specified', function() {
-            expect(function() {
-               cordova.plugin('rm');
-            }).toThrow('You need to qualify `add` or `remove` with one or more plugins!');
+        it('should report back an error if used with `rm` and no plugin is specified', function(done) {
+            expectFailure(cordova.raw.plugin('rm'), done, function(err) {
+                expect(err).toEqual(new Error('You need to qualify `add` or `remove` with one or more plugins!'));
+            });
         });
     });
 
     describe('success', function() {
-        it('should run inside a Cordova-based project by calling util.isCordova', function() {
-            cordova.plugin();
-            expect(is_cordova).toHaveBeenCalled();
+        it('should run inside a Cordova-based project by calling util.isCordova', function(done) {
+            cordova.raw.plugin().then(function() {
+                expect(is_cordova).toHaveBeenCalled();
+                done();
+            });
         });
 
         describe('`ls`', function() {
@@ -106,55 +105,68 @@ describe('plugin command', function() {
                     expect(res).toEqual('No plugins added. Use `cordova plugin add <plugin>`.');
                     done();
                 });
-                cordova.plugin('list');
+                cordova.raw.plugin('list');
             });
             it('should list out added plugins in a project', function(done) {
                 cordova.on('results', function(res) {
                     expect(res).toEqual(sample_plugins);
                     done();
                 });
-                cordova.plugin('list');
+                cordova.raw.plugin('list');
             });
-            it('should trigger callback with list of plugins', function(done) {
-                cordova.plugin('list', [], function(e, plugins) {
-                    expect(e).not.toBeDefined();
+            it('should resolve with a list of plugins', function(done) {
+                cordova.raw.plugin('list', []).then(function(plugins) {
                     expect(plugins).toEqual(sample_plugins);
-                    done();
-                });
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
         describe('`add`', function() {
-            it('should call plugman.fetch for each plugin', function() {
-                cordova.plugin('add', sample_plugins);
-                sample_plugins.forEach(function(p) {
-                    expect(plugman_fetch).toHaveBeenCalledWith(p, plugins_dir, {}, jasmine.any(Function));
-                });
+            it('should call plugman.fetch for each plugin', function(done) {
+                cordova.raw.plugin('add', sample_plugins).then(function() {
+                    sample_plugins.forEach(function(p) {
+                        expect(plugman_fetch).toHaveBeenCalledWith(p, plugins_dir, {});
+                    });
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
-            it('should call plugman.install, for each plugin, for every platform', function() {
-                cordova.plugin('add', sample_plugins);
-                sample_plugins.forEach(function(plug) {
-                    supported_platforms.forEach(function(plat) {
-                        expect(plugman_install).toHaveBeenCalledWith((plat=='blackberry'?'blackberry10':plat), path.join(project_dir, 'platforms', plat), plug, plugins_dir, jasmine.any(Object), jasmine.any(Function));
+            it('should call plugman.install, for each plugin, for every platform', function(done) {
+                cordova.raw.plugin('add', sample_plugins).then(function(err) {
+                    sample_plugins.forEach(function(plug) {
+                        supported_platforms.forEach(function(plat) {
+                            expect(plugman_install).toHaveBeenCalledWith((plat=='blackberry'?'blackberry10':plat), path.join(project_dir, 'platforms', plat), plug, plugins_dir, jasmine.any(Object));
+                        });
                     });
-                });
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
-            it('should pass down variables into plugman', function() {
-                cordova.plugin('add', "one", "--variable", "foo=bar");
-                supported_platforms.forEach(function(plat) {
-                    expect(plugman_install).toHaveBeenCalledWith((plat=='blackberry'?'blackberry10':plat), path.join(project_dir, 'platforms', plat), "one", plugins_dir, {www_dir: jasmine.any(String), cli_variables: { FOO: "bar"}}, jasmine.any(Function));
-                });
+            it('should pass down variables into plugman', function(done) {
+                cordova.raw.plugin('add', "one", "--variable", "foo=bar").then(function() {
+                    supported_platforms.forEach(function(plat) {
+                        expect(plugman_install).toHaveBeenCalledWith((plat=='blackberry'?'blackberry10':plat), path.join(project_dir, 'platforms', plat), "one", plugins_dir, {www_dir: jasmine.any(String), cli_variables: { FOO: "bar"}});
+                    });
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
-            it('should trigger callback without an error', function(done) {
-                cordova.plugin('add', sample_plugins, function(e) {
-                    expect(e).not.toBeDefined();
-                    done();
-                });
+            it('should resolve without an error', function(done) {
+                cordova.raw.plugin('add', sample_plugins).then(function() {
+                    expect(1).toBe(1);
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
         describe('`search`', function() {
-            it('should call plugman.search', function() {
-                cordova.plugin('search', sample_plugins);
-                expect(plugman_search).toHaveBeenCalledWith(sample_plugins, jasmine.any(Function));
+            it('should call plugman.search', function(done) {
+                cordova.raw.plugin('search', sample_plugins).then(function() {
+                    expect(plugman_search).toHaveBeenCalledWith(sample_plugins);
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
         describe('`remove`',function() {
@@ -165,30 +177,39 @@ describe('plugin command', function() {
                     platforms:subset
                 });
             });
-            it('should throw if plugin is not installed', function() {
-                expect(function() {
-                    cordova.plugin('rm', 'somethingrandom');
-                }).toThrow('Plugin "somethingrandom" not added to project.');
+            it('should throw if plugin is not installed', function(done) {
+                cordova.raw.plugin('rm', 'somethingrandom').then(function() {
+                    expect('this call').toBe('fail');
+                }, function(err) {
+                    expect(err).toEqual(new Error('Plugin "somethingrandom" not added to project.'));
+                }).fin(done);
             });
 
-            it('should call plugman.uninstall.uninstallPlatform for every matching installedplugin-supportedplatform pair', function() {
-                cordova.plugin('rm', sample_plugins);
-                sample_plugins.forEach(function(plug) {
-                    subset.forEach(function(plat) {
-                        expect(uninstallPlatform).toHaveBeenCalledWith(plat, path.join(project_dir, 'platforms', plat), plug, plugins_dir, jasmine.any(Object));
+            it('should call plugman.uninstall.uninstallPlatform for every matching installedplugin-supportedplatform pair', function(done) {
+                cordova.raw.plugin('rm', sample_plugins).then(function() {
+                    sample_plugins.forEach(function(plug) {
+                        subset.forEach(function(plat) {
+                            expect(uninstallPlatform).toHaveBeenCalledWith(plat, path.join(project_dir, 'platforms', plat), plug, plugins_dir, jasmine.any(Object));
+                        });
                     });
-                });
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
-            it('should call plugman.uninstall.uninstallPlugin once for every removed plugin', function() {
+            it('should call plugman.uninstall.uninstallPlugin once for every removed plugin', function(done) {
                 uninstallPlugin.reset();
-                cordova.plugin('rm', sample_plugins);
-                expect(uninstallPlugin.callCount).toBe(2);
+                cordova.raw.plugin('rm', sample_plugins).then(function() {
+                    expect(uninstallPlugin.callCount).toBe(2);
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
-            it('should trigger callback without an error', function(done) {
-                cordova.plugin('rm', sample_plugins, function(e) {
-                    expect(e).not.toBeDefined();
-                    done();
-                });
+            it('should resolve without an error', function(done) {
+                cordova.raw.plugin('rm', sample_plugins).then(function() {
+                    expect(1).toBe(1);
+                }, function(err) {
+                    expect(err).not.toBeDefined();
+                }).fin(done);
             });
         });
     });
@@ -200,30 +221,45 @@ describe('plugin command', function() {
             });
         });
         describe('list (ls) hooks', function() {
-            it('should fire before hooks through the hooker module', function() {
-                cordova.plugin();
-                expect(fire).toHaveBeenCalledWith('before_plugin_ls', jasmine.any(Function));
+            it('should fire before hooks through the hooker module', function(done) {
+                cordova.raw.plugin().then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_plugin_ls');
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
-            it('should fire after hooks through the hooker module', function() {
-                cordova.plugin();
-                expect(fire).toHaveBeenCalledWith('after_plugin_ls', jasmine.any(Function));
+            it('should fire after hooks through the hooker module', function(done) {
+                cordova.raw.plugin().then(function() {
+                    expect(fire).toHaveBeenCalledWith('after_plugin_ls');
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
         describe('remove (rm) hooks', function() {
-            it('should fire before hooks through the hooker module', function() {
-                cordova.plugin('rm', 'two');
-                expect(fire).toHaveBeenCalledWith('before_plugin_rm', {plugins:['two'], options: []}, jasmine.any(Function));
+            it('should fire before hooks through the hooker module', function(done) {
+                cordova.raw.plugin('rm', 'two').then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_plugin_rm', {plugins:['two'], options: []});
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
-            it('should fire after hooks through the hooker module', function() {
-                cordova.plugin('rm', 'one');
-                expect(fire).toHaveBeenCalledWith('after_plugin_rm', {plugins:['one'], options:[]}, jasmine.any(Function));
+            it('should fire after hooks through the hooker module', function(done) {
+                cordova.raw.plugin('rm', 'one').then(function() {
+                    expect(fire).toHaveBeenCalledWith('after_plugin_rm', {plugins:['one'], options:[]});
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
         describe('add hooks', function() {
-            it('should fire before and after hooks through the hooker module', function() {
-                cordova.plugin('add', 'android');
-                expect(fire).toHaveBeenCalledWith('before_plugin_add', {plugins:['android'], options: []}, jasmine.any(Function));
-                expect(fire).toHaveBeenCalledWith('after_plugin_add', {plugins:['android'], options: []}, jasmine.any(Function));
+            it('should fire before and after hooks through the hooker module', function(done) {
+                cordova.raw.plugin('add', 'android').then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_plugin_add', {plugins:['android'], options: []});
+                    expect(fire).toHaveBeenCalledWith('after_plugin_add', {plugins:['android'], options: []});
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/prepare.spec.js
----------------------------------------------------------------------
diff --git a/spec/prepare.spec.js b/spec/prepare.spec.js
index 19710c9..c392e75 100644
--- a/spec/prepare.spec.js
+++ b/spec/prepare.spec.js
@@ -26,6 +26,7 @@ var cordova = require('../cordova'),
     platforms = require('../platforms'),
     hooker = require('../src/hooker'),
     fixtures = path.join(__dirname, 'fixtures'),
+    Q = require('q'),
     hooks = path.join(fixtures, 'hooks');
 
 var project_dir = '/some/path';
@@ -37,86 +38,99 @@ describe('prepare command', function() {
     beforeEach(function() {
         is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
-        fire = spyOn(hooker.prototype, 'fire').andCallFake(function(e, opts, cb) {
-            cb(false);
-        });
+        fire = spyOn(hooker.prototype, 'fire').andReturn(Q());
         config_parser = spyOn(util, 'config_parser');
         supported_platforms.forEach(function(p) {
-            parsers[p] = jasmine.createSpy(p + ' update_project').andCallFake(function(cfg, cb) {
-                cb();
-            });
+            parsers[p] = jasmine.createSpy(p + ' update_project').andReturn(Q());
             spyOn(platforms[p], 'parser').andReturn({
                 update_project:parsers[p],
                 www_dir:function() { return path.join(project_dir, 'platforms', p, 'www'); }
             });
         });
-        plugman_prepare = spyOn(plugman, 'prepare');
+        plugman_prepare = spyOn(plugman, 'prepare').andReturn(Q());
         find_plugins = spyOn(util, 'findPlugins').andReturn([]);
         plugman_get_json = spyOn(plugman.config_changes, 'get_platform_json').andReturn({});
-        load = spyOn(lazy_load, 'based_on_config').andCallFake(function(root, platform, cb) { cb(); });
+        load = spyOn(lazy_load, 'based_on_config').andReturn(Q());
     });
 
     describe('failure', function() {
-        it('should not run outside of a cordova-based project by calling util.isCordova', function() {
+        it('should not run outside of a cordova-based project by calling util.isCordova', function(done) {
             is_cordova.andReturn(false);
-            expect(function() {
-                cordova.prepare();
-                expect(is_cordova).toHaveBeenCalled();
-            }).toThrow('Current working directory is not a Cordova-based project.');
+            cordova.raw.prepare().then(function() {
+                expect('this call').toBe('fail');
+            }, function(err) {
+                expect(err).toEqual(new Error('Current working directory is not a Cordova-based project.'));
+            }).fin(done);
         });
-        it('should not run inside a cordova-based project with no platforms', function() {
+        it('should not run inside a cordova-based project with no platforms', function(done) {
             list_platforms.andReturn([]);
-            expect(function() {
-                cordova.prepare();
-            }).toThrow('No platforms added to this project. Please use `cordova platform add <platform>`.');
+            cordova.raw.prepare().then(function() {
+                expect('this call').toBe('fail');
+            }, function(err) {
+                expect(err).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+            }).fin(done);
         });
     });
 
     describe('success', function() {
-        it('should run inside a Cordova-based project by calling util.isCordova', function() {
-            cordova.prepare();
-            expect(is_cordova).toHaveBeenCalled();
+        it('should run inside a Cordova-based project by calling util.isCordova', function(done) {
+            cordova.raw.prepare().then(function() {
+                expect(is_cordova).toHaveBeenCalled();
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
-        it('should parse user\'s config.xml by instantiating a config_parser only _after_ before_prepare is called', function() {
-            var before_prep, after_prep, cont;
-            fire.andCallFake(function(e, opts, cb) {
+        it('should parse user\'s config.xml by instantiating a config_parser only _after_ before_prepare is called', function(done) {
+            var before_prep;
+            config_parser.andCallFake(function() {
+                expect(before_prep).toBe(true);
+            });
+            fire.andCallFake(function(e, opts) {
                 if (e == 'before_prepare') {
                     before_prep = true;
+                    expect(config_parser).not.toHaveBeenCalled();
                 }
-                cont = cb;
+                return Q();
             });
-            runs(function() {
-                cordova.prepare();
-            });
-            waitsFor(function() { return before_prep; });
-            runs(function() {
-                expect(config_parser).not.toHaveBeenCalled();
-                cont();
+
+            cordova.raw.prepare().then(function() {
+                expect(before_prep).toBe(true);
                 expect(config_parser).toHaveBeenCalledWith(path.join(project_dir, 'www', 'config.xml'));
-            });
-        });
-        it('should invoke each platform\'s parser\'s update_project method', function() {
-            cordova.prepare();
-            supported_platforms.forEach(function(p) {
-                expect(parsers[p]).toHaveBeenCalled();
-            });
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
-        it('should invoke lazy_load for each platform to make sure platform libraries are loaded', function() {
-            cordova.prepare();
-            supported_platforms.forEach(function(p) {
-                expect(load).toHaveBeenCalledWith(project_dir, p, jasmine.any(Function));
-            });
+        it('should invoke each platform\'s parser\'s update_project method', function(done) {
+            cordova.raw.prepare().then(function() {
+                supported_platforms.forEach(function(p) {
+                    expect(parsers[p]).toHaveBeenCalled();
+                });
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
-        describe('plugman integration', function() {
-            it('should invoke plugman.prepare after update_project', function() {
-                cordova.prepare();
-                var plugins_dir = path.join(project_dir, 'plugins');
+        it('should invoke lazy_load for each platform to make sure platform libraries are loaded', function(done) {
+            cordova.raw.prepare().then(function() {
                 supported_platforms.forEach(function(p) {
-                    var platform_path = path.join(project_dir, 'platforms', p);
-                    expect(plugman_prepare).toHaveBeenCalledWith(platform_path, (p=='blackberry'?'blackberry10':p), plugins_dir);
+                    expect(load).toHaveBeenCalledWith(project_dir, p);
                 });
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
+        });
+        describe('plugman integration', function() {
+            it('should invoke plugman.prepare after update_project', function(done) {
+                cordova.raw.prepare().then(function() {
+                    var plugins_dir = path.join(project_dir, 'plugins');
+                    supported_platforms.forEach(function(p) {
+                        var platform_path = path.join(project_dir, 'platforms', p);
+                        expect(plugman_prepare).toHaveBeenCalledWith(platform_path, (p=='blackberry'?'blackberry10':p), plugins_dir);
+                    });
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
-            it('should invoke add_plugin_changes for any added plugins to verify configuration changes for plugins are in place', function() {
+            it('should invoke add_plugin_changes for any added plugins to verify configuration changes for plugins are in place', function(done) {
                 var plugins_dir = path.join(project_dir, 'plugins');
                 find_plugins.andReturn(['testPlugin']);
                 plugman_get_json.andReturn({
@@ -125,11 +139,14 @@ describe('prepare command', function() {
                     }
                 });
                 var add_plugin_changes = spyOn(plugman.config_changes, 'add_plugin_changes');
-                cordova.prepare();
-                supported_platforms.forEach(function(p) {
-                    var platform_path = path.join(project_dir, 'platforms', p);
-                    expect(add_plugin_changes).toHaveBeenCalledWith((p=='blackberry'?'blackberry10':p), platform_path, plugins_dir, 'testPlugin', 'plugin vars', true, false);
-                });
+                cordova.raw.prepare().then(function() {
+                    supported_platforms.forEach(function(p) {
+                        var platform_path = path.join(project_dir, 'platforms', p);
+                        expect(add_plugin_changes).toHaveBeenCalledWith((p=='blackberry'?'blackberry10':p), platform_path, plugins_dir, 'testPlugin', 'plugin vars', true, false);
+                    });
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
     });
@@ -137,15 +154,19 @@ describe('prepare command', function() {
 
     describe('hooks', function() {
         describe('when platforms are added', function() {
-            it('should fire before hooks through the hooker module, and pass in platforms and paths as data object', function() {
-                cordova.prepare();
-                expect(fire).toHaveBeenCalledWith('before_prepare', {verbose: false, platforms:supported_platforms, options: [], paths:supported_platforms_paths}, jasmine.any(Function));
+            it('should fire before hooks through the hooker module, and pass in platforms and paths as data object', function(done) {
+                cordova.raw.prepare().then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_prepare', {verbose: false, platforms:supported_platforms, options: [], paths:supported_platforms_paths});
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
             it('should fire after hooks through the hooker module, and pass in platforms and paths as data object', function(done) {
-                cordova.prepare('android', function() {
-                     expect(fire).toHaveBeenCalledWith('after_prepare', {verbose: false, platforms:['android'], options: [], paths:[path.join(project_dir, 'platforms', 'android', 'www')]}, jasmine.any(Function));
-                     done();
-                });
+                cordova.raw.prepare('android').then(function() {
+                     expect(fire).toHaveBeenCalledWith('after_prepare', {verbose: false, platforms:['android'], options: [], paths:[path.join(project_dir, 'platforms', 'android', 'www')]});
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
 
@@ -153,12 +174,14 @@ describe('prepare command', function() {
             beforeEach(function() {
                 list_platforms.andReturn([]);
             });
-            it('should not fire the hooker', function() {
-                expect(function() {
-                    cordova.prepare();
-                }).toThrow();
-                expect(fire).not.toHaveBeenCalledWith('before_prepare');
-                expect(fire).not.toHaveBeenCalledWith('after_prepare');
+            it('should not fire the hooker', function(done) {
+                cordova.raw.prepare().then(function() {
+                    expect('this call').toBe('fail');
+                }, function(err) {
+                    expect(err).toEqual(jasmine.any(Error));
+                    expect(fire).not.toHaveBeenCalledWith('before_prepare');
+                    expect(fire).not.toHaveBeenCalledWith('after_prepare');
+                }).fin(done);
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/run.spec.js
----------------------------------------------------------------------
diff --git a/spec/run.spec.js b/spec/run.spec.js
index df113bd..302c425 100644
--- a/spec/run.spec.js
+++ b/spec/run.spec.js
@@ -18,10 +18,11 @@
 */
 var cordova = require('../cordova'),
     platforms = require('../platforms'),
-    shell = require('shelljs'),
+    child_process = require('child_process'),
     path = require('path'),
     fs = require('fs'),
     hooker = require('../src/hooker'),
+    Q = require('q'),
     util = require('../src/util');
 
 var supported_platforms = Object.keys(platforms).filter(function(p) { return p != 'www'; });
@@ -33,68 +34,79 @@ describe('run command', function() {
     beforeEach(function() {
         is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
-        fire = spyOn(hooker.prototype, 'fire').andCallFake(function(e, opts, cb) {
-            cb(false);
+        fire = spyOn(hooker.prototype, 'fire').andReturn(Q());
+        prepare_spy = spyOn(cordova.raw, 'prepare').andReturn(Q());
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(0, '', '');
         });
-        prepare_spy = spyOn(cordova, 'prepare').andCallFake(function(platforms, cb) {
-            cb();
-        });
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) { cb(0, ''); });
     });
     describe('failure', function() {
-        it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function() {
+        it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function(done) {
             list_platforms.andReturn([]);
-            expect(function() {
-                cordova.run();
-            }).toThrow('No platforms added to this project. Please use `cordova platform add <platform>`.');
+            cordova.raw.run().then(function() {
+                expect('this call').toBe('fail');
+            }, function(err) {
+                expect(err).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+            }).fin(done);
         });
-        it('should not run outside of a Cordova-based project', function() {
+        it('should not run outside of a Cordova-based project', function(done) {
             is_cordova.andReturn(false);
-            expect(function() {
-                cordova.run();
-            }).toThrow('Current working directory is not a Cordova-based project.');
+            cordova.raw.run().then(function() {
+                expect('this call').toBe('fail');
+            }, function(err) {
+                expect(err).toEqual(new Error('Current working directory is not a Cordova-based project.'));
+            }).fin(done);
         });
     });
 
     describe('success', function() {
         it('should run inside a Cordova-based project with at least one added platform and call prepare and shell out to the run script', function(done) {
-            cordova.run(['android','ios'], function(err) {
-                expect(prepare_spy).toHaveBeenCalledWith(['android', 'ios'], jasmine.any(Function));
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'android', 'cordova', 'run') + '" --device', jasmine.any(Object), jasmine.any(Function));
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'ios', 'cordova', 'run') + '" --device', jasmine.any(Object), jasmine.any(Function));
-                done();
-            });
+            cordova.raw.run(['android','ios']).then(function() {
+                expect(prepare_spy).toHaveBeenCalledWith(['android', 'ios']);
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'android', 'cordova', 'run') + '" --device', jasmine.any(Function));
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'ios', 'cordova', 'run') + '" --device', jasmine.any(Function));
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
         it('should pass down parameters', function(done) {
-            cordova.run({platforms: ['blackberry10'], options:['--device', '--password', '1q1q']}, function(err) {
-                expect(prepare_spy).toHaveBeenCalledWith(['blackberry10'], jasmine.any(Function));
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'blackberry10', 'cordova', 'run') + '" --device --password 1q1q', jasmine.any(Object), jasmine.any(Function));
-                done();
-            });
+            cordova.raw.run({platforms: ['blackberry10'], options:['--device', '--password', '1q1q']}).then(function() {
+                expect(prepare_spy).toHaveBeenCalledWith(['blackberry10']);
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'blackberry10', 'cordova', 'run') + '" --device --password 1q1q', jasmine.any(Function));
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
     });
 
     describe('hooks', function() {
         describe('when platforms are added', function() {
-            it('should fire before hooks through the hooker module', function() {
-                cordova.run(['android', 'ios']);
-                expect(fire).toHaveBeenCalledWith('before_run', {verbose: false, platforms:['android', 'ios'], options: []}, jasmine.any(Function));
+            it('should fire before hooks through the hooker module', function(done) {
+                cordova.raw.run(['android', 'ios']).then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_run', {verbose: false, platforms:['android', 'ios'], options: []});
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
             it('should fire after hooks through the hooker module', function(done) {
-                cordova.run('android', function() {
-                     expect(fire).toHaveBeenCalledWith('after_run', {verbose: false, platforms:['android'], options: []}, jasmine.any(Function));
-                     done();
-                });
+                cordova.raw.run('android').then(function() {
+                     expect(fire).toHaveBeenCalledWith('after_run', {verbose: false, platforms:['android'], options: []});
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
 
         describe('with no platforms added', function() {
-            it('should not fire the hooker', function() {
+            it('should not fire the hooker', function(done) {
                 list_platforms.andReturn([]);
-                expect(function() {
-                    cordova.run();
-                }).toThrow();
-                expect(fire).not.toHaveBeenCalled();
+                cordova.raw.run().then(function() {
+                    expect('this call').toBe('fail');
+                }, function(err) {
+                    expect(fire).not.toHaveBeenCalled();
+                    expect(err).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+                }).fin(done);
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/serve.spec.js
----------------------------------------------------------------------
diff --git a/spec/serve.spec.js b/spec/serve.spec.js
index aab1e43..6c43da0 100644
--- a/spec/serve.spec.js
+++ b/spec/serve.spec.js
@@ -59,10 +59,10 @@ xdescribe('serve command', function() {
         };
 
         beforeEach(function() {
-            cordova.create(tempDir);
+            cordova.raw.create(tempDir);
             process.chdir(tempDir);
-            cordova.platform('add', 'android');
-            cordova.platform('add', 'ios');
+            cordova.raw.platform('add', 'android');
+            cordova.raw.platform('add', 'ios');
 
             // Write testing HTML files into the directory.
             fs.writeFileSync(path.join(tempDir, 'platforms', 'android', 'assets', 'www', 'test.html'), payloads.android);

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/wrappers.spec.js
----------------------------------------------------------------------
diff --git a/spec/wrappers.spec.js b/spec/wrappers.spec.js
new file mode 100644
index 0000000..c002856
--- /dev/null
+++ b/spec/wrappers.spec.js
@@ -0,0 +1,39 @@
+var Q = require('q'),
+    cordova = require('../cordova');
+
+describe('callback wrapper', function() {
+    var calls = ['prepare', 'build', 'create', 'emulate', 'plugin', 'platform', 'compile', 'run'];
+    for (var i = 0; i < calls.length; i++) {
+        var call = calls[i];
+
+        describe('`' + call + '`', function() {
+            var raw;
+            beforeEach(function() {
+                raw = spyOn(cordova.raw, call);
+            });
+
+            it('should work with no callback and success', function() {
+                raw.andReturn(Q());
+                cordova[call]();
+                expect(raw).toHaveBeenCalled();
+            });
+
+            it('should call the callback on success', function(done) {
+                raw.andReturn(Q());
+                cordova[call](function(err) {
+                    expect(err).toBeUndefined();
+                    done();
+                });
+            });
+
+            it('should call the callback with the error on failure', function(done) {
+                raw.andReturn(Q.reject(new Error('junk')));
+                cordova[call](function(err) {
+                    expect(err).toEqual(new Error('junk'));
+                    done();
+                });
+            });
+        });
+    }
+});
+

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/build.js
----------------------------------------------------------------------
diff --git a/src/build.js b/src/build.js
index 5883762..440817c 100644
--- a/src/build.js
+++ b/src/build.js
@@ -17,18 +17,14 @@
     under the License.
 */
 var cordova_util      = require('./util'),
-    path              = require('path'),
-    fs                = require('fs'),
-    shell             = require('shelljs'),
-    hooker            = require('./hooker'),
-    events            = require('./events'),
-    n                 = require('ncallbacks');
+    Q                 = require('q'),
+    hooker            = require('./hooker');
 
-module.exports = function build(options, callback) {
+// Returns a promise.
+module.exports = function build(options) {
     var projectRoot = cordova_util.isCordova(process.cwd());
 
-    if (options instanceof Function && callback === undefined) {
-        callback = options;
+    if (!options) {
         options = {
             verbose: false,
             platforms: [],
@@ -38,39 +34,17 @@ module.exports = function build(options, callback) {
 
     options = cordova_util.preProcessOptions(options);
     if (options.constructor.name === "Error") {
-        if (callback) return callback(options);
-        else throw options;
+        return Q.reject(options);
     }
 
     // fire build hooks
     var hooks = new hooker(projectRoot);
-    hooks.fire('before_build', options, function(err) {
-        if (err) {
-            if (callback) callback(err);
-            else throw err;
-        } else {
-            require('../cordova').prepare(options, function(err) {
-                if (err) {
-                    if (callback) callback(err);
-                    else throw err;
-                } else {
-                    require('../cordova').compile(options, function(err) {
-                        if (err) {
-                            if (callback) callback(err);
-                            else throw err;
-                        } else {
-                            hooks.fire('after_build', options, function(err) {
-                                if (err) {
-                                    if (callback) callback(err);
-                                    else throw err;
-                                } else {
-                                    if (callback) callback();
-                                }
-                            });
-                        }
-                    });
-                }
-            });
-        }
+    return hooks.fire('before_build', options)
+    .then(function() {
+        return require('../cordova').raw.prepare(options);
+    }).then(function() {
+        return require('../cordova').raw.compile(options);
+    }).then(function() {
+        return hooks.fire('after_build', options);
     });
 };

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/cli.js
----------------------------------------------------------------------
diff --git a/src/cli.js b/src/cli.js
index 0e7ea76..4faff80 100755
--- a/src/cli.js
+++ b/src/cli.js
@@ -21,7 +21,7 @@ module.exports = function CLI(inputArgs) {
     var optimist  = require('optimist'),
         cordova   = require('../cordova');
 
-    args = optimist(inputArgs)
+   args = optimist(inputArgs)
         .boolean('d')
         .boolean('verbose')
         .boolean('v')
@@ -84,15 +84,15 @@ module.exports = function CLI(inputArgs) {
                     opts.options.push(option);
                 }
             });
-            cordova[cmd].call(this, opts);
+            cordova.raw[cmd].call(this, opts).done();
         } else if (cmd == 'create' || cmd == 'serve') {
-            cordova[cmd].apply(this, tokens);
+            cordova.raw[cmd].apply(this, tokens).done();
         } else {
             // platform/plugins add/rm [target(s)]
             var invocation = tokens.slice(0,1); // this has the sub-command, i.e. "platform add" or "plugin rm"
             var targets = tokens.slice(1); // this should be an array of targets, be it platforms or plugins
             invocation.push(targets);
-            cordova[cmd].apply(this, invocation);
+            cordova.raw[cmd].apply(this, invocation).done();
         }
     } else {
         throw new Error('Cordova does not know ' + cmd + '; try help for a list of all the available commands.');

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/compile.js
----------------------------------------------------------------------
diff --git a/src/compile.js b/src/compile.js
index e1ffde7..9716224 100644
--- a/src/compile.js
+++ b/src/compile.js
@@ -18,34 +18,33 @@
 */
 var cordova_util      = require('./util'),
     path              = require('path'),
-    fs                = require('fs'),
-    shell             = require('shelljs'),
-    et                = require('elementtree'),
+    child_process     = require('child_process'),
     hooker            = require('./hooker'),
     events            = require('./events'),
-    n                 = require('ncallbacks');
+    Q                 = require('q');
 
-function shell_out_to_build(projectRoot, platform, options,  error_callback, done) {
+// Returns a promise.
+function shell_out_to_build(projectRoot, platform, options) {
     var cmd = '"' + path.join(projectRoot, 'platforms', platform, 'cordova', 'build') + (options.length ? '" ' + options.join(" ") : '"');
     events.emit('log', 'Compiling platform "' + platform + '" with 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 building the ' + platform + ' project. ' + output);
-            if (error_callback) 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 building the ' + platform + ' project. ' + stderr));
         } else {
             events.emit('log', 'Platform "' + platform + '" compiled successfully.');
-            if (done) done();
+            d.resolve();
         }
     });
+    return d.promise;
 }
 
-module.exports = function compile(options, callback) {
+// Returns a promise.
+module.exports = function compile(options) {
     var projectRoot = cordova_util.isCordova(process.cwd());
 
-    if (options instanceof Function && callback === undefined) {
-        callback = options;
+    if (!options) {
         options = {
             verbose: false,
             platforms: [],
@@ -55,36 +54,17 @@ module.exports = function compile(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_compile', options, function(err) {
-        if (err) {
-            if (callback) callback(err);
-            else throw err;
-        } else {
-            var end = n(options.platforms.length, function() {
-                hooks.fire('after_compile', options, function(err) {
-                    if (err) {
-                        if (callback) callback(err);
-                        else throw err;
-                    } else {
-                        if (callback) callback();
-                    }
-                });
-            });
-
-            // Iterate over each added platform
-            options.platforms.forEach(function(platform) {
-                try {
-                    shell_out_to_build(projectRoot, platform, options.options, callback, end);
-                } catch(e) {
-                    if (callback) callback(e);
-                    else throw e;
-                }
-            });
-        }
+    return hooks.fire('before_compile', options)
+    .then(function() {
+        // Iterate over each added platform
+        return Q.all(options.platforms.map(function(platform) {
+            return shell_out_to_build(projectRoot, platform, options.options);
+        }));
+    }).then(function() {
+        return hooks.fire('after_compile', options);
     });
 };

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/config.js
----------------------------------------------------------------------
diff --git a/src/config.js b/src/config.js
index f6c7b88..2fa2557 100644
--- a/src/config.js
+++ b/src/config.js
@@ -20,9 +20,7 @@
 var path          = require('path'),
     fs            = require('fs'),
     url           = require('url'),
-    shell         = require('shelljs'),
-    events        = require('./events'),
-    util          = require('./util');
+    shell         = require('shelljs');
 
 module.exports = function config(project_root, opts) {
     var json = module.exports.read(project_root);

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/src/create.js
----------------------------------------------------------------------
diff --git a/src/create.js b/src/create.js
index da5cb02..6ec73db 100644
--- a/src/create.js
+++ b/src/create.js
@@ -24,6 +24,7 @@ var path          = require('path'),
     events        = require('./events'),
     config        = require('./config'),
     lazy_load     = require('./lazy_load'),
+    Q             = require('q'),
     util          = require('./util');
 
 var DEFAULT_NAME = "HelloCordova",
@@ -35,21 +36,17 @@ var DEFAULT_NAME = "HelloCordova",
  * create(dir, name) - as above, but with specified name
  * create(dir, id, name) - you get the gist
  **/
-module.exports = function create (dir, id, name, callback) {
+// Returns a promise.
+module.exports = function create (dir, id, name) {
     var options = [];
 
     if (arguments.length === 0) {
-        return help();
+        return Q(help());
     }
 
-    // Massage parameters
     var args = Array.prototype.slice.call(arguments, 0);
-    if (typeof args[args.length-1] == 'function') {
-        callback = args.pop();
-    } else if (typeof callback !== 'function') {
-        callback = undefined;
-    }
 
+    // Massage parameters
     if (args.length === 0) {
         dir = process.cwd();
         id = DEFAULT_ID;
@@ -117,6 +114,7 @@ module.exports = function create (dir, id, name, callback) {
 
     var config_json = config.read(dir);
 
+    // Returns a promise.
     var finalize = function(www_lib) {
         // Keep going into child "www" folder if exists in stock app package.
         while (fs.existsSync(path.join(www_lib, 'www'))) {
@@ -134,32 +132,24 @@ module.exports = function create (dir, id, name, callback) {
         var config = new util.config_parser(configPath);
         config.packageName(id);
         config.name(name);
-        if (callback) callback();
+        return Q();
     };
 
     // Check if www assets to use was overridden.
     if (config_json.lib && config_json.lib.www) {
         events.emit('log', 'Using custom www assets ('+config_json.lib.www.id+').');
-        lazy_load.custom(config_json.lib.www.uri, config_json.lib.www.id, 'www', config_json.lib.www.version, function(err) {
-            if (err) {
-                if (callback) callback(err);
-                else throw err;
-            } else {
-                events.emit('log', 'Copying custom www assets into "' + www_dir + '"');
-                finalize(path.join(util.libDirectory, 'www', config_json.lib.www.id, config_json.lib.www.version));
-            }
+        return lazy_load.custom(config_json.lib.www.uri, config_json.lib.www.id, 'www', config_json.lib.www.version)
+        .then(function() {
+            events.emit('log', 'Copying custom www assets into "' + www_dir + '"');
+            return finalize(path.join(util.libDirectory, 'www', config_json.lib.www.id, config_json.lib.www.version));
         });
     } else {
         // Nope, so use stock cordova-hello-world-app one.
         events.emit('log', 'Using stock cordova hello-world application.');
-        lazy_load.cordova('www', function(err) {
-            if (err) {
-                if (callback) callback(err);
-                else throw err;
-            } else {
-                events.emit('log', 'Copying stock Cordova www assets into "' + www_dir + '"');
-                finalize(path.join(util.libDirectory, 'www', 'cordova', platforms.www.version));
-            }
+        return lazy_load.cordova('www')
+        .then(function() {
+            events.emit('log', 'Copying stock Cordova www assets into "' + www_dir + '"');
+            return finalize(path.join(util.libDirectory, 'www', 'cordova', platforms.www.version));
         });
     }
 };


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

Posted by br...@apache.org.
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);
+        });
     });
 };