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

[1/3] git commit: update with latest refactor

Updated Branches:
  refs/heads/cordova-client bbf207f0a -> ec3e6d895


update with latest refactor


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/ec3e6d89
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/tree/ec3e6d89
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/diff/ec3e6d89

Branch: refs/heads/cordova-client
Commit: ec3e6d89541b8ae81e347c0ba619c7ece4450352
Parents: bbf207f
Author: Anis Kadri <an...@gmail.com>
Authored: Thu Jul 26 14:51:30 2012 -0700
Committer: Anis Kadri <an...@gmail.com>
Committed: Thu Jul 26 14:51:30 2012 -0700

----------------------------------------------------------------------
 README.md                                          |   11 +-
 cordova.js                                         |   96 +-
 package.json                                       |    8 +-
 spec/_platform.spec.js                             |  135 -
 spec/config_parser.spec.js                         |   44 +-
 spec/create.spec.js                                |   32 +-
 spec/fixtures/plugins/test/plugin.xml              |   11 +
 spec/helper.js                                     |   12 +
 spec/platform.spec.js                              |  121 +
 spec/plugin.spec.js                                |   69 +
 src/build.js                                       |   45 +-
 src/config_parser.js                               |   25 +-
 src/create.js                                      |   57 +
 src/docs.js                                        |   21 +
 src/help.js                                        |   29 +
 src/platform.js                                    |    7 +
 src/plugin.js                                      |   48 +-
 templates/www/config.xml                           |  104 +-
 templates/www/css/index.css                        |  100 +
 templates/www/img/cordova.png                      |  Bin 0 -> 19932 bytes
 templates/www/index.html                           |   28 +-
 templates/www/js/index.js                          |   20 +
 templates/www/res/icon/cordova_128.png             |  Bin 0 -> 11401 bytes
 templates/www/res/icon/cordova_16.png              |  Bin 0 -> 1699 bytes
 templates/www/res/icon/cordova_24.png              |  Bin 0 -> 2215 bytes
 templates/www/res/icon/cordova_256.png             |  Bin 0 -> 27408 bytes
 templates/www/res/icon/cordova_32.png              |  Bin 0 -> 2843 bytes
 templates/www/res/icon/cordova_48.png              |  Bin 0 -> 4111 bytes
 templates/www/res/icon/cordova_512.png             |  Bin 0 -> 39830 bytes
 templates/www/res/icon/cordova_64.png              |  Bin 0 -> 5463 bytes
 templates/www/res/icon/cordova_android_36.png      |  Bin 0 -> 3096 bytes
 templates/www/res/icon/cordova_android_48.png      |  Bin 0 -> 4090 bytes
 templates/www/res/icon/cordova_android_72.png      |  Bin 0 -> 6080 bytes
 templates/www/res/icon/cordova_android_96.png      |  Bin 0 -> 7685 bytes
 templates/www/res/icon/cordova_bb_80.png           |  Bin 0 -> 7287 bytes
 templates/www/res/icon/cordova_ios_114.png         |  Bin 0 -> 7869 bytes
 templates/www/res/icon/cordova_ios_144.png         |  Bin 0 -> 11706 bytes
 templates/www/res/icon/cordova_ios_57.png          |  Bin 0 -> 3908 bytes
 templates/www/res/icon/cordova_ios_72.png          |  Bin 0 -> 4944 bytes
 .../www/res/screen/android_hdpi_landscape.png      |  Bin 0 -> 218302 bytes
 templates/www/res/screen/android_hdpi_portrait.png |  Bin 0 -> 222148 bytes
 .../www/res/screen/android_ldpi_landscape.png      |  Bin 0 -> 42616 bytes
 templates/www/res/screen/android_ldpi_portrait.png |  Bin 0 -> 42034 bytes
 .../www/res/screen/android_mdpi_landscape.png      |  Bin 0 -> 92347 bytes
 templates/www/res/screen/android_mdpi_portrait.png |  Bin 0 -> 90555 bytes
 .../www/res/screen/android_xhdpi_landscape.png     |  Bin 0 -> 489604 bytes
 .../www/res/screen/android_xhdpi_portrait.png      |  Bin 0 -> 504508 bytes
 .../www/res/screen/blackberry_transparent_300.png  |  Bin 0 -> 15823 bytes
 .../www/res/screen/blackberry_transparent_400.png  |  Bin 0 -> 11001 bytes
 templates/www/res/screen/ipad_landscape.png        |  Bin 0 -> 407370 bytes
 templates/www/res/screen/ipad_portrait.png         |  Bin 0 -> 422441 bytes
 templates/www/res/screen/ipad_retina_landscape.png |  Bin 0 -> 1534088 bytes
 templates/www/res/screen/ipad_retina_portrait.png  |  Bin 0 -> 1610434 bytes
 templates/www/res/screen/iphone_landscape.png      |  Bin 0 -> 92301 bytes
 templates/www/res/screen/iphone_portrait.png       |  Bin 0 -> 93897 bytes
 .../www/res/screen/iphone_retina_landscape.png     |  Bin 0 -> 339639 bytes
 .../www/res/screen/iphone_retina_portrait.png      |  Bin 0 -> 350593 bytes
 .../www/res/screen/windows_phone_portrait.jpg      |  Bin 0 -> 11483 bytes
 templates/www/spec.html                            |   50 +
 templates/www/spec/helper.js                       |   11 +
 templates/www/spec/index.js                        |   49 +
 templates/www/spec/lib/jasmine-1.2.0/MIT.LICENSE   |   20 +
 .../www/spec/lib/jasmine-1.2.0/jasmine-html.js     |  616 ++++
 templates/www/spec/lib/jasmine-1.2.0/jasmine.css   |   81 +
 templates/www/spec/lib/jasmine-1.2.0/jasmine.js    | 2529 +++++++++++++++
 65 files changed, 4042 insertions(+), 337 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 3e6883f..667c4cd 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Cordova client has been tested on Windows, Linux and Mas OS X.
 
 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
