Skip to content
Snippets Groups Projects
Commit e2cc4264 authored by Cédric Moreau's avatar Cédric Moreau
Browse files

[enh] #918 Add external module support with `plug` and `unplug` commands

parent fec286f8
Branches
Tags
No related merge requests found
"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)
})
})
}
...@@ -21,6 +21,7 @@ const revertDependency = require('./app/modules/revert'); ...@@ -21,6 +21,7 @@ const revertDependency = require('./app/modules/revert');
const daemonDependency = require('./app/modules/daemon'); const daemonDependency = require('./app/modules/daemon');
const pSignalDependency = require('./app/modules/peersignal'); const pSignalDependency = require('./app/modules/peersignal');
const routerDependency = require('./app/modules/router'); const routerDependency = require('./app/modules/router');
const pluginDependency = require('./app/modules/plugin');
const MINIMAL_DEPENDENCIES = [ const MINIMAL_DEPENDENCIES = [
{ name: 'duniter-config', required: configDependency } { name: 'duniter-config', required: configDependency }
...@@ -35,7 +36,8 @@ const DEFAULT_DEPENDENCIES = MINIMAL_DEPENDENCIES.concat([ ...@@ -35,7 +36,8 @@ const DEFAULT_DEPENDENCIES = MINIMAL_DEPENDENCIES.concat([
{ name: 'duniter-revert', required: revertDependency }, { name: 'duniter-revert', required: revertDependency },
{ name: 'duniter-daemon', required: daemonDependency }, { name: 'duniter-daemon', required: daemonDependency },
{ name: 'duniter-psignal', required: pSignalDependency }, { 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([ const PRODUCTION_DEPENDENCIES = DEFAULT_DEPENDENCIES.concat([
...@@ -69,8 +71,9 @@ module.exports.statics = { ...@@ -69,8 +71,9 @@ module.exports.statics = {
// Look for compliant packages // Look for compliant packages
const prodDeps = Object.keys(pjson.dependencies); const prodDeps = Object.keys(pjson.dependencies);
const devDeps = Object.keys(pjson.devDependencies); 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) { for(const dep of duniterDeps) {
try {
const required = require(dep); const required = require(dep);
if (required.duniter) { if (required.duniter) {
duniterModules.push({ duniterModules.push({
...@@ -78,6 +81,7 @@ module.exports.statics = { ...@@ -78,6 +81,7 @@ module.exports.statics = {
required required
}); });
} }
} catch (e) { /* Silent errors for packages that fail to load */ }
} }
// The final stack // The final stack
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment