etherpad-lite/src/static/js/pluginfw/plugins.js

160 lines
5.1 KiB
JavaScript
Raw Normal View History

const fs = require('fs').promises;
const hooks = require('./hooks');
2020-11-23 13:24:19 -05:00
const npm = require('npm/lib/npm.js');
const readInstalled = require('./read-installed.js');
const path = require('path');
const tsort = require('./tsort');
const util = require('util');
const _ = require('underscore');
const settings = require('../../../node/utils/Settings');
2020-11-23 13:24:19 -05:00
const pluginUtils = require('./shared');
const defs = require('./plugin_defs');
exports.prefix = 'ep_';
exports.formatPlugins = function () {
2020-11-23 13:24:19 -05:00
return _.keys(defs.plugins).join(', ');
2012-04-04 09:51:46 +02:00
};
exports.formatPluginsWithVersion = function () {
2020-11-23 13:24:19 -05:00
const plugins = [];
_.forEach(defs.plugins, (plugin) => {
if (plugin.package.name !== 'ep_etherpad-lite') {
const pluginStr = `${plugin.package.name}@${plugin.package.version}`;
plugins.push(pluginStr);
}
});
2020-11-23 13:24:19 -05:00
return plugins.join(', ');
};
exports.formatParts = function () {
2020-11-23 13:24:19 -05:00
return _.map(defs.parts, (part) => part.full_name).join('\n');
2012-04-04 09:51:46 +02:00
};
2012-06-04 14:33:38 +02:00
exports.formatHooks = function (hook_set_name) {
2020-11-23 13:24:19 -05:00
const res = [];
const hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks');
2012-06-04 14:33:38 +02:00
2020-11-23 13:24:19 -05:00
_.chain(hooks).keys().forEach((hook_name) => {
_.forEach(hooks[hook_name], (hook) => {
res.push(`<dt>${hook.hook_name}</dt><dd>${hook.hook_fn_name} from ${hook.part.full_name}</dd>`);
});
});
2020-11-23 13:24:19 -05:00
return `<dl>${res.join('\n')}</dl>`;
2012-04-04 09:51:46 +02:00
};
2020-11-13 13:59:20 -05:00
const callInit = async () => {
await Promise.all(Object.keys(defs.plugins).map(async (plugin_name) => {
2020-11-23 13:24:19 -05:00
const plugin = defs.plugins[plugin_name];
const ep_init = path.normalize(path.join(plugin.package.path, '.ep_initialized'));
2020-11-13 13:59:20 -05:00
try {
await fs.stat(ep_init);
} catch (err) {
await fs.writeFile(ep_init, 'done');
2020-11-23 13:24:19 -05:00
await hooks.aCallAll(`init_${plugin_name}`, {});
2020-11-13 13:59:20 -05:00
}
}));
2020-11-23 13:24:19 -05:00
};
exports.pathNormalization = function (part, hook_fn_name, hook_name) {
const tmp = hook_fn_name.split(':'); // hook_fn_name might be something like 'C:\\foo.js:myFunc'.
// If there is a single colon assume it's 'filename:funcname' not 'C:\\filename'.
const functionName = (tmp.length > 1 ? tmp.pop() : null) || hook_name;
const moduleName = tmp.join(':') || part.plugin;
const packageDir = path.dirname(defs.plugins[part.plugin].package.path);
const fileName = path.normalize(path.join(packageDir, moduleName));
return `${fileName}:${functionName}`;
2020-11-23 13:24:19 -05:00
};
2012-05-28 18:33:03 -07:00
exports.update = async function () {
2020-11-23 13:24:19 -05:00
const packages = await exports.getPackages();
const parts = {}; // Key is full name. sortParts converts this into a topologically sorted array.
const plugins = {};
// Load plugin metadata ep.json
2020-11-13 13:59:20 -05:00
await Promise.all(Object.keys(packages).map(
2020-11-23 13:24:19 -05:00
async (pluginName) => await loadPlugin(packages, pluginName, plugins, parts)));
2020-11-13 13:59:20 -05:00
defs.plugins = plugins;
defs.parts = sortParts(parts);
defs.hooks = pluginUtils.extractHooks(defs.parts, 'hooks', exports.pathNormalization);
defs.loaded = true;
await callInit();
2020-11-23 13:24:19 -05:00
};
exports.getPackages = async function () {
// Load list of installed NPM packages, flatten it to a list, and filter out only packages with names that
2020-11-23 13:24:19 -05:00
const dir = settings.root;
const data = await util.promisify(readInstalled)(dir);
2020-11-23 13:24:19 -05:00
const packages = {};
function flatten(deps) {
2020-11-23 13:24:19 -05:00
_.chain(deps).keys().each((name) => {
if (name.indexOf(exports.prefix) === 0) {
packages[name] = _.clone(deps[name]);
// Delete anything that creates loops so that the plugin
// list can be sent as JSON to the web client
delete packages[name].dependencies;
delete packages[name].parent;
}
// I don't think we need recursion
2020-11-23 13:24:19 -05:00
// if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies);
});
}
2020-11-23 13:24:19 -05:00
const tmp = {};
tmp[data.name] = data;
flatten(tmp[data.name].dependencies);
return packages;
2012-05-28 17:46:14 -07:00
};
async function loadPlugin(packages, plugin_name, plugins, parts) {
2020-11-23 13:24:19 -05:00
const plugin_path = path.resolve(packages[plugin_name].path, 'ep.json');
try {
2020-11-23 13:24:19 -05:00
const data = await fs.readFile(plugin_path);
try {
2020-11-23 13:24:19 -05:00
const plugin = JSON.parse(data);
plugin.package = packages[plugin_name];
plugins[plugin_name] = plugin;
2020-11-23 13:24:19 -05:00
_.each(plugin.parts, (part) => {
part.plugin = plugin_name;
2020-11-23 13:24:19 -05:00
part.full_name = `${plugin_name}/${part.name}`;
parts[part.full_name] = part;
});
} catch (ex) {
2020-11-23 13:24:19 -05:00
console.error(`Unable to parse plugin definition file ${plugin_path}: ${ex.toString()}`);
}
} catch (er) {
2020-11-23 13:24:19 -05:00
console.error(`Unable to load plugin definition file ${plugin_path}`);
}
2012-05-28 18:11:59 -07:00
}
2012-05-28 18:11:59 -07:00
function partsToParentChildList(parts) {
2020-11-23 13:24:19 -05:00
const res = [];
_.chain(parts).keys().forEach((name) => {
_.each(parts[name].post || [], (child_name) => {
res.push([name, child_name]);
});
2020-11-23 13:24:19 -05:00
_.each(parts[name].pre || [], (parent_name) => {
res.push([parent_name, name]);
});
if (!parts[name].pre && !parts[name].post) {
2020-11-23 13:24:19 -05:00
res.push([name, `:${name}`]); // Include apps with no dependency info
}
});
return res;
2012-05-28 18:11:59 -07:00
}
2012-03-17 13:36:42 +01:00
// Used only in Node, so no need for _
2012-05-28 18:11:59 -07:00
function sortParts(parts) {
return tsort(
2020-11-23 13:24:19 -05:00
partsToParentChildList(parts),
).filter(
2020-11-23 13:24:19 -05:00
(name) => parts[name] !== undefined,
).map(
2020-11-23 13:24:19 -05:00
(name) => parts[name],
);
2012-05-28 18:11:59 -07:00
}