mirror of
https://github.com/bower/bower.git
synced 2026-04-24 03:00:19 -04:00
Add conflict resolve, presenting choices to the user.
This commit also contains: - Simplified Manager and Project - Add warning when an extraneous package is found. - Fix a lot of bugs in the overall resolve process - Made templates rendering synchronous -
This commit is contained in:
@@ -49,8 +49,7 @@ install.options = function (argv) {
|
||||
'help': { type: Boolean, shorthand: 'h' },
|
||||
'save': { type: Boolean, shorthand: 'S' },
|
||||
'save-dev': { type: Boolean, shorthand: 'D' },
|
||||
'production': { type: Boolean, shorthand: 'p' },
|
||||
'force-latest': { type: Boolean, shorthand: 'F'}
|
||||
'production': { type: Boolean, shorthand: 'p' }
|
||||
}, argv);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ var semver = require('semver');
|
||||
var path = require('path');
|
||||
var mkdirp = require('mkdirp');
|
||||
var rimraf = require('rimraf');
|
||||
var promptly = require('promptly');
|
||||
var PackageRepository = require('./PackageRepository');
|
||||
var copy = require('../util/copy');
|
||||
var createError = require('../util/createError');
|
||||
@@ -17,32 +18,31 @@ function Manager(config, logger) {
|
||||
|
||||
// -----------------
|
||||
|
||||
Manager.prototype.setProduction = function (production) {
|
||||
this._production = production;
|
||||
return this;
|
||||
};
|
||||
|
||||
Manager.prototype.configure = function (targets, resolved, installed) {
|
||||
// If working, error out
|
||||
if (this._working) {
|
||||
throw createError('Can\'t configure while working', 'EWORKING');
|
||||
}
|
||||
|
||||
this._targets = {};
|
||||
this._targets = targets;
|
||||
this._resolved = {};
|
||||
this._installed = {};
|
||||
|
||||
// Parse targets
|
||||
targets.forEach(function (decEndpoint) {
|
||||
this._targets[decEndpoint.name] = decEndpoint;
|
||||
decEndpoint.dependants = mout.object.values(decEndpoint.dependants);
|
||||
}, this);
|
||||
|
||||
// Parse resolved
|
||||
mout.object.forOwn(resolved, function (value, name) {
|
||||
this._resolved[name] = [{
|
||||
name: name,
|
||||
source: value._source,
|
||||
target: value.version || '*',
|
||||
pkgMeta: value,
|
||||
initial: true
|
||||
}];
|
||||
|
||||
this._installed[name] = value;
|
||||
mout.object.forOwn(resolved, function (decEndpoint, name) {
|
||||
decEndpoint.dependants = mout.object.values(decEndpoint.dependants);
|
||||
this._resolved[name] = [decEndpoint];
|
||||
this._installed[name] = decEndpoint.pkgMeta;
|
||||
}, this);
|
||||
|
||||
// Parse installed
|
||||
@@ -65,12 +65,12 @@ Manager.prototype.resolve = function () {
|
||||
this._deferred = Q.defer();
|
||||
|
||||
// If there's nothing to resolve, simply dissect
|
||||
if (mout.lang.isEmpty(this._targets)) {
|
||||
if (!this._targets.length) {
|
||||
process.nextTick(this._dissect.bind(this));
|
||||
// Otherwise, fetch each target from the repository
|
||||
// and let the process roll out
|
||||
} else {
|
||||
mout.object.forOwn(this._targets, this._fetch.bind(this));
|
||||
this._targets.forEach(this._fetch.bind(this));
|
||||
}
|
||||
|
||||
// Unset working flag when done
|
||||
@@ -138,8 +138,8 @@ Manager.prototype.areCompatible = function (first, second) {
|
||||
var validSecond = semver.valid(second) != null;
|
||||
var validRangeFirst;
|
||||
var validRangeSecond;
|
||||
var highestSecond;
|
||||
var highestFirst;
|
||||
var rangeSecond;
|
||||
var rangeFirst;
|
||||
|
||||
// Version -> Version
|
||||
if (validFirst && validSecond) {
|
||||
@@ -165,13 +165,23 @@ Manager.prototype.areCompatible = function (first, second) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Grab the highest version possible for both
|
||||
highestSecond = this._findHighestVersion(semver.toComparators(second));
|
||||
highestFirst = this._findHighestVersion(semver.toComparators(first));
|
||||
rangeFirst = {};
|
||||
rangeSecond = {};
|
||||
|
||||
// Check if the highest resolvable version for the
|
||||
// second is the same as the first one
|
||||
return semver.eq(highestSecond, highestFirst);
|
||||
// Grab the highest possible for both
|
||||
rangeFirst.max = this._getCap(semver.toComparators(first), 'highest');
|
||||
rangeSecond.max = this._getCap(semver.toComparators(second), 'highest');
|
||||
|
||||
// Grab the lowest possible for both
|
||||
rangeFirst.min = this._getCap(semver.toComparators(first), 'lowest');
|
||||
rangeSecond.min = this._getCap(semver.toComparators(second), 'lowest');
|
||||
|
||||
// Check if the highest/lowest resolvable version for the second is the
|
||||
// same as the first one
|
||||
return semver.eq(rangeFirst.max.version, rangeSecond.max.version) &&
|
||||
rangeFirst.max.comparator === rangeSecond.max.comparator &&
|
||||
semver.eq(rangeFirst.min.version, rangeSecond.min.version) &&
|
||||
rangeFirst.min.comparator === rangeSecond.min.comparator;
|
||||
}
|
||||
|
||||
// As fallback, check if both are the equal
|
||||
@@ -194,8 +204,9 @@ Manager.prototype._fetch = function (decEndpoint) {
|
||||
this._fetching[name].push(decEndpoint);
|
||||
this._nrFetching++;
|
||||
|
||||
// We create a new logger that pipes everything to ours
|
||||
// and add the endpoint for each log
|
||||
// Create a new logger that pipes everything to ours that will be
|
||||
// used to fetch
|
||||
// The endpoint is added for each log made
|
||||
logger = this._logger.geminate().intercept(function (log) {
|
||||
log.data = log.data || [];
|
||||
log.data.endpoint = mout.object.pick(decEndpoint, ['name', 'source', 'target']);
|
||||
@@ -246,7 +257,11 @@ Manager.prototype._onFetchSuccess = function (decEndpoint, canonicalPkg, pkgMeta
|
||||
}
|
||||
|
||||
// Parse dependencies
|
||||
this._parseDependencies(decEndpoint, pkgMeta);
|
||||
this._parseDependencies(decEndpoint, pkgMeta, 'dependencies');
|
||||
// Do the same for the dev dependencies
|
||||
if (!this._production) {
|
||||
this._parseDependencies(decEndpoint, pkgMeta, 'devDependencies');
|
||||
}
|
||||
|
||||
// If the resolve process ended, parse the resolved packages
|
||||
// to find the most suitable version for each package
|
||||
@@ -281,7 +296,7 @@ Manager.prototype._onFetchError = function (decEndpoint, err) {
|
||||
};
|
||||
|
||||
Manager.prototype._failFast = function () {
|
||||
if (this._failFastTimeout) {
|
||||
if (this._hasFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -295,27 +310,34 @@ Manager.prototype._failFast = function () {
|
||||
}.bind(this), 20000);
|
||||
};
|
||||
|
||||
Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta) {
|
||||
Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey) {
|
||||
// Parse package dependencies
|
||||
mout.object.forOwn(pkgMeta.dependencies, function (value, key) {
|
||||
mout.object.forOwn(pkgMeta[jsonKey], function (value, key) {
|
||||
var resolved;
|
||||
var beingFetched;
|
||||
var compatible;
|
||||
var childDecEndpoint = endpointParser.json2decomposed(key, value);
|
||||
|
||||
this._addDependant(childDecEndpoint, decEndpoint);
|
||||
|
||||
// Check if a compatible one is already resolved
|
||||
// If there's one, we don't need to resolve it twice
|
||||
resolved = this._resolved[key];
|
||||
if (resolved) {
|
||||
// Find compatible
|
||||
compatible = mout.array.find(resolved, function (resolved) {
|
||||
return this.areCompatible(resolved.target, childDecEndpoint.target);
|
||||
}, this);
|
||||
|
||||
// Simply mark it as resolved
|
||||
if (compatible) {
|
||||
childDecEndpoint.canonicalPkg = compatible.canonicalPkg;
|
||||
childDecEndpoint.pkgMeta = compatible.pkgMeta;
|
||||
this._resolved[key].push(childDecEndpoint);
|
||||
// If the compatible's target is equal, do not add to the resolved
|
||||
if (compatible.target === childDecEndpoint.target) {
|
||||
this._addDependant(compatible, decEndpoint);
|
||||
} else {
|
||||
childDecEndpoint.canonicalPkg = compatible.canonicalPkg;
|
||||
childDecEndpoint.pkgMeta = compatible.pkgMeta;
|
||||
this._resolved[key].push(childDecEndpoint);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -324,18 +346,23 @@ Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta) {
|
||||
// If there's one, we reuse it to avoid resolving it twice
|
||||
beingFetched = this._fetching[key];
|
||||
if (beingFetched) {
|
||||
// Find compatible
|
||||
compatible = mout.array.find(beingFetched, function (beingFetched) {
|
||||
return this.areCompatible(beingFetched.target, childDecEndpoint.target);
|
||||
}, this);
|
||||
|
||||
// Wait for it to resolve and then add it to the resolved packages
|
||||
if (compatible) {
|
||||
childDecEndpoint = compatible.promise.then(function () {
|
||||
childDecEndpoint.canonicalPkg = compatible.canonicalPkg;
|
||||
childDecEndpoint.pkgMeta = compatible.pkgMeta;
|
||||
this._resolved[key].push(childDecEndpoint);
|
||||
}.bind(this));
|
||||
|
||||
// If the compatible's target is equal, do not add to the resolved
|
||||
if (compatible.target === childDecEndpoint.target) {
|
||||
this._addDependant(compatible, decEndpoint);
|
||||
} else {
|
||||
compatible.promise.then(function () {
|
||||
childDecEndpoint.canonicalPkg = compatible.canonicalPkg;
|
||||
childDecEndpoint.pkgMeta = compatible.pkgMeta;
|
||||
this._resolved[key].push(childDecEndpoint);
|
||||
}.bind(this));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -345,9 +372,15 @@ Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta) {
|
||||
}, this);
|
||||
};
|
||||
|
||||
Manager.prototype._addDependant = function (decEndpoint, parentDecEndpoint) {
|
||||
decEndpoint.dependants = decEndpoint.dependants || [];
|
||||
decEndpoint.dependants.push(parentDecEndpoint);
|
||||
};
|
||||
|
||||
Manager.prototype._dissect = function () {
|
||||
var err;
|
||||
var suitables;
|
||||
var promises = [];
|
||||
var suitables = {};
|
||||
|
||||
// If something failed, reject the whole resolve promise
|
||||
// with the first error
|
||||
@@ -359,107 +392,162 @@ Manager.prototype._dissect = function () {
|
||||
return;
|
||||
}
|
||||
|
||||
suitables = {};
|
||||
mout.object.forOwn(this._resolved, function (decEndpoints, name) {
|
||||
var nonSemver;
|
||||
var validSemver;
|
||||
var suitable;
|
||||
var target = mout.object.get(this._targets, name + '.target');
|
||||
|
||||
// If this was initially configured as a target without a valid semver target,
|
||||
// it means the user wants it regardless of other ones
|
||||
if (target && semver.valid(target) == null && semver.validRange(target) == null) {
|
||||
suitables[name] = this._targets[name];
|
||||
// TODO: issue warning
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter non-semver ones
|
||||
nonSemver = decEndpoints.filter(function (decEndpoint) {
|
||||
return !decEndpoint.pkgMeta.version;
|
||||
});
|
||||
var promise;
|
||||
var semvers;
|
||||
var nonSemvers;
|
||||
|
||||
// Filter semver ones
|
||||
validSemver = decEndpoints.filter(function (decEndpoint) {
|
||||
semvers = decEndpoints.filter(function (decEndpoint) {
|
||||
return !!decEndpoint.pkgMeta.version;
|
||||
});
|
||||
|
||||
// Sort semver ones
|
||||
validSemver.sort(function (first, second) {
|
||||
semvers.sort(function (first, second) {
|
||||
if (semver.gt(first, second)) {
|
||||
return -1;
|
||||
}
|
||||
if (semver.lt(first, second)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If it gets here, they are equal but priority is given to
|
||||
// installed ones
|
||||
return first.initial ? -1 : (second.initial ? 1 : 0);
|
||||
return 0;
|
||||
});
|
||||
|
||||
// If there are no semver targets
|
||||
if (!validSemver.length) {
|
||||
// TODO: if various non-semver were found, resolve conflicts
|
||||
suitable = nonSemver[0];
|
||||
// Otherwise, find most suitable semver
|
||||
// Filter non-semver ones
|
||||
nonSemvers = decEndpoints.filter(function (decEndpoint) {
|
||||
return !decEndpoint.pkgMeta.version;
|
||||
});
|
||||
|
||||
promise = this._electSuitable(name, semvers, nonSemvers)
|
||||
.then(function (suitable) {
|
||||
suitables[name] = suitable;
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
}, this);
|
||||
|
||||
return Q.all(promises)
|
||||
.then(function () {
|
||||
// Filter only packages that need to be installed
|
||||
this._dissected = mout.object.filter(suitables, function (decEndpoint, name) {
|
||||
var installedMeta = this._installed[name];
|
||||
return !installedMeta || installedMeta._release !== decEndpoint.pkgMeta._release;
|
||||
}, this);
|
||||
|
||||
// Resolve with the package metas of the dissected object
|
||||
return mout.object.map(this._dissected, function (decEndpoint) {
|
||||
return decEndpoint.pkgMeta;
|
||||
});
|
||||
}.bind(this))
|
||||
.then(this._deferred.resolve, this._deferred.reject);
|
||||
};
|
||||
|
||||
Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
|
||||
var picks = [];
|
||||
var dataPicks;
|
||||
var choices;
|
||||
var suitable;
|
||||
|
||||
// If there are no semver targets, there's a conflict if several exist
|
||||
if (!semvers.length) {
|
||||
if (nonSemvers.length > 1) {
|
||||
picks.push.apply(nonSemvers);
|
||||
} else {
|
||||
// TODO: handle conflicts if there is no suitable version
|
||||
suitable = mout.array.find(validSemver, function (subject) {
|
||||
return validSemver.every(function (decEndpoint) {
|
||||
return semver.satisfies(subject.pkgMeta.version, decEndpoint.target);
|
||||
});
|
||||
suitable = nonSemvers[0];
|
||||
}
|
||||
// Otherwise, find most suitable semver
|
||||
} else {
|
||||
suitable = mout.array.find(semvers, function (subject) {
|
||||
return semvers.every(function (decEndpoint) {
|
||||
return semver.satisfies(subject.pkgMeta.version, decEndpoint.target);
|
||||
});
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
picks.push.apply(picks, nonSemvers);
|
||||
}
|
||||
|
||||
// If there are picks, the user needs to choose between them
|
||||
if (picks.length) {
|
||||
// If interactive is disabled, error out
|
||||
if (!this._config.interactive) {
|
||||
throw createError('Unable to find suitable version for ' + name, 'ECONFLICT', {
|
||||
picks: picks
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: handle case which there is a suitable version but there are no-semver ones too
|
||||
if (suitable) {
|
||||
suitables[name] = suitable;
|
||||
} else {
|
||||
throw new Error('No suitable version for "' + name + '"');
|
||||
}
|
||||
}, this);
|
||||
dataPicks = picks.map(function (pick) {
|
||||
return {
|
||||
endpoint: mout.object.pick(pick, ['name', 'source', 'target']),
|
||||
pkgMeta: pick.pkgMeta,
|
||||
canonicalPkg: pick.canonicalPkg,
|
||||
dependants: pick.dependants.map(function (dependant) {
|
||||
return {
|
||||
endpoint: mout.object.pick(dependant, ['name', 'source', 'target']),
|
||||
pkgMeta: dependant.pkgMeta,
|
||||
canonicalPkg: dependant.canonicalPkg
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
// Filter only packages that need to be installed
|
||||
this._dissected = mout.object.filter(suitables, function (decEndpoint, name) {
|
||||
var installedMeta = this._installed[name];
|
||||
return !installedMeta || installedMeta._release !== decEndpoint.pkgMeta._release;
|
||||
}, this);
|
||||
this._logger.conflict('incompatible', 'Unable to find suitable version for ' + name, {
|
||||
picks: dataPicks,
|
||||
name: name
|
||||
});
|
||||
|
||||
// Resolve just with the package metas of the dissected object
|
||||
this._deferred.resolve(mout.object.map(this._dissected, function (decEndpoint) {
|
||||
return decEndpoint.pkgMeta;
|
||||
}));
|
||||
// Question the user
|
||||
choices = picks.map(function (pick, index) {
|
||||
return index + 1;
|
||||
});
|
||||
|
||||
return Q.nfcall(promptly.choose, 'Choice:', choices)
|
||||
.then(function (choice) {
|
||||
return picks[choice - 1];
|
||||
});
|
||||
}
|
||||
|
||||
return Q.resolve(suitable);
|
||||
};
|
||||
|
||||
Manager.prototype._findHighestVersion = function (comparators) {
|
||||
var highest;
|
||||
Manager.prototype._getCap = function (comparators, side) {
|
||||
var matches;
|
||||
var version;
|
||||
var candidate;
|
||||
var cap = {};
|
||||
var compare = side === 'lowest' ? semver.lt : semver.gt;
|
||||
|
||||
comparators.forEach(function (comparator) {
|
||||
// Get version of this comparator
|
||||
// If it's an array, call recursively
|
||||
if (Array.isArray(comparator)) {
|
||||
version = this._findHighestVersion(comparator);
|
||||
candidate = this._getCap(comparator, side);
|
||||
|
||||
// Compare with the current highest version
|
||||
if (!cap.version || compare(candidate.version, cap.version)) {
|
||||
cap = candidate;
|
||||
}
|
||||
// Otherwise extract the version from the comparator
|
||||
// using a simple regexp
|
||||
} else {
|
||||
matches = comparator.match(/\d+\.\d+\.\d+.*$/);
|
||||
matches = comparator.match(/(.*?)(\d+\.\d+\.\d+.*)$/);
|
||||
if (!matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
version = matches[0];
|
||||
}
|
||||
|
||||
// Compare with the current highest version
|
||||
if (!highest || semver.gt(version, highest)) {
|
||||
highest = version;
|
||||
// Compare with the current highest version
|
||||
if (!cap.version || compare(matches[2], cap.version)) {
|
||||
cap.version = matches[2];
|
||||
cap.comparator = matches[1];
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
return highest;
|
||||
return cap;
|
||||
};
|
||||
|
||||
module.exports = Manager;
|
||||
|
||||
@@ -26,7 +26,7 @@ function Project(config, logger) {
|
||||
|
||||
Project.prototype.install = function (endpoints, options) {
|
||||
var that = this;
|
||||
var targets = {};
|
||||
var targets = [];
|
||||
var resolved = {};
|
||||
var installed;
|
||||
|
||||
@@ -38,50 +38,46 @@ Project.prototype.install = function (endpoints, options) {
|
||||
options = options || {};
|
||||
this._production = !!options.production;
|
||||
|
||||
// If no endpoints were specified, simply repair the project
|
||||
// Note that we also repair incompatible packages
|
||||
if (!endpoints) {
|
||||
return this._repair(true)
|
||||
.fin(function () {
|
||||
that._working = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Start by repairing the project, installing only missing packages
|
||||
return this._repair()
|
||||
// Analyse the project
|
||||
.then(that.analyse.bind(this))
|
||||
return this.analyse()
|
||||
.spread(function (json, tree, flattened) {
|
||||
// Mark targets
|
||||
endpoints.forEach(function (endpoint) {
|
||||
var decEndpoint = endpointParser.decompose(endpoint);
|
||||
targets[decEndpoint.name] = decEndpoint;
|
||||
});
|
||||
|
||||
// Mark every package from the tree as resolved
|
||||
// if it's not a target or a non-shared descendant of a target
|
||||
// This is done by walking the tree (deep first) and abort traversal
|
||||
// as soon as one target was found
|
||||
// Walk down the tree adding missing and incompatible
|
||||
// as targets
|
||||
that._walkTree(tree, function (node, name) {
|
||||
if (targets[name]) {
|
||||
return false; // Abort traversal
|
||||
if (node.walked) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolved[name] = node.pkgMeta;
|
||||
if (node.missing || node.incompatible) {
|
||||
targets.push(node);
|
||||
} else {
|
||||
resolved[name] = node;
|
||||
}
|
||||
|
||||
node.walked = true;
|
||||
});
|
||||
|
||||
// Add endpoints as targets
|
||||
if (endpoints) {
|
||||
endpoints.forEach(function (endpoint) {
|
||||
var decEndpoint = endpointParser.decompose(endpoint);
|
||||
targets[decEndpoint.name] = decEndpoint;
|
||||
});
|
||||
}
|
||||
|
||||
// Mark installed
|
||||
installed = mout.object.map(flattened, function (decEndpoint) {
|
||||
return decEndpoint.pkgMeta;
|
||||
});
|
||||
})
|
||||
// Bootstrap the process
|
||||
.then(function () {
|
||||
return that._bootstrap(targets, resolved, installed);
|
||||
return that._bootstrap(mout.object.values(targets), resolved, installed);
|
||||
})
|
||||
// Handle save and saveDev options
|
||||
.then(function (installed) {
|
||||
if (!options.save && !options.saveDev) {
|
||||
return;
|
||||
return installed;
|
||||
}
|
||||
|
||||
// Cycle through the initial targets and not the installed
|
||||
@@ -114,11 +110,9 @@ Project.prototype.install = function (endpoints, options) {
|
||||
|
||||
Project.prototype.update = function (names, options) {
|
||||
var that = this;
|
||||
var targets;
|
||||
var resolved;
|
||||
var targets = [];
|
||||
var resolved = {};
|
||||
var installed;
|
||||
var repaired;
|
||||
var promise;
|
||||
|
||||
// If already working, error out
|
||||
if (this._working) {
|
||||
@@ -128,77 +122,71 @@ Project.prototype.update = function (names, options) {
|
||||
options = options || {};
|
||||
this._production = !!options.production;
|
||||
|
||||
// If no names were specified, we update every package
|
||||
if (!names) {
|
||||
// Analyse the project
|
||||
promise = this.analyse()
|
||||
.spread(function (json, tree, flattened) {
|
||||
// Analyse the project
|
||||
return this.analyse()
|
||||
.spread(function (json, tree, flattened) {
|
||||
// If no names were specified, update every package
|
||||
if (!names) {
|
||||
// Mark each json entry as targets
|
||||
targets = mout.object.map(json.dependencies, function (value, key) {
|
||||
return endpointParser.json2decomposed(key, value);
|
||||
mout.object.forOwn(json.dependencies, function (value, key) {
|
||||
var decEndpoint = endpointParser.json2decomposed(key, value);
|
||||
decEndpoint.dependants = {};
|
||||
decEndpoint.dependants[tree.name] = tree;
|
||||
|
||||
targets.push(decEndpoint);
|
||||
});
|
||||
|
||||
// Mark installed
|
||||
installed = mout.object.map(flattened, function (decEndpoint) {
|
||||
return decEndpoint.pkgMeta;
|
||||
});
|
||||
});
|
||||
// Otherwise we selectively update the specified ones
|
||||
} else {
|
||||
// Start by repairing the project
|
||||
// Note that we also repair incompatible packages
|
||||
promise = this._repair(true)
|
||||
// Analyse the project
|
||||
.then(function (result) {
|
||||
repaired = result;
|
||||
return that.analyse();
|
||||
})
|
||||
.spread(function (json, tree, flattened) {
|
||||
targets = {};
|
||||
resolved = {};
|
||||
|
||||
// Mark targets
|
||||
names.forEach(function (name) {
|
||||
var decEndpoint = flattened[name];
|
||||
var jsonEntry;
|
||||
|
||||
if (!decEndpoint) {
|
||||
throw createError('Package ' + name + ' is not installed', 'ENOTINSTALLED');
|
||||
// Mark extraneous as targets
|
||||
mout.object.forOwn(flattened, function (decEndpoint) {
|
||||
if (decEndpoint.extraneous) {
|
||||
targets.push(decEndpoint);
|
||||
}
|
||||
|
||||
// If it was repaired, don't include in the targets
|
||||
if (repaired[name]) {
|
||||
});
|
||||
// Otherwise, selectively update the specified ones
|
||||
} else {
|
||||
// Walk down the tree adding missing, incompatible
|
||||
// and names as targets
|
||||
that._walkTree(tree, function (node, name) {
|
||||
if (node.walked) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use json entry if available, fallbacking to the installed one
|
||||
jsonEntry = json.dependencies && json.dependencies[name];
|
||||
if (jsonEntry) {
|
||||
targets[name] = endpointParser.json2decomposed(name, jsonEntry);
|
||||
if (node.missing || node.incompatible) {
|
||||
targets.push(node);
|
||||
} else if (names.indexOf(name) !== -1) {
|
||||
targets.push(node);
|
||||
} else {
|
||||
targets[name] = decEndpoint;
|
||||
}
|
||||
});
|
||||
|
||||
// Mark every package from the tree as resolved
|
||||
// if it's not a target or a non-shared descendant of a target
|
||||
that._walkTree(tree, function (node, name) {
|
||||
if (targets[name]) {
|
||||
return false; // Abort traversal
|
||||
resolved[name] = node;
|
||||
}
|
||||
|
||||
resolved[name] = node.pkgMeta;
|
||||
node.walked = true;
|
||||
});
|
||||
|
||||
// Mark installed
|
||||
installed = mout.object.map(flattened, function (decEndpoint) {
|
||||
return decEndpoint.pkgMeta;
|
||||
// Mark extraneous as targets only if
|
||||
// it's not already a target
|
||||
mout.object.forOwn(flattened, function (decEndpoint) {
|
||||
var foundTarget;
|
||||
var name = decEndpoint.name;
|
||||
|
||||
if (decEndpoint.extraneous && names.indexOf(name) !== -1) {
|
||||
foundTarget = !!mout.array.find(targets, function (target) {
|
||||
return target.name === name;
|
||||
});
|
||||
|
||||
if (!foundTarget) {
|
||||
targets.push(decEndpoint);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Mark installed
|
||||
installed = mout.object.map(flattened, function (decEndpoint) {
|
||||
return decEndpoint.pkgMeta;
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
// Bootstrap the process
|
||||
return promise.then(function () {
|
||||
.then(function () {
|
||||
return that._bootstrap(targets, resolved, installed);
|
||||
})
|
||||
.fin(function () {
|
||||
@@ -303,12 +291,17 @@ Project.prototype.analyse = function () {
|
||||
source: this._config.cwd,
|
||||
target: json.version,
|
||||
pkgMeta: json,
|
||||
canonicalPkg: this._config.cwd,
|
||||
root: true
|
||||
};
|
||||
|
||||
if (json.version) {
|
||||
root.pkgMeta._release = json.version;
|
||||
}
|
||||
|
||||
// Restore the original dependencies cross-references,
|
||||
// that is, the parent-child relationships
|
||||
this._restoreNode(root, flattened);
|
||||
this._restoreNode(root, flattened, 'dependencies');
|
||||
// Do the same for the dev dependencies
|
||||
if (!this._production) {
|
||||
this._restoreNode(root, flattened, 'devDependencies');
|
||||
@@ -316,22 +309,27 @@ Project.prototype.analyse = function () {
|
||||
|
||||
// Parse extraneous
|
||||
mout.object.forOwn(flattened, function (decEndpoint) {
|
||||
var release;
|
||||
|
||||
if (!decEndpoint.dependants) {
|
||||
decEndpoint.extraneous = true;
|
||||
|
||||
// Restore it
|
||||
this._restoreNode(decEndpoint, flattened);
|
||||
this._restoreNode(decEndpoint, flattened, 'dependencies');
|
||||
// Do the same for the dev dependencies
|
||||
if (!this._production) {
|
||||
this._restoreNode(decEndpoint, flattened, 'devDependencies');
|
||||
}
|
||||
|
||||
release = decEndpoint.pkgMeta._release;
|
||||
this._logger.log('warn', 'extraneous', decEndpoint.name + (release ? '#' + release : release), {
|
||||
pkgMeta: decEndpoint.pkgMeta,
|
||||
canonicalPkg: decEndpoint.canonicalPkg
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
|
||||
// The package meta set above is not really a package meta
|
||||
// so we delete it from the root
|
||||
// Also remove it from the flattened tree
|
||||
delete root.pkgMeta;
|
||||
// Remove root from the flattened tree
|
||||
delete flattened[json.name];
|
||||
|
||||
return [json, root, flattened];
|
||||
@@ -343,7 +341,8 @@ Project.prototype.analyse = function () {
|
||||
Project.prototype._bootstrap = function (targets, resolved, installed) {
|
||||
// Configure the manager and kick in the resolve process
|
||||
return this._manager
|
||||
.configure(mout.object.values(targets), resolved, installed)
|
||||
.setProduction(this._production)
|
||||
.configure(targets, resolved, installed)
|
||||
.resolve()
|
||||
// Install resolved ones
|
||||
.then(function () {
|
||||
@@ -351,39 +350,6 @@ Project.prototype._bootstrap = function (targets, resolved, installed) {
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Project.prototype._repair = function (incompatible) {
|
||||
var that = this;
|
||||
|
||||
return this.analyse()
|
||||
.spread(function (json, tree, flattened) {
|
||||
var targets = [];
|
||||
var resolved = {};
|
||||
var isBroken = false;
|
||||
|
||||
// Figure out which are the missing/incompatible ones
|
||||
// by parsing the flattened tree
|
||||
mout.object.forOwn(flattened, function (decEndpoint, name) {
|
||||
if (decEndpoint.missing) {
|
||||
targets.push(decEndpoint);
|
||||
isBroken = true;
|
||||
} else if (incompatible && decEndpoint.incompatible) {
|
||||
targets.push(decEndpoint);
|
||||
isBroken = true;
|
||||
} else if (!decEndpoint.extraneous) {
|
||||
resolved[name] = decEndpoint.pkgMeta;
|
||||
}
|
||||
});
|
||||
|
||||
// Do not proceed if the project does not need to be repaired
|
||||
if (!isBroken) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Configure the manager and kick in the resolve process
|
||||
return that._bootstrap(targets, resolved);
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype._readJson = function () {
|
||||
var that = this;
|
||||
|
||||
@@ -550,20 +516,27 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
|
||||
node.dependencies = {};
|
||||
node.dependants = node.dependants || {};
|
||||
|
||||
mout.object.forOwn(node.pkgMeta[jsonKey || 'dependencies'], function (value, key) {
|
||||
mout.object.forOwn(node.pkgMeta[jsonKey], function (value, key) {
|
||||
var local = flattened[key];
|
||||
var json = endpointParser.json2decomposed(key, value);
|
||||
var compatible;
|
||||
|
||||
// Check if the dependency is not installed
|
||||
if (!local) {
|
||||
flattened[key] = local = json;
|
||||
local.missing = true;
|
||||
// Even if it is installed, check if it's compatible
|
||||
} else if (!local.incompatible && !local.missing && !this._manager.areCompatible(local.pkgMeta.version || '*', json.target)) {
|
||||
json.pkgMeta = local.pkgMeta;
|
||||
flattened[key] = local = json;
|
||||
local = json;
|
||||
local.incompatible = true;
|
||||
} else {
|
||||
if (local.missing) {
|
||||
compatible = this._manager.areCompatible(local.target, json.target);
|
||||
} else {
|
||||
compatible = this._manager.areCompatible(local.pkgMeta.version || '*', json.target);
|
||||
}
|
||||
|
||||
if (!compatible) {
|
||||
local = json;
|
||||
local.incompatible = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Cross reference
|
||||
@@ -572,7 +545,7 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
|
||||
local.dependants[node.name] = node;
|
||||
|
||||
// Call restore for this dependency
|
||||
this._restoreNode(local, flattened);
|
||||
this._restoreNode(local, flattened, jsonKey);
|
||||
}, this);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
require('colors');
|
||||
var mout = require('mout');
|
||||
var semver = require('semver');
|
||||
var template = require('../util/template');
|
||||
|
||||
var wideCommands = ['install', 'update'];
|
||||
@@ -71,49 +72,31 @@ StandardRenderer.prototype.log = function (log) {
|
||||
};
|
||||
|
||||
StandardRenderer.prototype.updateNotice = function (data) {
|
||||
template('std/update-notice.std', data)
|
||||
.then(function (str) {
|
||||
this._write(process.stderr, str);
|
||||
}.bind(this), this.error.bind(this));
|
||||
var str = template.render('std/update-notice.std', data);
|
||||
this._write(process.stderr, str);
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
|
||||
StandardRenderer.prototype._install = function (installed) {
|
||||
// TODO: render tree of installed packages
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._update = function (updated) {
|
||||
// TODO: render tree of updated packages
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._help = function (data) {
|
||||
var str;
|
||||
var that = this;
|
||||
var specific;
|
||||
|
||||
if (!data.command) {
|
||||
template('std/help.std', data)
|
||||
.then(function (str) {
|
||||
that._write(process.stdout, str);
|
||||
}, this.error.bind(this));
|
||||
str = template.render('std/help.std', data);
|
||||
that._write(process.stdout, str);
|
||||
} else {
|
||||
// Try to render the help template for this command
|
||||
template('std/help-' + data.command + '.std', data)
|
||||
.then(function (str) {
|
||||
that._write(process.stdout, str);
|
||||
}, function (err) {
|
||||
// If it failed with something else than ENOENT
|
||||
// error out
|
||||
if (err.code !== 'ENOENT') {
|
||||
return err;
|
||||
}
|
||||
// Check if a specific template exists for the command
|
||||
specific = 'std/help-' + data.command + '.std';
|
||||
|
||||
// Otherwise the template does not exist,
|
||||
// so render the generic one
|
||||
return template('std/help-generic.std', data)
|
||||
.then(function (str) {
|
||||
that._write(process.stdout, str);
|
||||
}, that.error.bind(that));
|
||||
});
|
||||
if (template.exists(specific)) {
|
||||
str = template.render(specific, data);
|
||||
} else {
|
||||
str = template.render('std/help-generic.std', data);
|
||||
}
|
||||
|
||||
that._write(process.stdout, str);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -138,6 +121,52 @@ StandardRenderer.prototype._mutualLog = function (log) {
|
||||
this._genericLog(log);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._incompatibleLog = function (log) {
|
||||
var str;
|
||||
|
||||
// Generate dependants string for each pick
|
||||
log.data.picks.forEach(function (pick) {
|
||||
|
||||
pick.dependants = pick.dependants.map(function (dependant) {
|
||||
var release = dependant.pkgMeta._release;
|
||||
return dependant.endpoint.name + (release ? '#' + release : '');
|
||||
}).join(', ');
|
||||
});
|
||||
|
||||
// Sort picks by version/release
|
||||
log.data.picks.sort(function (pick1, pick2) {
|
||||
var version1 = pick1.pkgMeta.version;
|
||||
var version2 = pick2.pkgMeta.version;
|
||||
|
||||
// If both have versions, compare their versions using semver
|
||||
if (version1 && version2) {
|
||||
if (semver.gt(version1, version2)) {
|
||||
return 1;
|
||||
}
|
||||
if (semver.lt(version1, version2)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Give priority to the one that is a version
|
||||
if (version1) {
|
||||
return 1;
|
||||
}
|
||||
if (version2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
str = template.render('std/incompatible.std', log.data);
|
||||
|
||||
this._write(process.stdout, '\n');
|
||||
this._write(process.stdout, str);
|
||||
this._write(process.stdout, '\n');
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._checkoutLog = function (log) {
|
||||
if (this._compact) {
|
||||
log.message = log.origin + '#' + log.message;
|
||||
|
||||
@@ -14,32 +14,33 @@ mout.object.forOwn(helpers, function (register) {
|
||||
register(Handlebars);
|
||||
});
|
||||
|
||||
function template(name, data, escape) {
|
||||
var compiled = cache[name];
|
||||
var templatePath;
|
||||
function render(name, data, escape) {
|
||||
var contents;
|
||||
|
||||
// Check if already compiled
|
||||
// Note that the cache might contain promises so we resolve
|
||||
if (compiled) {
|
||||
return Q.resolve(compiled)
|
||||
.then(function (compiled) {
|
||||
return compiled(data);
|
||||
});
|
||||
if (cache[name]) {
|
||||
return cache[name](data);
|
||||
}
|
||||
|
||||
// Otherwise, read the file, compile and cache
|
||||
templatePath = path.join(templatesDir, name);
|
||||
compiled = cache[name] = Q.nfcall(fs.readFile, templatePath)
|
||||
.then(function (contents) {
|
||||
return cache[name] = Handlebars.compile(contents.toString(), {
|
||||
noEscape: !escape
|
||||
});
|
||||
contents = fs.readFileSync(path.join(templatesDir, name)).toString();
|
||||
cache[name] = Handlebars.compile(contents, {
|
||||
noEscape: !escape
|
||||
});
|
||||
|
||||
// Call the function again
|
||||
return compiled.then(function () {
|
||||
return template(name, data);
|
||||
});
|
||||
return render(name, data, escape);
|
||||
}
|
||||
|
||||
module.exports = template;
|
||||
function exists(name) {
|
||||
if (cache[name]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return fs.existsSync(path.join(templatesDir, name));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
render: render,
|
||||
exists: exists
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user