Updated plugin version fetching proposal based on discussion
diff --git a/proposals/plugin-verion-fetching.md b/proposals/plugin-verion-fetching.md
index 5f7adaf..060964a 100644
--- a/proposals/plugin-verion-fetching.md
+++ b/proposals/plugin-verion-fetching.md
@@ -2,30 +2,45 @@
 

 ## Overview

 

-This is a counterproposal to pinning plugin versions in cordova-lib. The issue with pinning plugin versions in lib is that it does not support third party plugins. If we are solving this problem in Cordova, we can do so in a general way that improves the stability of the entire ecosystem and not just core. This proposal suggests that we add support for a mapping of plugin versions to project requirements in the plugin package.json that the CLI can check against.

+This is a proposal to improve the way we fetch plugins from npm. There is a need for us to do something beyond pinning plugin versions in cordova-lib as that does not support third party plugins. If we are solving this problem in Cordova, we can do so in a general way that improves the stability of the entire ecosystem and not just core. This proposal suggests that we add support for a mapping of plugin versions to project requirements in the plugin package.json that the CLI can check against. This would replace the current `<engine>` tag in the plugin.xml.

 

 The reason we insert this into the package.json is that it is possible to get a package.json from npm without fetching the whole package (`npm info <plugin-package-name>`), thus preventing the need to fetch all the versions of a plugin to find a compatible version. This way the pinning information can live in the npm package rather than a centralized location like cordova-lib.

 

-## Proposal

+### Proposal

 

 I propose we add this entry to the "cordova" key in the package.json for plugins:

 

 ```

-cordovaDependencies: {

-    <plugin version>:

-        {

-            cordova: <cli version>,

-            android: <platform version>,

-            ios: <platform version>,

-            ...

-        },

+cordovaDependencies:

+{

+    <plugin-version>:

+    {

+        <constraint>: <constraint-version>,

+        ...,

+

+    },

     ...

 }

 ```

 

-Essentially, this compiles the plugin.xml engine tags for each version of a plugin into one handy list in package.json. Each entry in the object needs to only specify the platform versions it cares about, just like the engine tag. Platform versions take precedence over the CLI version specified. If a plugin version's engine information is not specified, we use the engine information for the next version down.

+#### `<plugin-version>` allowed values

+* Single version (e.g. `1.0.0`)

+* Upper Bound (e.g. `<2.0.0`). This is for BIG breaking changes. For example, if we made a change to some platform that breaks compatibility with all earlier versions of a plugin, we would use this tag to retroactively update their mappings. It won't override earlier tags, just add to them. This is sugar; we could leave it out if we want to keep things simple

 

-For example, consider the following entry:

+#### `<constraint>` allowed values:

+* `cordova`

+* Platforms (e.g. `cordova-android`)

+* Plugins (e.g. `cordova-plugin-camera`)

+

+

+#### `<constraint-version>` syntax:

+* npm semver

+

