You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by au...@apache.org on 2017/08/29 23:55:24 UTC

[1/4] cordova-lib git commit: CB-12870 : catch all use cases for getPlatformApiFunc and update tests accordingly

Repository: cordova-lib
Updated Branches:
  refs/heads/master 812373df4 -> 6b98390ec


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js
----------------------------------------------------------------------
diff --git a/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js
index bb56cda..125e3c5 100644
--- a/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js
+++ b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js
@@ -1,3 +1,364 @@
-module.exports = function PlatformApi (argument) {
-    this.platform = 'windows';
+/**
+    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');
+var events = require('cordova-common').events;
+var JsprojManager = require('./lib/JsprojManager');
+var PluginManager = require('cordova-common').PluginManager;
+var CordovaLogger = require('cordova-common').CordovaLogger;
+var PlatformMunger = require('./lib/ConfigChanges.js').PlatformMunger;
+var PlatformJson = require('cordova-common').PlatformJson;
+var PluginInfo = require('./lib/PluginInfo').PluginInfo;
+var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
+
+var PLATFORM = 'windows';
+
+function setupEvents(externalEventEmitter) {
+    if (externalEventEmitter) {
+        // This will make the platform internal events visible outside
+        events.forwardEventsTo(externalEventEmitter);
+        return;
+    }
+
+    // There is no logger if external emitter is not present,
+    // so attach a console logger
+    CordovaLogger.get().subscribe(events);
+}
+
+/**
+ * Class, that acts as abstraction over particular platform. Encapsulates the
+ *   platform's properties and methods.
+ *
+ * Platform that implements own PlatformApi instance _should implement all
+ *   prototype methods_ of this class to be fully compatible with cordova-lib.
+ *
+ * The PlatformApi instance also should define the following field:
+ *
+ * * platform: String that defines a platform name.
+ */
+function Api(platform, platformRootDir, eventEmitter) {
+    this.platform = PLATFORM;
+    this.root = path.resolve(__dirname, '..');
+
+    setupEvents(eventEmitter);
+
+    var self = this;
+    this.locations = {
+        root: self.root,
+        www: path.join(self.root, 'www'),
+        platformWww: path.join(self.root, 'platform_www'),
+        configXml: path.join(self.root, 'config.xml'),
+        defaultConfigXml: path.join(self.root, 'cordova/defaults.xml'),
+        // NOTE: Due to platformApi spec we need to return relative paths here
+        cordovaJs: 'template/www/cordova.js',
+        cordovaJsSrc: 'cordova-js-src'
+    };
+}
+
+/**
+ * Installs platform to specified directory and creates a platform project.
+ *
+ * @param  {String}  destinationDir  A directory, where platform should be
+ *   created/installed.
+ * @param  {ConfigParser} [projectConfig] A ConfigParser instance, used to get
+ *   some application properties for new platform like application name, package
+ *   id, etc. If not defined, this means that platform is used as standalone
+ *   project and is not a part of cordova project, so platform will use some
+ *   default values.
+ * @param  {Object}   [options]  An options object. The most common options are:
+ * @param  {String}   [options.customTemplate]  A path to custom template, that
+ *   should override the default one from platform.
+ * @param  {Boolean}  [options.link=false]  Flag that indicates that platform's
+ *   sources will be linked to installed platform instead of copying.
+ *
+ * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
+ *   instance or rejected with CordovaError.
+ */
+Api.createPlatform = function (destinationDir, projectConfig, options, events) {
+    setupEvents(events);
+    var result;
+
+    try {
+        result = require('../../bin/lib/create')
+        .create(destinationDir, projectConfig, options)
+        .then(function () {
+            var PlatformApi = require(path.resolve(destinationDir, 'cordova/Api'));
+            return new PlatformApi(PLATFORM, destinationDir, events);
+        });
+    }
+    catch(e) {
+        events.emit('error','createPlatform is not callable from the windows project API.');
+        throw(e);
+    }
+
+    return result;
 };
+
+/**
+ * Updates already installed platform.
+ *
+ * @param  {String}  destinationDir  A directory, where existing platform
+ *   installed, that should be updated.
+ * @param  {Object}  [options]  An options object. The most common options are:
+ * @param  {String}  [options.customTemplate]  A path to custom template, that
+ *   should override the default one from platform.
+ * @param  {Boolean}  [options.link=false]  Flag that indicates that platform's sources
+ *   will be linked to installed platform instead of copying.
+ * @param {EventEmitter} [events] The emitter that will be used for logging
+ *
+ * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
+ *   instance or rejected with CordovaError.
+ */
+Api.updatePlatform = function (destinationDir, options, events) {
+    setupEvents(events);
+    try {
+        return require('../../bin/lib/update')
+        .update(destinationDir, options)
+        .then(function () {
+            var PlatformApi = require(path.resolve(destinationDir, 'cordova/Api'));
+            return new PlatformApi(PLATFORM, destinationDir, events);
+        });
+    }
+    catch(e) {
+        events.emit('error','updatePlatform is not callable from the windows project API.');
+        throw(e);
+    }
+};
+
+/**
+ * Gets a CordovaPlatform object, that represents the platform structure.
+ *
+ * @return  {CordovaPlatform}  A structure that contains the description of
+ *   platform's file structure and other properties of platform.
+ */
+Api.prototype.getPlatformInfo = function () {
+
+    var result = {};
+    result.locations = this.locations;
+    result.root = this.root;
+    result.name = this.platform;
+    result.version = require('./version');
+    result.projectConfig = this._config;
+
+    return result;
+};
+
+/**
+ * Updates installed platform with provided www assets and new app
+ *   configuration. This method is required for CLI workflow and will be called
+ *   each time before build, so the changes, made to app configuration and www
+ *   code, will be applied to platform.
+ *
+ * @param {CordovaProject} cordovaProject A CordovaProject instance, that defines a
+ *   project structure and configuration, that should be applied to platform
+ *   (contains project's www location and ConfigParser instance for project's
+ *   config).
+ *
+ * @return  {Promise}  Return a promise either fulfilled, or rejected with
+ *   CordovaError instance.
+ */
+Api.prototype.prepare = function (cordovaProject, prepareOptions) {
+    return require('./lib/prepare').prepare.call(this, cordovaProject, prepareOptions);
+};
+
+/**
+ * Installs a new plugin into platform. This method only copies non-www files
+ *   (sources, libs, etc.) to platform. It also doesn't resolves the
+ *   dependencies of plugin. Both of handling of www files, such as assets and
+ *   js-files and resolving dependencies are the responsibility of caller.
+ *
+ * @param  {PluginInfo}  plugin  A PluginInfo instance that represents plugin
+ *   that will be installed.
+ * @param  {Object}  installOptions  An options object. Possible options below:
+ * @param  {Boolean}  installOptions.link: Flag that specifies that plugin
+ *   sources will be symlinked to app's directory instead of copying (if
+ *   possible).
+ * @param  {Object}  installOptions.variables  An object that represents
+ *   variables that will be used to install plugin. See more details on plugin
+ *   variables in documentation:
+ *   https://cordova.apache.org/docs/en/4.0.0/plugin_ref_spec.md.html
+ *
+ * @return  {Promise}  Return a promise either fulfilled, or rejected with
+ *   CordovaError instance.
+ */
+Api.prototype.addPlugin = function (plugin, installOptions) {
+
+    var self = this;
+
+    // We need to use custom PluginInfo to trigger windows-specific processing
+    // of changes in .appxmanifest files. See PluginInfo.js for details
+    var pluginInfo = new PluginInfo(plugin.dir);
+    var jsProject = JsprojManager.getProject(this.root);
+    installOptions = installOptions || {};
+    installOptions.variables = installOptions.variables || {};
+    // Add PACKAGE_NAME variable into vars
+    if (!installOptions.variables.PACKAGE_NAME) {
+        installOptions.variables.PACKAGE_NAME = jsProject.getPackageName();
+    }
+
+    var platformJson = PlatformJson.load(this.root, this.platform);
+    var pluginManager = PluginManager.get(this.platform, this.locations, jsProject);
+    pluginManager.munger = new PlatformMunger(this.platform, this.locations.root, platformJson, new PluginInfoProvider());
+    return pluginManager
+        .addPlugin(pluginInfo, installOptions)
+        .then(function () {
+            // CB-11657 Add BOM to www files here because files added by plugin
+            // probably don't have it. Prepare would add BOM but it might not be called
+            return require('./lib/prepare').addBOMSignature(self.locations.www);
+        })
+        // CB-11022 return non-falsy value to indicate
+        // that there is no need to run prepare after
+        .thenResolve(true);
+};
+
+/**
+ * Removes an installed plugin from platform.
+ *
+ * Since method accepts PluginInfo instance as input parameter instead of plugin
+ *   id, caller shoud take care of managing/storing PluginInfo instances for
+ *   future uninstalls.
+ *
+ * @param  {PluginInfo}  plugin  A PluginInfo instance that represents plugin
+ *   that will be installed.
+ *
+ * @return  {Promise}  Return a promise either fulfilled, or rejected with
+ *   CordovaError instance.
+ */
+Api.prototype.removePlugin = function (plugin, uninstallOptions) {
+    var self = this;
+
+    // We need to use custom PluginInfo to trigger windows-specific processing
+    // of changes in .appxmanifest files. See PluginInfo.js for details
+    var pluginInfo = new PluginInfo(plugin.dir);
+    var jsProject = JsprojManager.getProject(this.root);
+    var platformJson = PlatformJson.load(this.root, this.platform);
+    var pluginManager = PluginManager.get(this.platform, this.locations, jsProject);
+    //  CB-11933 We override this field by windows specific one because windows has special logic
+    //  for appxmanifest's capabilities removal (see also https://issues.apache.org/jira/browse/CB-11066)
+    pluginManager.munger = new PlatformMunger(this.platform, this.locations.root, platformJson, new PluginInfoProvider());
+    return pluginManager
+        .removePlugin(pluginInfo, uninstallOptions)
+        .then(function () {
+            // CB-11657 Add BOM to cordova_plugins, since it is was
+            // regenerated after plugin uninstallation and does not have BOM
+            return require('./lib/prepare').addBOMToFile(path.resolve(self.locations.www, 'cordova_plugins.js'));
+        })
+        // CB-11022 return non-falsy value to indicate
+        // that there is no need to run prepare after
+        .thenResolve(true);
+};
+
+/**
+ * Builds an application package for current platform.
+ *
+ * @param  {Object}  buildOptions  A build options. This object's structure is
+ *   highly depends on platform's specific. The most common options are:
+ * @param  {Boolean}  buildOptions.debug  Indicates that packages should be
+ *   built with debug configuration. This is set to true by default unless the
+ *   'release' option is not specified.
+ * @param  {Boolean}  buildOptions.release  Indicates that packages should be
+ *   built with release configuration. If not set to true, debug configuration
+ *   will be used.
+ * @param   {Boolean}  buildOptions.device  Specifies that built app is intended
+ *   to run on device
+ * @param   {Boolean}  buildOptions.emulator: Specifies that built app is
+ *   intended to run on emulator
+ * @param   {String}  buildOptions.target  Specifies the device id that will be
+ *   used to run built application.
+ * @param   {Boolean}  buildOptions.nobuild  Indicates that this should be a
+ *   dry-run call, so no build artifacts will be produced.
+ * @param   {String[]}  buildOptions.archs  Specifies chip architectures which
+ *   app packages should be built for. List of valid architectures is depends on
+ *   platform.
+ * @param   {String}  buildOptions.buildConfig  The path to build configuration
+ *   file. The format of this file is depends on platform.
+ * @param   {String[]} buildOptions.argv Raw array of command-line arguments,
+ *   passed to `build` command. The purpose of this property is to pass a
+ *   platform-specific arguments, and eventually let platform define own
+ *   arguments processing logic.
+ *
+ * @return {Promise<Object[]>} A promise either fulfilled with an array of build
+ *   artifacts (application packages) if package was built successfully,
+ *   or rejected with CordovaError. The resultant build artifact objects is not
+ *   strictly typed and may conatin arbitrary set of fields as in sample below.
+ *
+ *     {
+ *         architecture: 'x86',
+ *         buildType: 'debug',
+ *         path: '/path/to/build',
+ *         type: 'app'
+ *     }
+ *
+ * The return value in most cases will contain only one item but in some cases
+ *   there could be multiple items in output array, e.g. when multiple
+ *   arhcitectures is specified.
+ */
+Api.prototype.build = function(buildOptions) {
+    // TODO: Should we run check_reqs first? Android does this, but Windows appears doesn't.
+    return require('./lib/build').run.call(this, buildOptions)
+    .then(function (result) {
+        // Wrap result into array according to PlatformApi spec
+        return [result];
+    });
+};
+
+/**
+ * Builds an application package for current platform and runs it on
+ *   specified/default device. If no 'device'/'emulator'/'target' options are
+ *   specified, then tries to run app on default device if connected, otherwise
+ *   runs the app on emulator.
+ *
+ * @param   {Object}  runOptions  An options object. The structure is the same
+ *   as for build options.
+ *
+ * @return {Promise} A promise either fulfilled if package was built and ran
+ *   successfully, or rejected with CordovaError.
+ */
+Api.prototype.run = function(runOptions) {
+    // TODO: Should we run check_reqs first? Android does this, but Windows appears doesn't.
+    return require('./lib/run').run.call(this, runOptions);
+};
+
+/**
+ * Cleans out the build artifacts from platform's directory.
+ *
+ * @return  {Promise}  Return a promise either fulfilled, or rejected with
+ *   CordovaError.
+ */
+Api.prototype.clean = function(cleanOpts) {
+    var self = this;
+    return require('./lib/build').clean.call(this, cleanOpts)
+    .then(function () {
+        return require('./lib/prepare').clean.call(self, cleanOpts);
+    });
+};
+
+/**
+ * Performs a requirements check for current platform. Each platform defines its
+ *   own set of requirements, which should be resolved before platform can be
+ *   built successfully.
+ *
+ * @return  {Promise<Requirement[]>}  Promise, resolved with set of Requirement
+ *   objects for current platform.
+ */
+Api.prototype.requirements = function() {
+    return require('./lib/check_reqs').check_all();
+};
+
+module.exports = Api;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec/cordova/platforms/platforms.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova/platforms/platforms.spec.js b/spec/cordova/platforms/platforms.spec.js
index b57ff2d..7045ae7 100644
--- a/spec/cordova/platforms/platforms.spec.js
+++ b/spec/cordova/platforms/platforms.spec.js
@@ -74,8 +74,10 @@ describe('getPlatformApi method', function () {
         var platformApi = platforms.getPlatformApi('browser', 'PLATFORM_WOUT_API');
         expect(platformApi).toEqual(jasmine.any(PlatformApiPoly));
         expect(util.convertToRealPathSafe.calls.count()).toEqual(1);
-        expect(events.emit.calls.count()).toEqual(1);
-        expect(events.emit.calls.argsFor(0)[1]).toEqual('Failed to require PlatformApi instance for platform "browser". Using polyfill instead.');
+        expect(events.emit.calls.count()).toEqual(3);
+        expect(events.emit.calls.argsFor(0)[1]).toContain('Unable to load PlatformApi from platform. Error: Cannot find module');
+        expect(events.emit.calls.argsFor(1)[1]).toEqual('Platform not found or needs polyfill.');
+        expect(events.emit.calls.argsFor(2)[1]).toEqual('Failed to require PlatformApi instance for platform "browser". Using polyfill instead.');
         expect(util.isCordova.calls.count()).toEqual(0);
         expect(util.requireNoCache.calls.count()).toEqual(0);
     });
@@ -83,8 +85,8 @@ describe('getPlatformApi method', function () {
     it('should throw error if using deprecated platform', function () {
         try {
             platforms.getPlatformApi('android', path.join(CORDOVA_ROOT, 'platforms/android'));
-        } catch (error) {
-            expect(error.toString()).toContain('platform does not have Api.js');
+        } catch(error) {
+            expect(error.toString()).toContain('Using this version of Cordova with older version of cordova-android is deprecated. Upgrade to cordova-android@5.0.0 or newer.');
         }
     });
 

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec/cordova/util.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova/util.spec.js b/spec/cordova/util.spec.js
index 6c63421..5d4a2f6 100644
--- a/spec/cordova/util.spec.js
+++ b/spec/cordova/util.spec.js
@@ -309,5 +309,45 @@ describe('util module', function () {
                 });
             });
         });
+
+        describe('getPlatformApiFunction', function() {
+            it('Test 027 : should throw error informing user to update platform', function() {
+                expect(function(){util.getPlatformApiFunction('some/path', 'android');}).toThrow(new Error
+                ('Uncaught, unspecified "error" event. ( Using this version of Cordova with older version of cordova-android is deprecated. Upgrade to cordova-android@5.0.0 or newer.)'));
+            });
+
+            it('Test 028 : should throw error if platform is not supported', function() {
+                spyOn(events, 'emit').and.returnValue(true);
+                expect(function(){util.getPlatformApiFunction('some/path', 'somePlatform');}).toThrow();
+                expect(events.emit.calls.count()).toBe(2);
+                expect(events.emit.calls.argsFor(0)[1]).toBe('Unable to load PlatformApi from platform. Error: Cannot find module \'some/path\'');
+                expect(events.emit.calls.argsFor(1)[1]).toBe('The platform "somePlatform" does not appear to be a valid cordova platform. It is missing API.js. somePlatform not supported.');
+            });
+
+            it('Test 029 : should use polyfill if blackberry10, webos, ubuntu', function() {
+                spyOn(events, 'emit').and.returnValue(true);
+                util.getPlatformApiFunction('some/path', 'blackberry10');
+                expect(events.emit.calls.count()).toBe(3);
+                expect(events.emit.calls.argsFor(0)[1]).toBe('Unable to load PlatformApi from platform. Error: Cannot find module \'some/path\'');
+                expect(events.emit.calls.argsFor(1)[1]).toBe('Platform not found or needs polyfill.');
+                expect(events.emit.calls.argsFor(2)[1]).toBe('Failed to require PlatformApi instance for platform "blackberry10". Using polyfill instead.');
+            });
+
+            it('Test 030 : successfully find platform Api', function() {
+                spyOn(events, 'emit').and.returnValue(true);
+                var specPlugDir = __dirname.replace('spec-cordova', 'spec-plugman');
+                util.getPlatformApiFunction((path.join(specPlugDir, 'projects', 'android', 'cordova', 'Api.js')), 'android');
+                expect(events.emit.calls.count()).toBe(1);
+                expect(events.emit.calls.argsFor(0)[1]).toBe('PlatformApi successfully found for platform android');
+            });
+
+            it('Test 031 : should inform user that entry point should be called Api.js', function() {
+                spyOn(events, 'emit').and.returnValue(true);
+                var specPlugDir = __dirname.replace('spec-cordova', 'spec-plugman');
+                expect(function(){util.getPlatformApiFunction((path.join(specPlugDir, 'projects', 'android', 'cordova', 'clean')), 'android');}).toThrow();
+                expect(events.emit.calls.count()).toBe(3);
+                expect(events.emit.calls.argsFor(0)[1]).toBe('File name should be called Api.js.');
+            });
+        });
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/src/cordova/util.js
----------------------------------------------------------------------
diff --git a/src/cordova/util.js b/src/cordova/util.js
index e08fbd5..6bf8f82 100644
--- a/src/cordova/util.js
+++ b/src/cordova/util.js
@@ -459,49 +459,62 @@ function getLatestNpmVersion (module_name) {
     });
 }
 
