Implement the uninstall command.

Made also some tweaks to the render stuff.
This commit is contained in:
André Cruz
2013-05-30 21:57:05 +01:00
parent 3cf56ff7d5
commit 20ba998383
8 changed files with 268 additions and 52 deletions

View File

@@ -3,6 +3,8 @@ var path = require('path');
var fs = require('fs');
var Q = require('q');
var mout = require('mout');
var rimraf = require('rimraf');
var promptly = require('promptly');
var bowerJson = require('bower-json');
var Manager = require('./Manager');
var defaultConfig = require('../config');
@@ -40,7 +42,7 @@ Project.prototype.install = function (endpoints, options) {
// Start by repairing the project, installing only missing packages
return this._repair()
// Analyse the project
.then(that._analyse.bind(this))
.then(that.analyse.bind(this))
.spread(function (json, tree, flattened) {
var targets = {};
var resolved = {};
@@ -71,19 +73,19 @@ Project.prototype.install = function (endpoints, options) {
return that._bootstrap(targets, resolved, installed)
// Handle save and saveDev options
.then(function () {
var key;
var jsonKey;
if (!options.save && !options.saveDev) {
return;
}
key = options.save ? 'dependencies' : 'devDependencies';
that._json[key] = that._json[key] || {};
jsonKey = options.save ? 'dependencies' : 'devDependencies';
that._json[jsonKey] = that._json[jsonKey] || {};
mout.object.forOwn(targets, function (decEndpoint) {
var source = decEndpoint.registry ? '' : decEndpoint.source;
var target = decEndpoint.pkgMeta.version ? '~' + decEndpoint.pkgMeta.version : decEndpoint.target;
that._json[key][decEndpoint.name] = mout.string.ltrim(source + '#' + target, ['#']);
that._json[jsonKey][decEndpoint.name] = mout.string.ltrim(source + '#' + target, ['#']);
});
return that._saveJson()
@@ -116,7 +118,7 @@ Project.prototype.update = function (names, options) {
// If no names were specified, we update every package
if (!names) {
// Analyse the project
promise = this._analyse()
promise = this.analyse()
.spread(function (json, tree, flattened) {
// Mark each json entry as targets
targets = mout.object.map(json.dependencies, function (value, key) {
@@ -136,7 +138,7 @@ Project.prototype.update = function (names, options) {
// Analyse the project
.then(function (result) {
repaired = result;
return that._analyse();
return that.analyse();
})
.spread(function (json, tree, flattened) {
targets = {};
@@ -192,16 +194,98 @@ Project.prototype.update = function (names, options) {
};
Project.prototype.uninstall = function (names, options) {
var that = this;
var deferred = Q.defer();
this.analyse()
.spread(function (json, tree, flattened) {
var promise = Q.resolve();
var packages = [];
names.forEach(function (name) {
var decEndpoint = flattened[name];
// Check if it is not installed
if (!decEndpoint || decEndpoint.missing) {
packages[name] = null;
return;
}
// Decide if the package will be uninstalled or skipped
// This is done with a promise that resolves to a boolean
promise = promise
.then(function () {
var dependants;
var message;
var data;
// Check if it has dependants
// Note that the root is filtered from the dependants
// as well as other dependants marked to be uninstalled
dependants = [];
mout.object.forOwn(decEndpoint.dependants, function (decEndpoint) {
if (!decEndpoint.root && names.indexOf(decEndpoint.name) === -1) {
dependants.push(decEndpoint);
}
});
// If the package has no dependants or the force config is enabled,
// mark it to be removed
if (!dependants.length || that._config.force) {
packages[name] = decEndpoint.dir;
// Otherwise we need to figure it out if the user really wants to remove it,
// even with dependants
} else {
message = dependants.map(function (dep) { return dep.name; }).join(', ') + ' depend on ' + decEndpoint.name;
data = {
package: decEndpoint.name,
dependants: dependants.map(function (decEndpoint) {
return decEndpoint.name;
})
};
// If interactive is disabled, error out
if (!that._config.interactive) {
throw createError(message, 'ECONFLICT', {
skippable: true,
data: data
});
// Question the user
} else {
deferred.notify({
level: 'conflict',
id: 'mutual',
message: message,
data: data
});
return Q.nfcall(promptly.confirm, 'Continue anyway? (y/n)')
.then(function (confirmed) {
// If the user decided to skip it, remove from the array so that it won't
// influence subsequent dependants
if (!confirmed) {
mout.array.remove(names, name);
} else {
packages[name] = decEndpoint.dir;
}
});
}
}
});
});
return promise
.then(function () {
return that._removePackages(packages, options)
.progress(deferred.notify);
});
})
.then(deferred.resolve, deferred.reject);
return deferred.promise;
};
Project.prototype.getTree = function () {
};
// -----------------
Project.prototype._analyse = function () {
Project.prototype.analyse = function () {
return F.all([
this._readJson(),
this._readInstalled()
@@ -215,10 +299,10 @@ Project.prototype._analyse = function () {
source: this._config.cwd,
target: json.version,
dir: this._config.cwd,
pkgMeta: json
pkgMeta: json,
root: true
};
// Restore the original dependencies cross-references,
// that is, the parent-child relationships
this._restoreNode(root, flattened);
@@ -238,6 +322,8 @@ Project.prototype._analyse = function () {
}.bind(this));
};
// -----------------
Project.prototype._bootstrap = function (targets, resolved, installed) {
// Configure the manager and kick in the resolve process
return this._manager
@@ -252,7 +338,7 @@ Project.prototype._bootstrap = function (targets, resolved, installed) {
Project.prototype._repair = function (incompatible) {
var that = this;
return this._analyse()
return this.analyse()
.spread(function (json, tree, flattened) {
var targets = [];
var resolved = {};
@@ -332,7 +418,7 @@ Project.prototype._readJson = function () {
return deferred.promise;
};
Project.prototype._saveJson = function (json) {
Project.prototype._saveJson = function () {
var deferred = Q.defer();
if (!this._jsonFile) {
@@ -345,9 +431,7 @@ Project.prototype._saveJson = function (json) {
deferred.resolve();
});
} else {
json = json || this._json;
Q.nfcall(fs.writeFile, this._jsonFile, JSON.stringify(json, null, ' '))
Q.nfcall(fs.writeFile, this._jsonFile, JSON.stringify(this._json, null, ' '))
.then(deferred.resolve, deferred.reject, deferred.notify);
}
@@ -399,6 +483,65 @@ Project.prototype._readInstalled = function () {
});
};
Project.prototype._removePackages = function (packages, options) {
var promises = [];
var jsonKey = options.save ? 'dependencies' : (options.saveDev ? 'devDependencies' : null);
var deferred = Q.defer();
mout.object.forOwn(packages, function (dir, name) {
var promise;
// Delete directory
if (!dir) {
process.nextTick(function () {
deferred.notify({
level: 'info',
id: 'missing',
message: name,
data: {
package: name
}
});
});
promise = Q.resolve();
} else {
process.nextTick(function () {
deferred.notify({
level: 'action',
id: 'uninstall',
message: name,
data: {
package: name,
dir: dir
}
});
});
promise = Q.nfcall(rimraf, dir);
}
// Remove from json only if successfully deleted
if (jsonKey && this._json[jsonKey]) {
promise = promise
.then(function () {
delete this._json[jsonKey][name];
}.bind(this));
}
promises.push(promise);
}, this);
Q.all(promises)
// Save json
.then(this._saveJson.bind(this))
// Resolve with removed packages
.then(function () {
return packages;
})
.then(deferred.resolve, deferred.reject, deferred.notify);
return deferred.promise;
};
Project.prototype._walkTree = function (node, fn) {
var queue = [node];
var result;
@@ -434,7 +577,7 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
flattened[key] = local = json;
local.missing = true;
// Even if it is installed, check if it's compatible
} else if (!local.incompatible && !this._manager.areCompatible(local.pkgMeta.version || '*', json.target)) {
} else if (!local.incompatible && !local.missing && !this._manager.areCompatible(local.pkgMeta.version || '*', json.target)) {
json.pkgMeta = local.pkgMeta;
flattened[key] = local = json;
local = json;