You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by an...@apache.org on 2015/12/11 10:02:42 UTC

[03/49] cordova-windows git commit: CB-9828 Implement and expose PlatformApi for Windows

http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/58047a3d/template/cordova/lib/AppxManifest.js
----------------------------------------------------------------------
diff --git a/template/cordova/lib/AppxManifest.js b/template/cordova/lib/AppxManifest.js
new file mode 100644
index 0000000..461ac64
--- /dev/null
+++ b/template/cordova/lib/AppxManifest.js
@@ -0,0 +1,595 @@
+/**
+    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 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
+ * @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
+ * @return  {AppxManifest|Win10AppxManifest}  Manifest instance
+ */
+AppxManifest.get = function (fileName) {
+
+    if (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;
+    return (manifestCache[fileName] = new Manifest(fileName, prefix));
+};
+
+AppxManifest.prototype.getPhoneIdentity = function () {
+    var phoneIdentity = this.doc.getroot().find('./mp:PhoneIdentity');
+    if (!phoneIdentity)
+        throw new Error('Failed to find PhoneIdentity element.');
+
+    return {
+        getPhoneProductId: function () {
+            return phoneIdentity.attrib.PhoneProductId;
+        },
+        setPhoneProductId: function (id) {
+            if (!id) throw new Error('Argument for "setPhoneProductId" must be defined');
+            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');
+            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');
+            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');
+
+            // 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');
+            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');
+            var publisher = properties.find('./PublisherDisplayName');
+
+            if (!publisher) {
+                publisher = new et.Element('PublisherDisplayName');
+                properties.append(publisher);
+            }
+
+            publisher.text = name;
+
+            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 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');
+            // 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 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');
+            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');
+
+            visualElements.attrib.BackgroundColor = refineColor(color);
+            return this;
+        },
+        trySetBackgroundColor: function (color) {
+            try {
+                return this.setBackgroundColor(color);
+            } catch (e) { 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 (color && splashNode) {
+                splashNode.attrib.BackgroundColor = refineColor(color);
+            } else if (!color) {
+                delete splashNode.attrib.BackgroundColor;
+            }
+            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;
+        }
+    };
+};
+
+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 refineColor(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;
+}
+
+// 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');
+                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; };
+
+    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) {
+    ensureUapPrefixedCapabilities(this.doc.find('.//Capabilities'));
+    // 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');
+};
+
+/**
+ * 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;
+        }
+    });
+}
+
+module.exports = AppxManifest;

http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/58047a3d/template/cordova/lib/ConfigParser.js
----------------------------------------------------------------------
diff --git a/template/cordova/lib/ConfigParser.js b/template/cordova/lib/ConfigParser.js
index 0fe4b4c..c9ce6a8 100644
--- a/template/cordova/lib/ConfigParser.js
+++ b/template/cordova/lib/ConfigParser.js
@@ -17,213 +17,167 @@
     under the License.
 */
 
-/* jshint node:true, bitwise:true, undef:true, trailing:true, quotmark:true,
-          indent:4, unused:vars, latedef:nofunc,
-          sub:true
-*/
+/* jshint sub:true */
 
-var et = require('elementtree'),
-    fs = require('fs');
-
-/** Wraps a config.xml file */
-function ConfigParser(path) {
-    this.path = path;
-    try {
-        var contents = fs.readFileSync(path, 'utf-8');
-        if(contents) {
-            //Windows is the BOM. Skip the Byte Order Mark.
-            contents = contents.substring(contents.indexOf('<'));
-        }
-        this.doc = new et.ElementTree(et.XML(contents));
+var util = require('util');
+var Version = require('./Version');
+var ConfigParser = require('cordova-common').ConfigParser;
 
-    } catch (e) {
-        console.error('Parsing '+path+' failed');
-        throw e;
-    }
-    var r = this.doc.getroot();
-    if (r.tag !== 'widget') {
-        throw new Error(path + ' has incorrect root node name (expected "widget", was "' + r.tag + '")');
-    }
-}
+var BASE_UAP_VERSION    = new Version(10, 0, 10240, 0);
 
