Files
NodeBB/src/plugins/install.js

181 lines
5.1 KiB
JavaScript
Raw Normal View History

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');
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');
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');
const { paths } = require('../constants');
const pkgInstall = require('../cli/package-install');
2019-07-22 00:30:47 -04:00
const packageManager = pkgInstall.getPackageManager();
2019-07-22 00:30:47 -04:00
let packageManagerExecutable = packageManager;
const packageManagerCommands = {
yarn: {
install: 'add',
uninstall: 'remove',
},
npm: {
install: 'install',
uninstall: 'uninstall',
},
cnpm: {
install: 'install',
uninstall: 'uninstall',
},
pnpm: {
install: 'install',
uninstall: 'uninstall',
},
};
if (process.platform === 'win32') {
packageManagerExecutable += '.cmd';
}
module.exports = function (Plugins) {
if (nconf.get('isPrimary')) {
2021-02-04 00:01:39 -07:00
pubsub.on('plugins:toggleInstall', (data) => {
if (data.hostname !== os.hostname()) {
toggleInstall(data.id, data.version);
}
});
2021-02-04 00:01:39 -07:00
pubsub.on('plugins:upgrade', (data) => {
if (data.hostname !== os.hostname()) {
upgrade(data.id, data.version);
}
});
}
2019-07-22 00:30:47 -04:00
Plugins.toggleActive = async function (id) {
feat: Allow defining active plugins in config (#10767) * Revert "Revert "feat: cross origin opener policy options (#10710)"" This reverts commit 46050ace1a65430fe1b567086727da22afab4f73. * Revert "Revert "chore(i18n): fallback strings for new resources: nodebb.admin-settings-advanced"" This reverts commit 9f291c07d3423a41550b38770620a998b45e5c55. * feat: closes #10719, don't trim children if category is marked section * feat: fire hook to allow plugins to filter the pids returned in a user profile /cc julianlam/nodebb-plugin-support-forum#14 * fix: use `user.hidePrivateData();` more consistently across user retrieval endpoints * feat: Allow defining active plugins in config resolves #10766 * fix: assign the db result to files properly * test: add tests with plugins in config * feat: better theme change handling * feat: add visual indication that plugins can't be activated * test: correct hooks * test: fix test definitions * test: remove instead of resetting nconf to avoid affecting other tests * test: ... I forgot how nconf worked * fix: remove negation * docs: improve wording of error message * feat: reduce code duplication * style: remove a redundant space * fix: remove unused imports * fix: use nconf instead of requiring config.json * fix: await... * fix: second missed await * fix: move back from getActiveIds to getActive * fix: use paths again? * fix: typo * fix: move require into the function * fix: forgot to change back to getActive * test: getActive returns only id * test: accedently commented out some stuff * feat: added note to top of plugins page if \!canChangeState Co-authored-by: Julian Lam <julian@nodebb.org> Co-authored-by: Barış Soner Uşaklı <barisusakli@gmail.com>
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;
const hook = isActive ? 'deactivate' : 'activate';
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
};
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]]');
};
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);
};
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 18:54:20 -05: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}` : ''),
'--save',
2021-02-04 00:01:39 -07:00
], (err, stdout) => {
if (err) {
return callback(err);
}
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
});
}
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);
};
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 18:54:20 -05:00
2019-07-22 00:30:47 -04:00
Plugins.isInstalled = async function (id) {
const pluginDir = path.join(paths.nodeModules, id);
2019-07-22 00:30:47 -04:00
try {
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) {
feat: Allow defining active plugins in config (#10767) * Revert "Revert "feat: cross origin opener policy options (#10710)"" This reverts commit 46050ace1a65430fe1b567086727da22afab4f73. * Revert "Revert "chore(i18n): fallback strings for new resources: nodebb.admin-settings-advanced"" This reverts commit 9f291c07d3423a41550b38770620a998b45e5c55. * feat: closes #10719, don't trim children if category is marked section * feat: fire hook to allow plugins to filter the pids returned in a user profile /cc julianlam/nodebb-plugin-support-forum#14 * fix: use `user.hidePrivateData();` more consistently across user retrieval endpoints * feat: Allow defining active plugins in config resolves #10766 * fix: assign the db result to files properly * test: add tests with plugins in config * feat: better theme change handling * feat: add visual indication that plugins can't be activated * test: correct hooks * test: fix test definitions * test: remove instead of resetting nconf to avoid affecting other tests * test: ... I forgot how nconf worked * fix: remove negation * docs: improve wording of error message * feat: reduce code duplication * style: remove a redundant space * fix: remove unused imports * fix: use nconf instead of requiring config.json * fix: await... * fix: second missed await * fix: move back from getActiveIds to getActive * fix: use paths again? * fix: typo * fix: move require into the function * fix: forgot to change back to getActive * test: getActive returns only id * test: accedently commented out some stuff * feat: added note to top of plugins page if \!canChangeState Co-authored-by: Julian Lam <julian@nodebb.org> Co-authored-by: Barış Soner Uşaklı <barisusakli@gmail.com>
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 () {
feat: Allow defining active plugins in config (#10767) * Revert "Revert "feat: cross origin opener policy options (#10710)"" This reverts commit 46050ace1a65430fe1b567086727da22afab4f73. * Revert "Revert "chore(i18n): fallback strings for new resources: nodebb.admin-settings-advanced"" This reverts commit 9f291c07d3423a41550b38770620a998b45e5c55. * feat: closes #10719, don't trim children if category is marked section * feat: fire hook to allow plugins to filter the pids returned in a user profile /cc julianlam/nodebb-plugin-support-forum#14 * fix: use `user.hidePrivateData();` more consistently across user retrieval endpoints * feat: Allow defining active plugins in config resolves #10766 * fix: assign the db result to files properly * test: add tests with plugins in config * feat: better theme change handling * feat: add visual indication that plugins can't be activated * test: correct hooks * test: fix test definitions * test: remove instead of resetting nconf to avoid affecting other tests * test: ... I forgot how nconf worked * fix: remove negation * docs: improve wording of error message * feat: reduce code duplication * style: remove a redundant space * fix: remove unused imports * fix: use nconf instead of requiring config.json * fix: await... * fix: second missed await * fix: move back from getActiveIds to getActive * fix: use paths again? * fix: typo * fix: move require into the function * fix: forgot to change back to getActive * test: getActive returns only id * test: accedently commented out some stuff * feat: added note to top of plugins page if \!canChangeState Co-authored-by: Julian Lam <julian@nodebb.org> Co-authored-by: Barış Soner Uşaklı <barisusakli@gmail.com>
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
};
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
};