in src/plugman/uninstall.js [93:227]
module.exports.uninstallPlugin = function (id, plugins_dir, options) {
plugins_dir = cordovaUtil.convertToRealPathSafe(plugins_dir);
options = options || {};
options.pluginInfoProvider = options.pluginInfoProvider || new PluginInfoProvider();
const pluginInfoProvider = options.pluginInfoProvider;
const plugin_dir = path.join(plugins_dir, id);
// @tests - important this event is checked spec/uninstall.spec.js
events.emit('log', 'Removing "' + id + '"');
// If already removed, skip.
if (!fs.existsSync(plugin_dir)) {
events.emit('verbose', 'Plugin "' + id + '" already removed (' + plugin_dir + ')');
return Promise.resolve();
}
/*
* Deletes plugin from plugins directory and node_modules directory.
*
* @param {String} id the id of the plugin being removed
*
* @return {Promise||Error} Returns a empty promise or a promise of doing the npm uninstall
*/
const doDelete = function (id) {
const plugin_dir = path.join(plugins_dir, id);
if (!fs.existsSync(plugin_dir)) {
events.emit('verbose', 'Plugin "' + id + '" already removed (' + plugin_dir + ')');
return Promise.resolve();
}
fs.removeSync(plugin_dir);
events.emit('verbose', 'Deleted plugin "' + id + '"');
// remove plugin from node_modules directory
return npmUninstall(id, options.projectRoot, options);
};
// We've now lost the metadata for the plugins that have been uninstalled, so we can't use that info.
// Instead, we list all dependencies of the target plugin, and check the remaining metadata to see if
// anything depends on them, or if they're listed as top-level.
// If neither, they can be deleted.
const top_plugin_id = id;
// Recursively remove plugins which were installed as dependents (that are not top-level)
const toDelete = [];
function findDependencies (pluginId) {
const depPluginDir = path.join(plugins_dir, pluginId);
// Skip plugin check for dependencies if it does not exist (CB-7846).
if (!fs.existsSync(depPluginDir)) {
events.emit('verbose', 'Plugin "' + pluginId + '" does not exist (' + depPluginDir + ')');
return;
}
const pluginInfo = pluginInfoProvider.get(depPluginDir);
// TODO: Should remove dependencies in a separate step, since dependencies depend on platform.
const deps = pluginInfo.getDependencies();
deps.forEach(function (d) {
if (toDelete.indexOf(d.id) === -1) {
toDelete.push(d.id);
findDependencies(d.id);
}
});
}
findDependencies(top_plugin_id);
toDelete.push(top_plugin_id);
// Okay, now we check if any of these are depended on, or top-level.
// Find the installed platforms by whether they have a metadata file.
const platforms = Object.keys(platform_modules).filter(function (platform) {
return fs.existsSync(path.join(plugins_dir, platform + '.json'));
});
// Can have missing plugins on some platforms when not supported..
const dependList = {};
platforms.forEach(function (platform) {
const platformJson = PlatformJson.load(plugins_dir, platform);
const depsInfo = dependencies.generateDependencyInfo(platformJson, plugins_dir, pluginInfoProvider);
const tlps = depsInfo.top_level_plugins;
let deps;
// Top-level deps must always be explicitely asked to remove by user
tlps.forEach(function (plugin_id) {
if (top_plugin_id === plugin_id) { return; }
const i = toDelete.indexOf(plugin_id);
if (i >= 0) { toDelete.splice(i, 1); }
});
toDelete.forEach(function (plugin) {
deps = dependencies.dependents(plugin, depsInfo, platformJson, pluginInfoProvider);
const i = deps.indexOf(top_plugin_id);
if (i >= 0) { deps.splice(i, 1); } // remove current/top-level plugin as blocking uninstall
if (deps.length) {
dependList[plugin] = deps.join(', ');
}
});
});
const dependPluginIds = toDelete.filter(function (plugin_id) {
return dependList[plugin_id];
});
const createMsg = function (plugin_id) {
return 'Plugin "' + plugin_id + '" is required by (' + dependList[plugin_id] + ')';
};
const createMsg2 = function (plugin_id) {
return createMsg(plugin_id) + ' and cannot be removed (hint: use -f or --force)';
};
if (!options.force && dependPluginIds.includes(top_plugin_id)) {
const msg = createMsg2(top_plugin_id);
return Promise.reject(new CordovaError(msg));
}
// action emmiting events.
if (options.force) {
dependPluginIds.forEach(function (plugin_id) {
const msg = createMsg(plugin_id);
events.emit('log', msg + ' but forcing removal.');
});
} else {
dependPluginIds.forEach(function (plugin_id) {
const msg = createMsg2(plugin_id);
events.emit('warn', msg);
});
}
const deletePluginIds = options.force ? toDelete : toDelete.filter(function (plugin_id) { return !dependList[plugin_id]; });
const deleteExecList = deletePluginIds.map(function (plugin_id) {
return function () { return doDelete(plugin_id); };
});
return deleteExecList.reduce(function (acc, deleteExec) {
return acc.then(deleteExec);
}, Promise.resolve());
};