Repository: cordova-lib
Updated Branches:
  refs/heads/master d8cd62ba2 -> 836f43375


CB-10986: Adding support for scoped npm package plugins

This closes #425


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

Branch: refs/heads/master
Commit: 836f4337553841cca751a92c52360c2f784e5258
Parents: d8cd62b
Author: Richard Knoll <richard.b.kn...@gmail.com>
Authored: Thu Apr 7 10:57:28 2016 -0700
Committer: Richard Knoll <richard.b.kn...@gmail.com>
Committed: Wed Apr 13 11:16:55 2016 -0700

----------------------------------------------------------------------
 cordova-lib/spec-cordova/plugin.spec.js         | 35 ++++++++
 .../spec-cordova/plugin_package_parse.spec.js   | 59 ++++++++++++++
 cordova-lib/spec-cordova/save.spec.js           | 86 ++++++++++++++++++--
 cordova-lib/spec-cordova/util.spec.js           |  1 +
 cordova-lib/spec-plugman/fetch.spec.js          | 16 +++-
 cordova-lib/src/cordova/plugin.js               | 47 ++++++++---
 cordova-lib/src/cordova/plugin_spec_parser.js   | 61 ++++++++++++++
 cordova-lib/src/cordova/util.js                 |  2 +-
 cordova-lib/src/plugman/fetch.js                | 49 ++++++-----
 cordova-lib/src/plugman/install.js              | 21 ++---
 cordova-lib/src/plugman/uninstall.js            | 13 +--
 11 files changed, 326 insertions(+), 64 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/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 c8a0446..ba22d1b 100644
--- a/cordova-lib/spec-cordova/plugin.spec.js
+++ b/cordova-lib/spec-cordova/plugin.spec.js
@@ -190,4 +190,39 @@ describe('plugin end-to-end', function() {
         .fail(errorHandler.errorCallback)
         .fin(done);
     });
+
+    it('should handle scoped npm packages', function(done) {
+        var scopedPackage = '@testscope/' + npmInfoTestPlugin;
+        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, 
npmInfoTestPlugin));
+
+        spyOn(registry, 'info').andReturn(Q({}));
+        addPlugin(scopedPackage, npmInfoTestPlugin, {}, done)
+        .then(function() {
+            // Check to make sure that we are at least trying to get the 
correct package.
+            // This package is not published to npm, so we can't truly do 
end-to-end tests
+
+            expect(registry.info).toHaveBeenCalledWith([scopedPackage]);
+
+            var fetchTarget = plugman.raw.fetch.mostRecentCall.args[0];
+            expect(fetchTarget).toEqual(scopedPackage);
+        })
+        .fail(errorHandler.errorCallback)
+        .fin(done);
+    });
+
+    it('should handle scoped npm packages with given version tags', 
function(done) {
+        var scopedPackage = '@testscope/' + npmInfoTestPlugin + '@latest';
+        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, 
npmInfoTestPlugin));
+
+        spyOn(registry, 'info');
+        addPlugin(scopedPackage, npmInfoTestPlugin, {}, done)
+        .then(function() {
+            expect(registry.info).not.toHaveBeenCalled();
+
+            var fetchTarget = plugman.raw.fetch.mostRecentCall.args[0];
+            expect(fetchTarget).toEqual(scopedPackage);
+        })
+        .fail(errorHandler.errorCallback)
+        .fin(done);
+    });
 });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/cordova-lib/spec-cordova/plugin_package_parse.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/plugin_package_parse.spec.js 
