You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by fi...@apache.org on 2012/08/27 23:32:44 UTC

[7/7] git commit: First cordova-client commit

First cordova-client commit


Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/commit/bbf207f0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/tree/bbf207f0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/diff/bbf207f0

Branch: refs/heads/cordova-client
Commit: bbf207f0a27bc0b3adee3d045210e6ae2a540898
Parents: 68340ce
Author: Anis Kadri <an...@gmail.com>
Authored: Thu Jul 26 11:50:38 2012 -0700
Committer: Anis Kadri <an...@gmail.com>
Committed: Thu Jul 26 11:50:38 2012 -0700

----------------------------------------------------------------------
 README.md                  |  149 +++++++++++++++++++++++++++++++++++----
 bin/cordova                |   18 +++++
 cordova.js                 |   91 ++++++++++++++++++++++++
 doc/help.txt               |   24 ++++++
 package.json               |   37 ++++++++++
 platforms.js               |    1 +
 spec/_platform.spec.js     |  135 +++++++++++++++++++++++++++++++++++
 spec/config_parser.spec.js |   86 ++++++++++++++++++++++
 spec/create.spec.js        |   39 ++++++++++
 src/build.js               |   57 +++++++++++++++
 src/config_parser.js       |   42 +++++++++++
 src/emulate.js             |   27 +++++++
 src/platform.js            |   91 ++++++++++++++++++++++++
 src/plugin.js              |   67 +++++++++++++++++
 src/util.js                |   34 +++++++++
 templates/www/config.xml   |   58 +++++++++++++++
 templates/www/index.html   |   10 +++
 17 files changed, 952 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 2d78d3a..3e6883f 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,143 @@
-# Cordova Laboratory
+# cordova-client
 
