CB-14133 Avoid fetching already installed packages
diff --git a/index.js b/index.js
index 4fd3a21..dfa51f7 100644
--- a/index.js
+++ b/index.js
@@ -22,6 +22,9 @@
 var path = require('path');
 var fs = require('fs-extra');
 var CordovaError = require('cordova-common').CordovaError;
+const { getInstalledPath } = require('get-installed-path');
+const npa = require('npm-package-arg');
+const semver = require('semver');
 
 /*
  * A function that npm installs a module from npm or a git url
@@ -42,7 +45,10 @@
             // Create dest if it doesn't exist yet
             fs.ensureDirSync(dest);
         })
-        .then(_ => installPackage(target, dest, opts))
+        .then(_ => {
+            return pathToInstalledPackage(target, dest)
+                .catch(_ => installPackage(target, dest, opts));
+        })
         .catch(function (err) {
             throw new CordovaError(err);
         });
@@ -92,17 +98,18 @@
     }
 }
 
+// Resolves to installation path of package defined by spec if the right version
+// is installed, rejects otherwise.
 function pathToInstalledPackage (spec, dest) {
-    // Strip version from spec
-    const parts = spec.split('@');
-    const isScoped = parts.length > 1 && parts[0] === '';
-    const pkgName = isScoped ? '@' + parts[1] : parts[0];
-
-    // append node_modules to nodeModulesDir if it doesn't come included
-    const nodeModulesDir = path.basename(dest) === 'node_modules' ?
-        dest : path.resolve(path.join(dest, 'node_modules'));
-
-    return path.resolve(nodeModulesDir, pkgName);
+    const { name, rawSpec } = npa(spec, dest);
+    return getInstalledPath(name, { local: true, cwd: dest })
+        .then(installPath => {
+            const { version } = fs.readJsonSync(path.join(installPath, 'package.json'));
+            if (!semver.satisfies(version, rawSpec)) {
+                throw new CordovaError(`Installed package ${name}@${version} does not satisfy ${name}@${rawSpec}`);
+            }
+            return installPath;
+        });
 }
 
 /*
diff --git a/package.json b/package.json
index bb55b8c..f68f296 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,10 @@
   "dependencies": {
     "cordova-common": "^2.2.0",
     "fs-extra": "^6.0.1",
+    "get-installed-path": "^4.0.8",
+    "npm-package-arg": "^6.1.0",
     "q": "^1.4.1",
+    "semver": "^5.5.0",
     "which": "^1.3.1"
   },
   "devDependencies": {
diff --git a/spec/fetch-unit.spec.js b/spec/fetch-unit.spec.js
index cee0ec5..a62c8e1 100644
--- a/spec/fetch-unit.spec.js
+++ b/spec/fetch-unit.spec.js
@@ -16,10 +16,41 @@
 */
 
 /* eslint-env jasmine */
+/* eslint-disable prefer-promise-reject-errors */
+
 const rewire = require('rewire');
-const fetch = rewire('..');
+
+describe('fetch', function () {
+    let fetch, installPackage;
+
+    beforeEach(function () {
+        fetch = rewire('..');
+        installPackage = jasmine.createSpy()
+            .and.returnValue(Promise.resolve('/foo'));
+        fetch.__set__({ fs: { ensureDirSync: _ => _ }, installPackage });
+    });
+
+    it('should return path to installed package', function () {
+        fetch.__set__({ pathToInstalledPackage: _ => Promise.resolve('/foo') });
+
+        return fetch('foo', 'bar').then(result => {
+            expect(result).toBe('/foo');
+            expect(installPackage).not.toHaveBeenCalled();
+        });
+    });
+
+    it('should install package if not found', function () {
+        fetch.__set__({ pathToInstalledPackage: _ => Promise.reject() });
+
+        return fetch('foo', 'bar').then(result => {
+            expect(result).toBe('/foo');
+            expect(installPackage).toHaveBeenCalled();
+        });
+    });
+});
 
 describe('npmArgs', function () {
+    const fetch = rewire('..');
     const npmArgs = fetch.__get__('npmArgs');
 
     it('should handle missing options', function () {