You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by sg...@apache.org on 2015/09/20 14:18:01 UTC

[02/10] cordova-lib git commit: CB-9598 Initial implementation for cordova-common module

CB-9598 Initial implementation for cordova-common module


Project: http://git-wip-us.apache.org/repos/asf/cordova-lib/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-lib/commit/28ce0d1d
Tree: http://git-wip-us.apache.org/repos/asf/cordova-lib/tree/28ce0d1d
Diff: http://git-wip-us.apache.org/repos/asf/cordova-lib/diff/28ce0d1d

Branch: refs/heads/master
Commit: 28ce0d1d8665caff4977622f6b178b7f4899896e
Parents: 436679f
Author: Vladimir Kotikov <v-...@microsoft.com>
Authored: Wed Sep 16 18:17:24 2015 +0300
Committer: sgrebnov <v-...@microsoft.com>
Committed: Sun Sep 20 15:14:53 2015 +0300

----------------------------------------------------------------------
 cordova-common/.jscs.json                       |  24 +
 cordova-common/.jshintignore                    |   1 +
 cordova-common/.npmignore                       |   2 +
 cordova-common/.ratignore                       |   2 +
 cordova-common/README.md                        |  33 ++
 cordova-common/RELEASENOTES.md                  |  24 +
 cordova-common/cordova-common.js                |  41 ++
 cordova-common/package.json                     |  46 ++
 cordova-common/spec/.jshintrc                   |  11 +
 cordova-common/spec/ConfigParser.spec.js        | 224 +++++++++
 cordova-common/src/.jshintrc                    |  10 +
 cordova-common/src/ActionStack.js               |  85 ++++
 .../src/ConfigChanges/ConfigChanges.js          | 401 ++++++++++++++++
 cordova-common/src/ConfigChanges/ConfigFile.js  | 220 +++++++++
 .../src/ConfigChanges/ConfigKeeper.js           |  65 +++
 cordova-common/src/ConfigChanges/munge-util.js  | 160 +++++++
 cordova-common/src/CordovaError.js              |  32 ++
 cordova-common/src/PlatformJson.js              | 155 ++++++
 cordova-common/src/PluginInfo/PluginInfo.js     | 416 ++++++++++++++++
 .../src/PluginInfo/PluginInfoProvider.js        |  82 ++++
 cordova-common/src/configparser/ConfigParser.js | 470 +++++++++++++++++++
 cordova-common/src/configparser/README.md       |  86 ++++
 cordova-common/src/events.js                    |  19 +
 cordova-common/src/superspawn.js                | 154 ++++++
 cordova-common/src/util/plist-helpers.js        | 101 ++++
 cordova-common/src/util/xml-helpers.js          | 266 +++++++++++
 26 files changed, 3130 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/.jscs.json