-> Caution: Safety Goggles are Recommended!
+> Build, deploy and manage [Cordova](http://cordova.io)-based applications.
 
-## Purpose
+## Supported Platforms
 
-The purpose of this repo is for experimental code. Examples include demo apps,
-native api explorations, or anything really that does not fit in an existing Cordova platform.
+- iOS
+- Android
 
-## Project Organization
+# Requirements
 
-> Everyone works on a branch
+Cordova client requires [nodejs](http://nodejs.org/).
 
-`master` branch should *never* have content.
+For every platform that Cordova supports and you want to use with
+cordova-client, you will need to install the SDK for that platform. See:
 
-Each project should create a separate branch to work on. There are major benefits
-to this practice:
+- [iOS SDK](http://developer.apple.com)
+- [Android SDK](http://developer.android.com)
+- [BlackBerry WebWorks SDK](http://developer.blackberry.com)
 
-- Each project has an isolate git history, which allows for easy migration to
-  a new git repository;
-- Working directory is not polluted with the files of other projects.
-- Projects will not step on each others toes.
+Cordova client has been tested on Windows, Linux and Mas OS X.
+
+# Getting Started
+
+You should (eventually) be able to `npm install cordova-client -g`.
+Until then, after you clone this code, run `npm install` from inside this
+directory. After that you will be able to access the client interface
+via:
+
+    $ ./bin/cordova
+
+## Creating A Cordova-Based Project
+
+    $ cordova create [directory]
+
+Creates a Cordova application. When called with no arguments, `cordova create` will generate a Cordova-based project in the current directory.
+
+A Cordova application built with cordova-client will have the following
+directory structure:
+
+    myApp
+    |-.cordova
+    |- platforms
+    |- plugins
+    `- www
+
+- `.cordova`: contains meta-data related to your application
+- `platforms`: platforms added to your application will have the native
+  application project structures laid out within this directory
+- `plugins`: any added plugins will be extracted into this directory
+- `www`: your main application assets
+
+From here, you have a Cordova-based project whose state you can
+manipulate using the below project-level commands.
+
+## Project-Level Commands
+
+Inside a Cordova-based project, you can use `cordova` with the
+`platform`, `plugin`, `build` and `emulate` sub-commands.
+
+### Managing Platforms
+
+#### Listing All Platforms
+
+    $ cordova platform [ls]
+
+Lists out all platforms that the Cordova-based project is currently
+being built to.
+
+#### Adding A Platform
+
+    $ cordova platform add [platform]
+
+Adds the platform as a build target for the current Cordova-based
+project.
+
+#### Removing A Platform
+
+    $ cordova platform remove [platform]
+
+Removes the platform as a build target from the current Cordova-based
+project.
+
+### Building Your Project
+
+    $ cordova build
+
+You can call `cordova build` with no arguments if you are inside a cordova based project. This will compile your app for all platforms added to your Cordova project.
+
+### Emulating Your Project
+
+    $ cordova emulate
+
+Will launch emulators for all platforms added to your
+Cordova project.
+
+### Managing Plugins
+
+Plugin integration hinges on:
+
+- You having the plugin code locally on your computer
+- The plugin code adheres to the [Cordova Plugin Specification](https://github.com/alunny/cordova-plugin-spec)
+
+#### Listing All Plugins
+
+    $ cordova plugin [ls]
+
+Lists out all plugins added to the current Cordova-based project.
+
+#### Adding A Plugin
+
+    $ cordova plugin add [path-to-plugin]
+
+Adds the platform as a build target for the current Cordova-based
+project.
+
+#### Removing A Plugin
+
+    $ cordova plugin remove [plugin]
+
+**NOT IMPLEMENTED!**
+
+# Examples
+
+## Creating a sample project
+
+    $ cordova create
+
+# Contributing
+
+## Running Tests
+
+    $ npm test
+
+**WARNING**: If you run tests and don't have any sub-directories under
+`./lib`, be prepared to see some failing tests as then this project will
+start cloning any necessary Cordova libraries (which may take a while).
+
+## TO-DO
+
+- `grep` through this project for 'TODO'
+- blackberry support
+- moar tests

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/bin/cordova
----------------------------------------------------------------------
diff --git a/bin/cordova b/bin/cordova
new file mode 100755
index 0000000..439ea35
--- /dev/null
+++ b/bin/cordova
@@ -0,0 +1,18 @@
+#!/usr/bin/env node
+var cordova = require('./../cordova')
+,   cmd     = process.argv[2]
+,   opts    = process.argv.slice(3, process.argv.length)
+
+if (cmd === undefined)  {
+    console.log(cordova.help());
+} else if (cordova.hasOwnProperty(cmd)) {
+    try {
+        var r = cordova[cmd].apply(this, opts);
+        if (r) console.log(r);
+    } catch(e) {
+        console.error(e);
+    }
+}
+else {
+    console.error('Cordova does not know ' + cmd + '; try help for a list of all the available commands.')
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/cordova.js
----------------------------------------------------------------------
diff --git a/cordova.js b/cordova.js
new file mode 100755
index 0000000..e29bd53
--- /dev/null
+++ b/cordova.js
@@ -0,0 +1,91 @@
+var fs            = require('fs')
+,   path          = require('path')
+,   util          = require('util')
+,   exec          = require('child_process').exec
+,   dist          = process.env.CORDOVA_HOME != undefined ? process.env.CORDOVA_HOME : path.join(__dirname, 'lib', 'cordova-1.9.0')
+,   colors        = require('colors')
+,   wrench        = require('wrench')
+,   config_parser = require('./src/config_parser')
+
+
+module.exports = {
+    help: function help () {
+        var raw = fs.readFileSync(path.join(__dirname, 'doc', 'help.txt')).toString('utf8').split("\n");
+        return 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");
+    },
+    docs: function docs () {
+
+        var express = require('express')
+        ,   port    = 2222
+        ,   static  = path.join(dist, 'doc')
+        ,   server  = express.createServer();
+        
+        server.configure(function() {
+            server.use(express.static(static));
+            server.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
+        });
+
+        server.get('/', function(req, res) {
+            return res.render('index.html');
+        });
+
+        console.log("\nServing Cordova/Docs at: ".grey + 'http://localhost:2222'.blue.underline + "\n");
+        console.log('Hit ctrl + c to terminate the process.'.cyan);
+        server.listen(parseInt(port, 10));
+    },
+    create: function create (dir) {
+        if (dir === undefined) {
+            return module.exports.help();
+        }
+
+
+        var mkdirp = wrench.mkdirSyncRecursive,
+            cpr = wrench.copyDirSyncRecursive;
+        if (dir && (dir[0] == '~' || dir[0] == '/')) {
+        } else {
+            dir = dir ? path.join(process.cwd(), dir) : process.cwd();
+        }
+
+        // Check for existing cordova project
+        try {
+            if (fs.lstatSync(path.join(dir, '.cordova')).isDirectory()) {
+                console.error('Cordova project already exists at ' + dir + ', aborting.');
+                return;
+            }
+        } catch(e) { /* no dirs, we're fine */ }
+
+        // Create basic project structure.
+        mkdirp(path.join(dir, '.cordova'));
+        mkdirp(path.join(dir, 'platforms'));
+        mkdirp(path.join(dir, 'plugins'));
+        mkdirp(path.join(dir, 'www'));
+
+        // Copy in base template
+        cpr(path.join(__dirname, 'templates', 'www'), path.join(dir, 'www'));
+    },
+    platform:require('./src/platform'),
+    build:require('./src/build'),
+    emulate:require('./src/emulate'),
+    plugin:require('./src/plugin')
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/doc/help.txt
----------------------------------------------------------------------
diff --git a/doc/help.txt b/doc/help.txt
new file mode 100644
index 0000000..fdc2cba
--- /dev/null
+++ b/doc/help.txt
@@ -0,0 +1,24 @@
+
+Synopsis
+    
+    cordova command [options]
+
+Global Commands
+
+    create [path]...................... creates a cordova project in the specified directory
+
+Project-Level Commands
+
+    platform [add|remove|ls [name]] ... adds or removes a platform, or lists all currently-added platforms
+    plugin [add|remove|ls [path]] ..... adds or removes a plugin (from the specified path), or lists all currently-added plugins
+    build ............................. builds a cordova project
+    emulate ........................... starts emulator for cordova project
+    docs .............................. serves docs at http://localhost:2222
+
+Example usage
+
+    $ cordova create Baz
+    $ cd Baz
+    $ cordova platform add android
+    $ codova build && cordova emulate 
+

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6b0205c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,37 @@
+{
+  "name": "cordova",
+  "version": "0.0.2",
+  "preferGlobal": "true",
+  "description": "Cordova client tool",
+  "main": "cordova",
+  "bin": {
+    "cordova": "./bin/cordova"
+  },
+  "scripts": {
+    "test": "./node_modules/jasmine-node/bin/jasmine-node --color spec"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/imhotep/cordova-client.git"
+  },
+  "keywords": [
+    "cordova",
+    "client",
+    "cli"
+  ],
+  "dependencies": {
+    "colors":">=0.6.0",
+    "wrench":"",
+    "elementtree":"",
+    "pluginstall":"git+https://github.com/filmaj/pluginstall.git"
+  },
+  "devDependencies": {
+    "jasmine-node":">=1.0.0"
+  },
+  "author": "Anis Kadri",
+  "contributors": [
+    {"name": "Brian LeRoux","email": "b@brian.io"},
+    {"name": "Fil Maj", "email": "filmaj@apache.org"}
+  ],
+  "license": "Apache version 2.0"
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/platforms.js
----------------------------------------------------------------------
diff --git a/platforms.js b/platforms.js
new file mode 100644
index 0000000..c6c5601
--- /dev/null
+++ b/platforms.js
@@ -0,0 +1 @@
+module.exports = ['ios','android'];

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/spec/_platform.spec.js
----------------------------------------------------------------------
diff --git a/spec/_platform.spec.js b/spec/_platform.spec.js
new file mode 100644
index 0000000..378d705
--- /dev/null
+++ b/spec/_platform.spec.js
@@ -0,0 +1,135 @@
+// Spy on exec so we can mock out certain CLI calls (and speed up
+// testing)
+var _exec = require('child_process').exec;
+require('child_process').exec = function(cmd, cb){
+    var space = cmd.indexOf(' ');
+    // Just invoke callback for create calls.
+    if (Array.prototype.slice.call(cmd, space-6, space).join('') == 'create') {
+        cb();
+    } else {
+        _exec(cmd, cb);
+    }
+};
+
+var cordova = require('../cordova'),
+    wrench = require('wrench'),
+    mkdirp = wrench.mkdirSyncRecursive,
+    path = require('path'),
+    rmrf = wrench.rmdirSyncRecursive,
+    fs = require('fs'),
+    tempDir = path.join(__dirname, '..', 'temp');
+
+
+describe('platform command', function() {
+    beforeEach(function() {
+        // Make a temp directory
+        try { rmrf(tempDir); } catch(e) {}
+        mkdirp(tempDir);
+    });
+
+    it('should run inside a Cordova-based project', function() {
+        var cwd = process.cwd();
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        cordova.create(tempDir);
+
+        process.chdir(tempDir);
+
+        expect(function() {
+            cordova.platform();
+        }).not.toThrow();
+    });
+    it('should not run outside of a Cordova-based project', function() {
+        var cwd = process.cwd();
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        process.chdir(tempDir);
+
+        expect(function() {
+            cordova.platform();
+        }).toThrow();
+    });
+
+    describe('ls', function() {
+        var cwd = process.cwd();
+
+        beforeEach(function() {
+            cordova.create(tempDir);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+
+        it('should list out no platforms for a fresh project', function() {
+            process.chdir(tempDir);
+
+            expect(cordova.platform('ls')).toEqual('No platforms added. Use `cordova platform add <platform>`.');
+        });
+
+        it('should list out added platforms in a project', function() {
+            var cb = jasmine.createSpy().andCallFake(function() {
+                expect(cordova.platform('ls')).toEqual('android');
+            });
+
+            process.chdir(tempDir);
+            runs(function() {
+                cordova.platform('add', 'android', cb);
+            });
+            waitsFor(function() { return cb.wasCalled; }, "create callback", 17500);
+        });
+    });
+
+    describe('add', function() {
+        var cwd = process.cwd();
+
+        beforeEach(function() {
+            cordova.create(tempDir);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+
+        it('should add a supported platform', function() {
+            var cb = jasmine.createSpy().andCallFake(function() {
+                expect(cordova.platform('ls')).toEqual('android');
+            });
+
+            process.chdir(tempDir);
+            runs(function() {
+                cordova.platform('add', 'android', cb);
+            });
+            waitsFor(function() { return cb.wasCalled; }, "create callback", 17500);
+        });
+    });
+
+    describe('remove', function() {
+        var cwd = process.cwd();
+
+        beforeEach(function() {
+            cordova.create(tempDir);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+
+        it('should remove a supported and added platform', function() {
+            var cb = jasmine.createSpy().andCallFake(function() {
+                cordova.platform('remove', 'android');
+                expect(cordova.platform('ls')).toEqual('No platforms added. Use `cordova platform add <platform>`.');
+            });
+
+            process.chdir(tempDir);
+            runs(function() {
+                cordova.platform('add', 'android', cb);
+            });
+            waitsFor(function() { return cb.wasCalled; }, "create callback", 17500);
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/spec/config_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/config_parser.spec.js b/spec/config_parser.spec.js
new file mode 100644
index 0000000..ba39e0a
--- /dev/null
+++ b/spec/config_parser.spec.js
@@ -0,0 +1,86 @@
+var cordova = require('../cordova'),
+    wrench = require('wrench'),
+    mkdirp = wrench.mkdirSyncRecursive,
+    path = require('path'),
+    rmrf = wrench.rmdirSyncRecursive,
+    fs = require('fs'),
+    config_parser = require('../src/config_parser'),
+    tempDir = path.join(__dirname, '..', 'temp'),
+    et = require('elementtree'),
+    xml = path.join(tempDir, 'www', 'config.xml');
+
+describe('config parser', function () {
+    beforeEach(function() {
+        // Make a temp directory
+        try { rmrf(tempDir); } catch(e) {}
+        mkdirp(tempDir);
+        cordova.create(tempDir);
+    });
+
+    it('should create an instance based on an xml file', function() {
+        var cfg;
+        expect(function () {
+            cfg = new config_parser(xml);
+        }).not.toThrow();
+        expect(cfg).toBeDefined();
+        expect(cfg.doc).toBeDefined();
+    });
+
+    describe('platforms', function() {
+        describe('ls command', function() {
+            it('should return an empty array if there are no platforms specified in the document', function() {
+                var cfg = new config_parser(xml);
+
+                expect(cfg.ls_platforms().length).toBe(0);
+            });
+            it('should return a populated array if there are platforms specified in the document', function() {
+                var doc = new et.ElementTree(et.XML(fs.readFileSync(xml, 'utf-8')));
+                var p = new et.Element('platform');
+                p.attrib['name'] = 'android';
+                doc.find('platforms').append(p);
+                fs.writeFileSync(xml, doc.write(), 'utf-8');
+
+                var cfg = new config_parser(xml);
+                expect(cfg.ls_platforms().length).toBe(1);
+                expect(cfg.ls_platforms()[0]).toEqual('android');
+            });
+        });
+
+        describe('add command', function() {
+            it('should add a platform element to the platforms element', function() {
+                var cfg = new config_parser(xml);
+                cfg.add_platform('android');
+                
+                var doc = new et.ElementTree(et.XML(fs.readFileSync(xml, 'utf-8')));
+                expect(doc.find('platforms').getchildren()[0].attrib['name']).toEqual('android');
+            });
+            it('should ignore existing platforms', function() {
+                var cfg = new config_parser(xml);
+                cfg.add_platform('android');
+                cfg.add_platform('android');
+                
+                var doc = new et.ElementTree(et.XML(fs.readFileSync(xml, 'utf-8')));
+                expect(doc.find('platforms').getchildren().length).toEqual(1);
+            });
+            it('should ignore garbage platforms', function() {
+                var cfg = new config_parser(xml);
+                cfg.add_platform('bat country');
+                
+                var doc = new et.ElementTree(et.XML(fs.readFileSync(xml, 'utf-8')));
+                expect(doc.find('platforms').getchildren().length).toEqual(0);
+            });
+        });
+
+        describe('remove command', function() {
+            it('should remove a platform element from the platforms element', function() {
+                var cfg = new config_parser(xml);
+                cfg.add_platform('ios');
+
+                cfg.remove_platform('ios');
+
+                var doc = new et.ElementTree(et.XML(fs.readFileSync(xml, 'utf-8')));
+                expect(doc.find('platforms').getchildren().length).toEqual(0);
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/spec/create.spec.js
----------------------------------------------------------------------
diff --git a/spec/create.spec.js b/spec/create.spec.js
new file mode 100644
index 0000000..bdd2ceb
--- /dev/null
+++ b/spec/create.spec.js
@@ -0,0 +1,39 @@
+var cordova = require('../cordova'),
+    wrench = require('wrench'),
+    mkdirp = wrench.mkdirSyncRecursive,
+    path = require('path'),
+    rmrf = wrench.rmdirSyncRecursive,
+    fs = require('fs'),
+    tempDir = path.join(__dirname, '..', 'temp');
+
+describe('create command', function () {
+    beforeEach(function() {
+        // Make a temp directory
+        try { rmrf(tempDir); } catch(e) {}
+        mkdirp(tempDir);
+    });
+
+    it('should print out help txt if no directory is provided', function() {
+        var cwd = process.cwd();
+        this.after(function() {
+            process.chdir(cwd);
+        });
+        process.chdir(tempDir);
+        expect(cordova.create()).toMatch(/synopsis/i);
+    });
+    it('should create a cordova project in the specified directory if parameter is provided', function() {
+        cordova.create(tempDir);
+        expect(fs.lstatSync(path.join(tempDir, '.cordova')).isDirectory()).toBe(true);
+    });
+    it('should warn if the directory is already a cordova project', function() {
+        spyOn(console, 'error');
+
+        var cb = jasmine.createSpy();
+
+        mkdirp(path.join(tempDir, '.cordova'));
+
+        cordova.create(tempDir);
+
+        expect(console.error).toHaveBeenCalled();
+    });
+});

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/src/build.js
----------------------------------------------------------------------
diff --git a/src/build.js b/src/build.js
new file mode 100644
index 0000000..fc9453e
--- /dev/null
+++ b/src/build.js
@@ -0,0 +1,57 @@
+var cordova_util = require('./util'),
+    path = require('path'),
+    exec = require('child_process').exec,
+    wrench = require('wrench'),
+    rmrf = wrench.rmdirSyncRecursive,
+    cpr = wrench.copyDirSyncRecursive,
+    config_parser = require('./config_parser'),
+    fs = require('fs'),
+    util = require('util');
+
+module.exports = function build () {
+    var projectRoot = cordova_util.isCordova(process.cwd());
+
+    if (!projectRoot) {
+        throw 'Current working directory is not a Cordova-based project.';
+    }
+
+    var xml = path.join(projectRoot, 'www', 'config.xml');
+    var assets = path.join(projectRoot, 'www');
+    var cfg = new config_parser(xml);
+    var platforms = cfg.ls_platforms();
+
+    // Iterate over each added platform 
+    platforms.map(function(platform) {
+        // Copy in latest www assets.
+        var assetsPath;
+        switch (platform) {
+            // First clean out the existing www.
+            case 'android':
+                assetsPath = path.join(projectRoot, 'platforms', 'android', 'assets', 'www');
+                break;
+            case 'ios':
+                assetsPath = path.join(projectRoot, 'platforms', 'ios', 'www');
+                break;
+        } 
+        rmrf(assetsPath);
+        cpr(assets, assetsPath);
+        // Copy in the appropriate JS
+        var js;
+        var jsPath = path.join(assetsPath, 'cordova.js');
+        switch (platform) {
+            case 'android':
+                js = path.join(__dirname, '..', 'lib', 'android', 'framework', 'assets', 'js', 'cordova.android.js');
+                break;
+            case 'ios':
+                js = path.join(__dirname, '..', 'lib', 'ios', 'CordovaLib', 'javascript', 'cordova.ios.js');
+                break;
+        }
+        fs.writeFileSync(jsPath, fs.readFileSync(js));
+
+        // shell out to debug command
+        var cmd = path.join(projectRoot, 'platforms', platform, 'cordova', 'debug > /dev/null');
+        exec(cmd, function(err, stderr, stdout) {
+            if (err) throw 'An error occurred while building the ' + platform + ' project. ' + err;
+        });
+    });
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/src/config_parser.js
----------------------------------------------------------------------
diff --git a/src/config_parser.js b/src/config_parser.js
new file mode 100644
index 0000000..979e71f
--- /dev/null
+++ b/src/config_parser.js
@@ -0,0 +1,42 @@
+var et = require('elementtree'),
+    platforms = require('./../platforms'),
+    fs = require('fs');
+
+function config_parser(xmlPath) {
+    this.path = xmlPath;
+    this.doc = new et.ElementTree(et.XML(fs.readFileSync(xmlPath, 'utf-8')));
+}
+
+config_parser.prototype = {
+    ls_platforms:function() {
+        return this.doc.find('platforms').getchildren().map(function(p) {
+            return p.attrib['name'];
+        });
+    },
+    add_platform:function(platform) {
+        if ((platforms.indexOf(platform) == -1) || this.doc.find('platforms/platform[@name="' + platform + '"]')) return;
+        else {
+            var p = new et.Element('platform');
+            p.attrib['name'] = platform;
+            this.doc.find('platforms').append(p);
+            fs.writeFileSync(this.path, this.doc.write(), 'utf-8');
+        }
+    },
+    remove_platform:function(platform) {
+        if ((platforms.indexOf(platform) == -1) || !(this.doc.find('platforms/platform[@name="' + platform + '"]'))) return;
+        else {
+            var psEl = this.doc.find('platforms');
+            var pEl = psEl.find('platform[@name="' + platform + '"]');
+            psEl.remove(null, pEl);
+            fs.writeFileSync(this.path, this.doc.write(), 'utf-8');
+        }
+    },
+    packageName:function() {
+        return this.doc.getroot().attrib.id;
+    },
+    name:function() {
+        return this.doc.find('name').text;
+    }
+};
+
+module.exports = config_parser;

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/src/emulate.js
----------------------------------------------------------------------
diff --git a/src/emulate.js b/src/emulate.js
new file mode 100644
index 0000000..a7825c1
--- /dev/null
+++ b/src/emulate.js
@@ -0,0 +1,27 @@
+var cordova_util = require('./util'),
+    path = require('path'),
+    exec = require('child_process').exec,
+    config_parser = require('./config_parser'),
+    fs = require('fs'),
+    util = require('util');
+
+module.exports = function emulate () {
+    var projectRoot = cordova_util.isCordova(process.cwd());
+
+    if (!projectRoot) {
+        throw 'Current working directory is not a Cordova-based project.';
+    }
+
+    var xml = path.join(projectRoot, 'www', 'config.xml');
+    var cfg = new config_parser(xml);
+    var platforms = cfg.ls_platforms();
+
+    // Iterate over each added platform and shell out to debug command
+    platforms.map(function(platform) {
+        var cmd = path.join(projectRoot, 'platforms', platform, 'cordova', 'emulate');
+        exec(cmd, function(err, stderr, stdout) {
+            if (err) throw 'An error occurred while emulating/deploying the ' + platform + ' project.' + err;
+        });
+    });
+};
+

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/src/platform.js
----------------------------------------------------------------------
diff --git a/src/platform.js b/src/platform.js
new file mode 100644
index 0000000..16da06b
--- /dev/null
+++ b/src/platform.js
@@ -0,0 +1,91 @@
+var config_parser = require('./config_parser'),
+    cordova_util = require('./util'),
+    util = require('util'),
+    fs = require('fs'),
+    wrench = require('wrench'),
+    rmrf = wrench.rmdirSyncRecursive,
+    exec = require('child_process').exec,
+    path = require('path');
+
+var repos = {
+    ios:'https://git-wip-us.apache.org/repos/asf/incubator-cordova-ios.git',
+    android:'https://git-wip-us.apache.org/repos/asf/incubator-cordova-android.git'
+};
+
+// Creates a platform app using the ./bin/create scripts that exist in
+// each repo.
+// TODO: eventually refactor to allow multiple versions to be created.
+// Currently only checks out HEAD.
+function create(target, dir, cfg, callback) {
+    // Check if it already exists.
+    try {
+        fs.lstatSync(dir);
+    } catch(e) {
+        // Doesn't exist, continue.
+        var bin = path.join(__dirname, '..', 'lib', target, 'bin', 'create');
+        var pkg = cfg.packageName();
+        var name = cfg.name().replace(/\W/g,'_');
+        var cmd = util.format('%s "%s" "%s" "%s"', bin, dir, pkg, name);
+        exec(cmd, function(err, stderr, stdout) {
+            if (err) {
+                cfg.remove_platform(target);
+                throw 'An error occured during creation of ' + target + ' sub-project. ' + err;
+            } else if (callback) callback();
+        });
+    }
+}
+
+module.exports = function platform(command, target, callback) {
+    var projectRoot = cordova_util.isCordova(process.cwd());
+
+    if (!projectRoot) {
+        throw 'Current working directory is not a Cordova-based project.';
+    }
+    if (arguments.length === 0) command = 'ls';
+
+    var xml = path.join(projectRoot, 'www', 'config.xml');
+    var cfg = new config_parser(xml);
+
+    switch(command) {
+        case 'ls':
+            var platforms = cfg.ls_platforms();
+            if (platforms.length) {
+                return platforms.join('\n');
+            } else return 'No platforms added. Use `cordova platform add <platform>`.';
+            break;
+        case 'add':
+            // Add the platform to the config.xml
+            cfg.add_platform(target);
+
+            var output = path.join(projectRoot, 'platforms', target);
+
+            // Do we have the cordova library for this platform?
+            if (!cordova_util.havePlatformLib(target)) {
+                // Shell out to git.
+                var outPath = path.join(__dirname, '..', 'lib', target);
+                var cmd = util.format('git clone %s %s', repos[target], outPath);
+                console.log('Cloning ' + repos[target] + ', this may take a while...');
+                exec(cmd, function(err, stderr, stdout) {
+                    if (err) {
+                        cfg.remove_platform(target);
+                        throw 'An error occured during git-clone of ' + repos[target] + '. ' + err;
+                    }
+                    create(target, output, cfg, callback);
+                });
+            } else {
+                create(target, output, cfg, callback);
+            }
+            break;
+        case 'remove':
+            // Remove the platform from the config.xml
+            cfg.remove_platform(target);
+
+            // Remove the Cordova project for the platform.
+            try {
+                rmrf(path.join(projectRoot, 'platforms', target));
+            } catch(e) {}
+            break;
+        default:
+            throw 'Unrecognized command "' + command + '". Use either `add`, `remove`, or `ls`.';
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/src/plugin.js
----------------------------------------------------------------------
diff --git a/src/plugin.js b/src/plugin.js
new file mode 100644
index 0000000..f5acc4b
--- /dev/null
+++ b/src/plugin.js
@@ -0,0 +1,67 @@
+var cordova_util = require('./util'),
+    util = require('util'),
+    wrench = require('wrench'),
+    fs = require('fs'),
+    path = require('path'),
+    config_parser = require('./config_parser'),
+    exec = require('child_process').exec,
+    ls = fs.readdirSync;
+
+module.exports = function plugin(command, target) {
+    var projectRoot = cordova_util.isCordova(process.cwd());
+
+    if (!projectRoot) {
+        throw 'Current working directory is not a Cordova-based project.';
+    }
+    if (arguments.length === 0) command = 'ls';
+
+    // Grab config info for the project
+    var xml = path.join(projectRoot, 'www', 'config.xml');
+    var cfg = new config_parser(xml);
+    var platforms = cfg.ls_platforms();
+
+    // Massage plugin name / path
+    var pluginPath = path.join(projectRoot, 'plugins');
+    var plugins = ls(pluginPath);
+    var targetName = target.substr(target.lastIndexOf('/') + 1);
+    if (targetName[targetName.length-1] == '/') targetName = targetName.substr(0, targetName.length-1);
+
+    switch(command) {
+        case 'ls':
+            if (plugins.length) {
+                return plugins.join('\n');
+            } else return 'No plugins added. Use `cordova plugin add <plugin>.';
+            break;
+        case 'add':
+            // Check if we already have the plugin.
+            if (plugins.indexOf(targetName) > -1) {
+                throw 'Plugin "' + targetName + '" already added to project.';
+            }
+            
+            // Check if the plugin has a plugin.xml in the root of the
+            // specified dir.
+            var pluginContents = ls(target);
+            if (pluginContents.indexOf('plugin.xml') == -1) {
+                throw 'Plugin "' + targetName + '" does not have a plugin.xml in the root. Plugin must support the Cordova Plugin Specification: https://github.com/alunny/cordova-plugin-spec';
+            }
+
+            // Iterate over all platforms in the project and install the
+            // plugin.
+            var cli = path.join(__dirname, '..', 'node_modules', 'pluginstall', 'cli.js');
+            platforms.map(function(platform) {
+                var cmd = util.format('%s %s "%s" "%s"', cli, platform, path.join(projectRoot, 'platforms', platform), target);
+                exec(cmd, function(err, stderr, stdout) {
+                    if (err) {
+                        console.error(stderr);
+                        throw 'An error occured during plugin installation. ' + err;
+                    }
+                });
+            });
+
+            break;
+        case 'remove':
+            throw 'Plugin removal not supported yet! sadface';
+        default:
+            throw 'Unrecognized command "' + command + '". Use either `add`, `remove`, or `ls`.';
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/src/util.js
----------------------------------------------------------------------
diff --git a/src/util.js b/src/util.js
new file mode 100644
index 0000000..b4c7b8f
--- /dev/null
+++ b/src/util.js
@@ -0,0 +1,34 @@
+var fs = require('fs'),
+    path = require('path');
+
+module.exports = {
+    // Runs up the directory chain looking for a .cordova directory.
+    // IF it is found we are in a Cordova project.
+    // If not.. we're not.
+    isCordova: function isCordova(dir) {
+        if (dir) {
+            var contents = fs.readdirSync(dir);
+            if (contents && contents.length && (contents.indexOf('.cordova') > -1)) {
+                return dir;
+            } else {
+                var parent = path.join(dir, '..');
+                if (parent && parent.length > 1) {
+                    return isCordova(parent);
+                } else return false;
+            }
+        } else return false;
+    },
+    // Determines whether the library has a copy of the specified
+    // Cordova implementation
+    havePlatformLib: function havePlatformLib(platform) {
+        var dir = path.join(__dirname, '..', 'lib', platform);
+        try {
+            fs.lstatSync(dir);
+            // Have it!
+            return true;
+        } catch(e) {
+            // Don't have it.
+            return false;
+        }
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/templates/www/config.xml
----------------------------------------------------------------------
diff --git a/templates/www/config.xml b/templates/www/config.xml
new file mode 100644
index 0000000..400c377
--- /dev/null
+++ b/templates/www/config.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets"
+        xmlns:rim="http://www.blackberry.com/ns/widgets"
+        id="org.apache.cordova.sampleapp"
+        version="1.0.0">
+
+  <name>Cordova Sample Application</name>
+
+  <description>
+    Mind-blowing Sample Application
+  </description>
+
+  <author>
+    Anonymous
+  </author>
+
+  <platforms>
+  </platforms>
+
+  <!-- Cordova API -->
+  <feature id="org.apache.cordova" required="true" version="1.0.0" />
+  <!-- BlackBerry-specific -->
+  <feature id="blackberry.system" required="true" version="1.0.0.0" />
+  <feature id="blackberry.find" required="true" version="1.0.0.0" />
+  <feature id="blackberry.identity" required="true" version="1.0.0.0" />
+  <feature id="blackberry.pim.Address" required="true" version="1.0.0.0" />
+  <feature id="blackberry.pim.Contact" required="true" version="1.0.0.0" />
+  <feature id="blackberry.io.file" required="true" version="1.0.0.0" />
+  <feature id="blackberry.utils" required="true" version="1.0.0.0" />
+  <feature id="blackberry.io.dir" required="true" version="1.0.0.0" />
+  <feature id="blackberry.app" required="true" version="1.0.0.0" />
+  <feature id="blackberry.app.event" required="true" version="1.0.0.0" />
+  <feature id="blackberry.system.event" required="true" version="1.0.0.0"/>
+  <feature id="blackberry.widgetcache" required="true" version="1.0.0.0"/>
+  <feature id="blackberry.media.camera" />
+  <feature id="blackberry.ui.dialog" />
+  <feature id="blackberry.media.microphone" required="true" version="1.0.0.0"/>
+  
+  <!-- Cordova API -->
+  <access subdomains="true" uri="file:///store/home" />
+  <access subdomains="true" uri="file:///SDCard" />
+  <!-- Expose access to all URIs, including the file and http protocols -->
+  <access subdomains="true" uri="*" />
+
+  <!-- potential icon stuff
+  <icon src="blurry.png" /> -->
+
+  <!-- Starting Page -->
+  <content src="index.html" />
+
+  <rim:permissions>
+    <rim:permit>use_camera</rim:permit>
+    <rim:permit>read_device_identifying_information</rim:permit>
+    <rim:permit>access_shared</rim:permit>
+    <rim:permit>read_geolocation</rim:permit>
+    <rim:permit>record_audio</rim:permit> 
+  </rim:permissions>
+</widget>

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/bbf207f0/templates/www/index.html
----------------------------------------------------------------------
diff --git a/templates/www/index.html b/templates/www/index.html
new file mode 100644
index 0000000..51661b3
--- /dev/null
+++ b/templates/www/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Sample Cordova Project</title>
+    <script src="cordova.js" type="text/javascript"></script>
+  </head>
+  <body>
+    <h1>Hello Cordova</h1>
+  </body>
+</html>