@ -1,16 +1,19 @@
'use strict' ;
'use strict' ;
var fs = require ( 'fs' ) ;
const fs = require ( 'fs' ) ;
var path = require ( 'path' ) ;
const path = require ( 'path' ) ;
var async = require ( 'async' ) ;
const async = require ( 'async' ) ;
var winston = require ( 'winston' ) ;
const winston = require ( 'winston' ) ;
var semver = require ( 'semver' ) ;
const semver = require ( 'semver' ) ;
var nconf = require ( 'nconf' ) ;
const nconf = require ( 'nconf' ) ;
const util = require ( 'util' ) ;
const readdirAsync = util . promisify ( fs . readdir ) ;
var app ;
var app ;
var middleware ;
var middleware ;
var Plugins = module . exports ;
const Plugins = module . exports ;
require ( './install' ) ( Plugins ) ;
require ( './install' ) ( Plugins ) ;
require ( './load' ) ( Plugins ) ;
require ( './load' ) ( Plugins ) ;
@ -65,10 +68,9 @@ Plugins.requireLibrary = function (pluginID, libraryPath) {
Plugins . libraryPaths . push ( libraryPath ) ;
Plugins . libraryPaths . push ( libraryPath ) ;
} ;
} ;
Plugins . init = function ( nbbApp , nbbMiddleware , callback ) {
Plugins . init = async function ( nbbApp , nbbMiddleware ) {
callback = callback || function ( ) { } ;
if ( Plugins . initialized ) {
if ( Plugins . initialized ) {
return callback ( ) ;
return ;
}
}
if ( nbbApp ) {
if ( nbbApp ) {
@ -80,22 +82,15 @@ Plugins.init = function (nbbApp, nbbMiddleware, callback) {
winston . verbose ( '[plugins] Initializing plugins system' ) ;
winston . verbose ( '[plugins] Initializing plugins system' ) ;
}
}
Plugins . reload ( function ( err ) {
await Plugins . reload ( ) ;
if ( err ) {
if ( global . env === 'development' ) {
winston . error ( '[plugins] NodeBB encountered a problem while loading plugins' , err ) ;
winston . info ( '[plugins] Plugins OK' ) ;
return callback ( err ) ;
}
}
if ( global . env === 'development' ) {
winston . info ( '[plugins] Plugins OK' ) ;
}
Plugins . initialized = true ;
Plugins . initialized = true ;
callback ( ) ;
} ) ;
} ;
} ;
Plugins . reload = function ( callback ) {
Plugins . reload = async function ( ) {
// Resetting all local plugin data
// Resetting all local plugin data
Plugins . libraries = { } ;
Plugins . libraries = { } ;
Plugins . loadedHooks = { } ;
Plugins . loadedHooks = { } ;
@ -109,266 +104,213 @@ Plugins.reload = function (callback) {
Plugins . libraryPaths . length = 0 ;
Plugins . libraryPaths . length = 0 ;
Plugins . loadedPlugins . length = 0 ;
Plugins . loadedPlugins . length = 0 ;
async . waterfall ( [
const paths = await Plugins . getPluginPaths ( ) ;
Plugins . getPluginPaths ,
for ( const path of paths ) {
function ( paths , next ) {
/* eslint-disable no-await-in-loop */
async . eachSeries ( paths , Plugins . loadPlugin , next ) ;
await Plugins . loadPlugin ( path ) ;
} ,
}
function ( next ) {
// If some plugins are incompatible, throw the warning here
if ( Plugins . versionWarning . length && nconf . get ( 'isPrimary' ) === 'true' ) {
console . log ( '' ) ;
winston . warn ( '[plugins/load] The following plugins may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing. In the event of an unresponsive NodeBB caused by this plugin, run `./nodebb reset -p PLUGINNAME` to disable it.' ) ;
for ( var x = 0 , numPlugins = Plugins . versionWarning . length ; x < numPlugins ; x += 1 ) {
console . log ( ' * ' . yellow + Plugins . versionWarning [ x ] ) ;
}
console . log ( '' ) ;
}
Object . keys ( Plugins . loadedHooks ) . forEach ( function ( hook ) {
var hooks = Plugins . loadedHooks [ hook ] ;
hooks . sort ( function ( a , b ) {
return a . priority - b . priority ;
} ) ;
} ) ;
next ( ) ;
} ,
] , callback ) ;
} ;
Plugins . reloadRoutes = function ( router , callback ) {
// If some plugins are incompatible, throw the warning here
var controllers = require ( '../controllers' ) ;
if ( Plugins . versionWarning . length && nconf . get ( 'isPrimary' ) === 'true' ) {
Plugins . fireHook ( 'static:app.load' , { app : app , router : router , middleware : middleware , controllers : controllers } , function ( err ) {
console . log ( '' ) ;
if ( err ) {
winston . warn ( '[plugins/load] The following plugins may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing. In the event of an unresponsive NodeBB caused by this plugin, run `./nodebb reset -p PLUGINNAME` to disable it.' ) ;
winston . error ( '[plugins] Encountered error while executing post-router plugins hooks' , err ) ;
for ( var x = 0 , numPlugins = Plugins . versionWarning . length ; x < numPlugins ; x += 1 ) {
return callback ( err ) ;
console . log ( ' * ' . yellow + Plugins . versionWarning [ x ] ) ;
}
}
console . log ( '' ) ;
}
winston . verbose ( '[plugins] All plugins reloaded and rerouted' ) ;
Object . keys ( Plugins . loadedHooks ) . forEach ( function ( hook ) {
callback( ) ;
Plugins . loadedHooks [ hook ] . sort ( ( a , b ) => a . priority - b . priority ) ;
} ) ;
} ) ;
} ;
} ;
Plugins . get = function ( id , callback ) {
Plugins . reloadRoutes = async function ( router ) {
var url = ( nconf . get ( 'registry' ) || 'https://packages.nodebb.org' ) + '/api/v1/plugins/' + id ;
var controllers = require ( '../controllers' ) ;
await Plugins . fireHook ( 'static:app.load' , { app : app , router : router , middleware : middleware , controllers : controllers } ) ;
winston . verbose ( '[plugins] All plugins reloaded and rerouted' ) ;
} ;
function request ( url , callback ) {
require ( 'request' ) ( url , {
require ( 'request' ) ( url , {
json : true ,
json : true ,
} , function ( err , res , body ) {
} , function ( err , res , body ) {
if ( res . statusCode === 404 || ! body .payload ) {
if ( res . statusCode === 404 || ! body ) {
return callback ( err , { } ) ;
return callback ( err , { } ) ;
}
}
callback ( err , body ) ;
Plugins . normalise ( [ body . payload ] , function ( err , normalised ) {
normalised = normalised . filter ( function ( plugin ) {
return plugin . id === id ;
} ) ;
return callback ( err , ! err ? normalised [ 0 ] : undefined ) ;
} ) ;
} ) ;
} ) ;
}
const requestAsync = util . promisify ( request ) ;
Plugins . get = async function ( id ) {
const url = ( nconf . get ( 'registry' ) || 'https://packages.nodebb.org' ) + '/api/v1/plugins/' + id ;
const body = await requestAsync ( url ) ;
let normalised = await Plugins . normalise ( [ body ? body . payload : { } ] ) ;
normalised = normalised . filter ( plugin => plugin . id === id ) ;
return normalised . length ? normalised [ 0 ] : undefined ;
} ;
} ;
Plugins . list = function ( matching , callback ) {
Plugins . list = async function ( matching ) {
if ( arguments . length === 1 && typeof matching === 'function' ) {
if ( matching === undefined ) {
callback = matching ;
matching = true ;
matching = true ;
}
}
var version = require ( path . join ( nconf . get ( 'base_dir' ) , 'package.json' ) ) . version ;
const 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 url = ( nconf . get ( 'registry' ) || 'https://packages.nodebb.org' ) + '/api/v1/plugins' + ( matching !== false ? '?version=' + version : '' ) ;
try {
require ( 'request' ) ( url , {
const body = await requestAsync ( url ) ;
json : true ,
return await Plugins . normalise ( body ) ;
} , function ( err , res , body ) {
} catch ( err ) {
if ( err || ( res && res . statusCode !== 200 ) ) {
winston . error ( 'Error loading ' + url , err ) ;
winston . error ( 'Error loading ' + url , err || body ) ;
return await Plugins . normalise ( [ ] ) ;
return Plugins . normalise ( [ ] , callback ) ;
}
}
Plugins . normalise ( body , callback ) ;
} ) ;
} ;
} ;
Plugins . normalise = function ( apiReturn , callback ) {
Plugins . normalise = async function ( apiReturn ) {
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/ ;
const themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/ ;
var pluginMap = { } ;
const pluginMap = { } ;
var dependencies = require ( path . join ( nconf . get ( 'base_dir' ) , 'package.json' ) ) . dependencies ;
const dependencies = require ( path . join ( nconf . get ( 'base_dir' ) , 'package.json' ) ) . dependencies ;
apiReturn = Array . isArray ( apiReturn ) ? apiReturn : [ ] ;
apiReturn = Array . isArray ( apiReturn ) ? apiReturn : [ ] ;
for ( var i = 0 ; i < apiReturn . length ; i += 1 ) {
apiReturn . forEach ( function ( packageData ) {
apiReturn [ i ] . id = apiReturn [ i ] . name ;
packageData . id = packageData . name ;
apiReturn [ i ] . installed = false ;
packageData . installed = false ;
apiReturn [ i ] . active = false ;
packageData . active = false ;
apiReturn [ i ] . url = apiReturn [ i ] . url || ( apiReturn [ i ] . repository ? apiReturn [ i ] . repository . url : '' ) ;
packageData . url = packageData . url || ( packageData . repository ? packageData . repository . url : '' ) ;
pluginMap [ apiReturn [ i ] . name ] = apiReturn [ i ] ;
pluginMap [ packageData . name ] = packageData ;
}
} ) ;
Plugins . showInstalled ( function ( err , installedPlugins ) {
if ( err ) {
return callback ( err ) ;
}
installedPlugins = installedPlugins . filter ( function ( plugin ) {
let installedPlugins = await Plugins . showInstalled ( ) ;
return plugin && ! plugin . system ;
installedPlugins = installedPlugins . filter ( plugin => plugin && ! plugin . system ) ;
} ) ;
async . each ( installedPlugins , function ( plugin , next ) {
// 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 ( ) ;
}
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 ] = pluginMap [ plugin . id ] || { } ;
pluginMap [ plugin . id ] . id = pluginMap [ plugin . id ] . id || plugin . id ;
pluginMap [ plugin . id ] . name = plugin . name || pluginMap [ plugin . id ] . name ;
pluginMap [ plugin . id ] . description = plugin . description ;
pluginMap [ plugin . id ] . url = pluginMap [ plugin . id ] . url || plugin . url ;
pluginMap [ plugin . id ] . installed = true ;
pluginMap [ plugin . id ] . installed = true ;
pluginMap [ plugin . id ] . isTheme = themeNamePattern . test ( plugin . id ) ;
pluginMap [ plugin . id ] . error = true ;
pluginMap [ plugin . id ] . error = plugin . error || false ;
return ;
pluginMap [ plugin . id ] . active = plugin . active ;
}
pluginMap [ plugin . id ] . version = plugin . version ;
pluginMap [ plugin . id ] . settingsRoute = plugin . settingsRoute ;
pluginMap [ plugin . id ] . license = plugin . license ;
// If package.json defines a version to use, stick to that
if ( dependencies . hasOwnProperty ( plugin . id ) && semver . valid ( dependencies [ plugin . id ] ) ) {
pluginMap [ plugin . id ] . latest = dependencies [ plugin . id ] ;
} else {
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 = [ ] ;
pluginMap [ plugin . id ] = pluginMap [ plugin . id ] || { } ;
pluginMap [ plugin . id ] . id = pluginMap [ plugin . id ] . id || plugin . id ;
pluginMap [ plugin . id ] . name = plugin . name || pluginMap [ plugin . id ] . name ;
pluginMap [ plugin . id ] . description = plugin . description ;
pluginMap [ plugin . id ] . url = pluginMap [ plugin . id ] . url || plugin . url ;
pluginMap [ plugin . id ] . installed = true ;
pluginMap [ plugin . id ] . isTheme = themeNamePattern . test ( plugin . id ) ;
pluginMap [ plugin . id ] . error = plugin . error || false ;
pluginMap [ plugin . id ] . active = plugin . active ;
pluginMap [ plugin . id ] . version = plugin . version ;
pluginMap [ plugin . id ] . settingsRoute = plugin . settingsRoute ;
pluginMap [ plugin . id ] . license = plugin . license ;
// If package.json defines a version to use, stick to that
if ( dependencies . hasOwnProperty ( plugin . id ) && semver . valid ( dependencies [ plugin . id ] ) ) {
pluginMap [ plugin . id ] . latest = dependencies [ plugin . id ] ;
} else {
pluginMap [ plugin . id ] . latest = pluginMap [ plugin . id ] . latest || plugin . version ;
}
pluginMap [ plugin . id ] . outdated = semver . gt ( pluginMap [ plugin . id ] . latest , pluginMap [ plugin . id ] . version ) ;
} ) ;
for ( var key in pluginMap ) {
const pluginArray = [ ] ;
if ( pluginMap . hasOwnProperty ( key ) ) {
pluginArray . push ( pluginMap [ key ] ) ;
for ( var key in pluginMap ) {
}
if ( pluginMap . hasOwnProperty ( key ) ) {
}
pluginArray . push ( pluginMap [ key ] ) ;
}
}
pluginArray . sort ( function ( a , b ) {
pluginArray . sort ( function ( a , b ) {
if ( a . name > b . name ) {
if ( a . name > b . name ) {
return 1 ;
return 1 ;
} else if ( a . name < b . name ) {
} else if ( a . name < b . name ) {
return - 1 ;
return - 1 ;
}
}
return 0 ;
return 0 ;
} ) ;
callback ( null , pluginArray ) ;
} ) ;
} ) ;
} ) ;
return pluginArray ;
} ;
} ;
Plugins . nodeModulesPath = path . join ( _ _dirname , '../../node_modules' ) ;
Plugins . nodeModulesPath = path . join ( _ _dirname , '../../node_modules' ) ;
Plugins . showInstalled = function ( callback ) {
Plugins . showInstalled = async function ( ) {
var pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/ ;
const dirs = await readdirAsync ( Plugins . nodeModulesPath ) ;
async . waterfall ( [
let pluginPaths = await findNodeBBModules ( dirs ) ;
function ( next ) {
pluginPaths = pluginPaths . map ( dir => path . join ( Plugins . nodeModulesPath , dir ) ) ;
fs . readdir ( Plugins . nodeModulesPath , next ) ;
} ,
async function load ( file ) {
function ( dirs , next ) {
try {
var pluginPaths = [ ] ;
const pluginData = await Plugins . loadPluginInfo ( file ) ;
const isActive = await Plugins . isActive ( pluginData . name ) ;
async . each ( dirs , function ( dirname , next ) {
delete pluginData . hooks ;
var dirPath = path . join ( Plugins . nodeModulesPath , dirname ) ;
delete pluginData . library ;
pluginData . active = isActive ;
async . waterfall ( [
pluginData . installed = true ;
function ( cb ) {
pluginData . error = false ;
fs . stat ( dirPath , function ( err , stats ) {
return pluginData ;
if ( err && err . code !== 'ENOENT' ) {
} catch ( err ) {
return cb ( err ) ;
winston . error ( err ) ;
}
}
if ( err || ! stats . isDirectory ( ) ) {
}
return next ( ) ;
const plugins = await Promise . all ( pluginPaths . map ( file => load ( file ) ) ) ;
}
return plugins . filter ( Boolean ) ;
} ;
if ( pluginNamePattern . test ( dirname ) ) {
pluginPaths . push ( dirname ) ;
async function findNodeBBModules ( dirs ) {
return next ( ) ;
const pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/ ;
}
const pluginPaths = [ ] ;
await async . each ( dirs , function ( dirname , next ) {
if ( dirname [ 0 ] !== '@' ) {
var dirPath = path . join ( Plugins . nodeModulesPath , dirname ) ;
return next ( ) ;
}
async . waterfall ( [
fs . readdir ( dirPath , cb ) ;
function ( cb ) {
} ) ;
fs . stat ( dirPath , function ( err , stats ) {
} ,
if ( err && err . code !== 'ENOENT' ) {
function ( subdirs , cb ) {
return cb ( err ) ;
async . each ( subdirs , function ( subdir , next ) {
}
if ( ! pluginNamePattern . test ( subdir ) ) {
if ( err || ! stats . isDirectory ( ) ) {
return next ( ) ;
return next ( ) ;
}
}
var subdirPath = path . join ( dirPath , subdir ) ;
if ( pluginNamePattern . test ( dirname ) ) {
fs . stat ( subdirPath , function ( err , stats ) {
pluginPaths . push ( dirname ) ;
if ( err && err . code !== 'ENOENT' ) {
return next ( ) ;
return next ( err ) ;
}
if ( err || ! stats . isDirectory ( ) ) {
return next ( ) ;
}
pluginPaths . push ( dirname + '/' + subdir ) ;
next ( ) ;
} ) ;
} , 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
}
}
plugins . push ( pluginData ) ;
if ( dirname [ 0 ] !== '@' ) {
next ( ) ;
return next ( ) ;
}
fs . readdir ( dirPath , cb ) ;
} ) ;
} ) ;
} , function ( err ) {
} ,
next ( err , plugins ) ;
function ( subdirs , cb ) {
} ) ;
async . each ( subdirs , function ( subdir , next ) {
} ,
if ( ! pluginNamePattern . test ( subdir ) ) {
] , callback ) ;
return next ( ) ;
} ;
}
var subdirPath = path . join ( dirPath , subdir ) ;
fs . stat ( subdirPath , function ( err , stats ) {
if ( err && err . code !== 'ENOENT' ) {
return next ( err ) ;
}
if ( err || ! stats . isDirectory ( ) ) {
return next ( ) ;
}
pluginPaths . push ( dirname + '/' + subdir ) ;
next ( ) ;
} ) ;
} , cb ) ;
} ,
] , next ) ;
} ) ;
return pluginPaths ;
}
Plugins . async = require ( '../promisify' ) ( Plugins ) ;
Plugins . async = require ( '../promisify' ) ( Plugins ) ;