+Essentially, this compiles the plugin.xml `<engine>` tags for each version of a plugin into one handy list in package.json. Each entry in the object needs to only specify the platform versions it cares about, just like the engine tag. We can also add support for plugin dependency versions (as in the plugin.xml's `<dependency>` tag).

+

+When the user runs `cordova plugin add <plugin-package-name>`, the CLI will check the plugin versions in descending order to examine each "engine" entry and determine if the current project can support that plugin version. It will then try and fetch the highest compatible plugin version (see [1] and [2] for high level pseudocode). The CLI must clearly indicate why the user is not getting the latest version of a plugin if it is not compatible with the current project. This can take the form of listing the engine information for the latest plugin version in some human readable way.

+

+If a plugin version's engine information is not specified, the CLI will use the engine information for the next version down. For example, consider the following entry:

 

 ```

 cordovaDependencies: {

@@ -34,13 +49,15 @@
 }

 ```

 

-All versions of the plugin from 0.0.1 to 1.0.0 are assumed to have the engine information that is specified in the 0.0.1 entry. If the current project satisfies the 0.0.1 engine information, then we get the highest version of the plugin that is greater than or equal to 0.0.1 and less than 1.0.0. Supporting ranges like these just helps to cut down on the size of the mapping.

+Here, we have specified the project requirements of two versions of a plugin (0.0.1 and 1.0.0). All versions of the plugin between 0.0.1 and 1.0.0 are assumed to have the engine information that is specified in the 0.0.1 entry. If the current project satisfies the 0.0.1 engine information, then we get the highest version of the plugin that is greater than or equal to 0.0.1 and less than 1.0.0. This helps us to reduce the size of the mapping if the project requirements don't change across multiple releases.

 

-When the user runs `cordova plugin add <plugin-package-name>`, the CLI will check the plugin versions in descending order and look at each engine entry to see if the current project can support that plugin version (see [1] for high level pseudocode). The CLI must clearly indicate why the user is not getting the latest version of a plugin if it is not compatible with the current project. This can take the form of listing the engine information for the latest plugin version in some human readable way.

+Fetching should always fall back to the current behavior (fetching latest/pinned plugin versions) if there are no versions of the plugin that the project supports. This is to make sure that if some plugin developer neglects their plugin mapping, their plugin does not become impossible to install without giving a specific version. However, in this case the CLI will deliver a stern warning that the project might not build and explain what project requirements were not satisfied.

 

 ### Generating the mapping

 

-Generating the cordovaDependencies list will be automated and done automatically as a plugin is published. A script can be added to plugman that goes through a git repo and gathers the engine information from plugin.xml. This gives third party plugins an easy way of generating the mapping as well.

+We can include a script in plugman to generate the mapping based on old `<engine>` tags in a git repo to make it easier for other developers to opt in. For future releases, the mapping will have to be updated as part of the release step. This should mostly involve us staying up to date with the `<engine>` tag information like we have in the past and updating older versions if there were breaking changes in the platform (with a new upper bound).

+

+While the cordova CLI version can be used as a constraint in the mapping, we should probably default to using platform constraints instead whenever possible. My reasoning here is that it is too easy for the CLI version to go out of sync with a project's installed platforms/plugins. Still, after discussing this with others offline it was made clear to me that it should be available as a constraint because it is more likely that third party plugin developers test against a CLI version rather than individual platforms.

 

 ## Pros

 

@@ -54,37 +71,42 @@
 

 ## Footnotes

 

-[1] High level pseudocode for `cordova plugin add <plugin-package-name>`:

+#### [1] Plugin Fetching behavior for `cordova plugin add`

 ```

-// User specified versions should always trump everything else

-if(user specified a version) {

-    return fetch(user's verion)

-}

-else if(config.xml specifies a version) {

-    return fetch(config.xml's version)

-}

-else {

-    // Fetch the plugin information from npm

-    package = exec(npm info plugin-package)

-    dependencies = package.cordova.cordovaDependencies

+If version is specified by user (i.e. '@'):

+    Fetch that version

+Else if version is specified in config.xml:

+    Fetch that version

+Else:

+    If Cordova dependency constraint mappings are found in plugin's package.json:

+        If some plugin version's constraint mapping is satisfied by current project:

+            Let x = plugin version of highest mapping that is satisfied

+            Let y = plugin version of the next mapping above x (an unsatisfied mapping)

 

-    if(!dependencies) {

-        return fetch(latest plugin version)

-    }

-    else {

-        // Search for the newest version that works with the current project

-        for(pluginVersion in dependencies DESC) {

+            Fetch highest version of plugin that is >= x and < y

+            Print brief explanation as to why that version was fetched

+        Else:

+            Fetch latest (or pinned) version of plugin

+            Warn user that the project might not build

+    Else:

+        Fetch latest (or pinned) version of plugin (i.e. the current behavior)

+```

 

-            // Version-range and platform checking logic is omitted here

-            if(current project satisfies dependencies[pluginVersion]) {

-                return fetch("plugin-package-name@" + pluginVersion)

-            }

-        }

+##### [2] Logic for determining if a plugin version's constraint mapping is satisfied

+```

+First, add all applicable upper range constraints to this plugin version's constraint mapping (see <plugin-version> allowed values above)

 

-        /*

-         * We couldn't find a version that works with this project. Warn the

-         * user that their project might not build and fetch latest.

-         */

-    }

-}

+If Cordova version is constrained and installed version does not satisfy semver constraint:

+    return NotSatisfied

+Else:

+    For each platform/plugin in constraint mapping:

+        If that platform/plugin is installed in project:

+            If project's installed platform/plugin version satisfies the mapped semver constraint:

+                Continue

+            Else:

+                Return NotSatisfied

+        Else:

+            Continue

+

+    Return Satisfied

 ```