-// Display deprecation message if platform is api compatible
-function checkPlatformApiCompatible (platform) {
-    if (platforms[platform] && platforms[platform].apiCompatibleSince) {
-        events.emit('warn', ' Using this version of Cordova with older version of cordova-' + platform +
-            ' is deprecated. Upgrade to cordova-' + platform + '@' +
-            platforms[platform].apiCompatibleSince + ' or newer.');
-    }
-}
-
-// libdir should be path to API.js
+// Takes a libDir (root of platform where pkgJson is expected) & a platform name.
+// Platform is used if things go wrong, so we can use polyfill.
+// Potential errors : path doesn't exist, module isn't found or can't load. 
+// Message prints if file not named Api.js or falls back to pollyfill.
 function getPlatformApiFunction (libDir, platform) {
     var PlatformApi;
     try {
         // First we need to find whether platform exposes its' API via js module
         // If it does, then we require and instantiate it.
+        // This will throw if package.json does not exist, or specify 'main'.
         var apiEntryPoint = require.resolve(libDir);
-        if (path.basename(apiEntryPoint) === 'Api.js') {
+        if (apiEntryPoint) {
+            if(path.basename(apiEntryPoint) !== 'Api.js') {
+                events.emit('verbose', 'File name should be called Api.js.');
+                // Not an error, still load it ...
+            }
             PlatformApi = exports.requireNoCache(apiEntryPoint);
-            events.emit('verbose', 'PlatformApi successfully found for platform ' + platform);
-        }
 
-        // For versions which are not api compatible, require.resolve returns path to /bin/create ('main' field in package.json)
-        // We should display the same deprecation message as in the catch block
-        if (!PlatformApi && ['android', 'windows', 'ios', 'osx'].indexOf(platform) >= 0) {
-            checkPlatformApiCompatible(platform);
+            if (!PlatformApi.createPlatform) {
+                PlatformApi = null;
+                events.emit('error', 'Does not appear to implement platform Api.');
+            } else {
+               events.emit('verbose', 'PlatformApi successfully found for platform ' + platform); 
+            }
+        }
+        else {
+            events.emit('verbose', 'No Api.js entry point found.');
         }
-    } catch (err) {
+    }
+    catch (err) {
+        // Emit the err, someone might care ...
+        events.emit('warn','Unable to load PlatformApi from platform. ' + err);
         // Check if platform already compatible w/ PlatformApi and show deprecation warning if not
-        if (err && err.code === 'MODULE_NOT_FOUND') {
-            checkPlatformApiCompatible(platform);
+        //checkPlatformApiCompatible(platform);
+        if (platforms[platform] && platforms[platform].apiCompatibleSince) {
+            events.emit('error', ' Using this version of Cordova with older version of cordova-' + platform +
+                    ' is deprecated. Upgrade to cordova-' + platform + '@' +
+                    platforms[platform].apiCompatibleSince + ' or newer.');
+        }
+        else if (!platforms[platform]) {
+            // Throw error because polyfill doesn't support non core platforms
+            events.emit('error', 'The platform "' + platform + '" does not appear to be a valid cordova platform. It is missing API.js. '+ platform +' not supported.');
         } else {
-            events.emit('verbose', 'Error: PlatformApi not loaded for platform.' + err);
+            events.emit('verbose', 'Platform not found or needs polyfill.');
         }
-    } finally {
+    }
 
-        // here is no Api.js and no deprecation information hence
-        // the platform just does not expose Api and we will try polyfill if applicable
-        if (!PlatformApi && (platform === 'blackberry10' || platform === 'browser' || platform === 'ubuntu' || platform === 'webos')) {
+    if (!PlatformApi) {
+        // The platform just does not expose Api and we will try to polyfill it
+        var polyPlatforms = ['blackberry10','browser','ubuntu','webos'];
+        if( polyPlatforms.indexOf(platform) > -1) {
             events.emit('verbose', 'Failed to require PlatformApi instance for platform "' + platform +
-                '". Using polyfill instead.');
+            '". Using polyfill instead.');
             PlatformApi = require('../platforms/PlatformApiPoly.js');
-        } else if (!PlatformApi) {
-            throw new Error('Your ' + platform + ' platform does not have Api.js'); // eslint-disable-line no-unsafe-finally
+        } else {
+            throw new Error('Your ' + platform + ' platform does not have Api.js');
         }
     }
     return PlatformApi;


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


[3/4] cordova-lib git commit: CB-12870 : rebased and updated paths

Posted by au...@apache.org.
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js
----------------------------------------------------------------------
diff --git a/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js
new file mode 100644
index 0000000..1875bf7
--- /dev/null
+++ b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js
@@ -0,0 +1,733 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+var fs = require('fs');
+var util = require('util');
+var et = require('elementtree');
+var path = require('path');
+var xml= require('cordova-common').xmlHelpers;
+
+var UAP_RESTRICTED_CAPS = ['enterpriseAuthentication', 'sharedUserCertificates',
+                           'documentsLibrary', 'musicLibrary', 'picturesLibrary',
+                           'videosLibrary', 'removableStorage', 'internetClientClientServer',
+                           'privateNetworkClientServer'];
+
+// UAP namespace capabilities come from the XSD type ST_Capability_Uap from AppxManifestTypes.xsd
+var CAPS_NEEDING_UAPNS  = ['documentsLibrary', 'picturesLibrary', 'videosLibrary',
+                           'musicLibrary', 'enterpriseAuthentication', 'sharedUserCertificates',
+                           'removableStorage', 'appointments', 'contacts', 'userAccountInformation',
+                           'phoneCall', 'blockedChatMessages', 'objects3D'];
+
+var KNOWN_ORIENTATIONS = {
+    'default':   ['portrait', 'landscape', 'landscapeFlipped'],
+    'portrait':  ['portrait'],
+    'landscape': ['landscape', 'landscapeFlipped']
+};
+
+/**
+ * Store to cache appxmanifest files based on file location
+ * @type  {Object}
+ */
+var manifestCache = {};
+
+/**
+ * @constructor
+ * @constructs AppxManifest
+ *
+ * Wraps an AppxManifest file. Shouldn't be instantiated directly.
+ *   AppxManifest.get should be used instead to select proper manifest type
+ *   (AppxManifest for Win 8/8.1/Phone 8.1, Win10AppxManifest for Win 10)
+ *
+ * @param  {string}  path    Path to appxmanifest to wrap
+ * @param  {string}  prefix  A namespace prefix used to prepend some elements.
+ *   Depends on manifest type.
+ */
+function AppxManifest(path, prefix) {
+    this.path = path;
+    // Append ':' to prefix if needed
+    prefix = prefix || '';
+    this.prefix = (prefix.indexOf(':') === prefix.length - 1) ? prefix : prefix + ':';
+    this.doc = xml.parseElementtreeSync(path);
+    if (this.doc.getroot().tag !== 'Package') {
+        // Some basic validation
+        throw new Error(path + ' has incorrect root node name (expected "Package")');
+    }
+
+    // Indicates that this manifest is for phone application (either WinPhone 8.1 or Universal Windows 10)
+    this.hasPhoneIdentity = this.prefix === 'uap:' || this.prefix === 'm3:';
+}
+
+//  Static read-only property to get capabilities which need to be prefixed with uap
+Object.defineProperty(AppxManifest, 'CapsNeedUapPrefix', {
+    writable: false,
+    configurable: false,
+    value: CAPS_NEEDING_UAPNS
+});
+
+/**
+ * @static
+ * @constructs AppxManifest|Win10AppxManifest
+ *
+ * Instantiates a new AppxManifest/Win10AppxManifest class. Chooses which
+ *   constructor to use based on xmlns attributes of Package node
+ *
+ * @param   {String}  fileName  File to create manifest for
+ * @param   {Boolean} [ignoreCache=false]  Specifies, whether manifest cache will be
+ *   used to return resultant object
+ *
+ * @return  {AppxManifest|Win10AppxManifest}  Manifest instance
+ */
+AppxManifest.get = function (fileName, ignoreCache) {
+
+    if (!ignoreCache && manifestCache[fileName]) {
+        return manifestCache[fileName];
+    }
+
+    var root = xml.parseElementtreeSync(fileName).getroot();
+    var prefixes = Object.keys(root.attrib)
+    .reduce(function (result, attrib) {
+        if (attrib.indexOf('xmlns') === 0 && attrib !== 'xmlns:mp') {
+            result.push(attrib.replace('xmlns', '').replace(':', ''));
+        }
+
+        return result;
+    }, []).sort();
+
+    var prefix = prefixes[prefixes.length - 1];
+    var Manifest = prefix === 'uap' ? Win10AppxManifest : AppxManifest;
+    var result = new Manifest(fileName, prefix);
+
+    if (!ignoreCache) {
+        manifestCache[fileName] = result;
+    }
+
+    return result;
+};
+
+/**
+ * Removes manifests from cache to prevent using stale entries
+ *
+ * @param {String|String[]} [cacheKeys] The keys to delete from cache. If not
+ *   specified, the whole cache will be purged
+ */
+AppxManifest.purgeCache = function (cacheKeys) {
+    if (!cacheKeys) {
+        // if no arguments passed, remove all entries
+        manifestCache = {};
+        return;
+    }
+
+    var keys = Array.isArray(cacheKeys) ? cacheKeys : [cacheKeys];
+    keys.forEach(function (key) {
+        delete manifestCache[key];
+    });
+};
+
+AppxManifest.prototype.getPhoneIdentity = function () {
+    var phoneIdentity = this.doc.getroot().find('./mp:PhoneIdentity');
+    if (!phoneIdentity)
+        throw new Error('Failed to find PhoneIdentity element in appxmanifest at ' + this.path);
+
+    return {
+        getPhoneProductId: function () {
+            return phoneIdentity.attrib.PhoneProductId;
+        },
+        setPhoneProductId: function (id) {
+            if (!id) throw new Error('Argument for "setPhoneProductId" must be defined in appxmanifest at ' + this.path);
+            phoneIdentity.attrib.PhoneProductId = id;
+            return this;
+        }
+    };
+};
+
+AppxManifest.prototype.getIdentity = function () {
+    var identity = this.doc.getroot().find('./Identity');
+    if (!identity)
+        throw new Error('Failed to find "Identity" node. The appxmanifest at ' + this.path + ' is invalid');
+
+    return {
+        getName: function () {
+            return identity.attrib.Name;
+        },
+        setName: function (name) {
+            if (!name) throw new TypeError('Identity.Name attribute must be non-empty in appxmanifest at ' + this.path);
+            identity.attrib.Name = name;
+            return this;
+        },
+        getPublisher: function () {
+            return identity.attrib.Publisher;
+        },
+        setPublisher: function (publisherId) {
+            if (!publisherId) throw new TypeError('Identity.Publisher attribute must be non-empty in appxmanifest at ' + this.path);
+            identity.attrib.Publisher = publisherId;
+            return this;
+        },
+        getVersion: function () {
+            return identity.attrib.Version;
+        },
+        setVersion: function (version) {
+            if (!version) throw new TypeError('Identity.Version attribute must be non-empty in appxmanifest at ' + this.path );
+
+            // Adjust version number as per CB-5337 Windows8 build fails due to invalid app version
+            if(version && version.match(/\.\d/g)) {
+                var numVersionComponents = version.match(/\.\d/g).length + 1;
+                while (numVersionComponents++ < 4) {
+                    version += '.0';
+                }
+            }
+
+            identity.attrib.Version = version;
+            return this;
+        }
+    };
+};
+
+AppxManifest.prototype.getProperties = function () {
+    var properties = this.doc.getroot().find('./Properties');
+
+    if (!properties)
+        throw new Error('Failed to find "Properties" node. The appxmanifest at ' + this.path + ' is invalid');
+
+    return {
+        getDisplayName: function () {
+            var displayName = properties.find('./DisplayName');
+            return displayName && displayName.text;
+        },
+        setDisplayName: function (name) {
+            if (!name) throw new TypeError('Properties.DisplayName elements must be non-empty in appxmanifest at ' + this.path);
+            var displayName = properties.find('./DisplayName');
+
+            if (!displayName) {
+                displayName = new et.Element('DisplayName');
+                properties.append(displayName);
+            }
+
+            displayName.text = name;
+
+            return this;
+        },
+        getPublisherDisplayName: function () {
+            var publisher = properties.find('./PublisherDisplayName');
+            return publisher && publisher.text;
+        },
+        setPublisherDisplayName: function (name) {
+            if (!name) throw new TypeError('Properties.PublisherDisplayName elements must be non-empty in appxmanifest at ' + this.path);
+            var publisher = properties.find('./PublisherDisplayName');
+
+            if (!publisher) {
+                publisher = new et.Element('PublisherDisplayName');
+                properties.append(publisher);
+            }
+
+            publisher.text = name;
+
+            return this;
+        },
+        getDescription: function () {
+            var description = properties.find('./Description');
+            return description && description.text;
+        },
+        setDescription: function (text) {
+
+            var description = properties.find('./Description');
+
+            if (!text || text.length === 0) {
+                if (description) properties.remove(description);
+                return this;
+            }
+
+            if (!description) {
+                description = new et.Element('Description');
+                properties.append(description);
+            }
+
+            description.text = processDescription(text);
+
+            return this;
+        },
+    };
+};
+
+AppxManifest.prototype.getApplication = function () {
+    var application = this.doc.getroot().find('./Applications/Application');
+    if (!application)
+        throw new Error('Failed to find "Application" element. The appxmanifest at ' + this.path + ' is invalid');
+
+    var self = this;
+
+    return {
+        _node: application,
+        getVisualElements: function () {
+            return self.getVisualElements();
+        },
+        getId: function () {
+            return application.attrib.Id;
+        },
+        setId: function (id) {
+            if (!id) throw new TypeError('Application.Id attribute must be defined in appxmanifest at ' + this.path);
+            // 64 symbols restriction goes from manifest schema definition
+            // http://msdn.microsoft.com/en-us/library/windows/apps/br211415.aspx
+            var appId = id.length <= 64 ? id : id.substr(0, 64);
+            application.attrib.Id = appId;
+            return this;
+        },
+        getStartPage: function () {
+            return application.attrib.StartPage;
+        },
+        setStartPage: function (page) {
+            if (!page) page = 'www/index.html'; // Default valur is always index.html
+            application.attrib.StartPage = page;
+            return this;
+        },
+        getAccessRules: function () {
+            return application
+                .findall('./ApplicationContentUriRules/Rule')
+                .map(function (rule) {
+                    return rule.attrib.Match;
+                });
+        },
+        setAccessRules: function (rules) {
+            var appUriRules = application.find('ApplicationContentUriRules');
+            if (appUriRules) {
+                application.remove(appUriRules);
+            }
+
+            // No rules defined
+            if (!rules || rules.length === 0) {
+                return;
+            }
+
+            appUriRules = new et.Element('ApplicationContentUriRules');
+            application.append(appUriRules);
+
+            rules.forEach(function(rule) {
+                appUriRules.append(new et.Element('Rule', {Match: rule, Type: 'include'}));
+            });
+
+            return this;
+        }
+    };
+};
+
+AppxManifest.prototype.getVisualElements = function () {
+    var self = this;
+    var visualElements = this.doc.getroot().find('./Applications/Application/' +
+        this.prefix  + 'VisualElements');
+
+    if (!visualElements)
+        throw new Error('Failed to find "VisualElements" node. The appxmanifest at ' + this.path + ' is invalid');
+
+    return {
+        _node: visualElements,
+        getDisplayName: function () {
+            return visualElements.attrib.DisplayName;
+        },
+        setDisplayName: function (name) {
+            if (!name) throw new TypeError('VisualElements.DisplayName attribute must be defined in appxmanifest at ' + this.path);
+            visualElements.attrib.DisplayName = name;
+            return this;
+        },
+        getOrientation: function () {
+            return visualElements.findall(self.prefix + 'Rotation')
+                .map(function (element) {
+                    return element.attrib.Preference;
+                });
+        },
+        setOrientation: function (orientation) {
+            if (!orientation || orientation === ''){
+                orientation = 'default';
+            }
+
+            var rotationPreferenceRootName = self.prefix + 'InitialRotationPreference';
+            var rotationPreferenceRoot = visualElements.find('./' + rotationPreferenceRootName);
+
+            if (!orientation && rotationPreferenceRoot) {
+                // Remove InitialRotationPreference root element to revert to defaults
+                visualElements.remove(rotationPreferenceRoot);
+                return this;
+            }
+
+            if(!rotationPreferenceRoot) {
+                rotationPreferenceRoot = new et.Element(rotationPreferenceRootName);
+                visualElements.append(rotationPreferenceRoot);
+            }
+
+            rotationPreferenceRoot.clear();
+
+            var orientations = KNOWN_ORIENTATIONS[orientation] || orientation.split(',');
+            orientations.forEach(function(orientation) {
+                var el = new et.Element(self.prefix + 'Rotation', {Preference: orientation} );
+                rotationPreferenceRoot.append(el);
+            });
+
+            return this;
+        },
+        getBackgroundColor: function () {
+            return visualElements.attrib.BackgroundColor;
+        },
+        setBackgroundColor: function (color) {
+            if (!color)
+                throw new TypeError('VisualElements.BackgroundColor attribute must be defined in appxmanifest at ' + this.path);
+
+            visualElements.attrib.BackgroundColor = refineColor(color);
+            return this;
+        },
+        trySetBackgroundColor: function (color) {
+            try {
+                return this.setBackgroundColor(color);
+            } catch (e) { return this; }
+        },
+        getForegroundText: function () {
+            return visualElements.attrib.ForegroundText;
+        },
+        setForegroundText: function (color) {
+            // If color is not set, fall back to 'light' by default
+            visualElements.attrib.ForegroundText = color || 'light';
+
+            return this;
+        },
+        getSplashBackgroundColor: function () {
+            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
+            return splashNode && splashNode.attrib.BackgroundColor;
+        },
+        setSplashBackgroundColor: function (color) {
+            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
+            if (splashNode) {
+                if (color) {
+                    splashNode.attrib.BackgroundColor = refineColor(color);
+                } else {
+                    delete splashNode.attrib.BackgroundColor;
+                }
+            }
+            return this;
+        },
+        getSplashScreenExtension: function (extension) {
+            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
+            return splashNode && splashNode.attrib.Image && path.extname(splashNode.attrib.Image);
+        },
+        setSplashScreenExtension: function (extension) {
+            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
+            if (splashNode) {
+                var oldPath = splashNode.attrib.Image; 
+                splashNode.attrib.Image = path.dirname(oldPath) + '\\' + path.basename(oldPath, path.extname(oldPath)) + extension;
+            }
+            return this;
+        },
+        getToastCapable: function () {
+            return visualElements.attrib.ToastCapable;
+        },
+        setToastCapable: function (isToastCapable) {
+            if (isToastCapable === true || isToastCapable.toString().toLowerCase() === 'true') {
+                visualElements.attrib.ToastCapable = 'true';
+            } else {
+                delete visualElements.attrib.ToastCapable;
+            }
+
+            return this;
+        },
+        getDescription: function () {
+            return visualElements.attrib.Description;
+        },
+        setDescription: function (description) {
+            if (!description || description.length === 0)
+                throw new TypeError('VisualElements.Description attribute must be defined and non-empty in appxmanifest at ' + this.path);
+
+            visualElements.attrib.Description = processDescription(description);
+            return this;
+        },
+    };
+};
+
+AppxManifest.prototype.getCapabilities = function () {
+    var capabilities = this.doc.find('./Capabilities');
+    if (!capabilities) return [];
+
+    return capabilities.getchildren()
+    .map(function (element) {
+        return { type: element.tag, name: element.attrib.Name };
+    });
+};
+
+function isCSSColorName(color) {
+    return color.indexOf('0x') === -1 && color.indexOf('#') === -1;
+}
+
+function refineColor(color) {
+    if (isCSSColorName(color)) {
+        return color;
+    }
+
+    // return three-byte hexadecimal number preceded by "#" (required for Windows)
+    color = color.replace('0x', '').replace('#', '');
+    if (color.length == 3) {
+        color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2];
+    }
+    // alpha is not supported, so we remove it
+    if (color.length == 8) { // AArrggbb
+        color = color.slice(2);
+    }
+    return '#' + color;
+}
+
+function processDescription(text) {
+    var result = text;
+
+    // Description value limitations: https://msdn.microsoft.com/en-us/library/windows/apps/br211429.aspx
+    // value should be no longer than 2048 characters
+    if (text.length > 2048) {
+        result = text.substr(0, 2048);
+    }
+
+    // value should not contain newlines and tabs
+    return result.replace(/(\n|\r)/g, ' ').replace(/\t/g, '    ');
+}
+
+// Shortcut for getIdentity.setName
+AppxManifest.prototype.setPackageName = function (name) {
+    this.getIdentity().setName(name);
+    return this;
+};
+
+// Shortcut for multiple inner methods calls
+AppxManifest.prototype.setAppName = function (name) {
+    this.getProperties().setDisplayName(name);
+    this.getVisualElements().setDisplayName(name);
+
+    return this;
+};
+
+/**
+ * Writes manifest to disk syncronously. If filename is specified, then manifest
+ *   will be written to that file
+ *
+ * @param   {String}  [destPath]  File to write manifest to. If omitted,
+ *   manifest will be written to file it has been read from.
+ */
+AppxManifest.prototype.write = function(destPath) {
+    // sort Capability elements as per CB-5350 Windows8 build fails due to invalid 'Capabilities' definition
+    sortCapabilities(this.doc);
+    fs.writeFileSync(destPath || this.path, this.doc.write({indent: 4}), 'utf-8');
+};
+
+/**
+ * Sorts 'capabilities' elements in manifest in ascending order
+ * @param   {Elementtree.Document}  manifest  An XML document that represents
+ *   appxmanifest
+ */
+function sortCapabilities(manifest) {
+
+    // removes namespace prefix (m3:Capability -> Capability)
+    // this is required since elementtree returns qualified name with namespace
+    function extractLocalName(tag) {
+        return tag.split(':').pop(); // takes last part of string after ':'
+    }
+
+    var capabilitiesRoot = manifest.find('.//Capabilities'),
+        capabilities = capabilitiesRoot.getchildren() || [];
+    // to sort elements we remove them and then add again in the appropriate order
+    capabilities.forEach(function(elem) { // no .clear() method
+        capabilitiesRoot.remove(elem);
+        // CB-7601 we need local name w/o namespace prefix to sort capabilities correctly
+        elem.localName = extractLocalName(elem.tag);
+    });
+    capabilities.sort(function(a, b) {
+        return (a.localName > b.localName) ? 1: -1;
+    });
+    capabilities.forEach(function(elem) {
+        capabilitiesRoot.append(elem);
+    });
+}
+
+
+function Win10AppxManifest(path) {
+    AppxManifest.call(this, path, /*prefix=*/'uap');
+}
+
+util.inherits(Win10AppxManifest, AppxManifest);
+
+Win10AppxManifest.prototype.getApplication = function () {
+    // Call overridden method
+    var result = AppxManifest.prototype.getApplication.call(this);
+    var application = result._node;
+
+    result.getAccessRules = function () {
+        return application
+            .findall('./uap:ApplicationContentUriRules/uap:Rule')
+            .map(function (rule) {
+                return rule.attrib.Match;
+            });
+    };
+
+    result.setAccessRules = function (rules) {
+        var appUriRules = application.find('./uap:ApplicationContentUriRules');
+        if (appUriRules) {
+            application.remove(appUriRules);
+        }
+
+        // No rules defined
+        if (!rules || rules.length === 0) {
+            return;
+        }
+
+        appUriRules = new et.Element('uap:ApplicationContentUriRules');
+        application.append(appUriRules);
+
+        rules.forEach(function(rule) {
+            appUriRules.append(new et.Element('uap:Rule', { Match: rule, Type: 'include', WindowsRuntimeAccess: 'all' }));
+        });
+
+        return this;
+    };
+
+    return result;
+};
+
+Win10AppxManifest.prototype.getVisualElements = function () {
+    // Call base method and extend its results
+    var result = AppxManifest.prototype.getVisualElements.call(this);
+    var defaultTitle = result._node.find('./uap:DefaultTile');
+
+    result.getDefaultTitle = function () {
+        return {
+            getShortName: function () {
+                return defaultTitle.attrib.ShortName;
+            },
+            setShortName: function (name) {
+                if (!name) throw new TypeError('Argument for "setDisplayName" must be defined in appxmanifest at ' + this.path);
+                defaultTitle.attrib.ShortName = name;
+                return this;
+            }
+        };
+    };
+
+    // ToastCapable attribute was removed in Windows 10.
+    // See https://msdn.microsoft.com/ru-ru/library/windows/apps/dn423310.aspx
+    result.getToastCapable = function () {};
+    result.setToastCapable = function () { return this; };
+
+    // ForegroundText was removed in Windows 10 as well
+    result.getForegroundText = function () {};
+    result.setForegroundText = function () { return this; };
+
+    return result;
+};
+
+// Shortcut for multiple inner methods calls
+Win10AppxManifest.prototype.setAppName = function (name) {
+    // Call base method
+    AppxManifest.prototype.setAppName.call(this, name);
+    this.getVisualElements().getDefaultTitle().setShortName(name);
+
+    return this;
+};
+
+/**
+ * Checks for capabilities which are Restricted in Windows 10 UAP.
+ * @return {string[]|false} An array of restricted capability names, or false.
+ */
+Win10AppxManifest.prototype.getRestrictedCapabilities = function () {
+    var restrictedCapabilities = this.getCapabilities()
+    .filter(function (capability) {
+        return UAP_RESTRICTED_CAPS.indexOf(capability.name) >= 0;
+    });
+
+    return restrictedCapabilities.length === 0 ? false : restrictedCapabilities;
+};
+
+/**
+ * Sets up a Dependencies section for appxmanifest. If no arguments provided,
+ *   deletes Dependencies section.
+ *
+ * @param  {Object[]}  dependencies  Array of arbitrary object, which fields
+ *   will be used to set each dependency attributes.
+ *
+ * @returns {Win10AppxManifest}  self instance
+ */
+Win10AppxManifest.prototype.setDependencies = function (dependencies) {
+    var dependenciesElement = this.doc.find('./Dependencies');
+
+    if ((!dependencies || dependencies.length === 0) && dependenciesElement) {
+        this.doc.remove(dependenciesElement);
+        return this;
+    }
+
+    if (!dependenciesElement) {
+        dependenciesElement = new et.Element('Dependencies');
+        this.doc.append(dependenciesElement);
+    }
+
+    if (dependenciesElement.len() > 0) {
+        dependenciesElement.clear();
+    }
+
+    dependencies.forEach(function (uapVersionInfo) {
+        dependenciesElement.append(new et.Element('TargetDeviceFamily', uapVersionInfo));
+    });
+};
+
+/**
+ * Writes manifest to disk syncronously. If filename is specified, then manifest
+ *   will be written to that file
+ *
+ * @param   {String}  [destPath]  File to write manifest to. If omitted,
+ *   manifest will be written to file it has been read from.
+ */
+Win10AppxManifest.prototype.write = function(destPath) {
+    fs.writeFileSync(destPath || this.path, this.writeToString(), 'utf-8');
+};
+
+Win10AppxManifest.prototype.writeToString = function() {
+    ensureUapPrefixedCapabilities(this.doc.find('.//Capabilities'));
+    ensureUniqueCapabilities(this.doc.find('.//Capabilities'));
+    // sort Capability elements as per CB-5350 Windows8 build fails due to invalid 'Capabilities' definition
+    sortCapabilities(this.doc);
+    return this.doc.write({indent: 4});
+};
+
+/**
+ * Checks for capabilities which require the uap: prefix in Windows 10.
+ * @param capabilities {ElementTree.Element} The appx manifest element for <capabilities>
+ */
+function ensureUapPrefixedCapabilities(capabilities) {
+    capabilities.getchildren()
+    .forEach(function(el) {
+        if (CAPS_NEEDING_UAPNS.indexOf(el.attrib.Name) > -1 && el.tag.indexOf('uap:') !== 0) {
+            el.tag = 'uap:' + el.tag;
+        }
+    });
+}
+
+/**
+ * Cleans up duplicate capability declarations that were generated during the prepare process
+ * @param capabilities {ElementTree.Element} The appx manifest element for <capabilities>
+ */
+function ensureUniqueCapabilities(capabilities) {
+    var uniqueCapabilities = [];
+    capabilities.getchildren()
+    .forEach(function(el) {
+        var name = el.attrib.Name;
+        if (uniqueCapabilities.indexOf(name) !== -1) {
+            capabilities.remove(el);
+        } else {
+            uniqueCapabilities.push(name);
+        }
+    });
+}
+
+module.exports = AppxManifest;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js
----------------------------------------------------------------------
diff --git a/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js
new file mode 100644
index 0000000..c07a77a
--- /dev/null
+++ b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js
@@ -0,0 +1,164 @@
+/*
+ * 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 util = require('util');
+var path = require('path');
+var CommonMunger = require('cordova-common').ConfigChanges.PlatformMunger;
+var CapsNeedUapPrefix = require(path.join(__dirname, 'AppxManifest')).CapsNeedUapPrefix;
+
+var CAPS_SELECTOR = '/Package/Capabilities';
+var WINDOWS10_MANIFEST = 'package.windows10.appxmanifest';
+
+function PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider) {
+    CommonMunger.apply(this, arguments);
+}
+
+util.inherits(PlatformMunger, CommonMunger);
+
+/**
+ * This is an override of apply_file_munge method from cordova-common's PlatformMunger class.
+ * In addition to parent's method logic also removes capabilities with 'uap:' prefix that were
+ * added by AppxManifest class
+ *
+ * @param {String}  file   A file name to apply munge to
+ * @param {Object}  munge  Serialized changes that need to be applied to the file
+ * @param {Boolean} [remove=false] Flag that specifies whether the changes
+ *   need to be removed or added to the file
+ */
+PlatformMunger.prototype.apply_file_munge = function (file, munge, remove) {
+
+    // Create a copy to avoid modification of original munge
+    var mungeCopy = cloneObject(munge);
+    var capabilities = mungeCopy.parents[CAPS_SELECTOR];
+
+    if (capabilities) {
+        // Add 'uap' prefixes for windows 10 manifest
+        if (file === WINDOWS10_MANIFEST) {
+            capabilities = generateUapCapabilities(capabilities);
+        }
+
+        // Remove duplicates and sort capabilities when installing plugin
+        if (!remove) {
+            capabilities = getUniqueCapabilities(capabilities).sort(compareCapabilities);
+        }
+
+        // Put back capabilities into munge's copy
+        mungeCopy.parents[CAPS_SELECTOR] = capabilities;
+    }
+
+    PlatformMunger.super_.prototype.apply_file_munge.call(this, file, mungeCopy, remove);
+};
+
+// Recursive function to clone an object
+function cloneObject(obj) {
+    if (obj === null || typeof obj !== 'object') {
+        return obj;
+    }
+
+    var copy = obj.constructor();
+    Object.keys(obj).forEach(function(key) {
+        copy[key] = cloneObject(obj[key]);
+    });
+
+    return copy;
+}
+
+/**
+ * Retrieve capabality name from xml field
+ * @param {Object} capability with xml field like <Capability Name="CapabilityName">
+ * @return {String} name of capability
+ */
+function getCapabilityName(capability) {
+    var reg = /Name\s*=\s*"(.*?)"/;
+    return capability.xml.match(reg)[1];
+}
+
+/**
+ * Remove capabilities with same names
+ * @param {Object} an array of capabilities
+ * @return {Object} an unique array of capabilities
+ */
+function getUniqueCapabilities(capabilities) {
+    return capabilities.reduce(function(uniqueCaps, currCap) {
+
+        var isRepeated = uniqueCaps.some(function(cap) {
+            return getCapabilityName(cap) === getCapabilityName(currCap);
+        });
+
+        return isRepeated ? uniqueCaps : uniqueCaps.concat([currCap]);
+    }, []);
+}
+
+/**
+ * Comparator function to pass to Array.sort
+ * @param {Object} firstCap first capability
+ * @param {Object} secondCap second capability
+ * @return {Number} either -1, 0 or 1
+ */
+function compareCapabilities(firstCap, secondCap) {
+    var firstCapName = getCapabilityName(firstCap);
+    var secondCapName = getCapabilityName(secondCap);
+
+    if (firstCapName < secondCapName) {
+        return -1;
+    }
+
+    if (firstCapName > secondCapName) {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+/**
+ * Generates a new munge that contains <uap:Capability> elements created based on
+ * corresponding <Capability> elements from base munge. If there are no such elements
+ * found in base munge, the empty munge is returned (selectors might be present under
+ * the 'parents' key, but they will contain no changes).
+ *
+ * @param {Object} capabilities A list of capabilities
+ * @return {Object} A list with 'uap'-prefixed capabilities
+ */
+function generateUapCapabilities(capabilities) {
+
+    function hasCapabilityChange(change) {
+        return /^\s*<(\w+:)?(Device)?Capability\s/.test(change.xml);
+    }
+
+    function createPrefixedCapabilityChange(change) {
+        if (CapsNeedUapPrefix.indexOf(getCapabilityName(change)) < 0) {
+            return change;
+        }
+
+        //  If capability is already prefixed, avoid adding another prefix
+        var replaceXML = change.xml.indexOf('uap:') > 0 ? change.xml : change.xml.replace(/Capability/, 'uap:Capability');
+        return {
+            xml: replaceXML,
+            count: change.count,
+            before: change.before
+        };
+    }
+
+    return capabilities
+     // For every xml change check if it adds a <Capability> element ...
+    .filter(hasCapabilityChange)
+    // ... and create a duplicate with 'uap:' prefix
+    .map(createPrefixedCapabilityChange);
+
+}
+
+exports.PlatformMunger = PlatformMunger;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js
----------------------------------------------------------------------
diff --git a/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js
new file mode 100644
index 0000000..28b2fa3
--- /dev/null
+++ b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js
@@ -0,0 +1,626 @@
+/**
+ 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 */
+
+/*
+ Helper for dealing with Windows Store JS app .jsproj files
+ */
+
+var fs = require('fs');
+var et = require('elementtree');
+var path = require('path');
+var util = require('util');
+var semver = require('semver');
+var shell = require('shelljs');
+var AppxManifest = require('./AppxManifest');
+var PluginHandler = require('./PluginHandler');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+var xml_helpers = require('cordova-common').xmlHelpers;
+var AppxManifest = require('./AppxManifest');
+
+var WinCSharpProjectTypeGUID = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";  // .csproj
+var WinCplusplusProjectTypeGUID = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";  // .vcxproj
+
+// Match a JavaScript Project
+var JSPROJ_REGEXP = /(Project\("\{262852C6-CD72-467D-83FE-5EEB1973A190}"\)\s*=\s*"[^"]+",\s*"[^"]+",\s*"\{[0-9a-f\-]+}"[^\r\n]*[\r\n]*)/gi;
+
+// Chars in a string that need to be escaped when used in a RegExp
+var ESCAPE_REGEXP = /([.?*+\^$\[\]\\(){}|\-])/g;
+
+function jsprojManager(location) {
+    this.root = path.dirname(location);
+    this.isUniversalWindowsApp = path.extname(location).toLowerCase() === ".projitems";
+    this.projects = [];
+    this.master = this.isUniversalWindowsApp ? new proj(location) : new jsproj(location);
+    this.projectFolder = path.dirname(location);
+    this.www = path.join(this.root, 'www');
+    this.platformWww = path.join(this.root, 'platform_www');
+}
+
+function getProjectName(pluginProjectXML, relative_path) {
+    var projNameElt = pluginProjectXML.find("PropertyGroup/ProjectName");
+    // Falling back on project file name in case ProjectName is missing
+    return !!projNameElt ? projNameElt.text : path.basename(relative_path, path.extname(relative_path));
+}
+
+jsprojManager.getProject = function (directory) {
+    var projectFiles = shell.ls(path.join(directory, '*.projitems'));
+    if (projectFiles.length === 0) {
+        throw (new CordovaError('The directory ' + directory +
+            ' does not appear to be a Unified Windows Store project (no .projitems file found)'));
+    }
+    return new jsprojManager(path.normalize(projectFiles[0]));
+};
+
+jsprojManager.prototype = {
+    _projects: null,
+
+    getPackageName: function() {
+        // CB-10394 Do not cache manifest file while getting package name to avoid problems
+        // with windows.appxmanifest cached twice (here and in ConfigFile module)
+        return AppxManifest.get(path.join(this.root, 'package.windows.appxmanifest'), /*ignoreCache=*/true)
+            .getProperties().getDisplayName();
+    },
+
+    write: function () {
+        this.master.write();
+        if (this._projects) {
+            var that = this;
+            this._projects.forEach(function (project) {
+                if (project !== that.master && project.touched) {
+                    project.write();
+                }
+            });
+        }
+    },
+
+    addSDKRef: function (incText, targetConditions) {
+        events.emit('verbose', 'jsprojManager.addSDKRef(incText: ' + incText + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        var item = createItemGroupElement('ItemGroup/SDKReference', incText, targetConditions);
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.appendToRoot(item);
+        });
+    },
+
+    removeSDKRef: function (incText, targetConditions) {
+        events.emit('verbose', 'jsprojManager.removeSDKRef(incText: ' + incText + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.removeItemGroupElement('ItemGroup/SDKReference', incText, targetConditions);
+        });
+    },
+
+    addResourceFileToProject: function (sourcePath, destPath, targetConditions) {
+        events.emit('verbose', 'jsprojManager.addResourceFile(sourcePath: ' + sourcePath + ', destPath: ' + destPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        // add hint path with full path
+        var link = new et.Element('Link');
+        link.text = destPath;
+        var children = [link];
+
+        var copyToOutputDirectory = new et.Element('CopyToOutputDirectory');
+        copyToOutputDirectory.text = 'Always';
+        children.push(copyToOutputDirectory);
+
+        var item = createItemGroupElement('ItemGroup/Content', sourcePath, targetConditions, children);
+
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.appendToRoot(item);
+        });
+    },
+
+    removeResourceFileFromProject: function (relPath, targetConditions) {
+        events.emit('verbose', 'jsprojManager.removeResourceFile(relPath: ' + relPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.removeItemGroupElement('ItemGroup/Content', relPath, targetConditions);
+        });
+    },
+
+    addReference: function (relPath, targetConditions, implPath) {
+        events.emit('verbose', 'jsprojManager.addReference(incText: ' + relPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        // add hint path with full path
+        var hint_path = new et.Element('HintPath');
+        hint_path.text = relPath;
+        var children = [hint_path];
+
+        var extName = path.extname(relPath);
+        if (extName === ".winmd") {
+            var mdFileTag = new et.Element("IsWinMDFile");
+            mdFileTag.text = "true";
+            children.push(mdFileTag);
+        }
+
+        // We only need to add <Implementation> tag when dll base name differs from winmd name
+        if (implPath && path.basename(relPath, '.winmd') !== path.basename(implPath, '.dll')) {
+            var implementTag = new et.Element('Implementation');
+            implementTag.text = path.basename(implPath);
+            children.push(implementTag);
+        }
+
+        var item = createItemGroupElement('ItemGroup/Reference', path.basename(relPath, extName), targetConditions, children);
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.appendToRoot(item);
+        });
+    },
+
+    removeReference: function (relPath, targetConditions) {
+        events.emit('verbose', 'jsprojManager.removeReference(incText: ' + relPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        var extName = path.extname(relPath);
+        var includeText = path.basename(relPath, extName);
+
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.removeItemGroupElement('ItemGroup/Reference', includeText, targetConditions);
+        });
+    },
+
+    addSourceFile: function (relative_path) {
+        events.emit('verbose', 'jsprojManager.addSourceFile(relative_path: ' + relative_path + ')');
+        this.master.addSourceFile(relative_path);
+    },
+
+    removeSourceFile: function (relative_path) {
+        events.emit('verbose', 'jsprojManager.removeSourceFile(incText: ' + relative_path + ')');
+        this.master.removeSourceFile(relative_path);
+    },
+
+    addProjectReference: function (relative_path, targetConditions) {
+        events.emit('verbose', 'jsprojManager.addProjectReference(incText: ' + relative_path + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        // relative_path is the actual path to the file in the current OS, where-as inserted_path is what we write in
+        // the project file, and is always in Windows format.
+        relative_path = path.normalize(relative_path);
+        var inserted_path = relative_path.split('/').join('\\');
+
+        var pluginProjectXML = xml_helpers.parseElementtreeSync(path.resolve(this.projectFolder, relative_path));
+
+        // find the guid + name of the referenced project
+        var projectGuid = pluginProjectXML.find("PropertyGroup/ProjectGuid").text;
+        var projName = getProjectName(pluginProjectXML, relative_path);
+
+        // get the project type
+        var projectTypeGuid = getProjectTypeGuid(relative_path);
+        if (!projectTypeGuid) {
+            throw new CordovaError("Unrecognized project type at " + relative_path + " (not .csproj or .vcxproj)");
+        }
+
+        var preInsertText = "\tProjectSection(ProjectDependencies) = postProject\r\n" +
+            "\t\t" + projectGuid + "=" + projectGuid + "\r\n" +
+            "\tEndProjectSection\r\n";
+        var postInsertText = '\r\nProject("' + projectTypeGuid + '") = "' +
+            projName + '", "' + inserted_path + '", ' +
+            '"' + projectGuid + '"\r\nEndProject';
+
+        var matchingProjects = this._getMatchingProjects(targetConditions);
+        if (matchingProjects.length === 0) {
+            // No projects meet the specified target and version criteria, so nothing to do.
+            return;
+        }
+
+        // Will we be writing into the .projitems file rather than individual .jsproj files?
+        var useProjItems = this.isUniversalWindowsApp && matchingProjects.length === 1 && matchingProjects[0] === this.master;
+
+        // There may be multiple solution files (for different VS versions) - process them all
+        getSolutionPaths(this.projectFolder).forEach(function (solutionPath) {
+            var solText = fs.readFileSync(solutionPath, {encoding: "utf8"});
+
+            if (useProjItems) {
+                // Insert a project dependency into every jsproj in the solution.
+                var jsProjectFound = false;
+                solText = solText.replace(JSPROJ_REGEXP, function (match) {
+                    jsProjectFound = true;
+                    return match + preInsertText;
+                });
+
+                if (!jsProjectFound) {
+                    throw new CordovaError("No jsproj found in solution");
+                }
+            } else {
+                // Insert a project dependency only for projects that match specified target and version
+                matchingProjects.forEach(function (project) {
+                    solText = solText.replace(getJsProjRegExForProject(path.basename(project.location)), function (match) {
+                        return match + preInsertText;
+                    });
+                });
+            }
+
+            // Add the project after existing projects. Note that this fairly simplistic check should be fine, since the last
+            // EndProject in the file should actually be an EndProject (and not an EndProjectSection, for example).
+            var pos = solText.lastIndexOf("EndProject");
+            if (pos === -1) {
+                throw new Error("No EndProject found in solution");
+            }
+            pos += 10; // Move pos to the end of EndProject text
+            solText = solText.slice(0, pos) + postInsertText + solText.slice(pos);
+
+            fs.writeFileSync(solutionPath, solText, {encoding: "utf8"});
+        });
+
+        // Add the ItemGroup/ProjectReference to each matching cordova project :
+        // <ItemGroup><ProjectReference Include="blahblah.csproj"/></ItemGroup>
+        var item = createItemGroupElement('ItemGroup/ProjectReference', inserted_path, targetConditions);
+        matchingProjects.forEach(function (project) {
+            project.appendToRoot(item);
+        });
+    },
+
+    removeProjectReference: function (relative_path, targetConditions) {
+        events.emit('verbose', 'jsprojManager.removeProjectReference(incText: ' + relative_path + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        // relative_path is the actual path to the file in the current OS, where-as inserted_path is what we write in
+        // the project file, and is always in Windows format.
+        relative_path = path.normalize(relative_path);
+        var inserted_path = relative_path.split('/').join('\\');
+
+        // find the guid + name of the referenced project
+        var pluginProjectXML = xml_helpers.parseElementtreeSync(path.resolve(this.projectFolder, relative_path));
+        var projectGuid = pluginProjectXML.find("PropertyGroup/ProjectGuid").text;
+        var projName = getProjectName(pluginProjectXML, relative_path);
+
+        // get the project type
+        var projectTypeGuid = getProjectTypeGuid(relative_path);
+        if (!projectTypeGuid) {
+            throw new Error("Unrecognized project type at " + relative_path + " (not .csproj or .vcxproj)");
+        }
+
+        var preInsertTextRegExp = getProjectReferencePreInsertRegExp(projectGuid);
+        var postInsertTextRegExp = getProjectReferencePostInsertRegExp(projName, projectGuid, inserted_path, projectTypeGuid);
+
+        // There may be multiple solutions (for different VS versions) - process them all
+        getSolutionPaths(this.projectFolder).forEach(function (solutionPath) {
+            var solText = fs.readFileSync(solutionPath, {encoding: "utf8"});
+
+            // To be safe (to handle subtle changes in formatting, for example), use a RegExp to find and remove
+            // preInsertText and postInsertText
+
+            solText = solText.replace(preInsertTextRegExp, function () {
+                return "";
+            });
+
+            solText = solText.replace(postInsertTextRegExp, function () {
+                return "";
+            });
+
+            fs.writeFileSync(solutionPath, solText, {encoding: "utf8"});
+        });
+
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.removeItemGroupElement('ItemGroup/ProjectReference', inserted_path, targetConditions);
+        });
+    },
+
+    _getMatchingProjects: function (targetConditions) {
+        // If specified, target can be 'all' (default), 'phone' or 'windows'. Ultimately should probably allow a comma
+        // separated list, but not needed now.
+        var target = getDeviceTarget(targetConditions);
+        var versions = getVersions(targetConditions);
+
+        if (target || versions) {
+            var matchingProjects = this.projects.filter(function (project) {
+                return (!target || target === project.target) &&
+                    (!versions || semver.satisfies(project.getSemVersion(), versions, /* loose */ true));
+            });
+
+            if (matchingProjects.length < this.projects.length) {
+                return matchingProjects;
+            }
+        }
+
+        // All projects match. If this is a universal project, return the projitems file. Otherwise return our single
+        // project.
+        return [this.master];
+    },
+
+    get projects() {
+        var projects = this._projects;
+        if (!projects) {
+            projects = [];
+            this._projects = projects;
+
+            if (this.isUniversalWindowsApp) {
+                var projectPath = this.projectFolder;
+                var projectFiles = shell.ls(path.join(projectPath, '*.jsproj'));
+                projectFiles.forEach(function (projectFile) {
+                    projects.push(new jsproj(projectFile));
+                });
+            } else {
+                this.projects.push(this.master);
+            }
+        }
+
+        return projects;
+    }
+};
+
+jsprojManager.prototype.getInstaller = function (type) {
+    return PluginHandler.getInstaller(type);
+};
+
+jsprojManager.prototype.getUninstaller = function (type) {
+    return PluginHandler.getUninstaller(type);
+};
+
+function getProjectReferencePreInsertRegExp(projectGuid) {
+    projectGuid = escapeRegExpString(projectGuid);
+    return new RegExp("\\s*ProjectSection\\(ProjectDependencies\\)\\s*=\\s*postProject\\s*" + projectGuid + "\\s*=\\s*" + projectGuid + "\\s*EndProjectSection", "gi");
+}
+
+function getProjectReferencePostInsertRegExp(projName, projectGuid, relative_path, projectTypeGuid) {
+    projName = escapeRegExpString(projName);
+    projectGuid = escapeRegExpString(projectGuid);
+    relative_path = escapeRegExpString(relative_path);
+    projectTypeGuid = escapeRegExpString(projectTypeGuid);
+    return new RegExp('\\s*Project\\("' + projectTypeGuid + '"\\)\\s*=\\s*"' + projName + '"\\s*,\\s*"' + relative_path + '"\\s*,\\s*"' + projectGuid + '"\\s*EndProject', 'gi');
+}
+
+function getSolutionPaths(projectFolder) {
+    return shell.ls(path.join(projectFolder, "*.sln"));
+}
+
+function escapeRegExpString(regExpString) {
+    return regExpString.replace(ESCAPE_REGEXP, "\\$1");
+}
+
+function getJsProjRegExForProject(projectFile) {
+    projectFile = escapeRegExpString(projectFile);
+    return new RegExp('(Project\\("\\{262852C6-CD72-467D-83FE-5EEB1973A190}"\\)\\s*=\\s*"[^"]+",\\s*"' + projectFile + '",\\s*"\\{[0-9a-f\\-]+}"[^\\r\\n]*[\\r\\n]*)', 'gi');
+}
+
+function getProjectTypeGuid(projectPath) {
+    switch (path.extname(projectPath)) {
+        case ".vcxproj":
+            return WinCplusplusProjectTypeGUID;
+
+        case ".csproj":
+            return WinCSharpProjectTypeGUID;
+    }
+    return null;
+}
+
+function createItemGroupElement(path, incText, targetConditions, children) {
+    path = path.split('/');
+    path.reverse();
+
+    var lastElement = null;
+    path.forEach(function (elementName) {
+        var element = new et.Element(elementName);
+        if (lastElement) {
+            element.append(lastElement);
+        } else {
+            element.attrib.Include = incText;
+
+            var condition = createConditionAttrib(targetConditions);
+            if (condition) {
+                element.attrib.Condition = condition;
+            }
+
+            if (children) {
+                children.forEach(function (child) {
+                    element.append(child);
+                });
+            }
+        }
+        lastElement = element;
+    });
+
+    return lastElement;
+}
+
+function getDeviceTarget(targetConditions) {
+    var target = targetConditions.deviceTarget;
+    if (target) {
+        target = target.toLowerCase().trim();
+        if (target === "all") {
+            target = null;
+        } else if (target === "win") {
+            // Allow "win" as alternative to "windows"
+            target = "windows";
+        } else if (target !== 'phone' && target !== 'windows') {
+            throw new Error('Invalid device-target attribute (must be "all", "phone", "windows" or "win"): ' + target);
+        }
+    }
+    return target;
+}
+
+function getVersions(targetConditions) {
+    var versions = targetConditions.versions;
+    if (versions && !semver.validRange(versions, /* loose */ true)) {
+        throw new Error('Invalid versions attribute (must be a valid semantic version range): ' + versions);
+    }
+    return versions;
+}
+
+
+/* proj */
+
+function proj(location) {
+    // Class to handle simple project xml operations
+    if (!location) {
+        throw new Error('Project file location can\'t be null or empty');
+    }
+    this.location = location;
+    this.xml = xml_helpers.parseElementtreeSync(location);
+}
+
+proj.prototype = {
+    write: function () {
+        fs.writeFileSync(this.location, this.xml.write({indent: 4}), 'utf-8');
+    },
+
+    appendToRoot: function (element) {
+        this.touched = true;
+        this.xml.getroot().append(element);
+    },
+
+    removeItemGroupElement: function (path, incText, targetConditions) {
+        var xpath = path + '[@Include="' + incText + '"]';
+        var condition = createConditionAttrib(targetConditions);
+        if (condition) {
+            xpath += '[@Condition="' + condition + '"]';
+        }
+        xpath += '/..';
+
+        var itemGroup = this.xml.find(xpath);
+        if (itemGroup) {
+            this.touched = true;
+            this.xml.getroot().remove(itemGroup);
+        }
+    },
+
+    addSourceFile: function (relative_path) {
+        // we allow multiple paths to be passed at once as array so that
+        // we don't create separate ItemGroup for each source file, CB-6874
+        if (!(relative_path instanceof Array)) {
+            relative_path = [relative_path];
+        }
+
+        // make ItemGroup to hold file.
+        var item = new et.Element('ItemGroup');
+
+        relative_path.forEach(function (filePath) {
+            // filePath is never used to find the actual file - it determines what we write to the project file, and so
+            // should always be in Windows format.
+            filePath = filePath.split('/').join('\\');
+
+            var content = new et.Element('Content');
+            content.attrib.Include = filePath;
+            item.append(content);
+        });
+
+        this.appendToRoot(item);
+    },
+
+    removeSourceFile: function (relative_path) {
+        var isRegexp = relative_path instanceof RegExp;
+        if (!isRegexp) {
+            // relative_path is never used to find the actual file - it determines what we write to the project file,
+            // and so should always be in Windows format.
+            relative_path = relative_path.split('/').join('\\');
+        }
+
+        var root = this.xml.getroot();
+        var that = this;
+        // iterate through all ItemGroup/Content elements and remove all items matched
+        this.xml.findall('ItemGroup').forEach(function (group) {
+            // matched files in current ItemGroup
+            var filesToRemove = group.findall('Content').filter(function (item) {
+                if (!item.attrib.Include) {
+                    return false;
+                }
+                return isRegexp ? item.attrib.Include.match(relative_path) : item.attrib.Include === relative_path;
+            });
+
+            // nothing to remove, skip..
+            if (filesToRemove.length < 1) {
+                return;
+            }
+
+            filesToRemove.forEach(function (file) {
+                // remove file reference
+                group.remove(file);
+            });
+            // remove ItemGroup if empty
+            if (group.findall('*').length < 1) {
+                that.touched = true;
+                root.remove(group);
+            }
+        });
+    }
+};
+
+
+/* jsproj */
+
+function jsproj(location) {
+    function targetPlatformIdentifierToDevice(jsprojPlatform) {
+        var index = ["Windows", "WindowsPhoneApp", "UAP"].indexOf(jsprojPlatform);
+        if (index < 0) {
+            throw new Error("Unknown TargetPlatformIdentifier '" + jsprojPlatform + "' in project file '" + location + "'");
+        }
+        return ["windows", "phone", "windows"][index];
+    }
+
+    function validateVersion(version) {
+        version = version.split('.');
+        while (version.length < 3) {
+            version.push("0");
+        }
+        return version.join(".");
+    }
+
+    // Class to handle a jsproj file
+    proj.call(this, location);
+
+    var propertyGroup = this.xml.find('PropertyGroup[TargetPlatformIdentifier]');
+    if (!propertyGroup) {
+        throw new Error("Unable to find PropertyGroup/TargetPlatformIdentifier in project file '" + this.location + "'");
+    }
+
+    var jsprojPlatform = propertyGroup.find('TargetPlatformIdentifier').text;
+    this.target = targetPlatformIdentifierToDevice(jsprojPlatform);
+
+    var version = propertyGroup.find('TargetPlatformVersion');
+    if (!version) {
+        throw new Error("Unable to find PropertyGroup/TargetPlatformVersion in project file '" + this.location + "'");
+    }
+    this.version = validateVersion(version.text);
+}
+
+util.inherits(jsproj, proj);
+
+jsproj.prototype.target = null;
+jsproj.prototype.version = null;
+
+// Returns valid semantic version (http://semver.org/).
+jsproj.prototype.getSemVersion = function () {
+    // For example, for version 10.0.10240.0 we will return 10.0.10240 (first three components)
+    var semVersion = this.version;
+    var splittedVersion = semVersion.split('.');
+    if (splittedVersion.length > 3) {
+        semVersion = splittedVersion.splice(0, 3).join('.');
+    }
+
+    return semVersion;
+    // Alternative approach could be replacing last dot with plus sign to
+    // be complaint w/ semver specification, for example
+    // 10.0.10240.0 -> 10.0.10240+0
+};
+
+/* Common support functions */
+
+function createConditionAttrib(targetConditions) {
+    var arch = targetConditions.arch;
+    if (arch) {
+        if (arch === "arm") {
+            // Specifcally allow "arm" as alternative to "ARM"
+            arch = "ARM";
+        } else if (arch !== "x86" && arch !== "x64" && arch !== "ARM") {
+            throw new Error('Invalid arch attribute (must be "x86", "x64" or "ARM"): ' + arch);
+        }
+        return "'$(Platform)'=='" + arch + "'";
+    }
+    return null;
+}
+
+
+module.exports = jsprojManager;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js
----------------------------------------------------------------------
diff --git a/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js
new file mode 100644
index 0000000..c264f20
--- /dev/null
+++ b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js
@@ -0,0 +1,282 @@
+/*
+ *
+ * Copyright 2013 Jesse MacFadyen
+ *
+ * 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 shell = require('shelljs');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+
+// returns relative file path for a file in the plugin's folder that can be referenced
+// from a project file.
+function getPluginFilePath(plugin, pluginFile, targetDir) {
+    var src = path.resolve(plugin.dir, pluginFile);
+    return '$(ProjectDir)' + path.relative(targetDir, src);
+}
+
+var handlers = {
+    'source-file': {
+        install:function(obj, plugin, project, options) {
+            var dest = path.join('plugins', plugin.id, obj.targetDir || '', path.basename(obj.src));
+            if (options && options.force) {
+                copyFile(plugin.dir, obj.src, project.root, dest);
+            } else {
+                copyNewFile(plugin.dir, obj.src, project.root, dest);
+            }
+            // add reference to this file to jsproj.
+            project.addSourceFile(dest);
+        },
+        uninstall:function(obj, plugin, project, options) {
+            var dest = path.join('plugins', plugin.id, obj.targetDir || '', path.basename(obj.src));
+            removeFile(project.root, dest);
+            // remove reference to this file from csproj.
+            project.removeSourceFile(dest);
+        }
+    },
+    'resource-file':{
+        install:function(obj, plugin, project, options) {
+            if (obj.reference) {
+                // do not copy, but reference the file in the plugin folder. This allows to
+                // have multiple source files map to the same target and select the appropriate
+                // one based on the current build settings, e.g. architecture.
+                // also, we don't check for existence. This allows to insert build variables
+                // into the source file name, e.g.
+                // <resource-file src="$(Platform)/My.dll" target="My.dll" />
+                var relativeSrcPath = getPluginFilePath(plugin, obj.src, project.projectFolder);
+                project.addResourceFileToProject(relativeSrcPath, obj.target, getTargetConditions(obj));
+            } else {
+                // if target already exists, emit warning to consider using a reference instead of copying
+                if (fs.existsSync(path.resolve(project.root, obj.target))) {
+                    events.emit('warn', '<resource-file> with target ' + obj.target + ' already exists and will be overwritten ' +
+                    'by a <resource-file> with the same target. Consider using the attribute reference="true" in the ' +
+                    '<resource-file> tag to avoid overwriting files with the same target. Using reference will not copy files ' +
+                    'to the destination, instead will create a reference to the source path.');
+                }
+                // as per specification resource-file target is specified relative to platform root
+                copyFile(plugin.dir, obj.src, project.root, obj.target);
+                project.addResourceFileToProject(obj.target, obj.target, getTargetConditions(obj));
+            }
+        },
+        uninstall:function(obj, plugin, project, options) {
+            if (obj.reference) {
+                var relativeSrcPath = getPluginFilePath(plugin, obj.src, project.projectFolder);
+                project.removeResourceFileFromProject(relativeSrcPath, getTargetConditions(obj));
+            } else {
+                removeFile(project.root, obj.target);
+                project.removeResourceFileFromProject(obj.target, getTargetConditions(obj));
+            }
+        }
+    },
+    'lib-file': {
+        install:function(obj, plugin, project, options) {
+            var inc  = obj.Include || obj.src;
+            project.addSDKRef(inc, getTargetConditions(obj));
+        },
+        uninstall:function(obj, plugin, project, options) {
+            events.emit('verbose', 'windows lib-file uninstall :: ' + plugin.id);
+            var inc = obj.Include || obj.src;
+            project.removeSDKRef(inc, getTargetConditions(obj));
+        }
+    },
+    'framework': {
+        install:function(obj, plugin, project, options) {
+            events.emit('verbose', 'windows framework install :: ' + plugin.id);
+
+            var src = obj.src;
+            var dest = src;
+            var type = obj.type;
+            var targetDir = obj.targetDir || '';
+            var implementPath = obj.implementation;
+
+            if(type === 'projectReference') {
+                dest = path.join(path.relative(project.projectFolder, plugin.dir), targetDir, src);
+                project.addProjectReference(dest, getTargetConditions(obj));
+            } else {
+                // path.join ignores empty paths passed so we don't check whether targetDir is not empty
+                dest = path.join('plugins', plugin.id, targetDir, path.basename(src));
+                copyFile(plugin.dir, src, project.root, dest);
+                if (implementPath) {
+                    copyFile(plugin.dir, implementPath, project.root, path.join(path.dirname(dest), path.basename(implementPath)));
+                }
+                project.addReference(dest, getTargetConditions(obj), implementPath);
+            }
+
+        },
+        uninstall:function(obj, plugin, project, options) {
+            events.emit('verbose', 'windows framework uninstall :: ' + plugin.id  );
+
+            var src = obj.src;
+            var type = obj.type;
+
+            if(type === 'projectReference') {
+                project.removeProjectReference(path.join(path.relative(project.projectFolder, plugin.dir), src), getTargetConditions(obj));
+            }
+            else {
+                var targetPath = path.join('plugins', plugin.id);
+                removeFile(project.root, targetPath);
+                project.removeReference(src, getTargetConditions(obj));
+            }
+        }
+    },
+    asset:{
+        install:function(obj, plugin, project, options) {
+            if (!obj.src) {
+                throw new CordovaError(generateAttributeError('src', 'asset', plugin.id));
+            }
+            if (!obj.target) {
+                throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
+            }
+
+            copyFile(plugin.dir, obj.src, project.www, obj.target);
+            if (options && options.usePlatformWww) copyFile(plugin.dir, obj.src, project.platformWww, obj.target);
+        },
+        uninstall:function(obj, plugin, project, options) {
+            var target = obj.target || obj.src;
+
+            if (!target) throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
+
+            removeFile(project.www, target);
+            removeFile(project.www, path.join('plugins', plugin.id));
+            if (options && options.usePlatformWww) {
+                removeFile(project.platformWww, target);
+                removeFile(project.platformWww, path.join('plugins', plugin.id));
+            }
+        }
+    },
+    'js-module': {
+        install: function (obj, plugin, project, options) {
+            // Copy the plugin's files into the www directory.
+            var moduleSource = path.resolve(plugin.dir, obj.src);
+            var moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname (obj.src)));
+
+            // Read in the file, prepend the cordova.define, and write it back out.
+            var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
+            if (moduleSource.match(/.*\.json$/)) {
+                scriptContent = 'module.exports = ' + scriptContent;
+            }
+            scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
+
+            var moduleDestination = path.resolve(project.www, 'plugins', plugin.id, obj.src);
+            shell.mkdir('-p', path.dirname(moduleDestination));
+            fs.writeFileSync(moduleDestination, scriptContent, 'utf-8');
+            if (options && options.usePlatformWww) {
+                var platformWwwDestination = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src);
+                shell.mkdir('-p', path.dirname(platformWwwDestination));
+                fs.writeFileSync(platformWwwDestination, scriptContent, 'utf-8');
+            }
+        },
+        uninstall: function (obj, plugin, project, options) {
+            var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
+            removeFileAndParents(project.www, pluginRelativePath);
+            if (options && options.usePlatformWww) removeFileAndParents(project.platformWww, pluginRelativePath);
+        }
+    }
+};
+
+// Helpers from common
+
+module.exports.getInstaller = function (type) {
+    if (handlers[type] && handlers[type].install) {
+        return handlers[type].install;
+    }
+
+    events.emit('verbose', '<' + type + '> is not supported for Windows plugins');
+};
+
+module.exports.getUninstaller = function(type) {
+    if (handlers[type] && handlers[type].uninstall) {
+        return handlers[type].uninstall;
+    }
+
+    events.emit('verbose', '<' + type + '> is not supported for Windows plugins');
+};
+
+function getTargetConditions(obj) {
+    return { versions: obj.versions, deviceTarget: obj.deviceTarget, arch: obj.arch };
+}
+
+function copyFile (plugin_dir, src, project_dir, dest, link) {
+    src = path.resolve(plugin_dir, src);
+    if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');
+
+    // check that src path is inside plugin directory
+    var real_path = fs.realpathSync(src);
+    var real_plugin_path = fs.realpathSync(plugin_dir);
+    if (real_path.indexOf(real_plugin_path) !== 0)
+        throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"');
+
+    dest = path.resolve(project_dir, dest);
+
+    // check that dest path is located in project directory
+    if (dest.indexOf(path.resolve(project_dir)) !== 0)
+        throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project');
+
+    shell.mkdir('-p', path.dirname(dest));
+
+    if (link) {
+        fs.symlinkSync(path.relative(path.dirname(dest), src), dest);
+    } else if (fs.statSync(src).isDirectory()) {
+        // XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
+        shell.cp('-Rf', src+'/*', dest);
+    } else {
+        shell.cp('-f', src, dest);
+    }
+}
+
+// Same as copy file but throws error if target exists
+function copyNewFile (plugin_dir, src, project_dir, dest, link) {
+    var target_path = path.resolve(project_dir, dest);
+    if (fs.existsSync(target_path))
+        throw new CordovaError('"' + target_path + '" already exists!');
+
+    copyFile(plugin_dir, src, project_dir, dest, !!link);
+}
+
+// checks if file exists and then deletes. Error if doesn't exist
+function removeFile (project_dir, src) {
+    var file = path.resolve(project_dir, src);
+    shell.rm('-Rf', file);
+}
+
+function removeFileAndParents (baseDir, destFile, stopper) {
+    stopper = stopper || '.';
+    var file = path.resolve(baseDir, destFile);
+    if (!fs.existsSync(file)) return;
+
+    shell.rm('-rf', file);
+
+    // check if directory is empty
+    var curDir = path.dirname(file);
+
+    while(curDir !== path.resolve(baseDir, stopper)) {
+        if(fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
+            fs.rmdirSync(curDir);
+            curDir = path.resolve(curDir, '..');
+        } else {
+            // directory not empty...do nothing
+            break;
+        }
+    }
+}
+
+function generateAttributeError(attribute, element, id) {
+    return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id;
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js
----------------------------------------------------------------------
diff --git a/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js
new file mode 100644
index 0000000..78c9593
--- /dev/null
+++ b/spec/cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js
@@ -0,0 +1,139 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+var semver = require('semver');
+var CommonPluginInfo = require('cordova-common').PluginInfo;
+
+var MANIFESTS = {
+    'windows': {
+        '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.1.0': ['package.windows.appxmanifest', 'package.phone.appxmanifest'],
+        '10.0.0': 'package.windows10.appxmanifest'
+    }
+};
+
+var SUBSTS = ['package.phone.appxmanifest', 'package.windows.appxmanifest', 'package.windows10.appxmanifest'];
+var TARGETS = ['windows', 'phone', 'all'];
+
+function processChanges(changes) {
+    var hasManifestChanges  = changes.some(function(change) {
+        return change.target === 'package.appxmanifest';
+    });
+
+    if (!hasManifestChanges) {
+        return changes;
+    }
+
+    // Demux 'package.appxmanifest' into relevant platform-specific appx manifests.
+    // Only spend the cycles if there are version-specific plugin settings
+    var oldChanges = changes;
+    changes = [];
+
+    oldChanges.forEach(function(change) {
+        // 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 manifestsForChange = getManifestsForChange(change);
+        changes = changes.concat(demuxChangeWithSubsts(change, manifestsForChange));
+    });
+
+    return changes;
+}
+
+function demuxChangeWithSubsts(change, manifestFiles) {
+    return manifestFiles.map(function(file) {
+         return createReplacement(file, change);
+    });
+}
+
+function getManifestsForChange(change) {
+    var hasTarget = (typeof change.deviceTarget !== 'undefined');
+    var hasVersion = (typeof change.versions !== 'undefined');
+
+    var targetDeviceSet = hasTarget ? change.deviceTarget : 'all';
+
+    if (TARGETS.indexOf(targetDeviceSet) === -1) {
+        // target-device couldn't be resolved, fix it up here to a valid value
+        targetDeviceSet = 'all';
+    }
+
+    // No semver/device-target for this config-file, pass it through
+    if (!(hasTarget || hasVersion)) {
+        return SUBSTS;
+    }
+
+    var knownWindowsVersionsForTargetDeviceSet = Object.keys(MANIFESTS[targetDeviceSet]);
+    return knownWindowsVersionsForTargetDeviceSet.reduce(function(manifestFiles, winver) {
+        if (hasVersion && !semver.satisfies(winver, change.versions)) {
+            return manifestFiles;
+        }
+        return manifestFiles.concat(MANIFESTS[targetDeviceSet][winver]);
+    }, []);
+}
+
+// This is a local function that creates the new replacement representing the
+// mutation.  Used to save code further down.
+function createReplacement(manifestFile, originalChange) {
+    var replacement = {
+        target:         manifestFile,
+        parent:         originalChange.parent,
+        after:          originalChange.after,
+        xmls:           originalChange.xmls,
+        versions:       originalChange.versions,
+        deviceTarget:   originalChange.deviceTarget
+    };
+    return replacement;
+}
+
+
+/*
+A class for holidng the information currently stored in plugin.xml
+It's inherited from cordova-common's PluginInfo class
+In addition it overrides getConfigFiles, getEditConfigs, getFrameworks methods to use windows-specific logic
+ */
+function PluginInfo(dirname) {
+    //  We're not using `util.inherit' because original PluginInfo defines
+    //  its' methods inside of constructor
+    CommonPluginInfo.apply(this, arguments);
+    var parentGetConfigFiles = this.getConfigFiles;
+    var parentGetEditConfigs = this.getEditConfigs;
+
+    this.getEditConfigs = function(platform) {
+        var editConfigs = parentGetEditConfigs(platform);
+        return processChanges(editConfigs);
+    };
+
+    this.getConfigFiles = function(platform) {
+        var configFiles = parentGetConfigFiles(platform);
+        return processChanges(configFiles);
+    };
+}
+
+exports.PluginInfo = PluginInfo;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec/cordova/platforms/platforms.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova/platforms/platforms.spec.js b/spec/cordova/platforms/platforms.spec.js
index 7045ae7..cf09410 100644
--- a/spec/cordova/platforms/platforms.spec.js
+++ b/spec/cordova/platforms/platforms.spec.js
@@ -85,7 +85,7 @@ describe('getPlatformApi method', function () {
     it('should throw error if using deprecated platform', function () {
         try {
             platforms.getPlatformApi('android', path.join(CORDOVA_ROOT, 'platforms/android'));
-        } catch(error) {
+        } catch (error) {
             expect(error.toString()).toContain('Using this version of Cordova with older version of cordova-android is deprecated. Upgrade to cordova-android@5.0.0 or newer.');
         }
     });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec/cordova/util.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova/util.spec.js b/spec/cordova/util.spec.js
index 5d4a2f6..08b0a16 100644
--- a/spec/cordova/util.spec.js
+++ b/spec/cordova/util.spec.js
@@ -310,21 +310,21 @@ describe('util module', function () {
             });
         });
 
