CB-10679: New version choosing logic for plugin add

Adds support for plugins specifying their cordova
related dependencies in their package.json to
guide cordova-lib in choosing the correct version
of a plugin to fetch for the current project

This closes #363


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

Branch: refs/heads/common-1.1.x
Commit: 45a235fa73e5b23ed9fa44734449503976b999e3
Parents: 114de6c
Author: riknoll <richard.b.kn...@gmail.com>
Authored: Fri Jan 15 13:35:26 2016 -0800
Committer: riknoll <richard.b.kn...@gmail.com>
Committed: Mon Mar 7 13:37:17 2016 -0800

----------------------------------------------------------------------
 cordova-lib/spec-cordova/plugin.spec.js       | 157 +++++--
 cordova-lib/spec-cordova/plugin_fetch.spec.js | 522 +++++++++++++++++++++
 cordova-lib/spec-cordova/util.spec.js         |  19 +
 cordova-lib/src/cordova/platform.js           |  21 +-
 cordova-lib/src/cordova/plugin.js             | 324 +++++++++++--
 cordova-lib/src/cordova/util.js               |  20 +-
 cordova-lib/src/plugman/registry/registry.js  |  26 +-
 7 files changed, 985 insertions(+), 104 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/45a235fa/cordova-lib/spec-cordova/plugin.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/plugin.spec.js 
b/cordova-lib/spec-cordova/plugin.spec.js
index b09fbff..11a1b16 100644
--- a/cordova-lib/spec-cordova/plugin.spec.js
+++ b/cordova-lib/spec-cordova/plugin.spec.js
@@ -19,9 +19,12 @@
 
 var helpers = require('./helpers'),
     path = require('path'),
+    Q = require('q'),
     shell = require('shelljs'),
     events = require('cordova-common').events,
-    cordova = require('../src/cordova/cordova');
+    cordova = require('../src/cordova/cordova'),
+    plugman = require('../src/plugman/plugman'),
+    registry = require('../src/plugman/registry/registry');
 
 var tmpDir = helpers.tmpDir('plugin_test');
 var project = path.join(tmpDir, 'project');
@@ -29,21 +32,66 @@ var pluginsDir = path.join(__dirname, 'fixtures', 
'plugins');
 var pluginId = 'org.apache.cordova.fakeplugin1';
 var org_test_defaultvariables = 'org.test.defaultvariables';
 
