You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by st...@apache.org on 2016/05/09 21:18:37 UTC

[2/2] cordova-lib git commit: CB-9858 merging initial fetch work for plugin and platform fetching

CB-9858 merging initial fetch work for plugin and platform fetching


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

Branch: refs/heads/master
Commit: 6025a5f23c00284fb3416d8042ff5d738bf03db3
Parents: b21bcc3
Author: Steve Gill <st...@gmail.com>
Authored: Mon May 9 14:18:21 2016 -0700
Committer: Steve Gill <st...@gmail.com>
Committed: Mon May 9 14:18:21 2016 -0700

----------------------------------------------------------------------
 .gitignore                                |    1 +
 .travis.yml                               |    2 +
 appveyor.yml                              |    2 +
 cordova-fetch/.jshintrc                   |   12 +
 cordova-fetch/README.md                   |   36 +
 cordova-fetch/RELEASENOTES.md             |   22 +
 cordova-fetch/index.js                    |  236 ++++
 cordova-fetch/package.json                |   43 +
 cordova-fetch/spec/fetch.spec.js          |  300 +++++
 cordova-fetch/spec/helpers.js             |   16 +
 cordova-fetch/spec/support/jasmine.json   |   11 +
 cordova-fetch/spec/testpkg.json           |   11 +
 cordova-lib/spec-cordova/helpers.js       |    3 +
 cordova-lib/spec-cordova/platform.spec.js |  108 ++
 cordova-lib/src/cordova/platform.js       |   31 +-
 cordova-lib/src/cordova/plugin.js         | 1608 ++++++++++++------------
 cordova-lib/src/plugman/fetch.js          |   35 +-
 cordova-lib/src/plugman/plugman.js        |   13 +-
 cordova-lib/src/plugman/uninstall.js      |   29 +-
 19 files changed, 1700 insertions(+), 819 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index ea05522..dae2aca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ src/plugman/defaults.json
 cordova-lib/src/plugman/defaults.json
 cordova-lib/spec-plugman/plugins/recursivePlug/demo/fetch.json
 cordova-lib/spec-plugman/plugins/recursivePlug/demo/test-recursive
+cordova-fetch/test/temp

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index bb404a1..e9f15ee 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,8 +12,10 @@ install:
   - npm link ../cordova-js
   - npm link ../cordova-common
   - npm link ../cordova-serve
+  - npm link ../cordova-fetch
   - npm install
 
 script:
   - "(cd ../cordova-common && npm test)"
+  - "(cd ../cordova-common && npm test)"
   - "npm run ci"

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/appveyor.yml
----------------------------------------------------------------------
diff --git a/appveyor.yml b/appveyor.yml
index 36e2b26..dc26ed4 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -7,6 +7,7 @@ install:
   - npm link ../cordova-js
   - npm link ../cordova-common
   - npm link ../cordova-serve
+  - npm link ../cordova-fetch
   - npm install
 
 build: off
@@ -15,4 +16,5 @@ test_script:
   - node --version
   - npm --version
   - "cd ../cordova-common && npm test"