----------------------------------------------------------------------
diff --git a/cordova-common/.jscs.json b/cordova-common/.jscs.json
new file mode 100644
index 0000000..5cc7e26
--- /dev/null
+++ b/cordova-common/.jscs.json
@@ -0,0 +1,24 @@
+{
+    "disallowMixedSpacesAndTabs": true,
+    "disallowTrailingWhitespace": true,
+    "validateLineBreaks": "LF",
+    "validateIndentation": 4,
+    "requireLineFeedAtFileEnd": true,
+
+    "disallowSpaceAfterPrefixUnaryOperators": true,
+    "disallowSpaceBeforePostfixUnaryOperators": true,
+    "requireSpaceAfterLineComment": true,
+    "requireCapitalizedConstructors": true,
+
+    "disallowSpacesInNamedFunctionExpression": {
+        "beforeOpeningRoundBrace": true
+    },
+
+    "requireSpaceAfterKeywords": [
+      "if",
+      "else",
+      "for",
+      "while",
+      "do"
+    ]
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/.jshintignore
----------------------------------------------------------------------
diff --git a/cordova-common/.jshintignore b/cordova-common/.jshintignore
new file mode 100644
index 0000000..d606f61
--- /dev/null
+++ b/cordova-common/.jshintignore
@@ -0,0 +1 @@
+spec/fixtures/*

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/.npmignore
----------------------------------------------------------------------
diff --git a/cordova-common/.npmignore b/cordova-common/.npmignore
new file mode 100644
index 0000000..5d14118
--- /dev/null
+++ b/cordova-common/.npmignore
@@ -0,0 +1,2 @@
+spec
+coverage

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/.ratignore
----------------------------------------------------------------------
diff --git a/cordova-common/.ratignore b/cordova-common/.ratignore
new file mode 100644
index 0000000..26f7205
--- /dev/null
+++ b/cordova-common/.ratignore
@@ -0,0 +1,2 @@
+fixtures
+coverage

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/README.md
----------------------------------------------------------------------
diff --git a/cordova-common/README.md b/cordova-common/README.md
new file mode 100644
index 0000000..13dba33
--- /dev/null
+++ b/cordova-common/README.md
@@ -0,0 +1,33 @@
+<!--
+#
+# 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.
+#
+-->
+
+# cordova-lib
+Contains shared classes and routines used by [cordova-lib](https://github.com/apache/cordova-lib/) and platforms.
+
+## Setup
+* Clone this repository onto your local machine. 
+    `git clone https://git-wip-us.apache.org/repos/asf/cordova-lib.git`
+* In terminal, navigate to the inner cordova-common directory.
+    `cd cordova-lib/cordova-common`
+* Install dependencies and npm-link
+    `npm install && npm link`
+* Navigate to cordova-lib directory and link cordova-common
+    `cd ../cordova-lib && npm link cordova-common && npm install`

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/RELEASENOTES.md
----------------------------------------------------------------------
diff --git a/cordova-common/RELEASENOTES.md b/cordova-common/RELEASENOTES.md
new file mode 100644
index 0000000..0541a9e
--- /dev/null
+++ b/cordova-common/RELEASENOTES.md
@@ -0,0 +1,24 @@
+<!--
+#
+# 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.
+#
+-->
+# Cordova-lib Release Notes
+
+### 0.1.0 (Aug 25, 2015)
+* Initial release

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/cordova-common.js
----------------------------------------------------------------------
diff --git a/cordova-common/cordova-common.js b/cordova-common/cordova-common.js
new file mode 100644
index 0000000..5873aa7
--- /dev/null
+++ b/cordova-common/cordova-common.js
@@ -0,0 +1,41 @@
+/**
+    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.
+*/
+
+/* jshint node:true */
+
+// For now expose plugman and cordova just as they were in the old repos
+exports = module.exports = {
+    events: require('./src/events'),
+    superspawn: require('./src/superspawn'),
+
+    ActionStack: require('./src/ActionStack'),
+    CordovaError: require('./src/CordovaError'),
+    PlatformJson: require('./src/PlatformJson'),
+    ConfigParser: require('./src/ConfigParser/ConfigParser.js'),
+
+    PluginInfo: require('./src/PluginInfo/PluginInfo.js'),
+    PluginInfoProvider: require('./src/PluginInfo/PluginInfoProvider.js'),
+
+    ConfigChanges: require('./src/ConfigChanges/ConfigChanges.js'),
+    ConfigKeeper: require('./src/ConfigChanges/ConfigKeeper.js'),
+    ConfigFile: require('./src/ConfigChanges/ConfigFile.js'),
+    mungeUtil: require('./src/ConfigChanges/munge-util.js'),
+
+    xmlHelpers: require('./src/util/xml-helpers')
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/package.json
----------------------------------------------------------------------
diff --git a/cordova-common/package.json b/cordova-common/package.json
new file mode 100644
index 0000000..525e951
--- /dev/null
+++ b/cordova-common/package.json
@@ -0,0 +1,46 @@
+{
+  "author": "Apache Software Foundation",
+  "name": "cordova-common",
+  "description": "Apache Cordova tools and platforms shared routines",
+  "license": "Apache-2.0",
+  "version": "0.1.0",
+  "repository": {
+    "type": "git",
+    "url": "git://git-wip-us.apache.org/repos/asf/cordova-common.git"
+  },
+  "bugs": {
+    "url": "https://issues.apache.org/jira/browse/CB",
+    "email": "dev@cordova.apache.org"
+  },
+  "main": "cordova-common.js",
+  "engines": {
+    "node": ">=0.9.9"
+  },
+  "scripts": {
+    "test": "npm run jshint && npm run jasmine",
+    "jshint": "node node_modules/jshint/bin/jshint src && node node_modules/jshint/bin/jshint spec",
+    "jasmine": "node node_modules/jasmine-node/bin/jasmine-node --captureExceptions --color spec",
+    "cover": "node node_modules/istanbul/lib/cli.js cover --root src --print detail node_modules/jasmine-node/bin/jasmine-node -- spec"
+  },
+  "engineStrict": true,
+  "dependencies": {
+    "bplist-parser": "^0.1.0",
+    "cordova-registry-mapper": "^1.1.8",
+    "elementtree": "^0.1.6",
+    "glob": "^5.0.13",
+    "osenv": "^0.1.3",
+    "plist": "^1.1.0",
+    "q": "^1.4.1",
+    "semver": "^5.0.1",
+    "shelljs": "^0.5.1",
+    "underscore": "^1.8.3",
+    "unorm": "^1.3.3",
+    "xcode": "^0.8.0"
+  },
+  "devDependencies": {
+    "istanbul": "^0.3.17",
+    "jasmine-node": "^1.14.5",
+    "jshint": "^2.8.0"
+  },
+  "contributors": []
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/spec/.jshintrc
----------------------------------------------------------------------
diff --git a/cordova-common/spec/.jshintrc b/cordova-common/spec/.jshintrc
new file mode 100644
index 0000000..17eae32
--- /dev/null
+++ b/cordova-common/spec/.jshintrc
@@ -0,0 +1,11 @@
+{
+    "node": true
+  , "bitwise": true
+  , "undef": true
+  , "trailing": true
+  , "quotmark": true
+  , "indent": 4
+  , "unused": "vars"
+  , "latedef": "nofunc"
+  , "jasmine": true
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/spec/ConfigParser.spec.js
----------------------------------------------------------------------
diff --git a/cordova-common/spec/ConfigParser.spec.js b/cordova-common/spec/ConfigParser.spec.js
new file mode 100644
index 0000000..a888cf4
--- /dev/null
+++ b/cordova-common/spec/ConfigParser.spec.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 path = require('path'),
+    fs = require('fs'),
+    ConfigParser = require('../src/configparser/ConfigParser'),
+    xml = path.join(__dirname, 'test-config.xml'),
+    xml_contents = fs.readFileSync(xml, 'utf-8');
+
+describe('config.xml parser', function () {
+    var readFile;
+    beforeEach(function() {
+        readFile = spyOn(fs, 'readFileSync').andReturn(xml_contents);
+    });
+
+    it('should create an instance based on an xml file', function() {
+        var cfg;
+        expect(function () {
+            cfg = new ConfigParser(xml);
+        }).not.toThrow();
+        expect(cfg).toBeDefined();
+        expect(cfg.doc).toBeDefined();
+    });
+
+    describe('methods', function() {
+        var cfg;
+        beforeEach(function() {
+            cfg = new ConfigParser(xml);
+        });
+
+        describe('package name / id', function() {
+            it('should get the (default) packagename', function() {
+                expect(cfg.packageName()).toEqual('io.cordova.hellocordova');
+            });
+            it('should allow setting the packagename', function() {
+                cfg.setPackageName('this.is.bat.country');
+                expect(cfg.packageName()).toEqual('this.is.bat.country');
+            });
+        });
+
+        describe('package name / android-packageName', function() {
+            it('should get the android packagename', function() {
+                expect(cfg.android_packageName()).toEqual('io.cordova.hellocordova.android');
+            });
+        });
+
+        describe('package name / ios-CFBundleIdentifier', function() {
+            it('should get the ios packagename', function() {
+                expect(cfg.ios_CFBundleIdentifier()).toEqual('io.cordova.hellocordova.ios');
+            });
+        });
+
+        describe('version', function() {
+            it('should get the version', function() {
+                expect(cfg.version()).toEqual('0.0.1');
+            });
+            it('should allow setting the version', function() {
+                cfg.setVersion('2.0.1');
+                expect(cfg.version()).toEqual('2.0.1');
+            });
+        });
+
+        describe('app name', function() {
+            it('should get the (default) app name', function() {
+                expect(cfg.name()).toEqual('Hello Cordova');
+            });
+            it('should allow setting the app name', function() {
+                cfg.setName('this.is.bat.country');
+                expect(cfg.name()).toEqual('this.is.bat.country');
+            });
+        });
+        describe('preference', function() {
+            it('should return the value of a global preference', function() {
+                expect(cfg.getPreference('fullscreen')).toEqual('true');
+            });
+            it('should return the value of a platform-specific preference', function() {
+                expect(cfg.getPreference('android-minSdkVersion', 'android')).toEqual('10');
+            });
+            it('should return an empty string for a non-existing preference', function() {
+                expect(cfg.getPreference('zimzooo!')).toEqual('');
+            });
+        });
+        describe('global preference', function() {
+            it('should return the value of a global preference', function() {
+                expect(cfg.getGlobalPreference('orientation')).toEqual('portrait');
+            });
+            it('should return an empty string for a non-existing preference', function() {
+                expect(cfg.getGlobalPreference('foobar')).toEqual('');
+            });
+        });
+        describe('platform-specific preference', function() {
+            it('should return the value of a platform specific preference', function() {
+                expect(cfg.getPlatformPreference('orientation', 'android')).toEqual('landscape');
+            });
+            it('should return an empty string when querying for a non-existing preference', function() {
+                expect(cfg.getPlatformPreference('foobar', 'android')).toEqual('');
+            });
+            it('should return an empty string when querying with unsupported platform', function() {
+                expect(cfg.getPlatformPreference('orientation', 'foobar')).toEqual('');
+            });
+        });
+        describe('plugin',function(){
+            it('should read plugin id list', function() {
+               var expectedList = [
+                   'org.apache.cordova.pluginwithvars',
+                   'org.apache.cordova.pluginwithurl',
+                   'org.apache.cordova.pluginwithversion',
+                   'org.apache.cordova.pluginwithurlandversion',
+                   'org.apache.cordova.justaplugin',
+                   'org.apache.cordova.legacyfeatureversion',
+                   'org.apache.cordova.legacyfeatureurl',
+                   'org.apache.cordova.legacyfeatureversionandurl'
+               ];
+               var list = cfg.getPluginIdList();
+               expect(list.length).toEqual(expectedList.length);
+               expectedList.forEach(function(plugin){
+                   expect(list).toContain(plugin);
+               });
+            });
+            it('should read plugin given id', function(){
+                var plugin = cfg.getPlugin('org.apache.cordova.justaplugin');
+                expect(plugin).toBeDefined();
+                expect(plugin.name).toEqual('org.apache.cordova.justaplugin');
+                expect(plugin.variables).toBeDefined();
+            });
+            it('should not read plugin given undefined id', function(){
+                var plugin = cfg.getPlugin('org.apache.cordova.undefinedplugin');
+                expect(plugin).not.toBeDefined();
+            });
+            it('should read plugin with src and store it in spec field', function(){
+                var plugin = cfg.getPlugin('org.apache.cordova.pluginwithurl');
+                expect(plugin.spec).toEqual('http://cordova.apache.org/pluginwithurl');
+            });
+            it('should read plugin with version and store it in spec field', function(){
+                var plugin = cfg.getPlugin('org.apache.cordova.pluginwithversion');
+                expect(plugin.spec).toEqual('1.1.1');
+            });
+            it('should read plugin with source and version and store source in spec field', function(){
+                var plugin = cfg.getPlugin('org.apache.cordova.pluginwithurlandversion');
+                expect(plugin.spec).toEqual('http://cordova.apache.org/pluginwithurlandversion');
+            });
+            it('should read plugin variables', function () {
+                var plugin = cfg.getPlugin('org.apache.cordova.pluginwithvars');
+                expect(plugin.variables).toBeDefined();
+                expect(plugin.variables.var).toBeDefined();
+                expect(plugin.variables.var).toEqual('varvalue');
+            });
+            it('should allow adding a new plugin', function(){
+                cfg.addPlugin({name:'myplugin'});
+                var plugins = cfg.doc.findall('plugin');
+                var pluginNames = plugins.map(function(plugin){
+                    return plugin.attrib.name;
+                });
+                expect(pluginNames).toContain('myplugin');
+            });
+            it('should allow adding features with params', function(){
+                cfg.addPlugin({name:'aplugin'}, [{name:'paraname',value:'paravalue'}]);
+                // Additional check for new parameters syntax
+                cfg.addPlugin({name:'bplugin'}, {paraname: 'paravalue'});
+                var plugins = cfg.doc.findall('plugin')
+                .filter(function (plugin) {
+                    return plugin.attrib.name === 'aplugin' || plugin.attrib.name === 'bplugin';
+                });
+                expect(plugins.length).toBe(2);
+                plugins.forEach(function (plugin) {
+                    var variables = plugin.findall('variable');
+                    expect(variables[0].attrib.name).toEqual('paraname');
+                    expect(variables[0].attrib.value).toEqual('paravalue');
+                });
+            });
+            it('should be able to read legacy feature entries with a version', function(){
+                var plugin = cfg.getPlugin('org.apache.cordova.legacyfeatureversion');
+                expect(plugin).toBeDefined();
+                expect(plugin.name).toEqual('org.apache.cordova.legacyfeatureversion');
+                expect(plugin.spec).toEqual('1.2.3');
+                expect(plugin.variables).toBeDefined();
+                expect(plugin.variables.aVar).toEqual('aValue');
+            });
+            it('should be able to read legacy feature entries with a url', function(){
+                var plugin = cfg.getPlugin('org.apache.cordova.legacyfeatureurl');
+                expect(plugin).toBeDefined();
+                expect(plugin.name).toEqual('org.apache.cordova.legacyfeatureurl');
+                expect(plugin.spec).toEqual('http://cordova.apache.org/legacyfeatureurl');
+            });
+            it('should be able to read legacy feature entries with a version and a url', function(){
+                var plugin = cfg.getPlugin('org.apache.cordova.legacyfeatureversionandurl');
+                expect(plugin).toBeDefined();
+                expect(plugin.name).toEqual('org.apache.cordova.legacyfeatureversionandurl');
+                expect(plugin.spec).toEqual('http://cordova.apache.org/legacyfeatureversionandurl');
+            });
+            it('it should remove given plugin', function(){
+                cfg.removePlugin('org.apache.cordova.justaplugin');
+                var plugins = cfg.doc.findall('plugin');
+                var pluginNames = plugins.map(function(plugin){
+                    return plugin.attrib.name;
+                });
+                expect(pluginNames).not.toContain('org.apache.cordova.justaplugin');
+            });
+            it('it should remove given legacy feature id', function(){
+                cfg.removePlugin('org.apache.cordova.legacyplugin');
+                var plugins = cfg.doc.findall('feature');
+                var pluginNames = plugins.map(function(plugin){
+                    return plugin.attrib.name;
+                });
+                expect(pluginNames).not.toContain('org.apache.cordova.legacyplugin');
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/src/.jshintrc
----------------------------------------------------------------------
diff --git a/cordova-common/src/.jshintrc b/cordova-common/src/.jshintrc
new file mode 100644
index 0000000..89a121c
--- /dev/null
+++ b/cordova-common/src/.jshintrc
@@ -0,0 +1,10 @@
+{
+    "node": true
+  , "bitwise": true
+  , "undef": true
+  , "trailing": true
+  , "quotmark": true
+  , "indent": 4
+  , "unused": "vars"
+  , "latedef": "nofunc"
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/src/ActionStack.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/ActionStack.js b/cordova-common/src/ActionStack.js
new file mode 100644
index 0000000..5ef6f84
--- /dev/null
+++ b/cordova-common/src/ActionStack.js
@@ -0,0 +1,85 @@
+/**
+    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.
+*/
+
+/* jshint quotmark:false */
+
+var events = require('./events'),
+    Q = require('q');
+
+function ActionStack() {
+    this.stack = [];
+    this.completed = [];
+}
+
+ActionStack.prototype = {
+    createAction:function(handler, action_params, reverter, revert_params) {
+        return {
+            handler:{
+                run:handler,
+                params:action_params
+            },
+            reverter:{
+                run:reverter,
+                params:revert_params
+            }
+        };
+    },
+    push:function(tx) {
+        this.stack.push(tx);
+    },
+    // Returns a promise.
+    process:function(platform) {
+        events.emit('verbose', 'Beginning processing of action stack for ' + platform + ' project...');
+
+        while (this.stack.length) {
+            var action = this.stack.shift();
+            var handler = action.handler.run;
+            var action_params = action.handler.params;
+
+            try {
+                handler.apply(null, action_params);
+            } catch(e) {
+                events.emit('warn', 'Error during processing of action! Attempting to revert...');
+                this.stack.unshift(action);
+                var issue = 'Uh oh!\n';
+                // revert completed tasks
+                while(this.completed.length) {
+                    var undo = this.completed.shift();
+                    var revert = undo.reverter.run;
+                    var revert_params = undo.reverter.params;
+
+                    try {
+                        revert.apply(null, revert_params);
+                    } catch(err) {
+                        events.emit('warn', 'Error during reversion of action! We probably really messed up your project now, sorry! D:');
+                        issue += 'A reversion action failed: ' + err.message + '\n';
+                    }
+                }
+                e.message = issue + e.message;
+                return Q.reject(e);
+            }
+            this.completed.push(action);
+        }
+        events.emit('verbose', 'Action stack processing complete.');
+
+        return Q();
+    }
+};
+
+module.exports = ActionStack;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/src/ConfigChanges/ConfigChanges.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/ConfigChanges/ConfigChanges.js b/cordova-common/src/ConfigChanges/ConfigChanges.js
new file mode 100644
index 0000000..8175ae7
--- /dev/null
+++ b/cordova-common/src/ConfigChanges/ConfigChanges.js
@@ -0,0 +1,401 @@
+/*
+ *
+ * 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 sub:true */
+
+var fs   = require('fs'),
+    path = require('path'),
+    et   = require('elementtree'),
+    semver = require('semver'),
+    events = require('../events'),
+    ConfigKeeper = require('./ConfigKeeper');
+
+var mungeutil = require('./munge-util');
+
+
+// These frameworks are required by cordova-ios by default. We should never add/remove them.
+var keep_these_frameworks = [
+    'MobileCoreServices.framework',
+    'CoreGraphics.framework',
+    'AssetsLibrary.framework'
+];
+
+
+exports.PlatformMunger = PlatformMunger;
+
+exports.process = function(plugins_dir, project_dir, platform, platformJson, pluginInfoProvider) {
+    var munger = new PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider);
+    munger.process(plugins_dir);
+    munger.save_all();
+};
+
+/******************************************************************************
+* 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, platformJson, pluginInfoProvider) {
+    this.platform = platform;
+    this.project_dir = project_dir;
+    this.config_keeper = new ConfigKeeper(project_dir);
+    this.platformJson = platformJson;
+    this.pluginInfoProvider = pluginInfoProvider;
+}
+
+// Write out all unsaved files.
+PlatformMunger.prototype.save_all = PlatformMunger_save_all;
+function PlatformMunger_save_all() {
+    this.config_keeper.save_all();
+    this.platformJson.save();
+}
+
+// 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');
+        // CoreLocation dependency removed in cordova-ios@3.6.0.
+        var keepFrameworks = keep_these_frameworks;
+        if (semver.lt(pbxproj.cordovaVersion, '3.6.0-dev')) {
+            keepFrameworks = keepFrameworks.concat(['CoreLocation.framework']);
+        }
+        for (var src in munge.parents) {
+            for (xml_child in munge.parents[src]) {
+                var weak = 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: weak});
+                    }
+                    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(pluginInfo, is_top_level) {
+    var self = this;
+    var platform_config = self.platformJson.root;
+    var plugin_vars = is_top_level ?
+        platform_config.installed_plugins[pluginInfo.id] :
+        platform_config.dependent_plugins[pluginInfo.id];
+
+    // get config munge, aka how did this plugin change various config files
+    var config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars);
+    // global munge looks at all plugins' changes to config files
+    var global_munge = platform_config.config_munge;
+    var munge = mungeutil.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 "' + pluginInfo.id + '" uses <plugins-plist> element(s), ' +
+                'which are no longer supported. Support has been removed as of Cordova 3.4.'
+            );
+            continue;
+        }
+        // CB-6976 Windows Universal Apps. Compatibility fix for existing plugins.
+        if (self.platform == 'windows' && file == 'package.appxmanifest' &&
+            !fs.existsSync(path.join(self.project_dir, 'package.appxmanifest'))) {
+            // New windows template separate manifest files for Windows8, Windows8.1 and WP8.1
+            var substs = ['package.phone.appxmanifest', 'package.windows.appxmanifest', 'package.windows80.appxmanifest', 'package.windows10.appxmanifest'];
+            /* jshint loopfunc:true */
+            substs.forEach(function(subst) {
+                events.emit('verbose', 'Applying munge to ' + subst);
+                self.apply_file_munge(subst, munge.files[file], true);
+            });
+            /* jshint loopfunc:false */
+        }
+        self.apply_file_munge(file, munge.files[file], /* remove = */ true);
+    }
+
+    // Remove from installed_plugins
+    self.platformJson.removePlugin(pluginInfo.id, is_top_level);
+    return self;
+}
+
+
+PlatformMunger.prototype.add_plugin_changes = add_plugin_changes;
+function add_plugin_changes(pluginInfo, plugin_vars, is_top_level, should_increment) {
+    var self = this;
+    var platform_config = self.platformJson.root;
+
+    // get config munge, aka how should this plugin change various config files
+    var config_munge = self.generate_plugin_config_munge(pluginInfo, 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 = mungeutil.increment_munge(global_munge, config_munge);
+    } else {
+        global_munge = mungeutil.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 "' + pluginInfo.id + '" uses <plugins-plist> element(s), ' +
+                'which are no longer supported. Support has been removed as of Cordova 3.4.'
+            );
+            continue;
+        }
+        // CB-6976 Windows Universal Apps. Compatibility fix for existing plugins.
+        if (self.platform == 'windows' && file == 'package.appxmanifest' &&
+            !fs.existsSync(path.join(self.project_dir, 'package.appxmanifest'))) {
+            var substs = ['package.phone.appxmanifest', 'package.windows.appxmanifest', 'package.windows80.appxmanifest', 'package.windows10.appxmanifest'];
+            /* jshint loopfunc:true */
+            substs.forEach(function(subst) {
+                events.emit('verbose', 'Applying munge to ' + subst);
+                self.apply_file_munge(subst, munge.files[file]);
+            });
+            /* jshint loopfunc:false */
+        }
+        self.apply_file_munge(file, munge.files[file]);
+    }
+
+    // Move to installed/dependent_plugins
+    self.platformJson.addPlugin(pluginInfo.id, plugin_vars || {}, is_top_level);
+    return self;
+}
+
+
+// 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 = self.platformJson.root;
+    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]);
+    }
+
+    return self;
+}
+
+
+// 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(pluginInfo, vars) {
+    var self = this;
+
+    vars = vars || {};
+    var munge = { files: {} };
+    var changes = pluginInfo.getConfigFiles(self.platform);
+
+    // note down pbxproj framework munges in special section of munge obj
+    // CB-5238 this is only for systems frameworks
+    if (self.platform === 'ios') {
+        var frameworks = pluginInfo.getFrameworks(self.platform);
+        frameworks.forEach(function (f) {
+            if (!f.custom) {
+                mungeutil.deep_add(munge, 'framework', f.src, { xml: f.weak, count: 1 });
+            }
+        });
+    }
+
+    // Demux 'package.appxmanifest' into relevant platform-specific appx manifests.
+    // Only spend the cycles if there are version-specific plugin settings
+    if (self.platform === 'windows' &&
+            changes.some(function(change) {
+                return ((typeof change.versions !== 'undefined') ||
+                    (typeof change.deviceTarget !== 'undefined'));
+            }))
+    {
+        var manifests = {
+            'windows': {
+                '8.0.0': 'package.windows80.appxmanifest',
+                '8.1.0': 'package.windows.appxmanifest',
+                '10.0.0': 'package.windows10.appxmanifest'
+            },
+            'phone': {
+                '8.1.0': 'package.phone.appxmanifest',
+                '10.0.0': 'package.windows10.appxmanifest'
+            },
+            'all': {
+                '8.0.0': 'package.windows80.appxmanifest',
+                '8.1.0': ['package.windows.appxmanifest', 'package.phone.appxmanifest'],
+                '10.0.0': 'package.windows10.appxmanifest'
+            }
+        };
+
+        var oldChanges = changes;
+        changes = [];
+
+        oldChanges.forEach(function(change, changeIndex) {
+            // Only support semver/device-target demux for package.appxmanifest
+            // Pass through in case something downstream wants to use it
+            if (change.target !== 'package.appxmanifest') {
+                changes.push(change);
+                return;
+            }
+
+            var hasVersion = (typeof change.versions !== 'undefined');
+            var hasTargets = (typeof change.deviceTarget !== 'undefined');
+
+            // No semver/device-target for this config-file, pass it through
+            if (!(hasVersion || hasTargets)) {
+                changes.push(change);
+                return;
+            }
+
+            var targetDeviceSet = hasTargets ? change.deviceTarget : 'all';
+            if (['windows', 'phone', 'all'].indexOf(targetDeviceSet) === -1) {
+                // target-device couldn't be resolved, fix it up here to a valid value
+                targetDeviceSet = 'all';
+            }
+            var knownWindowsVersionsForTargetDeviceSet = Object.keys(manifests[targetDeviceSet]);
+
+            // at this point, 'change' targets package.appxmanifest and has a version attribute
+            knownWindowsVersionsForTargetDeviceSet.forEach(function(winver) {
+                // This is a local function that creates the new replacement representing the
+                // mutation.  Used to save code further down.
+                var createReplacement = function(manifestFile, originalChange) {
+                    var replacement = {
+                        target:         manifestFile,
+                        parent:         originalChange.parent,
+                        after:          originalChange.after,
+                        xmls:           originalChange.xmls,
+                        versions:       originalChange.versions,
+                        deviceTarget:   originalChange.deviceTarget
+                    };
+                    return replacement;
+                };
+
+                // version doesn't satisfy, so skip
+                if (hasVersion && !semver.satisfies(winver, change.versions)) {
+                    return;
+                }
+
+                var versionSpecificManifests = manifests[targetDeviceSet][winver];
+                if (versionSpecificManifests.constructor === Array) {
+                    // e.g. all['8.1.0'] === ['pkg.windows.appxmanifest', 'pkg.phone.appxmanifest']
+                    versionSpecificManifests.forEach(function(manifestFile) {
+                        changes.push(createReplacement(manifestFile, change));
+                    });
+                }
+                else {
+                    // versionSpecificManifests is actually a single string
+                    changes.push(createReplacement(versionSpecificManifests, change));
+                }
+            });
+        });
+    }
+
+    changes.forEach(function(change) {
+        change.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
+            mungeutil.deep_add(munge, change.target, change.parent, { xml: stringified, count: 1, after: change.after });
+        });
+    });
+    return munge;
+}
+
+// Go over the prepare queue and apply the config munges for each plugin
+// that has been (un)installed.
+PlatformMunger.prototype.process = PlatformMunger_process;
+function PlatformMunger_process(plugins_dir) {
+    var self = this;
+    var platform_config = self.platformJson.root;
+
+    // Uninstallation first
+    platform_config.prepare_queue.uninstalled.forEach(function(u) {
+        var pluginInfo = self.pluginInfoProvider.get(path.join(plugins_dir, u.plugin));
+        self.remove_plugin_changes(pluginInfo, u.topLevel);
+    });
+
+    // Now handle installation
+    platform_config.prepare_queue.installed.forEach(function(u) {
+        var pluginInfo = self.pluginInfoProvider.get(path.join(plugins_dir, u.plugin));
+        self.add_plugin_changes(pluginInfo, u.vars, u.topLevel, true);
+    });
+
+    // Empty out installed/ uninstalled queues.
+    platform_config.prepare_queue.uninstalled = [];
+    platform_config.prepare_queue.installed = [];
+}
+/**** END of PlatformMunger ****/

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/src/ConfigChanges/ConfigFile.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/ConfigChanges/ConfigFile.js b/cordova-common/src/ConfigChanges/ConfigFile.js
new file mode 100644
index 0000000..a531e1f
--- /dev/null
+++ b/cordova-common/src/ConfigChanges/ConfigFile.js
@@ -0,0 +1,220 @@
+/*
+ * 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 fs = require('fs');
+var path = require('path');
+
+var bplist = require('bplist-parser');
+var et   = require('elementtree');
+var glob = require('glob');
+var plist = require('plist');
+var xcode = require('xcode');
+
+var plist_helpers = require('../util/plist-helpers');
+var xml_helpers = require('../util/xml-helpers');
+
+/******************************************************************************
+* 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).parseSync();
+        self.cordovaVersion = fs.readFileSync(path.join(self.project_dir, 'CordovaLib', 'VERSION'), 'utf8').trim();
+    } 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.data = isBinaryPlist(filepath) ?
+                bplist.parseBuffer(fs.readFileSync(filepath)) :
+                plist.parse(fs.readFileSync(filepath, 'utf8'));
+    }
+}
+
+ConfigFile.prototype.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.prototype.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.prototype.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;
+};
+
+// 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];
+
+        // [CB-5989] multiple Info.plist files may exist. default to $PROJECT_NAME-Info.plist
+        if(matches.length > 1 && file.indexOf('-Info.plist')>-1){
+            var plistName =  getIOSProjectname(project_dir)+'-Info.plist';
+            for (var i=0; i < matches.length; i++) {
+                if(matches[i].indexOf(plistName) > -1){
+                    filepath = matches[i];
+                    break;
+                }
+            }
+        }
+        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;
+}
+
+// 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;
+}
+
+// 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';
+}
+
+module.exports = ConfigFile;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/src/ConfigChanges/ConfigKeeper.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/ConfigChanges/ConfigKeeper.js b/cordova-common/src/ConfigChanges/ConfigKeeper.js
new file mode 100644
index 0000000..894e922
--- /dev/null
+++ b/cordova-common/src/ConfigChanges/ConfigKeeper.js
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ *
+*/
+/* jshint sub:true */
+
+var path = require('path');
+var ConfigFile = require('./ConfigFile');
+
+/******************************************************************************
+* 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(project_dir, plugins_dir) {
+    this.project_dir = project_dir;
+    this.plugins_dir = plugins_dir;
+    this._cached = {};
+}
+
+ConfigKeeper.prototype.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 = 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();
+    });
+};
+
+module.exports = ConfigKeeper;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/src/ConfigChanges/munge-util.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/ConfigChanges/munge-util.js b/cordova-common/src/ConfigChanges/munge-util.js
new file mode 100644
index 0000000..307b3c1
--- /dev/null
+++ b/cordova-common/src/ConfigChanges/munge-util.js
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ *
+*/
+/* jshint sub:true  */
+
+var _ = require('underscore');
+
+// add the count of [key1][key2]...[keyN] to obj
+// return true if it didn't exist before
+exports.deep_add = function deep_add(obj, keys /* or key1, key2 .... */ ) {
+    if ( !Array.isArray(keys) ) {
+        keys = Array.prototype.slice.call(arguments, 1);
+    }
+
+    return exports.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
+exports.deep_remove = function deep_remove(obj, keys /* or key1, key2 .... */ ) {
+    if ( !Array.isArray(keys) ) {
+        keys = Array.prototype.slice.call(arguments, 1);
+    }
+
+    var result = exports.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
+exports.deep_find = function deep_find(obj, keys /* or key1, key2 .... */ ) {
+    if ( !Array.isArray(keys) ) {
+        keys = Array.prototype.slice.call(arguments, 1);
+    }
+
+    return exports.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
+
+exports.process_munge = 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 exports.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 exports.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] += munge[file][selector][child]
+// Returns a munge object containing values that exist in munge
+// but not in base.
+exports.increment_munge = 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 = exports.deep_add(base, [file, selector, val]);
+                if (newlyAdded) {
+                    exports.deep_add(diff, file, selector, val);
+                }
+            }
+        }
+    }
+    return diff;
+};
+
+// Update the base munge object as
+// base[file][selector][child] -= munge[file][selector][child]
+// nodes that reached zero value are removed from base and added to the returned munge
+// object.
+exports.decrement_munge = 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 = exports.deep_remove(base, [file, selector, val]);
+                if (removed) {
+                    exports.deep_add(zeroed, file, selector, val);
+                }
+            }
+        }
+    }
+    return zeroed;
+};
+
+// For better readability where used
+exports.clone_munge = function clone_munge(munge) {
+    return exports.increment_munge({}, munge);
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/src/CordovaError.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/CordovaError.js b/cordova-common/src/CordovaError.js
new file mode 100644
index 0000000..d9989c4
--- /dev/null
+++ b/cordova-common/src/CordovaError.js
@@ -0,0 +1,32 @@
+/**
+    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.
+*/
+
+/* jshint proto:true */
+
+// A derived exception class. See usage example in cli.js
+// Based on:
+// stackoverflow.com/questions/1382107/whats-a-good-way-to-extend-error-in-javascript/8460753#8460753
+function CordovaError(message) {
+    Error.captureStackTrace(this, this.constructor);
+    this.name = this.constructor.name;
+    this.message = message;
+}
+CordovaError.prototype.__proto__ = Error.prototype;
+
+module.exports = CordovaError;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/src/PlatformJson.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/PlatformJson.js b/cordova-common/src/PlatformJson.js
new file mode 100644
index 0000000..793e976
--- /dev/null
+++ b/cordova-common/src/PlatformJson.js
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ *
+*/
+/* jshint sub:true */
+
+var fs = require('fs');
+var path = require('path');
+var shelljs = require('shelljs');
+var mungeutil = require('./ConfigChanges/munge-util');
+var pluginMappernto = require('cordova-registry-mapper').newToOld;
+var pluginMapperotn = require('cordova-registry-mapper').oldToNew;
+
+function PlatformJson(filePath, platform, root) {
+    this.filePath = filePath;
+    this.platform = platform;
+    this.root = fix_munge(root || {});
+}
+
+PlatformJson.load = function(plugins_dir, platform) {
+    var filePath = path.join(plugins_dir, platform + '.json');
+    var root = null;
+    if (fs.existsSync(filePath)) {
+        root = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
+    }
+    return new PlatformJson(filePath, platform, root);
+};
+
+PlatformJson.prototype.save = function() {
+    shelljs.mkdir('-p', path.dirname(this.filePath));
+    fs.writeFileSync(this.filePath, JSON.stringify(this.root, null, 4), 'utf-8');
+};
+
+/**
+ * Indicates whether the specified plugin is installed as a top-level (not as
+ *  dependency to others)
+ * @method function
+ * @param  {String} pluginId A plugin id to check for.
+ * @return {Boolean} true if plugin installed as top-level, otherwise false.
+ */
+PlatformJson.prototype.isPluginTopLevel = function(pluginId) {
+    var installedPlugins = this.root.installed_plugins;
+    return installedPlugins[pluginId] ||
+        installedPlugins[pluginMappernto[pluginId]] ||
+        installedPlugins[pluginMapperotn[pluginId]];
+};
+
+/**
+ * Indicates whether the specified plugin is installed as a dependency to other
+ *  plugin.
+ * @method function
+ * @param  {String} pluginId A plugin id to check for.
+ * @return {Boolean} true if plugin installed as a dependency, otherwise false.
+ */
+PlatformJson.prototype.isPluginDependent = function(pluginId) {
+    var dependentPlugins = this.root.dependent_plugins;
+    return dependentPlugins[pluginId] ||
+        dependentPlugins[pluginMappernto[pluginId]] ||
+        dependentPlugins[pluginMapperotn[pluginId]];
+};
+
+/**
+ * Indicates whether plugin is installed either as top-level or as dependency.
+ * @method function
+ * @param  {String} pluginId A plugin id to check for.
+ * @return {Boolean} true if plugin installed, otherwise false.
+ */
+PlatformJson.prototype.isPluginInstalled = function(pluginId) {
+    return this.isPluginTopLevel(pluginId) ||
+        this.isPluginDependent(pluginId);
+};
+
+PlatformJson.prototype.addPlugin = function(pluginId, variables, isTopLevel) {
+    var pluginsList = isTopLevel ?
+        this.root.installed_plugins :
+        this.root.dependent_plugins;
+
+    pluginsList[pluginId] = variables;
+
+    return this;
+};
+
+PlatformJson.prototype.removePlugin = function(pluginId, isTopLevel) {
+    var pluginsList = isTopLevel ?
+        this.root.installed_plugins :
+        this.root.dependent_plugins;
+
+    delete pluginsList[pluginId];
+
+    return this;
+};
+
+PlatformJson.prototype.addInstalledPluginToPrepareQueue = function(pluginDirName, vars, is_top_level) {
+    this.root.prepare_queue.installed.push({'plugin':pluginDirName, 'vars':vars, 'topLevel':is_top_level});
+};
+
+PlatformJson.prototype.addUninstalledPluginToPrepareQueue = function(pluginId, is_top_level) {
+    this.root.prepare_queue.uninstalled.push({'plugin':pluginId, 'id':pluginId, 'topLevel':is_top_level});
+};
+
+/**
+ * Moves plugin, specified by id to top-level plugins. If plugin is top-level
+ *  already, then does nothing.
+ * @method function
+ * @param  {String} pluginId A plugin id to make top-level.
+ * @return {PlatformJson} PlatformJson instance.
+ */
+PlatformJson.prototype.makeTopLevel = function(pluginId) {
+    var plugin = this.root.dependent_plugins[pluginId];
+    if (plugin) {
+        delete this.root.dependent_plugins[pluginId];
+        this.root.installed_plugins[pluginId] = plugin;
+    }
+    return this;
+};
+
+// convert a munge from the old format ([file][parent][xml] = count) to the current one
+function fix_munge(root) {
+    root.prepare_queue = root.prepare_queue || {installed:[], uninstalled:[]};
+    root.config_munge = root.config_munge || {files: {}};
+    root.installed_plugins = root.installed_plugins || {};
+    root.dependent_plugins = root.dependent_plugins || {};
+
+    var munge = root.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++) {
+                        mungeutil.deep_add(new_munge, [file, selector, { xml: xml_child, count: val }]);
+                    }
+                }
+            }
+        }
+        root.config_munge = new_munge;
+    }
+
+    return root;
+}
+
+module.exports = PlatformJson;
+

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/src/PluginInfo/PluginInfo.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/PluginInfo/PluginInfo.js b/cordova-common/src/PluginInfo/PluginInfo.js
new file mode 100644
index 0000000..10cfdf0
--- /dev/null
+++ b/cordova-common/src/PluginInfo/PluginInfo.js
@@ -0,0 +1,416 @@
+/**
+    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.
+*/
+
+/* jshint sub:true, laxcomma:true, laxbreak:true */
+
+/*
+A class for holidng the information currently stored in plugin.xml
+It should also be able to answer questions like whether the plugin
+is compatible with a given engine version.
+
+TODO (kamrik): refactor this to not use sync functions and return promises.
+*/
+
+
+var path = require('path')
+  , fs = require('fs')
+  , xml_helpers = require('../util/xml-helpers')
+  , CordovaError = require('../CordovaError')
+  ;
+
+function PluginInfo(dirname) {
+    var self = this;
+
+    // METHODS
+    // Defined inside the constructor to avoid the "this" binding problems.
+
+    // <preference> tag
+    // Example: <preference name="API_KEY" />
+    // Used to require a variable to be specified via --variable when installing the plugin.
+    self.getPreferences = getPreferences;
+    function getPreferences(platform) {
+        var arprefs = _getTags(self._et, 'preference', platform, _parsePreference);
+
+        var prefs= {};
+        for(var i in arprefs)
+        {
+            var pref=arprefs[i];
+            prefs[pref.preference]=pref.default;
+        }
+        // returns { key : default | null}
+        return prefs;
+    }
+
+    function _parsePreference(prefTag) {
+        var name = prefTag.attrib.name.toUpperCase();
+        var def = prefTag.attrib.default || null;
+        return {preference: name, default: def};
+    }
+
+    // <asset>
+    self.getAssets = getAssets;
+    function getAssets(platform) {
+        var assets = _getTags(self._et, 'asset', platform, _parseAsset);
+        return assets;
+    }
+
+    function _parseAsset(tag) {
+        var src = tag.attrib.src;
+        var target = tag.attrib.target;
+
+        if ( !src || !target) {
+            var msg =
+                'Malformed <asset> tag. Both "src" and "target" attributes'
+                + 'must be specified in\n'
+                + self.filepath
+                ;
+            throw new Error(msg);
+        }
+
+        var asset = {
+            itemType: 'asset',
+            src: src,
+            target: target
+        };
+        return asset;
+    }
+
+
+    // <dependency>
+    // Example:
+    // <dependency id="com.plugin.id"
+    //     url="https://github.com/myuser/someplugin"
+    //     commit="428931ada3891801"
+    //     subdir="some/path/here" />
+    self.getDependencies = getDependencies;
+    function getDependencies(platform) {
+        var deps = _getTags(
+                self._et,
+                'dependency',
+                platform,
+                _parseDependency
+        );
+        return deps;
+    }
+
+    function _parseDependency(tag) {
+        var dep =
+            { id : tag.attrib.id
+            , url : tag.attrib.url || ''
+            , subdir : tag.attrib.subdir || ''
+            , commit : tag.attrib.commit
+            };
+
+        dep.git_ref = dep.commit;
+
+        if ( !dep.id ) {
+            var msg =
+                '<dependency> tag is missing id attribute in '
+                + self.filepath
+                ;
+            throw new CordovaError(msg);
+        }
+        return dep;
+    }
+
+
+    // <config-file> tag
+    self.getConfigFiles = getConfigFiles;
+    function getConfigFiles(platform) {
+        var configFiles = _getTags(self._et, 'config-file', platform, _parseConfigFile);
+        return configFiles;
+    }
+
+    function _parseConfigFile(tag) {
+        var configFile =
+            { target : tag.attrib['target']
+            , parent : tag.attrib['parent']
+            , after : tag.attrib['after']
+            , xmls : tag.getchildren()
+            // To support demuxing via versions
+            , versions : tag.attrib['versions']
+            , deviceTarget: tag.attrib['device-target']
+            };
+        return configFile;
+    }
+
+    // <info> tags, both global and within a <platform>
+    // TODO (kamrik): Do we ever use <info> under <platform>? Example wanted.
+    self.getInfo = getInfo;
+    function getInfo(platform) {
+        var infos = _getTags(
+                self._et,
+                'info',
+                platform,
+                function(elem) { return elem.text; }
+        );
+        // Filter out any undefined or empty strings.
+        infos = infos.filter(Boolean);
+        return infos;
+    }
+
+    // <source-file>
+    // Examples:
+    // <source-file src="src/ios/someLib.a" framework="true" />
+    // <source-file src="src/ios/someLib.a" compiler-flags="-fno-objc-arc" />
+    self.getSourceFiles = getSourceFiles;
+    function getSourceFiles(platform) {
+        var sourceFiles = _getTagsInPlatform(self._et, 'source-file', platform, _parseSourceFile);
+        return sourceFiles;
+    }
+
+    function _parseSourceFile(tag) {
+        return {
+            itemType: 'source-file',
+            src: tag.attrib.src,
+            framework: isStrTrue(tag.attrib.framework),
+            weak: isStrTrue(tag.attrib.weak),
+            compilerFlags: tag.attrib['compiler-flags'],
+            targetDir: tag.attrib['target-dir']
+        };
+    }
+
+    // <header-file>
+    // Example:
+    // <header-file src="CDVFoo.h" />
+    self.getHeaderFiles = getHeaderFiles;
+    function getHeaderFiles(platform) {
+        var headerFiles = _getTagsInPlatform(self._et, 'header-file', platform, function(tag) {
+            return {
+                itemType: 'header-file',
+                src: tag.attrib.src,
+                targetDir: tag.attrib['target-dir']
+            };
+        });
+        return headerFiles;
+    }
+
+    // <resource-file>
+    // Example:
+    // <resource-file src="FooPluginStrings.xml" target="res/values/FooPluginStrings.xml" device-target="win" arch="x86" versions="&gt;=8.1" />
+    self.getResourceFiles = getResourceFiles;
+    function getResourceFiles(platform) {
+        var resourceFiles = _getTagsInPlatform(self._et, 'resource-file', platform, function(tag) {
+            return {
+                itemType: 'resource-file',
+                src: tag.attrib.src,
+                target: tag.attrib.target,
+                versions: tag.attrib.versions,
+                deviceTarget: tag.attrib['device-target'],
+                arch: tag.attrib.arch
+            };
+        });
+        return resourceFiles;
+    }
+
+    // <lib-file>
+    // Example:
+    // <lib-file src="src/BlackBerry10/native/device/libfoo.so" arch="device" />
+    self.getLibFiles = getLibFiles;
+    function getLibFiles(platform) {
+        var libFiles = _getTagsInPlatform(self._et, 'lib-file', platform, function(tag) {
+            return {
+                itemType: 'lib-file',
+                src: tag.attrib.src,
+                arch: tag.attrib.arch,
+                Include: tag.attrib.Include,
+                versions: tag.attrib.versions,
+                deviceTarget: tag.attrib['device-target'] || tag.attrib.target
+            };
+        });
+        return libFiles;
+    }
+
+    // <hook>
+    // Example:
+    // <hook type="before_build" src="scripts/beforeBuild.js" />
+    self.getHookScripts = getHookScripts;
+    function getHookScripts(hook, platforms) {
+        var scriptElements =  self._et.findall('./hook');
+
+        if(platforms) {
+            platforms.forEach(function (platform) {
+                scriptElements = scriptElements.concat(self._et.findall('./platform[@name="' + platform + '"]/hook'));
+            });
+        }
+
+        function filterScriptByHookType(el) {
+            return el.attrib.src && el.attrib.type && el.attrib.type.toLowerCase() === hook;
+        }
+
+        return scriptElements.filter(filterScriptByHookType);
+    }
+
+    self.getJsModules = getJsModules;
+    function getJsModules(platform) {
+        var modules = _getTags(self._et, 'js-module', platform, _parseJsModule);
+        return modules;
+    }
+
+    function _parseJsModule(tag) {
+        var ret = {
+            itemType: 'js-module',
+            name: tag.attrib.name,
+            src: tag.attrib.src,
+            clobbers: tag.findall('clobbers').map(function(tag) { return { target: tag.attrib.target }; }),
+            merges: tag.findall('merges').map(function(tag) { return { target: tag.attrib.target }; }),
+            runs: tag.findall('runs').length > 0
+        };
+
+        return ret;
+    }
+
+    self.getEngines = function() {
+        return self._et.findall('engines/engine').map(function(n) {
+            return {
+                name: n.attrib.name,
+                version: n.attrib.version,
+                platform: n.attrib.platform,
+                scriptSrc: n.attrib.scriptSrc
+            };
+        });
+    };
+
+    self.getPlatforms = function() {
+        return self._et.findall('platform').map(function(n) {
+            return { name: n.attrib.name };
+        });
+    };
+
+    self.getPlatformsArray = function() {
+        return self._et.findall('platform').map(function(n) {
+            return n.attrib.name;
+        });
+    };
+    self.getFrameworks = function(platform) {
+        return _getTags(self._et, 'framework', platform, function(el) {
+            var ret = {
+                itemType: 'framework',
+                type: el.attrib.type,
+                parent: el.attrib.parent,
+                custom: isStrTrue(el.attrib.custom),
+                src: el.attrib.src,
+                weak: isStrTrue(el.attrib.weak),
+                versions: el.attrib.versions,
+                targetDir: el.attrib['target-dir'],
+                deviceTarget: el.attrib['device-target'] || el.attrib.target,
+                arch: el.attrib.arch
+            };
+            return ret;
+        });
+    };
+
+    self.getFilesAndFrameworks = getFilesAndFrameworks;
+    function getFilesAndFrameworks(platform) {
+        // Please avoid changing the order of the calls below, files will be
+        // installed in this order.
+        var items = [].concat(
+            self.getSourceFiles(platform),
+            self.getHeaderFiles(platform),
+            self.getResourceFiles(platform),
+            self.getFrameworks(platform),
+            self.getLibFiles(platform)
+        );
+        return items;
+    }
+    ///// End of PluginInfo methods /////
+
+
+    ///// PluginInfo Constructor logic  /////
+    self.filepath = path.join(dirname, 'plugin.xml');
+    if (!fs.existsSync(self.filepath)) {
+        throw new CordovaError('Cannot find plugin.xml for plugin \'' + path.basename(dirname) + '\'. Please try adding it again.');
+    }
+
+    self.dir = dirname;
+    var et = self._et = xml_helpers.parseElementtreeSync(self.filepath);
+    var pelem = et.getroot();
+    self.id = pelem.attrib.id;
+    self.version = pelem.attrib.version;
+
+    // Optional fields
+    self.name = pelem.findtext('name');
+    self.description = pelem.findtext('description');
+    self.license = pelem.findtext('license');
+    self.repo = pelem.findtext('repo');
+    self.issue = pelem.findtext('issue');
+    self.keywords = pelem.findtext('keywords');
+    self.info = pelem.findtext('info');
+    if (self.keywords) {
+        self.keywords = self.keywords.split(',').map( function(s) { return s.trim(); } );
+    }
+    self.getKeywordsAndPlatforms = function () {
+        var ret = self.keywords || [];
+        return ret.concat('ecosystem:cordova').concat(addCordova(self.getPlatformsArray()));
+    };
+}  // End of PluginInfo constructor.
+
+// Helper function used to prefix every element of an array with cordova-
+// Useful when we want to modify platforms to be cordova-platform
+function addCordova(someArray) {
+    var newArray = someArray.map(function(element) {
+        return 'cordova-' + element;
+    });
+    return newArray;
+}
+
+// Helper function used by most of the getSomething methods of PluginInfo.
+// Get all elements of a given name. Both in root and in platform sections
+// for the given platform. If transform is given and is a function, it is
+// applied to each element.
+function _getTags(pelem, tag, platform, transform) {
+    var platformTag = pelem.find('./platform[@name="' + platform + '"]');
+    if (platform == 'windows' && !platformTag) {
+        platformTag = pelem.find('platform[@name="' + 'windows8' + '"]');
+    }
+    var tagsInRoot = pelem.findall(tag);
+    tagsInRoot = tagsInRoot || [];
+    var tagsInPlatform = platformTag ? platformTag.findall(tag) : [];
+    var tags = tagsInRoot.concat(tagsInPlatform);
+    if ( typeof transform === 'function' ) {
+        tags = tags.map(transform);
+    }
+    return tags;
+}
+
+// Same as _getTags() but only looks inside a platfrom section.
+function _getTagsInPlatform(pelem, tag, platform, transform) {
+    var platformTag = pelem.find('./platform[@name="' + platform + '"]');
+    if (platform == 'windows' && !platformTag) {
+        platformTag = pelem.find('platform[@name="' + 'windows8' + '"]');
+    }
+    var tags = platformTag ? platformTag.findall(tag) : [];
+    if ( typeof transform === 'function' ) {
+        tags = tags.map(transform);
+    }
+    return tags;
+}
+
+// Check if x is a string 'true'.
+function isStrTrue(x) {
+    return String(x).toLowerCase() == 'true';
+}
+
+module.exports = PluginInfo;
+// Backwards compat:
+PluginInfo.PluginInfo = PluginInfo;
+PluginInfo.loadPluginsDir = function(dir) {
+    var PluginInfoProvider = require('./PluginInfoProvider');
+    return new PluginInfoProvider().getAllWithinSearchPath(dir);
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/28ce0d1d/cordova-common/src/PluginInfo/PluginInfoProvider.js
----------------------------------------------------------------------
diff --git a/cordova-common/src/PluginInfo/PluginInfoProvider.js b/cordova-common/src/PluginInfo/PluginInfoProvider.js
new file mode 100644
index 0000000..6240119
--- /dev/null
+++ b/cordova-common/src/PluginInfo/PluginInfoProvider.js
@@ -0,0 +1,82 @@
+/**
+    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.
+*/
+
+/* jshint sub:true, laxcomma:true, laxbreak:true */
+
+var fs = require('fs');
+var path = require('path');
+var PluginInfo = require('./PluginInfo');
+var events = require('../events');
+
+function PluginInfoProvider() {
+    this._cache = {};
+    this._getAllCache = {};
+}
+
+PluginInfoProvider.prototype.get = function(dirName) {
+    var absPath = path.resolve(dirName);
+    if (!this._cache[absPath]) {
+        this._cache[absPath] = new PluginInfo(dirName);
+    }
+    return this._cache[absPath];
+};
+
+// Normally you don't need to put() entries, but it's used
+// when copying plugins, and in unit tests.
+PluginInfoProvider.prototype.put = function(pluginInfo) {
+    var absPath = path.resolve(pluginInfo.dir);
+    this._cache[absPath] = pluginInfo;
+};
+
+// Used for plugin search path processing.
+// Given a dir containing multiple plugins, create a PluginInfo object for
+// each of them and return as array.
+// Should load them all in parallel and return a promise, but not yet.
+PluginInfoProvider.prototype.getAllWithinSearchPath = function(dirName) {
+    var absPath = path.resolve(dirName);
+    if (!this._getAllCache[absPath]) {
+        this._getAllCache[absPath] = getAllHelper(absPath, this);
+    }
+    return this._getAllCache[absPath];
+};
+
+function getAllHelper(absPath, provider) {
+    if (!fs.existsSync(absPath)){
+        return [];
+    }
+    // If dir itself is a plugin, return it in an array with one element.
+    if (fs.existsSync(path.join(absPath, 'plugin.xml'))) {
+        return [provider.get(absPath)];
+    }
+    var subdirs = fs.readdirSync(absPath);
+    var plugins = [];
+    subdirs.forEach(function(subdir) {
+        var d = path.join(absPath, subdir);
+        if (fs.existsSync(path.join(d, 'plugin.xml'))) {
+            try {
+                plugins.push(provider.get(d));
+            } catch (e) {
+                events.emit('warn', 'Error parsing ' + path.join(d, 'plugin.xml.\n' + e.stack));
+            }
+        }
+    });
+    return plugins;
+}
+
+module.exports = PluginInfoProvider;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org