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:26 UTC
[3/4] cordova-lib git commit: CB-12870 : rebased and updated paths
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