+  - "cd ../cordova-fetch && npm test"
   - "cd ../cordova-lib && npm test"

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/.jshintrc
----------------------------------------------------------------------
diff --git a/cordova-fetch/.jshintrc b/cordova-fetch/.jshintrc
new file mode 100644
index 0000000..62edb5c
--- /dev/null
+++ b/cordova-fetch/.jshintrc
@@ -0,0 +1,12 @@
+{
+  "node": true,
+  "bitwise": true,
+  "undef": true,
+  "trailing": true,
+  "quotmark": true,
+  "indent": 4,
+  "unused": "vars",
+  "latedef": "nofunc",
+  "-W030": false,
+  "jasmine": true
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/README.md
----------------------------------------------------------------------
diff --git a/cordova-fetch/README.md b/cordova-fetch/README.md
new file mode 100644
index 0000000..5cc75e9
--- /dev/null
+++ b/cordova-fetch/README.md
@@ -0,0 +1,36 @@
+<!--
+#
+# 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.
+#
+-->
+
+# cordova-fetch
+
+This module is used for fetching modules from npm and gitURLs. It fetches the modules via `npm install`. 
+
+Usage:
+```
+var fetch = require('cordova-fetch');
+
+fetch(spec, dest, opts);
+```
+
+`spec` can be a string containg a npm `packageID` or a `git URL`. 
+`dest` is string of the directory location you wish to `npm install` these modules.
+`opts` is an Object of options cordova fetch handles. Currently, fetch only support the `save` option.
+    eg. `{'save':true}`

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/RELEASENOTES.md
----------------------------------------------------------------------
diff --git a/cordova-fetch/RELEASENOTES.md b/cordova-fetch/RELEASENOTES.md
new file mode 100644
index 0000000..305cdc6
--- /dev/null
+++ b/cordova-fetch/RELEASENOTES.md
@@ -0,0 +1,22 @@
+<!--
+#
+# 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.
+#
+-->
+# Cordova-fetch Release Notes
+

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/index.js
----------------------------------------------------------------------
diff --git a/cordova-fetch/index.js b/cordova-fetch/index.js
new file mode 100644
index 0000000..0e3c3fe
--- /dev/null
+++ b/cordova-fetch/index.js
@@ -0,0 +1,236 @@
+/**
+ 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 Q = require('q');
+var shell = require('shelljs');
+var superspawn = require('cordova-common').superspawn;
+var events = require('cordova-common').events;
+var depls = require('dependency-ls');
+var path = require('path');
+var fs = require('fs');
+var CordovaError = require('cordova-common').CordovaError;
+var isUrl = require('is-url');
+
+/* 
+ * A function that npm installs a module from npm or a git url
+ *
+ * @param {String} target   the packageID or git url
+ * @param {String} dest     destination of where to install the module
+ * @param {Object} opts     [opts={save:true}] options to pass to fetch module
+ *
+ * @return {String|Promise}    Returns string of the absolute path to the installed module.
+ *
+ */
+module.exports = function(target, dest, opts) {
+    var fetchArgs = ['install'];
+    opts = opts || {};
+    var tree1;
+
+    //check if npm is installed
+    return isNpmInstalled()
+    .then(function() {
+        if(dest && target) {
+            //add target to fetchArgs Array
+            fetchArgs.push(target);
+        
+            //append node_modules to dest if it doesn't come included
+            if (path.basename(dest) !== 'node_modules') {
+            dest = path.resolve(path.join(dest, 'node_modules'));
+            }
+        
+            //create dest if it doesn't exist
+            if(!fs.existsSync(dest)) {
+                shell.mkdir('-p', dest);         
+            } 
+
+        } else return Q.reject(new CordovaError('Need to supply a target and destination'));
+
+        //set the directory where npm install will be run
+        opts.cwd = dest;
+
+        //if user added --save flag, pass it to npm install command
+        if(opts.save) {
+            events.emit('verbose', 'saving');
+            fetchArgs.push('--save'); 
+        } 
+    
+
+        //Grab json object of installed modules before npm install
+        return depls(dest);
+    })
+    .then(function(depTree) {
+        tree1 = depTree;
+
+        //install new module
+        return superspawn.spawn('npm', fetchArgs, opts);
+    })
+    .then(function(output) {
+        //Grab object of installed modules after npm install
+        return depls(dest);
+    })
+    .then(function(depTree2) {
+        var tree2 = depTree2;
+
+        //getJsonDiff will fail if the module already exists in node_modules.
+        //Need to use trimID in that case. 
+        //This could happen on a platform update.
+        var id = getJsonDiff(tree1, tree2) || trimID(target); 
+
+        return getPath(id, dest);
+    }) 
+    .fail(function(err){
+        return Q.reject(new CordovaError(err));
+    });
+};
+
+
+/*
+ * Takes two JSON objects and returns the key of the new property as a string.
+ * If a module already exists in node_modules, the diff will be blank. 
+ * cordova-fetch will use trimID in that case.
+ *
+ * @param {Object} obj1     json object representing installed modules before latest npm install
+ * @param {Object} obj2     json object representing installed modules after latest npm install
+ *
+ * @return {String}         String containing the key value of the difference between the two objects
+ *
+ */
+function getJsonDiff(obj1, obj2) {
+    var result = '';
+
+    //regex to filter out peer dependency warnings from result
+    var re = /UNMET PEER DEPENDENCY/;
+
+    for (var key in obj2) {
+        //if it isn't a unmet peer dependency, continue
+        if (key.search(re) === -1) {
+            if(obj2[key] != obj1[key]) result = key;
+        }
+    }
+    return result;
+}
+
+/*
+ * Takes the specified target and returns the moduleID
+ * If the git repoName is different than moduleID, then the 
+ * output from this function will be incorrect. This is the 
+ * backup way to get ID. getJsonDiff is the preferred way to 
+ * get the moduleID of the installed module.
+ *
+ * @param {String} target    target that was passed into cordova-fetch.
+ *                           can be moduleID, moduleID@version or gitURL
+ *
+ * @return {String} ID       moduleID without version.
+ */
+function trimID(target) {
+    var parts;
+
+    //If GITURL, set target to repo name
+    if (isUrl(target)) {
+        var re = /.*\/(.*).git/;
+        parts = target.match(re);
+        target = parts[1];
+    }
+    
+    //strip away everything after '@'
+    if(target.indexOf('@') != -1) {
+        parts = target.split('@');
+        target = parts[0];
+    }        
+    
+    return target;
+}
+
+/* 
+ * Takes the moduleID and destination and returns an absolute path to the module
+ *
+ * @param {String} id       the packageID
+ * @param {String} dest     destination of where to fetch the modules
+ *
+ * @return {String|Error}  Returns the absolute url for the module or throws a error
+ *
+ */
+
+function getPath(id, dest) {
+    var finalDest = path.resolve(path.join(dest, id));
+    
+    //Sanity check it exists
+    if(fs.existsSync(finalDest)){
+        return finalDest;
+    } else return Q.reject(new CordovaError('Failed to get absolute path to installed module'));
+}
+
+
+/*
+ * Checks to see if npm is installed on the users system
+ * @return {Promise|Error} Returns true or a cordova error.
+ */
+
+function isNpmInstalled() {
+    if(!shell.which('npm')) {
+        return Q.reject(new CordovaError('"npm" command line tool is not installed: make sure it is accessible on your PATH.'));
+    }
+    return Q();
+}
+
+/* 
+ * A function that deletes the target from node_modules and runs npm uninstall 
+ *
+ * @param {String} target   the packageID
+ * @param {String} dest     destination of where to uninstall the module from
+ * @param {Object} opts     [opts={save:true}] options to pass to npm uninstall
+ *
+ * @return {Promise|Error}    Returns a promise with the npm uninstall output or an error.
+ *
+ */
+module.exports.uninstall = function(target, dest, opts) {
+    var fetchArgs = ['uninstall'];
+    opts = opts || {};
+
+    //check if npm is installed on the system
+    return isNpmInstalled()
+    .then(function() {    
+        if(dest && target) {
+            //add target to fetchArgs Array
+            fetchArgs.push(target);  
+        } else return Q.reject(new CordovaError('Need to supply a target and destination'));
+
+        //set the directory where npm uninstall will be run
+        opts.cwd = dest;
+
+        //if user added --save flag, pass it to npm uninstall command
+        if(opts.save) {
+            fetchArgs.push('--save'); 
+        }
+
+        //run npm uninstall, this will remove dependency
+        //from package.json if --save was used.
+        return superspawn.spawn('npm', fetchArgs, opts);
+    })
+    .then(function(res) {
+        var pluginDest = path.join(dest, 'node_modules', target);
+        if(fs.existsSync(pluginDest)) {
+            shell.rm('-rf', pluginDest);
+        } 
+        return res;
+    })
+    .fail(function(err) {
+        return Q.reject(new CordovaError(err));
+    });
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/package.json
----------------------------------------------------------------------
diff --git a/cordova-fetch/package.json b/cordova-fetch/package.json
new file mode 100644
index 0000000..2e7e330
--- /dev/null
+++ b/cordova-fetch/package.json
@@ -0,0 +1,43 @@
+{
+  "name": "cordova-fetch",
+  "version": "1.0.0-dev",
+  "description": "Apache Cordova fetch module. Fetches from git and npm.",
+  "main": "index.js",
+  "repository": {
+    "type": "git",
+    "url": "git://git-wip-us.apache.org/repos/asf/cordova-lib.git"
+  },
+  "keywords": [
+    "cordova",
+    "fetch",
+    "apache",
+    "ecosystem:cordova",
+    "cordova:tool"
+  ],
+  "author": "Apache Software Foundation",
+  "license": "Apache-2.0",
+  "bugs": {
+    "url": "https://issues.apache.org/jira/browse/CB",
+    "email": "dev@cordova.apache.org"
+  },
+  "dependencies": {
+    "cordova-common": "^1.0.0",
+    "dependency-ls": "^1.0.0",
+    "is-url": "^1.2.1",
+    "q": "^1.4.1",
+    "shelljs": "^0.7.0"
+  },
+  "devDependencies": {
+    "jasmine": "^2.4.1",
+    "jshint": "^2.8.0"
+  },
+  "scripts": {
+    "test": "npm run jshint && npm run jasmine",
+    "jshint": "jshint index.js spec/fetch.spec.js",
+    "jasmine": "jasmine spec/fetch.spec.js"
+  },
+  "engines": {
+    "node": ">= 0.12.0",
+    "npm": ">= 2.5.1"
+  }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/spec/fetch.spec.js
----------------------------------------------------------------------
diff --git a/cordova-fetch/spec/fetch.spec.js b/cordova-fetch/spec/fetch.spec.js
new file mode 100644
index 0000000..2f6513a
--- /dev/null
+++ b/cordova-fetch/spec/fetch.spec.js
@@ -0,0 +1,300 @@
+/**
+    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 fetch = require('../index.js');
+var uninstall = require('../index.js').uninstall;
+var shell = require('shelljs');
+var path = require('path');
+var fs = require('fs');
+var helpers = require('./helpers.js');
+
+describe('platform fetch/uninstall tests via npm & git', function () {
+
+    var tmpDir = helpers.tmpDir('plat_fetch');
+    var opts = {};
+
+    beforeEach(function() {
+        process.chdir(tmpDir);
+    });
+    
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    it('should fetch and uninstall a cordova platform via npm & git', function(done) {
+        
+        fetch('cordova-android', tmpDir, opts)
+        .then(function(result) {
+            var pkgJSON = require(path.join(result,'package.json'));
+            expect(result).toBeDefined();
+            expect(fs.existsSync(result)).toBe(true);
+            expect(pkgJSON.name).toBe('cordova-android');
+            
+            return uninstall('cordova-android', tmpDir, opts);
+        })
+        .then(function() {
+            expect(fs.existsSync(path.join(tmpDir,'node_modules', 'cordova-android'))).toBe(false);
+            
+            return fetch('https://github.com/apache/cordova-ios.git', tmpDir, opts);       
+        })
+        .then(function(result) {
+            var pkgJSON = require(path.join(result,'package.json'));
+            expect(result).toBeDefined();
+            expect(fs.existsSync(result)).toBe(true);
+            expect(pkgJSON.name).toBe('cordova-ios');
+            
+            return uninstall('cordova-ios', tmpDir, opts);
+        })
+        .then(function() {
+            expect(fs.existsSync(path.join(tmpDir,'node_modules', 'cordova-ios'))).toBe(false);    
+        })
+        .fail(function(err) {
+            console.error(err);
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 60000);
+});
+
+describe('platform fetch/uninstall test via npm & git tags with --save', function () {
+
+    var tmpDir = helpers.tmpDir('plat_fetch_save');
+    var opts = {'save':true};
+    
+    beforeEach(function() {
+        //copy package.json from spec directory to tmpDir
+        shell.cp('spec/testpkg.json', path.join(tmpDir,'package.json'));
+        process.chdir(tmpDir);
+    });
+    
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    it('should fetch and uninstall a cordova platform via npm & git tags/branches', function(done) {
+        fetch('cordova-android@5.1.1', tmpDir, opts)
+        .then(function(result) {
+            var pkgJSON = require(path.join(result,'package.json'));
+            expect(result).toBeDefined();
+            expect(fs.existsSync(result)).toBe(true);
+            expect(pkgJSON.name).toBe('cordova-android');
+            expect(pkgJSON.version).toBe('5.1.1');
+
+            var rootPJ = require(path.join(tmpDir,'package.json'));
+            expect(rootPJ.dependencies['cordova-android']).toBe('^5.1.1');
+
+            return uninstall('cordova-android', tmpDir, opts);
+        })
+        .then(function() {
+            var rootPJ = JSON.parse(fs.readFileSync(path.join(tmpDir,'package.json'), 'utf8'));
+            expect(Object.keys(rootPJ.dependencies).length).toBe(0);
+            expect(fs.existsSync(path.join(tmpDir,'node_modules', 'cordova-android'))).toBe(false);
+
+            return fetch('https://github.com/apache/cordova-ios.git#rel/4.1.1', tmpDir, opts);       
+        })
+        .then(function(result) {
+            var pkgJSON = require(path.join(result,'package.json'));
+            expect(result).toBeDefined();
+            expect(fs.existsSync(result)).toBe(true);
+            expect(pkgJSON.name).toBe('cordova-ios');
+            expect(pkgJSON.version).toBe('4.1.1');
+
+            var rootPJ = JSON.parse(fs.readFileSync(path.join(tmpDir,'package.json'), 'utf8'));
+            expect(rootPJ.dependencies['cordova-ios']).toBe('git+https://github.com/apache/cordova-ios.git#rel/4.1.1');
+
+            return uninstall('cordova-ios', tmpDir, opts);
+        })
+        .then(function() {
+            var rootPJ = JSON.parse(fs.readFileSync(path.join(tmpDir,'package.json'), 'utf8'));
+            expect(Object.keys(rootPJ.dependencies).length).toBe(0);
+            expect(fs.existsSync(path.join(tmpDir,'node_modules', 'cordova-ios'))).toBe(false);
+
+            return fetch('https://github.com/apache/cordova-android.git#4.1.x', tmpDir, opts);
+        })
+        .then(function(result) {
+            var pkgJSON = JSON.parse(fs.readFileSync(path.join(result,'package.json'), 'utf8'));
+            expect(result).toBeDefined();
+            expect(fs.existsSync(result)).toBe(true);
+            expect(pkgJSON.name).toBe('cordova-android');
+            expect(pkgJSON.version).toBe('4.1.1');
+
+            var rootPJ = JSON.parse(fs.readFileSync(path.join(tmpDir,'package.json'), 'utf8'));
+            expect(rootPJ.dependencies['cordova-android']).toBe('git+https://github.com/apache/cordova-android.git#4.1.x');
+
+            return uninstall('cordova-android', tmpDir, opts);
+        })
+        .fail(function(err) {
+            console.error(err);
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 60000);
+});
+
+describe('plugin fetch/uninstall test with --save', function () {
+
+    var tmpDir = helpers.tmpDir('plug_fetch_save');
+    var opts = {'save':true};
+    
+    beforeEach(function() {
+        //copy package.json from spec directory to tmpDir
+        shell.cp('spec/testpkg.json', path.join(tmpDir,'package.json'));
+        process.chdir(tmpDir);
+    });
+    
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    it('should fetch and uninstall a cordova plugin via git commit sha', function(done) {
+        fetch('https://github.com/apache/cordova-plugin-contacts.git#7db612115755c2be73a98dda76ff4c5fd9d8a575', tmpDir, opts)
+        .then(function(result) {
+            var pkgJSON = require(path.join(result,'package.json'));
+            expect(result).toBeDefined();
+            expect(fs.existsSync(result)).toBe(true);
+            expect(pkgJSON.name).toBe('cordova-plugin-contacts');
+            expect(pkgJSON.version).toBe('2.0.2-dev');
+
+            var rootPJ = require(path.join(tmpDir,'package.json'));
+            expect(rootPJ.dependencies['cordova-plugin-contacts']).toBe('git+https://github.com/apache/cordova-plugin-contacts.git#7db612115755c2be73a98dda76ff4c5fd9d8a575');
+
+            return uninstall('cordova-plugin-contacts', tmpDir, opts);
+        })
+        .then(function() {
+            var rootPJ = JSON.parse(fs.readFileSync(path.join(tmpDir,'package.json'), 'utf8'));
+            expect(Object.keys(rootPJ.dependencies).length).toBe(0);
+            expect(fs.existsSync(path.join(tmpDir,'node_modules', 'cordova-plugin-contacts'))).toBe(false);
+        })
+        .fail(function(err) {
+            console.error(err);
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+});
+
+describe('test trimID method for npm and git', function () {
+
+    var tmpDir = helpers.tmpDir('plug_trimID');
+    var opts = {};
+    
+    beforeEach(function() {
+        process.chdir(tmpDir);
+    });
+    
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    it('should fetch the same cordova plugin twice in a row', function(done) {
+        fetch('cordova-plugin-device', tmpDir, opts)
+        .then(function(result) {
+            var pkgJSON = require(path.join(result,'package.json'));
+            expect(result).toBeDefined();
+            expect(fs.existsSync(result)).toBe(true);
+            expect(pkgJSON.name).toBe('cordova-plugin-device');
+            
+            return fetch('https://github.com/apache/cordova-plugin-media.git', tmpDir, opts);
+        })
+        .then(function(result) {
+            var pkgJSON = require(path.join(result,'package.json'));
+            expect(result).toBeDefined();
+            expect(fs.existsSync(result)).toBe(true);
+            expect(pkgJSON.name).toBe('cordova-plugin-media');
+
+            //refetch to trigger trimID
+            return fetch('cordova-plugin-device', tmpDir, opts);
+            
+        })
+        .then(function(result) {
+            expect(result).toBeDefined();
+            expect(fs.existsSync(result)).toBe(true);
+
+            //refetch to trigger trimID
+            return fetch('https://github.com/apache/cordova-plugin-media.git', tmpDir, opts);
+        })
+        .then(function(result) {
+            expect(result).toBeDefined();
+            expect(fs.existsSync(result)).toBe(true);
+        })
+        .fail(function(err) {
+            console.error(err);
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+});
+
+describe('fetch failure with unknown module', function () {
+
+    var tmpDir = helpers.tmpDir('fetch_fails_npm');
+    var opts = {};
+    
+    beforeEach(function() {
+        process.chdir(tmpDir);
+    });
+    
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    it('should fail fetching a module that does not exist on npm', function(done) {
+        fetch('NOTAMODULE', tmpDir, opts)
+        .then(function(result) {
+            console.log('This should fail and it should not be seen');
+        })
+        .fail(function(err) {
+            expect(err.message.code).toBe(1);
+            expect(err).toBeDefined();
+        })
+        .fin(done);
+    }, 30000);
+});
+
+describe('fetch failure with git subdirectory', function () {
+
+    var tmpDir = helpers.tmpDir('fetch_fails_subdirectory');
+    var opts = {};
+
+    beforeEach(function() {
+        process.chdir(tmpDir);
+    });
+    
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    it('should fail fetching a giturl which contains a subdirectory', function(done) {
+        fetch('https://github.com/apache/cordova-plugins.git#:keyboard', tmpDir, opts)
+        .then(function(result) {
+            console.log('This should fail and it should not be seen');
+        })
+        .fail(function(err) {
+            expect(err.message.code).toBe(1);
+            expect(err).toBeDefined();
+        })
+        .fin(done);
+    }, 30000);
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/spec/helpers.js
----------------------------------------------------------------------
diff --git a/cordova-fetch/spec/helpers.js b/cordova-fetch/spec/helpers.js
new file mode 100644
index 0000000..ee3f57b
--- /dev/null
+++ b/cordova-fetch/spec/helpers.js
@@ -0,0 +1,16 @@
+var path    = require('path'),
+    fs      = require('fs'),
+    shell   = require('shelljs'),
+    os      = require('os');
+
+module.exports.tmpDir = function (subdir) {
+    var dir = path.join(os.tmpdir(), 'e2e-test');
+    if (subdir) {
+        dir = path.join(dir, subdir);
+    }
+    if(fs.existsSync(dir)) {
+        shell.rm('-rf', dir);
+    }
+    shell.mkdir('-p', dir);
+    return dir;
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/spec/support/jasmine.json
----------------------------------------------------------------------
diff --git a/cordova-fetch/spec/support/jasmine.json b/cordova-fetch/spec/support/jasmine.json
new file mode 100644
index 0000000..3ea3166
--- /dev/null
+++ b/cordova-fetch/spec/support/jasmine.json
@@ -0,0 +1,11 @@
+{
+  "spec_dir": "spec",
+  "spec_files": [
+    "**/*[sS]pec.js"
+  ],
+  "helpers": [
+    "helpers/**/*.js"
+  ],
+  "stopSpecOnExpectationFailure": false,
+  "random": false
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-fetch/spec/testpkg.json
----------------------------------------------------------------------
diff --git a/cordova-fetch/spec/testpkg.json b/cordova-fetch/spec/testpkg.json
new file mode 100644
index 0000000..94e7f64
--- /dev/null
+++ b/cordova-fetch/spec/testpkg.json
@@ -0,0 +1,11 @@
+{
+  "name": "test",
+  "version": "1.0.0",
+  "description": "",
+  "main": "fetch.spec.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC"
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-lib/spec-cordova/helpers.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/helpers.js b/cordova-lib/spec-cordova/helpers.js
index a5d23fd..4cc1e2c 100644
--- a/cordova-lib/spec-cordova/helpers.js
+++ b/cordova-lib/spec-cordova/helpers.js
@@ -40,6 +40,9 @@ module.exports.tmpDir = function (subdir) {
     if (subdir) {
         dir = path.join(dir, subdir);
     }
+    if(fs.existsSync(dir)) {
+        shell.rm('-rf', dir);
+    }
     shell.mkdir('-p', dir);
     return dir;
 };

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-lib/spec-cordova/platform.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/platform.spec.js b/cordova-lib/spec-cordova/platform.spec.js
index a35216f..a0bd104 100644
--- a/cordova-lib/spec-cordova/platform.spec.js
+++ b/cordova-lib/spec-cordova/platform.spec.js
@@ -248,3 +248,111 @@ describe('platform add plugin rm end-to-end', function () {
         .fin(done);
     }, 20000);
 });
+
+describe('platform add and remove --fetch', function () {
+
+    var tmpDir = helpers.tmpDir('plat_add_remove_fetch_test');
+    var project = path.join(tmpDir, 'helloFetch');
+    var platformsDir = path.join(project, 'platforms');
+    var nodeModulesDir = path.join(project, 'node_modules');
+    
+    beforeEach(function() {
+        process.chdir(tmpDir);
+    });
+    
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    it('should add and remove platform from node_modules directory', function(done) {
+        
+        cordova.raw.create('helloFetch')
+        .then(function() {
+            process.chdir(project);
+            return cordova.raw.platform('add', 'ios', {'fetch':true});
+        })
+        .then(function() {
+            expect(path.join(nodeModulesDir, 'cordova-ios')).toExist();
+            expect(path.join(platformsDir, 'ios')).toExist();
+            return cordova.raw.platform('add', 'android', {'fetch':true});
+        })
+        .then(function() {    
+            expect(path.join(nodeModulesDir, 'cordova-android')).toExist();
+            expect(path.join(platformsDir, 'android')).toExist();
+            //Tests finish before this command finishes resolving
+            //return cordova.raw.platform('rm', 'ios', {'fetch':true});
+        })
+        .then(function() {
+            //expect(path.join(nodeModulesDir, 'cordova-ios')).not.toExist();
+            //expect(path.join(platformsDir, 'ios')).not.toExist();
+            //Tests finish before this command finishes resolving
+            //return cordova.raw.platform('rm', 'android', {'fetch':true});
+        })
+        .then(function() {
+            //expect(path.join(nodeModulesDir, 'cordova-android')).not.toExist();
+            //expect(path.join(platformsDir, 'android')).not.toExist();
+        })
+        .fail(function(err) {
+            console.error(err);
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 40000);
+});
+
+describe('plugin add and rm end-to-end --fetch', function () {
+
+    var tmpDir = helpers.tmpDir('plugin_rm_fetch_test');
+    var project = path.join(tmpDir, 'hello3');
+    var pluginsDir = path.join(project, 'plugins');
+    
+    beforeEach(function() {
+        process.chdir(tmpDir);
+    });
+    
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    it('should remove dependency when removing parent plugin', function(done) {
+        
+        cordova.raw.create('hello3')
+        .then(function() {
+            process.chdir(project);
+            return cordova.raw.platform('add', 'ios', {'fetch': true});
+        })
+        .then(function() {
+            return cordova.raw.plugin('add', 'cordova-plugin-media', {'fetch': true});
+        })
+        .then(function() {
+            expect(path.join(pluginsDir, 'cordova-plugin-media')).toExist();
+            expect(path.join(pluginsDir, 'cordova-plugin-file')).toExist();
+            expect(path.join(pluginsDir, 'cordova-plugin-compat')).toExist();
+            expect(path.join(project, 'node_modules', 'cordova-plugin-media')).toExist();
+            expect(path.join(project, 'node_modules', 'cordova-plugin-file')).toExist();
+            expect(path.join(project, 'node_modules', 'cordova-plugin-compat')).toExist();
+            return cordova.raw.platform('add', 'android', {'fetch':true});
+        })
+        .then(function() {
+            expect(path.join(pluginsDir, 'cordova-plugin-media')).toExist();
+            expect(path.join(pluginsDir, 'cordova-plugin-file')).toExist();
+            return cordova.raw.plugin('rm', 'cordova-plugin-media', {'fetch':true});
+        })
+        .then(function() {
+            expect(path.join(pluginsDir, 'cordova-plugin-media')).not.toExist();
+            expect(path.join(pluginsDir, 'cordova-plugin-file')).not.toExist();
+            expect(path.join(pluginsDir, 'cordova-plugin-compat')).not.toExist();
+            //These don't work yet due to the tests finishing before the promise resolves.
+            //expect(path.join(project, 'node_modules', 'cordova-plugin-media')).not.toExist();
+            //expect(path.join(project, 'node_modules', 'cordova-plugin-file')).not.toExist();
+            //expect(path.join(project, 'node_modules', 'cordova-plugin-compat')).not.toExist();
+        })
+        .fail(function(err) {
+            console.error(err);
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 60000);
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/6025a5f2/cordova-lib/src/cordova/platform.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/platform.js b/cordova-lib/src/cordova/platform.js
index db6f53c..cdfeb85 100644
--- a/cordova-lib/src/cordova/platform.js
+++ b/cordova-lib/src/cordova/platform.js
@@ -37,6 +37,8 @@ var config            = require('./config'),
     shell             = require('shelljs'),
     _                 = require('underscore'),
     PlatformJson      = require('cordova-common').PlatformJson,
+    fetch             = require('cordova-fetch'),
+    npmUninstall         = require('cordova-fetch').uninstall,
     platformMetadata  = require('./platform_metadata');
 
 // Expose the platform parsers on top of this command
@@ -257,6 +259,21 @@ function getSpecString(spec) {
 function downloadPlatform(projectRoot, platform, version, opts) {
     var target = version ? (platform + '@' + version) : platform;
     return Q().then(function() {
+        if (opts.fetch) {
+            //append cordova to platform
+            if(platform in platforms) {
+                target = 'cordova-'+target;
+            }
+
+            //gitURLs don't supply a platform, it equals null
+            if(!platform) {
+                target = version;
+            }
+
+            events.emit('log', 'Using cordova-fetch for '+ target);
+            return fetch(target, projectRoot, opts);
+        }
+
         if (cordova_util.isUrl(version)) {
             events.emit('log', 'git cloning: ' + version);
             var parts = version.split('#');
@@ -356,8 +373,8 @@ function remove(hooksRunner, projectRoot, targets, opts) {
                 events.emit('log', 'Removing ' + target + ' from config.xml file ...');
                 cfg.removeEngine(platformName);
                 cfg.write();
-        });
-    }
+            });
+        }
     }).then(function() {
         // Remove targets from platforms.json
         targets.forEach(function(target) {
@@ -365,6 +382,16 @@ function remove(hooksRunner, projectRoot, targets, opts) {
             platformMetadata.remove(projectRoot, target);
         });
     }).then(function() {
+        //Remove from node_modules if it exists and --fetch was used
+        if(opts.fetch) {
+            targets.forEach(function(target) {
+                if(target in platforms) {
+                    target = 'cordova-'+target;
+                }
+                return npmUninstall(target, projectRoot, opts);
+            });
+        }
+    }).then(function() {
         return hooksRunner.fire('after_platform_rm', opts);
     });
 }


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