feat: #7743 plugins

v1.18.x
Barış Soner Uşaklı 6 years ago
parent f5f5f76b12
commit c126cd8572

@ -213,3 +213,5 @@ events.output = function (numEvents) {
process.exit(0);
});
};
require('./promisify')(events);

@ -216,7 +216,7 @@ Data.getModules = async function getModules(pluginData) {
await Promise.all(Object.keys(pluginModules).map(key => processModule(key)));
const len = Object.keys(modules).length;
winston.info('[plugins] Found ' + len + ' AMD-style module(s) for plugin ' + pluginData.id);
winston.verbose('[plugins] Found ' + len + ' AMD-style module(s) for plugin ' + pluginData.id);
return modules;
};

@ -10,30 +10,31 @@ module.exports = function (Plugins) {
};
Plugins.internals = {
_register: function (data, callback) {
_register: function (data) {
Plugins.loadedHooks[data.hook] = Plugins.loadedHooks[data.hook] || [];
Plugins.loadedHooks[data.hook].push(data);
callback();
},
};
const hookTypeToMethod = {
filter: fireFilterHook,
action: fireActionHook,
static: fireStaticHook,
response: fireResponseHook,
};
/*
`data` is an object consisting of (* is required):
`data.hook`*, the name of the NodeBB hook
`data.method`*, the method called in that plugin (can be an array of functions)
`data.priority`, the relative priority of the method when it is eventually called (default: 10)
*/
Plugins.registerHook = function (id, data, callback) {
callback = callback || function () {};
if (!data.hook) {
winston.warn('[plugins/' + id + '] registerHook called with invalid data.hook', data);
return callback();
Plugins.registerHook = function (id, data) {
if (!data.hook || !data.method) {
winston.warn('[plugins/' + id + '] registerHook called with invalid data.hook/method', data);
return;
}
var method;
if (Plugins.deprecatedHooks[data.hook]) {
winston.warn('[plugins/' + id + '] Hook `' + data.hook + '` is deprecated, ' +
(Plugins.deprecatedHooks[data.hook] ?
@ -42,7 +43,6 @@ module.exports = function (Plugins) {
));
}
if (data.hook && data.method) {
data.id = id;
if (!data.priority) {
data.priority = 10;
@ -50,12 +50,12 @@ module.exports = function (Plugins) {
if (Array.isArray(data.method) && data.method.every(method => typeof method === 'function' || typeof method === 'string')) {
// Go go gadget recursion!
async.eachSeries(data.method, function (method, next) {
data.method.forEach(function (method) {
const singularData = Object.assign({}, data, { method: method });
Plugins.registerHook(id, singularData, next);
}, callback);
Plugins.registerHook(id, singularData);
});
} else if (typeof data.method === 'string' && data.method.length > 0) {
method = data.method.split('.').reduce(function (memo, prop) {
const method = data.method.split('.').reduce(function (memo, prop) {
if (memo && memo[prop]) {
return memo[prop];
}
@ -66,13 +66,11 @@ module.exports = function (Plugins) {
// Write the actual method reference to the hookObj
data.method = method;
Plugins.internals._register(data, callback);
Plugins.internals._register(data);
} else if (typeof data.method === 'function') {
Plugins.internals._register(data, callback);
Plugins.internals._register(data);
} else {
winston.warn('[plugins/' + id + '] Hook method mismatch: ' + data.hook + ' => ' + data.method);
return callback();
}
}
};
@ -83,52 +81,33 @@ module.exports = function (Plugins) {
});
};
Plugins.fireHook = function (hook, params, callback) {
callback = typeof callback === 'function' ? callback : function () {};
function done(err, result) {
if (err) {
return callback(err);
}
Plugins.fireHook = async function (hook, params) {
const hookList = Plugins.loadedHooks[hook];
const hookType = hook.split(':')[0];
if (hook !== 'action:plugins.firehook') {
Plugins.fireHook('action:plugins.firehook', { hook: hook, params: params });
}
if (result !== undefined) {
callback(null, result);
} else {
callback();
winston.verbose('[plugins/fireHook] ' + hook);
}
if (!hookTypeToMethod[hookType]) {
winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook);
return;
}
var hookList = Plugins.loadedHooks[hook];
var hookType = hook.split(':')[0];
const result = await hookTypeToMethod[hookType](hook, hookList, params);
if (hook !== 'action:plugins.firehook') {
winston.verbose('[plugins/fireHook] ' + hook);
Plugins.fireHook('action:plugins.firehook', { hook: hook, params: params });
}
switch (hookType) {
case 'filter':
fireFilterHook(hook, hookList, params, done);
break;
case 'action':
fireActionHook(hook, hookList, params, done);
break;
case 'static':
fireStaticHook(hook, hookList, params, done);
break;
case 'response':
fireResponseHook(hook, hookList, params, done);
break;
default:
winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook);
callback();
break;
if (result !== undefined) {
return result;
}
};
function fireFilterHook(hook, hookList, params, callback) {
async function fireFilterHook(hook, hookList, params) {
if (!Array.isArray(hookList) || !hookList.length) {
return callback(null, params);
return params;
}
async.reduce(hookList, params, function (params, hookObj, next) {
return await async.reduce(hookList, params, function (params, hookObj, next) {
if (typeof hookObj.method !== 'function') {
if (global.env === 'development') {
winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
@ -142,14 +121,14 @@ module.exports = function (Plugins) {
err => setImmediate(next, err)
);
}
}, callback);
});
}
function fireActionHook(hook, hookList, params, callback) {
async function fireActionHook(hook, hookList, params) {
if (!Array.isArray(hookList) || !hookList.length) {
return callback();
return;
}
async.each(hookList, function (hookObj, next) {
await async.each(hookList, function (hookObj, next) {
if (typeof hookObj.method !== 'function') {
if (global.env === 'development') {
winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
@ -159,14 +138,14 @@ module.exports = function (Plugins) {
hookObj.method(params);
next();
}, callback);
});
}
function fireStaticHook(hook, hookList, params, callback) {
async function fireStaticHook(hook, hookList, params) {
if (!Array.isArray(hookList) || !hookList.length) {
return callback();
return;
}
async.each(hookList, function (hookObj, next) {
await async.each(hookList, function (hookObj, next) {
if (typeof hookObj.method === 'function') {
let timedOut = false;
const timeoutId = setTimeout(function () {
@ -201,14 +180,14 @@ module.exports = function (Plugins) {
} else {
next();
}
}, callback);
});
}
function fireResponseHook(hook, hookList, params, callback) {
async function fireResponseHook(hook, hookList, params) {
if (!Array.isArray(hookList) || !hookList.length) {
return callback();
return;
}
async.eachSeries(hookList, function (hookObj, next) {
await async.eachSeries(hookList, function (hookObj, next) {
if (typeof hookObj.method !== 'function') {
if (global.env === 'development') {
winston.warn('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
@ -223,7 +202,7 @@ module.exports = function (Plugins) {
hookObj.method(params);
next();
}, callback);
});
}
Plugins.hasListeners = function (hook) {

@ -1,16 +1,19 @@
'use strict';
var fs = require('fs');
var path = require('path');
var async = require('async');
var winston = require('winston');
var semver = require('semver');
var nconf = require('nconf');
const fs = require('fs');
const path = require('path');
const async = require('async');
const winston = require('winston');
const semver = require('semver');
const nconf = require('nconf');
const util = require('util');
const readdirAsync = util.promisify(fs.readdir);
var app;
var middleware;
var Plugins = module.exports;
const Plugins = module.exports;
require('./install')(Plugins);
require('./load')(Plugins);
@ -65,10 +68,9 @@ Plugins.requireLibrary = function (pluginID, libraryPath) {
Plugins.libraryPaths.push(libraryPath);
};
Plugins.init = function (nbbApp, nbbMiddleware, callback) {
callback = callback || function () {};
Plugins.init = async function (nbbApp, nbbMiddleware) {
if (Plugins.initialized) {
return callback();
return;
}
if (nbbApp) {
@ -80,22 +82,15 @@ Plugins.init = function (nbbApp, nbbMiddleware, callback) {
winston.verbose('[plugins] Initializing plugins system');
}
Plugins.reload(function (err) {
if (err) {
winston.error('[plugins] NodeBB encountered a problem while loading plugins', err);
return callback(err);
}
await Plugins.reload();
if (global.env === 'development') {
winston.info('[plugins] Plugins OK');
}
Plugins.initialized = true;
callback();
});
};
Plugins.reload = function (callback) {
Plugins.reload = async function () {
// Resetting all local plugin data
Plugins.libraries = {};
Plugins.loadedHooks = {};
@ -109,12 +104,12 @@ Plugins.reload = function (callback) {
Plugins.libraryPaths.length = 0;
Plugins.loadedPlugins.length = 0;
async.waterfall([
Plugins.getPluginPaths,
function (paths, next) {
async.eachSeries(paths, Plugins.loadPlugin, next);
},
function (next) {
const paths = await Plugins.getPluginPaths();
for (const path of paths) {
/* eslint-disable no-await-in-loop */
await Plugins.loadPlugin(path);
}
// If some plugins are incompatible, throw the warning here
if (Plugins.versionWarning.length && nconf.get('isPrimary') === 'true') {
console.log('');
@ -126,98 +121,75 @@ Plugins.reload = function (callback) {
}
Object.keys(Plugins.loadedHooks).forEach(function (hook) {
var hooks = Plugins.loadedHooks[hook];
hooks.sort(function (a, b) {
return a.priority - b.priority;
Plugins.loadedHooks[hook].sort((a, b) => a.priority - b.priority);
});
});
next();
},
], callback);
};
Plugins.reloadRoutes = function (router, callback) {
Plugins.reloadRoutes = async function (router) {
var controllers = require('../controllers');
Plugins.fireHook('static:app.load', { app: app, router: router, middleware: middleware, controllers: controllers }, function (err) {
if (err) {
winston.error('[plugins] Encountered error while executing post-router plugins hooks', err);
return callback(err);
}
await Plugins.fireHook('static:app.load', { app: app, router: router, middleware: middleware, controllers: controllers });
winston.verbose('[plugins] All plugins reloaded and rerouted');
callback();
});
};
Plugins.get = function (id, callback) {
var url = (nconf.get('registry') || 'https://packages.nodebb.org') + '/api/v1/plugins/' + id;
function request(url, callback) {
require('request')(url, {
json: true,
}, function (err, res, body) {
if (res.statusCode === 404 || !body.payload) {
if (res.statusCode === 404 || !body) {
return callback(err, {});
}
Plugins.normalise([body.payload], function (err, normalised) {
normalised = normalised.filter(function (plugin) {
return plugin.id === id;
});
return callback(err, !err ? normalised[0] : undefined);
});
callback(err, body);
});
};
Plugins.list = function (matching, callback) {
if (arguments.length === 1 && typeof matching === 'function') {
callback = matching;
matching = true;
}
var version = require(path.join(nconf.get('base_dir'), 'package.json')).version;
var url = (nconf.get('registry') || 'https://packages.nodebb.org') + '/api/v1/plugins' + (matching !== false ? '?version=' + version : '');
const requestAsync = util.promisify(request);
require('request')(url, {
json: true,
}, function (err, res, body) {
if (err || (res && res.statusCode !== 200)) {
winston.error('Error loading ' + url, err || body);
return Plugins.normalise([], callback);
}
Plugins.get = async function (id) {
const url = (nconf.get('registry') || 'https://packages.nodebb.org') + '/api/v1/plugins/' + id;
const body = await requestAsync(url);
Plugins.normalise(body, callback);
});
let normalised = await Plugins.normalise([body ? body.payload : {}]);
normalised = normalised.filter(plugin => plugin.id === id);
return normalised.length ? normalised[0] : undefined;
};
Plugins.normalise = function (apiReturn, callback) {
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
var pluginMap = {};
var dependencies = require(path.join(nconf.get('base_dir'), 'package.json')).dependencies;
apiReturn = Array.isArray(apiReturn) ? apiReturn : [];
for (var i = 0; i < apiReturn.length; i += 1) {
apiReturn[i].id = apiReturn[i].name;
apiReturn[i].installed = false;
apiReturn[i].active = false;
apiReturn[i].url = apiReturn[i].url || (apiReturn[i].repository ? apiReturn[i].repository.url : '');
pluginMap[apiReturn[i].name] = apiReturn[i];
Plugins.list = async function (matching) {
if (matching === undefined) {
matching = true;
}
Plugins.showInstalled(function (err, installedPlugins) {
if (err) {
return callback(err);
const version = require(path.join(nconf.get('base_dir'), 'package.json')).version;
const url = (nconf.get('registry') || 'https://packages.nodebb.org') + '/api/v1/plugins' + (matching !== false ? '?version=' + version : '');
try {
const body = await requestAsync(url);
return await Plugins.normalise(body);
} catch (err) {
winston.error('Error loading ' + url, err);
return await Plugins.normalise([]);
}
};
installedPlugins = installedPlugins.filter(function (plugin) {
return plugin && !plugin.system;
Plugins.normalise = async function (apiReturn) {
const themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
const pluginMap = {};
const dependencies = require(path.join(nconf.get('base_dir'), 'package.json')).dependencies;
apiReturn = Array.isArray(apiReturn) ? apiReturn : [];
apiReturn.forEach(function (packageData) {
packageData.id = packageData.name;
packageData.installed = false;
packageData.active = false;
packageData.url = packageData.url || (packageData.repository ? packageData.repository.url : '');
pluginMap[packageData.name] = packageData;
});
async.each(installedPlugins, function (plugin, next) {
let installedPlugins = await Plugins.showInstalled();
installedPlugins = installedPlugins.filter(plugin => plugin && !plugin.system);
installedPlugins.forEach(function (plugin) {
// If it errored out because a package.json or plugin.json couldn't be read, no need to do this stuff
if (plugin.error) {
pluginMap[plugin.id] = pluginMap[plugin.id] || {};
pluginMap[plugin.id].installed = true;
pluginMap[plugin.id].error = true;
return next();
return;
}
pluginMap[plugin.id] = pluginMap[plugin.id] || {};
@ -240,13 +212,9 @@ Plugins.normalise = function (apiReturn, callback) {
pluginMap[plugin.id].latest = pluginMap[plugin.id].latest || plugin.version;
}
pluginMap[plugin.id].outdated = semver.gt(pluginMap[plugin.id].latest, pluginMap[plugin.id].version);
next();
}, function (err) {
if (err) {
return callback(err);
}
});
var pluginArray = [];
const pluginArray = [];
for (var key in pluginMap) {
if (pluginMap.hasOwnProperty(key)) {
@ -263,24 +231,39 @@ Plugins.normalise = function (apiReturn, callback) {
return 0;
});
callback(null, pluginArray);
});
});
return pluginArray;
};
Plugins.nodeModulesPath = path.join(__dirname, '../../node_modules');
Plugins.showInstalled = function (callback) {
var pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/;
Plugins.showInstalled = async function () {
const dirs = await readdirAsync(Plugins.nodeModulesPath);
async.waterfall([
function (next) {
fs.readdir(Plugins.nodeModulesPath, next);
},
function (dirs, next) {
var pluginPaths = [];
let pluginPaths = await findNodeBBModules(dirs);
pluginPaths = pluginPaths.map(dir => path.join(Plugins.nodeModulesPath, dir));
async function load(file) {
try {
const pluginData = await Plugins.loadPluginInfo(file);
const isActive = await Plugins.isActive(pluginData.name);
delete pluginData.hooks;
delete pluginData.library;
pluginData.active = isActive;
pluginData.installed = true;
pluginData.error = false;
return pluginData;
} catch (err) {
winston.error(err);
}
}
const plugins = await Promise.all(pluginPaths.map(file => load(file)));
return plugins.filter(Boolean);
};
async.each(dirs, function (dirname, next) {
async function findNodeBBModules(dirs) {
const pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/;
const pluginPaths = [];
await async.each(dirs, function (dirname, next) {
var dirPath = path.join(Plugins.nodeModulesPath, dirname);
async.waterfall([
@ -326,49 +309,8 @@ Plugins.showInstalled = function (callback) {
}, cb);
},
], next);
}, function (err) {
next(err, pluginPaths);
});
},
function (dirs, next) {
dirs = dirs.map(function (dir) {
return path.join(Plugins.nodeModulesPath, dir);
});
var plugins = [];
async.each(dirs, function (file, next) {
async.waterfall([
function (next) {
Plugins.loadPluginInfo(file, next);
},
function (pluginData, next) {
Plugins.isActive(pluginData.name, function (err, active) {
if (err) {
return next(new Error('no-active-state'));
}
delete pluginData.hooks;
delete pluginData.library;
pluginData.active = active;
pluginData.installed = true;
pluginData.error = false;
next(null, pluginData);
});
},
], function (err, pluginData) {
if (err) {
return next(); // Silently fail
return pluginPaths;
}
plugins.push(pluginData);
next();
});
}, function (err) {
next(err, plugins);
});
},
], callback);
};
Plugins.async = require('../promisify')(Plugins);

@ -1,21 +1,23 @@
'use strict';
var winston = require('winston');
var async = require('async');
var path = require('path');
var fs = require('fs');
var nconf = require('nconf');
var os = require('os');
var cproc = require('child_process');
var db = require('../database');
var meta = require('../meta');
var pubsub = require('../pubsub');
var events = require('../events');
var packageManager = nconf.get('package_manager') === 'yarn' ? 'yarn' : 'npm';
var packageManagerExecutable = packageManager;
var packageManagerCommands = {
const winston = require('winston');
const path = require('path');
const fs = require('fs');
const nconf = require('nconf');
const os = require('os');
const cproc = require('child_process');
const util = require('util');
const db = require('../database');
const meta = require('../meta');
const pubsub = require('../pubsub');
const events = require('../events');
const statAsync = util.promisify(fs.stat);
const packageManager = nconf.get('package_manager') === 'yarn' ? 'yarn' : 'npm';
let packageManagerExecutable = packageManager;
const packageManagerCommands = {
yarn: {
install: 'add',
uninstall: 'remove',
@ -45,83 +47,43 @@ module.exports = function (Plugins) {
});
}
Plugins.toggleActive = function (id, callback) {
callback = callback || function () {};
var isActive;
async.waterfall([
function (next) {
Plugins.isActive(id, next);
},
function (_isActive, next) {
isActive = _isActive;
Plugins.toggleActive = async function (id) {
const isActive = await Plugins.isActive(id);
if (isActive) {
db.sortedSetRemove('plugins:active', id, next);
await db.sortedSetRemove('plugins:active', id);
} else {
db.sortedSetCard('plugins:active', function (err, count) {
if (err) {
return next(err);
}
db.sortedSetAdd('plugins:active', count, id, next);
});
const count = await db.sortedSetCard('plugins:active');
await db.sortedSetAdd('plugins:active', count, id);
}
},
function (next) {
meta.reloadRequired = true;
Plugins.fireHook(isActive ? 'action:plugin.deactivate' : 'action:plugin.activate', { id: id });
setImmediate(next);
},
function (next) {
events.log({
await events.log({
type: 'plugin-' + (isActive ? 'deactivate' : 'activate'),
text: id,
}, next);
},
], function (err) {
if (err) {
winston.warn('[plugins] Could not toggle active state on plugin \'' + id + '\'');
return callback(err);
}
callback(null, { id: id, active: !isActive });
});
return { id: id, active: !isActive };
};
Plugins.toggleInstall = function (id, version, callback) {
Plugins.toggleInstall = async function (id, version) {
pubsub.publish('plugins:toggleInstall', { hostname: os.hostname(), id: id, version: version });
toggleInstall(id, version, callback);
return await toggleInstall(id, version);
};
function toggleInstall(id, version, callback) {
var installed;
var type;
async.waterfall([
function (next) {
Plugins.isInstalled(id, next);
},
function (_installed, next) {
installed = _installed;
type = installed ? 'uninstall' : 'install';
Plugins.isActive(id, next);
},
function (active, next) {
const runPackageManagerCommandAsync = util.promisify(runPackageManagerCommand);
async function toggleInstall(id, version) {
const [installed, active] = await Promise.all([
Plugins.isInstalled(id),
Plugins.isActive(id),
]);
const type = installed ? 'uninstall' : 'install';
if (active) {
Plugins.toggleActive(id, function (err) {
next(err);
});
return;
await Plugins.toggleActive(id);
}
setImmediate(next);
},
function (next) {
runPackageManagerCommand(type, id, version || 'latest', next);
},
function (next) {
Plugins.get(id, next);
},
function (pluginData, next) {
await runPackageManagerCommandAsync(type, id, version || 'latest');
const pluginData = await Plugins.get(id);
Plugins.fireHook('action:plugin.' + type, { id: id, version: version });
setImmediate(next, null, pluginData);
},
], callback);
return pluginData;
}
function runPackageManagerCommand(command, pkgName, version, callback) {
@ -139,37 +101,34 @@ module.exports = function (Plugins) {
});
}
Plugins.upgrade = function (id, version, callback) {
Plugins.upgrade = async function (id, version) {
pubsub.publish('plugins:upgrade', { hostname: os.hostname(), id: id, version: version });
upgrade(id, version, callback);
return await upgrade(id, version);
};
function upgrade(id, version, callback) {
async.waterfall([
async.apply(runPackageManagerCommand, 'install', id, version || 'latest'),
function (next) {
Plugins.isActive(id, next);
},
function (isActive, next) {
async function upgrade(id, version) {
await runPackageManagerCommandAsync('install', id, version || 'latest');
const isActive = await Plugins.isActive(id);
meta.reloadRequired = isActive;
next(null, isActive);
},
], callback);
return isActive;
}
Plugins.isInstalled = function (id, callback) {
var pluginDir = path.join(__dirname, '../../node_modules', id);
fs.stat(pluginDir, function (err, stats) {
callback(null, err ? false : stats.isDirectory());
});
Plugins.isInstalled = async function (id) {
const pluginDir = path.join(__dirname, '../../node_modules', id);
try {
const stats = await statAsync(pluginDir);
return stats.isDirectory();
} catch (err) {
return false;
}
};
Plugins.isActive = function (id, callback) {
db.isSortedSetMember('plugins:active', id, callback);
Plugins.isActive = async function (id) {
return await db.isSortedSetMember('plugins:active', id);
};
Plugins.getActive = function (callback) {
db.getSortedSetRange('plugins:active', 0, -1, callback);
Plugins.getActive = async function () {
return await db.getSortedSetRange('plugins:active', 0, -1);
};
};

@ -1,21 +1,21 @@
'use strict';
var path = require('path');
var semver = require('semver');
var async = require('async');
var winston = require('winston');
var nconf = require('nconf');
var _ = require('lodash');
const path = require('path');
const semver = require('semver');
const async = require('async');
const winston = require('winston');
const nconf = require('nconf');
const _ = require('lodash');
var meta = require('../meta');
const meta = require('../meta');
module.exports = function (Plugins) {
function registerPluginAssets(pluginData, fields, callback) {
async function registerPluginAssets(pluginData, fields) {
function add(dest, arr) {
dest.push.apply(dest, arr || []);
}
var handlers = {
const handlers = {
staticDirs: function (next) {
Plugins.data.getStaticDirectories(pluginData, next);
},
@ -45,20 +45,16 @@ module.exports = function (Plugins) {
},
};
var methods;
var methods = {};
if (Array.isArray(fields)) {
methods = fields.reduce(function (prev, field) {
prev[field] = handlers[field];
return prev;
}, {});
fields.forEach(function (field) {
methods[field] = handlers[field];
});
} else {
methods = handlers;
}
async.parallel(methods, function (err, results) {
if (err) {
return callback(err);
}
const results = await async.parallel(methods);
Object.assign(Plugins.staticDirs, results.staticDirs || {});
add(Plugins.cssFiles, results.cssFiles);
@ -75,13 +71,10 @@ module.exports = function (Plugins) {
Plugins.languageData.namespaces = _.union(Plugins.languageData.namespaces, results.languageData.namespaces);
}
Plugins.pluginsData[pluginData.id] = pluginData;
callback();
});
}
Plugins.prepareForBuild = function (targets, callback) {
var map = {
Plugins.prepareForBuild = async function (targets) {
const map = {
'plugin static dirs': ['staticDirs'],
'requirejs modules': ['modules'],
'client js bundle': ['clientScripts'],
@ -92,7 +85,7 @@ module.exports = function (Plugins) {
languages: ['languageData'],
};
var fields = _.uniq(_.flatMap(targets, target => map[target] || []));
const fields = _.uniq(_.flatMap(targets, target => map[target] || []));
// clear old data before build
fields.forEach((field) => {
@ -116,43 +109,34 @@ module.exports = function (Plugins) {
});
winston.verbose('[plugins] loading the following fields from plugin data: ' + fields.join(', '));
async.waterfall([
Plugins.data.getActive,
function (plugins, next) {
async.each(plugins, function (pluginData, next) {
registerPluginAssets(pluginData, fields, next);
}, next);
},
], callback);
const plugins = await Plugins.data.getActive();
await Promise.all(plugins.map(p => registerPluginAssets(p, fields)));
};
var themeNamePattern = /(@.*?\/)?nodebb-theme-.*$/;
const themeNamePattern = /(@.*?\/)?nodebb-theme-.*$/;
Plugins.loadPlugin = function (pluginPath, callback) {
Plugins.data.loadPluginInfo(pluginPath, function (err, pluginData) {
if (err) {
Plugins.loadPlugin = async function (pluginPath) {
let pluginData;
try {
pluginData = await Plugins.data.loadPluginInfo(pluginPath);
} catch (err) {
if (err.message === '[[error:parse-error]]') {
return callback();
return;
}
return callback(themeNamePattern.test(pluginPath) ? null : err);
if (!themeNamePattern.test(pluginPath)) {
throw err;
}
return;
}
checkVersion(pluginData);
async.parallel([
function (next) {
registerHooks(pluginData, next);
},
function (next) {
registerPluginAssets(pluginData, ['soundpack'], next);
},
], function (err) {
if (err) {
try {
registerHooks(pluginData);
await registerPluginAssets(pluginData, ['soundpack']);
} catch (err) {
winston.error(err.stack);
winston.verbose('[plugins] Could not load plugin : ' + pluginData.id);
return callback();
return;
}
if (!pluginData.private) {
@ -163,9 +147,6 @@ module.exports = function (Plugins) {
}
winston.verbose('[plugins] Loaded plugin: ' + pluginData.id);
callback();
});
});
};
function checkVersion(pluginData) {
@ -184,28 +165,24 @@ module.exports = function (Plugins) {
}
}
function registerHooks(pluginData, callback) {
function registerHooks(pluginData) {
if (!pluginData.library) {
return callback();
return;
}
var libraryPath = path.join(pluginData.path, pluginData.library);
const libraryPath = path.join(pluginData.path, pluginData.library);
try {
if (!Plugins.libraries[pluginData.id]) {
Plugins.requireLibrary(pluginData.id, libraryPath);
}
if (Array.isArray(pluginData.hooks) && pluginData.hooks.length > 0) {
async.each(pluginData.hooks, function (hook, next) {
Plugins.registerHook(pluginData.id, hook, next);
}, callback);
} else {
callback();
if (Array.isArray(pluginData.hooks)) {
pluginData.hooks.forEach(hook => Plugins.registerHook(pluginData.id, hook));
}
} catch (err) {
winston.warn('[plugins] Unable to parse library for: ' + pluginData.id);
callback(err);
throw err;
}
}
};

Loading…
Cancel
Save