Merge pull request #23 from raphinesse/remove-shelljs

[CB-14140] Remove shelljs
diff --git a/index.js b/index.js
index 5a666ef..d968711 100644
--- a/index.js
+++ b/index.js
@@ -16,11 +16,11 @@
  */
 
 var Q = require('q');
-var shell = require('shelljs');
+var which = Q.denodeify(require('which'));
 var superspawn = require('cordova-common').superspawn;
 var events = require('cordova-common').events;
 var path = require('path');
-var fs = require('fs');
+var fs = require('fs-extra');
 var CordovaError = require('cordova-common').CordovaError;
 
 /*
@@ -49,9 +49,7 @@
                     nodeModulesDir = path.resolve(path.join(dest, 'node_modules'));
                 }
                 // create node_modules if it doesn't exist
-                if (!fs.existsSync(nodeModulesDir)) {
-                    shell.mkdir('-p', nodeModulesDir);
-                }
+                fs.ensureDirSync(nodeModulesDir);
             } else throw new CordovaError('Need to supply a target and destination');
 
             // set the directory where npm install will be run
@@ -110,11 +108,8 @@
  */
 
 function isNpmInstalled () {
-    return Q.Promise(function (resolve) {
-        if (!shell.which('npm')) {
-            throw new CordovaError('"npm" command line tool is not installed: make sure it is accessible on your PATH.');
-        }
-        resolve();
+    return which('npm').catch(_ => {
+        throw new CordovaError('"npm" command line tool is not installed: make sure it is accessible on your PATH.');
     });
 }
 
@@ -155,13 +150,6 @@
             // 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;
-        })
         .catch(function (err) {
             throw new CordovaError(err);
         });