b/cordova-lib/spec-cordova/plugin_package_parse.spec.js
new file mode 100644
index 0000000..7d99e2e
--- /dev/null
+++ b/cordova-lib/spec-cordova/plugin_package_parse.spec.js
@@ -0,0 +1,59 @@
+/**
+    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 pluginSpec = require('../src/cordova/plugin_spec_parser');
+
+describe('methods for parsing npm plugin packages', function() {
+
+    function checkPluginSpecParsing(testString, scope, id, version) {
+        var parsedSpec = pluginSpec.parse(testString);
+        expect(parsedSpec.scope).toEqual(scope);
+        expect(parsedSpec.id).toEqual(id || testString);
+        expect(parsedSpec.version).toEqual(version);
+        expect(parsedSpec.package).toEqual(scope ? scope + id : id);
+    }
+
+    it('should handle package names with no scope or version', function() {
+        checkPluginSpecParsing('test-plugin', null, 'test-plugin', null);
+    });
+    it('should handle package names with a version', function() {
+        checkPluginSpecParsing('test-plugin@1.0.0', null, 'test-plugin', 
'1.0.0');
+        checkPluginSpecParsing('test-plugin@latest', null, 'test-plugin', 
'latest');
+    });
+    it('should handle package names with a scope', function() {
+        checkPluginSpecParsing('@test/test-plugin', '@test/', 'test-plugin', 
null);
+    });
+    it('should handle package names with a scope and a version', function() {
+        checkPluginSpecParsing('@test/test-plugin@1.0.0', '@test/', 
'test-plugin', '1.0.0');
+        checkPluginSpecParsing('@test/test-plugin@latest', '@test/', 
'test-plugin', 'latest');
+    });
+    it('should handle invalid package specs', function() {
+        checkPluginSpecParsing('@nonsense', null, null, null);
+        checkPluginSpecParsing('@/nonsense', null, null, null);
+        checkPluginSpecParsing('@', null, null, null);
+        checkPluginSpecParsing('@nonsense@latest', null, null, null);
+        checkPluginSpecParsing('@/@', null, null, null);
+        checkPluginSpecParsing('/', null, null, null);
+        checkPluginSpecParsing('../../@directory', null, null, null);
+        checkPluginSpecParsing('@directory/../@directory', null, null, null);
+        checkPluginSpecParsing('./directory', null, null, null);
+        checkPluginSpecParsing('directory/directory', null, null, null);
+        checkPluginSpecParsing('http://cordova.apache.org', null, null, null);
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/cordova-lib/spec-cordova/save.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/save.spec.js 
b/cordova-lib/spec-cordova/save.spec.js
index a457890..550af26 100644
--- a/cordova-lib/spec-cordova/save.spec.js
+++ b/cordova-lib/spec-cordova/save.spec.js
@@ -27,6 +27,7 @@ describe('(save flag)', function () {
         shell       = require('shelljs'),
         util        = require('../src/cordova/util'),
         prepare     = require('../src/cordova/prepare'),
+        registry    = require('../src/plugman/registry/registry'),
         PlatformApi = require('../src/platforms/PlatformApiPoly'),
         platform    = rewire('../src/cordova/platform');
 
@@ -47,6 +48,8 @@ describe('(save flag)', function () {
         otherPlatformSpec      = '4.0.0',
         pluginName             = 'cordova-plugin-console',
         pluginVersion          = '1.0.0',
+        pluginName2            = 'cordova-plugin-globalization',
+        pluginVersion2         = '1.0.2',
         pluginGitUrl           = 
'https://github.com/apache/cordova-plugin-console.git',
         pluginOldName          = 'org.apache.cordova.console',
         pluginOldVersion       = '0.2.11',
@@ -74,6 +77,22 @@ describe('(save flag)', function () {
         });
     }
 
+    /**
+     * For testing scoped packages. We don't have those packages published, so 
just
+     * redirect the registry calls to their un-scoped counterparts
+     */
+    function redirectRegistryCalls(id) {
+        var originalFetch = registry.fetch;
+        spyOn(registry, 'fetch').andCallFake(function(package) {
+            return originalFetch([id]);
+        });
+
+        var originalInfo = registry.info;
+        spyOn(registry, 'info').andCallFake(function(package) {
+            return originalInfo([id]);
+        });
+    }
+
     beforeEach(function (done) {
         // initial cleanup
         shell.rm('-rf', tempPath);
@@ -110,8 +129,8 @@ describe('(save flag)', function () {
     describe('preparing fixtures', function () {
         it('cloning "old" platform', function (done) {
             shell.rm('-rf', platformLocalPathOld);
-            shell.exec('git clone ' + platformGitUrl + ' ' + 
platformLocalPathOld + 
-            ' && cd ' + platformLocalPathOld + 
+            shell.exec('git clone ' + platformGitUrl + ' ' + 
platformLocalPathOld +
+            ' && cd ' + platformLocalPathOld +
             ' && git reset --hard ' + platformVersionOld, { silent: true }, 
function (err) {
                 expect(err).toBe(0);
                 done();
@@ -120,8 +139,8 @@ describe('(save flag)', function () {
 
         it('cloning "new" platform', function (done) {
             shell.rm('-rf', platformLocalPathNew);
-            shell.exec('git clone ' + platformGitUrl + ' ' + 
platformLocalPathNew + 
-            ' && cd ' + platformLocalPathNew + 
+            shell.exec('git clone ' + platformGitUrl + ' ' + 
platformLocalPathNew +
+            ' && cd ' + platformLocalPathNew +
             ' && git reset --hard ' + platformVersionNew, { silent: true }, 
function (err) {
                 expect(err).toBe(0);
                 done();
@@ -130,8 +149,8 @@ describe('(save flag)', function () {
 
         it('cloning "newer" platform', function (done) {
             shell.rm('-rf', platformLocalPathNewer);
-            shell.exec('git clone ' + platformGitUrl + ' ' + 
platformLocalPathNewer + 
-            ' && cd ' + platformLocalPathNewer + 
+            shell.exec('git clone ' + platformGitUrl + ' ' + 
platformLocalPathNewer +
+            ' && cd ' + platformLocalPathNewer +
             ' && git reset --hard ' + platformVersionNewer, { silent: true }, 
function (err) {
                 expect(err).toBe(0);
                 done();
@@ -337,7 +356,7 @@ describe('(save flag)', function () {
                 expect(false).toBe(true);
                 done();
             }).finally(function (err) {
-                
+
             });
         }, TIMEOUT);
     });
@@ -420,6 +439,24 @@ describe('(save flag)', function () {
                 done();
             });
         }, TIMEOUT);
+
+        it('spec.16.1 save scoped registry packages as spec', function (done) {
+            redirectRegistryCalls(pluginName + '@' + pluginVersion);
+            var scopedPackage = '@test-scope/' + pluginName;
+
+            platform('add', platformLocalPathNewer)
+            .then(function () {
+                return cordova.raw.plugin('add', scopedPackage + '@' + 
pluginVersion, { 'save': true });
+            }).then(function () {
+                expect(registry.fetch).toHaveBeenCalledWith([scopedPackage + 
'@' + pluginVersion]);
+                expect(helpers.getPluginSpec(appPath, 
pluginName)).toBe(scopedPackage + '@~' + pluginVersion);
+                done();
+            }).catch(function (err) {
+                expect(true).toBe(false);
+                console.log(err.message);
+                done();
+            });
+        }, TIMEOUT);
     });
 
     describe('plugin remove --save', function () {
@@ -519,6 +556,24 @@ describe('(save flag)', function () {
                 done();
             });
         }, TIMEOUT);
+
+        it('spec.22.1 should update config with a spec that includes the scope 
for scoped plugins', function (done) {
+            // Fetching globalization rather than console to avoid conflicts 
with earlier tests
+            redirectRegistryCalls(pluginName2 + '@' + pluginVersion2);
+            var scopedPackage = '@test-scope/' + pluginName2;
+            cordova.raw.plugin('add', scopedPackage + '@' + pluginVersion2)
+            .then(function () {
+                return cordova.raw.plugin('save');
+            }).then(function () {
+                expect(registry.fetch).toHaveBeenCalledWith([scopedPackage + 
'@' + pluginVersion2]);
+                expect(helpers.getPluginSpec(appPath, 
pluginName2)).toBe(scopedPackage + '@~' + pluginVersion2);
+                done();
+            }).catch(function (err) {
+                expect(true).toBe(false);
+                console.log(err.message);
+                done();
+            });
+        }, TIMEOUT);
     });
 
     describe('prepare', function () {
@@ -542,6 +597,23 @@ describe('(save flag)', function () {
             });
         }, TIMEOUT);
 
+        it('spec.23.1 should restore scoped plugins', function (done) {
+            redirectRegistryCalls(pluginName2 + '@~' + pluginVersion2);
+            var scopedPackage = '@test-scope/' + pluginName2;
+            helpers.setEngineSpec(appPath, platformName, 
platformLocalPathNewer);
+            helpers.setPluginSpec(appPath, pluginName2, scopedPackage + '@~' + 
pluginVersion2);
+            prepare()
+            .then(function () {
+                expect(registry.fetch).toHaveBeenCalledWith([scopedPackage + 
'@~' + pluginVersion2]);
+                expect(path.join(appPath, 'plugins', pluginName2)).toExist();
+                done();
+            }).catch(function (err) {
+                expect(true).toBe(false);
+                console.log(err.message);
+                done();
+            });
+        }, TIMEOUT);
+
         it('spec.24 should restore only specified platform', function (done) {
             helpers.setEngineSpec(appPath, platformName, 
platformLocalPathNewer);
             helpers.setEngineSpec(appPath, otherPlatformName, 
otherPlatformSpec);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/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 314de3b..e0799f7 100644
--- a/cordova-lib/spec-cordova/util.spec.js
+++ b/cordova-lib/spec-cordova/util.spec.js
@@ -204,6 +204,7 @@ describe('util module', function() {
             expect(res.indexOf('CVS')).toEqual(-1);
         });
     });
+
     describe('preprocessOptions method', function() {
 
         var isCordova, listPlatforms;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/cordova-lib/spec-plugman/fetch.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/fetch.spec.js 
b/cordova-lib/spec-plugman/fetch.spec.js
index 8b5c625..9aa04ef 100644
--- a/cordova-lib/spec-plugman/fetch.spec.js
+++ b/cordova-lib/spec-plugman/fetch.spec.js
@@ -44,7 +44,7 @@ describe('fetch', function() {
         }).fin(done);
     }
     /*
-     * Taking out the following test. Fetch has a copyPlugin method that uses 
existsSync to see if a plugin already exists in the plugins folder. If the 
plugin exists in the plugins directory for the cordova project, it won't be 
copied over. This test fails now due it always returning true for existsSync. 
+     * Taking out the following test. Fetch has a copyPlugin method that uses 
existsSync to see if a plugin already exists in the plugins folder. If the 
plugin exists in the plugins directory for the cordova project, it won't be 
copied over. This test fails now due it always returning true for existsSync.
     describe('plugin in a dir with spaces', function() {
         it('should copy locally-available plugin to plugins directory when 
spaces in path', function(done) {
             // XXX: added this because plugman tries to fetch from registry 
when plugin folder does not exist
@@ -251,7 +251,7 @@ describe('fetch', function() {
 
         var srcDir = path.join(__dirname, 'plugins/recursivePlug');
         var appDir = path.join(__dirname, 'plugins/recursivePlug/demo');
-        
+
         if(/^win/.test(process.platform)) {
             it('should copy all but the /demo/ folder',function(done) {
                 var cp = spyOn(shell, 'cp');
@@ -312,5 +312,17 @@ describe('fetch', function() {
                 expect(1).toBe(1);
             });
         });
+        it('should fetch plugins that are scoped packages', function(done) {
+            var scopedPackage = '@testcope/dummy-plugin';
+            wrapper(fetch(scopedPackage, temp, { expected_id: test_plugin_id 
}), done, function() {
+                expect(sFetch).toHaveBeenCalledWith([scopedPackage]);
+            });
+        });
+        it('should fetch plugins that are scoped packages and have versions 
specified', function(done) {
+            var scopedPackage = '@testcope/dummy-plugin@latest';
+            wrapper(fetch(scopedPackage, temp, { expected_id: test_plugin_id 
}), done, function() {
+                expect(sFetch).toHaveBeenCalledWith([scopedPackage]);
+            });
+        });
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/cordova-lib/src/cordova/plugin.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/plugin.js 
b/cordova-lib/src/cordova/plugin.js
index 87d5c74..5136d3f 100644
--- a/cordova-lib/src/cordova/plugin.js
+++ b/cordova-lib/src/cordova/plugin.js
@@ -29,6 +29,7 @@ var cordova_util  = require('./util'),
     PluginInfoProvider = require('cordova-common').PluginInfoProvider,
     plugman       = require('../plugman/plugman'),
     pluginMapper  = require('cordova-registry-mapper').newToOld,
+    pluginSpec    = require('./plugin_spec_parser'),
     events        = require('cordova-common').events,
     metadata      = require('../plugman/util/metadata'),
     registry      = require('../plugman/registry/registry'),
@@ -191,10 +192,22 @@ module.exports = function plugin(command, targets, opts) {
                             if(saveToConfigXmlOn(config_json, opts)){
                                 var src = parseSource(target, opts);
                                 var attributes = {
-                                    name: pluginInfo.id,
-                                    spec: src ? src : '~' + pluginInfo.version
+                                    name: pluginInfo.id
                                 };
 
+                                if (src) {
+                                    attributes.spec = src;
+                                } else {
+                                    var ver = '~' + pluginInfo.version;
+                                    // Scoped packages need to have the 
package-spec along with the version
+                                    var parsedSpec = pluginSpec.parse(target);
+                                    if (parsedSpec.scope) {
+                                        attributes.spec = parsedSpec.package + 
'@' + ver;
+                                    } else {
+                                        attributes.spec = ver;
+                                    }
+                                }
+
                                 xml = cordova_util.projectConfig(projectRoot);
                                 cfg = new ConfigParser(xml);
                                 cfg.removePlugin(pluginInfo.id);
@@ -294,11 +307,11 @@ 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];
+    var parsedSpec = pluginSpec.parse(target);
 
-    if (version || cordova_util.isUrl(id) || cordova_util.isDirectory(id)) {
+    var id = parsedSpec.package || target;
+
+    if (parsedSpec.version || cordova_util.isUrl(id) || 
cordova_util.isDirectory(id)) {
         return Q(target);
     }
 
@@ -306,7 +319,7 @@ function determinePluginTarget(projectRoot, cfg, target, 
fetchOptions) {
     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)) {
+    if (cordova_util.isUrl(ver) || cordova_util.isDirectory(ver) || 
pluginSpec.parse(ver).scope) {
         return Q(ver);
     }
 
@@ -410,11 +423,12 @@ function getPluginVariables(variables){
 }
 
 function getVersionFromConfigFile(plugin, cfg){
-    var pluginEntry = cfg.getPlugin(plugin);
-    if (!pluginEntry) {
+    var parsedSpec = pluginSpec.parse(plugin);
+    var pluginEntry = cfg.getPlugin(parsedSpec.id);
+    if (!pluginEntry && !parsedSpec.scope) {
         // If the provided plugin id is in the new format (e.g. 
cordova-plugin-camera), it might be stored in config.xml
         // under the old format (e.g. org.apache.cordova.camera), so check for 
that.
-        var oldStylePluginId = pluginMapper[plugin];
+        var oldStylePluginId = pluginMapper[parsedSpec.id];
         if (oldStylePluginId) {
             pluginEntry = cfg.getPlugin(oldStylePluginId);
         }
@@ -506,13 +520,18 @@ function getSpec(pluginSource, projectRoot, pluginName) {
     }
 
     var version = null;
+    var scopedPackage = null;
     if (pluginSource.hasOwnProperty('id')) {
         // Note that currently version is only saved here if it was explicitly 
specified when the plugin was added.
-        var parts = pluginSource.id.split('@');
-        version = parts[1];
+        var parsedSpec = pluginSpec.parse(pluginSource.id);
+        version = parsedSpec.version;
         if (version) {
             version = versionString(version);
         }
+
+        if (parsedSpec.scope) {
+            scopedPackage = parsedSpec.package;
+        }
     }
 
     if (!version) {
@@ -530,6 +549,10 @@ function getSpec(pluginSource, projectRoot, pluginName) {
         }
     }
 
+    if (scopedPackage) {
+        version = scopedPackage + '@' + version;
+    }
+
     return version;
 }
 

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/cordova-lib/src/cordova/plugin_spec_parser.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/plugin_spec_parser.js 
b/cordova-lib/src/cordova/plugin_spec_parser.js
new file mode 100644
index 0000000..a7b38ed
--- /dev/null
+++ b/cordova-lib/src/cordova/plugin_spec_parser.js
@@ -0,0 +1,61 @@
+/**
+    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.
+*/
+
+// npm packages follow the pattern of (@scope/)?package(@spec)? where scope 
and tag are optional
+var NPM_SPEC_REGEX = /^(@[^\/]+\/)?([^@\/]+)(?:@(.+))?$/;
+
+module.exports.parse = parse;
+
+/**
+ * Represents a parsed specification for a plugin
+ * @class
+ * @param {String} raw      The raw specification (i.e. provided by the user)
+ * @param {String} scope    The scope of the package if this is an npm package
+ * @param {String} id       The id of the package if this is an npm package
+ * @param {String} version  The version specified for the package if this is 
an npm package
+ */
+function PluginSpec(raw, scope, id, version) {
+    /** @member {String|null} The npm scope of the plugin spec or null if it 
does not have one */
+    this.scope = scope || null;
+
+    /** @member {String|null} The id of the plugin or the raw plugin spec if 
it is not an npm package */
+    this.id = id || raw;
+
+    /** @member {String|null} The specified version of the plugin or null if 
no version was specified */
+    this.version = version || null;
+
+    /** @member {String|null} The npm package of the plugin (with scope) or 
null if this is not a spec for an npm package */
+    this.package = (scope ? scope + id : id) || null;
+}
+
+/**
+ * Tries to parse the given string as an npm-style package specification of
+ * the form (@scope/)?package(@version)? and return the various parts.
+ *
+ * @param {String} raw  The string to be parsed
+ * @return {PluginSpec}  The parsed plugin spec
+ */
+function parse(raw) {
+    var split = NPM_SPEC_REGEX.exec(raw);
+    if (split) {
+        return new PluginSpec(raw, split[1], split[2], split[3]);
+    }
+
+    return new PluginSpec(raw);
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/cordova-lib/src/cordova/util.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/util.js b/cordova-lib/src/cordova/util.js
index 0f533f5..eb36330 100644
--- a/cordova-lib/src/cordova/util.js
+++ b/cordova-lib/src/cordova/util.js
@@ -400,4 +400,4 @@ function getAvailableNpmVersions(module_name) {
             return result[Object.keys(result)[0]].versions;
         });
     });
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/cordova-lib/src/plugman/fetch.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/fetch.js b/cordova-lib/src/plugman/fetch.js
index a6f5eda..9583688 100644
--- a/cordova-lib/src/plugman/fetch.js
+++ b/cordova-lib/src/plugman/fetch.js
@@ -31,7 +31,8 @@ var shell   = require('shelljs'),
     Q       = require('q'),
     registry = require('./registry/registry'),
     pluginMappernto = require('cordova-registry-mapper').newToOld,
-    pluginMapperotn = require('cordova-registry-mapper').oldToNew;
+    pluginMapperotn = require('cordova-registry-mapper').oldToNew,
+    pluginSpec      = require('../cordova/plugin_spec_parser');
 var cordovaUtil = require('../cordova/util');
 
 // Cache of PluginInfo objects for plugins in search path.
@@ -132,30 +133,30 @@ function fetchPlugin(plugin_src, plugins_dir, options) {
                 ));
             }
             // If not found in local search path, fetch from the registry.
-            var splitVersion = plugin_src.split('@');
-            var newID = pluginMapperotn[splitVersion[0]];
+            var parsedSpec = pluginSpec.parse(plugin_src);
+            var newID = parsedSpec.scope ? null : 
pluginMapperotn[parsedSpec.id];
             if(newID) {
                 plugin_src = newID;
-                if (splitVersion[1]) {
-                    plugin_src += '@'+splitVersion[1];
+                if (parsedSpec.version) {
+                    plugin_src += '@' + parsedSpec.version;
                 }
             }
             var P, skipCopyingPlugin;
-            plugin_dir = path.join(plugins_dir, splitVersion[0]);
+            plugin_dir = path.join(plugins_dir, parsedSpec.id);
             // if the plugin has already been fetched, use it.
             if (fs.existsSync(plugin_dir)) {
                 P = Q(plugin_dir);
                 skipCopyingPlugin = true;
             } else {
                 // if the plugin alias has already been fetched, use it.
-                var alias = pluginMappernto[splitVersion[0]] || newID;
+                var alias = parsedSpec.scope ? null : 
pluginMappernto[parsedSpec.id] || newID;
                 if (alias && fs.existsSync(path.join(plugins_dir, alias))) {
-                    events.emit('warn', 'Found '+alias+' is already fetched. 
Skipped fetching '+splitVersion[0]);
+                    events.emit('warn', 'Found '+alias+' is already fetched. 
Skipped fetching ' + parsedSpec.id);
                     P = Q(path.join(plugins_dir, alias));
                     skipCopyingPlugin = true;
                 } else {
                     if (newID) {
-                        events.emit('warn', 'Notice: ' + splitVersion[0] + ' 
has been automatically converted to ' + newID + ' to be fetched from npm. This 
is due to our old plugins registry shutting down.');
+                        events.emit('warn', 'Notice: ' + parsedSpec.id + ' has 
been automatically converted to ' + newID + ' to be fetched from npm. This is 
due to our old plugins registry shutting down.');
                     }
                     P = registry.fetch([plugin_src]);
                     skipCopyingPlugin = false;
@@ -205,16 +206,17 @@ function fetchPlugin(plugin_src, plugins_dir, options) {
 // Helper function for checking expected plugin IDs against reality.
 function checkID(expectedIdAndVersion, pinfo) {
     if (!expectedIdAndVersion) return;
-    var expectedId = expectedIdAndVersion.split('@')[0];
-    var expectedVersion = expectedIdAndVersion.split('@')[1];
-    if (expectedId != pinfo.id) {
-        var alias = pluginMappernto[expectedId] || pluginMapperotn[expectedId];
+
+    var parsedSpec = pluginSpec.parse(expectedIdAndVersion);
+
+    if (parsedSpec.id != pinfo.id) {
+        var alias = parsedSpec.scope ? null : pluginMappernto[parsedSpec.id] 
|| pluginMapperotn[parsedSpec.id];
         if (alias !== pinfo.id) {
-            throw new Error('Expected plugin to have ID "' + expectedId + '" 
but got "' + pinfo.id + '".');
+            throw new Error('Expected plugin to have ID "' + parsedSpec.id + 
'" but got "' + pinfo.id + '".');
         }
     }
-    if (expectedVersion && !semver.satisfies(pinfo.version, expectedVersion)) {
-        throw new Error('Expected plugin ' + pinfo.id + ' to satisfy version 
"' + expectedVersion + '" but got "' + pinfo.version + '".');
+    if (parsedSpec.version && !semver.satisfies(pinfo.version, 
parsedSpec.version)) {
+        throw new Error('Expected plugin ' + pinfo.id + ' to satisfy version 
"' + parsedSpec.version + '" but got "' + pinfo.version + '".');
     }
 }
 
@@ -258,16 +260,11 @@ function loadLocalPlugins(searchpath, pluginInfoProvider) 
{
 //      org.apache.cordova.file@>=1.2.0
 function findLocalPlugin(plugin_src, searchpath, pluginInfoProvider) {
     loadLocalPlugins(searchpath, pluginInfoProvider);
-    var id = plugin_src;
-    var versionspec = '*';
-    if (plugin_src.indexOf('@') != -1) {
-        var parts = plugin_src.split('@');
-        id = parts[0];
-        versionspec = parts[1];
-    }
+    var parsedSpec = pluginSpec.parse(plugin_src);
+    var versionspec = parsedSpec.version || '*';
 
     var latest = null;
-    var versions = localPlugins.plugins[id];
+    var versions = localPlugins.plugins[parsedSpec.id];
 
     if (!versions) return null;
 
@@ -313,7 +310,7 @@ function copyPlugin(pinfo, plugins_dir, link) {
     shell.rm('-rf', dest);
 
     if(!link && dest.indexOf(path.resolve(plugin_dir)) === 0) {
-        
+
         if(/^win/.test(process.platform)) {
             /*
                 [CB-10423]
@@ -330,7 +327,7 @@ function copyPlugin(pinfo, plugins_dir, link) {
             shell.mkdir('-p', dest);
             events.emit('verbose', 'Copying plugin "' + resolvedSrcPath + '" 
=> "' + dest + '"');
             events.emit('verbose', 'Skipping folder "' + relativeRootFolder + 
'"');
-            
+
             filenames.forEach(function(elem) {
                 shell.cp('-R', path.join(resolvedSrcPath,elem) , dest);
             });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/cordova-lib/src/plugman/install.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/install.js 
b/cordova-lib/src/plugman/install.js
index c0a12c1..efbbda1 100644
--- a/cordova-lib/src/plugman/install.js
+++ b/cordova-lib/src/plugman/install.js
@@ -37,6 +37,7 @@ var path = require('path'),
     HooksRunner = require('../hooks/HooksRunner'),
     isWindows = (os.platform().substr(0,3) === 'win'),
     pluginMapper = require('cordova-registry-mapper'),
+    pluginSpec = require('../cordova/plugin_spec_parser'),
     cordovaUtil = require('../cordova/util');
 
 var superspawn = require('cordova-common').superspawn;
@@ -72,7 +73,7 @@ module.exports = function installPlugin(platform, 
project_dir, id, plugins_dir,
     plugins_dir = cordovaUtil.convertToRealPathSafe(plugins_dir);
     options = options || {};
     if (!options.hasOwnProperty('is_top_level')) options.is_top_level = true;
-    
+
     plugins_dir = plugins_dir || path.join(project_dir, 'cordova', 'plugins');
 
     if (!platform_modules[platform]) {
@@ -89,36 +90,36 @@ module.exports = function installPlugin(platform, 
project_dir, id, plugins_dir,
 // possible options: subdir, cli_variables, www_dir, git_ref, is_top_level
 // Returns a promise.
 function possiblyFetch(id, plugins_dir, options) {
-    // Split @Version from the plugin id if it exists.
-    var splitVersion = id.split('@');
+    var parsedSpec = pluginSpec.parse(id);
     //Check if a mapping exists for the plugin id
     //if it does, convert id to new name id
-    var newId = pluginMapper.oldToNew[splitVersion[0]];
+    var newId = parsedSpec.scope ? null : pluginMapper.oldToNew[parsedSpec.id];
     if(newId) {
-        if(splitVersion[1]) {
-            id = newId +'@'+splitVersion[1];
+        if(parsedSpec.version) {
+            id = newId + '@' + parsedSpec.version;
         } else {
             id = newId;
         }
     }
+
     // if plugin is a relative path, check if it already exists
-    var plugin_src_dir = isAbsolutePath(id) ? id : path.join(plugins_dir, 
splitVersion[0]);
+    var plugin_src_dir = isAbsolutePath(id) ? id : path.join(plugins_dir, 
parsedSpec.id);
 
     // Check that the plugin has already been fetched.
     if (fs.existsSync(plugin_src_dir)) {
         return Q(plugin_src_dir);
     }
 
-    var alias = pluginMapper.newToOld[splitVersion[0]] || newId;
+    var alias =  parsedSpec.scope ? null : 
pluginMapper.newToOld[parsedSpec.id] || newId;
     // if the plugin alias has already been fetched, use it.
     if (alias && fs.existsSync(path.join(plugins_dir, alias))) {
-        events.emit('warn', 'Found ' + alias + ' is already fetched, so it is 
installed instead of '+splitVersion[0]);
+        events.emit('warn', 'Found ' + alias + ' is already fetched, so it is 
installed instead of ' + parsedSpec.id);
         return Q(path.join(plugins_dir, alias));
     }
 
     // if plugin doesnt exist, use fetch to get it.
     if (newId) {
-        events.emit('warn', 'Notice: ' + splitVersion[0] + ' has been 
automatically converted to ' + newId + ' and fetched from npm. This is due to 
our old plugins registry shutting down.');
+        events.emit('warn', 'Notice: ' + parsedSpec.id + ' has been 
automatically converted to ' + newId + ' and fetched from npm. This is due to 
our old plugins registry shutting down.');
     }
     var opts = underscore.extend({}, options, {
         client: 'plugman'

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/836f4337/cordova-lib/src/plugman/uninstall.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/uninstall.js 
b/cordova-lib/src/plugman/uninstall.js
index f481950..ae88889 100644
--- a/cordova-lib/src/plugman/uninstall.js
+++ b/cordova-lib/src/plugman/uninstall.js
@@ -33,7 +33,8 @@ var path = require('path'),
     promiseutil = require('../util/promise-util'),
     HooksRunner = require('../hooks/HooksRunner'),
     cordovaUtil = require('../cordova/util'),
-    pluginMapper = require('cordova-registry-mapper').oldToNew;
+    pluginMapper = require('cordova-registry-mapper').oldToNew,
+    pluginSpec = require('../cordova/plugin_spec_parser');
 
 var superspawn = require('cordova-common').superspawn;
 var PlatformJson = require('cordova-common').PlatformJson;
@@ -145,10 +146,10 @@ module.exports.uninstallPlugin = function(id, 
plugins_dir, options) {
         var deps = pluginInfo.getDependencies();
         var deps_path;
         deps.forEach(function (d) {
-            var splitVersion = d.id.split('@');
-            deps_path = path.join(plugin_dir, '..', splitVersion[0]);
+            var parsedSpec = pluginSpec.parse(d.id);
+            deps_path = path.join(plugin_dir, '..', parsedSpec.id);
             if (!fs.existsSync(deps_path)) {
-                var newId = pluginMapper[splitVersion[0]];
+                var newId = parsedSpec.scope ? null : 
pluginMapper[parsedSpec.id];
                 if (newId && toDelete.indexOf(newId) === -1) {
                    events.emit('verbose', 'Automatically converted ' + d.id + 
' to ' + newId + 'for uninstallation.');
                    toDelete.push(newId);
@@ -269,8 +270,8 @@ function runUninstallPlatform(actions, platform, 
project_dir, plugin_dir, plugin
 
             //try to convert ID if old-id path doesn't exist.
             if (!fs.existsSync(dependent_path)) {
-                var splitVersion = dangler.split('@');
-                var newId = pluginMapper[splitVersion[0]];
+                var parsedSpec = pluginSpec.parse(dangler);
+                var newId = parsedSpec.scope ? null : 
pluginMapper[parsedSpec.id];
                 if(newId) {
                     dependent_path = path.join(plugins_dir, newId);
                     events.emit('verbose', 'Automatically converted ' + 
dangler + ' to ' + newId + 'for uninstallation.');


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

Reply via email to