Files
NodeBB/src/plugins/load.js

317 lines
9.2 KiB
JavaScript
Raw Normal View History

2014-12-26 18:54:20 -05:00
'use strict';
2016-11-19 14:24:37 -05:00
var db = require('../database');
2016-10-14 21:48:38 +03:00
var fs = require('fs');
var path = require('path');
var semver = require('semver');
var async = require('async');
var winston = require('winston');
var nconf = require('nconf');
var _ = require('underscore');
var file = require('../file');
var meta = require('../meta');
2014-12-26 18:54:20 -05:00
module.exports = function (Plugins) {
2016-11-19 14:24:37 -05:00
Plugins.getPluginPaths = function (callback) {
async.waterfall([
function (next) {
db.getSortedSetRange('plugins:active', 0, -1, next);
},
function (plugins, next) {
if (!Array.isArray(plugins)) {
return next();
}
plugins = plugins.filter(function (plugin) {
return plugin && typeof plugin === 'string';
}).map(function (plugin) {
return path.join(__dirname, '../../node_modules/', plugin);
});
async.filter(plugins, file.exists, function (plugins) {
next(null, plugins);
});
},
], callback);
};
Plugins.prepareForBuild = function (callback) {
2016-12-21 15:18:43 +03:00
Plugins.cssFiles.length = 0;
Plugins.lessFiles.length = 0;
Plugins.clientScripts.length = 0;
Plugins.acpScripts.length = 0;
2016-12-23 15:58:40 +03:00
2016-11-19 14:24:37 -05:00
async.waterfall([
async.apply(Plugins.getPluginPaths),
2016-11-20 13:33:35 +03:00
function (paths, next) {
async.map(paths, function (path, next) {
2016-11-19 14:24:37 -05:00
Plugins.loadPluginInfo(path, next);
}, next);
},
2016-11-20 13:33:35 +03:00
function (plugins, next) {
async.each(plugins, function (pluginData, next) {
2016-11-19 14:24:37 -05:00
async.parallel([
async.apply(mapFiles, pluginData, 'css', 'cssFiles'),
async.apply(mapFiles, pluginData, 'less', 'lessFiles'),
async.apply(mapClientSideScripts, pluginData),
async.apply(mapClientModules, pluginData),
async.apply(mapStaticDirectories, pluginData, pluginData.path),
2016-11-19 14:24:37 -05:00
], next);
}, next);
2017-02-17 19:31:21 -07:00
},
2016-11-19 14:24:37 -05:00
], callback);
};
2014-12-26 18:54:20 -05:00
Plugins.loadPlugin = function (pluginPath, callback) {
Plugins.loadPluginInfo(pluginPath, function (err, pluginData) {
2014-12-26 18:54:20 -05:00
if (err) {
2015-03-11 18:04:27 -04:00
if (err.message === '[[error:parse-error]]') {
return callback();
}
2014-12-26 18:54:20 -05:00
return callback(pluginPath.match('nodebb-theme') ? null : err);
}
checkVersion(pluginData);
2014-12-26 18:54:20 -05:00
async.parallel([
function (next) {
2014-12-26 18:54:20 -05:00
registerHooks(pluginData, pluginPath, next);
},
function (next) {
2014-12-26 18:54:20 -05:00
mapStaticDirectories(pluginData, pluginPath, next);
},
function (next) {
2014-12-26 19:02:50 -05:00
mapFiles(pluginData, 'css', 'cssFiles', next);
2014-12-26 18:54:20 -05:00
},
function (next) {
2014-12-26 19:02:50 -05:00
mapFiles(pluginData, 'less', 'lessFiles', next);
2014-12-26 18:54:20 -05:00
},
function (next) {
2014-12-26 18:54:20 -05:00
mapClientSideScripts(pluginData, next);
},
function (next) {
mapClientModules(pluginData, next);
},
], function (err) {
2014-12-26 18:54:20 -05:00
if (err) {
winston.verbose('[plugins] Could not load plugin : ' + pluginData.id);
return callback(err);
}
winston.verbose('[plugins] Loaded plugin: ' + pluginData.id);
callback();
});
});
};
function checkVersion(pluginData) {
function add() {
if (Plugins.versionWarning.indexOf(pluginData.id) === -1) {
Plugins.versionWarning.push(pluginData.id);
}
2015-03-07 00:59:03 -05:00
}
if (pluginData.nbbpm && pluginData.nbbpm.compatibility && semver.validRange(pluginData.nbbpm.compatibility)) {
if (!semver.satisfies(nconf.get('version'), pluginData.nbbpm.compatibility)) {
add();
2015-03-07 00:59:03 -05:00
}
} else {
add();
2015-03-07 00:59:03 -05:00
}
}
2014-12-26 18:54:20 -05:00
function registerHooks(pluginData, pluginPath, callback) {
if (!pluginData.library) {
return callback();
2014-12-26 18:54:20 -05:00
}
var libraryPath = path.join(pluginPath, pluginData.library);
2015-01-12 23:24:19 -05:00
try {
2014-12-26 18:54:20 -05:00
if (!Plugins.libraries[pluginData.id]) {
2015-01-06 23:29:48 -05:00
Plugins.requireLibrary(pluginData.id, libraryPath);
2014-12-26 18:54:20 -05:00
}
if (Array.isArray(pluginData.hooks) && pluginData.hooks.length > 0) {
async.each(pluginData.hooks, function (hook, next) {
2014-12-26 18:54:20 -05:00
Plugins.registerHook(pluginData.id, hook, next);
}, callback);
} else {
callback();
}
2015-01-12 23:24:19 -05:00
} catch(err) {
winston.error(err.stack);
winston.warn('[plugins] Unable to parse library for: ' + pluginData.id);
callback();
2015-01-12 23:24:19 -05:00
}
2014-12-26 18:54:20 -05:00
}
function mapStaticDirectories(pluginData, pluginPath, callback) {
function mapStaticDirs(mappedPath, callback) {
if (Plugins.staticDirs[mappedPath]) {
winston.warn('[plugins/' + pluginData.id + '] Mapped path (' + mappedPath + ') already specified!');
callback();
} else if (!validMappedPath.test(mappedPath)) {
winston.warn('[plugins/' + pluginData.id + '] Invalid mapped path specified: ' + mappedPath + '. Path must adhere to: ' + validMappedPath.toString());
callback();
} else {
var realPath = pluginData.staticDirs[mappedPath];
var staticDir = path.join(pluginPath, realPath);
file.exists(staticDir, function (exists) {
2014-12-26 18:54:20 -05:00
if (exists) {
Plugins.staticDirs[pluginData.id + '/' + mappedPath] = staticDir;
} else {
winston.warn('[plugins/' + pluginData.id + '] Mapped path \'' + mappedPath + ' => ' + staticDir + '\' not found.');
}
callback();
});
}
}
var validMappedPath = /^[\w\-_]+$/;
pluginData.staticDirs = pluginData.staticDirs || {};
var dirs = Object.keys(pluginData.staticDirs);
async.each(dirs, mapStaticDirs, callback);
}
2014-12-26 19:02:50 -05:00
function mapFiles(pluginData, type, globalArray, callback) {
if (Array.isArray(pluginData[type])) {
2014-12-26 18:54:20 -05:00
if (global.env === 'development') {
2014-12-26 19:02:50 -05:00
winston.verbose('[plugins] Found ' + pluginData[type].length + ' ' + type + ' file(s) for plugin ' + pluginData.id);
2014-12-26 18:54:20 -05:00
}
Plugins[globalArray] = Plugins[globalArray].concat(pluginData[type].map(function (file) {
2014-12-26 18:54:20 -05:00
return path.join(pluginData.id, file);
}));
}
callback();
}
function mapClientSideScripts(pluginData, callback) {
2016-12-27 21:20:05 +03:00
function mapScripts(scripts, param) {
2016-12-23 15:58:40 +03:00
if (Array.isArray(scripts) && scripts.length) {
if (global.env === 'development') {
winston.verbose('[plugins] Found ' + scripts.length + ' js file(s) for plugin ' + pluginData.id);
}
2014-12-26 18:54:20 -05:00
2016-12-27 21:20:05 +03:00
Plugins[param] = Plugins[param].concat(scripts.map(function (file) {
2016-12-23 15:58:40 +03:00
return resolveModulePath(path.join(__dirname, '../../node_modules/', pluginData.id, file), file);
})).filter(Boolean);
2016-01-18 15:17:21 -05:00
}
}
2016-12-27 21:20:05 +03:00
mapScripts(pluginData.scripts, 'clientScripts');
mapScripts(pluginData.acpScripts, 'acpScripts');
2016-01-18 15:17:21 -05:00
2014-12-26 18:54:20 -05:00
callback();
}
function mapClientModules(pluginData, callback) {
if (!pluginData.hasOwnProperty('modules')) {
return callback();
}
var modules = {};
if (Array.isArray(pluginData.modules)) {
if (global.env === 'development') {
winston.verbose('[plugins] Found ' + pluginData.modules.length + ' AMD-style module(s) for plugin ' + pluginData.id);
}
var strip = pluginData.hasOwnProperty('modulesStrip') ? parseInt(pluginData.modulesStrip, 10) : 0;
2016-04-27 14:14:22 -04:00
pluginData.modules.forEach(function (file) {
2016-04-27 14:14:22 -04:00
if (strip) {
modules[file.replace(new RegExp('\.?(\/[^\/]+){' + strip + '}\/'), '')] = path.join('./node_modules/', pluginData.id, file);
} else {
modules[path.basename(file)] = path.join('./node_modules/', pluginData.id, file);
}
});
meta.js.scripts.modules = _.extend(meta.js.scripts.modules, modules);
} else {
var keys = Object.keys(pluginData.modules);
if (global.env === 'development') {
winston.verbose('[plugins] Found ' + keys.length + ' AMD-style module(s) for plugin ' + pluginData.id);
}
for (var name in pluginData.modules) {
if (pluginData.modules.hasOwnProperty(name)) {
modules[name] = path.join('./node_modules/', pluginData.id, pluginData.modules[name]);
}
}
meta.js.scripts.modules = _.extend(meta.js.scripts.modules, modules);
}
callback();
}
2014-12-26 18:54:20 -05:00
function resolveModulePath(fullPath, relPath) {
/**
* With npm@3, dependencies can become flattened, and appear at the root level.
* This method resolves these differences if it can.
*/
var matches = fullPath.match(/node_modules/g);
var atRootLevel = !matches || matches.length === 1;
try {
fs.statSync(fullPath);
winston.verbose('[plugins/load] File found: ' + fullPath);
return fullPath;
} catch (e) {
// File not visible to the calling process, ascend to root level if possible and try again
if (!atRootLevel && relPath) {
winston.verbose('[plugins/load] File not found: ' + fullPath + ' (Ascending)');
return resolveModulePath(path.join(__dirname, '../..', relPath));
} else {
// Already at root level, file was simply not found
winston.warn('[plugins/load] File not found: ' + fullPath + ' (Ignoring)');
return null;
}
}
}
Plugins.loadPluginInfo = function (pluginPath, callback) {
2015-01-06 23:29:48 -05:00
async.parallel({
package: function (next) {
2015-01-06 23:29:48 -05:00
fs.readFile(path.join(pluginPath, 'package.json'), next);
},
plugin: function (next) {
2015-01-06 23:29:48 -05:00
fs.readFile(path.join(pluginPath, 'plugin.json'), next);
2017-02-17 19:31:21 -07:00
},
}, function (err, results) {
2015-01-06 23:29:48 -05:00
if (err) {
return callback(err);
}
var pluginData;
var packageData;
2015-01-06 23:29:48 -05:00
try {
pluginData = JSON.parse(results.plugin);
packageData = JSON.parse(results.package);
2015-01-06 23:29:48 -05:00
2015-01-12 23:10:36 -05:00
pluginData.id = packageData.name;
pluginData.name = packageData.name;
pluginData.description = packageData.description;
pluginData.version = packageData.version;
pluginData.repository = packageData.repository;
pluginData.nbbpm = packageData.nbbpm;
pluginData.path = pluginPath;
2015-01-06 23:29:48 -05:00
} catch(err) {
var pluginDir = pluginPath.split(path.sep);
2016-10-13 11:42:29 +02:00
pluginDir = pluginDir[pluginDir.length - 1];
2015-01-06 23:29:48 -05:00
2015-03-11 18:04:27 -04:00
winston.error('[plugins/' + pluginDir + '] Error in plugin.json or package.json! ' + err.message);
2015-01-06 23:29:48 -05:00
return callback(new Error('[[error:parse-error]]'));
2015-01-06 23:29:48 -05:00
}
callback(null, pluginData);
2015-01-06 23:29:48 -05:00
});
};
};