From e2cc426471cd32cd169bc0871bf1e5829e2ab35c Mon Sep 17 00:00:00 2001 From: cgeek <cem.moreau@gmail.com> Date: Wed, 24 May 2017 12:00:37 +0200 Subject: [PATCH] =?UTF-8?q?[enh]=C2=A0#918=20Add=20external=20module=20sup?= =?UTF-8?q?port=20with=20`plug`=20and=20`unplug`=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/modules/plugin.js | 129 ++++++++++++++++++++++++++++++++++++++++++ index.js | 22 ++++--- 2 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 app/modules/plugin.js diff --git a/app/modules/plugin.js b/app/modules/plugin.js new file mode 100644 index 000000000..5c6708376 --- /dev/null +++ b/app/modules/plugin.js @@ -0,0 +1,129 @@ +"use strict"; + +const co = require('co'); +const fs = require('fs'); +const path = require('path'); +const spawn = require('child_process').spawn; + +module.exports = { + duniter: { + + methods: { + canWrite: getNPMAccess + }, + + cli: [{ + name: 'plug [what]', + desc: 'Plugs in a duniter module to this Duniter codebase, making it available for the node.', + logs: false, + onDatabaseExecute: (server, conf, program, params) => co(function*() { + const what = params[0]; + try { + console.log('Trying to install module "%s"...', what) + yield checkNPMAccess() + yield npmInstall(what) + console.log('Module successfully installed.') + } catch (err) { + console.error('Error during installation of the plugin:', err); + } + // Close the DB connection properly + return server && server.disconnect() + }) + }, { + name: 'unplug [what]', + desc: 'Plugs in a duniter module to this Duniter codebase, making it available for the node.', + logs: false, + onDatabaseExecute: (server, conf, program, params) => co(function*() { + const what = params[0]; + try { + console.log('Trying to remove module "%s"...', what) + yield checkNPMAccess() + yield npmRemove(what) + console.log('Module successfully uninstalled.') + } catch (err) { + console.error('Error during installation of the plugin:', err); + } + // Close the DB connection properly + return server && server.disconnect() + }) + }] + } +} + +function npmInstall(what) { + return new Promise((res, rej) => { + const npm = getNPM() + const cwd = getCWD() + const install = spawn(npm, ['i', '--save', what], { cwd }) + + install.stdout.pipe(process.stdout) + install.stderr.pipe(process.stderr) + + install.stderr.on('data', (data) => { + if (data.toString().match(/ERR!/)) { + setTimeout(() => { + install.kill('SIGINT') + }, 100) + } + }); + + install.on('close', (code) => { + if (code === null || code > 0) { + return rej('could not retrieve or install the plugin') + } + res() + }); + }) +} + + +function npmRemove(what) { + return new Promise((res, rej) => { + const npm = getNPM() + const cwd = getCWD() + const uninstall = spawn(npm, ['remove', '--save', what], { cwd }) + + uninstall.stdout.pipe(process.stdout) + uninstall.stderr.pipe(process.stderr) + + uninstall.stderr.on('data', (data) => { + if (data.toString().match(/ERR!/)) { + setTimeout(() => { + uninstall.kill('SIGINT') + }, 100) + } + }); + + uninstall.on('close', (code) => { + if (code === null || code > 0) { + return rej('error during the uninstallation of the plugin') + } + res() + }); + }) +} + +function getNPM() { + return process.argv[0].replace(/node$/, 'npm') +} + +function getCWD() { + return process.argv[1].replace(/bin\/duniter$/, '') +} + +function checkNPMAccess() { + return co(function*() { + const hasReadWriteAccess = yield getNPMAccess() + if (!hasReadWriteAccess) { + throw 'no write access on disk' + } + }) +} + +function getNPMAccess() { + return new Promise((res) => { + fs.access(path.join(__dirname, '/../../package.json'), fs.constants.R_OK | fs.constants.W_OK, (err) => { + res(!err) + }) + }) +} diff --git a/index.js b/index.js index d880837da..f66df9097 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,7 @@ const revertDependency = require('./app/modules/revert'); const daemonDependency = require('./app/modules/daemon'); const pSignalDependency = require('./app/modules/peersignal'); const routerDependency = require('./app/modules/router'); +const pluginDependency = require('./app/modules/plugin'); const MINIMAL_DEPENDENCIES = [ { name: 'duniter-config', required: configDependency } @@ -35,7 +36,8 @@ const DEFAULT_DEPENDENCIES = MINIMAL_DEPENDENCIES.concat([ { name: 'duniter-revert', required: revertDependency }, { name: 'duniter-daemon', required: daemonDependency }, { name: 'duniter-psignal', required: pSignalDependency }, - { name: 'duniter-router', required: routerDependency } + { name: 'duniter-router', required: routerDependency }, + { name: 'duniter-plugin', required: pluginDependency } ]); const PRODUCTION_DEPENDENCIES = DEFAULT_DEPENDENCIES.concat([ @@ -69,15 +71,17 @@ module.exports.statics = { // Look for compliant packages const prodDeps = Object.keys(pjson.dependencies); const devDeps = Object.keys(pjson.devDependencies); - const duniterDeps = _.filter(prodDeps.concat(devDeps), (dep) => dep.match(/^duniter-/)); + const duniterDeps = prodDeps.concat(devDeps) for(const dep of duniterDeps) { - const required = require(dep); - if (required.duniter) { - duniterModules.push({ - name: dep, - required - }); - } + try { + const required = require(dep); + if (required.duniter) { + duniterModules.push({ + name: dep, + required + }); + } + } catch (e) { /* Silent errors for packages that fail to load */ } } // The final stack -- GitLab