diff --git a/package.json b/package.json
index c828575..c82c60f 100644
--- a/package.json
+++ b/package.json
@@ -22,8 +22,9 @@
   },
   "dependencies": {
     "cordova-common": "^2.2.0",
+    "fs-extra": "^6.0.1",
     "q": "^1.4.1",
-    "shelljs": "^0.7.0"
+    "which": "^1.3.1"
   },
   "devDependencies": {
     "eslint": "^3.19.0",
diff --git a/spec/fetch-unit.spec.js b/spec/fetch-unit.spec.js
index 18449ed..6a30511 100644
--- a/spec/fetch-unit.spec.js
+++ b/spec/fetch-unit.spec.js
@@ -17,17 +17,14 @@
 
 /* eslint-env jasmine */
 var fetch = require('../index.js');
-var shell = require('shelljs');
-var fs = require('fs');
+var fs = require('fs-extra');
 var superspawn = require('cordova-common').superspawn;
 
 describe('unit tests for index.js', function () {
     beforeEach(function () {
         spyOn(superspawn, 'spawn').and.returnValue('+ foo@1.2.3');
-        spyOn(shell, 'mkdir').and.returnValue(true);
-        spyOn(shell, 'which').and.returnValue(Promise.resolve());
         spyOn(fetch, 'isNpmInstalled').and.returnValue(Promise.resolve());
-        spyOn(fs, 'existsSync').and.returnValue(false);
+        spyOn(fs, 'ensureDirSync').and.returnValue(false);
     });
 
     it('should handle missing options', function () {
diff --git a/spec/fetch.spec.js b/spec/fetch.spec.js
index e30d54e..8ceb454 100644
--- a/spec/fetch.spec.js
+++ b/spec/fetch.spec.js
@@ -18,30 +18,31 @@
 /* eslint-env jasmine */
 var fetch = require('../index.js');
 var uninstall = require('../index.js').uninstall;
-var shell = require('shelljs');
 var path = require('path');
-var fs = require('fs');
+var fs = require('fs-extra');
 var helpers = require('./helpers.js');
 
+var tmpDir;
+
+beforeEach(function () {
+    tmpDir = helpers.tmpDir();
+    process.chdir(tmpDir);
+});
+
+afterEach(function () {
+    process.chdir(__dirname); // Needed to rm the dir on Windows.
+    fs.removeSync(tmpDir);
+});
+
 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 () {
 
         return fetch('cordova-android', tmpDir, opts)
             .then(function (result) {
-                var pkgJSON = require(path.join(result, 'package.json'));
+                var pkgJSON = fs.readJsonSync(path.join(result, 'package.json'));
                 expect(result).toBeDefined();
                 expect(fs.existsSync(result)).toBe(true);
                 expect(pkgJSON.name).toBe('cordova-android');
@@ -54,7 +55,7 @@
                 return fetch('https://github.com/apache/cordova-ios.git', tmpDir, opts);
             })
             .then(function (result) {
-                var pkgJSON = require(path.join(result, 'package.json'));
+                var pkgJSON = fs.readJsonSync(path.join(result, 'package.json'));
                 expect(result).toBeDefined();
                 expect(fs.existsSync(result)).toBe(true);
                 expect(pkgJSON.name).toBe('cordova-ios');
@@ -69,7 +70,7 @@
                 return fetch('https://github.com/apache/cordova-browser.git', tmpDir, opts);
             })
             .then(function (result) {
-                var pkgJSON = require(path.join(result, 'package.json'));
+                var pkgJSON = fs.readJsonSync(path.join(result, 'package.json'));
                 expect(result).toBeDefined();
                 expect(fs.existsSync(result)).toBe(true);
                 expect(pkgJSON.name).toBe('cordova-browser');
@@ -79,68 +80,61 @@
 
 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);
+        fs.copySync(path.join(__dirname, 'testpkg.json'), 'package.json');
     });
 
     it('should fetch and uninstall a cordova platform via npm & git tags/branches', function () {
         return fetch('cordova-android@5.1.1', tmpDir, opts)
             .then(function (result) {
-                var pkgJSON = require(path.join(result, 'package.json'));
+                var pkgJSON = fs.readJsonSync(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'));
+                var rootPJ = fs.readJsonSync(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'));
+                var rootPJ = fs.readJsonSync(path.join(tmpDir, 'package.json'));
                 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'));
+                var pkgJSON = fs.readJsonSync(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'));
+                var rootPJ = fs.readJsonSync(path.join(tmpDir, 'package.json'));
                 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'));
+                var rootPJ = fs.readJsonSync(path.join(tmpDir, 'package.json'));
                 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'));
+                var pkgJSON = fs.readJsonSync(path.join(result, 'package.json'));
                 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'));
+                var rootPJ = fs.readJsonSync(path.join(tmpDir, 'package.json'));
                 expect(rootPJ.dependencies['cordova-android']).toBe('git+https://github.com/apache/cordova-android.git#4.1.x');
 
                 return uninstall('cordova-android', tmpDir, opts);
@@ -150,36 +144,29 @@
 
 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);
+        fs.copySync(path.join(__dirname, 'testpkg.json'), 'package.json');
     });
 
     it('should fetch and uninstall a cordova plugin via git commit sha', function () {
         return fetch('https://github.com/apache/cordova-plugin-contacts.git#7db612115755c2be73a98dda76ff4c5fd9d8a575', tmpDir, opts)
             .then(function (result) {
-                var pkgJSON = require(path.join(result, 'package.json'));
+                var pkgJSON = fs.readJsonSync(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'));
+                var rootPJ = fs.readJsonSync(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'));
+                var rootPJ = fs.readJsonSync(path.join(tmpDir, 'package.json'));
                 expect(Object.keys(rootPJ.dependencies).length).toBe(0);
                 expect(fs.existsSync(path.join(tmpDir, 'node_modules', 'cordova-plugin-contacts'))).toBe(false);
             });
@@ -188,24 +175,16 @@
 
 describe('test trimID method for npm and git', function () {
 
-    var tmpDir;
     var opts = {};
 
     beforeEach(function () {
-        tmpDir = helpers.tmpDir('plug_trimID');
-        shell.cp('-R', 'spec/support', path.join(tmpDir, 'support'));
-        process.chdir(tmpDir);
-    });
-
-    afterEach(function () {
-        process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows.
-        shell.rm('-rf', tmpDir);
+        fs.copySync(path.join(__dirname, 'support'), 'support');
     });
 
     it('should fetch the same cordova plugin twice in a row', function () {
         return fetch('cordova-plugin-device', tmpDir, opts)
             .then(function (result) {
-                var pkgJSON = require(path.join(result, 'package.json'));
+                var pkgJSON = fs.readJsonSync(path.join(result, 'package.json'));
                 expect(result).toBeDefined();
                 expect(fs.existsSync(result)).toBe(true);
                 expect(pkgJSON.name).toBe('cordova-plugin-device');
@@ -213,7 +192,7 @@
                 return fetch('https://github.com/apache/cordova-plugin-media.git', tmpDir, opts);
             })
             .then(function (result) {
-                var pkgJSON = require(path.join(result, 'package.json'));
+                var pkgJSON = fs.readJsonSync(path.join(result, 'package.json'));
                 expect(result).toBeDefined();
                 expect(fs.existsSync(result)).toBe(true);
                 expect(pkgJSON.name).toBe('cordova-plugin-media');
@@ -282,7 +261,7 @@
                 return fetch('git+http://gitbox.apache.org/repos/asf/cordova-plugin-dialogs.git', tmpDir, opts);
             })
             .then(function (result) {
-                var pkgJSON = require(path.join(result, 'package.json'));
+                var pkgJSON = fs.readJsonSync(path.join(result, 'package.json'));
                 expect(result).toBeDefined();
                 expect(fs.existsSync(result)).toBe(true);
                 expect(pkgJSON.name).toBe('cordova-plugin-dialogs');
@@ -292,18 +271,8 @@
 
 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 () {
         return fetch('NOTAMODULE', tmpDir, opts)
             .then(function (result) {
@@ -317,18 +286,8 @@
 
 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 () {
         return fetch('https://github.com/apache/cordova-plugins.git#:keyboard', tmpDir, opts)
             .then(function (result) {
@@ -342,22 +301,12 @@
 
 describe('scoped plugin fetch/uninstall tests via npm', function () {
 
-    var tmpDir = helpers.tmpDir('scoped_plug_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 a scoped plugin from npm', function () {
         return fetch('@stevegill/cordova-plugin-device', tmpDir, opts)
             .then(function (result) {
-                var pkgJSON = require(path.join(result, 'package.json'));
+                var pkgJSON = fs.readJsonSync(path.join(result, 'package.json'));
                 expect(result).toBeDefined();
                 expect(fs.existsSync(result)).toBe(true);
                 expect(pkgJSON.name).toBe('@stevegill/cordova-plugin-device');
diff --git a/spec/helpers.js b/spec/helpers.js
index e081a78..f6e7248 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -18,18 +18,10 @@
  */
 
 var path = require('path');
-var fs = require('fs');
-var shell = require('shelljs');
+var fs = require('fs-extra');
 var 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;
+const tmpDirTemplate = path.join(os.tmpdir(), 'cordova-fetch-tests-');
+module.exports.tmpDir = function () {
+    return fs.mkdtempSync(tmpDirTemplate);
 };