-function getNodeTextSafe(el) {
-    return el && el.text && el.text.trim();
+/**
+ * A wrapper arount config.xml file, based on cordova-common implementation,
+ *   extended with some windows-specific methods.
+ *
+ * @constructor
+ * @extends {ConfigParser}
+ *
+ * @param  {String}  path  Path to config.xml file
+ */
+function WindowsConfigParser(path) {
+    ConfigParser.call(this, path);
 }
 
-function findOrCreate(doc, name) {
-    var ret = doc.find(name);
-    if (!ret) {
-        ret = new et.Element(name);
-        doc.getroot().append(ret);
+util.inherits(WindowsConfigParser, ConfigParser);
+
+WindowsConfigParser.prototype.startPage = function() {
+    var content = this.doc.find('content');
+    if (content) {
+        return content.attrib.src;
     }
-    return ret;
-}
+    return null;
+};
+
+WindowsConfigParser.prototype.windows_packageVersion = function() {
+    return this.doc.getroot().attrib['windows-packageVersion'];
+};
 
-ConfigParser.prototype = {
-    packageName: function(id) {
-        return this.doc.getroot().attrib['id'];
-    },
-    setPackageName: function(id) {
-        this.doc.getroot().attrib['id'] = id;
-    },
-    name: function() {
-        return getNodeTextSafe(this.doc.find('name'));
-    },
-    setName: function(name) {
-        var el = findOrCreate(this.doc, 'name');
-        el.text = name;
-    },
-    startPage: function() {
-        var content = this.doc.find('content');
-        if (content) {
-            return content.attrib.src;
+WindowsConfigParser.prototype.getMatchingPreferences = function(regexp) {
+    var preferences = this.doc.findall('preference');
+    var result = [];
+    preferences.forEach(function(preference) {
+        if (regexp.test(preference.attrib.name)) {
+            result.push({ name: preference.attrib.name, value: preference.attrib.value });
         }
-        return null;
-    },
-    description: function() {
-        return this.doc.find('description').text.trim();
-    },
-    setDescription: function(text) {
-        var el = findOrCreate(this.doc, 'description');
-        el.text = text;
-    },
-    version: function() {
-        return this.doc.getroot().attrib['version'];
-    },
-    windows_packageVersion: function() {
-        return this.doc.getroot().attrib['windows-packageVersion'];
-    },
-    android_versionCode: function() {
-        return this.doc.getroot().attrib['android-versionCode'];
-    },
-    ios_CFBundleVersion: function() {
-        return this.doc.getroot().attrib['ios-CFBundleVersion'];
-    },
-    setVersion: function(value) {
-        this.doc.getroot().attrib['version'] = value;
-    },
-    author: function() {
-        return getNodeTextSafe(this.doc.find('author'));
-    },
-    getPreference: function(name) {
-        var preferences = this.doc.findall('preference');
-        var ret = null;
-        preferences.forEach(function (preference) {
-            // Take the last one that matches.
-            if (preference.attrib.name.toLowerCase() === name.toLowerCase()) {
-                ret = preference.attrib.value;
-            }
-        });
-        return ret;
-    },
-    getMatchingPreferences: function(regexp) {
-        var preferences = this.doc.findall('preference');
-        var result = [];
-        preferences.forEach(function(preference) {
-            if (regexp.test(preference.attrib.name)) {
-                result.push({ name: preference.attrib.name, value: preference.attrib.value });
-            }
-        });
+    });
 
-        return result;
-    },
-    /**
-     * Returns all resources.
-     * @param {string}  resourceName Type of static resources to return.
-     *                               "icon" and "splash" currently supported.
-     * @return {Array}               Resources for the platform specified.
-     */
-    getStaticResources: function(resourceName) {
-        return this.doc.findall(resourceName).map(function (elt) {
-            var res = {};
-            res.src = elt.attrib.src;
-            res.target = elt.attrib.target;
-            res.density = elt.attrib['density'] || elt.attrib['cdv:density'] || elt.attrib['gap:density'];
-            res.platform = elt.platform || null; // null means icon represents default icon (shared between platforms)
-            res.width = elt.attrib.width;
-            res.height = elt.attrib.height;
-
-            return res;
-        });
-    },
-
-    /**
-     * Returns all defined icons.
-     * @return {Resource[]}      Array of icon objects.
-     */
-    getIcons: function() {
-        return this.getStaticResources('icon');
-    },
-
-    /**
-     * Returns all defined splash images.
-     * @return {Resource[]}      Array of Splash objects.
-     */
-    getSplashScreens: function() {
-        return this.getStaticResources('splash');
-    },
-
-    /**
-     * Returns all access rules.
-     * @return {string[]}      Array of access rules.
-     */
-    getAccessRules: function() { 
-        var rules = this.doc.getroot().findall('access'); 
-        var ret = []; 
-        rules.forEach(function (rule) { 
-         if (rule.attrib.origin) {
-             ret.push(rule.attrib.origin); 
-         } 
-        }); 
-        return ret; 
-    },
-    
-    /**
-     * Returns all <allow-navigation> rules.
-     * @return {string[]} Array of allow-navigation rules.
-     */
-    getNavigationWhitelistRules: function() {
-        var rules = this.doc.getroot().findall('allow-navigation');
-        var result = [];
-        rules.forEach(function(rule) {
-            if (rule.attrib.href) {
-                result.push(rule.attrib.href);
-            }
-        });
+    return result;
+};
+
+WindowsConfigParser.prototype.getWindowsTargetVersion = function() {
+    var preference = this.getPreference('windows-target-version');
 
-        return result;
-    },
+    if (!preference)
+        preference = '8.1'; // default is 8.1.
 
-    getWindowsTargetVersion: function() {
-        var preference = this.getPreference('windows-target-version');
+    return preference;
+};
+
+WindowsConfigParser.prototype.getWindowsPhoneTargetVersion = function() {
+    // This is a little more complicated than the previous one.
+    // 1. Check for an explicit preference.  If the preference is set explicitly, return that, irrespective of whether it is valid
+    // 2. Get the Windows baseline version.  If it's equivalent to 8.0, bump it to 8.1.
+    // 3. Return the Windows baseline version.
+    var explicitPreference = this.getPreference('windows-phone-target-version');
+    if (explicitPreference)
+        return explicitPreference;
 
-        if (!preference) 
-            preference = '8.1'; // default is 8.1.
+    var windowsTargetVersion = this.getWindowsTargetVersion();
+    if (windowsTargetVersion === '8' || windowsTargetVersion === '8.0')
+        windowsTargetVersion = '8.1';
 
-        return preference;
-    },
+    return windowsTargetVersion;
+};
+
+/**
+ * Gets min/max UAP versions from the configuration. If no version preferences
+ *   are in the configuration file, this will provide Windows.Universal at
+ *   BASE_UAP_VERSION for both min and max. This will always return a rational
+ *   object or will fail; for example, if a platform expects a higher
+ *   min-version than max-version, it will raise the max version to the min
+ *   version.
+ *
+ * @return {Object[]} An array of objects in the shape of:
+ *   [ {'Name': 'Windows.Mobile', 'MinVersion': Version, 'MaxVersion': Version } ] (where
+ *   Version is a Version object)
+ *
+ * @exception {RangeError} Thrown if a Version string is badly formed.
+ */
+WindowsConfigParser.prototype.getAllMinMaxUAPVersions = function () {
+    var uapVersionPreferenceTest = /(Microsoft.+?|Windows.+?)\-(MinVersion|MaxVersionTested)/i;
+    var platformBag = Object.create(null);
+
+    this.getMatchingPreferences(uapVersionPreferenceTest)
+    .forEach(function(verPref) {
+        var matches = uapVersionPreferenceTest.exec(verPref.name);
+        // 'matches' should look like: ['Windows.Universal-MinVersion', 'Windows.Universal', 'MinVersion']
+        var platformName = matches[1];
+        var versionPropertyName = matches[2];
+
+        var platformVersionSet = platformBag[platformName];
+        if (typeof platformVersionSet === 'undefined') {
+            platformVersionSet = { };
+            platformBag[platformName] = platformVersionSet;
+        }
 
-    getWindowsPhoneTargetVersion: function() {
-        // This is a little more complicated than the previous one.
-        // 1. Check for an explicit preference.  If the preference is set explicitly, return that, irrespective of whether it is valid
-        // 2. Get the Windows baseline version.  If it's equivalent to 8.0, bump it to 8.1.
-        // 3. Return the Windows baseline version.
-        var explicitPreference = this.getPreference('windows-phone-target-version');
-        if (explicitPreference)
-            return explicitPreference;
+        var versionTest = Version.tryParse(verPref.value);
+        if (!versionTest) {
+            throw new RangeError('Could not comprehend a valid version from the string "' + verPref.value + '" of platform-boundary "' + verPref.name + '".');
+        }
 
-        var windowsTargetVersion = this.getWindowsTargetVersion();
-        if (windowsTargetVersion === '8' || windowsTargetVersion === '8.0')
-            windowsTargetVersion = '8.1';
+        platformVersionSet[versionPropertyName] = versionTest;
+    });
 
-        return windowsTargetVersion;
+    for (var platformName in platformBag) {
+        // Go through each and make sure there are min/max set
+        var versionPref = platformBag[platformName];
+        if (!versionPref.MaxVersionTested && !!versionPref.MinVersion) { // min is set, but max is not
+            versionPref.MaxVersionTested = versionPref.MinVersion;
+        }
+        else if (!versionPref.MinVersion && !!versionPref.MaxVersionTested) { // max is set, min is not
+            versionPref.MinVersion = versionPref.MaxVersionTested;
+        }
+        else if (!versionPref.MinVersion && !versionPref.MaxVersionTested) { // neither are set
+            versionPref.MinVersion = BASE_UAP_VERSION;
+            versionPref.MaxVersionTested = BASE_UAP_VERSION;
+        }
+        else { // both are set
+            if (versionPref.MinVersion.gt(versionPref.MaxVersionTested)) {
+                versionPref.MaxVersionTested = versionPref.MinVersion;
+            }
+        }
+    }
 
-    },
-    
-    // Returns the widget defaultLocale
-    defaultLocale: function() {
-        return this.doc.getroot().attrib['defaultlocale'];
+    if (Object.keys(platformBag).length === 0) {
+        platformBag['Windows.Universal'] = { MinVersion: BASE_UAP_VERSION, MaxVersionTested: BASE_UAP_VERSION };
     }
+
+    return Object.keys(platformBag).map(function (platformName) {
+        return {
+            Name: platformName,
+            MinVersion: platformBag[platformName].MinVersion.toString(),
+            MaxVersionTested: platformBag[platformName].MaxVersionTested.toString(),
+        };
+    });
+};
+
+// Returns the widget defaultLocale
+WindowsConfigParser.prototype.defaultLocale = function() {
+    return this.doc.getroot().attrib['defaultlocale'];
+};
+
+/**
+ * Checks to see whether access rules or
+ * @return {boolean} True if the config specifies remote URIs for access or start; false otherwise.
+ */
+WindowsConfigParser.prototype.hasRemoteUris = function() {
+    var test = /(https?|ms-appx-web):\/\//i;
+
+    return test.test(this.startPage) ||
+        this.getAllowNavigations()
+        .some(function(rule) {
+            return test.test(rule.href);
+        });
 };
 
-module.exports = ConfigParser;
+module.exports = WindowsConfigParser;

http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/58047a3d/template/cordova/lib/ConsoleLogger.js
----------------------------------------------------------------------
diff --git a/template/cordova/lib/ConsoleLogger.js b/template/cordova/lib/ConsoleLogger.js
new file mode 100644
index 0000000..4ad6a75
--- /dev/null
+++ b/template/cordova/lib/ConsoleLogger.js
@@ -0,0 +1,75 @@
+/**
+    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 loggerInstance;
+var util = require('util');
+var EventEmitter = require('events').EventEmitter;
+var CordovaError = require('cordova-common').CordovaError;
+
+/**
+ * @class ConsoleLogger
+ * @extends EventEmitter
+ *
+ * Implements basic logging for platform. Inherits regular NodeJS EventEmitter.
+ *   All events, emitted on this class instance are immediately logged to
+ *   console.
+ *
+ * Also attaches handler to process' uncaught exceptions, so these exceptions
+ *   logged to console similar to regular error events.
+ */
+function ConsoleLogger() {
+    EventEmitter.call(this);
+
+    var isVerbose = process.argv.indexOf('-d') >= 0 || process.argv.indexOf('--verbose') >= 0;
+    // For CordovaError print only the message without stack trace unless we
+    // are in a verbose mode.
+    process.on('uncaughtException', function(err){
+        if ((err instanceof CordovaError) && isVerbose) {
+            console.error(err.stack);
+        } else {
+            console.error(err.message);
+        }
+        process.exit(1);
+    });
+
+    this.on('results', console.log);
+    this.on('verbose', function () {
+        if (isVerbose)
+            console.log.apply(console, arguments);
+    });
+    this.on('info', console.log);
+    this.on('log', console.log);
+    this.on('warn', console.warn);
+}
+util.inherits(ConsoleLogger, EventEmitter);
+
+/**
+ * Returns already instantiated/newly created instance of ConsoleLogger class.
+ *   This method should be used instead of creating ConsoleLogger directly,
+ *   otherwise we'll get multiple handlers attached to process'
+ *   uncaughtException
+ *
+ * @return  {ConsoleLogger}  New or already created instance of ConsoleLogger
+ */
+ConsoleLogger.get = function () {
+    loggerInstance = loggerInstance || new ConsoleLogger();
+    return loggerInstance;
+};
+
+module.exports = ConsoleLogger;

http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/58047a3d/template/cordova/lib/JsprojManager.js
----------------------------------------------------------------------
diff --git a/template/cordova/lib/JsprojManager.js b/template/cordova/lib/JsprojManager.js
new file mode 100644
index 0000000..714c7eb
--- /dev/null
+++ b/template/cordova/lib/JsprojManager.js
@@ -0,0 +1,608 @@
+/**
+ 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 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)'));
+    }
+    return new jsprojManager(path.normalize(projectFiles[0]));
+};
+
+jsprojManager.prototype = {
+    _projects: null,
+
+    getPackageName: function() {
+        return AppxManifest.get(path.join(this.root, 'package.windows.appxmanifest'))
+            .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 (relPath, targetConditions) {
+        events.emit('verbose', 'jsprojManager.addResourceFile(relPath: ' + relPath + ', targetConditions: ' + JSON.stringify(targetConditions) + ')');
+
+        // add hint path with full path
+        var link = new et.Element('Link');
+        link.text = relPath;
+        var children = [link];
+
+        var copyToOutputDirectory = new et.Element('CopyToOutputDirectory');
+        copyToOutputDirectory.text = 'Always';
+        children.push(copyToOutputDirectory);
+
+        var item = createItemGroupElement('ItemGroup/Content', relPath, 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) {
+        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);
+        }
+
+        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(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");
+        }
+
+        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(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");
+        }
+
+        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;
+    }
+};
+
+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-windows/blob/58047a3d/template/cordova/lib/MSBuildTools.js
----------------------------------------------------------------------
diff --git a/template/cordova/lib/MSBuildTools.js b/template/cordova/lib/MSBuildTools.js
index 1742bfa..c56c99a 100644
--- a/template/cordova/lib/MSBuildTools.js
+++ b/template/cordova/lib/MSBuildTools.js
@@ -17,12 +17,12 @@
        under the License.
 */
 
-var Q     = require('q'),
-    path  = require('path'),
-    exec  = require('./exec'),
-    shell = require('shelljs'),
-    spawn  = require('./spawn'),
-    Version = require('./Version');
+var Q     = require('q');
+var path  = require('path');
+var shell = require('shelljs');
+var Version = require('./Version');
+var events = require('cordova-common').events;
+var spawn = require('cordova-common').superspawn.spawn;
 
 function MSBuildTools (version, path) {
     this.version = version;
@@ -30,9 +30,9 @@ function MSBuildTools (version, path) {
 }
 
 MSBuildTools.prototype.buildProject = function(projFile, buildType, buildarch, otherConfigProperties) {
-    console.log('Building project: ' + projFile);
-    console.log('\tConfiguration : ' + buildType);
-    console.log('\tPlatform      : ' + buildarch);
+    events.emit('log', 'Building project: ' + projFile);
+    events.emit('log', '\tConfiguration : ' + buildType);
+    events.emit('log', '\tPlatform      : ' + buildarch);
 
     var args = ['/clp:NoSummary;NoItemAndPropertyList;Verbosity=minimal', '/nologo',
     '/p:Configuration=' + buildType,
@@ -45,7 +45,7 @@ MSBuildTools.prototype.buildProject = function(projFile, buildType, buildarch, o
         });
     }
 
-    return spawn(path.join(this.path, 'msbuild'), [projFile].concat(args));
+    return spawn(path.join(this.path, 'msbuild'), [projFile].concat(args), { stdio: 'inherit' });
 };
 
 // returns full path to msbuild tools required to build the project and tools version
@@ -62,6 +62,7 @@ module.exports.findAvailableVersion = function () {
 
 module.exports.findAllAvailableVersions = function () {
     var versions = ['14.0', '12.0', '4.0'];
+    events.emit('verbose', 'Searching for available MSBuild versions...');
 
     return Q.all(versions.map(checkMSBuildVersion)).then(function(unprocessedResults) {
         return unprocessedResults.filter(function(item) {
@@ -71,27 +72,25 @@ module.exports.findAllAvailableVersions = function () {
 };
 
 function checkMSBuildVersion(version) {
-    var deferred = Q.defer();
-    exec('reg query HKLM\\SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\' + version + ' /v MSBuildToolsPath')
+    return spawn('reg', ['query', 'HKLM\\SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\' + version, '/v', 'MSBuildToolsPath'])
     .then(function(output) {
         // fetch msbuild path from 'reg' output
-        var path = /MSBuildToolsPath\s+REG_SZ\s+(.*)/i.exec(output);
-        if (path) {
-            path = path[1];
+        var toolsPath = /MSBuildToolsPath\s+REG_SZ\s+(.*)/i.exec(output);
+        if (toolsPath) {
+            toolsPath = toolsPath[1];
             // CB-9565: Windows 10 invokes .NET Native compiler, which only runs on x86 arch,
             // so if we're running an x64 Node, make sure to use x86 tools.
-            if (version === '14.0' && path.indexOf('amd64') > -1) {
-                path = require('path').join(path, '..');
+            if (version === '14.0' && toolsPath.indexOf('amd64') > -1) {
+                toolsPath = path.resolve(toolsPath, '..');
             }
-            deferred.resolve(new MSBuildTools(version, path));
-            return;
+            events.emit('verbose', 'Found MSBuild v' + version + ' at ' + toolsPath);
+            return new MSBuildTools(version, toolsPath);
         }
-        deferred.resolve(null); // not found
-    }, function (err) {
+    })
+    .catch(function (err) {
         // if 'reg' exits with error, assume that registry key not found
-        deferred.resolve(null);
+        return;
     });
-    return deferred.promise;
 }
 
 /// returns an array of available UAP Versions
@@ -119,4 +118,5 @@ function getAvailableUAPVersions() {
 
     return result;
 }
+
 module.exports.getAvailableUAPVersions = getAvailableUAPVersions;

http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/58047a3d/template/cordova/lib/PluginHandler.js
----------------------------------------------------------------------
diff --git a/template/cordova/lib/PluginHandler.js b/template/cordova/lib/PluginHandler.js
new file mode 100644
index 0000000..43c4d03
--- /dev/null
+++ b/template/cordova/lib/PluginHandler.js
@@ -0,0 +1,233 @@
+/*
+ *
+ * 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;
+
+var handlers = {
+    'source-file': {
+        install:function(obj, plugin, project, options) {
+            var dest = path.join('plugins', plugin.id, obj.targetDir || '', path.basename(obj.src));
+            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) {
+            // 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, getTargetConditions(obj));
+        },
+        uninstall:function(obj, plugin, project, options) {
+            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;
+
+            if(type === 'projectReference') {
+                project.addProjectReference(path.join(plugin.dir,src), getTargetConditions(obj));
+            }
+            else {
+                var targetDir = obj.targetDir || '';
+                // 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);
+                project.addReference(dest, getTargetConditions(obj));
+            }
+
+        },
+        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(plugin.dir, 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('<asset> tag without required "src" attribute. plugin=' + plugin.dir);
+            }
+            if (!obj.target) {
+                throw new CordovaError('<asset> tag without required "target" attribute');
+            }
+
+            var www = options.usePlatformWww ? project.platformWww : project.www;
+            copyFile(plugin.dir, obj.src, www, obj.target);
+        },
+        uninstall:function(obj, plugin, project, options) {
+            var target = obj.target || obj.src;
+
+            if (!target) throw new CordovaError('<asset> tag without required "target" attribute');
+
+            var www = options.usePlatformWww ? project.platformWww : project.www;
+            removeFile(www, target);
+            shell.rm('-Rf', path.resolve(www, '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.parse(obj.src).name);
+
+            // 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 www = options.usePlatformWww ? project.platformWww : project.www;
+            var moduleDestination = path.resolve(www, 'plugins', plugin.id, obj.src);
+            shell.mkdir('-p', path.dirname(moduleDestination));
+            fs.writeFileSync(moduleDestination, scriptContent, 'utf-8');
+        },
+        uninstall: function (obj, plugin, project, options) {
+            var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
+            var www = options.usePlatformWww ? project.platformWww : project.www;
+            removeFileAndParents(www, 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('"' + src + '" not located within plugin!');
+
+    dest = path.resolve(project_dir, dest);
+
+    // check that dest path is located in project directory
+    if (dest.indexOf(project_dir) !== 0)
+        throw new CordovaError('"' + dest + '" not located within 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;
+        }
+    }
+}


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