Files
bower/lib/commands/uninstall.js
2012-12-01 21:32:34 +01:00

170 lines
4.8 KiB
JavaScript

// ==========================================
// BOWER: Uninstall API
// ==========================================
// Copyright 2012 Twitter, Inc
// Licensed under The MIT License
// http://opensource.org/licenses/MIT
// ==========================================
var Emitter = require('events').EventEmitter;
var async = require('async');
var nopt = require('nopt');
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var template = require('../util/template');
var Manager = require('../core/manager');
var config = require('../core/config');
var help = require('./help');
var optionTypes = { help: Boolean, force: Boolean, save: Boolean };
var shorthand = { 'h': ['--help'], 'S': ['--save'], 'f': ['--force'] };
module.exports = function (names, options) {
var packages, uninstallables, packagesCount = {};
var emitter = new Emitter;
var manager = new Manager;
var force = !!options.force;
manager.on('data', emitter.emit.bind(emitter, 'data'));
manager.on('error', emitter.emit.bind(emitter, 'error'));
var resolveLocal = function () {
packages = _.flatten(_.values(manager.dependencies));
uninstallables = packages.filter(function (pkg) {
return _.include(names, pkg.name);
});
async.forEach(packages, function (pkg, next) {
pkg.once('loadJSON', next).loadJSON();
}, function () {
if (showWarnings(force) && !force) return;
includeShared();
uninstall();
});
};
var showWarnings = function (force) {
var foundConflicts = false;
packages.forEach(function (pkg) {
if (!pkg.json.dependencies) return;
if (uninstallables.indexOf(pkg) !== -1) return;
var conflicts = _.intersection(
Object.keys(pkg.json.dependencies),
_.pluck(uninstallables, 'name')
);
if (conflicts.length) {
foundConflicts = true;
if (!force) {
conflicts.forEach(function (conflictName) {
emitter.emit('data', template('warning-uninstall', { packageName: pkg.name, conflictName: conflictName }, true));
});
}
}
});
if (foundConflicts && !force) {
emitter.emit('data', template('warn', { message: 'To proceed, run uninstall with the --force flag'}, true));
}
return foundConflicts;
};
var includeShared = function () {
count(packages, packagesCount);
uninstallables.forEach(function (pkg) {
parseUninstallableDeps(pkg);
});
for (var name in packagesCount) {
var pkg = manager.dependencies[name][0];
var jsonDeps = manager.json.dependencies ? manager.json.dependencies : {};
if ((!packagesCount[name] || (packagesCount[name] === 1 && !jsonDeps[name]))
&& uninstallables.indexOf(pkg) === -1) {
if (packagesCount[name] > 0) packagesCount[name] -= 1;
uninstallables.push(pkg);
}
}
};
var count = function (packages, counts) {
packages.forEach(function (pkg) {
counts[pkg.name] = (counts[pkg.name] || 0) + 1;
if (pkg.json.dependencies) {
for (var key in pkg.json.dependencies) {
count(manager.dependencies[key], counts);
}
}
});
};
var parseUninstallableDeps = function (pkg) {
if (packagesCount[pkg.name] > 0) packagesCount[pkg.name] -= 1;
if (pkg.json.dependencies) {
for (var key in pkg.json.dependencies) {
parseUninstallableDeps(manager.dependencies[key][0]);
}
}
};
var uninstall = function () {
async.forEach(uninstallables, function (pkg, next) {
pkg.on('uninstall', next).uninstall();
}, function () {
// Finally save
if (options.save) save();
emitter.emit.bind(emitter, 'end');
});
};
var save = function () {
if (manager.json.dependencies) {
names.forEach(function (name) {
delete manager.json.dependencies[name];
});
fs.writeFileSync(path.join(manager.cwd, config.json), JSON.stringify(manager.json, null, 2));
}
};
manager.on('loadJSON', function () {
manager.on('resolveLocal', resolveLocal).resolveLocal();
}).loadJSON();
return emitter;
};
module.exports.line = function (argv) {
var options = nopt(optionTypes, shorthand, argv);
if (options.help) return help('uninstall');
var names = options.argv.remain.slice(1);
return module.exports(names, options);
};
module.exports.completion = function (opts, cb) {
var word = opts.word;
// completing options?
if (opts.words[0] === 'uninstall' && word.charAt(0) === '-') {
return cb(null, Object.keys(optionTypes).map(function (option) {
return '--' + option;
}));
}
fs.readdir(config.directory, function (err, dirs) {
// ignore ENOENT, ./components not created yet
if (err && err.code === 'ENOENT') return cb(null, []);
cb(err, dirs);
});
};