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 () {