2014-12-26 18:54:20 -05:00
'use strict' ;
2019-07-22 00:30:47 -04:00
const winston = require ( 'winston' ) ;
const path = require ( 'path' ) ;
2021-11-22 15:37:32 -05:00
const fs = require ( 'fs' ) . promises ;
2019-07-22 00:30:47 -04:00
const nconf = require ( 'nconf' ) ;
const os = require ( 'os' ) ;
const cproc = require ( 'child_process' ) ;
const util = require ( 'util' ) ;
2020-09-24 15:06:47 -04:00
const request = require ( 'request-promise-native' ) ;
2019-07-22 00:30:47 -04:00
const db = require ( '../database' ) ;
const meta = require ( '../meta' ) ;
const pubsub = require ( '../pubsub' ) ;
2020-10-01 21:02:44 -06:00
const { paths } = require ( '../constants' ) ;
2022-02-02 12:46:13 -05:00
const pkgInstall = require ( '../cli/package-install' ) ;
2019-07-22 00:30:47 -04:00
2022-02-02 12:46:13 -05:00
const packageManager = pkgInstall . getPackageManager ( ) ;
2019-07-22 00:30:47 -04:00
let packageManagerExecutable = packageManager ;
const packageManagerCommands = {
2017-12-20 13:56:14 -07:00
yarn : {
install : 'add' ,
uninstall : 'remove' ,
} ,
npm : {
install : 'install' ,
uninstall : 'uninstall' ,
} ,
2020-04-13 21:59:52 +08:00
cnpm : {
install : 'install' ,
uninstall : 'uninstall' ,
} ,
pnpm : {
install : 'install' ,
uninstall : 'uninstall' ,
} ,
2017-12-20 13:56:14 -07:00
} ;
if ( process . platform === 'win32' ) {
packageManagerExecutable += '.cmd' ;
}
2016-10-13 11:43:39 +02:00
module . exports = function ( Plugins ) {
2020-07-07 20:13:14 -04:00
if ( nconf . get ( 'isPrimary' ) ) {
2021-02-04 00:01:39 -07:00
pubsub . on ( 'plugins:toggleInstall' , ( data ) => {
2014-12-26 20:18:05 -05:00
if ( data . hostname !== os . hostname ( ) ) {
toggleInstall ( data . id , data . version ) ;
}
} ) ;
2021-02-04 00:01:39 -07:00
pubsub . on ( 'plugins:upgrade' , ( data ) => {
2014-12-26 20:18:05 -05:00
if ( data . hostname !== os . hostname ( ) ) {
upgrade ( data . id , data . version ) ;
}
} ) ;
}
2019-07-22 00:30:47 -04:00
Plugins . toggleActive = async function ( id ) {
2022-07-25 20:04:55 +02:00
if ( nconf . get ( 'plugins:active' ) ) {
winston . error ( 'Cannot activate plugins while plugin state is set in the configuration (config.json, environmental variables or terminal arguments), please modify the configuration instead' ) ;
throw new Error ( '[[error:plugins-set-in-configuration]]' ) ;
}
2019-07-22 00:30:47 -04:00
const isActive = await Plugins . isActive ( id ) ;
if ( isActive ) {
await db . sortedSetRemove ( 'plugins:active' , id ) ;
} else {
const count = await db . sortedSetCard ( 'plugins:active' ) ;
await db . sortedSetAdd ( 'plugins:active' , count , id ) ;
}
meta . reloadRequired = true ;
2021-08-31 10:45:08 -04:00
const hook = isActive ? 'deactivate' : 'activate' ;
2021-08-31 11:33:20 -04:00
Plugins . hooks . fire ( ` action:plugin. ${ hook } ` , { id : id } ) ;
2019-07-22 00:30:47 -04:00
return { id : id , active : ! isActive } ;
2014-12-26 18:54:20 -05:00
} ;
2020-09-24 15:06:47 -04:00
Plugins . checkWhitelist = async function ( id , version ) {
const body = await request ( {
method : 'GET' ,
url : ` https://packages.nodebb.org/api/v1/plugins/ ${ encodeURIComponent ( id ) } ` ,
json : true ,
} ) ;
if ( body && body . code === 'ok' && ( version === 'latest' || body . payload . valid . includes ( version ) ) ) {
return ;
}
throw new Error ( '[[error:plugin-not-whitelisted]]' ) ;
} ;
2022-11-24 11:17:06 -05:00
Plugins . suggest = async function ( pluginId , nbbVersion ) {
const body = await request ( {
method : 'GET' ,
url : ` https://packages.nodebb.org/api/v1/suggest?package= ${ encodeURIComponent ( pluginId ) } &version= ${ encodeURIComponent ( nbbVersion ) } ` ,
json : true ,
} ) ;
return body ;
} ;
2019-07-22 00:30:47 -04:00
Plugins . toggleInstall = async function ( id , version ) {
2017-02-18 12:30:49 -07:00
pubsub . publish ( 'plugins:toggleInstall' , { hostname : os . hostname ( ) , id : id , version : version } ) ;
2019-07-22 00:30:47 -04:00
return await toggleInstall ( id , version ) ;
2014-12-26 20:18:05 -05:00
} ;
2019-07-22 00:30:47 -04:00
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 ) {
await Plugins . toggleActive ( id ) ;
}
await runPackageManagerCommandAsync ( type , id , version || 'latest' ) ;
const pluginData = await Plugins . get ( id ) ;
2021-02-03 23:59:08 -07:00
Plugins . hooks . fire ( ` action:plugin. ${ type } ` , { id : id , version : version } ) ;
2019-07-22 00:30:47 -04:00
return pluginData ;
2014-12-26 20:18:05 -05:00
}
2014-12-26 18:54:20 -05:00
2017-12-20 13:56:14 -07:00
function runPackageManagerCommand ( command , pkgName , version , callback ) {
cproc . execFile ( packageManagerExecutable , [
packageManagerCommands [ packageManager ] [ command ] ,
2021-02-03 23:59:08 -07:00
pkgName + ( command === 'install' ? ` @ ${ version } ` : '' ) ,
2017-12-20 13:56:14 -07:00
'--save' ,
2021-02-04 00:01:39 -07:00
] , ( err , stdout ) => {
2016-01-12 13:53:26 +02:00
if ( err ) {
return callback ( err ) ;
}
2016-12-14 09:28:26 -05:00
2021-02-03 23:59:08 -07:00
winston . verbose ( ` [plugins/ ${ command } ] ${ stdout } ` ) ;
2016-11-18 14:03:06 +03:00
callback ( ) ;
2016-12-15 16:08:32 +03:00
} ) ;
2016-01-12 13:53:26 +02:00
}
2019-07-22 00:30:47 -04:00
Plugins . upgrade = async function ( id , version ) {
2017-02-18 12:30:49 -07:00
pubsub . publish ( 'plugins:upgrade' , { hostname : os . hostname ( ) , id : id , version : version } ) ;
2019-07-22 00:30:47 -04:00
return await upgrade ( id , version ) ;
2014-12-26 20:18:05 -05:00
} ;
2019-07-22 00:30:47 -04:00
async function upgrade ( id , version ) {
await runPackageManagerCommandAsync ( 'install' , id , version || 'latest' ) ;
const isActive = await Plugins . isActive ( id ) ;
meta . reloadRequired = isActive ;
return isActive ;
2014-12-26 20:18:05 -05:00
}
2014-12-26 18:54:20 -05:00
2019-07-22 00:30:47 -04:00
Plugins . isInstalled = async function ( id ) {
2020-10-01 21:02:44 -06:00
const pluginDir = path . join ( paths . nodeModules , id ) ;
2019-07-22 00:30:47 -04:00
try {
2021-11-22 15:37:32 -05:00
const stats = await fs . stat ( pluginDir ) ;
2019-07-22 00:30:47 -04:00
return stats . isDirectory ( ) ;
} catch ( err ) {
return false ;
}
2014-12-26 18:54:20 -05:00
} ;
2019-07-22 00:30:47 -04:00
Plugins . isActive = async function ( id ) {
2022-07-25 20:04:55 +02:00
if ( nconf . get ( 'plugins:active' ) ) {
return nconf . get ( 'plugins:active' ) . includes ( id ) ;
}
2019-07-22 00:30:47 -04:00
return await db . isSortedSetMember ( 'plugins:active' , id ) ;
2014-12-26 18:54:20 -05:00
} ;
2015-02-23 15:55:35 -05:00
2019-07-22 00:30:47 -04:00
Plugins . getActive = async function ( ) {
2022-07-25 20:04:55 +02:00
if ( nconf . get ( 'plugins:active' ) ) {
return nconf . get ( 'plugins:active' ) ;
}
2019-07-22 00:30:47 -04:00
return await db . getSortedSetRange ( 'plugins:active' , 0 , - 1 ) ;
2015-02-23 15:55:35 -05:00
} ;
2021-11-22 15:37:32 -05:00
Plugins . autocomplete = async ( fragment ) => {
const pluginDir = paths . nodeModules ;
const plugins = ( await fs . readdir ( pluginDir ) ) . filter ( filename => filename . startsWith ( fragment ) ) ;
// Autocomplete only if single match
return plugins . length === 1 ? plugins . pop ( ) : fragment ;
} ;
2017-02-18 02:30:48 -07:00
} ;