-        describe('getPlatformApiFunction', function() {
-            it('Test 027 : should throw error informing user to update platform', function() {
-                expect(function(){util.getPlatformApiFunction('some/path', 'android');}).toThrow(new Error
+        describe('getPlatformApiFunction', function () {
+            it('Test 027 : should throw error informing user to update platform', function () {
+                expect(function () { util.getPlatformApiFunction('some/path', 'android'); }).toThrow(new Error // eslint-disable-line func-call-spacing
                 ('Uncaught, unspecified "error" event. ( Using this version of Cordova with older version of cordova-android is deprecated. Upgrade to cordova-android@5.0.0 or newer.)'));
             });
 
-            it('Test 028 : should throw error if platform is not supported', function() {
+            it('Test 028 : should throw error if platform is not supported', function () {
                 spyOn(events, 'emit').and.returnValue(true);
-                expect(function(){util.getPlatformApiFunction('some/path', 'somePlatform');}).toThrow();
+                expect(function () { util.getPlatformApiFunction('some/path', 'somePlatform'); }).toThrow();
                 expect(events.emit.calls.count()).toBe(2);
                 expect(events.emit.calls.argsFor(0)[1]).toBe('Unable to load PlatformApi from platform. Error: Cannot find module \'some/path\'');
                 expect(events.emit.calls.argsFor(1)[1]).toBe('The platform "somePlatform" does not appear to be a valid cordova platform. It is missing API.js. somePlatform not supported.');
             });
 
-            it('Test 029 : should use polyfill if blackberry10, webos, ubuntu', function() {
+            it('Test 029 : should use polyfill if blackberry10, webos, ubuntu', function () {
                 spyOn(events, 'emit').and.returnValue(true);
                 util.getPlatformApiFunction('some/path', 'blackberry10');
                 expect(events.emit.calls.count()).toBe(3);
@@ -333,19 +333,18 @@ describe('util module', function () {
                 expect(events.emit.calls.argsFor(2)[1]).toBe('Failed to require PlatformApi instance for platform "blackberry10". Using polyfill instead.');
             });
 
-            it('Test 030 : successfully find platform Api', function() {
+            it('Test 030 : successfully find platform Api', function () {
                 spyOn(events, 'emit').and.returnValue(true);
                 var specPlugDir = __dirname.replace('spec-cordova', 'spec-plugman');
-                util.getPlatformApiFunction((path.join(specPlugDir, 'projects', 'android', 'cordova', 'Api.js')), 'android');
+                util.getPlatformApiFunction((path.join(specPlugDir, 'fixtures', 'projects', 'platformApi', 'platforms', 'windows', 'cordova', 'Api.js')), 'windows');
                 expect(events.emit.calls.count()).toBe(1);
-                expect(events.emit.calls.argsFor(0)[1]).toBe('PlatformApi successfully found for platform android');
+                expect(events.emit.calls.argsFor(0)[1]).toBe('PlatformApi successfully found for platform windows');
             });
 
-            it('Test 031 : should inform user that entry point should be called Api.js', function() {
+            it('Test 031 : should inform user that entry point should be called Api.js', function () {
                 spyOn(events, 'emit').and.returnValue(true);
                 var specPlugDir = __dirname.replace('spec-cordova', 'spec-plugman');
-                expect(function(){util.getPlatformApiFunction((path.join(specPlugDir, 'projects', 'android', 'cordova', 'clean')), 'android');}).toThrow();
-                expect(events.emit.calls.count()).toBe(3);
+                expect(function () { util.getPlatformApiFunction((path.join(specPlugDir, 'fixtures', 'projects', 'platformApi', 'platforms', 'windows', 'cordova', 'lib', 'PluginInfo.js')), 'windows'); }).toThrow();
                 expect(events.emit.calls.argsFor(0)[1]).toBe('File name should be called Api.js.');
             });
         });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/src/cordova/util.js
----------------------------------------------------------------------
diff --git a/src/cordova/util.js b/src/cordova/util.js
index 6bf8f82..972ca89 100644
--- a/src/cordova/util.js
+++ b/src/cordova/util.js
@@ -461,7 +461,7 @@ function getLatestNpmVersion (module_name) {
 
 // Takes a libDir (root of platform where pkgJson is expected) & a platform name.
 // Platform is used if things go wrong, so we can use polyfill.
-// Potential errors : path doesn't exist, module isn't found or can't load. 
+// Potential errors : path doesn't exist, module isn't found or can't load.
 // Message prints if file not named Api.js or falls back to pollyfill.
 function getPlatformApiFunction (libDir, platform) {
     var PlatformApi;
@@ -471,7 +471,7 @@ function getPlatformApiFunction (libDir, platform) {
         // This will throw if package.json does not exist, or specify 'main'.
         var apiEntryPoint = require.resolve(libDir);
         if (apiEntryPoint) {
-            if(path.basename(apiEntryPoint) !== 'Api.js') {
+            if (path.basename(apiEntryPoint) !== 'Api.js') {
                 events.emit('verbose', 'File name should be called Api.js.');
                 // Not an error, still load it ...
             }
@@ -481,26 +481,23 @@ function getPlatformApiFunction (libDir, platform) {
                 PlatformApi = null;
                 events.emit('error', 'Does not appear to implement platform Api.');
             } else {
-               events.emit('verbose', 'PlatformApi successfully found for platform ' + platform); 
+                events.emit('verbose', 'PlatformApi successfully found for platform ' + platform);
             }
-        }
-        else {
+        } else {
             events.emit('verbose', 'No Api.js entry point found.');
         }
-    }
-    catch (err) {
+    } catch (err) {
         // Emit the err, someone might care ...
-        events.emit('warn','Unable to load PlatformApi from platform. ' + err);
+        events.emit('warn', 'Unable to load PlatformApi from platform. ' + err);
         // Check if platform already compatible w/ PlatformApi and show deprecation warning if not
-        //checkPlatformApiCompatible(platform);
+        // checkPlatformApiCompatible(platform);
         if (platforms[platform] && platforms[platform].apiCompatibleSince) {
             events.emit('error', ' Using this version of Cordova with older version of cordova-' + platform +
                     ' is deprecated. Upgrade to cordova-' + platform + '@' +
                     platforms[platform].apiCompatibleSince + ' or newer.');
-        }
-        else if (!platforms[platform]) {
+        } else if (!platforms[platform]) {
             // Throw error because polyfill doesn't support non core platforms
-            events.emit('error', 'The platform "' + platform + '" does not appear to be a valid cordova platform. It is missing API.js. '+ platform +' not supported.');
+            events.emit('error', 'The platform "' + platform + '" does not appear to be a valid cordova platform. It is missing API.js. ' + platform + ' not supported.');
         } else {
             events.emit('verbose', 'Platform not found or needs polyfill.');
         }
@@ -508,8 +505,8 @@ function getPlatformApiFunction (libDir, platform) {
 
     if (!PlatformApi) {
         // The platform just does not expose Api and we will try to polyfill it
-        var polyPlatforms = ['blackberry10','browser','ubuntu','webos'];
-        if( polyPlatforms.indexOf(platform) > -1) {
+        var polyPlatforms = ['blackberry10', 'browser', 'ubuntu', 'webos'];
+        if (polyPlatforms.indexOf(platform) > -1) {
             events.emit('verbose', 'Failed to require PlatformApi instance for platform "' + platform +
             '". Using polyfill instead.');
             PlatformApi = require('../platforms/PlatformApiPoly.js');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/src/plugman/init-defaults.js
----------------------------------------------------------------------
diff --git a/src/plugman/init-defaults.js b/src/plugman/init-defaults.js
index 2fedc41..361968c 100644
--- a/src/plugman/init-defaults.js
+++ b/src/plugman/init-defaults.js
@@ -135,19 +135,21 @@ if (!pkg.engines) {
     }
 }
 
+/* eslint-disable indent */
 if (!pkg.author) {
     exports.author = (config.get('init.author.name') ||
                      config.get('init-author-name')) ?
-        {
-            'name': config.get('init.author.name') ||
-                                    config.get('init-author-name'),
-            'email': config.get('init.author.email') ||
-                                    config.get('init-author-email'),
-            'url': config.get('init.author.url') ||
-                                    config.get('init-author-url')
-        }
+    {
+        'name': config.get('init.author.name') ||
+                            config.get('init-author-name'),
+        'email': config.get('init.author.email') ||
+                            config.get('init-author-email'),
+        'url': config.get('init.author.url') ||
+                            config.get('init-author-url')
+    }
         : prompt('author');
 }
+/* eslint-enable indent */
 
 var license = pkg.license ||
               defaults.license ||


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


[4/4] cordova-lib git commit: CB-12870 : rebased and updated paths

Posted by au...@apache.org.
CB-12870 : rebased and updated paths


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

Branch: refs/heads/master
Commit: 6b98390ecefb28a7e2249ac32d5ace2b463971a6
Parents: 60bbd8b
Author: Audrey So <au...@apache.org>
Authored: Thu Aug 10 11:25:39 2017 -0700
Committer: Audrey So <au...@apache.org>
Committed: Tue Aug 29 16:12:42 2017 -0700

----------------------------------------------------------------------
 integration-tests/pkgJson-restore.spec.js       |   2 +-
 package.json                                    |   2 +-
 .../windows/cordova/lib/AppxManifest.js         | 733 -------------------
 .../windows/cordova/lib/ConfigChanges.js        | 164 -----
 .../windows/cordova/lib/JsprojManager.js        | 626 ----------------
 .../windows/cordova/lib/PluginHandler.js        | 282 -------
 .../platforms/windows/cordova/lib/PluginInfo.js | 139 ----
 .../windows/cordova/lib/AppxManifest.js         | 733 +++++++++++++++++++
 .../windows/cordova/lib/ConfigChanges.js        | 164 +++++
 .../windows/cordova/lib/JsprojManager.js        | 626 ++++++++++++++++
 .../windows/cordova/lib/PluginHandler.js        | 282 +++++++
 .../platforms/windows/cordova/lib/PluginInfo.js | 139 ++++
 spec/cordova/platforms/platforms.spec.js        |   2 +-
 spec/cordova/util.spec.js                       |  23 +-
 src/cordova/util.js                             |  25 +-
 src/plugman/init-defaults.js                    |  18 +-
 16 files changed, 1979 insertions(+), 1981 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/integration-tests/pkgJson-restore.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/pkgJson-restore.spec.js b/integration-tests/pkgJson-restore.spec.js
index feaf090..dfd1f10 100644
--- a/integration-tests/pkgJson-restore.spec.js
+++ b/integration-tests/pkgJson-restore.spec.js
@@ -1249,7 +1249,7 @@ describe('platforms and plugins should be restored with config.xml even without
         // Copy then move because we need to copy everything, but that means it will copy the whole directory.
         // Using /* doesn't work because of hidden files.
         // Use basePkgJson13 because pkg.json and config.xml contain only android
-        shell.cp('-R', path.join(__dirname, '..', 'spec-cordova', 'fixtures', 'basePkgJson13'), tmpDir);
+        shell.cp('-R', path.join(__dirname, '..', 'spec', 'cordova', 'fixtures', 'basePkgJson13'), tmpDir);
         shell.mv(path.join(tmpDir, 'basePkgJson13'), project);
         process.chdir(project);
         events.on('results', function (res) { results = res; });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 3834ad5..bd611e5 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
     "eslint-plugin-standard": "^3.0.1",
     "istanbul": "^0.4.5",
     "jasmine": "^2.5.2",
-    "rewire": "2.5.2"
+    "rewire": "^2.5.2"
   },
   "scripts": {
     "test": "npm run eslint && npm run unit-tests && npm run e2e-tests",

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js
----------------------------------------------------------------------
diff --git a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js
deleted file mode 100644
index 1875bf7..0000000
--- a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js
+++ /dev/null
@@ -1,733 +0,0 @@
-/**
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-var fs = require('fs');
-var util = require('util');
-var et = require('elementtree');
-var path = require('path');
-var xml= require('cordova-common').xmlHelpers;
-
-var UAP_RESTRICTED_CAPS = ['enterpriseAuthentication', 'sharedUserCertificates',
-                           'documentsLibrary', 'musicLibrary', 'picturesLibrary',
-                           'videosLibrary', 'removableStorage', 'internetClientClientServer',
-                           'privateNetworkClientServer'];
-
-// UAP namespace capabilities come from the XSD type ST_Capability_Uap from AppxManifestTypes.xsd
-var CAPS_NEEDING_UAPNS  = ['documentsLibrary', 'picturesLibrary', 'videosLibrary',
-                           'musicLibrary', 'enterpriseAuthentication', 'sharedUserCertificates',
-                           'removableStorage', 'appointments', 'contacts', 'userAccountInformation',
-                           'phoneCall', 'blockedChatMessages', 'objects3D'];
-
-var KNOWN_ORIENTATIONS = {
-    'default':   ['portrait', 'landscape', 'landscapeFlipped'],
-    'portrait':  ['portrait'],
-    'landscape': ['landscape', 'landscapeFlipped']
-};
-
-/**
- * Store to cache appxmanifest files based on file location
- * @type  {Object}
- */
-var manifestCache = {};
-
-/**
- * @constructor
- * @constructs AppxManifest
- *
- * Wraps an AppxManifest file. Shouldn't be instantiated directly.
- *   AppxManifest.get should be used instead to select proper manifest type
- *   (AppxManifest for Win 8/8.1/Phone 8.1, Win10AppxManifest for Win 10)
- *
- * @param  {string}  path    Path to appxmanifest to wrap
- * @param  {string}  prefix  A namespace prefix used to prepend some elements.
- *   Depends on manifest type.
- */
-function AppxManifest(path, prefix) {
-    this.path = path;
-    // Append ':' to prefix if needed
-    prefix = prefix || '';
-    this.prefix = (prefix.indexOf(':') === prefix.length - 1) ? prefix : prefix + ':';
-    this.doc = xml.parseElementtreeSync(path);
-    if (this.doc.getroot().tag !== 'Package') {
-        // Some basic validation
-        throw new Error(path + ' has incorrect root node name (expected "Package")');
-    }
-
-    // Indicates that this manifest is for phone application (either WinPhone 8.1 or Universal Windows 10)
-    this.hasPhoneIdentity = this.prefix === 'uap:' || this.prefix === 'm3:';
-}
-
-//  Static read-only property to get capabilities which need to be prefixed with uap
-Object.defineProperty(AppxManifest, 'CapsNeedUapPrefix', {
-    writable: false,
-    configurable: false,
-    value: CAPS_NEEDING_UAPNS
-});
-
-/**
- * @static
- * @constructs AppxManifest|Win10AppxManifest
- *
- * Instantiates a new AppxManifest/Win10AppxManifest class. Chooses which
- *   constructor to use based on xmlns attributes of Package node
- *
- * @param   {String}  fileName  File to create manifest for
- * @param   {Boolean} [ignoreCache=false]  Specifies, whether manifest cache will be
- *   used to return resultant object
- *
- * @return  {AppxManifest|Win10AppxManifest}  Manifest instance
- */
-AppxManifest.get = function (fileName, ignoreCache) {
-
-    if (!ignoreCache && manifestCache[fileName]) {
-        return manifestCache[fileName];
-    }
-
-    var root = xml.parseElementtreeSync(fileName).getroot();
-    var prefixes = Object.keys(root.attrib)
-    .reduce(function (result, attrib) {
-        if (attrib.indexOf('xmlns') === 0 && attrib !== 'xmlns:mp') {
-            result.push(attrib.replace('xmlns', '').replace(':', ''));
-        }
-
-        return result;
-    }, []).sort();
-
-    var prefix = prefixes[prefixes.length - 1];
-    var Manifest = prefix === 'uap' ? Win10AppxManifest : AppxManifest;
-    var result = new Manifest(fileName, prefix);
-
-    if (!ignoreCache) {
-        manifestCache[fileName] = result;
-    }
-
-    return result;
-};
-
-/**
- * Removes manifests from cache to prevent using stale entries
- *
- * @param {String|String[]} [cacheKeys] The keys to delete from cache. If not
- *   specified, the whole cache will be purged
- */
-AppxManifest.purgeCache = function (cacheKeys) {
-    if (!cacheKeys) {
-        // if no arguments passed, remove all entries
-        manifestCache = {};
-        return;
-    }
-
-    var keys = Array.isArray(cacheKeys) ? cacheKeys : [cacheKeys];
-    keys.forEach(function (key) {
-        delete manifestCache[key];
-    });
-};
-
-AppxManifest.prototype.getPhoneIdentity = function () {
-    var phoneIdentity = this.doc.getroot().find('./mp:PhoneIdentity');
-    if (!phoneIdentity)
-        throw new Error('Failed to find PhoneIdentity element in appxmanifest at ' + this.path);
-
-    return {
-        getPhoneProductId: function () {
-            return phoneIdentity.attrib.PhoneProductId;
-        },
-        setPhoneProductId: function (id) {
-            if (!id) throw new Error('Argument for "setPhoneProductId" must be defined in appxmanifest at ' + this.path);
-            phoneIdentity.attrib.PhoneProductId = id;
-            return this;
-        }
-    };
-};
-
-AppxManifest.prototype.getIdentity = function () {
-    var identity = this.doc.getroot().find('./Identity');
-    if (!identity)
-        throw new Error('Failed to find "Identity" node. The appxmanifest at ' + this.path + ' is invalid');
-
-    return {
-        getName: function () {
-            return identity.attrib.Name;
-        },
-        setName: function (name) {
-            if (!name) throw new TypeError('Identity.Name attribute must be non-empty in appxmanifest at ' + this.path);
-            identity.attrib.Name = name;
-            return this;
-        },
-        getPublisher: function () {
-            return identity.attrib.Publisher;
-        },
-        setPublisher: function (publisherId) {
-            if (!publisherId) throw new TypeError('Identity.Publisher attribute must be non-empty in appxmanifest at ' + this.path);
-            identity.attrib.Publisher = publisherId;
-            return this;
-        },
-        getVersion: function () {
-            return identity.attrib.Version;
-        },
-        setVersion: function (version) {
-            if (!version) throw new TypeError('Identity.Version attribute must be non-empty in appxmanifest at ' + this.path );
-
-            // Adjust version number as per CB-5337 Windows8 build fails due to invalid app version
-            if(version && version.match(/\.\d/g)) {
-                var numVersionComponents = version.match(/\.\d/g).length + 1;
-                while (numVersionComponents++ < 4) {
-                    version += '.0';
-                }
-            }
-
-            identity.attrib.Version = version;
-            return this;
-        }
-    };
-};
-
-AppxManifest.prototype.getProperties = function () {
-    var properties = this.doc.getroot().find('./Properties');
-
-    if (!properties)
-        throw new Error('Failed to find "Properties" node. The appxmanifest at ' + this.path + ' is invalid');
-
-    return {
-        getDisplayName: function () {
-            var displayName = properties.find('./DisplayName');
-            return displayName && displayName.text;
-        },
-        setDisplayName: function (name) {
-            if (!name) throw new TypeError('Properties.DisplayName elements must be non-empty in appxmanifest at ' + this.path);
-            var displayName = properties.find('./DisplayName');
-
-            if (!displayName) {
-                displayName = new et.Element('DisplayName');
-                properties.append(displayName);
-            }
-
-            displayName.text = name;
-
-            return this;
-        },
-        getPublisherDisplayName: function () {
-            var publisher = properties.find('./PublisherDisplayName');
-            return publisher && publisher.text;
-        },
-        setPublisherDisplayName: function (name) {
-            if (!name) throw new TypeError('Properties.PublisherDisplayName elements must be non-empty in appxmanifest at ' + this.path);
-            var publisher = properties.find('./PublisherDisplayName');
-
-            if (!publisher) {
-                publisher = new et.Element('PublisherDisplayName');
-                properties.append(publisher);
-            }
-
-            publisher.text = name;
-
-            return this;
-        },
-        getDescription: function () {
-            var description = properties.find('./Description');
-            return description && description.text;
-        },
-        setDescription: function (text) {
-
-            var description = properties.find('./Description');
-
-            if (!text || text.length === 0) {
-                if (description) properties.remove(description);
-                return this;
-            }
-
-            if (!description) {
-                description = new et.Element('Description');
-                properties.append(description);
-            }
-
-            description.text = processDescription(text);
-
-            return this;
-        },
-    };
-};
-
-AppxManifest.prototype.getApplication = function () {
-    var application = this.doc.getroot().find('./Applications/Application');
-    if (!application)
-        throw new Error('Failed to find "Application" element. The appxmanifest at ' + this.path + ' is invalid');
-
-    var self = this;
-
-    return {
-        _node: application,
-        getVisualElements: function () {
-            return self.getVisualElements();
-        },
-        getId: function () {
-            return application.attrib.Id;
-        },
-        setId: function (id) {
-            if (!id) throw new TypeError('Application.Id attribute must be defined in appxmanifest at ' + this.path);
-            // 64 symbols restriction goes from manifest schema definition
-            // http://msdn.microsoft.com/en-us/library/windows/apps/br211415.aspx
-            var appId = id.length <= 64 ? id : id.substr(0, 64);
-            application.attrib.Id = appId;
-            return this;
-        },
-        getStartPage: function () {
-            return application.attrib.StartPage;
-        },
-        setStartPage: function (page) {
-            if (!page) page = 'www/index.html'; // Default valur is always index.html
-            application.attrib.StartPage = page;
-            return this;
-        },
-        getAccessRules: function () {
-            return application
-                .findall('./ApplicationContentUriRules/Rule')
-                .map(function (rule) {
-                    return rule.attrib.Match;
-                });
-        },
-        setAccessRules: function (rules) {
-            var appUriRules = application.find('ApplicationContentUriRules');
-            if (appUriRules) {
-                application.remove(appUriRules);
-            }
-
-            // No rules defined
-            if (!rules || rules.length === 0) {
-                return;
-            }
-
-            appUriRules = new et.Element('ApplicationContentUriRules');
-            application.append(appUriRules);
-
-            rules.forEach(function(rule) {
-                appUriRules.append(new et.Element('Rule', {Match: rule, Type: 'include'}));
-            });
-
-            return this;
-        }
-    };
-};
-
-AppxManifest.prototype.getVisualElements = function () {
-    var self = this;
-    var visualElements = this.doc.getroot().find('./Applications/Application/' +
-        this.prefix  + 'VisualElements');
-
-    if (!visualElements)
-        throw new Error('Failed to find "VisualElements" node. The appxmanifest at ' + this.path + ' is invalid');
-
-    return {
-        _node: visualElements,
-        getDisplayName: function () {
-            return visualElements.attrib.DisplayName;
-        },
-        setDisplayName: function (name) {
-            if (!name) throw new TypeError('VisualElements.DisplayName attribute must be defined in appxmanifest at ' + this.path);
-            visualElements.attrib.DisplayName = name;
-            return this;
-        },
-        getOrientation: function () {
-            return visualElements.findall(self.prefix + 'Rotation')
-                .map(function (element) {
-                    return element.attrib.Preference;
-                });
-        },
-        setOrientation: function (orientation) {
-            if (!orientation || orientation === ''){
-                orientation = 'default';
-            }
-
-            var rotationPreferenceRootName = self.prefix + 'InitialRotationPreference';
-            var rotationPreferenceRoot = visualElements.find('./' + rotationPreferenceRootName);
-
-            if (!orientation && rotationPreferenceRoot) {
-                // Remove InitialRotationPreference root element to revert to defaults
-                visualElements.remove(rotationPreferenceRoot);
-                return this;
-            }
-
-            if(!rotationPreferenceRoot) {
-                rotationPreferenceRoot = new et.Element(rotationPreferenceRootName);
-                visualElements.append(rotationPreferenceRoot);
-            }
-
-            rotationPreferenceRoot.clear();
-
-            var orientations = KNOWN_ORIENTATIONS[orientation] || orientation.split(',');
-            orientations.forEach(function(orientation) {
-                var el = new et.Element(self.prefix + 'Rotation', {Preference: orientation} );
-                rotationPreferenceRoot.append(el);
-            });
-
-            return this;
-        },
-        getBackgroundColor: function () {
-            return visualElements.attrib.BackgroundColor;
-        },
-        setBackgroundColor: function (color) {
-            if (!color)
-                throw new TypeError('VisualElements.BackgroundColor attribute must be defined in appxmanifest at ' + this.path);
-
-            visualElements.attrib.BackgroundColor = refineColor(color);
-            return this;
-        },
-        trySetBackgroundColor: function (color) {
-            try {
-                return this.setBackgroundColor(color);
-            } catch (e) { return this; }
-        },
-        getForegroundText: function () {
-            return visualElements.attrib.ForegroundText;
-        },
-        setForegroundText: function (color) {
-            // If color is not set, fall back to 'light' by default
-            visualElements.attrib.ForegroundText = color || 'light';
-
-            return this;
-        },
-        getSplashBackgroundColor: function () {
-            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
-            return splashNode && splashNode.attrib.BackgroundColor;
-        },
-        setSplashBackgroundColor: function (color) {
-            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
-            if (splashNode) {
-                if (color) {
-                    splashNode.attrib.BackgroundColor = refineColor(color);
-                } else {
-                    delete splashNode.attrib.BackgroundColor;
-                }
-            }
-            return this;
-        },
-        getSplashScreenExtension: function (extension) {
-            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
-            return splashNode && splashNode.attrib.Image && path.extname(splashNode.attrib.Image);
-        },
-        setSplashScreenExtension: function (extension) {
-            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
-            if (splashNode) {
-                var oldPath = splashNode.attrib.Image; 
-                splashNode.attrib.Image = path.dirname(oldPath) + '\\' + path.basename(oldPath, path.extname(oldPath)) + extension;
-            }
-            return this;
-        },
-        getToastCapable: function () {
-            return visualElements.attrib.ToastCapable;
-        },
-        setToastCapable: function (isToastCapable) {
-            if (isToastCapable === true || isToastCapable.toString().toLowerCase() === 'true') {
-                visualElements.attrib.ToastCapable = 'true';
-            } else {
-                delete visualElements.attrib.ToastCapable;
-            }
-
-            return this;
-        },
-        getDescription: function () {
-            return visualElements.attrib.Description;
-        },
-        setDescription: function (description) {
-            if (!description || description.length === 0)
-                throw new TypeError('VisualElements.Description attribute must be defined and non-empty in appxmanifest at ' + this.path);
-
-            visualElements.attrib.Description = processDescription(description);
-            return this;
-        },
-    };
-};
-
-AppxManifest.prototype.getCapabilities = function () {
-    var capabilities = this.doc.find('./Capabilities');
-    if (!capabilities) return [];
-
-    return capabilities.getchildren()
-    .map(function (element) {
-        return { type: element.tag, name: element.attrib.Name };
-    });
-};
-
-function isCSSColorName(color) {
-    return color.indexOf('0x') === -1 && color.indexOf('#') === -1;
-}
-
-function refineColor(color) {
-    if (isCSSColorName(color)) {
-        return color;
-    }
-
-    // return three-byte hexadecimal number preceded by "#" (required for Windows)
-    color = color.replace('0x', '').replace('#', '');
-    if (color.length == 3) {
-        color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2];
-    }
-    // alpha is not supported, so we remove it
-    if (color.length == 8) { // AArrggbb
-        color = color.slice(2);
-    }
-    return '#' + color;
-}
-
-function processDescription(text) {
-    var result = text;
-
-    // Description value limitations: https://msdn.microsoft.com/en-us/library/windows/apps/br211429.aspx
-    // value should be no longer than 2048 characters
-    if (text.length > 2048) {
-        result = text.substr(0, 2048);
-    }
-
-    // value should not contain newlines and tabs
-    return result.replace(/(\n|\r)/g, ' ').replace(/\t/g, '    ');
-}
-
-// Shortcut for getIdentity.setName
-AppxManifest.prototype.setPackageName = function (name) {
-    this.getIdentity().setName(name);
-    return this;
-};
-
-// Shortcut for multiple inner methods calls
-AppxManifest.prototype.setAppName = function (name) {
-    this.getProperties().setDisplayName(name);
-    this.getVisualElements().setDisplayName(name);
-
-    return this;
-};
-
-/**
- * Writes manifest to disk syncronously. If filename is specified, then manifest
- *   will be written to that file
- *
- * @param   {String}  [destPath]  File to write manifest to. If omitted,
- *   manifest will be written to file it has been read from.
- */
-AppxManifest.prototype.write = function(destPath) {
-    // sort Capability elements as per CB-5350 Windows8 build fails due to invalid 'Capabilities' definition
-    sortCapabilities(this.doc);
-    fs.writeFileSync(destPath || this.path, this.doc.write({indent: 4}), 'utf-8');
-};
-
-/**
- * Sorts 'capabilities' elements in manifest in ascending order
- * @param   {Elementtree.Document}  manifest  An XML document that represents
- *   appxmanifest
- */
-function sortCapabilities(manifest) {
-
-    // removes namespace prefix (m3:Capability -> Capability)
-    // this is required since elementtree returns qualified name with namespace
-    function extractLocalName(tag) {
-        return tag.split(':').pop(); // takes last part of string after ':'
-    }
-
-    var capabilitiesRoot = manifest.find('.//Capabilities'),
-        capabilities = capabilitiesRoot.getchildren() || [];
-    // to sort elements we remove them and then add again in the appropriate order
-    capabilities.forEach(function(elem) { // no .clear() method
-        capabilitiesRoot.remove(elem);
-        // CB-7601 we need local name w/o namespace prefix to sort capabilities correctly
-        elem.localName = extractLocalName(elem.tag);
-    });
-    capabilities.sort(function(a, b) {
-        return (a.localName > b.localName) ? 1: -1;
-    });
-    capabilities.forEach(function(elem) {
-        capabilitiesRoot.append(elem);
-    });
-}
-
-
-function Win10AppxManifest(path) {
-    AppxManifest.call(this, path, /*prefix=*/'uap');
-}
-
-util.inherits(Win10AppxManifest, AppxManifest);
-
-Win10AppxManifest.prototype.getApplication = function () {
-    // Call overridden method
-    var result = AppxManifest.prototype.getApplication.call(this);
-    var application = result._node;
-
-    result.getAccessRules = function () {
-        return application
-            .findall('./uap:ApplicationContentUriRules/uap:Rule')
-            .map(function (rule) {
-                return rule.attrib.Match;
-            });
-    };
-
-    result.setAccessRules = function (rules) {
-        var appUriRules = application.find('./uap:ApplicationContentUriRules');
-        if (appUriRules) {
-            application.remove(appUriRules);
-        }
-
-        // No rules defined
-        if (!rules || rules.length === 0) {
-            return;
-        }
-
-        appUriRules = new et.Element('uap:ApplicationContentUriRules');
-        application.append(appUriRules);
-
-        rules.forEach(function(rule) {
-            appUriRules.append(new et.Element('uap:Rule', { Match: rule, Type: 'include', WindowsRuntimeAccess: 'all' }));
-        });
-
-        return this;
-    };
-
-    return result;
-};
-
-Win10AppxManifest.prototype.getVisualElements = function () {
-    // Call base method and extend its results
-    var result = AppxManifest.prototype.getVisualElements.call(this);
-    var defaultTitle = result._node.find('./uap:DefaultTile');
-
-    result.getDefaultTitle = function () {
-        return {
-            getShortName: function () {
-                return defaultTitle.attrib.ShortName;
-            },
-            setShortName: function (name) {
-                if (!name) throw new TypeError('Argument for "setDisplayName" must be defined in appxmanifest at ' + this.path);
-                defaultTitle.attrib.ShortName = name;
-                return this;
-            }
-        };
-    };
-
-    // ToastCapable attribute was removed in Windows 10.
-    // See https://msdn.microsoft.com/ru-ru/library/windows/apps/dn423310.aspx
-    result.getToastCapable = function () {};
-    result.setToastCapable = function () { return this; };
-
-    // ForegroundText was removed in Windows 10 as well
-    result.getForegroundText = function () {};
-    result.setForegroundText = function () { return this; };
-
-    return result;
-};
-
-// Shortcut for multiple inner methods calls
-Win10AppxManifest.prototype.setAppName = function (name) {
-    // Call base method
-    AppxManifest.prototype.setAppName.call(this, name);
-    this.getVisualElements().getDefaultTitle().setShortName(name);
-
-    return this;
-};
-
-/**
- * Checks for capabilities which are Restricted in Windows 10 UAP.
- * @return {string[]|false} An array of restricted capability names, or false.
- */
-Win10AppxManifest.prototype.getRestrictedCapabilities = function () {
-    var restrictedCapabilities = this.getCapabilities()
-    .filter(function (capability) {
-        return UAP_RESTRICTED_CAPS.indexOf(capability.name) >= 0;
-    });
-
-    return restrictedCapabilities.length === 0 ? false : restrictedCapabilities;
-};
-
-/**
- * Sets up a Dependencies section for appxmanifest. If no arguments provided,
- *   deletes Dependencies section.
- *
- * @param  {Object[]}  dependencies  Array of arbitrary object, which fields
- *   will be used to set each dependency attributes.
- *
- * @returns {Win10AppxManifest}  self instance
- */
-Win10AppxManifest.prototype.setDependencies = function (dependencies) {
-    var dependenciesElement = this.doc.find('./Dependencies');
-
-    if ((!dependencies || dependencies.length === 0) && dependenciesElement) {
-        this.doc.remove(dependenciesElement);
-        return this;
-    }
-
-    if (!dependenciesElement) {
-        dependenciesElement = new et.Element('Dependencies');
-        this.doc.append(dependenciesElement);
-    }
-
-    if (dependenciesElement.len() > 0) {
-        dependenciesElement.clear();
-    }
-
-    dependencies.forEach(function (uapVersionInfo) {
-        dependenciesElement.append(new et.Element('TargetDeviceFamily', uapVersionInfo));
-    });
-};
-
-/**
- * Writes manifest to disk syncronously. If filename is specified, then manifest
- *   will be written to that file
- *
- * @param   {String}  [destPath]  File to write manifest to. If omitted,
- *   manifest will be written to file it has been read from.
- */
-Win10AppxManifest.prototype.write = function(destPath) {
-    fs.writeFileSync(destPath || this.path, this.writeToString(), 'utf-8');
-};
-
-Win10AppxManifest.prototype.writeToString = function() {
-    ensureUapPrefixedCapabilities(this.doc.find('.//Capabilities'));
-    ensureUniqueCapabilities(this.doc.find('.//Capabilities'));
-    // sort Capability elements as per CB-5350 Windows8 build fails due to invalid 'Capabilities' definition
-    sortCapabilities(this.doc);
-    return this.doc.write({indent: 4});
-};
-
-/**
- * Checks for capabilities which require the uap: prefix in Windows 10.
- * @param capabilities {ElementTree.Element} The appx manifest element for <capabilities>
- */
-function ensureUapPrefixedCapabilities(capabilities) {
-    capabilities.getchildren()
-    .forEach(function(el) {
-        if (CAPS_NEEDING_UAPNS.indexOf(el.attrib.Name) > -1 && el.tag.indexOf('uap:') !== 0) {
-            el.tag = 'uap:' + el.tag;
-        }
-    });
-}
-
-/**
- * Cleans up duplicate capability declarations that were generated during the prepare process
- * @param capabilities {ElementTree.Element} The appx manifest element for <capabilities>
- */
-function ensureUniqueCapabilities(capabilities) {
-    var uniqueCapabilities = [];
-    capabilities.getchildren()
-    .forEach(function(el) {
-        var name = el.attrib.Name;
-        if (uniqueCapabilities.indexOf(name) !== -1) {
-            capabilities.remove(el);
-        } else {
-            uniqueCapabilities.push(name);
-        }
-    });
-}
-
-module.exports = AppxManifest;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js
----------------------------------------------------------------------
diff --git a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js
deleted file mode 100644
index c07a77a..0000000
--- a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * 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 util = require('util');
-var path = require('path');
-var CommonMunger = require('cordova-common').ConfigChanges.PlatformMunger;
-var CapsNeedUapPrefix = require(path.join(__dirname, 'AppxManifest')).CapsNeedUapPrefix;
-
-var CAPS_SELECTOR = '/Package/Capabilities';
-var WINDOWS10_MANIFEST = 'package.windows10.appxmanifest';
-
-function PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider) {
-    CommonMunger.apply(this, arguments);
-}
-
-util.inherits(PlatformMunger, CommonMunger);
-
-/**
- * This is an override of apply_file_munge method from cordova-common's PlatformMunger class.
- * In addition to parent's method logic also removes capabilities with 'uap:' prefix that were
- * added by AppxManifest class
- *
- * @param {String}  file   A file name to apply munge to
- * @param {Object}  munge  Serialized changes that need to be applied to the file
- * @param {Boolean} [remove=false] Flag that specifies whether the changes
- *   need to be removed or added to the file
- */
-PlatformMunger.prototype.apply_file_munge = function (file, munge, remove) {
-
-    // Create a copy to avoid modification of original munge
-    var mungeCopy = cloneObject(munge);
-    var capabilities = mungeCopy.parents[CAPS_SELECTOR];
-
-    if (capabilities) {
-        // Add 'uap' prefixes for windows 10 manifest
-        if (file === WINDOWS10_MANIFEST) {
-            capabilities = generateUapCapabilities(capabilities);
-        }
-
-        // Remove duplicates and sort capabilities when installing plugin
-        if (!remove) {
-            capabilities = getUniqueCapabilities(capabilities).sort(compareCapabilities);
-        }
-
-        // Put back capabilities into munge's copy
-        mungeCopy.parents[CAPS_SELECTOR] = capabilities;
-    }
-
-    PlatformMunger.super_.prototype.apply_file_munge.call(this, file, mungeCopy, remove);
-};
-
-// Recursive function to clone an object
-function cloneObject(obj) {
-    if (obj === null || typeof obj !== 'object') {
-        return obj;
-    }
-
-    var copy = obj.constructor();
-    Object.keys(obj).forEach(function(key) {
-        copy[key] = cloneObject(obj[key]);
-    });
-
-    return copy;
-}
-
-/**
- * Retrieve capabality name from xml field
- * @param {Object} capability with xml field like <Capability Name="CapabilityName">
- * @return {String} name of capability
- */
-function getCapabilityName(capability) {
-    var reg = /Name\s*=\s*"(.*?)"/;
-    return capability.xml.match(reg)[1];
-}
-
-/**
- * Remove capabilities with same names
- * @param {Object} an array of capabilities
- * @return {Object} an unique array of capabilities
- */
-function getUniqueCapabilities(capabilities) {
-    return capabilities.reduce(function(uniqueCaps, currCap) {
-
-        var isRepeated = uniqueCaps.some(function(cap) {
-            return getCapabilityName(cap) === getCapabilityName(currCap);
-        });
-
-        return isRepeated ? uniqueCaps : uniqueCaps.concat([currCap]);
-    }, []);
-}
-
-/**
- * Comparator function to pass to Array.sort
- * @param {Object} firstCap first capability
- * @param {Object} secondCap second capability
- * @return {Number} either -1, 0 or 1
- */
-function compareCapabilities(firstCap, secondCap) {
-    var firstCapName = getCapabilityName(firstCap);
-    var secondCapName = getCapabilityName(secondCap);
-
-    if (firstCapName < secondCapName) {
-        return -1;
-    }
-
-    if (firstCapName > secondCapName) {
-        return 1;
-    }
-
-    return 0;
-}
-
-
-/**
- * Generates a new munge that contains <uap:Capability> elements created based on
- * corresponding <Capability> elements from base munge. If there are no such elements
- * found in base munge, the empty munge is returned (selectors might be present under
- * the 'parents' key, but they will contain no changes).
- *
- * @param {Object} capabilities A list of capabilities
- * @return {Object} A list with 'uap'-prefixed capabilities
- */
-function generateUapCapabilities(capabilities) {
-
-    function hasCapabilityChange(change) {
-        return /^\s*<(\w+:)?(Device)?Capability\s/.test(change.xml);
-    }
-
-    function createPrefixedCapabilityChange(change) {
-        if (CapsNeedUapPrefix.indexOf(getCapabilityName(change)) < 0) {
-            return change;
-        }
-
-        //  If capability is already prefixed, avoid adding another prefix
-        var replaceXML = change.xml.indexOf('uap:') > 0 ? change.xml : change.xml.replace(/Capability/, 'uap:Capability');
-        return {
-            xml: replaceXML,
-            count: change.count,
-            before: change.before
-        };
-    }
-
-    return capabilities
-     // For every xml change check if it adds a <Capability> element ...
-    .filter(hasCapabilityChange)
-    // ... and create a duplicate with 'uap:' prefix
-    .map(createPrefixedCapabilityChange);
-
-}
-
-exports.PlatformMunger = PlatformMunger;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js
----------------------------------------------------------------------
diff --git a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js
deleted file mode 100644
index 28b2fa3..0000000
--- a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js
+++ /dev/null
@@ -1,626 +0,0 @@
-/**
- 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 */
-
-/*
- Helper for dealing with Windows Store JS app .jsproj files
- */
-
-var fs = require('fs');
-var et = require('elementtree');
-var path = require('path');
-var util = require('util');
-var semver = require('semver');
-var shell = require('shelljs');
-var AppxManifest = require('./AppxManifest');
-var PluginHandler = require('./PluginHandler');
-var events = require('cordova-common').events;
-var CordovaError = require('cordova-common').CordovaError;
-var xml_helpers = require('cordova-common').xmlHelpers;
-var AppxManifest = require('./AppxManifest');
-
-var WinCSharpProjectTypeGUID = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";  // .csproj
-var WinCplusplusProjectTypeGUID = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";  // .vcxproj
-
-// Match a JavaScript Project
-var JSPROJ_REGEXP = /(Project\("\{262852C6-CD72-467D-83FE-5EEB1973A190}"\)\s*=\s*"[^"]+",\s*"[^"]+",\s*"\{[0-9a-f\-]+}"[^\r\n]*[\r\n]*)/gi;
-
-// Chars in a string that need to be escaped when used in a RegExp
-var ESCAPE_REGEXP = /([.?*+\^$\[\]\\(){}|\-])/g;
-
-function jsprojManager(location) {
-    this.root = path.dirname(location);
-    this.isUniversalWindowsApp = path.extname(location).toLowerCase() === ".projitems";
-    this.projects = [];
-    this.master = this.isUniversalWindowsApp ? new proj(location) : new jsproj(location);
-    this.projectFolder = path.dirname(location);
-    this.www = path.join(this.root, 'www');
-    this.platformWww = path.join(this.root, 'platform_www');
-}
-
-function getProjectName(pluginProjectXML, relative_path) {
-    var projNameElt = pluginProjectXML.find("PropertyGroup/ProjectName");
-    // Falling back on project file name in case ProjectName is missing
-    return !!projNameElt ? projNameElt.text : path.basename(relative_path, path.extname(relative_path));
-}
-
-jsprojManager.getProject = function (directory) {
-    var projectFiles = shell.ls(path.join(directory, '*.projitems'));
-    if (projectFiles.length === 0) {
-        throw (new CordovaError('The directory ' + directory +
-            ' does not appear to be a Unified Windows Store project (no .projitems file found)'));
-    }
-    return new jsprojManager(path.normalize(projectFiles[0]));
-};
-
-jsprojManager.prototype = {
-    _projects: null,
-
-    getPackageName: function() {
-        // CB-10394 Do not cache manifest file while getting package name to avoid problems
-        // with windows.appxmanifest cached twice (here and in ConfigFile module)
-        return AppxManifest.get(path.join(this.root, 'package.windows.appxmanifest'), /*ignoreCache=*/true)
-            .getProperties().getDisplayName();
-    },
-
-    write: function () {
-        this.master.write();
-        if (this._projects) {
-            var that = this;
-            this._projects.forEach(function (project) {
-                if (project !== that.master && project.touched) {
-                    project.write();
-                }
-            });
-        }
-    },
-
-    addSDKRef: function (incText, targetConditions) {
-        events.emit('verbose', 'jsprojManager.addSDKRef(incText: ' + incText + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
-
-        var item = createItemGroupElement('ItemGroup/SDKReference', incText, targetConditions);
-        this._getMatchingProjects(targetConditions).forEach(function (project) {
-            project.appendToRoot(item);
-        });
-    },
-
-    removeSDKRef: function (incText, targetConditions) {
-        events.emit('verbose', 'jsprojManager.removeSDKRef(incText: ' + incText + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
-
-        this._getMatchingProjects(targetConditions).forEach(function (project) {
-            project.removeItemGroupElement('ItemGroup/SDKReference', incText, targetConditions);
-        });
-    },
-
-    addResourceFileToProject: function (sourcePath, destPath, targetConditions) {
-        events.emit('verbose', 'jsprojManager.addResourceFile(sourcePath: ' + sourcePath + ', destPath: ' + destPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
-
-        // add hint path with full path
-        var link = new et.Element('Link');
-        link.text = destPath;
-        var children = [link];
-
-        var copyToOutputDirectory = new et.Element('CopyToOutputDirectory');
-        copyToOutputDirectory.text = 'Always';
-        children.push(copyToOutputDirectory);
-
-        var item = createItemGroupElement('ItemGroup/Content', sourcePath, targetConditions, children);
-
-        this._getMatchingProjects(targetConditions).forEach(function (project) {
-            project.appendToRoot(item);
-        });
-    },
-
-    removeResourceFileFromProject: function (relPath, targetConditions) {
-        events.emit('verbose', 'jsprojManager.removeResourceFile(relPath: ' + relPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
-        this._getMatchingProjects(targetConditions).forEach(function (project) {
-            project.removeItemGroupElement('ItemGroup/Content', relPath, targetConditions);
-        });
-    },
-
-    addReference: function (relPath, targetConditions, implPath) {
-        events.emit('verbose', 'jsprojManager.addReference(incText: ' + relPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
-
-        // add hint path with full path
-        var hint_path = new et.Element('HintPath');
-        hint_path.text = relPath;
-        var children = [hint_path];
-
-        var extName = path.extname(relPath);
-        if (extName === ".winmd") {
-            var mdFileTag = new et.Element("IsWinMDFile");
-            mdFileTag.text = "true";
-            children.push(mdFileTag);
-        }
-
-        // We only need to add <Implementation> tag when dll base name differs from winmd name
-        if (implPath && path.basename(relPath, '.winmd') !== path.basename(implPath, '.dll')) {
-            var implementTag = new et.Element('Implementation');
-            implementTag.text = path.basename(implPath);
-            children.push(implementTag);
-        }
-
-        var item = createItemGroupElement('ItemGroup/Reference', path.basename(relPath, extName), targetConditions, children);
-        this._getMatchingProjects(targetConditions).forEach(function (project) {
-            project.appendToRoot(item);
-        });
-    },
-
-    removeReference: function (relPath, targetConditions) {
-        events.emit('verbose', 'jsprojManager.removeReference(incText: ' + relPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
-
-        var extName = path.extname(relPath);
-        var includeText = path.basename(relPath, extName);
-
-        this._getMatchingProjects(targetConditions).forEach(function (project) {
-            project.removeItemGroupElement('ItemGroup/Reference', includeText, targetConditions);
-        });
-    },
-
-    addSourceFile: function (relative_path) {
-        events.emit('verbose', 'jsprojManager.addSourceFile(relative_path: ' + relative_path + ')');
-        this.master.addSourceFile(relative_path);
-    },
-
-    removeSourceFile: function (relative_path) {
-        events.emit('verbose', 'jsprojManager.removeSourceFile(incText: ' + relative_path + ')');
-        this.master.removeSourceFile(relative_path);
-    },
-
-    addProjectReference: function (relative_path, targetConditions) {
-        events.emit('verbose', 'jsprojManager.addProjectReference(incText: ' + relative_path + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
-
-        // relative_path is the actual path to the file in the current OS, where-as inserted_path is what we write in
-        // the project file, and is always in Windows format.
-        relative_path = path.normalize(relative_path);
-        var inserted_path = relative_path.split('/').join('\\');
-
-        var pluginProjectXML = xml_helpers.parseElementtreeSync(path.resolve(this.projectFolder, relative_path));
-
-        // find the guid + name of the referenced project
-        var projectGuid = pluginProjectXML.find("PropertyGroup/ProjectGuid").text;
-        var projName = getProjectName(pluginProjectXML, relative_path);
-
-        // get the project type
-        var projectTypeGuid = getProjectTypeGuid(relative_path);
-        if (!projectTypeGuid) {
-            throw new CordovaError("Unrecognized project type at " + relative_path + " (not .csproj or .vcxproj)");
-        }
-
-        var preInsertText = "\tProjectSection(ProjectDependencies) = postProject\r\n" +
-            "\t\t" + projectGuid + "=" + projectGuid + "\r\n" +
-            "\tEndProjectSection\r\n";
-        var postInsertText = '\r\nProject("' + projectTypeGuid + '") = "' +
-            projName + '", "' + inserted_path + '", ' +
-            '"' + projectGuid + '"\r\nEndProject';
-
-        var matchingProjects = this._getMatchingProjects(targetConditions);
-        if (matchingProjects.length === 0) {
-            // No projects meet the specified target and version criteria, so nothing to do.
-            return;
-        }
-
-        // Will we be writing into the .projitems file rather than individual .jsproj files?
-        var useProjItems = this.isUniversalWindowsApp && matchingProjects.length === 1 && matchingProjects[0] === this.master;
-
-        // There may be multiple solution files (for different VS versions) - process them all
-        getSolutionPaths(this.projectFolder).forEach(function (solutionPath) {
-            var solText = fs.readFileSync(solutionPath, {encoding: "utf8"});
-
-            if (useProjItems) {
-                // Insert a project dependency into every jsproj in the solution.
-                var jsProjectFound = false;
-                solText = solText.replace(JSPROJ_REGEXP, function (match) {
-                    jsProjectFound = true;
-                    return match + preInsertText;
-                });
-
-                if (!jsProjectFound) {
-                    throw new CordovaError("No jsproj found in solution");
-                }
-            } else {
-                // Insert a project dependency only for projects that match specified target and version
-                matchingProjects.forEach(function (project) {
-                    solText = solText.replace(getJsProjRegExForProject(path.basename(project.location)), function (match) {
-                        return match + preInsertText;
-                    });
-                });
-            }
-
-            // Add the project after existing projects. Note that this fairly simplistic check should be fine, since the last
-            // EndProject in the file should actually be an EndProject (and not an EndProjectSection, for example).
-            var pos = solText.lastIndexOf("EndProject");
-            if (pos === -1) {
-                throw new Error("No EndProject found in solution");
-            }
-            pos += 10; // Move pos to the end of EndProject text
-            solText = solText.slice(0, pos) + postInsertText + solText.slice(pos);
-
-            fs.writeFileSync(solutionPath, solText, {encoding: "utf8"});
-        });
-
-        // Add the ItemGroup/ProjectReference to each matching cordova project :
-        // <ItemGroup><ProjectReference Include="blahblah.csproj"/></ItemGroup>
-        var item = createItemGroupElement('ItemGroup/ProjectReference', inserted_path, targetConditions);
-        matchingProjects.forEach(function (project) {
-            project.appendToRoot(item);
-        });
-    },
-
-    removeProjectReference: function (relative_path, targetConditions) {
-        events.emit('verbose', 'jsprojManager.removeProjectReference(incText: ' + relative_path + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
-
-        // relative_path is the actual path to the file in the current OS, where-as inserted_path is what we write in
-        // the project file, and is always in Windows format.
-        relative_path = path.normalize(relative_path);
-        var inserted_path = relative_path.split('/').join('\\');
-
-        // find the guid + name of the referenced project
-        var pluginProjectXML = xml_helpers.parseElementtreeSync(path.resolve(this.projectFolder, relative_path));
-        var projectGuid = pluginProjectXML.find("PropertyGroup/ProjectGuid").text;
-        var projName = getProjectName(pluginProjectXML, relative_path);
-
-        // get the project type
-        var projectTypeGuid = getProjectTypeGuid(relative_path);
-        if (!projectTypeGuid) {
-            throw new Error("Unrecognized project type at " + relative_path + " (not .csproj or .vcxproj)");
-        }
-
-        var preInsertTextRegExp = getProjectReferencePreInsertRegExp(projectGuid);
-        var postInsertTextRegExp = getProjectReferencePostInsertRegExp(projName, projectGuid, inserted_path, projectTypeGuid);
-
-        // There may be multiple solutions (for different VS versions) - process them all
-        getSolutionPaths(this.projectFolder).forEach(function (solutionPath) {
-            var solText = fs.readFileSync(solutionPath, {encoding: "utf8"});
-
-            // To be safe (to handle subtle changes in formatting, for example), use a RegExp to find and remove
-            // preInsertText and postInsertText
-
-            solText = solText.replace(preInsertTextRegExp, function () {
-                return "";
-            });
-
-            solText = solText.replace(postInsertTextRegExp, function () {
-                return "";
-            });
-
-            fs.writeFileSync(solutionPath, solText, {encoding: "utf8"});
-        });
-
-        this._getMatchingProjects(targetConditions).forEach(function (project) {
-            project.removeItemGroupElement('ItemGroup/ProjectReference', inserted_path, targetConditions);
-        });
-    },
-
-    _getMatchingProjects: function (targetConditions) {
-        // If specified, target can be 'all' (default), 'phone' or 'windows'. Ultimately should probably allow a comma
-        // separated list, but not needed now.
-        var target = getDeviceTarget(targetConditions);
-        var versions = getVersions(targetConditions);
-
-        if (target || versions) {
-            var matchingProjects = this.projects.filter(function (project) {
-                return (!target || target === project.target) &&
-                    (!versions || semver.satisfies(project.getSemVersion(), versions, /* loose */ true));
-            });
-
-            if (matchingProjects.length < this.projects.length) {
-                return matchingProjects;
-            }
-        }
-
-        // All projects match. If this is a universal project, return the projitems file. Otherwise return our single
-        // project.
-        return [this.master];
-    },
-
-    get projects() {
-        var projects = this._projects;
-        if (!projects) {
-            projects = [];
-            this._projects = projects;
-
-            if (this.isUniversalWindowsApp) {
-                var projectPath = this.projectFolder;
-                var projectFiles = shell.ls(path.join(projectPath, '*.jsproj'));
-                projectFiles.forEach(function (projectFile) {
-                    projects.push(new jsproj(projectFile));
-                });
-            } else {
-                this.projects.push(this.master);
-            }
-        }
-
-        return projects;
-    }
-};
-
-jsprojManager.prototype.getInstaller = function (type) {
-    return PluginHandler.getInstaller(type);
-};
-
-jsprojManager.prototype.getUninstaller = function (type) {
-    return PluginHandler.getUninstaller(type);
-};
-
-function getProjectReferencePreInsertRegExp(projectGuid) {
-    projectGuid = escapeRegExpString(projectGuid);
-    return new RegExp("\\s*ProjectSection\\(ProjectDependencies\\)\\s*=\\s*postProject\\s*" + projectGuid + "\\s*=\\s*" + projectGuid + "\\s*EndProjectSection", "gi");
-}
-
-function getProjectReferencePostInsertRegExp(projName, projectGuid, relative_path, projectTypeGuid) {
-    projName = escapeRegExpString(projName);
-    projectGuid = escapeRegExpString(projectGuid);
-    relative_path = escapeRegExpString(relative_path);
-    projectTypeGuid = escapeRegExpString(projectTypeGuid);
-    return new RegExp('\\s*Project\\("' + projectTypeGuid + '"\\)\\s*=\\s*"' + projName + '"\\s*,\\s*"' + relative_path + '"\\s*,\\s*"' + projectGuid + '"\\s*EndProject', 'gi');
-}
-
-function getSolutionPaths(projectFolder) {
-    return shell.ls(path.join(projectFolder, "*.sln"));
-}
-
-function escapeRegExpString(regExpString) {
-    return regExpString.replace(ESCAPE_REGEXP, "\\$1");
-}
-
-function getJsProjRegExForProject(projectFile) {
-    projectFile = escapeRegExpString(projectFile);
-    return new RegExp('(Project\\("\\{262852C6-CD72-467D-83FE-5EEB1973A190}"\\)\\s*=\\s*"[^"]+",\\s*"' + projectFile + '",\\s*"\\{[0-9a-f\\-]+}"[^\\r\\n]*[\\r\\n]*)', 'gi');
-}
-
-function getProjectTypeGuid(projectPath) {
-    switch (path.extname(projectPath)) {
-        case ".vcxproj":
-            return WinCplusplusProjectTypeGUID;
-
-        case ".csproj":
-            return WinCSharpProjectTypeGUID;
-    }
-    return null;
-}
-
-function createItemGroupElement(path, incText, targetConditions, children) {
-    path = path.split('/');
-    path.reverse();
-
-    var lastElement = null;
-    path.forEach(function (elementName) {
-        var element = new et.Element(elementName);
-        if (lastElement) {
-            element.append(lastElement);
-        } else {
-            element.attrib.Include = incText;
-
-            var condition = createConditionAttrib(targetConditions);
-            if (condition) {
-                element.attrib.Condition = condition;
-            }
-
-            if (children) {
-                children.forEach(function (child) {
-                    element.append(child);
-                });
-            }
-        }
-        lastElement = element;
-    });
-
-    return lastElement;
-}
-
-function getDeviceTarget(targetConditions) {
-    var target = targetConditions.deviceTarget;
-    if (target) {
-        target = target.toLowerCase().trim();
-        if (target === "all") {
-            target = null;
-        } else if (target === "win") {
-            // Allow "win" as alternative to "windows"
-            target = "windows";
-        } else if (target !== 'phone' && target !== 'windows') {
-            throw new Error('Invalid device-target attribute (must be "all", "phone", "windows" or "win"): ' + target);
-        }
-    }
-    return target;
-}
-
-function getVersions(targetConditions) {
-    var versions = targetConditions.versions;
-    if (versions && !semver.validRange(versions, /* loose */ true)) {
-        throw new Error('Invalid versions attribute (must be a valid semantic version range): ' + versions);
-    }
-    return versions;
-}
-
-
-/* proj */
-
-function proj(location) {
-    // Class to handle simple project xml operations
-    if (!location) {
-        throw new Error('Project file location can\'t be null or empty');
-    }
-    this.location = location;
-    this.xml = xml_helpers.parseElementtreeSync(location);
-}
-
-proj.prototype = {
-    write: function () {
-        fs.writeFileSync(this.location, this.xml.write({indent: 4}), 'utf-8');
-    },
-
-    appendToRoot: function (element) {
-        this.touched = true;
-        this.xml.getroot().append(element);
-    },
-
-    removeItemGroupElement: function (path, incText, targetConditions) {
-        var xpath = path + '[@Include="' + incText + '"]';
-        var condition = createConditionAttrib(targetConditions);
-        if (condition) {
-            xpath += '[@Condition="' + condition + '"]';
-        }
-        xpath += '/..';
-
-        var itemGroup = this.xml.find(xpath);
-        if (itemGroup) {
-            this.touched = true;
-            this.xml.getroot().remove(itemGroup);
-        }
-    },
-
-    addSourceFile: function (relative_path) {
-        // we allow multiple paths to be passed at once as array so that
-        // we don't create separate ItemGroup for each source file, CB-6874
-        if (!(relative_path instanceof Array)) {
-            relative_path = [relative_path];
-        }
-
-        // make ItemGroup to hold file.
-        var item = new et.Element('ItemGroup');
-
-        relative_path.forEach(function (filePath) {
-            // filePath is never used to find the actual file - it determines what we write to the project file, and so
-            // should always be in Windows format.
-            filePath = filePath.split('/').join('\\');
-
-            var content = new et.Element('Content');
-            content.attrib.Include = filePath;
-            item.append(content);
-        });
-
-        this.appendToRoot(item);
-    },
-
-    removeSourceFile: function (relative_path) {
-        var isRegexp = relative_path instanceof RegExp;
-        if (!isRegexp) {
-            // relative_path is never used to find the actual file - it determines what we write to the project file,
-            // and so should always be in Windows format.
-            relative_path = relative_path.split('/').join('\\');
-        }
-
-        var root = this.xml.getroot();
-        var that = this;
-        // iterate through all ItemGroup/Content elements and remove all items matched
-        this.xml.findall('ItemGroup').forEach(function (group) {
-            // matched files in current ItemGroup
-            var filesToRemove = group.findall('Content').filter(function (item) {
-                if (!item.attrib.Include) {
-                    return false;
-                }
-                return isRegexp ? item.attrib.Include.match(relative_path) : item.attrib.Include === relative_path;
-            });
-
-            // nothing to remove, skip..
-            if (filesToRemove.length < 1) {
-                return;
-            }
-
-            filesToRemove.forEach(function (file) {
-                // remove file reference
-                group.remove(file);
-            });
-            // remove ItemGroup if empty
-            if (group.findall('*').length < 1) {
-                that.touched = true;
-                root.remove(group);
-            }
-        });
-    }
-};
-
-
-/* jsproj */
-
-function jsproj(location) {
-    function targetPlatformIdentifierToDevice(jsprojPlatform) {
-        var index = ["Windows", "WindowsPhoneApp", "UAP"].indexOf(jsprojPlatform);
-        if (index < 0) {
-            throw new Error("Unknown TargetPlatformIdentifier '" + jsprojPlatform + "' in project file '" + location + "'");
-        }
-        return ["windows", "phone", "windows"][index];
-    }
-
-    function validateVersion(version) {
-        version = version.split('.');
-        while (version.length < 3) {
-            version.push("0");
-        }
-        return version.join(".");
-    }
-
-    // Class to handle a jsproj file
-    proj.call(this, location);
-
-    var propertyGroup = this.xml.find('PropertyGroup[TargetPlatformIdentifier]');
-    if (!propertyGroup) {
-        throw new Error("Unable to find PropertyGroup/TargetPlatformIdentifier in project file '" + this.location + "'");
-    }
-
-    var jsprojPlatform = propertyGroup.find('TargetPlatformIdentifier').text;
-    this.target = targetPlatformIdentifierToDevice(jsprojPlatform);
-
-    var version = propertyGroup.find('TargetPlatformVersion');
-    if (!version) {
-        throw new Error("Unable to find PropertyGroup/TargetPlatformVersion in project file '" + this.location + "'");
-    }
-    this.version = validateVersion(version.text);
-}
-
-util.inherits(jsproj, proj);
-
-jsproj.prototype.target = null;
-jsproj.prototype.version = null;
-
-// Returns valid semantic version (http://semver.org/).
-jsproj.prototype.getSemVersion = function () {
-    // For example, for version 10.0.10240.0 we will return 10.0.10240 (first three components)
-    var semVersion = this.version;
-    var splittedVersion = semVersion.split('.');
-    if (splittedVersion.length > 3) {
-        semVersion = splittedVersion.splice(0, 3).join('.');
-    }
-
-    return semVersion;
-    // Alternative approach could be replacing last dot with plus sign to
-    // be complaint w/ semver specification, for example
-    // 10.0.10240.0 -> 10.0.10240+0
-};
-
-/* Common support functions */
-
-function createConditionAttrib(targetConditions) {
-    var arch = targetConditions.arch;
-    if (arch) {
-        if (arch === "arm") {
-            // Specifcally allow "arm" as alternative to "ARM"
-            arch = "ARM";
-        } else if (arch !== "x86" && arch !== "x64" && arch !== "ARM") {
-            throw new Error('Invalid arch attribute (must be "x86", "x64" or "ARM"): ' + arch);
-        }
-        return "'$(Platform)'=='" + arch + "'";
-    }
-    return null;
-}
-
-
-module.exports = jsprojManager;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js
----------------------------------------------------------------------
diff --git a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js
deleted file mode 100644
index c264f20..0000000
--- a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- *
- * Copyright 2013 Jesse MacFadyen
- *
- * 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 shell = require('shelljs');
-var events = require('cordova-common').events;
-var CordovaError = require('cordova-common').CordovaError;
-
-// returns relative file path for a file in the plugin's folder that can be referenced
-// from a project file.
-function getPluginFilePath(plugin, pluginFile, targetDir) {
-    var src = path.resolve(plugin.dir, pluginFile);
-    return '$(ProjectDir)' + path.relative(targetDir, src);
-}
-
-var handlers = {
-    'source-file': {
-        install:function(obj, plugin, project, options) {
-            var dest = path.join('plugins', plugin.id, obj.targetDir || '', path.basename(obj.src));
-            if (options && options.force) {
-                copyFile(plugin.dir, obj.src, project.root, dest);
-            } else {
-                copyNewFile(plugin.dir, obj.src, project.root, dest);
-            }
-            // add reference to this file to jsproj.
-            project.addSourceFile(dest);
-        },
-        uninstall:function(obj, plugin, project, options) {
-            var dest = path.join('plugins', plugin.id, obj.targetDir || '', path.basename(obj.src));
-            removeFile(project.root, dest);
-            // remove reference to this file from csproj.
-            project.removeSourceFile(dest);
-        }
-    },
-    'resource-file':{
-        install:function(obj, plugin, project, options) {
-            if (obj.reference) {
-                // do not copy, but reference the file in the plugin folder. This allows to
-                // have multiple source files map to the same target and select the appropriate
-                // one based on the current build settings, e.g. architecture.
-                // also, we don't check for existence. This allows to insert build variables
-                // into the source file name, e.g.
-                // <resource-file src="$(Platform)/My.dll" target="My.dll" />
-                var relativeSrcPath = getPluginFilePath(plugin, obj.src, project.projectFolder);
-                project.addResourceFileToProject(relativeSrcPath, obj.target, getTargetConditions(obj));
-            } else {
-                // if target already exists, emit warning to consider using a reference instead of copying
-                if (fs.existsSync(path.resolve(project.root, obj.target))) {
-                    events.emit('warn', '<resource-file> with target ' + obj.target + ' already exists and will be overwritten ' +
-                    'by a <resource-file> with the same target. Consider using the attribute reference="true" in the ' +
-                    '<resource-file> tag to avoid overwriting files with the same target. Using reference will not copy files ' +
-                    'to the destination, instead will create a reference to the source path.');
-                }
-                // as per specification resource-file target is specified relative to platform root
-                copyFile(plugin.dir, obj.src, project.root, obj.target);
-                project.addResourceFileToProject(obj.target, obj.target, getTargetConditions(obj));
-            }
-        },
-        uninstall:function(obj, plugin, project, options) {
-            if (obj.reference) {
-                var relativeSrcPath = getPluginFilePath(plugin, obj.src, project.projectFolder);
-                project.removeResourceFileFromProject(relativeSrcPath, getTargetConditions(obj));
-            } else {
-                removeFile(project.root, obj.target);
-                project.removeResourceFileFromProject(obj.target, getTargetConditions(obj));
-            }
-        }
-    },
-    'lib-file': {
-        install:function(obj, plugin, project, options) {
-            var inc  = obj.Include || obj.src;
-            project.addSDKRef(inc, getTargetConditions(obj));
-        },
-        uninstall:function(obj, plugin, project, options) {
-            events.emit('verbose', 'windows lib-file uninstall :: ' + plugin.id);
-            var inc = obj.Include || obj.src;
-            project.removeSDKRef(inc, getTargetConditions(obj));
-        }
-    },
-    'framework': {
-        install:function(obj, plugin, project, options) {
-            events.emit('verbose', 'windows framework install :: ' + plugin.id);
-
-            var src = obj.src;
-            var dest = src;
-            var type = obj.type;
-            var targetDir = obj.targetDir || '';
-            var implementPath = obj.implementation;
-
-            if(type === 'projectReference') {
-                dest = path.join(path.relative(project.projectFolder, plugin.dir), targetDir, src);
-                project.addProjectReference(dest, getTargetConditions(obj));
-            } else {
-                // path.join ignores empty paths passed so we don't check whether targetDir is not empty
-                dest = path.join('plugins', plugin.id, targetDir, path.basename(src));
-                copyFile(plugin.dir, src, project.root, dest);
-                if (implementPath) {
-                    copyFile(plugin.dir, implementPath, project.root, path.join(path.dirname(dest), path.basename(implementPath)));
-                }
-                project.addReference(dest, getTargetConditions(obj), implementPath);
-            }
-
-        },
-        uninstall:function(obj, plugin, project, options) {
-            events.emit('verbose', 'windows framework uninstall :: ' + plugin.id  );
-
-            var src = obj.src;
-            var type = obj.type;
-
-            if(type === 'projectReference') {
-                project.removeProjectReference(path.join(path.relative(project.projectFolder, plugin.dir), src), getTargetConditions(obj));
-            }
-            else {
-                var targetPath = path.join('plugins', plugin.id);
-                removeFile(project.root, targetPath);
-                project.removeReference(src, getTargetConditions(obj));
-            }
-        }
-    },
-    asset:{
-        install:function(obj, plugin, project, options) {
-            if (!obj.src) {
-                throw new CordovaError(generateAttributeError('src', 'asset', plugin.id));
-            }
-            if (!obj.target) {
-                throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
-            }
-
-            copyFile(plugin.dir, obj.src, project.www, obj.target);
-            if (options && options.usePlatformWww) copyFile(plugin.dir, obj.src, project.platformWww, obj.target);
-        },
-        uninstall:function(obj, plugin, project, options) {
-            var target = obj.target || obj.src;
-
-            if (!target) throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
-
-            removeFile(project.www, target);
-            removeFile(project.www, path.join('plugins', plugin.id));
-            if (options && options.usePlatformWww) {
-                removeFile(project.platformWww, target);
-                removeFile(project.platformWww, path.join('plugins', plugin.id));
-            }
-        }
-    },
-    'js-module': {
-        install: function (obj, plugin, project, options) {
-            // Copy the plugin's files into the www directory.
-            var moduleSource = path.resolve(plugin.dir, obj.src);
-            var moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname (obj.src)));
-
-            // Read in the file, prepend the cordova.define, and write it back out.
-            var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
-            if (moduleSource.match(/.*\.json$/)) {
-                scriptContent = 'module.exports = ' + scriptContent;
-            }
-            scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
-
-            var moduleDestination = path.resolve(project.www, 'plugins', plugin.id, obj.src);
-            shell.mkdir('-p', path.dirname(moduleDestination));
-            fs.writeFileSync(moduleDestination, scriptContent, 'utf-8');
-            if (options && options.usePlatformWww) {
-                var platformWwwDestination = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src);
-                shell.mkdir('-p', path.dirname(platformWwwDestination));
-                fs.writeFileSync(platformWwwDestination, scriptContent, 'utf-8');
-            }
-        },
-        uninstall: function (obj, plugin, project, options) {
-            var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
-            removeFileAndParents(project.www, pluginRelativePath);
-            if (options && options.usePlatformWww) removeFileAndParents(project.platformWww, pluginRelativePath);
-        }
-    }
-};
-
-// Helpers from common
-
-module.exports.getInstaller = function (type) {
-    if (handlers[type] && handlers[type].install) {
-        return handlers[type].install;
-    }
-
-    events.emit('verbose', '<' + type + '> is not supported for Windows plugins');
-};
-
-module.exports.getUninstaller = function(type) {
-    if (handlers[type] && handlers[type].uninstall) {
-        return handlers[type].uninstall;
-    }
-
-    events.emit('verbose', '<' + type + '> is not supported for Windows plugins');
-};
-
-function getTargetConditions(obj) {
-    return { versions: obj.versions, deviceTarget: obj.deviceTarget, arch: obj.arch };
-}
-
-function copyFile (plugin_dir, src, project_dir, dest, link) {
-    src = path.resolve(plugin_dir, src);
-    if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');
-
-    // check that src path is inside plugin directory
-    var real_path = fs.realpathSync(src);
-    var real_plugin_path = fs.realpathSync(plugin_dir);
-    if (real_path.indexOf(real_plugin_path) !== 0)
-        throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"');
-
-    dest = path.resolve(project_dir, dest);
-
-    // check that dest path is located in project directory
-    if (dest.indexOf(path.resolve(project_dir)) !== 0)
-        throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project');
-
-    shell.mkdir('-p', path.dirname(dest));
-
-    if (link) {
-        fs.symlinkSync(path.relative(path.dirname(dest), src), dest);
-    } else if (fs.statSync(src).isDirectory()) {
-        // XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
-        shell.cp('-Rf', src+'/*', dest);
-    } else {
-        shell.cp('-f', src, dest);
-    }
-}
-
-// Same as copy file but throws error if target exists
-function copyNewFile (plugin_dir, src, project_dir, dest, link) {
-    var target_path = path.resolve(project_dir, dest);
-    if (fs.existsSync(target_path))
-        throw new CordovaError('"' + target_path + '" already exists!');
-
-    copyFile(plugin_dir, src, project_dir, dest, !!link);
-}
-
-// checks if file exists and then deletes. Error if doesn't exist
-function removeFile (project_dir, src) {
-    var file = path.resolve(project_dir, src);
-    shell.rm('-Rf', file);
-}
-
-function removeFileAndParents (baseDir, destFile, stopper) {
-    stopper = stopper || '.';
-    var file = path.resolve(baseDir, destFile);
-    if (!fs.existsSync(file)) return;
-
-    shell.rm('-rf', file);
-
-    // check if directory is empty
-    var curDir = path.dirname(file);
-
-    while(curDir !== path.resolve(baseDir, stopper)) {
-        if(fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
-            fs.rmdirSync(curDir);
-            curDir = path.resolve(curDir, '..');
-        } else {
-            // directory not empty...do nothing
-            break;
-        }
-    }
-}
-
-function generateAttributeError(attribute, element, id) {
-    return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id;
-}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6b98390e/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js
----------------------------------------------------------------------
diff --git a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js
deleted file mode 100644
index 78c9593..0000000
--- a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
-       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 semver = require('semver');
-var CommonPluginInfo = require('cordova-common').PluginInfo;
-
-var MANIFESTS = {
-    'windows': {
-        '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.1.0': ['package.windows.appxmanifest', 'package.phone.appxmanifest'],
-        '10.0.0': 'package.windows10.appxmanifest'
-    }
-};
-
-var SUBSTS = ['package.phone.appxmanifest', 'package.windows.appxmanifest', 'package.windows10.appxmanifest'];
-var TARGETS = ['windows', 'phone', 'all'];
-
-function processChanges(changes) {
-    var hasManifestChanges  = changes.some(function(change) {
-        return change.target === 'package.appxmanifest';
-    });
-
-    if (!hasManifestChanges) {
-        return changes;
-    }
-
-    // Demux 'package.appxmanifest' into relevant platform-specific appx manifests.
-    // Only spend the cycles if there are version-specific plugin settings
-    var oldChanges = changes;
-    changes = [];
-
-    oldChanges.forEach(function(change) {
-        // 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 manifestsForChange = getManifestsForChange(change);
-        changes = changes.concat(demuxChangeWithSubsts(change, manifestsForChange));
-    });
-
-    return changes;
-}
-
-function demuxChangeWithSubsts(change, manifestFiles) {
-    return manifestFiles.map(function(file) {
-         return createReplacement(file, change);
-    });
-}
-
-function getManifestsForChange(change) {
-    var hasTarget = (typeof change.deviceTarget !== 'undefined');
-    var hasVersion = (typeof change.versions !== 'undefined');
-
-    var targetDeviceSet = hasTarget ? change.deviceTarget : 'all';
-
-    if (TARGETS.indexOf(targetDeviceSet) === -1) {
-        // target-device couldn't be resolved, fix it up here to a valid value
-        targetDeviceSet = 'all';
-    }
-
-    // No semver/device-target for this config-file, pass it through
-    if (!(hasTarget || hasVersion)) {
-        return SUBSTS;
-    }
-
-    var knownWindowsVersionsForTargetDeviceSet = Object.keys(MANIFESTS[targetDeviceSet]);
-    return knownWindowsVersionsForTargetDeviceSet.reduce(function(manifestFiles, winver) {
-        if (hasVersion && !semver.satisfies(winver, change.versions)) {
-            return manifestFiles;
-        }
-        return manifestFiles.concat(MANIFESTS[targetDeviceSet][winver]);
-    }, []);
-}
-
-// This is a local function that creates the new replacement representing the
-// mutation.  Used to save code further down.
-function createReplacement(manifestFile, originalChange) {
-    var replacement = {
-        target:         manifestFile,
-        parent:         originalChange.parent,
-        after:          originalChange.after,
-        xmls:           originalChange.xmls,
-        versions:       originalChange.versions,
-        deviceTarget:   originalChange.deviceTarget
-    };
-    return replacement;
-}
-
-
-/*
-A class for holidng the information currently stored in plugin.xml
-It's inherited from cordova-common's PluginInfo class
-In addition it overrides getConfigFiles, getEditConfigs, getFrameworks methods to use windows-specific logic
- */
-function PluginInfo(dirname) {
-    //  We're not using `util.inherit' because original PluginInfo defines
-    //  its' methods inside of constructor
-    CommonPluginInfo.apply(this, arguments);
-    var parentGetConfigFiles = this.getConfigFiles;
-    var parentGetEditConfigs = this.getEditConfigs;
-
-    this.getEditConfigs = function(platform) {
-        var editConfigs = parentGetEditConfigs(platform);
-        return processChanges(editConfigs);
-    };
-
-    this.getConfigFiles = function(platform) {
-        var configFiles = parentGetConfigFiles(platform);
-        return processChanges(configFiles);
-    };
-}
-
-exports.PluginInfo = PluginInfo;


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


[2/4] cordova-lib git commit: CB-12870 : catch all use cases for getPlatformApiFunc and update tests accordingly

Posted by au...@apache.org.
CB-12870 : catch all use cases for getPlatformApiFunc and update tests accordingly


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

Branch: refs/heads/master
Commit: 60bbd8bc4df68ad810eb88db02fe0ce9524c7aa2
Parents: 812373d
Author: Audrey So <au...@apache.org>
Authored: Wed May 31 10:41:22 2017 -0700
Committer: Audrey So <au...@apache.org>
Committed: Tue Aug 29 16:12:41 2017 -0700

----------------------------------------------------------------------
 integration-tests/pkgJson-restore.spec.js       |  18 +-
 package.json                                    |   2 +-
 .../windows/cordova/lib/AppxManifest.js         | 733 +++++++++++++++++++
 .../windows/cordova/lib/ConfigChanges.js        | 164 +++++
 .../windows/cordova/lib/JsprojManager.js        | 626 ++++++++++++++++
 .../windows/cordova/lib/PluginHandler.js        | 282 +++++++
 .../platforms/windows/cordova/lib/PluginInfo.js | 139 ++++
 spec/cordova/fixtures/basePkgJson4/config.xml   |   2 +-
 spec/cordova/fixtures/basePkgJson4/package.json |   2 +-
 .../platforms/cordova-browser/package.json      |   1 -
 .../platforms/windows/cordova/Api.js            | 365 ++++++++-
 spec/cordova/platforms/platforms.spec.js        |  10 +-
 spec/cordova/util.spec.js                       |  40 +
 src/cordova/util.js                             |  69 +-
 14 files changed, 2406 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/integration-tests/pkgJson-restore.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/pkgJson-restore.spec.js b/integration-tests/pkgJson-restore.spec.js
index 8b814a0..feaf090 100644
--- a/integration-tests/pkgJson-restore.spec.js
+++ b/integration-tests/pkgJson-restore.spec.js
@@ -670,8 +670,8 @@ describe('update config.xml to include platforms in pkg.json', function () {
         expect(configEngArray.indexOf('browser')).toEqual(-1);
         expect(configEngArray.length === 1);
         // Pkg.json has cordova-browser in its dependencies.
-        expect(pkgJson.dependencies).toEqual({ 'cordova-android': '^3.1.0', 'cordova-browser': '^4.1.0' });
-        emptyPlatformList().then(function () {
+        expect(pkgJson.dependencies).toEqual({ 'cordova-android' : '^5.0.0', 'cordova-browser' : '^4.1.0' });
+        emptyPlatformList().then(function() {
             // Run cordova prepare.
             return prepare();
         }).then(function () {
@@ -690,10 +690,10 @@ describe('update config.xml to include platforms in pkg.json', function () {
             // Expect config.xml array to have 2 elements (platforms).
             expect(configEngArray.length === 2);
             // Check to make sure that 'browser' spec was added properly.
-            expect(engines).toEqual([ { name: 'android', spec: '^3.1.0' }, { name: 'browser', spec: '^4.1.0' } ]);
+            expect(engines).toEqual([ { name: 'android', spec: '^5.0.0' },{ name: 'browser', spec: '^4.1.0' } ]);
             // No change to pkg.json dependencies.
-            expect(pkgJson.dependencies).toEqual({ 'cordova-android': '^3.1.0', 'cordova-browser': '^4.1.0' });
-            expect(pkgJson.dependencies['cordova-android']).toEqual('^3.1.0');
+            expect(pkgJson.dependencies).toEqual({ 'cordova-android' : '^5.0.0', 'cordova-browser' : '^4.1.0' });
+            expect(pkgJson.dependencies['cordova-android']).toEqual('^5.0.0');
             expect(pkgJson.dependencies['cordova-browser']).toEqual('^4.1.0');
         }).fail(function (err) {
             expect(err).toBeUndefined();
@@ -1248,8 +1248,8 @@ describe('platforms and plugins should be restored with config.xml even without
         shell.rm('-rf', tmpDir);
         // Copy then move because we need to copy everything, but that means it will copy the whole directory.
         // Using /* doesn't work because of hidden files.
-        // Use basePkgJson6 because pkg.json and config.xml contain only android
-        shell.cp('-R', path.join(__dirname, '..', 'spec', 'cordova', 'fixtures', 'basePkgJson13'), tmpDir);
+        // Use basePkgJson13 because pkg.json and config.xml contain only android
+        shell.cp('-R', path.join(__dirname, '..', 'spec-cordova', 'fixtures', 'basePkgJson13'), tmpDir);
         shell.mv(path.join(tmpDir, 'basePkgJson13'), project);
         process.chdir(project);
         events.on('results', function (res) { results = res; });
@@ -1285,7 +1285,7 @@ describe('platforms and plugins should be restored with config.xml even without
         var pluginsFolderPath16 = path.join(cwd, 'plugins');
         var platformsJson;
         var androidPlatform = 'android';
-        var browserPlatform = 'browser';
+        var browserPlatform = 'windows';
 
         // Pkg.json does not exist.
         expect(path.join(cwd, 'package.json')).not.toExist();
@@ -1491,7 +1491,7 @@ describe('tests platform/spec restore with --save', function () {
         var pkgJson;
         var platformsFolderPath = path.join(cwd, 'platforms/platforms.json');
         var platformsJson;
-        var secondPlatformAdded = 'browser';
+        var secondPlatformAdded = 'windows';
 
         emptyPlatformList().then(function () {
             // Add the testing platform with --save.

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 7a7f4ea..3834ad5 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
     "properties-parser": "0.3.1",
     "q": "1.0.1",
     "request": "2.79.0",
-    "semver": "5.3.0",
+    "semver": "^5.3.0",
     "shelljs": "0.3.0",
     "tar": "2.2.1",
     "underscore": "1.8.3",

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js
----------------------------------------------------------------------
diff --git a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js
new file mode 100644
index 0000000..1875bf7
--- /dev/null
+++ b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/AppxManifest.js
@@ -0,0 +1,733 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+var fs = require('fs');
+var util = require('util');
+var et = require('elementtree');
+var path = require('path');
+var xml= require('cordova-common').xmlHelpers;
+
+var UAP_RESTRICTED_CAPS = ['enterpriseAuthentication', 'sharedUserCertificates',
+                           'documentsLibrary', 'musicLibrary', 'picturesLibrary',
+                           'videosLibrary', 'removableStorage', 'internetClientClientServer',
+                           'privateNetworkClientServer'];
+
+// UAP namespace capabilities come from the XSD type ST_Capability_Uap from AppxManifestTypes.xsd
+var CAPS_NEEDING_UAPNS  = ['documentsLibrary', 'picturesLibrary', 'videosLibrary',
+                           'musicLibrary', 'enterpriseAuthentication', 'sharedUserCertificates',
+                           'removableStorage', 'appointments', 'contacts', 'userAccountInformation',
+                           'phoneCall', 'blockedChatMessages', 'objects3D'];
+
+var KNOWN_ORIENTATIONS = {
+    'default':   ['portrait', 'landscape', 'landscapeFlipped'],
+    'portrait':  ['portrait'],
+    'landscape': ['landscape', 'landscapeFlipped']
+};
+
+/**
+ * Store to cache appxmanifest files based on file location
+ * @type  {Object}
+ */
+var manifestCache = {};
+
+/**
+ * @constructor
+ * @constructs AppxManifest
+ *
+ * Wraps an AppxManifest file. Shouldn't be instantiated directly.
+ *   AppxManifest.get should be used instead to select proper manifest type
+ *   (AppxManifest for Win 8/8.1/Phone 8.1, Win10AppxManifest for Win 10)
+ *
+ * @param  {string}  path    Path to appxmanifest to wrap
+ * @param  {string}  prefix  A namespace prefix used to prepend some elements.
+ *   Depends on manifest type.
+ */
+function AppxManifest(path, prefix) {
+    this.path = path;
+    // Append ':' to prefix if needed
+    prefix = prefix || '';
+    this.prefix = (prefix.indexOf(':') === prefix.length - 1) ? prefix : prefix + ':';
+    this.doc = xml.parseElementtreeSync(path);
+    if (this.doc.getroot().tag !== 'Package') {
+        // Some basic validation
+        throw new Error(path + ' has incorrect root node name (expected "Package")');
+    }
+
+    // Indicates that this manifest is for phone application (either WinPhone 8.1 or Universal Windows 10)
+    this.hasPhoneIdentity = this.prefix === 'uap:' || this.prefix === 'm3:';
+}
+
+//  Static read-only property to get capabilities which need to be prefixed with uap
+Object.defineProperty(AppxManifest, 'CapsNeedUapPrefix', {
+    writable: false,
+    configurable: false,
+    value: CAPS_NEEDING_UAPNS
+});
+
+/**
+ * @static
+ * @constructs AppxManifest|Win10AppxManifest
+ *
+ * Instantiates a new AppxManifest/Win10AppxManifest class. Chooses which
+ *   constructor to use based on xmlns attributes of Package node
+ *
+ * @param   {String}  fileName  File to create manifest for
+ * @param   {Boolean} [ignoreCache=false]  Specifies, whether manifest cache will be
+ *   used to return resultant object
+ *
+ * @return  {AppxManifest|Win10AppxManifest}  Manifest instance
+ */
+AppxManifest.get = function (fileName, ignoreCache) {
+
+    if (!ignoreCache && manifestCache[fileName]) {
+        return manifestCache[fileName];
+    }
+
+    var root = xml.parseElementtreeSync(fileName).getroot();
+    var prefixes = Object.keys(root.attrib)
+    .reduce(function (result, attrib) {
+        if (attrib.indexOf('xmlns') === 0 && attrib !== 'xmlns:mp') {
+            result.push(attrib.replace('xmlns', '').replace(':', ''));
+        }
+
+        return result;
+    }, []).sort();
+
+    var prefix = prefixes[prefixes.length - 1];
+    var Manifest = prefix === 'uap' ? Win10AppxManifest : AppxManifest;
+    var result = new Manifest(fileName, prefix);
+
+    if (!ignoreCache) {
+        manifestCache[fileName] = result;
+    }
+
+    return result;
+};
+
+/**
+ * Removes manifests from cache to prevent using stale entries
+ *
+ * @param {String|String[]} [cacheKeys] The keys to delete from cache. If not
+ *   specified, the whole cache will be purged
+ */
+AppxManifest.purgeCache = function (cacheKeys) {
+    if (!cacheKeys) {
+        // if no arguments passed, remove all entries
+        manifestCache = {};
+        return;
+    }
+
+    var keys = Array.isArray(cacheKeys) ? cacheKeys : [cacheKeys];
+    keys.forEach(function (key) {
+        delete manifestCache[key];
+    });
+};
+
+AppxManifest.prototype.getPhoneIdentity = function () {
+    var phoneIdentity = this.doc.getroot().find('./mp:PhoneIdentity');
+    if (!phoneIdentity)
+        throw new Error('Failed to find PhoneIdentity element in appxmanifest at ' + this.path);
+
+    return {
+        getPhoneProductId: function () {
+            return phoneIdentity.attrib.PhoneProductId;
+        },
+        setPhoneProductId: function (id) {
+            if (!id) throw new Error('Argument for "setPhoneProductId" must be defined in appxmanifest at ' + this.path);
+            phoneIdentity.attrib.PhoneProductId = id;
+            return this;
+        }
+    };
+};
+
+AppxManifest.prototype.getIdentity = function () {
+    var identity = this.doc.getroot().find('./Identity');
+    if (!identity)
+        throw new Error('Failed to find "Identity" node. The appxmanifest at ' + this.path + ' is invalid');
+
+    return {
+        getName: function () {
+            return identity.attrib.Name;
+        },
+        setName: function (name) {
+            if (!name) throw new TypeError('Identity.Name attribute must be non-empty in appxmanifest at ' + this.path);
+            identity.attrib.Name = name;
+            return this;
+        },
+        getPublisher: function () {
+            return identity.attrib.Publisher;
+        },
+        setPublisher: function (publisherId) {
+            if (!publisherId) throw new TypeError('Identity.Publisher attribute must be non-empty in appxmanifest at ' + this.path);
+            identity.attrib.Publisher = publisherId;
+            return this;
+        },
+        getVersion: function () {
+            return identity.attrib.Version;
+        },
+        setVersion: function (version) {
+            if (!version) throw new TypeError('Identity.Version attribute must be non-empty in appxmanifest at ' + this.path );
+
+            // Adjust version number as per CB-5337 Windows8 build fails due to invalid app version
+            if(version && version.match(/\.\d/g)) {
+                var numVersionComponents = version.match(/\.\d/g).length + 1;
+                while (numVersionComponents++ < 4) {
+                    version += '.0';
+                }
+            }
+
+            identity.attrib.Version = version;
+            return this;
+        }
+    };
+};
+
+AppxManifest.prototype.getProperties = function () {
+    var properties = this.doc.getroot().find('./Properties');
+
+    if (!properties)
+        throw new Error('Failed to find "Properties" node. The appxmanifest at ' + this.path + ' is invalid');
+
+    return {
+        getDisplayName: function () {
+            var displayName = properties.find('./DisplayName');
+            return displayName && displayName.text;
+        },
+        setDisplayName: function (name) {
+            if (!name) throw new TypeError('Properties.DisplayName elements must be non-empty in appxmanifest at ' + this.path);
+            var displayName = properties.find('./DisplayName');
+
+            if (!displayName) {
+                displayName = new et.Element('DisplayName');
+                properties.append(displayName);
+            }
+
+            displayName.text = name;
+
+            return this;
+        },
+        getPublisherDisplayName: function () {
+            var publisher = properties.find('./PublisherDisplayName');
+            return publisher && publisher.text;
+        },
+        setPublisherDisplayName: function (name) {
+            if (!name) throw new TypeError('Properties.PublisherDisplayName elements must be non-empty in appxmanifest at ' + this.path);
+            var publisher = properties.find('./PublisherDisplayName');
+
+            if (!publisher) {
+                publisher = new et.Element('PublisherDisplayName');
+                properties.append(publisher);
+            }
+
+            publisher.text = name;
+
+            return this;
+        },
+        getDescription: function () {
+            var description = properties.find('./Description');
+            return description && description.text;
+        },
+        setDescription: function (text) {
+
+            var description = properties.find('./Description');
+
+            if (!text || text.length === 0) {
+                if (description) properties.remove(description);
+                return this;
+            }
+
+            if (!description) {
+                description = new et.Element('Description');
+                properties.append(description);
+            }
+
+            description.text = processDescription(text);
+
+            return this;
+        },
+    };
+};
+
+AppxManifest.prototype.getApplication = function () {
+    var application = this.doc.getroot().find('./Applications/Application');
+    if (!application)
+        throw new Error('Failed to find "Application" element. The appxmanifest at ' + this.path + ' is invalid');
+
+    var self = this;
+
+    return {
+        _node: application,
+        getVisualElements: function () {
+            return self.getVisualElements();
+        },
+        getId: function () {
+            return application.attrib.Id;
+        },
+        setId: function (id) {
+            if (!id) throw new TypeError('Application.Id attribute must be defined in appxmanifest at ' + this.path);
+            // 64 symbols restriction goes from manifest schema definition
+            // http://msdn.microsoft.com/en-us/library/windows/apps/br211415.aspx
+            var appId = id.length <= 64 ? id : id.substr(0, 64);
+            application.attrib.Id = appId;
+            return this;
+        },
+        getStartPage: function () {
+            return application.attrib.StartPage;
+        },
+        setStartPage: function (page) {
+            if (!page) page = 'www/index.html'; // Default valur is always index.html
+            application.attrib.StartPage = page;
+            return this;
+        },
+        getAccessRules: function () {
+            return application
+                .findall('./ApplicationContentUriRules/Rule')
+                .map(function (rule) {
+                    return rule.attrib.Match;
+                });
+        },
+        setAccessRules: function (rules) {
+            var appUriRules = application.find('ApplicationContentUriRules');
+            if (appUriRules) {
+                application.remove(appUriRules);
+            }
+
+            // No rules defined
+            if (!rules || rules.length === 0) {
+                return;
+            }
+
+            appUriRules = new et.Element('ApplicationContentUriRules');
+            application.append(appUriRules);
+
+            rules.forEach(function(rule) {
+                appUriRules.append(new et.Element('Rule', {Match: rule, Type: 'include'}));
+            });
+
+            return this;
+        }
+    };
+};
+
+AppxManifest.prototype.getVisualElements = function () {
+    var self = this;
+    var visualElements = this.doc.getroot().find('./Applications/Application/' +
+        this.prefix  + 'VisualElements');
+
+    if (!visualElements)
+        throw new Error('Failed to find "VisualElements" node. The appxmanifest at ' + this.path + ' is invalid');
+
+    return {
+        _node: visualElements,
+        getDisplayName: function () {
+            return visualElements.attrib.DisplayName;
+        },
+        setDisplayName: function (name) {
+            if (!name) throw new TypeError('VisualElements.DisplayName attribute must be defined in appxmanifest at ' + this.path);
+            visualElements.attrib.DisplayName = name;
+            return this;
+        },
+        getOrientation: function () {
+            return visualElements.findall(self.prefix + 'Rotation')
+                .map(function (element) {
+                    return element.attrib.Preference;
+                });
+        },
+        setOrientation: function (orientation) {
+            if (!orientation || orientation === ''){
+                orientation = 'default';
+            }
+
+            var rotationPreferenceRootName = self.prefix + 'InitialRotationPreference';
+            var rotationPreferenceRoot = visualElements.find('./' + rotationPreferenceRootName);
+
+            if (!orientation && rotationPreferenceRoot) {
+                // Remove InitialRotationPreference root element to revert to defaults
+                visualElements.remove(rotationPreferenceRoot);
+                return this;
+            }
+
+            if(!rotationPreferenceRoot) {
+                rotationPreferenceRoot = new et.Element(rotationPreferenceRootName);
+                visualElements.append(rotationPreferenceRoot);
+            }
+
+            rotationPreferenceRoot.clear();
+
+            var orientations = KNOWN_ORIENTATIONS[orientation] || orientation.split(',');
+            orientations.forEach(function(orientation) {
+                var el = new et.Element(self.prefix + 'Rotation', {Preference: orientation} );
+                rotationPreferenceRoot.append(el);
+            });
+
+            return this;
+        },
+        getBackgroundColor: function () {
+            return visualElements.attrib.BackgroundColor;
+        },
+        setBackgroundColor: function (color) {
+            if (!color)
+                throw new TypeError('VisualElements.BackgroundColor attribute must be defined in appxmanifest at ' + this.path);
+
+            visualElements.attrib.BackgroundColor = refineColor(color);
+            return this;
+        },
+        trySetBackgroundColor: function (color) {
+            try {
+                return this.setBackgroundColor(color);
+            } catch (e) { return this; }
+        },
+        getForegroundText: function () {
+            return visualElements.attrib.ForegroundText;
+        },
+        setForegroundText: function (color) {
+            // If color is not set, fall back to 'light' by default
+            visualElements.attrib.ForegroundText = color || 'light';
+
+            return this;
+        },
+        getSplashBackgroundColor: function () {
+            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
+            return splashNode && splashNode.attrib.BackgroundColor;
+        },
+        setSplashBackgroundColor: function (color) {
+            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
+            if (splashNode) {
+                if (color) {
+                    splashNode.attrib.BackgroundColor = refineColor(color);
+                } else {
+                    delete splashNode.attrib.BackgroundColor;
+                }
+            }
+            return this;
+        },
+        getSplashScreenExtension: function (extension) {
+            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
+            return splashNode && splashNode.attrib.Image && path.extname(splashNode.attrib.Image);
+        },
+        setSplashScreenExtension: function (extension) {
+            var splashNode = visualElements.find('./' + self.prefix + 'SplashScreen');
+            if (splashNode) {
+                var oldPath = splashNode.attrib.Image; 
+                splashNode.attrib.Image = path.dirname(oldPath) + '\\' + path.basename(oldPath, path.extname(oldPath)) + extension;
+            }
+            return this;
+        },
+        getToastCapable: function () {
+            return visualElements.attrib.ToastCapable;
+        },
+        setToastCapable: function (isToastCapable) {
+            if (isToastCapable === true || isToastCapable.toString().toLowerCase() === 'true') {
+                visualElements.attrib.ToastCapable = 'true';
+            } else {
+                delete visualElements.attrib.ToastCapable;
+            }
+
+            return this;
+        },
+        getDescription: function () {
+            return visualElements.attrib.Description;
+        },
+        setDescription: function (description) {
+            if (!description || description.length === 0)
+                throw new TypeError('VisualElements.Description attribute must be defined and non-empty in appxmanifest at ' + this.path);
+
+            visualElements.attrib.Description = processDescription(description);
+            return this;
+        },
+    };
+};
+
+AppxManifest.prototype.getCapabilities = function () {
+    var capabilities = this.doc.find('./Capabilities');
+    if (!capabilities) return [];
+
+    return capabilities.getchildren()
+    .map(function (element) {
+        return { type: element.tag, name: element.attrib.Name };
+    });
+};
+
+function isCSSColorName(color) {
+    return color.indexOf('0x') === -1 && color.indexOf('#') === -1;
+}
+
+function refineColor(color) {
+    if (isCSSColorName(color)) {
+        return color;
+    }
+
+    // return three-byte hexadecimal number preceded by "#" (required for Windows)
+    color = color.replace('0x', '').replace('#', '');
+    if (color.length == 3) {
+        color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2];
+    }
+    // alpha is not supported, so we remove it
+    if (color.length == 8) { // AArrggbb
+        color = color.slice(2);
+    }
+    return '#' + color;
+}
+
+function processDescription(text) {
+    var result = text;
+
+    // Description value limitations: https://msdn.microsoft.com/en-us/library/windows/apps/br211429.aspx
+    // value should be no longer than 2048 characters
+    if (text.length > 2048) {
+        result = text.substr(0, 2048);
+    }
+
+    // value should not contain newlines and tabs
+    return result.replace(/(\n|\r)/g, ' ').replace(/\t/g, '    ');
+}
+
+// Shortcut for getIdentity.setName
+AppxManifest.prototype.setPackageName = function (name) {
+    this.getIdentity().setName(name);
+    return this;
+};
+
+// Shortcut for multiple inner methods calls
+AppxManifest.prototype.setAppName = function (name) {
+    this.getProperties().setDisplayName(name);
+    this.getVisualElements().setDisplayName(name);
+
+    return this;
+};
+
+/**
+ * Writes manifest to disk syncronously. If filename is specified, then manifest
+ *   will be written to that file
+ *
+ * @param   {String}  [destPath]  File to write manifest to. If omitted,
+ *   manifest will be written to file it has been read from.
+ */
+AppxManifest.prototype.write = function(destPath) {
+    // sort Capability elements as per CB-5350 Windows8 build fails due to invalid 'Capabilities' definition
+    sortCapabilities(this.doc);
+    fs.writeFileSync(destPath || this.path, this.doc.write({indent: 4}), 'utf-8');
+};
+
+/**
+ * Sorts 'capabilities' elements in manifest in ascending order
+ * @param   {Elementtree.Document}  manifest  An XML document that represents
+ *   appxmanifest
+ */
+function sortCapabilities(manifest) {
+
+    // removes namespace prefix (m3:Capability -> Capability)
+    // this is required since elementtree returns qualified name with namespace
+    function extractLocalName(tag) {
+        return tag.split(':').pop(); // takes last part of string after ':'
+    }
+
+    var capabilitiesRoot = manifest.find('.//Capabilities'),
+        capabilities = capabilitiesRoot.getchildren() || [];
+    // to sort elements we remove them and then add again in the appropriate order
+    capabilities.forEach(function(elem) { // no .clear() method
+        capabilitiesRoot.remove(elem);
+        // CB-7601 we need local name w/o namespace prefix to sort capabilities correctly
+        elem.localName = extractLocalName(elem.tag);
+    });
+    capabilities.sort(function(a, b) {
+        return (a.localName > b.localName) ? 1: -1;
+    });
+    capabilities.forEach(function(elem) {
+        capabilitiesRoot.append(elem);
+    });
+}
+
+
+function Win10AppxManifest(path) {
+    AppxManifest.call(this, path, /*prefix=*/'uap');
+}
+
+util.inherits(Win10AppxManifest, AppxManifest);
+
+Win10AppxManifest.prototype.getApplication = function () {
+    // Call overridden method
+    var result = AppxManifest.prototype.getApplication.call(this);
+    var application = result._node;
+
+    result.getAccessRules = function () {
+        return application
+            .findall('./uap:ApplicationContentUriRules/uap:Rule')
+            .map(function (rule) {
+                return rule.attrib.Match;
+            });
+    };
+
+    result.setAccessRules = function (rules) {
+        var appUriRules = application.find('./uap:ApplicationContentUriRules');
+        if (appUriRules) {
+            application.remove(appUriRules);
+        }
+
+        // No rules defined
+        if (!rules || rules.length === 0) {
+            return;
+        }
+
+        appUriRules = new et.Element('uap:ApplicationContentUriRules');
+        application.append(appUriRules);
+
+        rules.forEach(function(rule) {
+            appUriRules.append(new et.Element('uap:Rule', { Match: rule, Type: 'include', WindowsRuntimeAccess: 'all' }));
+        });
+
+        return this;
+    };
+
+    return result;
+};
+
+Win10AppxManifest.prototype.getVisualElements = function () {
+    // Call base method and extend its results
+    var result = AppxManifest.prototype.getVisualElements.call(this);
+    var defaultTitle = result._node.find('./uap:DefaultTile');
+
+    result.getDefaultTitle = function () {
+        return {
+            getShortName: function () {
+                return defaultTitle.attrib.ShortName;
+            },
+            setShortName: function (name) {
+                if (!name) throw new TypeError('Argument for "setDisplayName" must be defined in appxmanifest at ' + this.path);
+                defaultTitle.attrib.ShortName = name;
+                return this;
+            }
+        };
+    };
+
+    // ToastCapable attribute was removed in Windows 10.
+    // See https://msdn.microsoft.com/ru-ru/library/windows/apps/dn423310.aspx
+    result.getToastCapable = function () {};
+    result.setToastCapable = function () { return this; };
+
+    // ForegroundText was removed in Windows 10 as well
+    result.getForegroundText = function () {};
+    result.setForegroundText = function () { return this; };
+
+    return result;
+};
+
+// Shortcut for multiple inner methods calls
+Win10AppxManifest.prototype.setAppName = function (name) {
+    // Call base method
+    AppxManifest.prototype.setAppName.call(this, name);
+    this.getVisualElements().getDefaultTitle().setShortName(name);
+
+    return this;
+};
+
+/**
+ * Checks for capabilities which are Restricted in Windows 10 UAP.
+ * @return {string[]|false} An array of restricted capability names, or false.
+ */
+Win10AppxManifest.prototype.getRestrictedCapabilities = function () {
+    var restrictedCapabilities = this.getCapabilities()
+    .filter(function (capability) {
+        return UAP_RESTRICTED_CAPS.indexOf(capability.name) >= 0;
+    });
+
+    return restrictedCapabilities.length === 0 ? false : restrictedCapabilities;
+};
+
+/**
+ * Sets up a Dependencies section for appxmanifest. If no arguments provided,
+ *   deletes Dependencies section.
+ *
+ * @param  {Object[]}  dependencies  Array of arbitrary object, which fields
+ *   will be used to set each dependency attributes.
+ *
+ * @returns {Win10AppxManifest}  self instance
+ */
+Win10AppxManifest.prototype.setDependencies = function (dependencies) {
+    var dependenciesElement = this.doc.find('./Dependencies');
+
+    if ((!dependencies || dependencies.length === 0) && dependenciesElement) {
+        this.doc.remove(dependenciesElement);
+        return this;
+    }
+
+    if (!dependenciesElement) {
+        dependenciesElement = new et.Element('Dependencies');
+        this.doc.append(dependenciesElement);
+    }
+
+    if (dependenciesElement.len() > 0) {
+        dependenciesElement.clear();
+    }
+
+    dependencies.forEach(function (uapVersionInfo) {
+        dependenciesElement.append(new et.Element('TargetDeviceFamily', uapVersionInfo));
+    });
+};
+
+/**
+ * Writes manifest to disk syncronously. If filename is specified, then manifest
+ *   will be written to that file
+ *
+ * @param   {String}  [destPath]  File to write manifest to. If omitted,
+ *   manifest will be written to file it has been read from.
+ */
+Win10AppxManifest.prototype.write = function(destPath) {
+    fs.writeFileSync(destPath || this.path, this.writeToString(), 'utf-8');
+};
+
+Win10AppxManifest.prototype.writeToString = function() {
+    ensureUapPrefixedCapabilities(this.doc.find('.//Capabilities'));
+    ensureUniqueCapabilities(this.doc.find('.//Capabilities'));
+    // sort Capability elements as per CB-5350 Windows8 build fails due to invalid 'Capabilities' definition
+    sortCapabilities(this.doc);
+    return this.doc.write({indent: 4});
+};
+
+/**
+ * Checks for capabilities which require the uap: prefix in Windows 10.
+ * @param capabilities {ElementTree.Element} The appx manifest element for <capabilities>
+ */
+function ensureUapPrefixedCapabilities(capabilities) {
+    capabilities.getchildren()
+    .forEach(function(el) {
+        if (CAPS_NEEDING_UAPNS.indexOf(el.attrib.Name) > -1 && el.tag.indexOf('uap:') !== 0) {
+            el.tag = 'uap:' + el.tag;
+        }
+    });
+}
+
+/**
+ * Cleans up duplicate capability declarations that were generated during the prepare process
+ * @param capabilities {ElementTree.Element} The appx manifest element for <capabilities>
+ */
+function ensureUniqueCapabilities(capabilities) {
+    var uniqueCapabilities = [];
+    capabilities.getchildren()
+    .forEach(function(el) {
+        var name = el.attrib.Name;
+        if (uniqueCapabilities.indexOf(name) !== -1) {
+            capabilities.remove(el);
+        } else {
+            uniqueCapabilities.push(name);
+        }
+    });
+}
+
+module.exports = AppxManifest;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js
----------------------------------------------------------------------
diff --git a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js
new file mode 100644
index 0000000..c07a77a
--- /dev/null
+++ b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/ConfigChanges.js
@@ -0,0 +1,164 @@
+/*
+ * 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 util = require('util');
+var path = require('path');
+var CommonMunger = require('cordova-common').ConfigChanges.PlatformMunger;
+var CapsNeedUapPrefix = require(path.join(__dirname, 'AppxManifest')).CapsNeedUapPrefix;
+
+var CAPS_SELECTOR = '/Package/Capabilities';
+var WINDOWS10_MANIFEST = 'package.windows10.appxmanifest';
+
+function PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider) {
+    CommonMunger.apply(this, arguments);
+}
+
+util.inherits(PlatformMunger, CommonMunger);
+
+/**
+ * This is an override of apply_file_munge method from cordova-common's PlatformMunger class.
+ * In addition to parent's method logic also removes capabilities with 'uap:' prefix that were
+ * added by AppxManifest class
+ *
+ * @param {String}  file   A file name to apply munge to
+ * @param {Object}  munge  Serialized changes that need to be applied to the file
+ * @param {Boolean} [remove=false] Flag that specifies whether the changes
+ *   need to be removed or added to the file
+ */
+PlatformMunger.prototype.apply_file_munge = function (file, munge, remove) {
+
+    // Create a copy to avoid modification of original munge
+    var mungeCopy = cloneObject(munge);
+    var capabilities = mungeCopy.parents[CAPS_SELECTOR];
+
+    if (capabilities) {
+        // Add 'uap' prefixes for windows 10 manifest
+        if (file === WINDOWS10_MANIFEST) {
+            capabilities = generateUapCapabilities(capabilities);
+        }
+
+        // Remove duplicates and sort capabilities when installing plugin
+        if (!remove) {
+            capabilities = getUniqueCapabilities(capabilities).sort(compareCapabilities);
+        }
+
+        // Put back capabilities into munge's copy
+        mungeCopy.parents[CAPS_SELECTOR] = capabilities;
+    }
+
+    PlatformMunger.super_.prototype.apply_file_munge.call(this, file, mungeCopy, remove);
+};
+
+// Recursive function to clone an object
+function cloneObject(obj) {
+    if (obj === null || typeof obj !== 'object') {
+        return obj;
+    }
+
+    var copy = obj.constructor();
+    Object.keys(obj).forEach(function(key) {
+        copy[key] = cloneObject(obj[key]);
+    });
+
+    return copy;
+}
+
+/**
+ * Retrieve capabality name from xml field
+ * @param {Object} capability with xml field like <Capability Name="CapabilityName">
+ * @return {String} name of capability
+ */
+function getCapabilityName(capability) {
+    var reg = /Name\s*=\s*"(.*?)"/;
+    return capability.xml.match(reg)[1];
+}
+
+/**
+ * Remove capabilities with same names
+ * @param {Object} an array of capabilities
+ * @return {Object} an unique array of capabilities
+ */
+function getUniqueCapabilities(capabilities) {
+    return capabilities.reduce(function(uniqueCaps, currCap) {
+
+        var isRepeated = uniqueCaps.some(function(cap) {
+            return getCapabilityName(cap) === getCapabilityName(currCap);
+        });
+
+        return isRepeated ? uniqueCaps : uniqueCaps.concat([currCap]);
+    }, []);
+}
+
+/**
+ * Comparator function to pass to Array.sort
+ * @param {Object} firstCap first capability
+ * @param {Object} secondCap second capability
+ * @return {Number} either -1, 0 or 1
+ */
+function compareCapabilities(firstCap, secondCap) {
+    var firstCapName = getCapabilityName(firstCap);
+    var secondCapName = getCapabilityName(secondCap);
+
+    if (firstCapName < secondCapName) {
+        return -1;
+    }
+
+    if (firstCapName > secondCapName) {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+/**
+ * Generates a new munge that contains <uap:Capability> elements created based on
+ * corresponding <Capability> elements from base munge. If there are no such elements
+ * found in base munge, the empty munge is returned (selectors might be present under
+ * the 'parents' key, but they will contain no changes).
+ *
+ * @param {Object} capabilities A list of capabilities
+ * @return {Object} A list with 'uap'-prefixed capabilities
+ */
+function generateUapCapabilities(capabilities) {
+
+    function hasCapabilityChange(change) {
+        return /^\s*<(\w+:)?(Device)?Capability\s/.test(change.xml);
+    }
+
+    function createPrefixedCapabilityChange(change) {
+        if (CapsNeedUapPrefix.indexOf(getCapabilityName(change)) < 0) {
+            return change;
+        }
+
+        //  If capability is already prefixed, avoid adding another prefix
+        var replaceXML = change.xml.indexOf('uap:') > 0 ? change.xml : change.xml.replace(/Capability/, 'uap:Capability');
+        return {
+            xml: replaceXML,
+            count: change.count,
+            before: change.before
+        };
+    }
+
+    return capabilities
+     // For every xml change check if it adds a <Capability> element ...
+    .filter(hasCapabilityChange)
+    // ... and create a duplicate with 'uap:' prefix
+    .map(createPrefixedCapabilityChange);
+
+}
+
+exports.PlatformMunger = PlatformMunger;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js
----------------------------------------------------------------------
diff --git a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js
new file mode 100644
index 0000000..28b2fa3
--- /dev/null
+++ b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/JsprojManager.js
@@ -0,0 +1,626 @@
+/**
+ 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 */
+
+/*
+ Helper for dealing with Windows Store JS app .jsproj files
+ */
+
+var fs = require('fs');
+var et = require('elementtree');
+var path = require('path');
+var util = require('util');
+var semver = require('semver');
+var shell = require('shelljs');
+var AppxManifest = require('./AppxManifest');
+var PluginHandler = require('./PluginHandler');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+var xml_helpers = require('cordova-common').xmlHelpers;
+var AppxManifest = require('./AppxManifest');
+
+var WinCSharpProjectTypeGUID = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";  // .csproj
+var WinCplusplusProjectTypeGUID = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";  // .vcxproj
+
+// Match a JavaScript Project
+var JSPROJ_REGEXP = /(Project\("\{262852C6-CD72-467D-83FE-5EEB1973A190}"\)\s*=\s*"[^"]+",\s*"[^"]+",\s*"\{[0-9a-f\-]+}"[^\r\n]*[\r\n]*)/gi;
+
+// Chars in a string that need to be escaped when used in a RegExp
+var ESCAPE_REGEXP = /([.?*+\^$\[\]\\(){}|\-])/g;
+
+function jsprojManager(location) {
+    this.root = path.dirname(location);
+    this.isUniversalWindowsApp = path.extname(location).toLowerCase() === ".projitems";
+    this.projects = [];
+    this.master = this.isUniversalWindowsApp ? new proj(location) : new jsproj(location);
+    this.projectFolder = path.dirname(location);
+    this.www = path.join(this.root, 'www');
+    this.platformWww = path.join(this.root, 'platform_www');
+}
+
+function getProjectName(pluginProjectXML, relative_path) {
+    var projNameElt = pluginProjectXML.find("PropertyGroup/ProjectName");
+    // Falling back on project file name in case ProjectName is missing
+    return !!projNameElt ? projNameElt.text : path.basename(relative_path, path.extname(relative_path));
+}
+
+jsprojManager.getProject = function (directory) {
+    var projectFiles = shell.ls(path.join(directory, '*.projitems'));
+    if (projectFiles.length === 0) {
+        throw (new CordovaError('The directory ' + directory +
+            ' does not appear to be a Unified Windows Store project (no .projitems file found)'));
+    }
+    return new jsprojManager(path.normalize(projectFiles[0]));
+};
+
+jsprojManager.prototype = {
+    _projects: null,
+
+    getPackageName: function() {
+        // CB-10394 Do not cache manifest file while getting package name to avoid problems
+        // with windows.appxmanifest cached twice (here and in ConfigFile module)
+        return AppxManifest.get(path.join(this.root, 'package.windows.appxmanifest'), /*ignoreCache=*/true)
+            .getProperties().getDisplayName();
+    },
+
+    write: function () {
+        this.master.write();
+        if (this._projects) {
+            var that = this;
+            this._projects.forEach(function (project) {
+                if (project !== that.master && project.touched) {
+                    project.write();
+                }
+            });
+        }
+    },
+
+    addSDKRef: function (incText, targetConditions) {
+        events.emit('verbose', 'jsprojManager.addSDKRef(incText: ' + incText + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        var item = createItemGroupElement('ItemGroup/SDKReference', incText, targetConditions);
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.appendToRoot(item);
+        });
+    },
+
+    removeSDKRef: function (incText, targetConditions) {
+        events.emit('verbose', 'jsprojManager.removeSDKRef(incText: ' + incText + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.removeItemGroupElement('ItemGroup/SDKReference', incText, targetConditions);
+        });
+    },
+
+    addResourceFileToProject: function (sourcePath, destPath, targetConditions) {
+        events.emit('verbose', 'jsprojManager.addResourceFile(sourcePath: ' + sourcePath + ', destPath: ' + destPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        // add hint path with full path
+        var link = new et.Element('Link');
+        link.text = destPath;
+        var children = [link];
+
+        var copyToOutputDirectory = new et.Element('CopyToOutputDirectory');
+        copyToOutputDirectory.text = 'Always';
+        children.push(copyToOutputDirectory);
+
+        var item = createItemGroupElement('ItemGroup/Content', sourcePath, targetConditions, children);
+
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.appendToRoot(item);
+        });
+    },
+
+    removeResourceFileFromProject: function (relPath, targetConditions) {
+        events.emit('verbose', 'jsprojManager.removeResourceFile(relPath: ' + relPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.removeItemGroupElement('ItemGroup/Content', relPath, targetConditions);
+        });
+    },
+
+    addReference: function (relPath, targetConditions, implPath) {
+        events.emit('verbose', 'jsprojManager.addReference(incText: ' + relPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        // add hint path with full path
+        var hint_path = new et.Element('HintPath');
+        hint_path.text = relPath;
+        var children = [hint_path];
+
+        var extName = path.extname(relPath);
+        if (extName === ".winmd") {
+            var mdFileTag = new et.Element("IsWinMDFile");
+            mdFileTag.text = "true";
+            children.push(mdFileTag);
+        }
+
+        // We only need to add <Implementation> tag when dll base name differs from winmd name
+        if (implPath && path.basename(relPath, '.winmd') !== path.basename(implPath, '.dll')) {
+            var implementTag = new et.Element('Implementation');
+            implementTag.text = path.basename(implPath);
+            children.push(implementTag);
+        }
+
+        var item = createItemGroupElement('ItemGroup/Reference', path.basename(relPath, extName), targetConditions, children);
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.appendToRoot(item);
+        });
+    },
+
+    removeReference: function (relPath, targetConditions) {
+        events.emit('verbose', 'jsprojManager.removeReference(incText: ' + relPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        var extName = path.extname(relPath);
+        var includeText = path.basename(relPath, extName);
+
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.removeItemGroupElement('ItemGroup/Reference', includeText, targetConditions);
+        });
+    },
+
+    addSourceFile: function (relative_path) {
+        events.emit('verbose', 'jsprojManager.addSourceFile(relative_path: ' + relative_path + ')');
+        this.master.addSourceFile(relative_path);
+    },
+
+    removeSourceFile: function (relative_path) {
+        events.emit('verbose', 'jsprojManager.removeSourceFile(incText: ' + relative_path + ')');
+        this.master.removeSourceFile(relative_path);
+    },
+
+    addProjectReference: function (relative_path, targetConditions) {
+        events.emit('verbose', 'jsprojManager.addProjectReference(incText: ' + relative_path + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        // relative_path is the actual path to the file in the current OS, where-as inserted_path is what we write in
+        // the project file, and is always in Windows format.
+        relative_path = path.normalize(relative_path);
+        var inserted_path = relative_path.split('/').join('\\');
+
+        var pluginProjectXML = xml_helpers.parseElementtreeSync(path.resolve(this.projectFolder, relative_path));
+
+        // find the guid + name of the referenced project
+        var projectGuid = pluginProjectXML.find("PropertyGroup/ProjectGuid").text;
+        var projName = getProjectName(pluginProjectXML, relative_path);
+
+        // get the project type
+        var projectTypeGuid = getProjectTypeGuid(relative_path);
+        if (!projectTypeGuid) {
+            throw new CordovaError("Unrecognized project type at " + relative_path + " (not .csproj or .vcxproj)");
+        }
+
+        var preInsertText = "\tProjectSection(ProjectDependencies) = postProject\r\n" +
+            "\t\t" + projectGuid + "=" + projectGuid + "\r\n" +
+            "\tEndProjectSection\r\n";
+        var postInsertText = '\r\nProject("' + projectTypeGuid + '") = "' +
+            projName + '", "' + inserted_path + '", ' +
+            '"' + projectGuid + '"\r\nEndProject';
+
+        var matchingProjects = this._getMatchingProjects(targetConditions);
+        if (matchingProjects.length === 0) {
+            // No projects meet the specified target and version criteria, so nothing to do.
+            return;
+        }
+
+        // Will we be writing into the .projitems file rather than individual .jsproj files?
+        var useProjItems = this.isUniversalWindowsApp && matchingProjects.length === 1 && matchingProjects[0] === this.master;
+
+        // There may be multiple solution files (for different VS versions) - process them all
+        getSolutionPaths(this.projectFolder).forEach(function (solutionPath) {
+            var solText = fs.readFileSync(solutionPath, {encoding: "utf8"});
+
+            if (useProjItems) {
+                // Insert a project dependency into every jsproj in the solution.
+                var jsProjectFound = false;
+                solText = solText.replace(JSPROJ_REGEXP, function (match) {
+                    jsProjectFound = true;
+                    return match + preInsertText;
+                });
+
+                if (!jsProjectFound) {
+                    throw new CordovaError("No jsproj found in solution");
+                }
+            } else {
+                // Insert a project dependency only for projects that match specified target and version
+                matchingProjects.forEach(function (project) {
+                    solText = solText.replace(getJsProjRegExForProject(path.basename(project.location)), function (match) {
+                        return match + preInsertText;
+                    });
+                });
+            }
+
+            // Add the project after existing projects. Note that this fairly simplistic check should be fine, since the last
+            // EndProject in the file should actually be an EndProject (and not an EndProjectSection, for example).
+            var pos = solText.lastIndexOf("EndProject");
+            if (pos === -1) {
+                throw new Error("No EndProject found in solution");
+            }
+            pos += 10; // Move pos to the end of EndProject text
+            solText = solText.slice(0, pos) + postInsertText + solText.slice(pos);
+
+            fs.writeFileSync(solutionPath, solText, {encoding: "utf8"});
+        });
+
+        // Add the ItemGroup/ProjectReference to each matching cordova project :
+        // <ItemGroup><ProjectReference Include="blahblah.csproj"/></ItemGroup>
+        var item = createItemGroupElement('ItemGroup/ProjectReference', inserted_path, targetConditions);
+        matchingProjects.forEach(function (project) {
+            project.appendToRoot(item);
+        });
+    },
+
+    removeProjectReference: function (relative_path, targetConditions) {
+        events.emit('verbose', 'jsprojManager.removeProjectReference(incText: ' + relative_path + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        // relative_path is the actual path to the file in the current OS, where-as inserted_path is what we write in
+        // the project file, and is always in Windows format.
+        relative_path = path.normalize(relative_path);
+        var inserted_path = relative_path.split('/').join('\\');
+
+        // find the guid + name of the referenced project
+        var pluginProjectXML = xml_helpers.parseElementtreeSync(path.resolve(this.projectFolder, relative_path));
+        var projectGuid = pluginProjectXML.find("PropertyGroup/ProjectGuid").text;
+        var projName = getProjectName(pluginProjectXML, relative_path);
+
+        // get the project type
+        var projectTypeGuid = getProjectTypeGuid(relative_path);
+        if (!projectTypeGuid) {
+            throw new Error("Unrecognized project type at " + relative_path + " (not .csproj or .vcxproj)");
+        }
+
+        var preInsertTextRegExp = getProjectReferencePreInsertRegExp(projectGuid);
+        var postInsertTextRegExp = getProjectReferencePostInsertRegExp(projName, projectGuid, inserted_path, projectTypeGuid);
+
+        // There may be multiple solutions (for different VS versions) - process them all
+        getSolutionPaths(this.projectFolder).forEach(function (solutionPath) {
+            var solText = fs.readFileSync(solutionPath, {encoding: "utf8"});
+
+            // To be safe (to handle subtle changes in formatting, for example), use a RegExp to find and remove
+            // preInsertText and postInsertText
+
+            solText = solText.replace(preInsertTextRegExp, function () {
+                return "";
+            });
+
+            solText = solText.replace(postInsertTextRegExp, function () {
+                return "";
+            });
+
+            fs.writeFileSync(solutionPath, solText, {encoding: "utf8"});
+        });
+
+        this._getMatchingProjects(targetConditions).forEach(function (project) {
+            project.removeItemGroupElement('ItemGroup/ProjectReference', inserted_path, targetConditions);
+        });
+    },
+
+    _getMatchingProjects: function (targetConditions) {
+        // If specified, target can be 'all' (default), 'phone' or 'windows'. Ultimately should probably allow a comma
+        // separated list, but not needed now.
+        var target = getDeviceTarget(targetConditions);
+        var versions = getVersions(targetConditions);
+
+        if (target || versions) {
+            var matchingProjects = this.projects.filter(function (project) {
+                return (!target || target === project.target) &&
+                    (!versions || semver.satisfies(project.getSemVersion(), versions, /* loose */ true));
+            });
+
+            if (matchingProjects.length < this.projects.length) {
+                return matchingProjects;
+            }
+        }
+
+        // All projects match. If this is a universal project, return the projitems file. Otherwise return our single
+        // project.
+        return [this.master];
+    },
+
+    get projects() {
+        var projects = this._projects;
+        if (!projects) {
+            projects = [];
+            this._projects = projects;
+
+            if (this.isUniversalWindowsApp) {
+                var projectPath = this.projectFolder;
+                var projectFiles = shell.ls(path.join(projectPath, '*.jsproj'));
+                projectFiles.forEach(function (projectFile) {
+                    projects.push(new jsproj(projectFile));
+                });
+            } else {
+                this.projects.push(this.master);
+            }
+        }
+
+        return projects;
+    }
+};
+
+jsprojManager.prototype.getInstaller = function (type) {
+    return PluginHandler.getInstaller(type);
+};
+
+jsprojManager.prototype.getUninstaller = function (type) {
+    return PluginHandler.getUninstaller(type);
+};
+
+function getProjectReferencePreInsertRegExp(projectGuid) {
+    projectGuid = escapeRegExpString(projectGuid);
+    return new RegExp("\\s*ProjectSection\\(ProjectDependencies\\)\\s*=\\s*postProject\\s*" + projectGuid + "\\s*=\\s*" + projectGuid + "\\s*EndProjectSection", "gi");
+}
+
+function getProjectReferencePostInsertRegExp(projName, projectGuid, relative_path, projectTypeGuid) {
+    projName = escapeRegExpString(projName);
+    projectGuid = escapeRegExpString(projectGuid);
+    relative_path = escapeRegExpString(relative_path);
+    projectTypeGuid = escapeRegExpString(projectTypeGuid);
+    return new RegExp('\\s*Project\\("' + projectTypeGuid + '"\\)\\s*=\\s*"' + projName + '"\\s*,\\s*"' + relative_path + '"\\s*,\\s*"' + projectGuid + '"\\s*EndProject', 'gi');
+}
+
+function getSolutionPaths(projectFolder) {
+    return shell.ls(path.join(projectFolder, "*.sln"));
+}
+
+function escapeRegExpString(regExpString) {
+    return regExpString.replace(ESCAPE_REGEXP, "\\$1");
+}
+
+function getJsProjRegExForProject(projectFile) {
+    projectFile = escapeRegExpString(projectFile);
+    return new RegExp('(Project\\("\\{262852C6-CD72-467D-83FE-5EEB1973A190}"\\)\\s*=\\s*"[^"]+",\\s*"' + projectFile + '",\\s*"\\{[0-9a-f\\-]+}"[^\\r\\n]*[\\r\\n]*)', 'gi');
+}
+
+function getProjectTypeGuid(projectPath) {
+    switch (path.extname(projectPath)) {
+        case ".vcxproj":
+            return WinCplusplusProjectTypeGUID;
+
+        case ".csproj":
+            return WinCSharpProjectTypeGUID;
+    }
+    return null;
+}
+
+function createItemGroupElement(path, incText, targetConditions, children) {
+    path = path.split('/');
+    path.reverse();
+
+    var lastElement = null;
+    path.forEach(function (elementName) {
+        var element = new et.Element(elementName);
+        if (lastElement) {
+            element.append(lastElement);
+        } else {
+            element.attrib.Include = incText;
+
+            var condition = createConditionAttrib(targetConditions);
+            if (condition) {
+                element.attrib.Condition = condition;
+            }
+
+            if (children) {
+                children.forEach(function (child) {
+                    element.append(child);
+                });
+            }
+        }
+        lastElement = element;
+    });
+
+    return lastElement;
+}
+
+function getDeviceTarget(targetConditions) {
+    var target = targetConditions.deviceTarget;
+    if (target) {
+        target = target.toLowerCase().trim();
+        if (target === "all") {
+            target = null;
+        } else if (target === "win") {
+            // Allow "win" as alternative to "windows"
+            target = "windows";
+        } else if (target !== 'phone' && target !== 'windows') {
+            throw new Error('Invalid device-target attribute (must be "all", "phone", "windows" or "win"): ' + target);
+        }
+    }
+    return target;
+}
+
+function getVersions(targetConditions) {
+    var versions = targetConditions.versions;
+    if (versions && !semver.validRange(versions, /* loose */ true)) {
+        throw new Error('Invalid versions attribute (must be a valid semantic version range): ' + versions);
+    }
+    return versions;
+}
+
+
+/* proj */
+
+function proj(location) {
+    // Class to handle simple project xml operations
+    if (!location) {
+        throw new Error('Project file location can\'t be null or empty');
+    }
+    this.location = location;
+    this.xml = xml_helpers.parseElementtreeSync(location);
+}
+
+proj.prototype = {
+    write: function () {
+        fs.writeFileSync(this.location, this.xml.write({indent: 4}), 'utf-8');
+    },
+
+    appendToRoot: function (element) {
+        this.touched = true;
+        this.xml.getroot().append(element);
+    },
+
+    removeItemGroupElement: function (path, incText, targetConditions) {
+        var xpath = path + '[@Include="' + incText + '"]';
+        var condition = createConditionAttrib(targetConditions);
+        if (condition) {
+            xpath += '[@Condition="' + condition + '"]';
+        }
+        xpath += '/..';
+
+        var itemGroup = this.xml.find(xpath);
+        if (itemGroup) {
+            this.touched = true;
+            this.xml.getroot().remove(itemGroup);
+        }
+    },
+
+    addSourceFile: function (relative_path) {
+        // we allow multiple paths to be passed at once as array so that
+        // we don't create separate ItemGroup for each source file, CB-6874
+        if (!(relative_path instanceof Array)) {
+            relative_path = [relative_path];
+        }
+
+        // make ItemGroup to hold file.
+        var item = new et.Element('ItemGroup');
+
+        relative_path.forEach(function (filePath) {
+            // filePath is never used to find the actual file - it determines what we write to the project file, and so
+            // should always be in Windows format.
+            filePath = filePath.split('/').join('\\');
+
+            var content = new et.Element('Content');
+            content.attrib.Include = filePath;
+            item.append(content);
+        });
+
+        this.appendToRoot(item);
+    },
+
+    removeSourceFile: function (relative_path) {
+        var isRegexp = relative_path instanceof RegExp;
+        if (!isRegexp) {
+            // relative_path is never used to find the actual file - it determines what we write to the project file,
+            // and so should always be in Windows format.
+            relative_path = relative_path.split('/').join('\\');
+        }
+
+        var root = this.xml.getroot();
+        var that = this;
+        // iterate through all ItemGroup/Content elements and remove all items matched
+        this.xml.findall('ItemGroup').forEach(function (group) {
+            // matched files in current ItemGroup
+            var filesToRemove = group.findall('Content').filter(function (item) {
+                if (!item.attrib.Include) {
+                    return false;
+                }
+                return isRegexp ? item.attrib.Include.match(relative_path) : item.attrib.Include === relative_path;
+            });
+
+            // nothing to remove, skip..
+            if (filesToRemove.length < 1) {
+                return;
+            }
+
+            filesToRemove.forEach(function (file) {
+                // remove file reference
+                group.remove(file);
+            });
+            // remove ItemGroup if empty
+            if (group.findall('*').length < 1) {
+                that.touched = true;
+                root.remove(group);
+            }
+        });
+    }
+};
+
+
+/* jsproj */
+
+function jsproj(location) {
+    function targetPlatformIdentifierToDevice(jsprojPlatform) {
+        var index = ["Windows", "WindowsPhoneApp", "UAP"].indexOf(jsprojPlatform);
+        if (index < 0) {
+            throw new Error("Unknown TargetPlatformIdentifier '" + jsprojPlatform + "' in project file '" + location + "'");
+        }
+        return ["windows", "phone", "windows"][index];
+    }
+
+    function validateVersion(version) {
+        version = version.split('.');
+        while (version.length < 3) {
+            version.push("0");
+        }
+        return version.join(".");
+    }
+
+    // Class to handle a jsproj file
+    proj.call(this, location);
+
+    var propertyGroup = this.xml.find('PropertyGroup[TargetPlatformIdentifier]');
+    if (!propertyGroup) {
+        throw new Error("Unable to find PropertyGroup/TargetPlatformIdentifier in project file '" + this.location + "'");
+    }
+
+    var jsprojPlatform = propertyGroup.find('TargetPlatformIdentifier').text;
+    this.target = targetPlatformIdentifierToDevice(jsprojPlatform);
+
+    var version = propertyGroup.find('TargetPlatformVersion');
+    if (!version) {
+        throw new Error("Unable to find PropertyGroup/TargetPlatformVersion in project file '" + this.location + "'");
+    }
+    this.version = validateVersion(version.text);
+}
+
+util.inherits(jsproj, proj);
+
+jsproj.prototype.target = null;
+jsproj.prototype.version = null;
+
+// Returns valid semantic version (http://semver.org/).
+jsproj.prototype.getSemVersion = function () {
+    // For example, for version 10.0.10240.0 we will return 10.0.10240 (first three components)
+    var semVersion = this.version;
+    var splittedVersion = semVersion.split('.');
+    if (splittedVersion.length > 3) {
+        semVersion = splittedVersion.splice(0, 3).join('.');
+    }
+
+    return semVersion;
+    // Alternative approach could be replacing last dot with plus sign to
+    // be complaint w/ semver specification, for example
+    // 10.0.10240.0 -> 10.0.10240+0
+};
+
+/* Common support functions */
+
+function createConditionAttrib(targetConditions) {
+    var arch = targetConditions.arch;
+    if (arch) {
+        if (arch === "arm") {
+            // Specifcally allow "arm" as alternative to "ARM"
+            arch = "ARM";
+        } else if (arch !== "x86" && arch !== "x64" && arch !== "ARM") {
+            throw new Error('Invalid arch attribute (must be "x86", "x64" or "ARM"): ' + arch);
+        }
+        return "'$(Platform)'=='" + arch + "'";
+    }
+    return null;
+}
+
+
+module.exports = jsprojManager;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js
----------------------------------------------------------------------
diff --git a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js
new file mode 100644
index 0000000..c264f20
--- /dev/null
+++ b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginHandler.js
@@ -0,0 +1,282 @@
+/*
+ *
+ * Copyright 2013 Jesse MacFadyen
+ *
+ * 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 shell = require('shelljs');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+
+// returns relative file path for a file in the plugin's folder that can be referenced
+// from a project file.
+function getPluginFilePath(plugin, pluginFile, targetDir) {
+    var src = path.resolve(plugin.dir, pluginFile);
+    return '$(ProjectDir)' + path.relative(targetDir, src);
+}
+
+var handlers = {
+    'source-file': {
+        install:function(obj, plugin, project, options) {
+            var dest = path.join('plugins', plugin.id, obj.targetDir || '', path.basename(obj.src));
+            if (options && options.force) {
+                copyFile(plugin.dir, obj.src, project.root, dest);
+            } else {
+                copyNewFile(plugin.dir, obj.src, project.root, dest);
+            }
+            // add reference to this file to jsproj.
+            project.addSourceFile(dest);
+        },
+        uninstall:function(obj, plugin, project, options) {
+            var dest = path.join('plugins', plugin.id, obj.targetDir || '', path.basename(obj.src));
+            removeFile(project.root, dest);
+            // remove reference to this file from csproj.
+            project.removeSourceFile(dest);
+        }
+    },
+    'resource-file':{
+        install:function(obj, plugin, project, options) {
+            if (obj.reference) {
+                // do not copy, but reference the file in the plugin folder. This allows to
+                // have multiple source files map to the same target and select the appropriate
+                // one based on the current build settings, e.g. architecture.
+                // also, we don't check for existence. This allows to insert build variables
+                // into the source file name, e.g.
+                // <resource-file src="$(Platform)/My.dll" target="My.dll" />
+                var relativeSrcPath = getPluginFilePath(plugin, obj.src, project.projectFolder);
+                project.addResourceFileToProject(relativeSrcPath, obj.target, getTargetConditions(obj));
+            } else {
+                // if target already exists, emit warning to consider using a reference instead of copying
+                if (fs.existsSync(path.resolve(project.root, obj.target))) {
+                    events.emit('warn', '<resource-file> with target ' + obj.target + ' already exists and will be overwritten ' +
+                    'by a <resource-file> with the same target. Consider using the attribute reference="true" in the ' +
+                    '<resource-file> tag to avoid overwriting files with the same target. Using reference will not copy files ' +
+                    'to the destination, instead will create a reference to the source path.');
+                }
+                // as per specification resource-file target is specified relative to platform root
+                copyFile(plugin.dir, obj.src, project.root, obj.target);
+                project.addResourceFileToProject(obj.target, obj.target, getTargetConditions(obj));
+            }
+        },
+        uninstall:function(obj, plugin, project, options) {
+            if (obj.reference) {
+                var relativeSrcPath = getPluginFilePath(plugin, obj.src, project.projectFolder);
+                project.removeResourceFileFromProject(relativeSrcPath, getTargetConditions(obj));
+            } else {
+                removeFile(project.root, obj.target);
+                project.removeResourceFileFromProject(obj.target, getTargetConditions(obj));
+            }
+        }
+    },
+    'lib-file': {
+        install:function(obj, plugin, project, options) {
+            var inc  = obj.Include || obj.src;
+            project.addSDKRef(inc, getTargetConditions(obj));
+        },
+        uninstall:function(obj, plugin, project, options) {
+            events.emit('verbose', 'windows lib-file uninstall :: ' + plugin.id);
+            var inc = obj.Include || obj.src;
+            project.removeSDKRef(inc, getTargetConditions(obj));
+        }
+    },
+    'framework': {
+        install:function(obj, plugin, project, options) {
+            events.emit('verbose', 'windows framework install :: ' + plugin.id);
+
+            var src = obj.src;
+            var dest = src;
+            var type = obj.type;
+            var targetDir = obj.targetDir || '';
+            var implementPath = obj.implementation;
+
+            if(type === 'projectReference') {
+                dest = path.join(path.relative(project.projectFolder, plugin.dir), targetDir, src);
+                project.addProjectReference(dest, getTargetConditions(obj));
+            } else {
+                // path.join ignores empty paths passed so we don't check whether targetDir is not empty
+                dest = path.join('plugins', plugin.id, targetDir, path.basename(src));
+                copyFile(plugin.dir, src, project.root, dest);
+                if (implementPath) {
+                    copyFile(plugin.dir, implementPath, project.root, path.join(path.dirname(dest), path.basename(implementPath)));
+                }
+                project.addReference(dest, getTargetConditions(obj), implementPath);
+            }
+
+        },
+        uninstall:function(obj, plugin, project, options) {
+            events.emit('verbose', 'windows framework uninstall :: ' + plugin.id  );
+
+            var src = obj.src;
+            var type = obj.type;
+
+            if(type === 'projectReference') {
+                project.removeProjectReference(path.join(path.relative(project.projectFolder, plugin.dir), src), getTargetConditions(obj));
+            }
+            else {
+                var targetPath = path.join('plugins', plugin.id);
+                removeFile(project.root, targetPath);
+                project.removeReference(src, getTargetConditions(obj));
+            }
+        }
+    },
+    asset:{
+        install:function(obj, plugin, project, options) {
+            if (!obj.src) {
+                throw new CordovaError(generateAttributeError('src', 'asset', plugin.id));
+            }
+            if (!obj.target) {
+                throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
+            }
+
+            copyFile(plugin.dir, obj.src, project.www, obj.target);
+            if (options && options.usePlatformWww) copyFile(plugin.dir, obj.src, project.platformWww, obj.target);
+        },
+        uninstall:function(obj, plugin, project, options) {
+            var target = obj.target || obj.src;
+
+            if (!target) throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
+
+            removeFile(project.www, target);
+            removeFile(project.www, path.join('plugins', plugin.id));
+            if (options && options.usePlatformWww) {
+                removeFile(project.platformWww, target);
+                removeFile(project.platformWww, path.join('plugins', plugin.id));
+            }
+        }
+    },
+    'js-module': {
+        install: function (obj, plugin, project, options) {
+            // Copy the plugin's files into the www directory.
+            var moduleSource = path.resolve(plugin.dir, obj.src);
+            var moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname (obj.src)));
+
+            // Read in the file, prepend the cordova.define, and write it back out.
+            var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
+            if (moduleSource.match(/.*\.json$/)) {
+                scriptContent = 'module.exports = ' + scriptContent;
+            }
+            scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
+
+            var moduleDestination = path.resolve(project.www, 'plugins', plugin.id, obj.src);
+            shell.mkdir('-p', path.dirname(moduleDestination));
+            fs.writeFileSync(moduleDestination, scriptContent, 'utf-8');
+            if (options && options.usePlatformWww) {
+                var platformWwwDestination = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src);
+                shell.mkdir('-p', path.dirname(platformWwwDestination));
+                fs.writeFileSync(platformWwwDestination, scriptContent, 'utf-8');
+            }
+        },
+        uninstall: function (obj, plugin, project, options) {
+            var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
+            removeFileAndParents(project.www, pluginRelativePath);
+            if (options && options.usePlatformWww) removeFileAndParents(project.platformWww, pluginRelativePath);
+        }
+    }
+};
+
+// Helpers from common
+
+module.exports.getInstaller = function (type) {
+    if (handlers[type] && handlers[type].install) {
+        return handlers[type].install;
+    }
+
+    events.emit('verbose', '<' + type + '> is not supported for Windows plugins');
+};
+
+module.exports.getUninstaller = function(type) {
+    if (handlers[type] && handlers[type].uninstall) {
+        return handlers[type].uninstall;
+    }
+
+    events.emit('verbose', '<' + type + '> is not supported for Windows plugins');
+};
+
+function getTargetConditions(obj) {
+    return { versions: obj.versions, deviceTarget: obj.deviceTarget, arch: obj.arch };
+}
+
+function copyFile (plugin_dir, src, project_dir, dest, link) {
+    src = path.resolve(plugin_dir, src);
+    if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');
+
+    // check that src path is inside plugin directory
+    var real_path = fs.realpathSync(src);
+    var real_plugin_path = fs.realpathSync(plugin_dir);
+    if (real_path.indexOf(real_plugin_path) !== 0)
+        throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"');
+
+    dest = path.resolve(project_dir, dest);
+
+    // check that dest path is located in project directory
+    if (dest.indexOf(path.resolve(project_dir)) !== 0)
+        throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project');
+
+    shell.mkdir('-p', path.dirname(dest));
+
+    if (link) {
+        fs.symlinkSync(path.relative(path.dirname(dest), src), dest);
+    } else if (fs.statSync(src).isDirectory()) {
+        // XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
+        shell.cp('-Rf', src+'/*', dest);
+    } else {
+        shell.cp('-f', src, dest);
+    }
+}
+
+// Same as copy file but throws error if target exists
+function copyNewFile (plugin_dir, src, project_dir, dest, link) {
+    var target_path = path.resolve(project_dir, dest);
+    if (fs.existsSync(target_path))
+        throw new CordovaError('"' + target_path + '" already exists!');
+
+    copyFile(plugin_dir, src, project_dir, dest, !!link);
+}
+
+// checks if file exists and then deletes. Error if doesn't exist
+function removeFile (project_dir, src) {
+    var file = path.resolve(project_dir, src);
+    shell.rm('-Rf', file);
+}
+
+function removeFileAndParents (baseDir, destFile, stopper) {
+    stopper = stopper || '.';
+    var file = path.resolve(baseDir, destFile);
+    if (!fs.existsSync(file)) return;
+
+    shell.rm('-rf', file);
+
+    // check if directory is empty
+    var curDir = path.dirname(file);
+
+    while(curDir !== path.resolve(baseDir, stopper)) {
+        if(fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
+            fs.rmdirSync(curDir);
+            curDir = path.resolve(curDir, '..');
+        } else {
+            // directory not empty...do nothing
+            break;
+        }
+    }
+}
+
+function generateAttributeError(attribute, element, id) {
+    return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id;
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js
----------------------------------------------------------------------
diff --git a/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js
new file mode 100644
index 0000000..78c9593
--- /dev/null
+++ b/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/lib/PluginInfo.js
@@ -0,0 +1,139 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+var semver = require('semver');
+var CommonPluginInfo = require('cordova-common').PluginInfo;
+
+var MANIFESTS = {
+    'windows': {
+        '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.1.0': ['package.windows.appxmanifest', 'package.phone.appxmanifest'],
+        '10.0.0': 'package.windows10.appxmanifest'
+    }
+};
+
+var SUBSTS = ['package.phone.appxmanifest', 'package.windows.appxmanifest', 'package.windows10.appxmanifest'];
+var TARGETS = ['windows', 'phone', 'all'];
+
+function processChanges(changes) {
+    var hasManifestChanges  = changes.some(function(change) {
+        return change.target === 'package.appxmanifest';
+    });
+
+    if (!hasManifestChanges) {
+        return changes;
+    }
+
+    // Demux 'package.appxmanifest' into relevant platform-specific appx manifests.
+    // Only spend the cycles if there are version-specific plugin settings
+    var oldChanges = changes;
+    changes = [];
+
+    oldChanges.forEach(function(change) {
+        // 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 manifestsForChange = getManifestsForChange(change);
+        changes = changes.concat(demuxChangeWithSubsts(change, manifestsForChange));
+    });
+
+    return changes;
+}
+
+function demuxChangeWithSubsts(change, manifestFiles) {
+    return manifestFiles.map(function(file) {
+         return createReplacement(file, change);
+    });
+}
+
+function getManifestsForChange(change) {
+    var hasTarget = (typeof change.deviceTarget !== 'undefined');
+    var hasVersion = (typeof change.versions !== 'undefined');
+
+    var targetDeviceSet = hasTarget ? change.deviceTarget : 'all';
+
+    if (TARGETS.indexOf(targetDeviceSet) === -1) {
+        // target-device couldn't be resolved, fix it up here to a valid value
+        targetDeviceSet = 'all';
+    }
+
+    // No semver/device-target for this config-file, pass it through
+    if (!(hasTarget || hasVersion)) {
+        return SUBSTS;
+    }
+
+    var knownWindowsVersionsForTargetDeviceSet = Object.keys(MANIFESTS[targetDeviceSet]);
+    return knownWindowsVersionsForTargetDeviceSet.reduce(function(manifestFiles, winver) {
+        if (hasVersion && !semver.satisfies(winver, change.versions)) {
+            return manifestFiles;
+        }
+        return manifestFiles.concat(MANIFESTS[targetDeviceSet][winver]);
+    }, []);
+}
+
+// This is a local function that creates the new replacement representing the
+// mutation.  Used to save code further down.
+function createReplacement(manifestFile, originalChange) {
+    var replacement = {
+        target:         manifestFile,
+        parent:         originalChange.parent,
+        after:          originalChange.after,
+        xmls:           originalChange.xmls,
+        versions:       originalChange.versions,
+        deviceTarget:   originalChange.deviceTarget
+    };
+    return replacement;
+}
+
+
+/*
+A class for holidng the information currently stored in plugin.xml
+It's inherited from cordova-common's PluginInfo class
+In addition it overrides getConfigFiles, getEditConfigs, getFrameworks methods to use windows-specific logic
+ */
+function PluginInfo(dirname) {
+    //  We're not using `util.inherit' because original PluginInfo defines
+    //  its' methods inside of constructor
+    CommonPluginInfo.apply(this, arguments);
+    var parentGetConfigFiles = this.getConfigFiles;
+    var parentGetEditConfigs = this.getEditConfigs;
+
+    this.getEditConfigs = function(platform) {
+        var editConfigs = parentGetEditConfigs(platform);
+        return processChanges(editConfigs);
+    };
+
+    this.getConfigFiles = function(platform) {
+        var configFiles = parentGetConfigFiles(platform);
+        return processChanges(configFiles);
+    };
+}
+
+exports.PluginInfo = PluginInfo;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec/cordova/fixtures/basePkgJson4/config.xml
----------------------------------------------------------------------
diff --git a/spec/cordova/fixtures/basePkgJson4/config.xml b/spec/cordova/fixtures/basePkgJson4/config.xml
index b39102b..d1141a1 100644
--- a/spec/cordova/fixtures/basePkgJson4/config.xml
+++ b/spec/cordova/fixtures/basePkgJson4/config.xml
@@ -11,5 +11,5 @@
     <access origin="*" />
     <preference name="fullscreen" value="true" />
     <preference name="webviewbounce" value="true" />
-    <engine name="android" spec="2.1.0"/>
+    <engine name="android" spec="6.0.0"/>
 </widget>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec/cordova/fixtures/basePkgJson4/package.json
----------------------------------------------------------------------
diff --git a/spec/cordova/fixtures/basePkgJson4/package.json b/spec/cordova/fixtures/basePkgJson4/package.json
index 10248f3..5079224 100644
--- a/spec/cordova/fixtures/basePkgJson4/package.json
+++ b/spec/cordova/fixtures/basePkgJson4/package.json
@@ -4,7 +4,7 @@
     "description": "",
     "main": "index.js",
     "dependencies": {
-        "cordova-android": "^3.1.0",
+        "cordova-android": "^5.0.0",
         "cordova-browser": "^4.1.0"
     },
     "devDependencies": {},

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/60bbd8bc/spec/cordova/fixtures/platforms/cordova-browser/package.json
----------------------------------------------------------------------
diff --git a/spec/cordova/fixtures/platforms/cordova-browser/package.json b/spec/cordova/fixtures/platforms/cordova-browser/package.json
index 3daf7d4..dcc7b67 100644
--- a/spec/cordova/fixtures/platforms/cordova-browser/package.json
+++ b/spec/cordova/fixtures/platforms/cordova-browser/package.json
@@ -2,7 +2,6 @@
     "name": "cordova-browser",
     "version": "4.2.0-dev",
     "description": "cordova-browser release",
-    "main": "bin/create",
     "repository": {
         "type": "git",
         "url": "https://git-wip-us.apache.org/repos/asf/cordova-browser.git"


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