|
|
|
@ -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);
|
|
|
|
|