Save and use resolutions to resolve conflicts.

This commit is contained in:
André Cruz
2013-06-06 00:12:07 +01:00
parent 796e019f12
commit 2eea258b9b
2 changed files with 143 additions and 49 deletions

View File

@@ -14,6 +14,9 @@ function Manager(config, logger) {
this._config = config;
this._logger = logger;
this._repository = new PackageRepository(this._config);
this.setResolutions();
this.configure();
}
// -----------------
@@ -23,12 +26,22 @@ Manager.prototype.setProduction = function (production) {
return this;
};
Manager.prototype.getResolutions = function () {
return this._resolutions;
};
Manager.prototype.setResolutions = function (resolutions) {
this._resolutions = resolutions || {};
return this;
};
Manager.prototype.configure = function (targets, resolved, installed) {
// If working, error out
if (this._working) {
throw createError('Can\'t configure while working', 'EWORKING');
}
targets = targets || [];
this._targets = targets;
this._resolved = {};
this._installed = {};
@@ -90,7 +103,6 @@ Manager.prototype.install = function () {
}
destDir = path.join(this._config.cwd, this._config.directory);
return Q.nfcall(mkdirp, destDir)
.then(function () {
var promises = [];
@@ -270,6 +282,9 @@ Manager.prototype._onFetchSuccess = function (decEndpoint, canonicalPkg, pkgMeta
}
}
// Progress!
this._deferred.notify(decEndpoint);
// Parse dependencies
this._parseDependencies(decEndpoint, pkgMeta, 'dependencies');
// Do the same for the dev dependencies
@@ -453,19 +468,27 @@ Manager.prototype._dissect = function () {
};
Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
var picks = [];
var suitable;
var resolution;
var dataPicks;
var choices;
var suitable;
var picks = [];
// If there are no semver targets, there's a conflict if several exist
if (!semvers.length) {
if (nonSemvers.length > 1) {
picks.push.apply(picks, nonSemvers);
} else {
suitable = nonSemvers[0];
// If there are both semver and non-semver, there's no way
// to figure out the suitable one
if (semvers.length && nonSemvers.length) {
picks.push.apply(picks, semvers);
picks.push.apply(picks, nonSemvers);
// If there are only non-semver ones, the suitable is elected
// only if there's one
} else if (nonSemvers.length) {
if (nonSemvers.length === 1) {
return Q.resolve(nonSemvers[0]);
}
// Otherwise, find most suitable semver
picks.push.apply(picks, nonSemvers);
// If there are only semver ones, figure out the which one
// is compatible with every requirement
} else {
suitable = mout.array.find(semvers, function (subject) {
return semvers.every(function (decEndpoint) {
@@ -473,21 +496,32 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
});
});
// Note that the user needs to pick one if there's no
// suitable version or if some one them were non-semver
if (!suitable || nonSemvers.length) {
picks.push.apply(picks, semvers);
if (suitable) {
return Q.resolve(suitable);
}
picks.push.apply(picks, nonSemvers);
picks.push.apply(picks, semvers);
}
// If there are no picks, resolve to the suitable one
if (!picks.length) {
return Q.resolve(suitable);
// Check if there's a resolution that resolves the conflict
resolution = this._resolutions[name];
if (resolution) {
if (semver.valid(resolution) != null || semver.validRange(resolution) != null) {
suitable = mout.array.find(picks, function (pick) {
return semver.satisfies(pick.pkgMeta.version, resolution);
});
} else {
suitable = mout.array.find(picks, function (pick) {
return pick.pkgMeta._release === resolution;
});
}
if (suitable) {
return Q.resolve(suitable);
}
}
// At this point, the user needs to make a decision
// Sort picks by version/release
picks.sort(function (pick1, pick2) {
var version1 = pick1.pkgMeta.version;
@@ -511,9 +545,18 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
}
}
// Give priority to the one with most dependants
if (pick1.dependants.length > pick2.dependants.length) {
return -1;
}
if (pick1.dependants.length < pick2.dependants.length) {
return 1;
}
return 0;
});
// Prepare data to be sent bellow
dataPicks = picks.map(function (pick) {
return {
endpoint: mout.object.pick(pick, ['name', 'source', 'target']),
@@ -546,8 +589,17 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
choices = picks.map(function (pick, index) { return index + 1; });
return Q.nfcall(promptly.choose, 'Choice:', choices)
.then(function (choice) {
return picks[choice - 1];
});
var pick = picks[choice - 1];
// Store choice into resolutions
if (pick.target === '*') {
this._resolutions[name] = pick.pkgMeta._release || '*';
} else {
this._resolutions[name] = pick.target;
}
return pick;
}.bind(this));
};
Manager.prototype._getCap = function (comparators, side) {

View File

@@ -40,12 +40,13 @@ Project.prototype.install = function (endpoints, options) {
// Analyse the project
return this.analyse()
.spread(function (json, tree, flattened) {
// Walk down the tree adding missing and incompatible
// as targets
var promise;
// Walk down the tree adding missing as targets
that._walkTree(tree, function (node, name) {
if (node.missing || node.incompatible) {
if (node.missing) {
targets.push(node);
} else {
} else if (!node.incompatible) {
resolved[name] = node;
}
}, true);
@@ -53,37 +54,66 @@ Project.prototype.install = function (endpoints, options) {
// Add endpoints as targets
if (endpoints) {
endpoints.forEach(function (endpoint) {
targets.push(endpointParser.decompose(endpoint));
var decEndpoint = endpointParser.decompose(endpoint);
decEndpoint.specified = true;
targets.push(decEndpoint);
});
}
// Bootstrap the process
return that._bootstrap(targets, resolved, flattened);
})
// Handle save and saveDev options
.then(function (installed) {
if (!options.save && !options.saveDev) {
return installed;
// If there are targets configured, add incompatible
if (targets.length) {
that._walkTree(tree, function (node) {
if (node.incompatible) {
targets.push(node);
}
}, true);
}
// Cycle through the initial targets and not the installed
// ones because some targets could already be installed
mout.object.forOwn(targets, function (decEndpoint) {
var source = decEndpoint.registry ? '' : decEndpoint.source;
var target = decEndpoint.target;
var endpoint = mout.string.ltrim(source + '#' + target, ['#']);
// Bootstrap the process
promise = that._bootstrap(targets, resolved, flattened);
if (options.save) {
that._json.dependencies = that._json.dependencies || {};
that._json.dependencies[decEndpoint.name] = endpoint;
}
// If there are targets configured, listen to when they
// resolve in order to remove any associated resolution
// This can only be done at this step because endpoint names
// are not fully known before
if (endpoints) {
promise = promise.progress(function (decEndpoint) {
var resolutions;
if (options.saveDev) {
that._json.devDependencies = that._json.devDependencies || {};
that._json.devDependencies[decEndpoint.name] = endpoint;
}
});
if (decEndpoint.specified) {
resolutions = that._manager.getResolutions();
delete resolutions[decEndpoint.name];
delete decEndpoint.specified;
}
});
}
return promise;
})
.then(function (installed) {
// Handle save and saveDev options
if (options.save || options.saveDev) {
// Cycle through the initial targets and not the installed
// ones because some targets could already be installed
mout.object.forOwn(targets, function (decEndpoint) {
var source = decEndpoint.registry ? '' : decEndpoint.source;
var target = decEndpoint.target;
var endpoint = mout.string.ltrim(source + '#' + target, ['#']);
if (options.save) {
that._json.dependencies = that._json.dependencies || {};
that._json.dependencies[decEndpoint.name] = endpoint;
}
if (options.saveDev) {
that._json.devDependencies = that._json.devDependencies || {};
that._json.devDependencies[decEndpoint.name] = endpoint;
}
});
}
// Save JSON, might contain changes to dependencies
// and resolutions
return that._saveJson()
.then(function () {
return installed;
@@ -174,7 +204,9 @@ Project.prototype.update = function (names, options) {
}
// Bootstrap the process
return that._bootstrap(targets, resolved, flattened);
return that._bootstrap(targets, resolved, flattened)
// Save JSON, might contain changes to resolutions
.then(that._saveJson.bind(that));
})
.fin(function () {
that._working = false;
@@ -329,10 +361,20 @@ Project.prototype._bootstrap = function (targets, resolved, flattened) {
// Configure the manager and kick in the resolve process
return this._manager
.setProduction(this._production)
.setResolutions(this._json.resolutions)
.configure(targets, resolved, installed)
.resolve()
// Install resolved ones
.then(function () {
var resolutions = this._manager.getResolutions();
// Update resolutions
if (mout.object.size(resolutions)) {
this._json.resolutions = resolutions;
} else {
delete this._json.resolutions;
}
// Install resolved ones
return this._manager.install();
}.bind(this));
};