+directory (you may want to run that with `sudo`). After that you will be able to access the client interface
 via:
 
     $ ./bin/cordova
@@ -32,8 +32,12 @@ via:
 ## Creating A Cordova-Based Project
 
     $ cordova create [directory]
+    $ cordova create [directory name]
+    $ cordova create [directory id name]
 
-Creates a Cordova application. When called with no arguments, `cordova create` will generate a Cordova-based project in the current directory.
+Creates a Cordova application. You can optionally specify just a name
+for your application, or both an id (package name or reverse-domain
+style id) and a name.
 
 A Cordova application built with cordova-client will have the following
 directory structure:
@@ -124,7 +128,7 @@ project.
 
 ## Creating a sample project
 
-    $ cordova create
+    $ cordova create ~/src/myNewApp
 
 # Contributing
 
@@ -138,6 +142,7 @@ start cloning any necessary Cordova libraries (which may take a while).
 
 ## TO-DO
 
+- fix pluginstall for ios 2.0
 - `grep` through this project for 'TODO'
 - blackberry support
 - moar tests

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/cordova.js
----------------------------------------------------------------------
diff --git a/cordova.js b/cordova.js
index e29bd53..e3149cc 100755
--- a/cordova.js
+++ b/cordova.js
@@ -1,91 +1,9 @@
-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')
+    help:     require('./src/help'),
+    docs:     require('./src/docs'),
+    create:   require('./src/create'),
+    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/ec3e6d89/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 6b0205c..6a376bc 100644
--- a/package.json
+++ b/package.json
@@ -21,9 +21,11 @@
   ],
   "dependencies": {
     "colors":">=0.6.0",
-    "wrench":"",
-    "elementtree":"",
-    "pluginstall":"git+https://github.com/filmaj/pluginstall.git"
+    "wrench":"1.3.9",
+    "elementtree":"0.1.1",
+    "pluginstall":"git+https://github.com/filmaj/pluginstall.git",
+    "ncallbacks":"1.0.0",
+    "express":"3.0"
   },
   "devDependencies": {
     "jasmine-node":">=1.0.0"

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/spec/_platform.spec.js
----------------------------------------------------------------------
diff --git a/spec/_platform.spec.js b/spec/_platform.spec.js
deleted file mode 100644
index 378d705..0000000
--- a/spec/_platform.spec.js
+++ /dev/null
@@ -1,135 +0,0 @@
-// 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/ec3e6d89/spec/build.spec.js
----------------------------------------------------------------------
diff --git a/spec/build.spec.js b/spec/build.spec.js
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/spec/config_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/config_parser.spec.js b/spec/config_parser.spec.js
index ba39e0a..e26d170 100644
--- a/spec/config_parser.spec.js
+++ b/spec/config_parser.spec.js
@@ -26,6 +26,46 @@ describe('config parser', function () {
         expect(cfg.doc).toBeDefined();
     });
 
+    describe('package name / id', function() {
+        var cfg;
+
+        beforeEach(function() {
+            cfg = new config_parser(xml);
+        });
+
+        it('should get the packagename', function() {
+            expect(cfg.packageName()).toEqual('io.cordova.hello-cordova');
+        });
+        it('should allow setting the packagename', function() {
+            cfg.packageName('this.is.bat.country');
+            expect(cfg.packageName()).toEqual('this.is.bat.country');
+        });
+        it('should write to disk after setting the packagename', function() {
+            cfg.packageName('this.is.bat.country');
+            expect(fs.readFileSync(xml, 'utf-8')).toMatch(/id="this\.is\.bat\.country"/);
+        });
+    });
+
+    describe('app name', function() {
+        var cfg;
+
+        beforeEach(function() {
+            cfg = new config_parser(xml);
+        });
+
+        it('should get the app name', function() {
+            expect(cfg.packageName()).toEqual('io.cordova.hello-cordova');
+        });
+        it('should allow setting the app name', function() {
+            cfg.name('this.is.bat.country');
+            expect(cfg.name()).toEqual('this.is.bat.country');
+        });
+        it('should write to disk after setting the name', function() {
+            cfg.name('one toke over the line');
+            expect(fs.readFileSync(xml, 'utf-8')).toMatch(/<name>one toke over the line<\/name>/);
+        });
+    });
+
     describe('platforms', function() {
         describe('ls command', function() {
             it('should return an empty array if there are no platforms specified in the document', function() {
@@ -36,7 +76,7 @@ describe('config parser', function () {
             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';
+                p.attrib.name = 'android';
                 doc.find('platforms').append(p);
                 fs.writeFileSync(xml, doc.write(), 'utf-8');
 
@@ -52,7 +92,7 @@ describe('config parser', function () {
                 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');
+                expect(doc.find('platforms').getchildren()[0].attrib.name).toEqual('android');
             });
             it('should ignore existing platforms', function() {
                 var cfg = new config_parser(xml);

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/spec/create.spec.js
----------------------------------------------------------------------
diff --git a/spec/create.spec.js b/spec/create.spec.js
index bdd2ceb..cbe8951 100644
--- a/spec/create.spec.js
+++ b/spec/create.spec.js
@@ -23,17 +23,35 @@ describe('create command', function () {
     });
     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);
+        expect(fs.lstatSync(path.join(tempDir, '.cordova')).isFile()).toBe(true);
     });
-    it('should warn if the directory is already a cordova project', function() {
-        spyOn(console, 'error');
+    it('should throw if the directory is already a cordova project', function() {
+        mkdirp(path.join(tempDir, '.cordova'));
+        
+        expect(function() {
+            cordova.create(tempDir);
+        }).toThrow();
+    });
+    it('should create a cordova project in the specified dir with specified name if provided', function() {
+        cordova.create(tempDir, "balls");
 
-        var cb = jasmine.createSpy();
+        expect(fs.lstatSync(path.join(tempDir, '.cordova')).isFile()).toBe(true);
 
-        mkdirp(path.join(tempDir, '.cordova'));
+        expect(fs.readFileSync(path.join(tempDir, 'www', 'config.xml')).toString('utf8')).toMatch(/<name>balls<\/name>/);
 
-        cordova.create(tempDir);
+        expect(JSON.parse(fs.readFileSync(path.join(tempDir, '.cordova')).toString('utf8')).name).toEqual("balls");
+    });
+    it('should create a cordova project in the specified dir with specified name and id if provided', function() {
+        cordova.create(tempDir, "birdy.nam.nam", "numnum");
+
+        expect(fs.lstatSync(path.join(tempDir, '.cordova')).isFile()).toBe(true);
+
+        var config = fs.readFileSync(path.join(tempDir, 'www', 'config.xml')).toString('utf8');
+        expect(config).toMatch(/<name>numnum<\/name>/);
+        expect(config).toMatch(/id="birdy\.nam\.nam"/);
 
-        expect(console.error).toHaveBeenCalled();
+        var metadata = JSON.parse(fs.readFileSync(path.join(tempDir, '.cordova')).toString('utf8'));
+        expect(metadata.name).toEqual("numnum");
+        expect(metadata.id).toEqual("birdy.nam.nam");
     });
 });

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/spec/emulate.spec.js
----------------------------------------------------------------------
diff --git a/spec/emulate.spec.js b/spec/emulate.spec.js
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/spec/fixtures/plugins/test/plugin.xml
----------------------------------------------------------------------
diff --git a/spec/fixtures/plugins/test/plugin.xml b/spec/fixtures/plugins/test/plugin.xml
new file mode 100644
index 0000000..ed33b8a
--- /dev/null
+++ b/spec/fixtures/plugins/test/plugin.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    id="com.phonegap.plugins.childbrowser"
+    version="3.0.0">
+
+    <name>Test Plugin</name>
+
+    <asset src="www/test.js" target="test.js" />
+</plugin>
+

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/spec/fixtures/plugins/test/www/test.js
----------------------------------------------------------------------
diff --git a/spec/fixtures/plugins/test/www/test.js b/spec/fixtures/plugins/test/www/test.js
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/spec/helper.js
----------------------------------------------------------------------
diff --git a/spec/helper.js b/spec/helper.js
new file mode 100644
index 0000000..794eb5d
--- /dev/null
+++ b/spec/helper.js
@@ -0,0 +1,12 @@
+// Override exec for certain commands, to speed execution of tests.
+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);
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/spec/platform.spec.js
----------------------------------------------------------------------
diff --git a/spec/platform.spec.js b/spec/platform.spec.js
new file mode 100644
index 0000000..2963a70
--- /dev/null
+++ b/spec/platform.spec.js
@@ -0,0 +1,121 @@
+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", 500);
+        });
+    });
+
+    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", 500);
+        });
+    });
+
+    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", 500);
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/spec/plugin.spec.js
----------------------------------------------------------------------
diff --git a/spec/plugin.spec.js b/spec/plugin.spec.js
new file mode 100644
index 0000000..6354f5c
--- /dev/null
+++ b/spec/plugin.spec.js
@@ -0,0 +1,69 @@
+var cordova = require('../cordova'),
+    wrench = require('wrench'),
+    mkdirp = wrench.mkdirSyncRecursive,
+    path = require('path'),
+    rmrf = wrench.rmdirSyncRecursive,
+    fs = require('fs'),
+    tempDir = path.join(__dirname, '..', 'temp'),
+    fixturesDir = path.join(__dirname, 'fixtures'),
+    testPlugin = path.join(fixturesDir, 'plugins', 'test');
+
+describe('plugin 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.plugin();
+        }).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.plugin();
+        }).toThrow();
+    });
+
+    describe('ls', function() {
+        var cwd = process.cwd();
+
+        beforeEach(function() {
+            cordova.create(tempDir);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+
+        it('should list out no plugins for a fresh project', function() {
+            process.chdir(tempDir);
+
+            expect(cordova.plugin('ls')).toEqual('No plugins added. Use `cordova plugin add <plugin>`.');
+        });
+    });
+
+    describe('add', function() {
+    });
+
+    describe('remove', function() {
+    });
+});
+

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/src/build.js
----------------------------------------------------------------------
diff --git a/src/build.js b/src/build.js
index fc9453e..acfeecb 100644
--- a/src/build.js
+++ b/src/build.js
@@ -1,12 +1,12 @@
-var cordova_util = require('./util'),
-    path = require('path'),
-    exec = require('child_process').exec,
-    wrench = require('wrench'),
-    rmrf = wrench.rmdirSyncRecursive,
-    cpr = wrench.copyDirSyncRecursive,
+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');
+    fs            = require('fs'),
+    util          = require('util');
 
 module.exports = function build () {
     var projectRoot = cordova_util.isCordova(process.cwd());
@@ -18,34 +18,39 @@ module.exports = function build () {
     var xml = path.join(projectRoot, 'www', 'config.xml');
     var assets = path.join(projectRoot, 'www');
     var cfg = new config_parser(xml);
+    var name = cfg.name();
+    var id = cfg.packageName();
     var platforms = cfg.ls_platforms();
 
     // Iterate over each added platform 
     platforms.map(function(platform) {
-        // Copy in latest www assets.
-        var assetsPath;
+        // Figure out paths based on platform
+        var assetsPath, js;
         switch (platform) {
-            // First clean out the existing www.
             case 'android':
                 assetsPath = path.join(projectRoot, 'platforms', 'android', 'assets', 'www');
+                js = path.join(__dirname, '..', 'lib', 'android', 'framework', 'assets', 'js', 'cordova.android.js');
+
+                // TODO: drop activity name and package name into
+                // appropriate places in android
                 break;
             case 'ios':
                 assetsPath = path.join(projectRoot, 'platforms', 'ios', 'www');
+                js = path.join(__dirname, '..', 'lib', 'ios', 'CordovaLib', 'javascript', 'cordova.ios.js');
+
+                // TODO: drop app name and id into
+                // appropriate places in ios
                 break;
         } 
+
+        // Clean out the existing www.
         rmrf(assetsPath);
+
+        // Copy app assets into native package
         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

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/src/config_parser.js
----------------------------------------------------------------------
diff --git a/src/config_parser.js b/src/config_parser.js
index 979e71f..ec21814 100644
--- a/src/config_parser.js
+++ b/src/config_parser.js
@@ -10,16 +10,16 @@ function config_parser(xmlPath) {
 config_parser.prototype = {
     ls_platforms:function() {
         return this.doc.find('platforms').getchildren().map(function(p) {
-            return p.attrib['name'];
+            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;
+            p.attrib.name = platform;
             this.doc.find('platforms').append(p);
-            fs.writeFileSync(this.path, this.doc.write(), 'utf-8');
+            this.update();
         }
     },
     remove_platform:function(platform) {
@@ -28,14 +28,23 @@ config_parser.prototype = {
             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');
+            this.update();
         }
     },
-    packageName:function() {
-        return this.doc.getroot().attrib.id;
+    packageName:function(id) {
+        if (id) {
+            this.doc.getroot().attrib.id = id;
+            this.update();
+        } else return this.doc.getroot().attrib.id;
     },
-    name:function() {
-        return this.doc.find('name').text;
+    name:function(name) {
+        if (name) {
+            this.doc.find('name').text = name;
+            this.update();
+        } else return this.doc.find('name').text;
+    },
+    update:function() {
+        fs.writeFileSync(this.path, this.doc.write(), 'utf-8');
     }
 };
 

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/src/create.js
----------------------------------------------------------------------
diff --git a/src/create.js b/src/create.js
new file mode 100644
index 0000000..d2a7892
--- /dev/null
+++ b/src/create.js
@@ -0,0 +1,57 @@
+var wrench        = require('wrench'),
+    path          = require('path'),
+    fs            = require('fs'),
+    help          = require('./help'),
+    config_parser = require('./config_parser'),
+    mkdirp        = wrench.mkdirSyncRecursive,
+    cpr           = wrench.copyDirSyncRecursive;
+
+var DEFAULT_NAME = "Hello Cordova",
+    DEFAULT_ID   = "io.cordova.hello-cordova";
+
+/**
+ * Usage:
+ * create(dir) - creates in the specified directory
+ * create(dir, name) - as above, but with specified name
+ * create(dir, id, name) - you get the gist
+ **/
+module.exports = function create (dir, id, name) {
+    if (dir === undefined) {
+        return help();
+    }
+
+    // Massage parameters a bit.
+    if (id && name === undefined) {
+        name = id;
+        id = undefined;
+    }
+    id = id || DEFAULT_ID;
+    name = name || DEFAULT_NAME;
+
+    if (!(dir && (dir[0] == '~' || dir[0] == '/'))) {
+        dir = dir ? path.join(process.cwd(), dir) : process.cwd();
+    }
+
+    var dotCordova = path.join(dir, '.cordova');
+
+    // Check for existing cordova project
+    if (fs.existsSync(dotCordova)) {
+        throw 'Cordova project already exists at ' + dir + ', aborting.';
+    }
+
+    // Create basic project structure.
+    mkdirp(path.join(dir, 'platforms'));
+    mkdirp(path.join(dir, 'plugins'));
+    mkdirp(path.join(dir, 'www'));
+
+    fs.writeFileSync(dotCordova, 'do or do not. there is no try.');
+
+    // Copy in base template
+    cpr(path.join(__dirname, '..', 'templates', 'www'), path.join(dir, 'www'));
+
+    // Write out id and name to config.xml
+    var configPath = path.join(dir, 'www', 'config.xml');
+    var config = new config_parser(configPath);
+    config.packageName(id);
+    config.name(name);
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/src/docs.js
----------------------------------------------------------------------
diff --git a/src/docs.js b/src/docs.js
new file mode 100644
index 0000000..ba8bde0
--- /dev/null
+++ b/src/docs.js
@@ -0,0 +1,21 @@
+var express = require('express'),
+    path    = require('path'),
+    colors  = require('colors'),
+    port    = 2222,
+    statik  = path.join(__dirname, '..', 'doc'),
+    server  = express.createServer();
+
+module.exports = function docs () {
+    server.configure(function() {
+        server.use(express['static'](statik));
+        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));
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/src/help.js
----------------------------------------------------------------------
diff --git a/src/help.js b/src/help.js
new file mode 100644
index 0000000..05695ba
--- /dev/null
+++ b/src/help.js
@@ -0,0 +1,29 @@
+var fs = require('fs'),
+    colors = require('colors'),
+    path = require('path');
+
+module.exports = 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");
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/src/platform.js
----------------------------------------------------------------------
diff --git a/src/platform.js b/src/platform.js
index 16da06b..6e09f42 100644
--- a/src/platform.js
+++ b/src/platform.js
@@ -64,6 +64,13 @@ module.exports = function platform(command, target, callback) {
                 // Shell out to git.
                 var outPath = path.join(__dirname, '..', 'lib', target);
                 var cmd = util.format('git clone %s %s', repos[target], outPath);
+                
+                // TODO: refactor post-clone hooks
+                // make sure we run "make install" if we're cloning ios
+                if (target == 'ios') {
+                    cmd += ' && cd "' + output + '" && make install';
+                }
+
                 console.log('Cloning ' + repos[target] + ', this may take a while...');
                 exec(cmd, function(err, stderr, stdout) {
                     if (err) {

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/src/plugin.js
----------------------------------------------------------------------
diff --git a/src/plugin.js b/src/plugin.js
index f5acc4b..afba9f5 100644
--- a/src/plugin.js
+++ b/src/plugin.js
@@ -1,13 +1,15 @@
 var cordova_util = require('./util'),
     util = require('util'),
     wrench = require('wrench'),
+    cpr = wrench.copyDirSyncRecursive,
     fs = require('fs'),
     path = require('path'),
+    nCalls = require('ncallbacks'),
     config_parser = require('./config_parser'),
     exec = require('child_process').exec,
     ls = fs.readdirSync;
 
-module.exports = function plugin(command, target) {
+module.exports = function plugin(command, target, callback) {
     var projectRoot = cordova_util.isCordova(process.cwd());
 
     if (!projectRoot) {
@@ -17,20 +19,24 @@ module.exports = function plugin(command, target) {
 
     // Grab config info for the project
     var xml = path.join(projectRoot, 'www', 'config.xml');
+    var projectWww = path.join(projectRoot, 'www');
     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);
+    var pluginPath, plugins, targetName;
+    pluginPath = path.join(projectRoot, 'plugins');
+    plugins = ls(pluginPath);
+    if (target) { 
+        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>.';
+            } else return 'No plugins added. Use `cordova plugin add <plugin>`.';
             break;
         case 'add':
             // Check if we already have the plugin.
@@ -45,21 +51,49 @@ module.exports = function plugin(command, target) {
                 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';
             }
 
+            var pluginWww = path.join(target, 'www');
+            var wwwContents = ls(pluginWww);
+
+            var n = wwwContents.length + platforms.length;
+            var end = nCalls(n, callback || function(){});
+
             // 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) {
+            platforms.forEach(function(platform) {
                 var cmd = util.format('%s %s "%s" "%s"', cli, platform, path.join(projectRoot, 'platforms', platform), target);
+                console.log('executing ' + cmd);
                 exec(cmd, function(err, stderr, stdout) {
+                    end();
                     if (err) {
                         console.error(stderr);
+                        // TODO: remove plugin. requires pluginstall to
+                        // support removal.
                         throw 'An error occured during plugin installation. ' + err;
                     }
                 });
             });
+            
+            // Add the plugin web assets to the www folder as well
+            // TODO: assumption that web assets go under www folder
+            // inside plugin dir; instead should read plugin.xml
+            wwwContents.forEach(function(asset) {
+                asset = path.resolve(path.join(pluginWww, asset));
+                var info = fs.lstatSync(asset);
+                var name = asset.substr(asset.lastIndexOf('/')+1);
+                var wwwPath = path.join(projectWww, name);
+                if (info.isDirectory()) {
+                    cpr(asset, wwwPath);
+                } else {
+                    fs.writeFileSync(wwwPath, fs.readFileSync(asset));
+                }
+                end();
+            });
 
             break;
         case 'remove':
+            // TODO: remove plugin. requires pluginstall to
+            // support removal.
             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/ec3e6d89/templates/www/config.xml
----------------------------------------------------------------------
diff --git a/templates/www/config.xml b/templates/www/config.xml
index 400c377..fa3369d 100644
--- a/templates/www/config.xml
+++ b/templates/www/config.xml
@@ -1,58 +1,50 @@
 <?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 xmlns     = "http://www.w3.org/ns/widgets"
+        xmlns:gap = "http://phonegap.com/ns/1.0"
+        id        = "io.cordova.hello-cordova"
+        version   = "2.0.0">
+    <name>Hello Cordova</name>
+
+    <description>
+        A sample Apache Cordova application that responds to the deviceready event.
+    </description>
+
+    <author href="http://cordova.io" email="callback-dev@incubator.apache.org">
+        Apache Cordova Team
+    </author>
+
+    <platforms>
+    </platforms>
+
+    <icon src="res/icon/cordova_512.png"        width="512" height="512" />
+    <icon src="res/icon/cordova_android_96.png" width="96"  height="96"  gap:platform="android" />
+    <icon src="res/icon/cordova_bb_80.png"      width="80"  height="80"  gap:platform="blackberry" />
+    <icon src="res/icon/cordova_ios_144.png"    width="144" height="144" gap:platform="ios" />
+
+    <gap:splash src="res/screen/android_hdpi_landscape.png"      width="800"  height="480"  gap:platform="android" />
+    <gap:splash src="res/screen/android_hdpi_portrait.png"       width="480"  height="800"  gap:platform="android" />
+    <gap:splash src="res/screen/android_ldpi_landscape.png"      width="320"  height="200"  gap:platform="android" />
+    <gap:splash src="res/screen/android_ldpi_portrait.png"       width="200"  height="320"  gap:platform="android" />
+    <gap:splash src="res/screen/android_mdpi_landscape.png"      width="480"  height="320"  gap:platform="android" />
+    <gap:splash src="res/screen/android_mdpi_portrait.png"       width="320"  height="480"  gap:platform="android" />
+    <gap:splash src="res/screen/android_xhdpi_landscape.png"     width="1280" height="720"  gap:platform="android" />
+    <gap:splash src="res/screen/android_xhdpi_portrait.png"      width="720"  height="1280" gap:platform="android" />
+    <gap:splash src="res/screen/blackberry_transparent_300.png"  width="300"  height="300"  gap:platform="blackberry" />
+    <gap:splash src="res/screen/blackberry_transparent_400.png"  width="200"  height="200"  gap:platform="blackberry" />
+    <gap:splash src="res/screen/ipad_landscape.png"              width="1024" height="748"  gap:platform="ios" />
+    <gap:splash src="res/screen/ipad_portrait.png"               width="768"  height="1004" gap:platform="ios" />
+    <gap:splash src="res/screen/ipad_retina_landscape.png"       width="2048" height="1496" gap:platform="ios" />
+    <gap:splash src="res/screen/ipad_retina_portrait.png"        width="1536" height="2008" gap:platform="ios" />
+    <gap:splash src="res/screen/iphone_landscape.png"            width="480"  height="320"  gap:platform="ios" />
+    <gap:splash src="res/screen/iphone_portrait.png"             width="320"  height="480"  gap:platform="ios" />
+    <gap:splash src="res/screen/iphone_retina_landscape.png"     width="960"  height="640"  gap:platform="ios" />
+    <gap:splash src="res/screen/iphone_retina_portrait.png"      width="640"  height="960"  gap:platform="ios" />
+    <gap:splash src="res/screen/windows_phone_portrait.jpg"      width="480"  height="800"  gap:platform="winphone" />
+
+    <feature name="http://api.phonegap.com/1.0/device" />
+
+    <preference name="phonegap-version" value="1.9.0" />
+    <preference name="orientation"      value="default" />
+    <preference name="target-device"    value="universal" />
+    <preference name="fullscreen"       value="false" />
 </widget>

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/css/index.css
----------------------------------------------------------------------
diff --git a/templates/www/css/index.css b/templates/www/css/index.css
new file mode 100644
index 0000000..c869f87
--- /dev/null
+++ b/templates/www/css/index.css
@@ -0,0 +1,100 @@
+html,
+body {
+    height:100%;
+    font-size:12px;
+    width:100%;
+}
+
+html {
+    display:table;
+}
+
+body {
+    background-color:#A7A7A7;
+    background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
+    background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
+    background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
+    background-image:-webkit-gradient(
+        linear,
+        left top,
+        left bottom,
+        color-stop(0, #A7A7A7),
+        color-stop(0.51, #E4E4E4)
+    );
+    display:table-cell;
+    font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
+    text-transform:uppercase;
+    vertical-align:middle;
+}
+
+.app {
+    background-image:url(../img/cordova.png);
+    background-repeat:no-repeat;
+    margin:0px auto;
+    width:275px;
+}
+
+h1 {
+    font-size:2em;
+    font-weight:300;
+    margin:0px;
+    overflow:visible;
+    padding:0px;
+    text-align:center;
+}
+
+.status {
+    background-color:#333333;
+    border-radius:4px;
+    -webkit-border-radius:4px;
+    color:#FFFFFF;
+    font-size:1em;
+    margin:0px auto;
+    padding:2px 10px;
+    text-align:center;
+    width:100%;
+    max-width:175px;
+}
+
+.status.complete {
+    background-color:#4B946A;
+}
+
+.hide {
+    display:none;
+}
+
+@keyframes fade {
+    from { opacity: 1.0; }
+    50% { opacity: 0.4; }
+    to { opacity: 1.0; }
+}
+ 
+@-webkit-keyframes fade {
+    from { opacity: 1.0; }
+    50% { opacity: 0.4; }
+    to { opacity: 1.0; }
+}
+ 
+.blink {
+    animation:fade 3000ms infinite;
+    -webkit-animation:fade 3000ms infinite;
+}
+
+/* portrait */
+/* @media screen and (max-aspect-ratio: 1/1) */
+.app {
+    background-position:center top;
+    height:100px;              /* adds enough room for text */
+    padding:180px 0px 0px 0px; /* background height - shadow offset */
+}
+
+/* lanscape (when wide enough) */
+@media screen and (min-aspect-ratio: 1/1) and (min-width:445px) {
+    .app {
+        background-position:left center;
+        height:140px;       /* height + padding = background image size */
+        padding-left:170px; /* background width */
+        padding-top:60px;   /* center the text */
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/img/cordova.png
----------------------------------------------------------------------
diff --git a/templates/www/img/cordova.png b/templates/www/img/cordova.png
new file mode 100644
index 0000000..e8169cf
Binary files /dev/null and b/templates/www/img/cordova.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/index.html
----------------------------------------------------------------------
diff --git a/templates/www/index.html b/templates/www/index.html
index 51661b3..202af51 100644
--- a/templates/www/index.html
+++ b/templates/www/index.html
@@ -1,10 +1,24 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>Sample Cordova Project</title>
-    <script src="cordova.js" type="text/javascript"></script>
-  </head>
-  <body>
-    <h1>Hello Cordova</h1>
-  </body>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <meta name = "format-detection" content = "telephone=no"/>
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width;" />
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
+        <title>Hello Cordova</title>
+    </head>
+    <body>
+        <div class="app">
+            <h1>Apache Cordova</h1>
+            <div id="deviceready">
+                <p class="status pending blink">Connecting to Device</p>
+                <p class="status complete blink hide">Device is Ready</p>
+            </div>
+        </div>
+        <script type="text/javascript" src="cordova.js"></script>
+        <script type="text/javascript" src="js/index.js"></script>
+        <script type="text/javascript">
+            app.initialize();
+        </script>
+    </body>
 </html>

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/js/index.js
----------------------------------------------------------------------
diff --git a/templates/www/js/index.js b/templates/www/js/index.js
new file mode 100644
index 0000000..6140331
--- /dev/null
+++ b/templates/www/js/index.js
@@ -0,0 +1,20 @@
+var app = {
+    initialize: function() {
+        this.bind();
+    },
+    bind: function() {
+        document.addEventListener('deviceready', this.deviceready, false);
+    },
+    deviceready: function() {
+        // note that this is an event handler so the scope is that of the event
+        // so we need to call app.report(), and not this.report()
+        app.report('deviceready');
+    },
+    report: function(id) { 
+        console.log("report:" + id);
+        // hide the .pending <p> and show the .complete <p>
+        document.querySelector('#' + id + ' .pending').className += ' hide';
+        var completeElem = document.querySelector('#' + id + ' .complete');
+        completeElem.className = completeElem.className.split('hide').join('');
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_128.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_128.png b/templates/www/res/icon/cordova_128.png
new file mode 100644
index 0000000..3516df3
Binary files /dev/null and b/templates/www/res/icon/cordova_128.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_16.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_16.png b/templates/www/res/icon/cordova_16.png
new file mode 100644
index 0000000..54e19c5
Binary files /dev/null and b/templates/www/res/icon/cordova_16.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_24.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_24.png b/templates/www/res/icon/cordova_24.png
new file mode 100644
index 0000000..c7d43ad
Binary files /dev/null and b/templates/www/res/icon/cordova_24.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_256.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_256.png b/templates/www/res/icon/cordova_256.png
new file mode 100644
index 0000000..e1cd0e6
Binary files /dev/null and b/templates/www/res/icon/cordova_256.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_32.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_32.png b/templates/www/res/icon/cordova_32.png
new file mode 100644
index 0000000..734fffc
Binary files /dev/null and b/templates/www/res/icon/cordova_32.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_48.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_48.png b/templates/www/res/icon/cordova_48.png
new file mode 100644
index 0000000..8ad8bac
Binary files /dev/null and b/templates/www/res/icon/cordova_48.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_512.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_512.png b/templates/www/res/icon/cordova_512.png
new file mode 100644
index 0000000..c9465f3
Binary files /dev/null and b/templates/www/res/icon/cordova_512.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_64.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_64.png b/templates/www/res/icon/cordova_64.png
new file mode 100644
index 0000000..03b3849
Binary files /dev/null and b/templates/www/res/icon/cordova_64.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_android_36.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_android_36.png b/templates/www/res/icon/cordova_android_36.png
new file mode 100644
index 0000000..cd5032a
Binary files /dev/null and b/templates/www/res/icon/cordova_android_36.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_android_48.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_android_48.png b/templates/www/res/icon/cordova_android_48.png
new file mode 100644
index 0000000..e79c606
Binary files /dev/null and b/templates/www/res/icon/cordova_android_48.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_android_72.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_android_72.png b/templates/www/res/icon/cordova_android_72.png
new file mode 100644
index 0000000..4d27634
Binary files /dev/null and b/templates/www/res/icon/cordova_android_72.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_android_96.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_android_96.png b/templates/www/res/icon/cordova_android_96.png
new file mode 100644
index 0000000..ec7ffbf
Binary files /dev/null and b/templates/www/res/icon/cordova_android_96.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_bb_80.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_bb_80.png b/templates/www/res/icon/cordova_bb_80.png
new file mode 100644
index 0000000..f86a27a
Binary files /dev/null and b/templates/www/res/icon/cordova_bb_80.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_ios_114.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_ios_114.png b/templates/www/res/icon/cordova_ios_114.png
new file mode 100644
index 0000000..efd9c37
Binary files /dev/null and b/templates/www/res/icon/cordova_ios_114.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_ios_144.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_ios_144.png b/templates/www/res/icon/cordova_ios_144.png
new file mode 100644
index 0000000..dd819da
Binary files /dev/null and b/templates/www/res/icon/cordova_ios_144.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_ios_57.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_ios_57.png b/templates/www/res/icon/cordova_ios_57.png
new file mode 100644
index 0000000..c795fc4
Binary files /dev/null and b/templates/www/res/icon/cordova_ios_57.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/icon/cordova_ios_72.png
----------------------------------------------------------------------
diff --git a/templates/www/res/icon/cordova_ios_72.png b/templates/www/res/icon/cordova_ios_72.png
new file mode 100644
index 0000000..b1cfde7
Binary files /dev/null and b/templates/www/res/icon/cordova_ios_72.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/android_hdpi_landscape.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/android_hdpi_landscape.png b/templates/www/res/screen/android_hdpi_landscape.png
new file mode 100644
index 0000000..a61e2b1
Binary files /dev/null and b/templates/www/res/screen/android_hdpi_landscape.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/android_hdpi_portrait.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/android_hdpi_portrait.png b/templates/www/res/screen/android_hdpi_portrait.png
new file mode 100644
index 0000000..5d6a28a
Binary files /dev/null and b/templates/www/res/screen/android_hdpi_portrait.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/android_ldpi_landscape.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/android_ldpi_landscape.png b/templates/www/res/screen/android_ldpi_landscape.png
new file mode 100644
index 0000000..f3934cd
Binary files /dev/null and b/templates/www/res/screen/android_ldpi_landscape.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/android_ldpi_portrait.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/android_ldpi_portrait.png b/templates/www/res/screen/android_ldpi_portrait.png
new file mode 100644
index 0000000..65ad163
Binary files /dev/null and b/templates/www/res/screen/android_ldpi_portrait.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/android_mdpi_landscape.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/android_mdpi_landscape.png b/templates/www/res/screen/android_mdpi_landscape.png
new file mode 100644
index 0000000..a1b697c
Binary files /dev/null and b/templates/www/res/screen/android_mdpi_landscape.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/android_mdpi_portrait.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/android_mdpi_portrait.png b/templates/www/res/screen/android_mdpi_portrait.png
new file mode 100644
index 0000000..ea15693
Binary files /dev/null and b/templates/www/res/screen/android_mdpi_portrait.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/android_xhdpi_landscape.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/android_xhdpi_landscape.png b/templates/www/res/screen/android_xhdpi_landscape.png
new file mode 100644
index 0000000..79f2f09
Binary files /dev/null and b/templates/www/res/screen/android_xhdpi_landscape.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/android_xhdpi_portrait.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/android_xhdpi_portrait.png b/templates/www/res/screen/android_xhdpi_portrait.png
new file mode 100644
index 0000000..c2e8042
Binary files /dev/null and b/templates/www/res/screen/android_xhdpi_portrait.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/blackberry_transparent_300.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/blackberry_transparent_300.png b/templates/www/res/screen/blackberry_transparent_300.png
new file mode 100644
index 0000000..b548bdc
Binary files /dev/null and b/templates/www/res/screen/blackberry_transparent_300.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/blackberry_transparent_400.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/blackberry_transparent_400.png b/templates/www/res/screen/blackberry_transparent_400.png
new file mode 100644
index 0000000..3facdf9
Binary files /dev/null and b/templates/www/res/screen/blackberry_transparent_400.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/ipad_landscape.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/ipad_landscape.png b/templates/www/res/screen/ipad_landscape.png
new file mode 100644
index 0000000..04be5ac
Binary files /dev/null and b/templates/www/res/screen/ipad_landscape.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/ipad_portrait.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/ipad_portrait.png b/templates/www/res/screen/ipad_portrait.png
new file mode 100644
index 0000000..41e839d
Binary files /dev/null and b/templates/www/res/screen/ipad_portrait.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/ipad_retina_landscape.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/ipad_retina_landscape.png b/templates/www/res/screen/ipad_retina_landscape.png
new file mode 100644
index 0000000..95c542d
Binary files /dev/null and b/templates/www/res/screen/ipad_retina_landscape.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/ipad_retina_portrait.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/ipad_retina_portrait.png b/templates/www/res/screen/ipad_retina_portrait.png
new file mode 100644
index 0000000..aae1862
Binary files /dev/null and b/templates/www/res/screen/ipad_retina_portrait.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/iphone_landscape.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/iphone_landscape.png b/templates/www/res/screen/iphone_landscape.png
new file mode 100644
index 0000000..d154883
Binary files /dev/null and b/templates/www/res/screen/iphone_landscape.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/iphone_portrait.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/iphone_portrait.png b/templates/www/res/screen/iphone_portrait.png
new file mode 100644
index 0000000..6fcba56
Binary files /dev/null and b/templates/www/res/screen/iphone_portrait.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/iphone_retina_landscape.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/iphone_retina_landscape.png b/templates/www/res/screen/iphone_retina_landscape.png
new file mode 100644
index 0000000..0165669
Binary files /dev/null and b/templates/www/res/screen/iphone_retina_landscape.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/iphone_retina_portrait.png
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/iphone_retina_portrait.png b/templates/www/res/screen/iphone_retina_portrait.png
new file mode 100644
index 0000000..bd24886
Binary files /dev/null and b/templates/www/res/screen/iphone_retina_portrait.png differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/res/screen/windows_phone_portrait.jpg
----------------------------------------------------------------------
diff --git a/templates/www/res/screen/windows_phone_portrait.jpg b/templates/www/res/screen/windows_phone_portrait.jpg
new file mode 100644
index 0000000..9f95387
Binary files /dev/null and b/templates/www/res/screen/windows_phone_portrait.jpg differ

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/spec.html
----------------------------------------------------------------------
diff --git a/templates/www/spec.html b/templates/www/spec.html
new file mode 100644
index 0000000..83d7d2e
--- /dev/null
+++ b/templates/www/spec.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Jasmine Spec Runner</title>
+
+        <!-- jasmine source -->
+        <link rel="shortcut icon" type="image/png" href="spec/lib/jasmine-1.2.0/jasmine_favicon.png">
+        <link rel="stylesheet" type="text/css" href="spec/lib/jasmine-1.2.0/jasmine.css">
+        <script type="text/javascript" src="spec/lib/jasmine-1.2.0/jasmine.js"></script>
+        <script type="text/javascript" src="spec/lib/jasmine-1.2.0/jasmine-html.js"></script>
+
+        <!-- include source files here... -->
+        <script type="text/javascript" src="js/index.js"></script>
+
+        <!-- include spec files here... -->
+        <script type="text/javascript" src="spec/helper.js"></script>
+        <script type="text/javascript" src="spec/index.js"></script>
+
+        <script type="text/javascript">
+            (function() {
+                var jasmineEnv = jasmine.getEnv();
+                jasmineEnv.updateInterval = 1000;
+
+                var htmlReporter = new jasmine.HtmlReporter();
+
+                jasmineEnv.addReporter(htmlReporter);
+
+                jasmineEnv.specFilter = function(spec) {
+                    return htmlReporter.specFilter(spec);
+                };
+
+                var currentWindowOnload = window.onload;
+
+                window.onload = function() {
+                    if (currentWindowOnload) {
+                        currentWindowOnload();
+                    }
+                    execJasmine();
+                };
+
+                function execJasmine() {
+                    jasmineEnv.execute();
+                }
+            })();
+        </script>
+    </head>
+    <body>
+        <div id="stage" style="display:none;"></div>
+    </body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/spec/helper.js
----------------------------------------------------------------------
diff --git a/templates/www/spec/helper.js b/templates/www/spec/helper.js
new file mode 100644
index 0000000..9f99445
--- /dev/null
+++ b/templates/www/spec/helper.js
@@ -0,0 +1,11 @@
+afterEach(function() {
+    document.getElementById('stage').innerHTML = '';
+});
+
+var helper = {
+    trigger: function(obj, name) {
+        var e = document.createEvent('Event');
+        e.initEvent(name, true, true);
+        obj.dispatchEvent(e);
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/spec/index.js
----------------------------------------------------------------------
diff --git a/templates/www/spec/index.js b/templates/www/spec/index.js
new file mode 100644
index 0000000..121cf63
--- /dev/null
+++ b/templates/www/spec/index.js
@@ -0,0 +1,49 @@
+describe('app', function() {
+    describe('initialize', function() {
+        it('should bind deviceready', function() {
+            runs(function() {
+                spyOn(app, 'deviceready');
+                app.initialize();
+                helper.trigger(window.document, 'deviceready');
+            });
+
+            waitsFor(function() {
+                return (app.deviceready.calls.length > 0);
+            }, 'deviceready should be called once', 500);
+
+            runs(function() {
+                expect(app.deviceready).toHaveBeenCalled();
+            });
+        });
+    });
+
+    describe('deviceready', function() {
+        it('should report that it fired', function() {
+            spyOn(app, 'report');
+            app.deviceready();
+            expect(app.report).toHaveBeenCalledWith('deviceready');
+        });
+    });
+
+    describe('report', function() {
+        beforeEach(function() {
+            var el = document.getElementById('stage');
+            el.innerHTML = ['<div id="deviceready">',
+                            '    <p class="status pending">Pending</p>',
+                            '    <p class="status complete hide">Complete</p>',
+                            '</div>'].join('\n');
+        });
+
+        it('should show the completion state', function() {
+            app.report('deviceready');
+            var el = document.querySelector('#deviceready .complete:not(.hide)');
+            expect(el).toBeTruthy();
+        });
+
+        it('should hide the pending state', function() {
+            app.report('deviceready');
+            var el = document.querySelector('#deviceready .pending.hide');
+            expect(el).toBeTruthy();
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/incubator-cordova-labs/blob/ec3e6d89/templates/www/spec/lib/jasmine-1.2.0/MIT.LICENSE
----------------------------------------------------------------------
diff --git a/templates/www/spec/lib/jasmine-1.2.0/MIT.LICENSE b/templates/www/spec/lib/jasmine-1.2.0/MIT.LICENSE
new file mode 100644
index 0000000..7c435ba
--- /dev/null
+++ b/templates/www/spec/lib/jasmine-1.2.0/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2011 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.