You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ka...@apache.org on 2014/05/02 20:33:27 UTC

[08/24] Split out cordova-lib: move cordova-cli files

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/metadata/wp8_parser.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/metadata/wp8_parser.js b/cordova-lib/src/cordova/metadata/wp8_parser.js
new file mode 100644
index 0000000..b5f0326
--- /dev/null
+++ b/cordova-lib/src/cordova/metadata/wp8_parser.js
@@ -0,0 +1,287 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var fs            = require('fs'),
+    path          = require('path'),
+    et            = require('elementtree'),
+    util          = require('../util'),
+    events        = require('../events'),
+    shell         = require('shelljs'),
+    child_process = require('child_process'),
+    Q             = require('q'),
+    ConfigParser  = require('../ConfigParser'),
+    CordovaError  = require('../CordovaError'),
+    xml           = require('../xml-helpers'),
+    config        = require('../config'),
+    hooker        = require('../hooker');
+
+module.exports = function wp8_parser(project) {
+    try {
+        // TODO : Check that it's not a wp8 project?
+        var csproj_file   = fs.readdirSync(project).filter(function(e) { return e.match(/\.csproj$/i); })[0];
+        if (!csproj_file) throw new CordovaError('No .csproj file in "'+project+'"');
+        this.wp8_proj_dir = project;
+        this.csproj_path  = path.join(this.wp8_proj_dir, csproj_file);
+        this.sln_path     = path.join(this.wp8_proj_dir, csproj_file.replace(/\.csproj/, '.sln'));
+    } catch(e) {
+        throw new CordovaError('The provided path "' + project + '" is not a Windows Phone 8 project. ' + e);
+    }
+    this.manifest_path  = path.join(this.wp8_proj_dir, 'Properties', 'WMAppManifest.xml');
+};
+
+// Returns a promise.
+module.exports.check_requirements = function(project_root) {
+    events.emit('log', 'Checking wp8 requirements...');
+    var lib_path = path.join(util.libDirectory, 'wp', 'cordova', require('../../platforms').wp8.version, 'wp8');
+    var custom_path = config.has_custom_path(project_root, 'wp8');
+    if (custom_path) {
+        lib_path = path.join(custom_path, 'wp8');
+    }
+    var command = '"' + path.join(lib_path, 'bin', 'check_reqs') + '"';
+    events.emit('verbose', 'Running "' + command + '" (output to follow)');
+    var d = Q.defer();
+    child_process.exec(command, function(err, output, stderr) {
+        events.emit('verbose', output);
+        if (err) {
+            d.reject(new CordovaError('Requirements check failed: ' + output + stderr));
+        } else {
+            d.resolve();
+        }
+    });
+    return d.promise;
+};
+
+module.exports.prototype = {
+    update_from_config:function(config) {
+        //check config parser
+        if (config instanceof ConfigParser) {
+        } else throw new Error('update_from_config requires a ConfigParser object');
+
+        //Get manifest file
+        var manifest = xml.parseElementtreeSync(this.manifest_path);
+
+        //Update app version
+        var version = config.version();
+        manifest.find('.//App').attrib.Version = version;
+
+        // Update app name by editing app title in Properties\WMAppManifest.xml
+        var name = config.name();
+        var prev_name = manifest.find('.//App[@Title]')['attrib']['Title'];
+        if(prev_name != name) {
+            //console.log("Updating app name from " + prev_name + " to " + name);
+            manifest.find('.//App').attrib.Title = name;
+            manifest.find('.//App').attrib.Publisher = name + " Publisher";
+            manifest.find('.//App').attrib.Author = name + " Author";
+            manifest.find('.//PrimaryToken').attrib.TokenID = name;
+            //update name of sln and csproj.
+            name = name.replace(/(\.\s|\s\.|\s+|\.+)/g, '_'); //make it a ligitamate name
+            prev_name = prev_name.replace(/(\.\s|\s\.|\s+|\.+)/g, '_');
+            // TODO: might return .sln.user? (generated file)
+            var sln_name = fs.readdirSync(this.wp8_proj_dir).filter(function(e) { return e.match(/\.sln$/i); })[0];
+            var sln_path = path.join(this.wp8_proj_dir, sln_name);
+            var sln_file = fs.readFileSync(sln_path, 'utf-8');
+            var name_regex = new RegExp(prev_name, "g");
+            fs.writeFileSync(sln_path, sln_file.replace(name_regex, name), 'utf-8');
+            shell.mv('-f', this.csproj_path, path.join(this.wp8_proj_dir, name + '.csproj'));
+            this.csproj_path = path.join(this.wp8_proj_dir, name + '.csproj');
+            shell.mv('-f', sln_path, path.join(this.wp8_proj_dir, name + '.sln'));
+            this.sln_path    = path.join(this.wp8_proj_dir, name + '.sln');
+        }
+
+        // Update package name by changing:
+        /*  - CordovaAppProj.csproj
+         *  - MainPage.xaml
+         *  - MainPage.xaml.cs
+         *  - App.xaml
+         *  - App.xaml.cs
+         */
+         var pkg = config.packageName();
+         var csproj = xml.parseElementtreeSync(this.csproj_path);
+         prev_name = csproj.find('.//RootNamespace').text;
+         if(prev_name != pkg) {
+            //console.log("Updating package name from " + prev_name + " to " + pkg);
+            //CordovaAppProj.csproj
+            csproj.find('.//RootNamespace').text = pkg;
+            csproj.find('.//AssemblyName').text = pkg;
+            csproj.find('.//XapFilename').text = pkg + '.xap';
+            csproj.find('.//SilverlightAppEntry').text = pkg + '.App';
+            fs.writeFileSync(this.csproj_path, csproj.write({indent: 4}), 'utf-8');
+            //MainPage.xaml
+            var mainPageXAML = xml.parseElementtreeSync(path.join(this.wp8_proj_dir, 'MainPage.xaml'));
+            mainPageXAML.getroot().attrib['x:Class'] = pkg + '.MainPage';
+            fs.writeFileSync(path.join(this.wp8_proj_dir, 'MainPage.xaml'), mainPageXAML.write({indent: 4}), 'utf-8');
+            //MainPage.xaml.cs
+            var mainPageCS = fs.readFileSync(path.join(this.wp8_proj_dir, 'MainPage.xaml.cs'), 'utf-8');
+            var namespaceRegEx = new RegExp('namespace ' + prev_name);
+            fs.writeFileSync(path.join(this.wp8_proj_dir, 'MainPage.xaml.cs'), mainPageCS.replace(namespaceRegEx, 'namespace ' + pkg), 'utf-8');
+            //App.xaml
+            var appXAML = xml.parseElementtreeSync(path.join(this.wp8_proj_dir, 'App.xaml'));
+            appXAML.getroot().attrib['x:Class'] = pkg + '.App';
+            fs.writeFileSync(path.join(this.wp8_proj_dir, 'App.xaml'), appXAML.write({indent: 4}), 'utf-8');
+            //App.xaml.cs
+            var appCS = fs.readFileSync(path.join(this.wp8_proj_dir, 'App.xaml.cs'), 'utf-8');
+            fs.writeFileSync(path.join(this.wp8_proj_dir, 'App.xaml.cs'), appCS.replace(namespaceRegEx, 'namespace ' + pkg), 'utf-8');
+         }
+
+         //Write out manifest
+         fs.writeFileSync(this.manifest_path, manifest.write({indent: 4}), 'utf-8');
+
+        // Update icons
+        var icons = config.getIcons('wp8');
+        var platformRoot = this.wp8_proj_dir;
+        var appRoot = util.isCordova(platformRoot);
+
+        // icons, that should be added to platform
+        // @param dest {string} Path to copy icon to, relative to platform root
+        var platformIcons = [
+            {dest: "ApplicationIcon.png", width: 99, height: 99},
+            {dest: "Background.png", width: 159, height: 159},
+        ];
+
+        platformIcons.forEach(function (item) {
+            icon = icons.getIconBySize(item.width, item.height) || icons.getDefault();
+            if (icon){
+                var src = path.join(appRoot, icon.src),
+                    dest = path.join(platformRoot, item.dest);
+                events.emit('verbose', 'Copying icon from ' + src + ' to ' + dest);
+                shell.cp('-f', src, dest);
+            }
+        });
+
+    },
+    // Returns the platform-specific www directory.
+    www_dir:function() {
+        return path.join(this.wp8_proj_dir, 'www');
+    },
+    config_xml:function() {
+        return path.join(this.wp8_proj_dir, 'config.xml');
+    },
+    // copy files from merges directory to actual www dir
+    copy_merges:function(merges_sub_path) {
+        var merges_path = path.join(util.appDir(util.isCordova(this.wp8_proj_dir)), 'merges', merges_sub_path);
+        if (fs.existsSync(merges_path)) {
+            var overrides = path.join(merges_path, '*');
+            shell.cp('-rf', overrides, this.www_dir());
+        }
+    },
+
+    // Used for creating platform_www in projects created by older versions.
+    cordovajs_path:function(libDir) {
+        var jsPath = path.join(libDir, '..', 'common', 'www', 'cordova.js');
+        return path.resolve(jsPath);
+    },
+
+    // Replace the www dir with contents of platform_www and app www and updates the csproj file.
+    update_www:function() {
+        var projectRoot = util.isCordova(this.wp8_proj_dir);
+        var app_www = util.projectWww(projectRoot);
+        var platform_www = path.join(this.wp8_proj_dir, 'platform_www');
+
+        // Clear the www dir
+        shell.rm('-rf', this.www_dir());
+        shell.mkdir(this.www_dir());
+        // Copy over all app www assets
+        shell.cp('-rf', path.join(app_www, '*'), this.www_dir());
+
+        // Copy all files from merges directories - wp generic first, then wp8 specific.
+        this.copy_merges('wp');
+        this.copy_merges('wp8');
+
+        // Copy over stock platform www assets (cordova.js)
+        shell.cp('-rf', path.join(platform_www, '*'), this.www_dir());
+    },
+
+    // updates the csproj file to explicitly list all www content.
+    update_csproj:function() {
+        var csproj_xml = xml.parseElementtreeSync(this.csproj_path);
+        // remove any previous references to the www files
+        var item_groups = csproj_xml.findall('ItemGroup');
+        for (var i = 0, l = item_groups.length; i < l; i++) {
+            var group = item_groups[i];
+            var files = group.findall('Content');
+            for (var j = 0, k = files.length; j < k; j++) {
+                var file = files[j];
+                if (file.attrib.Include.substr(0, 3) == 'www') {
+                    // remove file reference
+                    group.remove(0, file);
+                    // remove ItemGroup if empty
+                    var new_group = group.findall('Content');
+                    if(new_group.length < 1) {
+                        csproj_xml.getroot().remove(0, group);
+                    }
+                }
+            }
+        }
+
+        // now add all www references back in from the root www folder
+        var www_files = this.folder_contents('www', this.www_dir());
+        for(file in www_files) {
+            var item = new et.Element('ItemGroup');
+            var content = new et.Element('Content');
+            content.attrib.Include = www_files[file];
+            item.append(content);
+            csproj_xml.getroot().append(item);
+        }
+        // save file
+        fs.writeFileSync(this.csproj_path, csproj_xml.write({indent:4}), 'utf-8');
+    },
+    // Returns an array of all the files in the given directory with relative paths
+    // - name     : the name of the top level directory (i.e all files will start with this in their path)
+    // - dir     : the directory whos contents will be listed under 'name' directory
+    folder_contents:function(name, dir) {
+        var results = [];
+        var folder_dir = fs.readdirSync(dir);
+        for(item in folder_dir) {
+            var stat = fs.statSync(path.join(dir, folder_dir[item]));
+
+            if(stat.isDirectory()) {
+                var sub_dir = this.folder_contents(path.join(name, folder_dir[item]), path.join(dir, folder_dir[item]));
+                //Add all subfolder item paths
+                for(sub_item in sub_dir) {
+                    results.push(sub_dir[sub_item]);
+                }
+            }
+            else if(stat.isFile()) {
+                results.push(path.join(name, folder_dir[item]));
+            }
+            // else { it is a FIFO, or a Socket or something ... }
+        }
+        return results;
+    },
+
+    // calls the nessesary functions to update the wp8 project
+    // Returns a promise.
+    update_project:function(cfg) {
+        try {
+            this.update_from_config(cfg);
+        } catch(e) {
+            return Q.reject(e);
+        }
+
+        // trigger an event in case anyone needs to modify the contents of the www folder before we package it.
+        var that = this;
+        var projectRoot = util.isCordova(process.cwd());
+
+        var hooks = new hooker(projectRoot);
+        return hooks.fire('pre_package', { wwwPath:this.www_dir(), platforms: ['wp8']  })
+        .then(function() {
+            that.update_csproj();
+            util.deleteSvnFolders(that.www_dir());
+        });
+    }
+};

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/platform.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/platform.js b/cordova-lib/src/cordova/platform.js
new file mode 100644
index 0000000..bd20780
--- /dev/null
+++ b/cordova-lib/src/cordova/platform.js
@@ -0,0 +1,399 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var config            = require('./config'),
+    cordova           = require('../cordova'),
+    cordova_util      = require('./util'),
+    ConfigParser     = require('./ConfigParser'),
+    util              = require('util'),
+    fs                = require('fs'),
+    os                = require('os'),
+    path              = require('path'),
+    hooker            = require('./hooker'),
+    events            = require('./events'),
+    lazy_load         = require('./lazy_load'),
+    CordovaError      = require('./CordovaError'),
+    Q                 = require('q'),
+    platforms         = require('../platforms'),
+    superspawn        = require('./superspawn'),
+    semver            = require('semver'),
+    shell             = require('shelljs');
+
+function getVersionFromScript(script, defaultValue) {
+    var versionPromise = Q(defaultValue);
+    if (fs.existsSync(script)) {
+        versionPromise = superspawn.spawn(script);
+    } else {
+        /* if you are here, it's probably because you're in Jasmine: fix your existsSync stub */
+        versionPromise = Q(defaultValue);
+    }
+    return versionPromise;
+}
+
+function add(hooks, projectRoot, targets, opts) {
+    var xml = cordova_util.projectConfig(projectRoot);
+    var cfg = new ConfigParser(xml);
+    if (!targets || !targets.length) {
+        return Q.reject(new CordovaError('No platform specified. Please specify a platform to add. See "platform list".'));
+    }
+    var config_json = config.read(projectRoot);
+    var platformsDir = path.join(projectRoot, 'platforms');
+
+    // The "platforms" dir is safe to delete, it's almost equivalent to
+    // cordova platfrom rm <list of all platforms>
+    if ( !fs.existsSync(platformsDir)) {
+        shell.mkdir('-p', platformsDir);
+    }
+
+    return hooks.fire('before_platform_add', opts)
+    .then(function() {
+        return targets.reduce(function(soFar, t) {
+            return soFar.then(function() {
+                return lazy_load.based_on_config(projectRoot, t)
+                .then(function(libDir) {
+                    var template = config_json.lib && config_json.lib[t] && config_json.lib[t].template || null;
+                    var copts = null;
+                    if ('spawnoutput' in opts) {
+                        copts = opts.spawnoutput;
+                    }
+                    return call_into_create(t, projectRoot, cfg, libDir, template, copts);
+                }, function(err) {
+                    throw new CordovaError('Unable to fetch platform ' + t + ': ' + err);
+                });
+            });
+        }, Q());
+    })
+    .then(function() {
+        return hooks.fire('after_platform_add', opts);
+    });
+};
+
+function remove(hooks, projectRoot, targets, opts) {
+    if (!targets || !targets.length) {
+        return Q.reject(new CordovaError('No platform[s] specified. Please specify platform[s] to remove. See "platform list".'));
+    }
+    return hooks.fire('before_platform_rm', opts)
+    .then(function() {
+        targets.forEach(function(target) {
+            shell.rm('-rf', path.join(projectRoot, 'platforms', target));
+            var plugins_json = path.join(projectRoot, 'plugins', target + '.json');
+            if (fs.existsSync(plugins_json)) shell.rm(plugins_json);
+        });
+    }).then(function() {
+        return hooks.fire('after_platform_rm', opts);
+    });
+}
+
+function update(hooks, projectRoot, targets, opts) {
+    // Shell out to the update script provided by the named platform.
+    if (!targets || !targets.length) {
+        return Q.reject(new CordovaError('No platform specified. Please specify a platform to update. See "platform list".'));
+    } else if (targets.length > 1) {
+        return Q.reject(new CordovaError('Platform update can only be executed on one platform at a time.'));
+    } else {
+        var plat = targets[0];
+        var platformPath = path.join(projectRoot, 'platforms', plat);
+        var installed_platforms = cordova_util.listPlatforms(projectRoot);
+        if (installed_platforms.indexOf(plat) < 0) {
+            return Q.reject(new CordovaError('Platform "' + plat + '" is not installed. See "platform list".'));
+        }
+
+        function copyCordovaJs() {
+            var parser = new platforms[plat].parser(platformPath);
+            var platform_www = path.join(platformPath, 'platform_www');
+            shell.mkdir('-p', platform_www);
+            shell.cp('-f', path.join(parser.www_dir(), 'cordova.js'), path.join(platform_www, 'cordova.js'));
+        }
+
+        // First, lazy_load the latest version.
+        return hooks.fire('before_platform_update', opts)
+        .then(function() {
+            return lazy_load.based_on_config(projectRoot, plat);
+        }).then(function(libDir) {
+            // Call the platform's update script.
+            var script = path.join(libDir, 'bin', 'update');
+            return superspawn.spawn(script, [platformPath], { stdio: 'inherit' })
+            .then(function() {
+                // Copy the new cordova.js from www -> platform_www.
+                copyCordovaJs();
+                // Leave it to the update script to log out "updated to v FOO".
+            });
+        });
+    }
+}
+
+function check(hooks, projectRoot) {
+    var platformsText = [],
+        platforms_on_fs = cordova_util.listPlatforms(projectRoot),
+        scratch = path.join(os.tmpdir(), "cordova-platform-check-"+Date.now()),
+        listeners = events._events;
+        events._events = {};
+    var result = Q.defer();
+    cordova.raw.create(scratch)
+    .then(function () {
+        var h = new hooker(scratch);
+        // Acquire the version number of each platform we have installed, and output that too.
+        Q.all(platforms_on_fs.map(function(p) {
+            var d = Q.defer();
+            add(h, scratch, [p], {spawnoutput: {stdio: 'ignore'}})
+            .then(function() {
+                var d_avail = Q.defer(),
+                    d_cur = Q.defer();
+                getVersionFromScript(path.join(scratch, 'platforms', p, 'cordova', 'version'), null)
+                .then(function(avail) {
+                    if (!avail) {
+                        /* Platform version script was silent, we can't work with this */
+                        d_avail.resolve('');
+                    } else {
+                        d_avail.resolve(avail);
+                    }
+                })
+                .catch(function () {
+                    /* Platform version script failed, we can't work with this */
+                    d_avail.resolve('');
+                });
+                getVersionFromScript(path.join(projectRoot, 'platforms', p, 'cordova', 'version'), null)
+                .catch(function () {
+                    d_cur.resolve('broken');
+                }).then(function(v) {
+                    d_cur.resolve(v || '');
+                });
+                Q.all([d_avail.promise, d_cur.promise]).spread(function (avail, v) {
+                    var m;
+                    if (avail && (!v || v == 'broken' || semver.gt(avail, v))) {
+                        m = p + ' @ ' + (v || 'unknown') + ' could be updated to: ' + avail;
+                        platformsText.push(m);
+                    }
+                    d.resolve(m);
+                })
+                .catch(function () {
+                    return '?';
+                })
+                .done();
+            })
+            .catch(function () {
+                /* If a platform doesn't install, then we can't realistically suggest updating */
+                d.resolve();
+            });
+            return d.promise;
+        })).then(function() {
+            var results = '';
+            events._events = listeners;
+            shell.rm('-rf', scratch);
+            if (platformsText) {
+                results = platformsText.filter(function (p) {return !!p}).sort().join('\n');
+            }
+            if (!results) {
+                results = 'All platforms are up-to-date.';
+            }
+            events.emit('results', results);
+            result.resolve();
+        })
+        .done();
+    }).catch(function (){
+        events._events = listeners;
+        shell.rm('-rf', scratch);
+    })
+    .done();
+    return result.promise;
+}
+
+function list(hooks, projectRoot) {
+    var platforms_on_fs = cordova_util.listPlatforms(projectRoot);
+    return hooks.fire('before_platform_ls')
+    .then(function() {
+        // Acquire the version number of each platform we have installed, and output that too.
+        return Q.all(platforms_on_fs.map(function(p) {
+            return getVersionFromScript(path.join(projectRoot, 'platforms', p, 'cordova', 'version'), null)
+            .then(function(v) {
+                if (!v) return p;
+                return p + ' ' + v;
+            }, function(v) {
+                return p + ' broken';
+            });
+        }));
+    }).then(function(platformsText) {
+        var results = 'Installed platforms: ' + platformsText.sort().join(', ') + '\n';
+        var available = Object.getOwnPropertyNames(platforms).filter(function(p) {
+            var platform = platforms[p] || {},
+                hostos = platform.hostos || null;
+            if (!hostos)
+                return true;
+            if (hostos.indexOf('*') >= 0)
+                return true;
+            if (hostos.indexOf(process.platform) >= 0)
+                return true;
+            return false;
+        });
+
+        available = available.filter(function(p) {
+            return platforms_on_fs.indexOf(p) < 0; // Only those not already installed.
+        });
+        results += 'Available platforms: ' + available.sort().join(', ');
+
+        events.emit('results', results);
+    }).then(function() {
+        return hooks.fire('after_platform_ls');
+    });
+}
+
+// Returns a promise.
+module.exports = function platform(command, targets) {
+    var projectRoot = cordova_util.cdProjectRoot();
+
+    var hooks = new hooker(projectRoot);
+
+    if (arguments.length === 0) command = 'ls';
+    if (targets) {
+        if (!(targets instanceof Array)) targets = [targets];
+        var err;
+        targets.forEach(function(t) {
+            if (!(t in platforms)) {
+                err = new CordovaError('Platform "' + t + '" not recognized as a core cordova platform. See "platform list".');
+            }
+        });
+        if (err) return Q.reject(err);
+    } else {
+        if (command == 'add' || command == 'rm') {
+            return Q.reject(new CordovaError('You need to qualify `add` or `remove` with one or more platforms!'));
+        }
+    }
+
+    var opts = {
+        platforms:targets
+    };
+    switch(command) {
+        case 'add':
+            return add(hooks, projectRoot, targets, opts);
+        case 'rm':
+        case 'remove':
+            return remove(hooks, projectRoot, targets, opts);
+        case 'update':
+        case 'up':
+            return update(hooks, projectRoot, targets, opts);
+        case 'check':
+            return check(hooks, projectRoot);
+        case 'ls':
+        case 'list':
+        default:
+            return list(hooks, projectRoot);
+    }
+};
+
+/**
+ * Check Platform Support.
+ *
+ *   - {String} `name` of the platform to test.
+ *   - Returns a promise, which shows any errors.
+ *
+ */
+
+function supports(project_root, name) {
+    // required parameters
+    if (!name) return Q.reject(new CordovaError('requires a platform name parameter'));
+
+    // check if platform exists
+    var platform = platforms[name];
+    if (!platform) {
+        return Q.reject(new CordovaError(util.format('"%s" platform does not exist', name)));
+    }
+
+    // look up platform meta-data parser
+    var platformParser = platforms[name].parser;
+    if (!platformParser) {
+        return Q.reject(new Error(util.format('"%s" platform parser does not exist', name)));
+    }
+
+    // check for platform support
+    return platformParser.check_requirements(project_root);
+};
+
+// Expose the platform parsers on top of this command
+for (var p in platforms) {
+    module.exports[p] = platforms[p];
+}
+function createOverrides(projectRoot, target) {
+    shell.mkdir('-p', path.join(cordova_util.appDir(projectRoot), 'merges', target));
+};
+
+// Returns a promise.
+function call_into_create(target, projectRoot, cfg, libDir, template_dir, opts) {
+    var output = path.join(projectRoot, 'platforms', target);
+
+    // Check if output directory already exists.
+    if (fs.existsSync(output)) {
+        return Q.reject(new CordovaError('Platform ' + target + ' already added'));
+    } else {
+        // Make sure we have minimum requirements to work with specified platform
+        events.emit('verbose', 'Checking if platform "' + target + '" passes minimum requirements...');
+        /* XXX this is calling the public symbol so that Jasmine Spy can attack it */
+        return module.exports.supports(projectRoot, target)
+        .then(function() {
+            events.emit('log', 'Creating ' + target + ' project...');
+            var bin = path.join(libDir, 'bin', 'create');
+            var args = [];
+            if (target == 'android') {
+                var platformVersion = fs.readFileSync(path.join(libDir, 'VERSION'), 'UTF-8').trim();
+                if (semver.gt(platformVersion, '3.3.0')) {
+                    args.push('--cli');
+                }
+            } else if (target == 'ios') {
+                var platformVersion = fs.readFileSync(path.join(libDir, 'CordovaLib', 'VERSION'), 'UTF-8').trim();
+                args.push('--arc');
+                if (semver.gt(platformVersion, '3.3.0')) {
+                    args.push('--cli');
+                }
+            }
+
+            var pkg = cfg.packageName().replace(/[^\w.]/g,'_');
+            var name = cfg.name();
+            args.push(output, pkg, name);
+            if (template_dir) {
+                args.push(template_dir);
+            }
+            return superspawn.spawn(bin, args, opts || { stdio: 'inherit' })
+            .then(function() {
+                return require('../cordova').raw.prepare(target);
+            })
+            .then(function() {
+                createOverrides(projectRoot, target);
+                // Install all currently installed plugins into this new platform.
+                var plugins_dir = path.join(projectRoot, 'plugins');
+                var plugins = cordova_util.findPlugins(plugins_dir);
+                var parser = new platforms[target].parser(output);
+                if (!plugins) return Q();
+
+                var plugman = require('plugman');
+                // Install them serially.
+                return plugins.reduce(function(soFar, plugin) {
+                    return soFar.then(function() {
+                        events.emit('verbose', 'Installing plugin "' + plugin + '" following successful platform add of ' + target);
+                        return plugman.raw.install(target, output, path.basename(plugin), plugins_dir);
+                    });
+                }, Q());
+            });
+        });
+    }
+}
+
+module.exports.add = add;
+module.exports.remove = remove;
+module.exports.update = update;
+module.exports.check = check;
+module.exports.list = list;
+module.exports.supports = supports;

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/platforms.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/platforms.js b/cordova-lib/src/cordova/platforms.js
new file mode 100644
index 0000000..5bb82f1
--- /dev/null
+++ b/cordova-lib/src/cordova/platforms.js
@@ -0,0 +1,88 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+module.exports = {
+    'ios' : {
+        hostos : ['darwin'],
+        parser : './src/metadata/ios_parser',
+        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-ios.git',
+        version: '3.4.1'
+    }, 
+    'android' : {
+        parser : './src/metadata/android_parser',
+        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git',
+        version: '3.4.0'
+    }, 
+    'ubuntu' : {
+        hostos : ['linux'],
+        parser : './src/metadata/ubuntu_parser',
+        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-ubuntu.git',
+        version: '3.4.0'
+    }, 
+     'amazon-fireos' : {
+        parser : './src/metadata/amazon_fireos_parser',
+        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-amazon-fireos.git',
+        version: '3.4.0'
+    },
+    'wp7' : {
+        hostos : ['win32'],
+        parser : './src/metadata/wp7_parser',
+        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-wp8.git',
+        version: '3.4.0',
+        subdirectory: 'wp7'
+    },
+    'wp8' : {
+        hostos : ['win32'],
+        parser : './src/metadata/wp8_parser',
+        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-wp8.git',
+        version: '3.4.0',
+        subdirectory: 'wp8'
+    },
+    'blackberry10' : {
+        parser : './src/metadata/blackberry10_parser',
+        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-blackberry.git',
+        version: '3.4.0',
+        subdirectory: 'blackberry10'
+    },
+    'www':{
+        hostos : [],
+        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-app-hello-world.git',
+        version: '3.4.0'
+    },
+    'firefoxos':{
+        parser: './src/metadata/firefoxos_parser',
+        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-firefoxos.git',
+        version: '3.4.0'
+    },
+    'windows8':{
+        hostos : ['win32'],
+        parser: './src/metadata/windows8_parser',
+        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-windows.git',
+        version: '3.4.0',
+        subdirectory: 'windows8'
+    }
+};
+
+var addModuleProperty = require('./src/util').addModuleProperty;
+Object.keys(module.exports).forEach(function(key) {
+    var obj = module.exports[key];
+    if (obj.parser) {
+        addModuleProperty(module, 'parser', obj.parser, false, obj);
+    }
+});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/plugin.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/plugin.js b/cordova-lib/src/cordova/plugin.js
new file mode 100644
index 0000000..f0e01f7
--- /dev/null
+++ b/cordova-lib/src/cordova/plugin.js
@@ -0,0 +1,210 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+// Returns a promise.
+module.exports = function plugin(command, targets, opts) {
+    var cordova_util  = require('./util'),
+        path          = require('path'),
+        hooker        = require('./hooker'),
+        config        = require('./config'),
+        Q             = require('q'),
+        CordovaError  = require('./CordovaError'),
+        events        = require('./events');
+
+    var projectRoot = cordova_util.cdProjectRoot(),
+        err;
+
+    // Dance with all the possible call signatures we've come up over the time. They can be:
+    // 1. plugin() -> list the plugins
+    // 2. plugin(command, Array of targets, maybe opts object)
+    // 3. plugin(command, target1, target2, target3 ... )
+    // The targets are not really targets, they can be a mixture of plugins and options to be passed to plugman.
+
+    command = command || 'ls';
+    targets = targets || [];
+    opts = opts || {};
+    if ( opts.length ) {
+        // This is the case with multiple targes as separate arguments and opts is not opts but another target.
+        targets = Array.prototype.slice.call(arguments, 1);
+        opts = {};
+    }
+    if ( !Array.isArray(targets) ) {
+        // This means we had a single target given as string.
+        targets = [targets];
+    }
+    opts.options = [];
+    opts.plugins = [];
+
+    var hooks = new hooker(projectRoot);
+    var platformList = cordova_util.listPlatforms(projectRoot);
+
+    // Massage plugin name(s) / path(s)
+    var pluginPath, plugins;
+    pluginPath = path.join(projectRoot, 'plugins');
+    plugins = cordova_util.findPlugins(pluginPath);
+    if (!targets || !targets.length) {
+        if (command == 'add' || command == 'rm') {
+            return Q.reject(new CordovaError('You need to qualify `add` or `remove` with one or more plugins!'));
+        } else {
+            targets = [];
+        }
+    }
+
+    //Split targets between plugins and options
+    //Assume everything after a token with a '-' is an option
+    var i;
+    for (i = 0; i < targets.length; i++) {
+        if (targets[i].match(/^-/)) {
+            opts.options = targets.slice(i);
+            break;
+        } else {
+            opts.plugins.push(targets[i]);
+        }
+    }
+
+    switch(command) {
+        case 'add':
+            if (!targets || !targets.length) {
+                return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to add. See "plugin search".'));
+            }
+
+            var config_json = config(projectRoot, {});
+            var searchPath = config_json.plugin_search_path || [];
+            if (typeof opts.searchpath == 'string') {
+                searchPath = opts.searchpath.split(path.delimiter).concat(searchPath);
+            } else if (opts.searchpath) {
+                searchPath = opts.searchpath.concat(searchPath);
+            }
+            // Blank it out to appease unit tests.
+            if (searchPath.length === 0) {
+                searchPath = undefined;
+            }
+
+            return hooks.fire('before_plugin_add', opts)
+            .then(function() {
+                return opts.plugins.reduce(function(soFar, target) {
+                    var pluginsDir = path.join(projectRoot, 'plugins');
+                    return soFar.then(function() {
+                        if (target[target.length - 1] == path.sep) {
+                            target = target.substring(0, target.length - 1);
+                        }
+
+                        // Fetch the plugin first.
+                        events.emit('verbose', 'Calling plugman.fetch on plugin "' + target + '"');
+                        var plugman = require('plugman');
+                        return plugman.raw.fetch(target, pluginsDir, { searchpath: searchPath});
+                    })
+                    .then(function(dir) {
+                        // Iterate (in serial!) over all platforms in the project and install the plugin.
+                        return platformList.reduce(function(soFar, platform) {
+                            return soFar.then(function() {
+                                var platforms = require('../platforms');
+                                var platformRoot = path.join(projectRoot, 'platforms', platform),
+                                    parser = new platforms[platform].parser(platformRoot),
+                                    options = {
+                                        cli_variables: {},
+                                        searchpath: searchPath
+                                    },
+                                    tokens,
+                                    key,
+                                    i;
+                                //parse variables into cli_variables
+                                for (i=0; i< opts.options.length; i++) {
+                                    if (opts.options[i] === "--variable" && typeof opts.options[++i] === "string") {
+                                        tokens = opts.options[i].split('=');
+                                        key = tokens.shift().toUpperCase();
+                                        if (/^[\w-_]+$/.test(key)) {
+                                            options.cli_variables[key] = tokens.join('=');
+                                        }
+                                    }
+                                }
+
+                                events.emit('verbose', 'Calling plugman.install on plugin "' + dir + '" for platform "' + platform + '" with options "' + JSON.stringify(options)  + '"');
+                                return plugman.raw.install(platform, platformRoot, path.basename(dir), pluginsDir, options);
+                            });
+                        }, Q());
+                    });
+                }, Q()); // end Q.all
+            }).then(function() {
+                return hooks.fire('after_plugin_add', opts);
+            });
+            break;
+        case 'rm':
+        case 'remove':
+            if (!targets || !targets.length) {
+                return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to remove. See "plugin list".'));
+            }
+            return hooks.fire('before_plugin_rm', opts)
+            .then(function() {
+                return opts.plugins.reduce(function(soFar, target) {
+                    // Check if we have the plugin.
+                    if (plugins.indexOf(target) < 0) {
+                        return Q.reject(new CordovaError('Plugin "' + target + '" is not present in the project. See "plugin list".'));
+                    }
+
+                    var targetPath = path.join(pluginPath, target);
+                    // Iterate over all installed platforms and uninstall.
+                    // If this is a web-only or dependency-only plugin, then
+                    // there may be nothing to do here except remove the
+                    // reference from the platform's plugin config JSON.
+                    var plugman = require('plugman');
+                    return platformList.reduce(function(soFar, platform) {
+                        return soFar.then(function() {
+                            var platformRoot = path.join(projectRoot, 'platforms', platform);
+                            var platforms = require('../platforms');
+                            var parser = new platforms[platform].parser(platformRoot);
+                            events.emit('verbose', 'Calling plugman.uninstall on plugin "' + target + '" for platform "' + platform + '"');
+                            return plugman.raw.uninstall.uninstallPlatform(platform, platformRoot, target, path.join(projectRoot, 'plugins'));
+                        });
+                    }, Q())
+                    .then(function() {
+                        return plugman.raw.uninstall.uninstallPlugin(target, path.join(projectRoot, 'plugins'));
+                    });
+                }, Q());
+            }).then(function() {
+                return hooks.fire('after_plugin_rm', opts);
+            });
+            break;
+        case 'search':
+            return hooks.fire('before_plugin_search')
+            .then(function() {
+                var plugman = require('plugman');
+                return plugman.raw.search(opts.plugins);
+            }).then(function(plugins) {
+                for(var plugin in plugins) {
+                    events.emit('results', plugins[plugin].name, '-', plugins[plugin].description || 'no description provided');
+                }
+            }).then(function() {
+                return hooks.fire('after_plugin_search');
+            });
+            break;
+        case 'ls':
+        case 'list':
+        default:
+            return hooks.fire('before_plugin_ls')
+            .then(function() {
+                events.emit('results', (plugins.length ? plugins : 'No plugins added. Use `cordova plugin add <plugin>`.'));
+                return hooks.fire('after_plugin_ls')
+                .then(function() {
+                    return plugins;
+                });
+            });
+            break;
+    }
+};

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/plugin_parser.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/plugin_parser.js b/cordova-lib/src/cordova/plugin_parser.js
new file mode 100644
index 0000000..d3099c0
--- /dev/null
+++ b/cordova-lib/src/cordova/plugin_parser.js
@@ -0,0 +1,30 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var xml = require('./xml-helpers'),
+    fs  = require('fs');
+
+function plugin_parser(xmlPath) {
+    this.path = xmlPath;
+    this.doc = xml.parseElementtreeSync(xmlPath);
+    this.platforms = this.doc.findall('platform').map(function(p) {
+        return p.attrib.name;
+    });
+}
+
+module.exports = plugin_parser;

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/prepare.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/prepare.js b/cordova-lib/src/cordova/prepare.js
new file mode 100644
index 0000000..eef0123
--- /dev/null
+++ b/cordova-lib/src/cordova/prepare.js
@@ -0,0 +1,187 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var cordova_util      = require('./util'),
+    ConfigParser = require('./ConfigParser'),
+    path              = require('path'),
+    platforms         = require('../platforms'),
+    platform          = require('./platform'),
+    fs                = require('fs'),
+    shell             = require('shelljs'),
+    et                = require('elementtree'),
+    hooker            = require('./hooker'),
+    lazy_load         = require('./lazy_load'),
+    events            = require('./events'),
+    Q                 = require('q'),
+    plugman           = require('plugman'),
+    util              = require('util');
+
+// Returns a promise.
+exports = module.exports = function prepare(options) {
+    var projectRoot = cordova_util.cdProjectRoot();
+
+    if (!options) {
+        options = {
+            verbose: false,
+            platforms: [],
+            options: []
+        };
+    }
+
+    options = cordova_util.preProcessOptions(options);
+
+    var xml = cordova_util.projectConfig(projectRoot);
+    var paths = options.platforms.map(function(p) {
+        var platform_path = path.join(projectRoot, 'platforms', p);
+        var parser = (new platforms[p].parser(platform_path));
+        return parser.www_dir();
+    });
+    options.paths = paths;
+
+    var hooks = new hooker(projectRoot);
+    return hooks.fire('before_prepare', options)
+    .then(function() {
+        var cfg = new ConfigParser(xml);
+
+        // Iterate over each added platform
+        return Q.all(options.platforms.map(function(platform) {
+            var platformPath = path.join(projectRoot, 'platforms', platform);
+            return lazy_load.based_on_config(projectRoot, platform)
+            .then(function(libDir) {
+                var parser = new platforms[platform].parser(platformPath),
+                    defaults_xml_path = path.join(platformPath, "cordova", "defaults.xml");
+                //If defaults.xml is present, overwrite platform config.xml with it
+                //Otherwise save whatever is there as defaults so it can be restored
+                //or copy project config into platform if none exists
+                if (fs.existsSync(defaults_xml_path)) {
+                    shell.cp("-f", defaults_xml_path, parser.config_xml());
+                    events.emit('verbose', 'Generating config.xml from defaults for platform "' + platform + '"');
+                } else {
+                    if(fs.existsSync(parser.config_xml())){
+                        shell.cp("-f", parser.config_xml(), defaults_xml_path);
+                    }else{
+                        shell.cp("-f",xml,parser.config_xml());
+                    }
+                }
+
+                var stagingPath = path.join(platformPath, '.staging');
+                if (fs.existsSync(stagingPath)) {
+                    events.emit('log', 'Deleting now-obsolete intermediate directory: ' + stagingPath);
+                    shell.rm('-rf', stagingPath);
+                }
+
+                var platform_www = path.join(platformPath, 'platform_www');
+                // Create platfom_www if project was created with older version.
+                if (!fs.existsSync(platform_www)) {
+                    shell.mkdir(platform_www);
+                    shell.cp(parser.cordovajs_path(libDir), path.join(platform_www, 'cordova.js'));
+                }
+
+                // Replace the existing web assets with the app master versions
+                parser.update_www();
+
+                // Call plugman --prepare for this platform. sets up js-modules appropriately.
+                var plugins_dir = path.join(projectRoot, 'plugins');
+                events.emit('verbose', 'Calling plugman.prepare for platform "' + platform + '"');
+                plugman.prepare(platformPath, platform, plugins_dir);
+
+                // Make sure that config changes for each existing plugin is in place
+                var munger = new plugman.config_changes.PlatformMunger(platform, platformPath, plugins_dir);
+                munger.reapply_global_munge();
+                munger.save_all();
+
+                // Update platform config.xml based on top level config.xml
+                var platform_cfg = new ConfigParser(parser.config_xml());
+                exports._mergeXml(cfg.doc.getroot(), platform_cfg.doc.getroot(), platform, true);
+                platform_cfg.write();
+
+                return parser.update_project(cfg);
+            });
+        })).then(function() {
+            return hooks.fire('after_prepare', options);
+        });
+    });
+};
+
+var BLACKLIST = ["platform"];
+var SINGLETONS = ["content", "author"];
+function mergeXml(src, dest, platform, clobber) {
+    if (BLACKLIST.indexOf(src.tag) === -1) {
+        //Handle attributes
+        Object.getOwnPropertyNames(src.attrib).forEach(function (attribute) {
+            if (clobber || !dest.attrib[attribute]) {
+                dest.attrib[attribute] = src.attrib[attribute];
+            }
+        });
+        //Handle text
+        if (src.text && (clobber || !dest.text)) {
+            dest.text = src.text;
+        }
+        //Handle platform
+        if (platform) {
+            src.findall('platform[@name="' + platform + '"]').forEach(function (platformElement) {
+                platformElement.getchildren().forEach(mergeChild);
+            });
+        }
+
+        //Handle children
+        src.getchildren().forEach(mergeChild);
+
+        function mergeChild (srcChild) {
+            var srcTag = srcChild.tag,
+                destChild = new et.Element(srcTag),
+                foundChild,
+                query = srcTag + "",
+                shouldMerge = true;
+
+            if (BLACKLIST.indexOf(srcTag) === -1) {
+                if (SINGLETONS.indexOf(srcTag) !== -1) {
+                    foundChild = dest.find(query);
+                    if (foundChild) {
+                        destChild = foundChild;
+                        dest.remove(0, destChild);
+                    }
+                } else {
+                    //Check for an exact match and if you find one don't add
+                    Object.getOwnPropertyNames(srcChild.attrib).forEach(function (attribute) {
+                        query += "[@" + attribute + '="' + srcChild.attrib[attribute] + '"]';
+                    });
+                    foundChild = dest.find(query);
+                    if (foundChild && textMatch(srcChild, foundChild)) {
+                        destChild = foundChild;
+                        dest.remove(0, destChild);
+                        shouldMerge = false;
+                    }
+                }
+
+                mergeXml(srcChild, destChild, platform, clobber && shouldMerge);
+                dest.append(destChild);
+            }
+        }
+
+        function textMatch(elm1, elm2) {
+            var text1 = elm1.text ? elm1.text.replace(/\s+/, "") : "",
+                text2 = elm2.text ? elm2.text.replace(/\s+/, "") : "";
+            return (text1 === "" || text1 === text2);
+        }
+    }
+}
+
+// Expose for testing.
+exports._mergeXml = mergeXml;
+

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/run.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/run.js b/cordova-lib/src/cordova/run.js
new file mode 100644
index 0000000..5de3031
--- /dev/null
+++ b/cordova-lib/src/cordova/run.js
@@ -0,0 +1,48 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+/*global require: true, module: true, process: true*/
+/*jslint sloppy: true, white: true, newcap: true */
+
+var cordova_util      = require('./util'),
+    path              = require('path'),
+    hooker            = require('./hooker'),
+    superspawn        = require('./superspawn'),
+    Q                 = require('q');
+
+// Returns a promise.
+module.exports = function run(options) {
+    var projectRoot = cordova_util.cdProjectRoot(),
+    options = cordova_util.preProcessOptions(options);
+
+    var hooks = new hooker(projectRoot);
+    return hooks.fire('before_run', options)
+    .then(function() {
+        // Run a prepare first, then shell out to run
+        return require('../cordova').raw.prepare(options.platforms)
+    }).then(function() {
+        // Deploy in parallel (output gets intermixed though...)
+        return Q.all(options.platforms.map(function(platform) {
+            var cmd = path.join(projectRoot, 'platforms', platform, 'cordova', 'run');
+            return superspawn.spawn(cmd, options.options, { printCommand: true, stdio: 'inherit' });
+        }));
+    }).then(function() {
+        return hooks.fire('after_run', options);
+    });
+};

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/serve.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/serve.js b/cordova-lib/src/cordova/serve.js
new file mode 100644
index 0000000..bd17fa3
--- /dev/null
+++ b/cordova-lib/src/cordova/serve.js
@@ -0,0 +1,224 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var cordova_util = require('./util'),
+    crypto = require('crypto'),
+    path = require('path'),
+    shell = require('shelljs'),
+    platforms     = require('../platforms'),
+    ConfigParser = require('./ConfigParser'),
+    hooker        = require('./hooker'),
+    fs = require('fs'),
+    http = require("http"),
+    url = require("url"),
+    mime = require("mime"),
+    zlib = require("zlib");
+
+function launchServer(projectRoot, port) {
+    var server = http.createServer(function(request, response) {
+        function do404() {
+            console.log('404 ' + request.url);
+            response.writeHead(404, {"Content-Type": "text/plain"});
+            response.write("404 Not Found\n");
+            response.end();
+        }
+        function do302(where) {
+            console.log('302 ' + request.url);
+            response.setHeader("Location", where);
+            response.writeHead(302, {"Content-Type": "text/plain"});
+            response.end();
+        }
+        function doRoot() {
+            response.writeHead(200, {"Content-Type": "text/html"});
+            var config = new ConfigParser(cordova_util.projectConfig(projectRoot));
+            response.write("<html><head><title>"+config.name()+"</title></head><body>");
+            response.write("<table border cellspacing=0><thead><caption><h3>Package Metadata</h3></caption></thead><tbody>");
+            for (var c in {"name": true, "packageName": true, "version": true}) {
+                response.write("<tr><th>"+c+"</th><td>"+config[c]()+"</td></tr>");
+            }
+            response.write("</tbody></table>");
+            response.write("<h3>Platforms</h3><ul>");
+            var installed_platforms = cordova_util.listPlatforms(projectRoot);
+            for (var p in platforms) {
+                if (installed_platforms.indexOf(p) >= 0) {
+                    response.write("<li><a href='"+p+"/'>"+p+"</a></li>\n");
+                } else {
+                    response.write("<li><em>"+p+"</em></li>\n");
+                }
+            }
+            response.write("</ul>");
+            response.write("<h3>Plugins</h3><ul>");
+            var pluginPath = path.join(projectRoot, 'plugins');
+            var plugins = cordova_util.findPlugins(pluginPath);
+            for (var p in plugins) {
+                response.write("<li>"+plugins[p]+"</li>\n");
+            }
+            response.write("</ul>");
+            response.write("</body></html>");
+            response.end();
+        }
+        var urlPath = url.parse(request.url).pathname;
+        var firstSegment = /\/(.*?)\//.exec(urlPath);
+        if (!firstSegment) {
+            return doRoot();
+        }
+        var platformId = firstSegment[1];
+        if (!platforms[platformId]) {
+            return do404();
+        }
+        // Strip the platform out of the path.
+        urlPath = urlPath.slice(platformId.length + 1);
+
+        try {
+            var parser = new platforms[platformId].parser(path.join(projectRoot, 'platforms', platformId));
+        } catch (e) {
+            return do404();
+        }
+        var filePath = null;
+
+        if (urlPath == '/config.xml') {
+            filePath = parser.config_xml();
+        } else if (urlPath == '/project.json') {
+            processAddRequest(request, response, platformId, projectRoot);
+            return;
+        } else if (/^\/www\//.test(urlPath)) {
+            filePath = path.join(parser.www_dir(), urlPath.slice(5));
+        } else if (/^\/+[^\/]*$/.test(urlPath)) {
+            return do302("/" + platformId + "/www/");
+        } else {
+            return do404();
+        }
+
+        fs.exists(filePath, function(exists) {
+            if (exists) {
+                if (fs.statSync(filePath).isDirectory()) {
+                    index = path.join(filePath, "index.html");
+                    try {
+                        if (fs.statSync(index)) {
+                            filePath = index;
+                        }
+                    } catch (e) {}
+                }
+                if (fs.statSync(filePath).isDirectory()) {
+                    if (!/\/$/.test(urlPath)) {
+                        return do302("/" + platformId + urlPath + "/");
+                    }
+                    console.log('200 ' + request.url);
+                    response.writeHead(200, {"Content-Type": "text/html"});
+                    response.write("<html><head><title>Directory listing of "+ urlPath + "</title></head>");
+                    response.write("<h3>Items in this directory</h3>");
+                    var items = fs.readdirSync(filePath);
+                    response.write("<ul>");
+                    for (var i in items) {
+                        var file = items[i];
+                        if (file) {
+                            response.write('<li><a href="'+file+'">'+file+'</a></li>\n');
+                        }
+                    }
+                    response.write("</ul>");
+                    response.end();
+                } else {
+                    var mimeType = mime.lookup(filePath);
+                    var respHeaders = {
+                      'Content-Type': mimeType
+                    };
+                    var readStream = fs.createReadStream(filePath);
+
+                    var acceptEncoding = request.headers['accept-encoding'] || '';
+                    if (acceptEncoding.match(/\bgzip\b/)) {
+                        respHeaders['content-encoding'] = 'gzip';
+                        readStream = readStream.pipe(zlib.createGzip());
+                    } else if (acceptEncoding.match(/\bdeflate\b/)) {
+                        respHeaders['content-encoding'] = 'deflate';
+                        readStream = readStream.pipe(zlib.createDeflate());
+                    }
+                    console.log('200 ' + request.url);
+                    response.writeHead(200, respHeaders);
+                    readStream.pipe(response);
+                }
+            } else {
+                return do404();
+            }
+        });
+    }).on('listening', function () {
+        console.log("Static file server running on port " + port + " (i.e. http://localhost:" + port + ")\nCTRL + C to shut down");
+    }).on('error', function (e) {
+        if (e && e.toString().indexOf('EADDRINUSE') !== -1) {
+            port++;
+            server.listen(port);
+        } else {
+            console.log("An error occured starting static file server: " + e);
+        }
+    }).listen(port);
+    return server;
+}
+
+function calculateMd5(fileName) {
+    var BUF_LENGTH = 64*1024,
+        buf = new Buffer(BUF_LENGTH),
+        bytesRead = BUF_LENGTH,
+        pos = 0,
+        fdr = fs.openSync(fileName, 'r');
+
+    try {
+        var md5sum = crypto.createHash('md5');
+        while (bytesRead === BUF_LENGTH) {
+            bytesRead = fs.readSync(fdr, buf, 0, BUF_LENGTH, pos);
+            pos += bytesRead;
+            md5sum.update(buf.slice(0, bytesRead));
+        }
+    } finally {
+        fs.closeSync(fdr);
+    }
+    return md5sum.digest('hex');
+}
+
+function processAddRequest(request, response, platformId, projectRoot) {
+    var parser = new platforms[platformId].parser(path.join(projectRoot, 'platforms', platformId));
+    var wwwDir = parser.www_dir();
+    var payload = {
+        'configPath': '/' + platformId + '/config.xml',
+        'wwwPath': '/' + platformId + '/www',
+        'wwwFileList': shell.find(wwwDir)
+            .filter(function(a) { return !fs.statSync(a).isDirectory() && !/(^\.)|(\/\.)/.test(a) })
+            .map(function(a) { return {'path': a.slice(wwwDir.length), 'etag': '' + calculateMd5(a)}; })
+    };
+    console.log('200 ' + request.url);
+    response.writeHead(200, {
+        'Content-Type': 'application/json',
+        'Cache-Control': 'no-cache'
+    });
+    response.write(JSON.stringify(payload));
+    response.end();
+}
+
+module.exports = function server(port) {
+    var projectRoot = cordova_util.cdProjectRoot();
+    port = +port || 8000;
+
+    var hooks = new hooker(projectRoot);
+    return hooks.fire('before_serve')
+    .then(function() {
+        // Run a prepare first!
+        return require('../cordova').raw.prepare([]);
+    }).then(function() {
+        launchServer(projectRoot, port);
+        return hooks.fire('after_serve');
+    });
+};
+

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/superspawn.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/superspawn.js b/cordova-lib/src/cordova/superspawn.js
new file mode 100644
index 0000000..60c9fdc
--- /dev/null
+++ b/cordova-lib/src/cordova/superspawn.js
@@ -0,0 +1,139 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+var child_process = require('child_process');
+var fs = require('fs');
+var path = require('path');
+var _ = require('underscore');
+var Q = require('q');
+var shell = require('shelljs');
+var events = require('./events');
+var iswin32 = process.platform == 'win32';
+
+// On Windows, spawn() for batch files requires absolute path & having the extension.
+function resolveWindowsExe(cmd) {
+    var winExtensions = ['.exe', '.cmd', '.bat', '.js', '.vbs'];
+    function isValidExe(c) {
+        return winExtensions.indexOf(path.extname(c)) !== -1 && fs.existsSync(c);
+    }
+    if (isValidExe(cmd)) {
+        return cmd;
+    }
+    cmd = shell.which(cmd) || cmd;
+    if (!isValidExe(cmd)) {
+        winExtensions.some(function(ext) {
+            if (fs.existsSync(cmd + ext)) {
+                cmd = cmd + ext;
+                return true;
+            }
+        });
+    }
+    return cmd;
+}
+
+function maybeQuote(a) {
+    if (a.indexOf(' ') != -1) {
+        a = '"' + a + '"';
+    }
+    return a;
+}
+
+// opts:
+//   printCommand: Whether to log the command (default: false)
+//   stdio: 'default' is to capture output and returning it as a string to success (same as exec)
+//          'ignore' means don't bother capturing it
+//          'inherit' means pipe the input & output. This is required for anything that prompts.
+//   env: Map of extra environment variables.
+//   cwd: Working directory for the command.
+// Returns a promise that succeeds only for return code = 0.
+exports.spawn = function(cmd, args, opts) {
+    args = args || [];
+    opts = opts || {};
+    var spawnOpts = {};
+    var d = Q.defer();
+
+    if (iswin32) {
+        cmd = resolveWindowsExe(cmd);
+        // If we couldn't find the file, likely we'll end up failing,
+        // but for things like "del", cmd will do the trick.
+        if (path.extname(cmd) != '.exe' && cmd.indexOf(' ') != -1) {
+            // We need to use /s to ensure that spaces are parsed properly with cmd spawned content
+            args = [['/s', '/c', '"'+[cmd].concat(args).map(function(a){if (/^[^"].* .*[^"]/.test(a)) return '"'+a+'"'; return a;}).join(" ")+'"'].join(" ")];
+            cmd = 'cmd';
+            spawnOpts.windowsVerbatimArguments = true;
+        } else if (!fs.existsSync(cmd)) {
+            // We need to use /s to ensure that spaces are parsed properly with cmd spawned content
+            args = ['/s', '/c', cmd].concat(args);
+        }
+    }
+
+    if (opts.stdio == 'ignore') {
+        spawnOpts.stdio = 'ignore';
+    } else if (opts.stdio == 'inherit') {
+        spawnOpts.stdio = 'inherit';
+    }
+    if (opts.cwd) {
+        spawnOpts.cwd = opts.cwd;
+    }
+    if (opts.env) {
+        spawnOpts.env = _.extend(_.extend({}, process.env), opts.env);
+    }
+
+    events.emit(opts.printCommand ? 'log' : 'verbose', 'Running command: ' + maybeQuote(cmd) + ' ' + args.map(maybeQuote).join(' '));
+
+    var child = child_process.spawn(cmd, args, spawnOpts);
+    var capturedOut = '';
+    var capturedErr = '';
+
+    if (child.stdout) {
+        child.stdout.setEncoding('utf8');
+        child.stdout.on('data', function(data) {
+            capturedOut += data;
+        });
+
+        child.stderr.setEncoding('utf8');
+        child.stderr.on('data', function(data) {
+            capturedErr += data;
+        });
+    }
+
+    child.on('close', whenDone);
+    child.on('error', whenDone);
+    function whenDone(arg) {
+        child.removeListener('close', whenDone);
+        child.removeListener('error', whenDone);
+        var code = typeof arg == 'number' ? arg : arg && arg.code;
+
+        events.emit('verbose', 'Command finished with error code ' + code + ': ' + cmd + ' ' + args);
+        if (code === 0) {
+            d.resolve(capturedOut.trim());
+        } else {
+            var errMsg = cmd + ': Command failed with exit code ' + code;
+            if (capturedErr) {
+                errMsg += ' Error output:\n' + capturedErr.trim();
+            }
+            var err = new Error(errMsg);
+            err.code = code;
+            d.reject(err);
+        }
+    }
+
+    return d.promise;
+};
+

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/util.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/util.js b/cordova-lib/src/cordova/util.js
new file mode 100644
index 0000000..2d3b12a
--- /dev/null
+++ b/cordova-lib/src/cordova/util.js
@@ -0,0 +1,214 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var fs            = require('fs'),
+    path          = require('path'),
+    CordovaError  = require('./CordovaError'),
+    shell         = require('shelljs');
+
+// Global configuration paths
+var HOME = process.env[(process.platform.slice(0, 3) == 'win') ? 'USERPROFILE' : 'HOME'];
+var global_config_path = path.join(HOME, '.cordova');
+var lib_path = path.join(global_config_path, 'lib');
+shell.mkdir('-p', lib_path);
+
+function isRootDir(dir) {
+    if (fs.existsSync(path.join(dir, 'www'))) {
+        if (fs.existsSync(path.join(dir, 'config.xml'))) {
+            // For sure is.
+            if (fs.existsSync(path.join(dir, 'platforms'))) {
+                return 2;
+            } else {
+                return 1;
+            }
+        }
+        // Might be (or may be under platforms/).
+        if (fs.existsSync(path.join(dir, 'www', 'config.xml'))) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+exports = module.exports = {
+    globalConfig:global_config_path,
+    libDirectory:lib_path,
+    // Runs up the directory chain looking for a .cordova directory.
+    // IF it is found we are in a Cordova project.
+    // Omit argument to use CWD.
+    isCordova: function isCordova(dir) {
+        if (!dir) {
+            // Prefer PWD over cwd so that symlinked dirs within your PWD work correctly (CB-5687).
+            var pwd = process.env.PWD;
+            var cwd = process.cwd();
+            if (pwd && pwd != cwd) {
+                return this.isCordova(pwd) || this.isCordova(cwd);
+            }
+            return this.isCordova(cwd);
+        }
+        var bestReturnValueSoFar = false;
+        for (var i = 0; i < 1000; ++i) {
+            var result = isRootDir(dir);
+            if (result === 2) {
+                return dir;
+            }
+            if (result === 1) {
+                bestReturnValueSoFar = dir;
+            }
+            var parentDir = path.normalize(path.join(dir, '..'));
+            // Detect fs root.
+            if (parentDir == dir) {
+                return bestReturnValueSoFar;
+            }
+            dir = parentDir;
+        }
+        console.error('Hit an unhandled case in util.isCordova');
+        return false;
+    },
+    // Cd to project root dir and return its path. Throw CordovaError if not in a Corodva project.
+    cdProjectRoot: function() {
+        var projectRoot = this.isCordova();
+        if (!projectRoot) {
+            throw new CordovaError('Current working directory is not a Cordova-based project.');
+        }
+        process.chdir(projectRoot);
+        return projectRoot;
+    },
+    // Recursively deletes .svn folders from a target path
+    deleteSvnFolders:function(dir) {
+        var contents = fs.readdirSync(dir);
+        contents.forEach(function(entry) {
+            var fullpath = path.join(dir, entry);
+            if (fs.statSync(fullpath).isDirectory()) {
+                if (entry == '.svn') {
+                    shell.rm('-rf', fullpath);
+                } else module.exports.deleteSvnFolders(fullpath);
+            }
+        });
+    },
+    listPlatforms:function(project_dir) {
+        var core_platforms = require('../platforms');
+        var platforms_dir = path.join(project_dir, 'platforms');
+        if ( !fs.existsSync(platforms_dir)) {
+            return [];
+        }
+        var subdirs = fs.readdirSync(platforms_dir);
+        return subdirs.filter(function(p) {
+            return Object.keys(core_platforms).indexOf(p) > -1;
+        });
+    },
+    // list the directories in the path, ignoring any files
+    findPlugins:function(pluginPath) {
+        var plugins = [],
+            stats;
+
+        if (fs.existsSync(pluginPath)) {
+            plugins = fs.readdirSync(pluginPath).filter(function (fileName) {
+               stats = fs.statSync(path.join(pluginPath, fileName));
+               return fileName != '.svn' && fileName != 'CVS' && stats.isDirectory();
+            });
+        }
+
+        return plugins;
+    },
+    appDir: function(projectDir) {
+        return projectDir;
+    },
+    projectWww: function(projectDir) {
+        return path.join(projectDir, 'www');
+    },
+    projectConfig: function(projectDir) {
+        var rootPath = path.join(projectDir, 'config.xml');
+        var wwwPath = path.join(projectDir, 'www', 'config.xml');
+        if (fs.existsSync(rootPath)) {
+            return rootPath;
+        } else if (fs.existsSync(wwwPath)) {
+            return wwwPath;
+        }
+        return rootPath;
+    },
+    preProcessOptions: function (inputOptions) {
+        /**
+         * Current Desired Arguments
+         * options: {verbose: boolean, platforms: [String], options: [String]}
+         * Accepted Arguments
+         * platformList: [String] -- assume just a list of platforms
+         * platform: String -- assume this is a platform
+         */
+        var result = inputOptions || {};
+        if (Array.isArray(inputOptions)) {
+            result = { platforms: inputOptions };
+        } else if (typeof inputOptions === 'string') {
+            result = { platforms: [inputOptions] };
+        }
+        result.verbose = result.verbose || false;
+        result.platforms = result.platforms || [];
+        result.options = result.options || [];
+
+        var projectRoot = this.isCordova();
+
+        if (!projectRoot) {
+            throw new CordovaError('Current working directory is not a Cordova-based project.');
+        }
+        var projectPlatforms = this.listPlatforms(projectRoot);
+        if (projectPlatforms.length === 0) {
+            throw new CordovaError('No platforms added to this project. Please use `cordova platform add <platform>`.');
+        }
+        if (result.platforms.length === 0) {
+            result.platforms = projectPlatforms;
+        }
+
+        return result;
+    }
+};
+
+// opt_wrap is a boolean: True means that a callback-based wrapper for the promise-based function
+// should be created.
+function addModuleProperty(module, symbol, modulePath, opt_wrap, opt_obj) {
+    var val = null;
+    if (opt_wrap) {
+        module.exports[symbol] = function() {
+            val = val || module.require(modulePath);
+            if (arguments.length && typeof arguments[arguments.length - 1] === 'function') {
+                // If args exist and the last one is a function, it's the callback.
+                var args = Array.prototype.slice.call(arguments);
+                var cb = args.pop();
+                val.apply(module.exports, args).done(function(result) {cb(undefined, result)}, cb);
+            } else {
+                val.apply(module.exports, arguments).done(null, function(err) { throw err; });
+            }
+        };
+    } else {
+        Object.defineProperty(opt_obj || module.exports, symbol, {
+            get : function() { return val = val || module.require(modulePath); },
+            set : function(v) { val = v; }
+        });
+    }
+
+    // Add the module.raw.foo as well.
+    if(module.exports.raw) {
+        Object.defineProperty(module.exports.raw, symbol, {
+            get : function() { return val = val || module.require(modulePath); },
+            set : function(v) { val = v; }
+        });
+    }
+}
+
+addModuleProperty(module, 'plugin_parser', './plugin_parser');
+
+exports.addModuleProperty = addModuleProperty;

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/src/cordova/xml-helpers.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/xml-helpers.js b/cordova-lib/src/cordova/xml-helpers.js
new file mode 100644
index 0000000..70ec96b
--- /dev/null
+++ b/cordova-lib/src/cordova/xml-helpers.js
@@ -0,0 +1,171 @@
+/*
+ *
+ * Copyright 2013 Anis Kadri
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * contains XML utility functions, some of which are specific to elementtree
+ */
+
+var fs = require('fs')
+  , path = require('path')
+  , et = require('elementtree');
+
+module.exports = {
+    // Returns a promise.
+    moveProjFile: function(origFile, projPath) {
+        var src = path.resolve(projPath, origFile)
+          , dest = src.replace('.orig', '');
+
+        var d = Q.defer();
+        fs.createReadStream(src)
+            .pipe(fs.createWriteStream(dest))
+            .on('close', d.resolve)
+            .on('error', d.reject);
+        return d.promise;
+    },
+
+    // compare two et.XML nodes, see if they match
+    // compares tagName, text, attributes and children (recursively)
+    equalNodes: function(one, two) {
+        if (one.tag != two.tag) {
+            return false;
+        } else if (one.text.trim() != two.text.trim()) {
+            return false;
+        } else if (one._children.length != two._children.length) {
+            return false;
+        }
+
+        var oneAttribKeys = Object.keys(one.attrib),
+            twoAttribKeys = Object.keys(two.attrib),
+            i = 0, attribName;
+
+        if (oneAttribKeys.length != twoAttribKeys.length) {
+            return false;
+        }
+
+        for (i; i < oneAttribKeys.length; i++) {
+            attribName = oneAttribKeys[i];
+
+            if (one.attrib[attribName] != two.attrib[attribName]) {
+                return false;
+            }
+        }
+
+        for (i; i < one._children.length; i++) {
+            if (!module.exports.equalNodes(one._children[i], two._children[i])) {
+                return false;
+            }
+        }
+
+        return true;
+    },
+
+    // adds node to doc at selector
+    graftXML: function(doc, nodes, selector) {
+        var parent = resolveParent(doc, selector);
+        if (!parent) return false;
+
+        nodes.forEach(function (node) {
+            // check if child is unique first
+            if (uniqueChild(node, parent)) {
+                parent.append(node);
+            }
+        });
+
+        return true;
+    },
+
+    // removes node from doc at selector
+    pruneXML: function(doc, nodes, selector) {
+        var parent = resolveParent(doc, selector);
+        if (!parent) return false;
+
+        nodes.forEach(function (node) {
+            var matchingKid = null;
+            if ((matchingKid = findChild(node, parent)) != null) {
+                // stupid elementtree takes an index argument it doesn't use
+                // and does not conform to the python lib
+                parent.remove(0, matchingKid);
+            }
+        });
+
+        return true;
+    },
+
+    parseElementtreeSync: function (filename) {
+        var contents = fs.readFileSync(filename, 'utf-8');
+        if(contents) {
+            contents = contents.substring(contents.indexOf("<")); //Windows is the BOM
+        }
+        return new et.ElementTree(et.XML(contents));
+    }
+};
+
+function findChild(node, parent) {
+    var matchingKids = parent.findall(node.tag)
+      , i, j;
+
+    for (i = 0, j = matchingKids.length ; i < j ; i++) {
+        if (module.exports.equalNodes(node, matchingKids[i])) {
+            return matchingKids[i];
+        }
+    }
+    return null;
+}
+
+function uniqueChild(node, parent) {
+    var matchingKids = parent.findall(node.tag)
+      , i = 0;
+
+    if (matchingKids.length == 0) {
+        return true;
+    } else  {
+        for (i; i < matchingKids.length; i++) {
+            if (module.exports.equalNodes(node, matchingKids[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+
+var ROOT = /^\/([^\/]*)/,
+    ABSOLUTE = /^\/([^\/]*)\/(.*)/;
+function resolveParent(doc, selector) {
+    var parent, tagName, subSelector;
+
+    // handle absolute selector (which elementtree doesn't like)
+    if (ROOT.test(selector)) {
+        tagName = selector.match(ROOT)[1];
+        // test for wildcard "any-tag" root selector
+        if (tagName == '*' || tagName === doc._root.tag) {
+            parent = doc._root;
+
+            // could be an absolute path, but not selecting the root
+            if (ABSOLUTE.test(selector)) {
+                subSelector = selector.match(ABSOLUTE)[2];
+                parent = parent.find(subSelector)
+            }
+        } else {
+            return false;
+        }
+    } else {
+        parent = doc.find(selector)
+    }
+    return parent;
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b51e1c12/cordova-lib/templates/config.xml
----------------------------------------------------------------------
diff --git a/cordova-lib/templates/config.xml b/cordova-lib/templates/config.xml
new file mode 100644
index 0000000..4a9a4d8
--- /dev/null
+++ b/cordova-lib/templates/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns     = "http://www.w3.org/ns/widgets"
+        xmlns:cdv = "http://cordova.apache.org/ns/1.0"
+        id        = "io.cordova.hellocordova"
+        version   = "0.0.1">
+    <name>Hello Cordova</name>
+
+    <description>
+        A sample Apache Cordova application that responds to the deviceready event.
+    </description>
+
+    <author href="http://cordova.io" email="dev@cordova.apache.org">
+        Apache Cordova Team
+    </author>
+
+    <content src="index.html" />
+
+    <access origin="*" />
+</widget>