+var results;
+
+
+// Runs: list, add, list
+function addPlugin(target, id, options) {
+    // Check there are no plugins yet.
+    return cordova.raw.plugin('list').then(function() {
+        expect(results).toMatch(/No plugins added/gi);
+    }).then(function() {
+        // Add a fake plugin from fixtures.
+        return cordova.raw.plugin('add', target, options);
+    }).then(function() {
+        expect(path.join(project, 'plugins', id, 'plugin.xml')).toExist();
+    }).then(function() {
+        return cordova.raw.plugin('ls');
+    }).then(function() {
+        expect(results).toContain(id);
+    });
+}
+
+// Runs: remove, list
+function removePlugin(id) {
+    return cordova.raw.plugin('rm', id)
+    .then(function() {
+        // The whole dir should be gone.
+        expect(path.join(project, 'plugins', id)).not.toExist();
+    }).then(function() {
+        return cordova.raw.plugin('ls');
+    }).then(function() {
+        expect(results).toMatch(/No plugins added/gi);
+    });
+}
+
+var errorHandler = {
+    errorCallback: function(error) {
+        // We want the error to be printed by jasmine
+        expect(error).toBeUndefined();
+    }
+};
+
+// We can't call add with a searchpath or else we will conflict with other 
tests
+// that use a searchpath. See loadLocalPlugins() in plugman/fetch.js for 
details.
+// The searchpath behavior gets tested in the plugman spec
+function mockPluginFetch(id, dir) {
+    spyOn(plugman.raw, 'fetch').andCallFake(function(target, pluginPath, 
fetchOptions) {
+        var dest = path.join(project, 'plugins', id);
+        var src = path.join(dir, 'plugin.xml');
+
+        shell.mkdir(dest);
+        shell.cp(src, dest);
+        return Q(dest);
+    });
+}
+
 describe('plugin end-to-end', function() {
-    var results;
+    events.on('results', function(res) { results = res; });
 
     beforeEach(function() {
         shell.rm('-rf', project);
-    });
-    afterEach(function() {
-        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on 
Windows.
-        shell.rm('-rf', tmpDir);
-    });
 
-    // The flow tested is: ls, add, ls, rm, ls.
-    // Plugin dependencies are not tested as that should be corvered in 
plugman tests.
-    // TODO (kamrik): Test the 'plugin search' command.
-    it('should successfully run', function(done) {
         // cp then mv because we need to copy everything, but that means it'll 
copy the whole directory.
         // Using /* doesn't work because of hidden files.
         shell.cp('-R', path.join(__dirname, 'fixtures', 'base'), tmpDir);
@@ -52,40 +100,57 @@ describe('plugin end-to-end', function() {
         shell.cp('-R', path.join(__dirname, 'fixtures', 'platforms', 
helpers.testPlatform), path.join(project, 'platforms'));
         process.chdir(project);
 
-        events.on('results', function(res) { results = res; });
-
-        // Check there are no plugins yet.
-        cordova.raw.plugin('list').then(function() {
-            expect(results).toMatch(/No plugins added/gi);
-        }).then(function() {
-            // Add a fake plugin from fixtures.
-            return cordova.raw.plugin('add', path.join(pluginsDir, 'fake1'));
-        }).then(function() {
-           expect(path.join(project, 'plugins', pluginId, 
'plugin.xml')).toExist();
-        }).then(function() {
-            return cordova.raw.plugin('ls');
-        }).then(function() {
-            expect(results).toContain(pluginId);
-        }).then(function() {
-            // And now remove it.
-            return cordova.raw.plugin('rm', pluginId);
-        }).then(function() {
-            // The whole dir should be gone.
-            expect(path.join(project, 'plugins', pluginId)).not.toExist();
-        }).then(function() {
-            return cordova.raw.plugin('ls');
-        }).then(function() {
-            expect(results).toMatch(/No plugins added/gi);
-        }).then(function() {
-            // Testing Default Variables plugin
-            return cordova.raw.plugin('add', path.join(pluginsDir, 
org_test_defaultvariables),{cli_variables: { REQUIRED:'yes', 
REQUIRED_ANDROID:'yes'}});
-         }).then(function() {
-            return cordova.raw.plugin('ls');
-        }).then(function() {
-            expect(results).toContain(org_test_defaultvariables);
-        }).fail(function(err) {
-            console.log(err.stack);
-            expect(err).toBeUndefined();
-        }).fin(done);
+        spyOn(errorHandler, 'errorCallback').andCallThrough();
+    });
+
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on 
Windows.
+        shell.rm('-rf', tmpDir);
+        expect(errorHandler.errorCallback).not.toHaveBeenCalled();
+    });
+
+    it('should successfully add and remove a plugin with no options', 
function(done) {
+        addPlugin(path.join(pluginsDir, 'fake1'), pluginId, {}, done)
+        .then(function() {
+            return removePlugin(pluginId);
+        })
+        .fail(errorHandler.errorCallback)
+        .fin(done);
+    });
+
+    it('should successfully add a plugin when specifying CLI variables', 
function(done) {
+        addPlugin(path.join(pluginsDir, org_test_defaultvariables), 
org_test_defaultvariables, {cli_variables: { REQUIRED:'yes', 
REQUIRED_ANDROID:'yes'}}, done)
+        .fail(errorHandler.errorCallback)
+        .fin(done);
+    });
+
+    it('should not check npm info when using the searchpath flag', 
function(done) {
+        mockPluginFetch(pluginId, path.join(pluginsDir, 'fake1'));
+
+        spyOn(registry, 'info');
+        addPlugin(pluginId, pluginId, {searchpath: pluginsDir}, done)
+        .then(function() {
+            expect(registry.info).not.toHaveBeenCalled();
+
+            var fetchOptions = plugman.raw.fetch.mostRecentCall.args[2];
+            expect(fetchOptions.searchpath).toBeDefined();
+        })
+        .fail(errorHandler.errorCallback)
+        .fin(done);
+    });
+
+    it('should not check npm info when using the noregistry flag', 
function(done) {
+        mockPluginFetch(pluginId, path.join(pluginsDir, 'fake1'));
+
+        spyOn(registry, 'info');
+        addPlugin(pluginId, pluginId, {noregistry:true}, done)
+        .then(function() {
+            expect(registry.info).not.toHaveBeenCalled();
+
+            var fetchOptions = plugman.raw.fetch.mostRecentCall.args[2];
+            expect(fetchOptions.noregistry).toBeTruthy();
+        })
+        .fail(errorHandler.errorCallback)
+        .fin(done);
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/45a235fa/cordova-lib/spec-cordova/plugin_fetch.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/plugin_fetch.spec.js 
b/cordova-lib/spec-cordova/plugin_fetch.spec.js
new file mode 100644
index 0000000..107b539
--- /dev/null
+++ b/cordova-lib/spec-cordova/plugin_fetch.spec.js
@@ -0,0 +1,522 @@
+/**
+    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 plugin  = require('../src/cordova/plugin'),
+    helpers = require('./helpers'),
+    path    = require('path'),
+    events = require('cordova-common').events,
+    shell   = require('shelljs');
+
+var testPluginVersions = [
+    '0.0.2',
+    '0.7.0',
+    '1.0.0',
+    '1.1.0',
+    '1.1.3',
+    '1.3.0',
+    '1.7.0',
+    '1.7.1',
+    '2.0.0-rc.1',
+    '2.0.0-rc.2',
+    '2.0.0',
+    '2.3.0'
+];
+
+var cordovaVersion = '3.4.2';
+
+var tempDir = helpers.tmpDir('plugin_fetch_spec');
+var project = path.join(tempDir, 'project');
+
+var getVersionErrorCallback;
+var warnings = [];
+
+// Used to extract the constraint, the installed version, and the required
+// semver range from a warning message
+var UNMET_REQ_REGEX = /\s+([^\s]+)[^\d]+(\d+\.\d+\.\d+) installed, (.+) 
required\)/;
+
+// We generate warnings when we don't fetch latest. Collect them to make sure 
we
+// are making the correct warnings
+events.on('warn', function(warning) {
+    warnings.push(warning);
+});
+
+// Tests a sample engine against the installed platforms/plugins in our test
+// project
+function testEngineWithProject(done, testEngine, testResult) {
+    plugin.getFetchVersion(project,
+        {
+            'version': '2.3.0',
+            'name': 'test-plugin',
+            'engines': { 'cordovaDependencies': testEngine },
+            'versions': testPluginVersions
+        }, cordovaVersion)
+    .then(function(toFetch) {
+        expect(toFetch).toBe(testResult);
+    })
+    .fail(getVersionErrorCallback)
+    .fin(done);
+}
+
+// Checks the warnings that were printed by the CLI to ensure that the code is
+// listing the correct reasons for failure. Checks against the global warnings
+// object which is reset before each test
+function checkUnmetRequirements(requirements) {
+    var reqWarnings = [];
+
+    warnings.forEach(function(warning) {
+        var extracted = UNMET_REQ_REGEX.exec(warning);
+        if (extracted) {
+            reqWarnings.push({
+                dependency: extracted[1],
+                installed: extracted[2],
+                required: extracted[3]
+            });
+        }
+    });
+
+    expect(reqWarnings.length).toEqual(requirements.length);
+
+    requirements.forEach(function(requirement) {
+        expect(reqWarnings).toContain(function(extractedWarning) {
+            return  extractedWarning.dependency === 
requirement.dependency.trim() &&
+                    extractedWarning.installed  === 
requirement.installed.trim() &&
+                    extractedWarning.required   === 
requirement.required.trim();
+        }, requirement);
+    });
+}
+
+// Helper functions for creating the requirements objects taken by
+// checkUnmetRequirements()
+function getPlatformRequirement(requirement) {
+    return {
+        dependency: 'cordova-android',
+        installed: '3.1.0',
+        required: requirement
+    };
+}
+
+function getCordovaRequirement(requirement) {
+    return {
+        dependency: 'cordova',
+        installed: cordovaVersion,
+        required: requirement
+    };
+}
+
+function getPluginRequirement(requirement) {
+    return {
+        dependency: 'ca.filmaj.AndroidPlugin',
+        installed: '4.2.0',
+        required: requirement
+    };
+}
+
+// Generates a callback that checks warning messages after the test is complete
+function getWarningCheckCallback(done, requirements) {
+    return function() {
+        checkUnmetRequirements(requirements);
+        expect(getVersionErrorCallback).not.toHaveBeenCalled();
+        done();
+    };
+}
+
+function createTestProject() {
+    // Get the base project
+    shell.cp('-R', path.join(__dirname, 'fixtures', 'base'), tempDir);
+    shell.mv(path.join(tempDir, 'base'), project);
+
+    // Copy a platform and a plugin to our sample project
+    shell.cp('-R',
+        path.join(__dirname, 'fixtures', 'platforms', helpers.testPlatform),
+        path.join(project, 'platforms'));
+    shell.cp('-R',
+        path.join(__dirname, 'fixtures', 'plugins', 'android'),
+        path.join(project, 'plugins'));
+}
+
+function removeTestProject() {
+    shell.rm('-rf', tempDir);
+}
+
+describe('plugin fetching version selection', function(done) {
+    createTestProject();
+
+    beforeEach(function() {
+        // Adding a matcher for checking the array of warning messages so that
+        // we can have meanigful error messages. Expected is passed because
+        // Jasmine will print it out if the matcher fails
+        this.addMatchers({
+            toContain: function(check, expected) {
+                for(var i = 0; i < this.actual.length; i++) {
+                    if (check(this.actual[i])) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        });
+
+        warnings = [];
+        getVersionErrorCallback = 
jasmine.createSpy('unexpectedPluginFetchErrorCallback');
+    });
+
+    it('should handle a mix of upper bounds and single versions', 
function(done) {
+        var testEngine = {
+            '0.0.0' : { 'cordova-android': '1.0.0' },
+            '0.0.2' : { 'cordova-android': '>1.0.0' },
+            '<1.0.0': { 'cordova-android': '<2.0.0' },
+            '1.0.0' : { 'cordova-android': '>2.0.0' },
+            '1.7.0' : { 'cordova-android': '>4.0.0' },
+            '<2.3.0': { 'cordova-android': '<6.0.0' },
+            '2.3.0' : { 'cordova-android': '6.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('6.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.3.0');
+    });
+
+    it('should apply upper bound engine constraints when there are no 
unspecified constraints above the upper bound', function(done) {
+        var testEngine = {
+            '1.0.0' : { 'cordova-android': '>2.0.0' },
+            '1.7.0' : { 'cordova-android': '>4.0.0' },
+            '<2.3.0': {
+                'cordova-android': '<6.0.0',
+                'ca.filmaj.AndroidPlugin': '<1.0.0'
+            },
+            '2.3.0' : { 'cordova-android': '6.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('6.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, null);
+    });
+
+    it('should apply upper bound engine constraints when there are unspecified 
constraints above the upper bound', function(done) {
+        var testEngine = {
+            '0.0.0' : {},
+            '2.0.0' : { 'cordova-android': '~5.0.0' },
+            '<1.0.0': { 'cordova-android': '>5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('~5.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.7.1');
+    });
+
+    it('should handle the case where there are no constraints for earliest 
releases', function(done) {
+        var testEngine = {
+            '1.0.0' : { 'cordova-android': '~5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('~5.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '0.7.0');
+    });
+
+    it('should handle the case where the lowest version is unsatisfied', 
function(done) {
+        var testEngine = {
+            '0.0.2' : { 'cordova-android': '~5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('~5.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, null);
+    });
+
+    it('should handle upperbounds if no single version constraints are given', 
function(done) {
+        var testEngine = {
+            '<1.0.0': { 'cordova-android': '<2.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, []);
+
+        testEngineWithProject(after, testEngine, '2.3.0');
+    });
+
+    it('should apply upper bounds greater than highest version', 
function(done) {
+        var testEngine = {
+            '0.0.0' : {},
+            '<5.0.0': { 'cordova-android': '<2.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('<2.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, null);
+    });
+
+    it('should treat empty constraints as satisfied', function(done) {
+        var testEngine = {
+            '1.0.0' : {},
+            '1.1.0' : { 'cordova-android': '>5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('>5.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.0.0');
+    });
+
+    it('should ignore an empty cordovaDependencies entry', function(done) {
+        var testEngine = {};
+
+        var after = getWarningCheckCallback(done, []);
+
+        testEngineWithProject(after, testEngine, null);
+    });
+
+    it('should ignore a badly formatted semver range', function(done) {
+        var testEngine = {
+            '1.1.3' : { 'cordova-android': 'badSemverRange' }
+        };
+
+        var after = getWarningCheckCallback(done, []);
+
+        testEngineWithProject(after, testEngine, '2.3.0');
+    });
+
+    it('should respect unreleased versions in constraints', function(done) {
+        var testEngine = {
+            '1.0.0' : { 'cordova-android': '3.1.0' },
+            '1.1.2' : { 'cordova-android': '6.0.0' },
+            '1.3.0' : { 'cordova-android': '6.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('6.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.1.0');
+    });
+
+    it('should respect plugin constraints', function(done) {
+        var testEngine = {
+            '0.0.0' : { 'ca.filmaj.AndroidPlugin': '1.2.0' },
+            '1.1.3' : { 'ca.filmaj.AndroidPlugin': '<5.0.0 || >2.3.0' },
+            '2.3.0' : { 'ca.filmaj.AndroidPlugin': '6.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPluginRequirement('6.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '2.0.0');
+    });
+
+    it('should respect cordova constraints', function(done) {
+        var testEngine = {
+            '0.0.0' : { 'cordova': '>1.0.0' },
+            '1.1.3' : { 'cordova': '<3.0.0 || >4.0.0' },
+            '2.3.0' : { 'cordova': '6.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getCordovaRequirement('6.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.1.0');
+    });
+
+    it('should not include pre-release versions', function(done) {
+        var testEngine = {
+            '0.0.0' : {},
+            '2.0.0' : { 'cordova-android': '>5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('>5.0.0')
+        ]);
+
+        // Should not return 2.0.0-rc.2
+        testEngineWithProject(after, testEngine, '1.7.1');
+    });
+
+    it('should not fail if there is no engine in the npm info', function(done) 
{
+        plugin.getFetchVersion(project, {
+                version: '2.3.0',
+                name: 'test-plugin',
+                versions: testPluginVersions
+            }, cordovaVersion)
+        .then(function(toFetch) {
+            expect(toFetch).toBe(null);
+        })
+        .fail(getVersionErrorCallback).fin(done);
+    });
+
+    it('should not fail if there is no cordovaDependencies in the engines', 
function(done) {
+        var after = getWarningCheckCallback(done, []);
+
+        plugin.getFetchVersion(project, {
+                version: '2.3.0',
+                name: 'test-plugin',
+                versions: testPluginVersions,
+                engines: {
+                    'node': '>7.0.0',
+                    'npm': '~2.0.0'
+                }
+            }, cordovaVersion)
+        .then(function(toFetch) {
+            expect(toFetch).toBe(null);
+        })
+        .fail(getVersionErrorCallback).fin(after);
+    });
+
+    it('should handle extra whitespace', function(done) {
+        var testEngine = {
+            '  1.0.0    '   : {},
+            '2.0.0   '      : { ' cordova-android': '~5.0.0   ' },
+            ' <  1.0.0\t'   : { ' cordova-android  ': ' > 5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('~5.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.7.1');
+    });
+
+    it('should ignore badly typed version requirement entries', function(done) 
{
+        var testEngine = {
+            '1.1.0' : ['cordova', '5.0.0'],
+            '1.3.0' : undefined,
+            '1.7.0' : null
+        };
+
+        var after = getWarningCheckCallback(done, []);
+
+        testEngineWithProject(after, testEngine, '2.3.0');
+    });
+
+    it('should ignore badly typed constraint entries', function(done) {
+        var testEngine = {
+            '0.0.2' : { 'cordova': 1 },
+            '0.7.0' : { 'cordova': {}},
+            '1.0.0' : { 'cordova': undefined},
+            '1.1.3' : { 8        : '5.0.0'},
+            '1.3.0' : { 'cordova': [] },
+            '1.7.1' : { 'cordova': null }
+        };
+
+        var after = getWarningCheckCallback(done, []);
+
+        testEngineWithProject(after, testEngine, '2.3.0');
+    });
+
+    it('should ignore bad semver versions', function(done) {
+        var testEngine = {
+            '0.0.0'         : { 'cordova-android': '5.0.0' },
+            'notAVersion'   : { 'cordova-android': '3.1.0' },
+            '^1.1.2'        : { 'cordova-android': '3.1.0' },
+            '<=1.3.0'       : { 'cordova-android': '3.1.0' },
+            '1.0'           : { 'cordova-android': '3.1.0' },
+            2               : { 'cordova-android': '3.1.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('5.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, null);
+    });
+
+    it('should not fail if there are bad semver versions', function(done) {
+        var testEngine = {
+            'notAVersion'   : { 'cordova-android': '3.1.0' },
+            '^1.1.2'        : { 'cordova-android': '3.1.0' },
+            '<=1.3.0'       : { 'cordova-android': '3.1.0' },
+            '1.0.0'         : { 'cordova-android': '~3'    },   // Good semver
+            '2.0.0'         : { 'cordova-android': '5.1.0' },   // Good semver
+            '1.0'           : { 'cordova-android': '3.1.0' },
+            2               : { 'cordova-android': '3.1.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('5.1.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.7.1');
+    });
+
+    it('should properly warn about multiple unmet requirements', 
function(done) {
+        var testEngine = {
+            '1.7.0' : {
+                'cordova-android'           : '>5.1.0',
+                'ca.filmaj.AndroidPlugin'   : '3.1.0',
+                'cordova'                   : '3.4.2'
+            }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('>5.1.0'),
+            getPluginRequirement('3.1.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.3.0');
+    });
+
+    it('should properly warn about both unmet latest and upper bound 
requirements', function(done) {
+        var testEngine = {
+            '1.7.0' : { 'cordova-android': '>5.1.0' },
+            '<5.0.0': {
+                'cordova-android'           : '>7.1.0',
+                'ca.filmaj.AndroidPlugin'   : '3.1.0'
+            }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('>5.1.0 AND >7.1.0'),
+            getPluginRequirement('3.1.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, null);
+    });
+
+    it('should not warn about versions past latest', function(done) {
+        var testEngine = {
+            '1.7.0' : { 'cordova-android': '>5.1.0' },
+            '7.0.0': {
+                'cordova-android'           : '>7.1.0',
+                'ca.filmaj.AndroidPlugin'   : '3.1.0'
+            }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('>5.1.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.3.0');
+    });
+
+    it('clean up after plugin fetch spec', function() {
+        removeTestProject();
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/45a235fa/cordova-lib/spec-cordova/util.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/util.spec.js 
b/cordova-lib/spec-cordova/util.spec.js
index 8a678c8..314de3b 100644
--- a/cordova-lib/spec-cordova/util.spec.js
+++ b/cordova-lib/spec-cordova/util.spec.js
@@ -24,6 +24,7 @@ var shell = require('shelljs'),
     fs = require('fs'),
     util = require('../src/cordova/util'),
     events = require('../cordova-lib').events,
+    helpers = require('./helpers'),
     temp = path.join(__dirname, '..', 'temp');
 
 var cwd = process.cwd();
@@ -143,6 +144,24 @@ describe('util module', function() {
             expect(res.indexOf('atari')).toEqual(-1);
         });
     });
+    describe('getInstalledPlatformsWithVersions method', function() {
+        afterEach(function() {
+            shell.rm('-rf', temp);
+        });
+        it('should get the supported platforms in the cordova project dir 
along with their reported versions', function(done) {
+            var platforms = path.join(temp, 'platforms');
+            var android = path.join(platforms, 'android');
+
+            shell.mkdir('-p', android);
+
+            shell.cp('-R',
+                path.join(__dirname, 'fixtures', 'platforms', 
helpers.testPlatform), platforms);
+            util.getInstalledPlatformsWithVersions(temp)
+            .then(function(platformMap) {
+                expect(platformMap['android']).toBe('3.1.0');
+            }).fin(done);
+        });
+    });
     describe('findPlugins method', function() {
         afterEach(function() {
             shell.rm('-rf', temp);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/45a235fa/cordova-lib/src/cordova/platform.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/platform.js 
b/cordova-lib/src/cordova/platform.js
index d130772..1b745be 100644
--- a/cordova-lib/src/cordova/platform.js
+++ b/cordova-lib/src/cordova/platform.js
@@ -492,26 +492,21 @@ function check(hooksRunner, projectRoot) {
 }
 
 function list(hooksRunner, projectRoot, opts) {
-    var platforms_on_fs = cordova_util.listPlatforms(projectRoot);
     return hooksRunner.fire('before_platform_ls', opts)
     .then(function() {
-        // Acquire the version number of each platform we have installed, and 
output that too.
-        return Q.all(platforms_on_fs.map(function(p) {
-            return superspawn.maybeSpawn(path.join(projectRoot, 'platforms', 
p, 'cordova', 'version'), [], { chmod: true })
-            .then(function(v) {
-                if (!v) return p;
-                return p + ' ' + v;
-            }, function(v) {
-                return p + ' broken';
-            });
-        }));
-    }).then(function(platformsText) {
+        return cordova_util.getInstalledPlatformsWithVersions(projectRoot);
+    }).then(function(platformMap) {
+        var platformsText = [];
+        for (var plat in platformMap) {
+            platformsText.push(platformMap[plat] ? plat + ' ' + 
platformMap[plat] : plat);
+        }
+
         platformsText = addDeprecatedInformationToPlatforms(platformsText);
         var results = 'Installed platforms:\n  ' + 
platformsText.sort().join('\n  ') + '\n';
         var available = Object.keys(platforms).filter(hostSupports);
 
         available = available.filter(function(p) {
-            return platforms_on_fs.indexOf(p) < 0; // Only those not already 
installed.
+            return !platformMap[p]; // Only those not already installed.
         });
 
         available = available.map(function (p){

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/45a235fa/cordova-lib/src/cordova/plugin.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/plugin.js 
b/cordova-lib/src/cordova/plugin.js
index 35819c3..c5ead29 100644
--- a/cordova-lib/src/cordova/plugin.js
+++ b/cordova-lib/src/cordova/plugin.js
@@ -31,10 +31,14 @@ var cordova_util  = require('./util'),
     pluginMapper  = require('cordova-registry-mapper').newToOld,
     events        = require('cordova-common').events,
     metadata      = require('../plugman/util/metadata'),
+    registry      = require('../plugman/registry/registry'),
     chainMap      = require('../util/promise-util').Q_chainmap,
     pkgJson       = require('../../package.json'),
     opener        = require('opener');
 
+// For upper bounds in cordovaDependencies
+var UPPER_BOUND_REGEX = /^<\d+\.\d+\.\d+$/;
+
 // Returns a promise.
 module.exports = function plugin(command, targets, opts) {
     // CB-10519 wrap function code into promise so throwing error
@@ -121,32 +125,7 @@ module.exports = function plugin(command, targets, opts) {
                                 target = target.substring(0, target.length - 
1);
                             }
 
-                            var parts = target.split('@');
-                            var id = parts[0];
-                            var version = parts[1];
-
-                            // If no version is specified, retrieve the 
version (or source) from config.xml
-                            if (!version && !cordova_util.isUrl(id) && 
!cordova_util.isDirectory(id)) {
-                                events.emit('verbose', 'no version specified, 
retrieving version from config.xml');
-                                var ver = getVersionFromConfigFile(id, cfg);
-
-                                if (cordova_util.isUrl(ver) || 
cordova_util.isDirectory(ver)) {
-                                    target = ver;
-                                } else {
-                                    //if version exists from config.xml, use 
that
-                                    if(ver) {
-                                        target = ver ? (id + '@' + ver) : 
target;
-                                    } else {
-                                        //fetch pinned version from cordova-lib
-                                        var pinnedVer = 
pkgJson.cordovaPlugins[id];
-                                        target = pinnedVer ? (id + '@' + 
pinnedVer) : target;
-                                    }
-                                }
-                            }
-
                             // Fetch the plugin first.
-                            events.emit('verbose', 'Calling plugman.fetch on 
plugin "' + target + '"');
-
                             var fetchOptions = {
                                 searchpath: searchPath,
                                 noregistry: opts.noregistry,
@@ -157,7 +136,12 @@ module.exports = function plugin(command, targets, opts) {
                                 is_top_level: true
                             };
 
-                            return plugman.raw.fetch(target, pluginPath, 
fetchOptions)
+                            return determinePluginTarget(projectRoot, cfg, 
target, fetchOptions)
+                            .then(function(resolvedTarget) {
+                                target = resolvedTarget;
+                                events.emit('verbose', 'Calling plugman.fetch 
on plugin "' + target + '"');
+                                return plugman.raw.fetch(target, pluginPath, 
fetchOptions);
+                            })
                             .then(function (directory) {
                                 return pluginInfoProvider.get(directory);
                             });
@@ -305,6 +289,51 @@ module.exports = function plugin(command, targets, opts) {
     });
 };
 
+function determinePluginTarget(projectRoot, cfg, target, fetchOptions) {
+    var parts = target.split('@');
+    var id = parts[0];
+    var version = parts[1];
+
+    if (version || cordova_util.isUrl(id) || cordova_util.isDirectory(id)) {
+        return Q(target);
+    }
+
+    // If no version is specified, retrieve the version (or source) from 
config.xml
+    events.emit('verbose', 'No version specified, retrieving version from 
config.xml');
+    var ver = getVersionFromConfigFile(id, cfg);
+
+    if (cordova_util.isUrl(ver) || cordova_util.isDirectory(ver)) {
+        return Q(ver);
+    }
+
+    // If version exists in config.xml, use that
+    if (ver) {
+        return Q(id + '@' + ver);
+    }
+
+    // If no version is given at all and we are fetching from npm, we
+    // can attempt to use the Cordova dependencies the plugin lists in
+    // their package.json
+    var shouldUseNpmInfo = !fetchOptions.searchpath && 
!fetchOptions.noregistry;
+
+    if(shouldUseNpmInfo) {
+        events.emit('verbose', 'No version given in config.xml, attempting to 
use plugin engine info');
+    }
+
+    return (shouldUseNpmInfo ? registry.info([id]) : Q({}))
+    .then(function(pluginInfo) {
+        return getFetchVersion(projectRoot, pluginInfo, pkgJson.version);
+    })
+    .then(function(fetchVersion) {
+        // Fallback to pinned version if available
+        fetchVersion = fetchVersion || pkgJson.cordovaPlugins[id];
+        return fetchVersion ? (id + '@' + fetchVersion) : target;
+    });
+}
+
+// Exporting for testing purposes
+module.exports.getFetchVersion = getFetchVersion;
+
 function validatePluginId(pluginId, installedPlugins) {
     if (installedPlugins.indexOf(pluginId) >= 0) {
         return pluginId;
@@ -395,10 +424,7 @@ function list(projectRoot, hooksRunner, opts) {
     var pluginsList = [];
     return hooksRunner.fire('before_plugin_ls', opts)
     .then(function() {
-        var pluginsDir = path.join(projectRoot, 'plugins');
-        // TODO: This should list based off of platform.json, not directories 
within plugins/
-        var pluginInfoProvider = new PluginInfoProvider();
-        return pluginInfoProvider.getAllWithinSearchPath(pluginsDir);
+        return getInstalledPlugins(projectRoot);
     })
     .then(function(plugins) {
         if (plugins.length === 0) {
@@ -445,6 +471,13 @@ function list(projectRoot, hooksRunner, opts) {
     });
 }
 
+function getInstalledPlugins(projectRoot) {
+    var pluginsDir = path.join(projectRoot, 'plugins');
+    // TODO: This should list based off of platform.json, not directories 
within plugins/
+    var pluginInfoProvider = new PluginInfoProvider();
+    return pluginInfoProvider.getAllWithinSearchPath(pluginsDir);
+}
+
 function saveToConfigXmlOn(config_json, options){
     options = options || {};
     var autosave =  config_json.auto_save_plugins || false;
@@ -512,3 +545,234 @@ function versionString(version) {
 
     return null;
 }
+
+/**
+ * Gets the version of a plugin that should be fetched for a given project 
based
+ * on the plugin's engine information from NPM and the platforms/plugins 
installed
+ * in the project. The cordovaDependencies object in the package.json's engines
+ * entry takes the form of an object that maps plugin versions to a series of
+ * constraints and semver ranges. For example:
+ *
+ *     { plugin-version: { constraint: semver-range, ...}, ...}
+ *
+ * Constraint can be a plugin, platform, or cordova version. Plugin-version
+ * can be either a single version (e.g. 3.0.0) or an upper bound (e.g. <3.0.0)
+ *
+ * @param {string}  projectRoot     The path to the root directory of the 
project
+ * @param {object}  pluginInfo      The NPM info of the plugin to be fetched 
(e.g. the
+ *                                  result of calling `registry.info()`)
+ * @param {string}  cordovaVersion  The semver version of cordova-lib
+ *
+ * @return {Promise}                A promise that will resolve to either a 
string
+ *                                  if there is a version of the plugin that 
this
+ *                                  project satisfies or null if there is not
+ */
+function getFetchVersion(projectRoot, pluginInfo, cordovaVersion) {
+    // Figure out the project requirements
+    if (pluginInfo.engines && pluginInfo.engines.cordovaDependencies) {
+        var pluginList = getInstalledPlugins(projectRoot);
+        var pluginMap = {};
+
+        pluginList.forEach(function(plugin) {
+            pluginMap[plugin.id] = plugin.version;
+        });
+
+        return cordova_util.getInstalledPlatformsWithVersions(projectRoot)
+        .then(function(platformVersions) {
+            return determinePluginVersionToFetch(
+                pluginInfo,
+                pluginMap,
+                platformVersions,
+                cordovaVersion);
+        });
+    } else {
+        // If we have no engine, we want to fall back to the default behavior
+        events.emit('verbose', 'No plugin engine info found or not using 
registry, falling back to latest or pinned version');
+        return Q(null);
+    }
+}
+
+function findVersion(versions, version) {
+    var cleanedVersion = semver.clean(version);
+    for(var i = 0; i < versions.length; i++) {
+        if(semver.clean(versions[i]) === cleanedVersion) {
+            return versions[i];
+        }
+    }
+    return null;
+}
+
+/*
+ * The engine entry maps plugin versions to constraints like so:
+ *  {
+ *      '1.0.0' : { 'cordova': '<5.0.0' },
+ *      '<2.0.0': {
+ *          'cordova': '>=5.0.0',
+ *          'cordova-ios': '~5.0.0',
+ *          'cordova-plugin-camera': '~5.0.0'
+ *      },
+ *      '3.0.0' : { 'cordova-ios': '>5.0.0' }
+ *  }
+ *
+ * See cordova-spec/plugin_fetch.spec.js for test cases and examples
+ */
+function determinePluginVersionToFetch(pluginInfo, pluginMap, platformMap, 
cordovaVersion) {
+    var allVersions = pluginInfo.versions;
+    var engine = pluginInfo.engines.cordovaDependencies;
+    var name = pluginInfo.name;
+
+    // Filters out pre-release versions
+    var latest = semver.maxSatisfying(allVersions, '>=0.0.0');
+
+    var versions = [];
+    var upperBound = null;
+    var upperBoundRange = null;
+    var upperBoundExists = false;
+
+    for(var version in engine) {
+        if(semver.valid(semver.clean(version)) && !semver.gt(version, latest)) 
{
+            versions.push(version);
+        } else {
+            // Check if this is an upperbound; validRange() handles whitespace
+            var cleanedRange = semver.validRange(version);
+            if(cleanedRange && UPPER_BOUND_REGEX.exec(cleanedRange)) {
+                upperBoundExists = true;
+                // We only care about the highest upper bound that our project 
does not support
+                if(getFailedRequirements(engine[version], pluginMap, 
platformMap, cordovaVersion).length !== 0) {
+                    var maxMatchingUpperBound = cleanedRange.substring(1);
+                    if (maxMatchingUpperBound && (!upperBound || 
semver.gt(maxMatchingUpperBound, upperBound))) {
+                        upperBound = maxMatchingUpperBound;
+                        upperBoundRange = version;
+                    }
+                }
+            } else {
+                events.emit('verbose', 'Ignoring invalid version in ' + name + 
' cordovaDependencies: ' + version + ' (must be a single version <= latest or 
an upper bound)');
+            }
+        }
+    }
+
+    // If there were no valid requirements, we fall back to old behavior
+    if(!upperBoundExists && versions.length === 0) {
+        events.emit('verbose', 'Ignoring ' + name + ' cordovaDependencies 
entry because it did not contain any valid plugin version entries');
+        return null;
+    }
+
+    // Handle the lower end of versions by giving them a satisfied engine
+    if(!findVersion(versions, '0.0.0')) {
+        versions.push('0.0.0');
+        engine['0.0.0'] = {};
+    }
+
+    // Add an entry after the upper bound to handle the versions above the
+    // upper bound but below the next entry. For example: 0.0.0, <1.0.0, 2.0.0
+    // needs a 1.0.0 entry that has the same engine as 0.0.0
+    if(upperBound && !findVersion(versions, upperBound) && 
!semver.gt(upperBound, latest)) {
+        versions.push(upperBound);
+        var below = semver.maxSatisfying(versions, upperBoundRange);
+
+        // Get the original entry without trimmed whitespace
+        below = below ? findVersion(versions, below) : null;
+        engine[upperBound] = below ? engine[below] : {};
+    }
+
+    // Sort in descending order; we want to start at latest and work back
+    versions.sort(semver.rcompare);
+
+    for(var i = 0; i < versions.length; i++) {
+        if(upperBound && semver.lt(versions[i], upperBound)) {
+            // Because we sorted in desc. order, if the upper bound we found
+            // applies to this version (and thus the ones below) we can just
+            // quit
+            break;
+        }
+
+        var range = i? ('>=' + versions[i] + ' <' + versions[i-1]) : ('>=' + 
versions[i]);
+        var maxMatchingVersion = semver.maxSatisfying(allVersions, range);
+
+        if (maxMatchingVersion && getFailedRequirements(engine[versions[i]], 
pluginMap, platformMap, cordovaVersion).length === 0) {
+
+            // Because we sorted in descending order, we can stop searching 
once
+            // we hit a satisfied constraint
+            if (maxMatchingVersion !== latest) {
+                var failedReqs = getFailedRequirements(engine[versions[0]], 
pluginMap, platformMap, cordovaVersion);
+
+                // Warn the user that we are not fetching latest
+                listUnmetRequirements(name, failedReqs);
+                events.emit('warn', 'Fetching highest version of ' + name + ' 
that this project supports: ' + maxMatchingVersion + ' (latest is ' + latest + 
')');
+            }
+            return maxMatchingVersion;
+        }
+    }
+
+    // No version of the plugin is satisfied. In this case, we fall back to
+    // fetching latest or pinned versions, but also output a warning
+    var latestFailedReqs = versions.length > 0 ? 
getFailedRequirements(engine[versions[0]], pluginMap, platformMap, 
cordovaVersion) : [];
+
+    // If the upper bound is greater than latest, we need to combine its engine
+    // requirements with latest to print out in the warning
+    if(upperBound && semver.satisfies(latest, upperBoundRange)) {
+        var upperFailedReqs = getFailedRequirements(engine[upperBoundRange], 
pluginMap, platformMap, cordovaVersion);
+        upperFailedReqs.forEach(function(failedReq) {
+            for(var i = 0; i < latestFailedReqs.length; i++) {
+                if(latestFailedReqs[i].dependency === failedReq.dependency) {
+                    // Not going to overcomplicate things and actually merge 
the ranges
+                    latestFailedReqs[i].required += ' AND ' + 
failedReq.required;
+                    return;
+                }
+            }
+
+            // There is no req to merge it with
+            latestFailedReqs.push(failedReq);
+        });
+    }
+
+    listUnmetRequirements(name, latestFailedReqs);
+    events.emit('warn', 'Current project does not satisfy the engine 
requirements specified by any version of ' + name + '. Fetching latest or 
pinned version of plugin anyway (may be incompatible)');
+
+    // No constraints were satisfied
+    return null;
+}
+
+
+function getFailedRequirements(reqs, pluginMap, platformMap, cordovaVersion) {
+    var failed = [];
+
+    for (var req in reqs) {
+        if(reqs.hasOwnProperty(req) && typeof req === 'string' && 
semver.validRange(reqs[req])) {
+            var badInstalledVersion = null;
+            var trimmedReq = req.trim();
+
+            if(pluginMap[trimmedReq] && 
!semver.satisfies(pluginMap[trimmedReq], reqs[req])) {
+                badInstalledVersion = pluginMap[req];
+            } else if(trimmedReq === 'cordova' && 
!semver.satisfies(cordovaVersion, reqs[req])) {
+                badInstalledVersion = cordovaVersion;
+            } else if(trimmedReq.indexOf('cordova-') === 0) {
+                // Might be a platform constraint
+                var platform = trimmedReq.substring(8);
+                if(platformMap[platform] && 
!semver.satisfies(platformMap[platform], reqs[req])) {
+                    badInstalledVersion = platformMap[platform];
+                }
+            }
+
+            if(badInstalledVersion) {
+                failed.push({
+                    dependency: trimmedReq,
+                    installed: badInstalledVersion.trim(),
+                    required: reqs[req].trim()
+                });
+            }
+        } else {
+            events.emit('verbose', 'Ignoring invalid plugin dependency 
constraint ' + req + ':' + reqs[req]);
+        }
+    }
+
+    return failed;
+}
+
+function listUnmetRequirements(name, failedRequirements) {
+    events.emit('warn', 'Unmet project requirements for latest version of ' + 
name + ':');
+
+    failedRequirements.forEach(function(req) {
+        events.emit('warn', '    ' + req.dependency + ' (' + req.installed + ' 
installed, ' + req.required + ' required)');
+    });
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/45a235fa/cordova-lib/src/cordova/util.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/util.js b/cordova-lib/src/cordova/util.js
index 2cba201..0f533f5 100644
--- a/cordova-lib/src/cordova/util.js
+++ b/cordova-lib/src/cordova/util.js
@@ -27,7 +27,8 @@ var fs            = require('fs'),
     npm           = require('npm'),
     nopt          = require('nopt'),
     Q             = require('q'),
-    semver        = require('semver');
+    semver        = require('semver'),
+    superspawn    = require('cordova-common').superspawn;
 
 // Global configuration paths
 var global_config_path = process.env['CORDOVA_HOME'];
@@ -63,6 +64,7 @@ exports.isDirectory = isDirectory;
 exports.isUrl = isUrl;
 exports.getLatestMatchingNpmVersion = getLatestMatchingNpmVersion;
 exports.getAvailableNpmVersions = getAvailableNpmVersions;
+exports.getInstalledPlatformsWithVersions = getInstalledPlatformsWithVersions;
 
 function isUrl(value) {
     var u = value && url.parse(value);
@@ -185,6 +187,22 @@ function listPlatforms(project_dir) {
     });
 }
 
+function getInstalledPlatformsWithVersions(project_dir) {
+    var result = {};
+    var platforms_on_fs = listPlatforms(project_dir);
+
+    return Q.all(platforms_on_fs.map(function(p) {
+        return superspawn.maybeSpawn(path.join(project_dir, 'platforms', p, 
'cordova', 'version'), [], { chmod: true })
+        .then(function(v) {
+            result[p] = v || null;
+        }, function(v) {
+            result[p] = 'broken';
+        });
+    })).then(function() {
+        return result;
+    });
+}
+
 // list the directories in the path, ignoring any files
 function findPlugins(pluginPath) {
     var plugins = [],

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/45a235fa/cordova-lib/src/plugman/registry/registry.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/registry/registry.js 
b/cordova-lib/src/plugman/registry/registry.js
index fd633ab..08562d8 100644
--- a/cordova-lib/src/plugman/registry/registry.js
+++ b/cordova-lib/src/plugman/registry/registry.js
@@ -89,20 +89,18 @@ module.exports = {
      */
     info: function(plugin) {
         plugin = plugin.shift();
-        return (Q.nbind(npm.load, npm))
-        .then(function() {
-            // Set cache timout limits to 0 to force npm to call the registry
-            // even when it has a recent .cache.json file.
-            npm.config.set('cache-min', 0);
-            npm.config.set('cache-max', 0);
-            return Q.ninvoke(npm.commands, 'view', [plugin], /* silent = */ 
true );
-        })
-        .then(function(info) {
-            // Plugin info should be accessed as info[version]. If a version
-            // specifier like >=x.y.z was used when calling npm view, info
-            // can contain several versions, but we take the first one here.
-            var version = Object.keys(info)[0];
-            return info[version];
+        return npmhelper.loadWithSettingsThenRestore({
+            'cache-min': 0,
+            'cache-max': 0
+        }, function() {
+            return Q.ninvoke(npm.commands, 'view', [plugin], /* silent = */ 
true )
+            .then(function(info) {
+                // Plugin info should be accessed as info[version]. If a 
version
+                // specifier like >=x.y.z was used when calling npm view, info
+                // can contain several versions, but we take the first one 
here.
+                var version = Object.keys(info)[0];
+                return info[version];
+            });
         });
     }
 };


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

Reply via email to