'use strict'; var fs = require('fs'); var path = require('path'); var async = require('async'); var winston = require('winston'); var db = require('../database'); var file = require('../file'); var Data = module.exports; var basePath = path.join(__dirname, '../../'); function getPluginPaths(callback) { async.waterfall([ function (next) { db.getSortedSetRange('plugins:active', 0, -1, next); }, function (plugins, 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, next); }, ], callback); } Data.getPluginPaths = getPluginPaths; function loadPluginInfo(pluginPath, callback) { async.parallel({ package: function (next) { fs.readFile(path.join(pluginPath, 'package.json'), next); }, plugin: function (next) { fs.readFile(path.join(pluginPath, 'plugin.json'), next); }, }, function (err, results) { if (err) { return callback(err); } var pluginData; var packageData; var licenseData; try { pluginData = JSON.parse(results.plugin); packageData = JSON.parse(results.package); try { licenseData = require('spdx-license-list/licenses/' + packageData.license); pluginData.license = { name: licenseData.name, text: licenseData.licenseText, }; } catch (e) { // No license matched pluginData.license = null; } 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; } catch (err) { var pluginDir = path.basename(pluginPath); winston.error('[plugins/' + pluginDir + '] Error in plugin.json or package.json! ' + err.message); return callback(new Error('[[error:parse-error]]')); } callback(null, pluginData); }); } Data.loadPluginInfo = loadPluginInfo; function getAllPluginData(callback) { async.waterfall([ function (next) { getPluginPaths(next); }, function (pluginPaths, next) { async.map(pluginPaths, loadPluginInfo, next); }, ], callback); } Data.getActive = getAllPluginData; function getStaticDirectories(pluginData, callback) { var validMappedPath = /^[\w\-_]+$/; if (!pluginData.staticDirs) { return callback(); } var dirs = Object.keys(pluginData.staticDirs); if (!dirs.length) { return callback(); } var staticDirs = {}; async.each(dirs, function (route, next) { if (!validMappedPath.test(route)) { winston.warn('[plugins/' + pluginData.id + '] Invalid mapped path specified: ' + route + '. Path must adhere to: ' + validMappedPath.toString()); return next(); } var dirPath = path.join(pluginData.path, pluginData.staticDirs[route]); fs.stat(dirPath, function (err, stats) { if (err && err.code === 'ENOENT') { winston.warn('[plugins/' + pluginData.id + '] Mapped path \'' + route + ' => ' + dirPath + '\' not found.'); return next(); } if (err) { return next(err); } if (!stats.isDirectory()) { winston.warn('[plugins/' + pluginData.id + '] Mapped path \'' + route + ' => ' + dirPath + '\' is not a directory.'); return next(); } staticDirs[pluginData.id + '/' + route] = dirPath; next(); }); }, function (err) { if (err) { return callback(err); } winston.verbose('[plugins] found ' + Object.keys(staticDirs).length + ' static directories for ' + pluginData.id); callback(null, staticDirs); }); } Data.getStaticDirectories = getStaticDirectories; function getFiles(pluginData, type, callback) { if (!Array.isArray(pluginData[type]) || !pluginData[type].length) { return callback(); } winston.verbose('[plugins] Found ' + pluginData[type].length + ' ' + type + ' file(s) for plugin ' + pluginData.id); var files = pluginData[type].map(function (file) { return path.join(pluginData.id, file); }); callback(null, files); } Data.getFiles = getFiles; /** * With npm@3, dependencies can become flattened, and appear at the root level. * This method resolves these differences if it can. */ function resolveModulePath(basePath, modulePath, callback) { var isNodeModule = /node_modules/; var currentPath = path.join(basePath, modulePath); file.exists(currentPath, function (err, exists) { if (err) { return callback(err); } if (exists) { return callback(null, currentPath); } if (!isNodeModule.test(modulePath)) { winston.warn('[plugins] File not found: ' + currentPath + ' (Ignoring)'); return callback(); } var dirPath = path.dirname(basePath); if (dirPath === basePath) { winston.warn('[plugins] File not found: ' + currentPath + ' (Ignoring)'); return callback(); } resolveModulePath(dirPath, modulePath, callback); }); } function getScripts(pluginData, target, callback) { target = (target === 'client') ? 'scripts' : 'acpScripts'; var input = pluginData[target]; if (!Array.isArray(input) || !input.length) { return callback(); } var scripts = []; async.each(input, function (filePath, next) { resolveModulePath(pluginData.path, filePath, function (err, modulePath) { if (err) { return next(err); } if (modulePath) { scripts.push(modulePath); } next(); }); }, function (err) { if (err) { return callback(err); } if (scripts.length) { winston.verbose('[plugins] Found ' + scripts.length + ' js file(s) for plugin ' + pluginData.id); } callback(err, scripts); }); } Data.getScripts = getScripts; function getModules(pluginData, callback) { if (!pluginData.modules || !pluginData.hasOwnProperty('modules')) { return callback(); } var pluginModules = pluginData.modules; if (Array.isArray(pluginModules)) { var strip = parseInt(pluginData.modulesStrip, 10) || 0; pluginModules = pluginModules.reduce(function (prev, modulePath) { var key; if (strip) { key = modulePath.replace(new RegExp('.?(/[^/]+){' + strip + '}/'), ''); } else { key = path.basename(modulePath); } prev[key] = modulePath; return prev; }, {}); } var modules = {}; async.each(Object.keys(pluginModules), function (key, next) { resolveModulePath(pluginData.path, pluginModules[key], function (err, modulePath) { if (err) { return next(err); } if (modulePath) { modules[key] = path.relative(basePath, modulePath); } next(); }); }, function (err) { if (err) { return callback(err); } var len = Object.keys(modules).length; winston.verbose('[plugins] Found ' + len + ' AMD-style module(s) for plugin ' + pluginData.id); callback(null, modules); }); } Data.getModules = getModules; function getSoundpack(pluginData, callback) { var spack = pluginData.soundpack; if (!spack || !spack.dir || !spack.sounds) { return callback(); } var soundpack = {}; soundpack.name = spack.name || pluginData.name; soundpack.id = pluginData.id; soundpack.dir = path.join(pluginData.path, spack.dir); soundpack.sounds = {}; async.each(Object.keys(spack.sounds), function (name, next) { var soundFile = spack.sounds[name]; file.exists(path.join(soundpack.dir, soundFile), function (err, exists) { if (err) { return next(err); } if (!exists) { winston.warn('[plugins] Sound file not found: ' + soundFile); return next(); } soundpack.sounds[name] = soundFile; next(); }); }, function (err) { if (err) { return callback(err); } var len = Object.keys(soundpack.sounds).length; winston.verbose('[plugins] Found ' + len + ' sound file(s) for plugin ' + pluginData.id); callback(null, soundpack); }); } Data.getSoundpack = getSoundpack; function getLanguageData(pluginData, callback) { if (typeof pluginData.languages !== 'string') { return callback(); } var pathToFolder = path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.languages); file.walk(pathToFolder, function (err, paths) { if (err) { return callback(err); } var namespaces = []; var languages = []; paths.forEach(function (p) { var rel = path.relative(pathToFolder, p).split(/[/\\]/); var language = rel.shift().replace('_', '-').replace('@', '-x-'); var namespace = rel.join('/').replace(/\.json$/, ''); if (!language || !namespace) { return; } languages.push(language); namespaces.push(namespace); }); callback(null, { languages: languages, namespaces: namespaces, }); }); } Data.getLanguageData = getLanguageData;