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/01 20:32:09 UTC

[24/53] [abbrv] Split out cordova-lib: move cordova-plugman files

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/src/plugman/util/config-changes.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/config-changes.js b/cordova-lib/src/plugman/util/config-changes.js
new file mode 100644
index 0000000..67adfb6
--- /dev/null
+++ b/cordova-lib/src/plugman/util/config-changes.js
@@ -0,0 +1,812 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+/*
+ * This module deals with shared configuration / dependency "stuff". That is:
+ * - XML configuration files such as config.xml, AndroidManifest.xml or WMAppManifest.xml.
+ * - plist files in iOS
+ * - pbxproj files in iOS
+ * Essentially, any type of shared resources that we need to handle with awareness
+ * of how potentially multiple plugins depend on a single shared resource, should be
+ * handled in this module.
+ *
+ * The implementation uses an object as a hash table, with "leaves" of the table tracking
+ * reference counts.
+ */
+
+/* jshint node:true, sub:true, unused:true, indent:4  */
+
+var fs   = require('fs'),
+    path = require('path'),
+    glob = require('glob'),
+    plist = require('plist-with-patches'),
+    bplist = require('bplist-parser'),
+    xcode = require('xcode'),
+    et   = require('elementtree'),
+    _ = require('underscore'),
+    xml_helpers = require('./../util/xml-helpers'),
+    platforms = require('./../platforms'),
+    events = require('./../events'),
+    plist_helpers = require('./../util/plist-helpers');
+
+
+// These frameworks are required by cordova-ios by default. We should never add/remove them.
+var keep_these_frameworks = [
+    'MobileCoreServices.framework',
+    'CoreGraphics.framework',
+    'CoreLocation.framework',
+    'AssetsLibrary.framework'
+];
+
+
+exports.PlatformMunger = PlatformMunger;
+
+/******************************************************************************
+Adapters to keep the current refactoring effort to within this file
+******************************************************************************/
+exports.add_plugin_changes = function(platform, project_dir, plugins_dir, plugin_id, plugin_vars, is_top_level, should_increment, cache) {
+    var munger = new PlatformMunger(platform, project_dir, plugins_dir);
+    munger.add_plugin_changes(plugin_id, plugin_vars, is_top_level, should_increment, cache);
+    munger.save_all();
+};
+
+exports.remove_plugin_changes = function(platform, project_dir, plugins_dir, plugin_name, plugin_id, is_top_level, should_decrement) {
+    // TODO: should_decrement parameter is never used, remove it here and wherever called
+    var munger = new PlatformMunger(platform, project_dir, plugins_dir);
+    munger.remove_plugin_changes(plugin_name, plugin_id, is_top_level);
+    munger.save_all();
+};
+
+exports.process = function(plugins_dir, project_dir, platform) {
+    var munger = new PlatformMunger(platform, project_dir, plugins_dir);
+    munger.process();
+    munger.save_all();
+};
+
+exports.get_munge_change = function(munge, keys) {
+    return deep_find.apply(null, arguments);
+}
+
+/******************************************************************************/
+
+
+exports.add_installed_plugin_to_prepare_queue = add_installed_plugin_to_prepare_queue;
+function add_installed_plugin_to_prepare_queue(plugins_dir, plugin, platform, vars, is_top_level) {
+    checkPlatform(platform);
+    var config = exports.get_platform_json(plugins_dir, platform);
+    config.prepare_queue.installed.push({'plugin':plugin, 'vars':vars, 'topLevel':is_top_level});
+    exports.save_platform_json(config, plugins_dir, platform);
+}
+
+exports.add_uninstalled_plugin_to_prepare_queue = add_uninstalled_plugin_to_prepare_queue;
+function add_uninstalled_plugin_to_prepare_queue(plugins_dir, plugin, platform, is_top_level) {
+    checkPlatform(platform);
+
+    var plugin_xml = xml_helpers.parseElementtreeSync(path.join(plugins_dir, plugin, 'plugin.xml'));
+    var config = exports.get_platform_json(plugins_dir, platform);
+    config.prepare_queue.uninstalled.push({'plugin':plugin, 'id':plugin_xml.getroot().attrib['id'], 'topLevel':is_top_level});
+    exports.save_platform_json(config, plugins_dir, platform);
+}
+
+
+/******************************************************************************
+* PlatformMunger class
+*
+* Can deal with config file of a single project.
+* Parsed config files are cached in a ConfigKeeper object.
+******************************************************************************/
+function PlatformMunger(platform, project_dir, plugins_dir) {
+    checkPlatform(platform);
+    this.platform = platform;
+    this.project_dir = project_dir;
+    this.plugins_dir = plugins_dir;
+    this.platform_handler = platforms[platform];
+    this.config_keeper = new ConfigKeeper();
+}
+
+// Write out all unsaved files.
+PlatformMunger.prototype.save_all = PlatformMunger_save_all;
+function PlatformMunger_save_all() {
+    this.config_keeper.save_all();
+}
+
+// Apply a munge object to a single config file.
+// The remove parameter tells whether to add the change or remove it.
+PlatformMunger.prototype.apply_file_munge = PlatformMunger_apply_file_munge;
+function PlatformMunger_apply_file_munge(file, munge, remove) {
+    var self = this;
+    var xml_child;
+
+    if ( file === 'framework' && self.platform === 'ios' ) {
+        // ios pbxproj file
+        var pbxproj = self.config_keeper.get(self.project_dir, self.platform, 'framework');
+        for (var src in munge.parents) {
+            for (xml_child in munge.parents[src]) {
+                var xml = munge.parents[src][xml_child].xml;
+                // Only add the framework if it's not a cordova-ios core framework
+                if (keep_these_frameworks.indexOf(src) == -1) {
+                    // xml_child in this case is whether the framework should use weak or not
+                    if (remove) {
+                        pbxproj.data.removeFramework(src);
+                    } else {
+                        pbxproj.data.addFramework(src, {weak: (xml === 'true')});
+                    }
+                    pbxproj.is_changed = true;
+                }
+            }
+        }
+    } else {
+        // all other types of files
+        for (var selector in munge.parents) {
+            for (xml_child in munge.parents[selector]) {
+                // this xml child is new, graft it (only if config file exists)
+                var config_file = self.config_keeper.get(self.project_dir, self.platform, file);
+                if (config_file.exists) {
+                    if (remove) config_file.prune_child(selector, munge.parents[selector][xml_child]);
+                    else config_file.graft_child(selector, munge.parents[selector][xml_child]);
+                }
+            }
+        }
+    }
+}
+
+
+PlatformMunger.prototype.remove_plugin_changes = remove_plugin_changes;
+function remove_plugin_changes(plugin_name, plugin_id, is_top_level) {
+    var self = this;
+    var platform_config = exports.get_platform_json(self.plugins_dir, self.platform);
+    var plugin_dir = path.join(self.plugins_dir, plugin_name);
+    var plugin_vars = (is_top_level ? platform_config.installed_plugins[plugin_id] : platform_config.dependent_plugins[plugin_id]);
+
+    // get config munge, aka how did this plugin change various config files
+    var config_munge = self.generate_plugin_config_munge(plugin_dir, plugin_vars);
+    // global munge looks at all plugins' changes to config files
+    var global_munge = platform_config.config_munge;
+    var munge = decrement_munge(global_munge, config_munge);
+
+    for (var file in munge.files) {
+        if (file == 'plugins-plist' && self.platform == 'ios') {
+            // TODO: remove this check and <plugins-plist> sections in spec/plugins/../plugin.xml files.
+            events.emit(
+                'warn',
+                'WARNING: Plugin "' + plugin_id + '" uses <plugins-plist> element(s), ' +
+                'which are no longer supported. Support has been removed as of Cordova 3.4.'
+            );
+            continue;
+        }
+        self.apply_file_munge(file, munge.files[file], /* remove = */ true);
+    }
+
+    // Remove from installed_plugins
+    if (is_top_level) {
+        delete platform_config.installed_plugins[plugin_id];
+    } else {
+        delete platform_config.dependent_plugins[plugin_id];
+    }
+
+    // save
+    exports.save_platform_json(platform_config, self.plugins_dir, self.platform);
+}
+
+
+PlatformMunger.prototype.add_plugin_changes = add_plugin_changes;
+function add_plugin_changes(plugin_id, plugin_vars, is_top_level, should_increment) {
+    var self = this;
+    var platform_config = exports.get_platform_json(self.plugins_dir, self.platform);
+    var plugin_dir = path.join(self.plugins_dir, plugin_id);
+
+    var plugin_config = self.config_keeper.get(plugin_dir, '', 'plugin.xml');
+    plugin_id = plugin_config.data.getroot().attrib.id;
+
+    // get config munge, aka how should this plugin change various config files
+    var config_munge = self.generate_plugin_config_munge(plugin_dir, plugin_vars);
+    // global munge looks at all plugins' changes to config files
+
+    // TODO: The should_increment param is only used by cordova-cli and is going away soon.
+    // If should_increment is set to false, avoid modifying the global_munge (use clone)
+    // and apply the entire config_munge because it's already a proper subset of the global_munge.
+    var munge, global_munge;
+    if (should_increment) {
+        global_munge = platform_config.config_munge;
+        munge = increment_munge(global_munge, config_munge);
+    } else {
+        global_munge = clone_munge(platform_config.config_munge);
+        munge = config_munge;
+    }
+
+    for (var file in munge.files) {
+        // TODO: remove this warning some time after 3.4 is out.
+        if (file == 'plugins-plist' && self.platform == 'ios') {
+            events.emit(
+                'warn',
+                'WARNING: Plugin "' + plugin_id + '" uses <plugins-plist> element(s), ' +
+                'which are no longer supported. Support has been removed as of Cordova 3.4.'
+            );
+            continue;
+        }
+        self.apply_file_munge(file, munge.files[file]);
+    }
+
+    // Move to installed_plugins if it is a top-level plugin
+    if (is_top_level) {
+        platform_config.installed_plugins[plugin_id] = plugin_vars || {};
+    } else {
+        platform_config.dependent_plugins[plugin_id] = plugin_vars || {};
+    }
+
+    // save
+    exports.save_platform_json(platform_config, self.plugins_dir, self.platform);
+}
+
+
+// Load the global munge from platform json and apply all of it.
+// Used by cordova prepare to re-generate some config file from platform
+// defaults and the global munge.
+PlatformMunger.prototype.reapply_global_munge = reapply_global_munge ;
+function reapply_global_munge () {
+    var self = this;
+
+    var platform_config = exports.get_platform_json(self.plugins_dir, self.platform);
+    var global_munge = platform_config.config_munge;
+    for (var file in global_munge.files) {
+        // TODO: remove this warning some time after 3.4 is out.
+        if (file == 'plugins-plist' && self.platform == 'ios') {
+            events.emit(
+                'warn',
+                'WARNING: One of your plugins uses <plugins-plist> element(s), ' +
+                'which are no longer supported. Support has been removed as of Cordova 3.4.'
+            );
+            continue;
+        }
+
+        self.apply_file_munge(file, global_munge.files[file]);
+    }
+}
+
+
+// generate_plugin_config_munge
+// Generate the munge object from plugin.xml + vars
+PlatformMunger.prototype.generate_plugin_config_munge = generate_plugin_config_munge;
+function generate_plugin_config_munge(plugin_dir, vars) {
+    var self = this;
+
+    vars = vars || {};
+    // Add PACKAGE_NAME variable into vars
+    if (!vars['PACKAGE_NAME']) {
+        vars['PACKAGE_NAME'] = self.platform_handler.package_name(self.project_dir);
+    }
+
+    var munge = { files: {} };
+    var plugin_config = self.config_keeper.get(plugin_dir, '', 'plugin.xml');
+    var plugin_xml = plugin_config.data;
+
+    var platformTag = plugin_xml.find('platform[@name="' + self.platform + '"]');
+    var changes = [];
+    // add platform-agnostic config changes
+    changes = changes.concat(plugin_xml.findall('config-file'));
+    if (platformTag) {
+        // add platform-specific config changes if they exist
+        changes = changes.concat(platformTag.findall('config-file'));
+
+        // note down pbxproj framework munges in special section of munge obj
+        // CB-5238 this is only for systems frameworks
+        var frameworks = platformTag.findall('framework');
+        frameworks.forEach(function(f) {
+            var custom = f.attrib['custom'];
+            if(!custom) {
+                var file = f.attrib['src'];
+                var weak = ('true' == f.attrib['weak']).toString();
+
+                deep_add(munge, 'framework', file, { xml: weak, count: 1 });
+            }
+        });
+    }
+
+    changes.forEach(function(change) {
+        var target = change.attrib['target'];
+        var parent = change.attrib['parent'];
+        var after = change.attrib['after'];
+        var xmls = change.getchildren();
+		xmls.forEach(function(xml) {
+            // 1. stringify each xml
+            var stringified = (new et.ElementTree(xml)).write({xml_declaration:false});
+            // interp vars
+            if (vars) {
+                Object.keys(vars).forEach(function(key) {
+                    var regExp = new RegExp("\\$" + key, "g");
+                    stringified = stringified.replace(regExp, vars[key]);
+                });
+            }
+            // 2. add into munge
+            deep_add(munge, target, parent, { xml: stringified, count: 1, after: after });
+        });
+    });
+    return munge;
+}
+
+// Go over the prepare queue an apply the config munges for each plugin
+// that has been (un)installed.
+PlatformMunger.prototype.process = PlatformMunger_process;
+function PlatformMunger_process() {
+    var self = this;
+
+    var platform_config = exports.get_platform_json(self.plugins_dir, self.platform);
+
+    // Uninstallation first
+    platform_config.prepare_queue.uninstalled.forEach(function(u) {
+        self.remove_plugin_changes(u.plugin, u.id, u.topLevel);
+    });
+
+    // Now handle installation
+    platform_config.prepare_queue.installed.forEach(function(u) {
+        self.add_plugin_changes(u.plugin, u.vars, u.topLevel, true);
+    });
+
+    platform_config = exports.get_platform_json(self.plugins_dir, self.platform);
+
+    // Empty out installed/ uninstalled queues.
+    platform_config.prepare_queue.uninstalled = [];
+    platform_config.prepare_queue.installed = [];
+    // save platform json
+    exports.save_platform_json(platform_config, self.plugins_dir, self.platform);
+}
+/**** END of PlatformMunger ****/
+
+
+/******************************************************************************
+* ConfigKeeper class
+*
+* Used to load and store config files to avoid re-parsing and writing them out
+* multiple times.
+*
+* The config files are referred to by a fake path constructed as
+* project_dir/platform/file
+* where file is the name used for the file in config munges.
+******************************************************************************/
+function ConfigKeeper() {
+    this._cached = {};
+}
+
+ConfigKeeper.prototype.get = ConfigKeeper_get;
+function ConfigKeeper_get(project_dir, platform, file) {
+    var self = this;
+
+    //This fixes a bug with older plugins - when specifying config xml instead of res/xml/config.xml
+    //https://issues.apache.org/jira/browse/CB-6414
+    if(file == 'config.xml' && platform == 'android'){
+        file = 'res/xml/config.xml';
+    }
+    var fake_path = path.join(project_dir, platform, file);
+
+    if (self._cached[fake_path]) {
+        return self._cached[fake_path];
+    }
+    // File was not cached, need to load.
+    var config_file = new ConfigFile(project_dir, platform, file);
+    self._cached[fake_path] = config_file;
+    return config_file;
+}
+
+
+ConfigKeeper.prototype.save_all = ConfigKeeper_save_all;
+function ConfigKeeper_save_all() {
+    var self = this;
+    Object.keys(self._cached).forEach(function (fake_path) {
+        var config_file = self._cached[fake_path];
+        if (config_file.is_changed) config_file.save();
+    });
+}
+/**** END of ConfigKeeper ****/
+
+// TODO: move save/get_platform_json to be part of ConfigKeeper or ConfigFile
+// For now they are used in many places in plugman and cordova-cli and can
+// save the file bypassing the ConfigKeeper's cache.
+exports.get_platform_json = get_platform_json;
+function get_platform_json(plugins_dir, platform) {
+    checkPlatform(platform);
+
+    var filepath = path.join(plugins_dir, platform + '.json');
+    if (fs.existsSync(filepath)) {
+        return fix_munge(JSON.parse(fs.readFileSync(filepath, 'utf-8')));
+    } else {
+        var config = {
+            prepare_queue:{installed:[], uninstalled:[]},
+            config_munge:{},
+            installed_plugins:{},
+            dependent_plugins:{}
+        };
+        return config;
+    }
+}
+
+exports.save_platform_json = save_platform_json;
+function save_platform_json(config, plugins_dir, platform) {
+    checkPlatform(platform);
+    var filepath = path.join(plugins_dir, platform + '.json');
+    fs.writeFileSync(filepath, JSON.stringify(config, null, 4), 'utf-8');
+}
+
+
+// convert a munge from the old format ([file][parent][xml] = count) to the current one
+function fix_munge(platform_config) {
+    var munge = platform_config.config_munge;
+    if (!munge.files) {
+        var new_munge = { files: {} };
+        for (var file in munge) {
+            for (var selector in munge[file]) {
+                for (var xml_child in munge[file][selector]) {
+                    var val = parseInt(munge[file][selector][xml_child]);
+                    for (var i = 0; i < val; i++) {
+                        deep_add(new_munge, [file, selector, { xml: xml_child, count: val }]);
+                    }
+                }
+            }
+        }
+        platform_config.config_munge = new_munge;
+    }
+
+    return platform_config;
+}
+
+/**** END of ConfigKeeper ****/
+
+
+/******************************************************************************
+* ConfigFile class
+*
+* Can load and keep various types of config files. Provides some functionality
+* specific to some file types such as grafting XML children. In most cases it
+* should be instantiated by ConfigKeeper.
+*
+* For plugin.xml files use as:
+* plugin_config = self.config_keeper.get(plugin_dir, '', 'plugin.xml');
+*
+* TODO: Consider moving it out to a separate file and maybe partially with
+* overrides in platform handlers.
+******************************************************************************/
+function ConfigFile(project_dir, platform, file_tag) {
+    this.project_dir = project_dir;
+    this.platform = platform;
+    this.file_tag = file_tag;
+    this.is_changed = false;
+
+    this.load();
+}
+
+// ConfigFile.load()
+ConfigFile.prototype.load = ConfigFile_load;
+function ConfigFile_load() {
+    var self = this;
+
+    // config file may be in a place not exactly specified in the target
+    var filepath = self.filepath = resolveConfigFilePath(self.project_dir, self.platform, self.file_tag);
+
+    if ( !filepath || !fs.existsSync(filepath) ) {
+        self.exists = false;
+        return;
+    }
+    self.exists = true;
+    var ext = path.extname(filepath);
+    // Windows8 uses an appxmanifest, and wp8 will likely use
+    // the same in a future release
+    if (ext == '.xml' || ext == '.appxmanifest') {
+        self.type = 'xml';
+        self.data = xml_helpers.parseElementtreeSync(filepath);
+    } else if (ext == '.pbxproj') {
+        self.type = 'pbxproj';
+        self.data = xcode.project(filepath);
+        self.data.parseSync();
+    } else {
+        // plist file
+        self.type = 'plist';
+        // TODO: isBinaryPlist() reads the file and then parse re-reads it again.
+        //       We always write out text plist, not binary.
+        //       Do we still need to support binary plist?
+        //       If yes, use plist.parseStringSync() and read the file once.
+        self.plist_module = (isBinaryPlist(filepath) ? bplist : plist);
+        self.data = self.plist_module.parseFileSync(filepath);
+    }
+}
+
+// ConfigFile.save()
+ConfigFile.prototype.save = ConfigFile_save;
+function ConfigFile_save() {
+    var self = this;
+    if (self.type === 'xml') {
+        fs.writeFileSync(self.filepath, self.data.write({indent: 4}), 'utf-8');
+    } else if (self.type === 'pbxproj') {
+        fs.writeFileSync(self.filepath, self.data.writeSync());
+    } else {
+        // plist
+        var regExp = new RegExp("<string>[ \t\r\n]+?</string>", "g");
+        fs.writeFileSync(self.filepath, plist.build(self.data).replace(regExp, "<string></string>"));
+    }
+    self.is_changed = false;
+}
+
+// ConfigFile.graft_child()
+ConfigFile.prototype.graft_child = ConfigFile_graft_child;
+function ConfigFile_graft_child(selector, xml_child) {
+    var self = this;
+    var filepath = self.filepath;
+    var result;
+    if (self.type === 'xml') {
+        var xml_to_graft = [et.XML(xml_child.xml)];
+        result = xml_helpers.graftXML(self.data, xml_to_graft, selector, xml_child.after);
+        if ( !result) {
+            throw new Error('grafting xml at selector "' + selector + '" from "' + filepath + '" during config install went bad :(');
+        }
+    } else {
+        // plist file
+        result = plist_helpers.graftPLIST(self.data, xml_child.xml, selector);
+        if ( !result ) {
+            throw new Error('grafting to plist "' + filepath + '" during config install went bad :(');
+        }
+    }
+    self.is_changed = true;
+}
+
+// ConfigFile.prune_child()
+ConfigFile.prototype.prune_child = ConfigFile_prune_child;
+function ConfigFile_prune_child(selector, xml_child) {
+    var self = this;
+    var filepath = self.filepath;
+    var result;
+    if (self.type === 'xml') {
+        var xml_to_graft = [et.XML(xml_child.xml)];
+        result = xml_helpers.pruneXML(self.data, xml_to_graft, selector);
+    } else {
+        // plist file
+        result = plist_helpers.prunePLIST(self.data, xml_child.xml, selector);
+    }
+    if ( !result) {
+        var err_msg = 'Pruning at selector "' + selector + '" from "' + filepath + '" went bad.';
+        throw new Error(err_msg);
+    }
+    self.is_changed = true;
+}
+/**** END of ConfigFile ****/
+
+
+/******************************************************************************
+* Utility functions
+******************************************************************************/
+
+// Check if we know such platform
+function checkPlatform(platform) {
+    if (!(platform in platforms)) throw new Error('platform "' + platform + '" not recognized.');
+}
+
+// determine if a plist file is binary
+function isBinaryPlist(filename) {
+    // I wish there was a synchronous way to read only the first 6 bytes of a
+    // file. This is wasteful :/
+    var buf = '' + fs.readFileSync(filename, 'utf8');
+    // binary plists start with a magic header, "bplist"
+    return buf.substring(0, 6) === 'bplist';
+}
+
+// Find out the real name of an iOS project
+// TODO: glob is slow, need a better way or caching, or avoid using more than once.
+function getIOSProjectname(project_dir) {
+    var matches = glob.sync(path.join(project_dir, '*.xcodeproj'));
+    var iospath;
+    if (matches.length === 1) {
+        iospath = path.basename(matches[0],'.xcodeproj');
+    } else {
+        var msg;
+        if (matches.length === 0) {
+            msg = 'Does not appear to be an xcode project, no xcode project file in ' + project_dir;
+        }
+        else {
+            msg = 'There are multiple *.xcodeproj dirs in ' + project_dir;
+        }
+        throw new Error(msg);
+    }
+    return iospath;
+}
+
+// Some config-file target attributes are not qualified with a full leading directory, or contain wildcards.
+// Resolve to a real path in this function.
+// TODO: getIOSProjectname is slow because of glob, try to avoid calling it several times per project.
+function resolveConfigFilePath(project_dir, platform, file) {
+    var filepath = path.join(project_dir, file);
+    var matches;
+
+    // .pbxproj file
+    if (file === 'framework') {
+        var proj_name = getIOSProjectname(project_dir);
+        filepath = path.join(project_dir, proj_name + '.xcodeproj', 'project.pbxproj');
+        return filepath;
+    }
+
+    if (file.indexOf('*') > -1) {
+        // handle wildcards in targets using glob.
+        matches = glob.sync(path.join(project_dir, '**', file));
+        if (matches.length) filepath = matches[0];
+        return filepath;
+    }
+
+    // special-case config.xml target that is just "config.xml". This should be resolved to the real location of the file.
+    // TODO: move the logic that contains the locations of config.xml from cordova CLI into plugman.
+    if (file == 'config.xml') {
+        if (platform == 'ubuntu') {
+            filepath = path.join(project_dir, 'config.xml');
+        } else if (platform == 'ios') {
+            var iospath = getIOSProjectname(project_dir);
+            filepath = path.join(project_dir,iospath, 'config.xml');
+        } else if (platform == 'android') {
+            filepath = path.join(project_dir, 'res', 'xml', 'config.xml');
+        } else {
+            matches = glob.sync(path.join(project_dir, '**', 'config.xml'));
+            if (matches.length) filepath = matches[0];
+        }
+        return filepath;
+    }
+
+    // None of the special cases matched, returning project_dir/file.
+    return filepath;
+}
+
+
+/******************************************************************************
+* Munge object manipulations functions
+******************************************************************************/
+
+// add the count of [key1][key2]...[keyN] to obj
+// return true if it didn't exist before
+function deep_add(obj, keys /* or key1, key2 .... */ ) {
+    if ( !Array.isArray(keys) ) {
+        keys = Array.prototype.slice.call(arguments, 1);
+    }
+
+    return process_munge(obj, true/*createParents*/, function (parentArray, k) {
+        var found = _.find(parentArray, function(element) {
+            return element.xml == k.xml;
+        });
+        if (found) {
+            found.after = found.after || k.after;
+            found.count += k.count;
+        } else {
+            parentArray.push(k);
+        }
+        return !found;
+    }, keys);
+}
+
+// decrement the count of [key1][key2]...[keyN] from obj and remove if it reaches 0
+// return true if it was removed or not found
+function deep_remove(obj, keys /* or key1, key2 .... */ ) {
+    if ( !Array.isArray(keys) ) {
+        keys = Array.prototype.slice.call(arguments, 1);
+    }
+
+    var result = process_munge(obj, false/*createParents*/, function (parentArray, k) {
+        var index = -1;
+        var found = _.find(parentArray, function (element) {
+            index++;
+            return element.xml == k.xml;
+        });
+        if (found) {
+            found.count -= k.count;
+            if (found.count > 0) {
+                return false;
+            }
+            else {
+                parentArray.splice(index, 1);
+            }
+        }
+        return undefined;
+    }, keys);
+
+    return typeof result === "undefined" ? true : result;
+}
+
+// search for [key1][key2]...[keyN]
+// return the object or undefined if not found
+function deep_find(obj, keys /* or key1, key2 .... */ ) {
+    if ( !Array.isArray(keys) ) {
+        keys = Array.prototype.slice.call(arguments, 1);
+    }
+
+    return process_munge(obj, false/*createParents?*/, function (parentArray, k) {
+        return _.find(parentArray, function (element) {
+            return element.xml == (k.xml || k);
+        });
+    }, keys);
+}
+
+// Execute func passing it the parent array and the xmlChild key.
+// When createParents is true, add the file and parent items  they are missing
+// When createParents is false, stop and return undefined if the file and/or parent items are missing
+
+function process_munge(obj, createParents, func, keys /* or key1, key2 .... */ ) {
+    if ( !Array.isArray(keys) ) {
+        keys = Array.prototype.slice.call(arguments, 1);
+    }
+    var k = keys[0];
+    if (keys.length == 1) {
+        return func(obj, k);
+    } else if (keys.length == 2) {
+        if (!obj.parents[k] && !createParents) {
+            return undefined;
+        }
+        obj.parents[k] = obj.parents[k] || [];
+        return process_munge(obj.parents[k], createParents, func, keys.slice(1));
+    } else if (keys.length == 3){
+        if (!obj.files[k] && !createParents) {
+            return undefined;
+        }
+        obj.files[k] = obj.files[k] || { parents: {} };
+        return process_munge(obj.files[k], createParents, func, keys.slice(1));
+    } else {
+        throw new Error("Invalid key format. Must contain at most 3 elements (file, parent, xmlChild).");
+    }
+}
+
+// All values from munge are added to base as
+// base[file][selector][child] += base[file][selector][child]
+// Returns a munge object containing values that exist in munge
+// but not in base.
+function increment_munge(base, munge) {
+    var diff = { files: {} };
+
+    for (var file in munge.files) {
+        for (var selector in munge.files[file].parents) {
+            for (var xml_child in munge.files[file].parents[selector]) {
+                var val = munge.files[file].parents[selector][xml_child];
+                // if node not in base, add it to diff and base
+                // else increment it's value in base without adding to diff
+                var newlyAdded = deep_add(base, [file, selector, val]);
+                if (newlyAdded) {
+                    deep_add(diff, file, selector, val);
+                }
+            }
+        }
+    }
+    return diff;
+}
+
+// Update the base munge object as
+// base[file][selector][child] -= base[file][selector][child]
+// nodes that reached zero value are removed from base and added to the returned munge
+// object.
+function decrement_munge(base, munge) {
+    var zeroed = { files: {} };
+
+    for (var file in munge.files) {
+        for (var selector in munge.files[file].parents) {
+            for (var xml_child in munge.files[file].parents[selector]) {
+                var val = munge.files[file].parents[selector][xml_child];
+                // if node not in base, add it to diff and base
+                // else increment it's value in base without adding to diff
+                var removed = deep_remove(base, [file, selector, val]);
+                if (removed) {
+                    deep_add(zeroed, file, selector, val);
+                }
+            }
+        }
+    }
+    return zeroed;
+}
+
+// For better readability where used
+function clone_munge(munge) {
+    return increment_munge({}, munge);
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/src/plugman/util/csproj.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/csproj.js b/cordova-lib/src/plugman/util/csproj.js
new file mode 100644
index 0000000..1024878
--- /dev/null
+++ b/cordova-lib/src/plugman/util/csproj.js
@@ -0,0 +1,124 @@
+var xml_helpers = require('./xml-helpers'),
+    et = require('elementtree'),
+    fs = require('fs'),
+    path = require('path');
+
+function csproj(location) {
+    this.location = location;
+    this.xml = xml_helpers.parseElementtreeSync(location);
+    return this;
+}
+
+csproj.prototype = {
+    write:function() {
+        fs.writeFileSync(this.location, this.xml.write({indent:4}), 'utf-8');
+    },
+
+    addReference:function(relPath) {
+        var item = new et.Element('ItemGroup');
+        var extName = path.extname(relPath);
+
+        var elem = new et.Element('Reference');
+        // add dll file name
+        elem.attrib.Include = path.basename(relPath, extName);
+        // add hint path with full path
+        var hint_path = new et.Element('HintPath');
+        hint_path.text = relPath;
+        elem.append(hint_path);
+
+        if(extName == ".winmd") {
+            var mdFileTag = new et.Element("IsWinMDFile");
+                mdFileTag.text = "true";
+            elem.append(mdFileTag);
+        }
+
+        item.append(elem);
+
+        this.xml.getroot().append(item);
+    },
+
+    removeReference:function(relPath) {
+        var item = new et.Element('ItemGroup');
+        var extName = path.extname(relPath);
+        var includeText = path.basename(relPath,extName);
+        // <ItemGroup>
+        //   <Reference Include="WindowsRuntimeComponent1">
+        var item_groups = this.xml.findall('ItemGroup/Reference[@Include="' + includeText + '"]/..');
+
+        if(item_groups.length > 0 ) {
+            this.xml.getroot().remove(0, item_groups[0]);
+        }
+    },
+
+    addSourceFile:function(relative_path) {
+        relative_path = relative_path.split('/').join('\\');
+        // make ItemGroup to hold file.
+        var item = new et.Element('ItemGroup');
+
+        var extName = path.extname(relative_path);
+        // check if it's a .xaml page
+        if(extName == ".xaml") {
+            var page = new et.Element('Page');
+            var sub_type = new et.Element('SubType');
+
+            sub_type.text = "Designer";
+            page.append(sub_type);
+            page.attrib.Include = relative_path;
+
+            var gen = new et.Element('Generator');
+            gen.text = "MSBuild:Compile";
+            page.append(gen);
+
+            var item_groups = this.xml.findall('ItemGroup');
+            if(item_groups.length == 0) {
+                item.append(page);
+            } else {
+                item_groups[0].append(page);
+            }
+        }
+        else if (extName == ".cs") {
+            var compile = new et.Element('Compile');
+            compile.attrib.Include = relative_path;
+            // check if it's a .xaml.cs page that would depend on a .xaml of the same name
+            if (relative_path.indexOf('.xaml.cs', relative_path.length - 8) > -1) {
+                var dep = new et.Element('DependentUpon');
+                var parts = relative_path.split('\\');
+                var xaml_file = parts[parts.length - 1].substr(0, parts[parts.length - 1].length - 3); // Benn, really !?
+                dep.text = xaml_file;
+                compile.append(dep);
+            }
+            item.append(compile);
+        }
+        else { // otherwise add it normally
+            var compile = new et.Element('Content');
+            compile.attrib.Include = relative_path;
+            item.append(compile);
+        }
+        this.xml.getroot().append(item);
+    },
+
+    removeSourceFile:function(relative_path) {
+        relative_path = relative_path.split('/').join('\\');
+        var item_groups = this.xml.findall('ItemGroup');
+        for (var i = 0, l = item_groups.length; i < l; i++) {
+            var group = item_groups[i];
+            var files = group.findall('Compile').concat(group.findall('Page')).concat(group.findall('Content'));
+            for (var j = 0, k = files.length; j < k; j++) {
+                var file = files[j];
+                if (file.attrib.Include == relative_path) {
+                    // remove file reference
+                    group.remove(0, file);
+                    // remove ItemGroup if empty
+                    var new_group = group.findall('Compile').concat(group.findall('Page')).concat(group.findall('Content'));
+                    if(new_group.length < 1) {
+                        this.xml.getroot().remove(0, group);
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+};
+
+module.exports = csproj;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/src/plugman/util/default-engines.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/default-engines.js b/cordova-lib/src/plugman/util/default-engines.js
new file mode 100644
index 0000000..5e4f151
--- /dev/null
+++ b/cordova-lib/src/plugman/util/default-engines.js
@@ -0,0 +1,36 @@
+var path = require('path');
+
+module.exports = function(project_dir){
+    return {
+        'cordova':
+            { 'platform':'*', 'scriptSrc': path.join(project_dir,'cordova','version') },
+        'cordova-plugman':
+            { 'platform':'*', 'currentVersion': require('../../package.json').version },
+        'cordova-android':
+            { 'platform':'android', 'scriptSrc': path.join(project_dir,'cordova','version') },
+        'cordova-ios':
+            { 'platform':'ios', 'scriptSrc': path.join(project_dir,'cordova','version') },
+        'cordova-blackberry10':
+            { 'platform':'blackberry10', 'scriptSrc': path.join(project_dir,'cordova','version') },
+        'cordova-wp7':
+            { 'platform':'wp7', 'scriptSrc': path.join(project_dir,'cordova','version') },
+        'cordova-wp8':
+            { 'platform':'wp8', 'scriptSrc': path.join(project_dir,'cordova','version') },
+        'cordova-windows8':
+            { 'platform':'windows8', 'scriptSrc': path.join(project_dir,'cordova','version') },
+        'apple-xcode' :
+            { 'platform':'ios', 'scriptSrc':  path.join(project_dir,'cordova','apple_xcode_version') },
+        'apple-ios' :
+            { 'platform':'ios', 'scriptSrc': path.join(project_dir,'cordova','apple_ios_version') },
+        'apple-osx' :
+            { 'platform':'ios', 'scriptSrc': path.join(project_dir,'cordova','apple_osx_version') },
+        'blackberry-ndk' :
+            { 'platform':'blackberry10', 'scriptSrc': path.join(project_dir,'cordova','bb10-ndk-version') },
+        'android-sdk' :
+            { 'platform':'android', 'scriptSrc': path.join(project_dir,'cordova','android_sdk_version') },
+        'windows-os' :
+            { 'platform':'wp7|wp8|windows8', 'scriptSrc': path.join(project_dir,'cordova','win_os_version') },
+        'windows-sdk' :
+            { 'platform':'wp7|wp8|windows8', 'scriptSrc': path.join(project_dir,'cordova','win_sdk_version') }
+    }
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/src/plugman/util/dependencies.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/dependencies.js b/cordova-lib/src/plugman/util/dependencies.js
new file mode 100644
index 0000000..a297372
--- /dev/null
+++ b/cordova-lib/src/plugman/util/dependencies.js
@@ -0,0 +1,96 @@
+var dep_graph = require('dep-graph'),
+    path = require('path'),
+    fs = require('fs'),
+    plugman = require('../../plugman'),
+    config_changes = require('./config-changes'),
+    underscore = require('underscore'),
+    xml_helpers = require('./xml-helpers'),
+    package;
+
+module.exports = package = {
+
+    resolvePath: function(plugin_id, plugins_dir)
+    {
+        return path.join(plugins_dir, plugin_id);
+    },
+
+    resolveConfig: function(plugin_id, plugins_dir)
+    {
+        return path.join(plugins_dir, plugin_id, 'plugin.xml');
+    },
+
+    generate_dependency_info:function(plugins_dir, platform) {
+        var json = config_changes.get_platform_json(plugins_dir, platform);
+
+        // TODO: store whole dependency tree in plugins/[platform].json
+        // in case plugins are forcefully removed...
+        var tlps = [];
+        var graph = new dep_graph();
+        Object.keys(json.installed_plugins).forEach(function(plugin_id) {
+            tlps.push(plugin_id);
+
+            var xml = xml_helpers.parseElementtreeSync( package.resolveConfig(plugin_id, plugins_dir) );
+            var deps = xml.findall('dependency');
+
+            deps && deps.forEach(function(dep) {
+                graph.add(plugin_id, dep.attrib.id);
+            });
+        });
+        Object.keys(json.dependent_plugins).forEach(function(plugin_id) {
+            var xml = xml_helpers.parseElementtreeSync( package.resolveConfig(plugin_id, plugins_dir) );
+            var deps = xml.findall('dependency');
+            deps && deps.forEach(function(dep) {
+                graph.add(plugin_id, dep.attrib.id);
+            });
+        });
+
+        return {
+            graph:graph,
+            top_level_plugins:tlps
+        };
+    },
+
+    // Returns a list of top-level plugins which are (transitively) dependent on the given plugin.
+    dependents: function(plugin_id, plugins_dir, platform) {
+        if(typeof plugins_dir == 'object')
+            var depsInfo = plugins_dir;
+        else
+            var depsInfo = package.generate_dependency_info(plugins_dir, platform);
+
+        var graph = depsInfo.graph;
+        var tlps = depsInfo.top_level_plugins;
+        var dependents = tlps.filter(function(tlp) {
+            return tlp != plugin_id && graph.getChain(tlp).indexOf(plugin_id) >= 0;
+        });
+
+        return dependents;
+    },
+
+    // Returns a list of plugins which the given plugin depends on, for which it is the only dependent.
+    // In other words, if the given plugin were deleted, these dangling dependencies should be deleted too.
+    danglers: function(plugin_id, plugins_dir, platform) {
+        if(typeof plugins_dir == 'object')
+            var depsInfo = plugins_dir;
+        else
+            var depsInfo = package.generate_dependency_info(plugins_dir, platform);
+
+        var graph = depsInfo.graph;
+        var dependencies = graph.getChain(plugin_id);
+
+        var tlps = depsInfo.top_level_plugins;
+        var diff_arr = [];
+        tlps.forEach(function(tlp) {
+            if (tlp != plugin_id) {
+                diff_arr.push(graph.getChain(tlp));
+            }
+        });
+
+        // if this plugin has dependencies, do a set difference to determine which dependencies are not required by other existing plugins
+        diff_arr.unshift(dependencies);
+        var danglers = underscore.difference.apply(null, diff_arr);
+
+        // Ensure no top-level plugins are tagged as danglers.
+        danglers = danglers && danglers.filter(function(x) { return tlps.indexOf(x) < 0; });
+        return danglers;
+    }
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/src/plugman/util/metadata.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/metadata.js b/cordova-lib/src/plugman/util/metadata.js
new file mode 100644
index 0000000..cfbb643
--- /dev/null
+++ b/cordova-lib/src/plugman/util/metadata.js
@@ -0,0 +1,19 @@
+var fs = require('fs'),
+    path = require('path');
+
+var filename = '.fetch.json';
+
+exports.get_fetch_metadata = function(plugin_dir) {
+    var filepath = path.join(plugin_dir, filename);
+    if (fs.existsSync(filepath)) {
+        return JSON.parse(fs.readFileSync(filepath, 'utf-8'));
+    } else {
+        return {};
+    }
+};
+
+exports.save_fetch_metadata = function(plugin_dir, data) {
+    var filepath = path.join(plugin_dir, '.fetch.json');
+    fs.writeFileSync(filepath, JSON.stringify(data), 'utf-8');
+};
+

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/src/plugman/util/plist-helpers.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/plist-helpers.js b/cordova-lib/src/plugman/util/plist-helpers.js
new file mode 100644
index 0000000..e1dfbd9
--- /dev/null
+++ b/cordova-lib/src/plugman/util/plist-helpers.js
@@ -0,0 +1,88 @@
+/*
+ *
+ * Copyright 2013 Brett Rudd
+ *
+ * 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 PLIST utility functions
+
+var et = require('elementtree'),
+    plist = require('plist-with-patches');
+
+// adds node to doc at selector
+module.exports = {
+    graftPLIST:function (doc, xml, selector) {
+        var obj = plist.parseStringSync("<plist>"+xml+"</plist>");
+
+        var node = doc[selector];
+        if (node && Array.isArray(node) && Array.isArray(obj))
+            doc[selector] = node.concat(obj);
+        else
+            doc[selector] = obj;
+
+        return true;
+    },
+    // removes node from doc at selector
+    prunePLIST:function(doc, xml, selector) {
+        var obj = plist.parseStringSync("<plist>"+xml+"</plist>");
+
+        pruneOBJECT(doc, selector, obj);
+
+        return true;
+    }
+}
+
+function pruneOBJECT(doc, selector, fragment) {
+    if (Array.isArray(fragment) && Array.isArray(doc[selector])) {
+        var empty = true;
+        for (i in fragment) {
+            for (j in doc[selector]) {
+                empty = pruneOBJECT(doc[selector], j, fragment[i]) && empty;
+            }
+        }
+        if (empty)
+        {
+            delete doc[selector];
+            return true;
+        }
+    }
+    else if (nodeEqual(doc[selector], fragment)) {
+        delete doc[selector];
+        return true;
+    }
+
+    return false;
+}
+
+function nodeEqual(node1, node2) {
+    if (typeof node1 != typeof node2)
+        return false;
+    else if (typeof node1 == 'string') {
+        node2 = escapeRE(node2).replace(new RegExp("\\$[a-zA-Z0-9-_]+","gm"),"(.*?)");
+        return new RegExp('^' + node2 + '$').test(node1);
+    }
+    else {
+        for (var key in node2) {
+            if (!nodeEqual(node1[key], node2[key])) return false;
+        }
+        return true;
+    }
+}
+
+// escape string for use in regex
+function escapeRE(str) {
+     return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\$&");
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/src/plugman/util/plugins.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/plugins.js b/cordova-lib/src/plugman/util/plugins.js
new file mode 100644
index 0000000..5a5d14a
--- /dev/null
+++ b/cordova-lib/src/plugman/util/plugins.js
@@ -0,0 +1,100 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var http = require('http'),
+    os = require('os'),
+    path = require('path'),
+    fs = require('fs'),
+    util = require('util'),
+    shell = require('shelljs'),
+    child_process = require('child_process'),
+    Q = require('q'),
+    xml_helpers = require('./xml-helpers'),
+    events = require('../events'),
+    tmp_dir;
+
+module.exports = {
+    searchAndReplace:require('./search-and-replace'),
+
+    clonePluginGit:function(plugin_git_url, plugins_dir, options) {
+        return module.exports.clonePluginGitRepo(plugin_git_url, plugins_dir, options.subdir, options.git_ref).then(
+            function(dst){
+                // Keep location where we checked out git repo
+                options.plugin_src_dir = tmp_dir;
+                return dst;
+            }
+        );
+    },
+
+    // Fetches plugin information from remote server.
+    // Returns a promise.
+    clonePluginGitRepo:function(plugin_git_url, plugins_dir, subdir, git_ref) {
+
+        if(!shell.which('git')) {
+            return Q.reject(new Error('"git" command line tool is not installed: make sure it is accessible on your PATH.'));
+        }
+        tmp_dir = path.join(os.tmpdir(), 'plugman', 'git', String((new Date).valueOf()));
+
+        shell.rm('-rf', tmp_dir);
+
+        var cmd = util.format('git clone "%s" "%s"', plugin_git_url, tmp_dir);
+        events.emit('verbose', 'Fetching plugin via git-clone command: ' + cmd);
+        var d = Q.defer();
+
+        child_process.exec(cmd, function(err, stdout, stderr) {
+            if (err) {
+                d.reject(err);
+            } else {
+                d.resolve();
+            }
+        });
+        return d.promise.then(function() {
+            events.emit('verbose', 'Plugin "' + plugin_git_url + '" fetched.');
+            // Check out the specified revision, if provided.
+            if (git_ref) {
+                var cmd = util.format('git checkout "%s"', git_ref);
+                var d2 = Q.defer();
+                child_process.exec(cmd, { cwd: tmp_dir }, function(err, stdout, stderr) {
+                    if (err) d2.reject(err);
+                    else d2.resolve();
+                });
+                return d2.promise.then(function() {
+                    events.emit('log', 'Plugin "' + plugin_git_url + '" checked out to git ref "' + git_ref + '".');
+                });
+            }
+        }).then(function() {
+            // Read the plugin.xml file and extract the plugin's ID.
+            tmp_dir = path.join(tmp_dir, subdir);
+            // TODO: what if plugin.xml does not exist?
+            var xml_file = path.join(tmp_dir, 'plugin.xml');
+            var xml = xml_helpers.parseElementtreeSync(xml_file);
+            var plugin_id = xml.getroot().attrib.id;
+
+            // TODO: what if a plugin depended on different subdirectories of the same plugin? this would fail.
+            // should probably copy over entire plugin git repo contents into plugins_dir and handle subdir separately during install.
+            var plugin_dir = path.join(plugins_dir, plugin_id);
+            events.emit('verbose', 'Copying fetched plugin over "' + plugin_dir + '"...');
+            shell.cp('-R', path.join(tmp_dir, '*'), plugin_dir);
+
+            events.emit('verbose', 'Plugin "' + plugin_id + '" fetched.');
+            return plugin_dir;
+        });
+    }
+};
+

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/src/plugman/util/search-and-replace.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/search-and-replace.js b/cordova-lib/src/plugman/util/search-and-replace.js
new file mode 100644
index 0000000..39bd9c3
--- /dev/null
+++ b/cordova-lib/src/plugman/util/search-and-replace.js
@@ -0,0 +1,37 @@
+#!/usr/bin/env node
+/*
+ *
+ * Copyright 2013 Brett Rudd
+ *
+ * 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.
+ *
+*/
+
+var glob = require('glob'),
+    fs = require('fs');
+
+module.exports = function searchAndReplace(srcGlob, variables) {
+    var files = glob.sync(srcGlob);
+    for (var i in files) {
+        var file = files[i];
+        if (fs.lstatSync(file).isFile()) {
+            var contents = fs.readFileSync(file, "utf-8");
+            for (var key in variables) {
+                var regExp = new RegExp("\\$" + key, "g");
+                contents = contents.replace(regExp, variables[key]);
+            }
+            fs.writeFileSync(file, contents);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/src/plugman/util/w8jsproj.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/w8jsproj.js b/cordova-lib/src/plugman/util/w8jsproj.js
new file mode 100644
index 0000000..903a214
--- /dev/null
+++ b/cordova-lib/src/plugman/util/w8jsproj.js
@@ -0,0 +1,239 @@
+/*
+  Helper for dealing with Windows Store JS app .jsproj files
+*/
+
+
+var xml_helpers = require('./xml-helpers'),
+    et = require('elementtree'),
+    fs = require('fs'),
+    shell = require('shelljs'),
+    events = require('../events'),
+    path = require('path');
+
+var WindowsStoreProjectTypeGUID = "{BC8A1FFA-BEE3-4634-8014-F334798102B3}";  // any of the below, subtype
+var WinCSharpProjectTypeGUID = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";    // .csproj
+var WinVBnetProjectTypeGUID = "{F184B08F-C81C-45F6-A57F-5ABD9991F28F}";     // who the ef cares?
+var WinCplusplusProjectTypeGUID = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}"; // .vcxproj
+
+
+function jsproj(location) {
+    events.emit('verbose','creating jsproj from project at : ' + location);
+    this.location = location;
+    this.xml = xml_helpers.parseElementtreeSync(location);
+    return this;
+}
+
+jsproj.prototype = {
+    location:null,
+    xml:null,
+    plugins_dir:"Plugins",
+    write:function() {
+        fs.writeFileSync(this.location, this.xml.write({indent:4}), 'utf-8');
+    },
+    // add/remove the item group for SDKReference
+    // example :
+    // <ItemGroup><SDKReference Include="Microsoft.VCLibs, version=12.0" /></ItemGroup>
+    addSDKRef:function(incText) {
+        var item_group = new et.Element('ItemGroup');
+        var elem = new et.Element('SDKReference');
+        elem.attrib.Include = incText;
+
+        item_group.append(elem);
+        this.xml.getroot().append(item_group);
+    },
+
+    removeSDKRef:function(incText) {
+        var item_group = this.xml.find('ItemGroup/SDKReference[@Include="' + incText + '"]/..');
+        if(item_group) { // TODO: error handling
+            this.xml.getroot().remove(0, item_group);
+        }
+    },
+
+    addReference:function(relPath,src) {
+
+        events.emit('verbose','addReference::' + relPath);
+
+        var item = new et.Element('ItemGroup');
+        var extName = path.extname(relPath);
+
+        var elem = new et.Element('Reference');
+        // add file name
+        elem.attrib.Include = path.basename(relPath, extName);
+
+        // add hint path with full path
+        var hint_path = new et.Element('HintPath');
+            hint_path.text = relPath;
+
+        elem.append(hint_path);
+
+        if(extName == ".winmd") {
+            var mdFileTag = new et.Element("IsWinMDFile");
+                mdFileTag.text = "true";
+            elem.append(mdFileTag);
+        }
+
+        item.append(elem);
+        this.xml.getroot().append(item);
+    },
+
+    removeReference:function(relPath) {
+        events.emit('verbose','removeReference::' + relPath);
+
+        var extName = path.extname(relPath);
+        var includeText = path.basename(relPath,extName);
+        // <ItemGroup>
+        //   <Reference Include="WindowsRuntimeComponent1">
+        var item_group = this.xml.find('ItemGroup/Reference[@Include="' + includeText + '"]/..');
+
+        if(item_group) { // TODO: erro handling
+            this.xml.getroot().remove(0, item_group);
+        }
+    },
+
+    addSourceFile:function(relative_path) {
+
+        relative_path = relative_path.split('/').join('\\');
+        // make ItemGroup to hold file.
+        var item = new et.Element('ItemGroup');
+
+        var content = new et.Element('Content');
+            content.attrib.Include = relative_path;
+        item.append(content);
+
+        this.xml.getroot().append(item);
+    },
+
+    removeSourceFile:function(relative_path) {
+
+        // path.normalize(relative_path);// ??
+        relative_path = relative_path.split('/').join('\\');
+        // var oneStep = this.xml.findall('ItemGroup/Content[@Include="' + relative_path + '""]/..');
+
+        var item_groups = this.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 == relative_path) {
+                    // remove file reference
+                    group.remove(0, file);
+                    // remove ItemGroup if empty
+                    var new_group = group.findall('Content');
+                    if(new_group.length < 1) {
+                        this.xml.getroot().remove(0, group);
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    },
+    // relative path must include the project file, so we can determine .csproj, .jsproj, .vcxproj...
+    addProjectReference:function(relative_path) {
+        events.emit('verbose','adding project reference to ' + relative_path);
+
+        relative_path = relative_path.split('/').join('\\');
+        // read the solution path from the base directory
+        var solutionPath = shell.ls(path.join(path.dirname(this.location),"*.sln"))[0];// TODO:error handling
+        // note we may not have a solution, in which case just add a project reference, I guess ..
+        // get the project extension to figure out project type
+        var projectExt = path.extname(relative_path);
+
+        var pluginProjectXML = xml_helpers.parseElementtreeSync(relative_path);
+        // find the guid + name of the referenced project
+        var projectGuid = pluginProjectXML.find("PropertyGroup/ProjectGuid").text;
+        var projName = pluginProjectXML.find("PropertyGroup/ProjectName").text;
+
+        var preInsertText = "ProjectSection(ProjectDependencies) = postProject\n\r" +
+                             projectGuid + "=" + projectGuid + "\n\r" +
+                            "EndProjectSection\n\r";
+
+        // read in the solution file
+        var solText = fs.readFileSync(solutionPath,{encoding:"utf8"});
+        var splitText = solText.split("EndProject");
+        if(splitText.length != 2) {
+            throw new Error("too many projects in solution.");
+        }
+
+        var projectTypeGuid = null;
+        if(projectExt == ".vcxproj") {
+            projectTypeGuid = WinCplusplusProjectTypeGUID;
+        }
+        else if(projectExt == ".csproj") {
+            projectTypeGuid = WinCSharpProjectTypeGUID;
+        }
+
+        if(!projectTypeGuid) {
+            throw new Error("unrecognized project type");
+        }
+
+        var postInsertText = 'Project("' + projectTypeGuid + '") = "' +
+                         projName + '", "' + relative_path + '",' +
+                        '"' + projectGuid + '"\n\r EndProject\n\r';
+
+        solText = splitText[0] + preInsertText + "EndProject\n\r" + postInsertText + splitText[1];
+        fs.writeFileSync(solutionPath,solText,{encoding:"utf8"});
+
+
+        // Add the ItemGroup/ProjectReference to the cordova project :
+        // <ItemGroup><ProjectReference Include="blahblah.csproj"/></ItemGroup>
+        var item = new et.Element('ItemGroup');
+
+        var projRef = new et.Element('ProjectReference');
+            projRef.attrib.Include = relative_path;
+            item.append(projRef);
+        this.xml.getroot().append(item);
+
+    },
+    removeProjectReference:function(relative_path) {
+        events.emit('verbose','removing project reference to ' + relative_path);
+
+        // find the guid + name of the referenced project
+        var pluginProjectXML = xml_helpers.parseElementtreeSync(relative_path);
+        var projectGuid = pluginProjectXML.find("PropertyGroup/ProjectGuid").text;
+        var projName = pluginProjectXML.find("PropertyGroup/ProjectName").text;
+
+        // get the project extension to figure out project type
+        var projectExt = path.extname(relative_path);
+        // get the project type
+        var projectTypeGuid = null;
+        if(projectExt == ".vcxproj") {
+            projectTypeGuid = WinCplusplusProjectTypeGUID;
+        }
+        else if(projectExt == ".csproj") {
+            projectTypeGuid = WinCSharpProjectTypeGUID;
+        }
+
+        if(!projectTypeGuid) {
+            throw new Error("unrecognized project type");
+        }
+
+        var preInsertText = "ProjectSection(ProjectDependencies) = postProject\n\r" +
+                             projectGuid + "=" + projectGuid + "\n\r" +
+                            "EndProjectSection\n\r";
+
+        var postInsertText = 'Project("' + projectTypeGuid + '") = "' +
+                              projName + '", "' + relative_path + '",' +
+                              '"' + projectGuid + '"\n\r EndProject\n\r';
+
+        // find and read in the solution file
+        var solutionPath = shell.ls(path.join(path.dirname(this.location),"*.sln"))[0];  // TODO:error handling
+        var solText = fs.readFileSync(solutionPath,{encoding:"utf8"});
+        var splitText = solText.split(preInsertText);
+
+        solText = splitText.join("").split(postInsertText);
+        solText = solText.join("");
+
+        fs.writeFileSync(solutionPath,solText,{encoding:"utf8"});
+
+        // select first ItemsGroups with a ChildNode ProjectReference
+        // ideally select all, and look for @attrib 'Include'= projectFullPath
+        var projectRefNodesPar = this.xml.find("ItemGroup/ProjectReference[@Include='" + relative_path + "']/..");
+        if(projectRefNodesPar) {
+            this.xml.getroot().remove(0, projectRefNodesPar);
+        }
+    }
+};
+
+module.exports = jsproj;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/src/plugman/util/xml-helpers.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/xml-helpers.js b/cordova-lib/src/plugman/util/xml-helpers.js
new file mode 100644
index 0000000..601ed4d
--- /dev/null
+++ b/cordova-lib/src/plugman/util/xml-helpers.js
@@ -0,0 +1,196 @@
+/*
+ *
+ * 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')
+  , _ = require('underscore')
+  , et = require('elementtree');
+
+module.exports = {
+    moveProjFile: function(origFile, projPath, callback) {
+        var src = path.resolve(projPath, origFile)
+          , dest = src.replace('.orig', '');
+
+        fs.createReadStream(src)
+            .pipe(fs.createWriteStream(dest))
+            .on('close', callback);
+    },
+
+    // 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, creating parent if it doesn't exist
+    graftXML: function(doc, nodes, selector, after) {
+        var parent = resolveParent(doc, selector);
+        if (!parent) {
+            //Try to create the parent recursively if necessary
+            try {
+                var parentToCreate = et.XML("<" + path.basename(selector) + ">"),
+                    parentSelector = path.dirname(selector);
+
+                this.graftXML(doc, [parentToCreate], parentSelector);
+            } catch (e) {
+                return false;
+            }
+            parent = resolveParent(doc, selector);
+            if (!parent) return false;
+        }
+
+        nodes.forEach(function (node) {
+            // check if child is unique first
+            if (uniqueChild(node, parent)) {
+                var children = parent.getchildren();
+                var insertIdx = after ? findInsertIdx(children, after) : children.length;
+
+                //TODO: replace with parent.insert after the bug in ElementTree is fixed
+                parent.getchildren().splice(insertIdx, 0, 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').replace("\ufeff", "");;
+        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;
+}
+
+// Find the index at which to insert an entry. After is a ;-separated priority list
+// of tags after which the insertion should be made. E.g. If we need to
+// insert an element C, and the rule is that the order of children has to be
+// As, Bs, Cs. After will be equal to "C;B;A".
+
+function findInsertIdx(children, after) {
+    var childrenTags = children.map(function(child) { return child.tag; });
+    var afters = after.split(";");
+    var afterIndexes = afters.map(function(current) { return childrenTags.lastIndexOf(current); });
+    var foundIndex = _.find(afterIndexes, function(index) { return index != -1; });
+
+    //add to the beginning if no matching nodes are found
+    return typeof foundIndex === 'undefined' ? 0 : foundIndex+1;
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/templates/base.js
----------------------------------------------------------------------
diff --git a/cordova-lib/templates/base.js b/cordova-lib/templates/base.js
new file mode 100644
index 0000000..20af664
--- /dev/null
+++ b/cordova-lib/templates/base.js
@@ -0,0 +1,5 @@
+var exec = require('cordova/exec');
+
+exports.coolMethod = function(arg0, success, error) {
+    exec(success, error, "%pluginName%", "coolMethod", [arg0]);
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/templates/platforms/android/android.xml
----------------------------------------------------------------------
diff --git a/cordova-lib/templates/platforms/android/android.xml b/cordova-lib/templates/platforms/android/android.xml
new file mode 100644
index 0000000..c3dc1b7
--- /dev/null
+++ b/cordova-lib/templates/platforms/android/android.xml
@@ -0,0 +1,12 @@
+<platform name="android">
+    <config-file target="res/xml/config.xml" parent="/*">
+        <feature name="%pluginName%">
+            <param name="android-package" value="%pluginID%.%pluginName%"/>
+        </feature>
+    </config-file>
+    <config-file target="AndroidManifest.xml" parent="/*">
+        <!--add permissions -->
+    </config-file>
+
+    <source-file src="src/android/%pluginName%.java" target-dir="src/%packageName%/%pluginName%" />
+</platform>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/templates/platforms/android/base.java
----------------------------------------------------------------------
diff --git a/cordova-lib/templates/platforms/android/base.java b/cordova-lib/templates/platforms/android/base.java
new file mode 100644
index 0000000..3136c1c
--- /dev/null
+++ b/cordova-lib/templates/platforms/android/base.java
@@ -0,0 +1,32 @@
+package %pluginID%;
+
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CallbackContext;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * This class echoes a string called from JavaScript.
+ */
+public class CDV%pluginName% extends CordovaPlugin {
+
+    @Override
+    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+        if (action.equals("coolMethod")) {
+            String message = args.getString(0);
+            this.coolMethod(message, callbackContext);
+            return true;
+        }
+        return false;
+    }
+
+    private void coolMethod(String message, CallbackContext callbackContext) {
+        if (message != null && message.length() > 0) {
+            callbackContext.success(message);
+        } else {
+            callbackContext.error("Expected one non-empty string argument.");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/templates/platforms/ios/base.m
----------------------------------------------------------------------
diff --git a/cordova-lib/templates/platforms/ios/base.m b/cordova-lib/templates/platforms/ios/base.m
new file mode 100644
index 0000000..cb25117
--- /dev/null
+++ b/cordova-lib/templates/platforms/ios/base.m
@@ -0,0 +1,28 @@
+/********* CDV%pluginName%.m Cordova Plugin Implementation *******/
+
+#import <Cordova/CDV.h>
+
+@interface CDV%pluginName% : CDVPlugin {
+  // Member variables go here.
+}
+
+- (void)coolMethod:(CDVInvokedUrlCommand*)command;
+@end
+
+@implementation CDV%pluginName%
+
+- (void)coolMethod:(CDVInvokedUrlCommand*)command
+{
+    CDVPluginResult* pluginResult = nil;
+    NSString* echo = [command.arguments objectAtIndex:0];
+
+    if (echo != nil && [echo length] > 0) {
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:echo];
+    } else {
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
+    }
+
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/templates/platforms/ios/ios.xml
----------------------------------------------------------------------
diff --git a/cordova-lib/templates/platforms/ios/ios.xml b/cordova-lib/templates/platforms/ios/ios.xml
new file mode 100644
index 0000000..6600567
--- /dev/null
+++ b/cordova-lib/templates/platforms/ios/ios.xml
@@ -0,0 +1,9 @@
+<platform name="ios">
+    <config-file target="config.xml" parent="/*">
+        <feature name="%pluginName%">
+            <param name="ios-package" value="%pluginName%" />
+        </feature>
+    </config-file>
+
+    <source-file src="src/ios/CDV%pluginName%.m" />
+</platform>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/doc/base.js
----------------------------------------------------------------------
diff --git a/doc/base.js b/doc/base.js
deleted file mode 100644
index 20af664..0000000
--- a/doc/base.js
+++ /dev/null
@@ -1,5 +0,0 @@
-var exec = require('cordova/exec');
-
-exports.coolMethod = function(arg0, success, error) {
-    exec(success, error, "%pluginName%", "coolMethod", [arg0]);
-};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/doc/platforms/android/android.xml
----------------------------------------------------------------------
diff --git a/doc/platforms/android/android.xml b/doc/platforms/android/android.xml
deleted file mode 100644
index c3dc1b7..0000000
--- a/doc/platforms/android/android.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<platform name="android">
-    <config-file target="res/xml/config.xml" parent="/*">
-        <feature name="%pluginName%">
-            <param name="android-package" value="%pluginID%.%pluginName%"/>
-        </feature>
-    </config-file>
-    <config-file target="AndroidManifest.xml" parent="/*">
-        <!--add permissions -->
-    </config-file>
-
-    <source-file src="src/android/%pluginName%.java" target-dir="src/%packageName%/%pluginName%" />
-</platform>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/doc/platforms/android/base.java
----------------------------------------------------------------------
diff --git a/doc/platforms/android/base.java b/doc/platforms/android/base.java
deleted file mode 100644
index 3136c1c..0000000
--- a/doc/platforms/android/base.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package %pluginID%;
-
-import org.apache.cordova.CordovaPlugin;
-import org.apache.cordova.CallbackContext;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * This class echoes a string called from JavaScript.
- */
-public class CDV%pluginName% extends CordovaPlugin {
-
-    @Override
-    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
-        if (action.equals("coolMethod")) {
-            String message = args.getString(0);
-            this.coolMethod(message, callbackContext);
-            return true;
-        }
-        return false;
-    }
-
-    private void coolMethod(String message, CallbackContext callbackContext) {
-        if (message != null && message.length() > 0) {
-            callbackContext.success(message);
-        } else {
-            callbackContext.error("Expected one non-empty string argument.");
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/doc/platforms/ios/base.m
----------------------------------------------------------------------
diff --git a/doc/platforms/ios/base.m b/doc/platforms/ios/base.m
deleted file mode 100644
index cb25117..0000000
--- a/doc/platforms/ios/base.m
+++ /dev/null
@@ -1,28 +0,0 @@
-/********* CDV%pluginName%.m Cordova Plugin Implementation *******/
-
-#import <Cordova/CDV.h>
-
-@interface CDV%pluginName% : CDVPlugin {
-  // Member variables go here.
-}
-
-- (void)coolMethod:(CDVInvokedUrlCommand*)command;
-@end
-
-@implementation CDV%pluginName%
-
-- (void)coolMethod:(CDVInvokedUrlCommand*)command
-{
-    CDVPluginResult* pluginResult = nil;
-    NSString* echo = [command.arguments objectAtIndex:0];
-
-    if (echo != nil && [echo length] > 0) {
-        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:echo];
-    } else {
-        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
-    }
-
-    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
-}
-
-@end

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/doc/platforms/ios/ios.xml
----------------------------------------------------------------------
diff --git a/doc/platforms/ios/ios.xml b/doc/platforms/ios/ios.xml
deleted file mode 100644
index 6600567..0000000
--- a/doc/platforms/ios/ios.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<platform name="ios">
-    <config-file target="config.xml" parent="/*">
-        <feature name="%pluginName%">
-            <param name="ios-package" value="%pluginName%" />
-        </feature>
-    </config-file>
-
-    <source-file src="src/ios/CDV%pluginName%.m" />
-</platform>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/plugman.js
----------------------------------------------------------------------
diff --git a/plugman.js b/plugman.js
deleted file mode 100644
index 4aa1843..0000000
--- a/plugman.js
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- *
- * 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.
- *
-*/
-
-// copyright (c) 2013 Andrew Lunny, Adobe Systems
-
-var events = require('./src/events');
-var Q = require('q');
-
-function addProperty(o, symbol, modulePath, doWrap) {
-    var val = null;
-
-    if (doWrap) {
-        o[symbol] = function() {
-            val = val || 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(o, args).done(function(result) {cb(undefined, result)}, cb);
-            } else {
-                val.apply(o, arguments).done(null, function(err){ throw err; });
-            }
-        };
-    } else {
-        // The top-level plugman.foo
-        Object.defineProperty(o, symbol, {
-            get : function() { return val = val || require(modulePath); },
-            set : function(v) { val = v; }
-        });
-    }
-
-    // The plugman.raw.foo
-    Object.defineProperty(o.raw, symbol, {
-        get : function() { return val = val || require(modulePath); },
-        set : function(v) { val = v; }
-    });
-}
-
-plugman = {
-    on:                 events.on.bind(events),
-    off:                events.removeListener.bind(events),
-    removeAllListeners: events.removeAllListeners.bind(events),
-    emit:               events.emit.bind(events),
-    raw:                {}
-};
-
-addProperty(plugman, 'help', './src/help');
-addProperty(plugman, 'install', './src/install', true);
-addProperty(plugman, 'uninstall', './src/uninstall', true);
-addProperty(plugman, 'fetch', './src/fetch', true);
-addProperty(plugman, 'prepare', './src/prepare');
-addProperty(plugman, 'config', './src/config', true);
-addProperty(plugman, 'owner', './src/owner', true);
-addProperty(plugman, 'adduser', './src/adduser', true);
-addProperty(plugman, 'publish', './src/publish', true);
-addProperty(plugman, 'unpublish', './src/unpublish', true);
-addProperty(plugman, 'search', './src/search', true);
-addProperty(plugman, 'info', './src/info', true);
-addProperty(plugman, 'create', './src/create', true);
-addProperty(plugman, 'platform', './src/platform_operation', true);
-addProperty(plugman, 'config_changes', './src/util/config-changes');
-
-plugman.commands =  {
-    'config'   : function(cli_opts) {
-        plugman.config(cli_opts.argv.remain, function(err) {
-            if (err) throw err;
-            else console.log('done');
-        });
-    },
-    'owner'   : function(cli_opts) {
-        plugman.owner(cli_opts.argv.remain);
-    },
-    'install'  : function(cli_opts) {
-        if(!cli_opts.platform || !cli_opts.project || !cli_opts.plugin) {
-            return console.log(plugman.help());
-        }
-        var cli_variables = {}
-        if (cli_opts.variable) {
-            cli_opts.variable.forEach(function (variable) {
-                    var tokens = variable.split('=');
-                    var key = tokens.shift().toUpperCase();
-                    if (/^[\w-_]+$/.test(key)) cli_variables[key] = tokens.join('=');
-                    });
-        }
-        var opts = {
-            subdir: '.',
-            cli_variables: cli_variables,
-            www_dir: cli_opts.www,
-            searchpath: cli_opts.searchpath
-        };
-
-        var p = Q();
-        cli_opts.plugin.forEach(function (pluginSrc) {
-            p = p.then(function () {
-                return plugman.raw.install(cli_opts.platform, cli_opts.project, pluginSrc, cli_opts.plugins_dir, opts);
-            })
-        });
-        
-        return p;
-    },
-    'uninstall': function(cli_opts) {
-        if(!cli_opts.platform || !cli_opts.project || !cli_opts.plugin) {
-            return console.log(plugman.help());
-        }
-
-        var p = Q();
-        cli_opts.plugin.forEach(function (pluginSrc) {
-            p = p.then(function () {
-                return plugman.raw.uninstall(cli_opts.platform, cli_opts.project, pluginSrc, cli_opts.plugins_dir, { www_dir: cli_opts.www });
-            });
-        });
-
-        return p;
-    },
-    'adduser'  : function(cli_opts) {
-        plugman.adduser(function(err) {
-            if (err) throw err;
-            else console.log('user added');
-        });
-    },
-
-    'search'   : function(cli_opts) {
-        plugman.search(cli_opts.argv.remain, function(err, plugins) {
-            if (err) throw err;
-            else {
-                for(var plugin in plugins) {
-                    console.log(plugins[plugin].name, '-', plugins[plugin].description || 'no description provided');
-                }
-            }
-        });
-    },
-    'info'     : function(cli_opts) {
-        plugman.info(cli_opts.argv.remain, function(err, plugin_info) {
-            if (err) throw err;
-            else {
-                console.log('name:', plugin_info.name);
-                console.log('version:', plugin_info.version);
-                if (plugin_info.engines) {
-                    for(var i = 0, j = plugin_info.engines.length ; i < j ; i++) {
-                        console.log(plugin_info.engines[i].name, 'version:', plugin_info.engines[i].version);
-                    }
-                }
-            }
-        });
-    },
-
-    'publish'  : function(cli_opts) {
-        var plugin_path = cli_opts.argv.remain;
-        if(!plugin_path) {
-            return console.log(plugman.help());
-        }
-        plugman.publish(plugin_path, function(err) {
-            if (err) throw err;
-            else console.log('Plugin published');
-        });
-    },
-
-    'unpublish': function(cli_opts) {
-        var plugin = cli_opts.argv.remain;
-        if(!plugin) {
-            return console.log(plugman.help());
-        }
-        plugman.unpublish(plugin, function(err) {
-            if (err) throw err;
-            else console.log('Plugin unpublished');
-        });
-    },
-    'create': function(cli_opts) {
-        if( !cli_opts.name || !cli_opts.plugin_id || !cli_opts.plugin_version) {
-            return console.log( plugman.help() );
-        }
-        var cli_variables = {};
-        if (cli_opts.variable) {
-            cli_opts.variable.forEach(function (variable) {
-                    var tokens = variable.split('=');
-                    var key = tokens.shift().toUpperCase();
-                    if (/^[\w-_]+$/.test(key)) cli_variables[key] = tokens.join('=');
-                    });
-        }
-        plugman.create( cli_opts.name, cli_opts.plugin_id, cli_opts.plugin_version, cli_opts.path || ".", cli_variables );
-    },
-    'platform': function(cli_opts) {
-        var operation = cli_opts.argv.remain[ 0 ] || "";
-        if( ( operation !== 'add' && operation !== 'remove' ) ||  !cli_opts.platform_name ) {
-            return console.log( plugman.help() );
-        }
-        plugman.platform( { operation: operation, platform_name: cli_opts.platform_name } );
-    }
-};
-
-module.exports = plugman;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/spec/add_platform.spec.js
----------------------------------------------------------------------
diff --git a/spec/add_platform.spec.js b/spec/add_platform.spec.js
deleted file mode 100644
index 18bad02..0000000
--- a/spec/add_platform.spec.js
+++ /dev/null
@@ -1,64 +0,0 @@
-var platform = require('../src/platform'),
-    Q = require('q'),
-    fs = require('fs'),
-    shell = require('shelljs'),
-    plugman = require('../plugman');
-
-describe( 'platform add/remove', function() {
-    it( 'should call platform add', function() {
-        var sPlatformA = spyOn( platform, 'add' ).andReturn(Q()),
-            sPlatformR = spyOn( platform, 'remove' ).andReturn(Q());
-        platform.add();
-        expect(sPlatformA).toHaveBeenCalled();
-        platform.remove();
-        expect(sPlatformR).toHaveBeenCalled();
-    });
-});
-
-
-describe( 'platform add', function() {
-    var done = false,
-        existsSync,
-        mkdir,
-        writeFileSync;
-    function platformPromise( f ) {
-        f.then( function() { done = true; }, function(err) { done = err; } );
-    }
-    beforeEach( function() {
-        existsSync = spyOn( fs, 'existsSync' ).andReturn( false );
-        done = false;
-    });
-    it( 'should error on non existing plugin.xml', function() {
-        runs(function() {
-            platformPromise( platform.add() );
-        });
-        waitsFor(function() { return done; }, 'platform promise never resolved', 500);
-        runs(function() {
-            expect(''+ done ).toContain( "can't find a plugin.xml.  Are you in the plugin?"  );
-        });
-    });
-});
-
-
-describe( 'platform remove', function() {
-    var done = false,
-        existsSync,
-        mkdir,
-        writeFileSync;
-    function platformPromise( f ) {
-        f.then( function() { done = true; }, function(err) { done = err; } );
-    }
-    beforeEach( function() {
-        existsSync = spyOn( fs, 'existsSync' ).andReturn( false );
-        done = false;
-    });
-    it( 'should error on non existing plugin.xml', function() {
-        runs(function() {
-            platformPromise( platform.remove() );
-        });
-        waitsFor(function() { return done; }, 'platform promise never resolved', 500);
-        runs(function() {
-            expect(''+ done ).toContain( "can't find a plugin.xml.  Are you in the plugin?"  );
-        });
-    });
-});