mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Add pre/postInstall hooks to module install path
This commit is contained in:
parent
22df59e229
commit
8140057bea
@ -9,6 +9,7 @@ const path = require("path");
|
||||
const clone = require("clone");
|
||||
const exec = require("@node-red/util").exec;
|
||||
const log = require("@node-red/util").log;
|
||||
const hooks = require("@node-red/util").hooks;
|
||||
|
||||
const BUILTIN_MODULES = require('module').builtinModules;
|
||||
const EXTERNAL_MODULES_DIR = "externalModules";
|
||||
@ -190,12 +191,22 @@ async function installModule(moduleDetails) {
|
||||
await ensureModuleDir();
|
||||
|
||||
var args = ["install", installSpec, "--production"];
|
||||
return exec.run(NPM_COMMAND, args, {
|
||||
cwd: installDir
|
||||
},true).then(result => {
|
||||
let triggerPayload = {
|
||||
"module": moduleDetails.module,
|
||||
"version": moduleDetails.version,
|
||||
"dir": installDir,
|
||||
}
|
||||
return hooks.trigger("preInstall", triggerPayload).then(() => {
|
||||
// preInstall passed
|
||||
// - run install
|
||||
log.trace(NPM_COMMAND + JSON.stringify(args));
|
||||
return exec.run(NPM_COMMAND, args, { cwd: installDir },true)
|
||||
}).then(() => {
|
||||
return hooks.trigger("postInstall", triggerPayload)
|
||||
}).then(() => {
|
||||
log.info(log._("server.install.installed", { name: installSpec }));
|
||||
}).catch(result => {
|
||||
var output = result.stderr;
|
||||
var output = result.stderr || result.toString();
|
||||
var e;
|
||||
if (/E404/.test(output) || /ETARGET/.test(output)) {
|
||||
log.error(log._("server.install.install-failed-not-found",{name:installSpec}));
|
||||
|
@ -23,7 +23,7 @@ const tar = require("tar");
|
||||
const registry = require("./registry");
|
||||
const registryUtil = require("./util");
|
||||
const library = require("./library");
|
||||
const {exec,log,events} = require("@node-red/util");
|
||||
const {exec,log,events,hooks} = require("@node-red/util");
|
||||
const child_process = require('child_process');
|
||||
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
let installerEnabled = false;
|
||||
@ -169,10 +169,23 @@ async function installModule(module,version,url) {
|
||||
|
||||
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
|
||||
var args = ['install','--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production',installName];
|
||||
log.trace(npmCommand + JSON.stringify(args));
|
||||
return exec.run(npmCommand,args,{
|
||||
cwd: installDir
|
||||
}, true).then(result => {
|
||||
let triggerPayload = {
|
||||
"module": module,
|
||||
"version": version,
|
||||
"url": url,
|
||||
"dir": installDir,
|
||||
"isExisting": isExisting,
|
||||
"isUpgrade": isUpgrade
|
||||
}
|
||||
|
||||
return hooks.trigger("preInstall", triggerPayload).then(() => {
|
||||
// preInstall passed
|
||||
// - run install
|
||||
log.trace(npmCommand + JSON.stringify(args));
|
||||
return exec.run(npmCommand,args,{ cwd: installDir}, true)
|
||||
}).then(() => {
|
||||
return hooks.trigger("postInstall", triggerPayload)
|
||||
}).then(() => {
|
||||
if (isExisting) {
|
||||
// This is a module we already have installed as a non-user module.
|
||||
// That means it was discovered when loading, but was not listed
|
||||
@ -191,29 +204,45 @@ async function installModule(module,version,url) {
|
||||
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
|
||||
return require("./registry").setModulePendingUpdated(module,version);
|
||||
}
|
||||
}).catch(result => {
|
||||
var output = result.stderr;
|
||||
var e;
|
||||
var lookFor404 = new RegExp(" 404 .*"+module,"m");
|
||||
var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m");
|
||||
if (lookFor404.test(output)) {
|
||||
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
||||
e = new Error("Module not found");
|
||||
e.code = 404;
|
||||
throw e;
|
||||
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
|
||||
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
|
||||
e = new Error("Module not found");
|
||||
e.code = 404;
|
||||
throw e;
|
||||
} else {
|
||||
}).catch(err => {
|
||||
let e;
|
||||
if (err.hook) {
|
||||
// preInstall failed
|
||||
log.warn(log._("server.install.install-failed-long",{name:module}));
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(output);
|
||||
log.warn(err.toString());
|
||||
log.warn("------------------------------------------");
|
||||
throw new Error(log._("server.install.install-failed"));
|
||||
e = new Error(log._("server.install.install-failed")+": "+err.toString());
|
||||
if (err.hook === "postInstall") {
|
||||
return exec.run(npmCommand,["remove",module],{ cwd: installDir}, false).finally(() => {
|
||||
throw e;
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// npm install failed
|
||||
let output = err.stderr;
|
||||
let lookFor404 = new RegExp(" 404 .*"+module,"m");
|
||||
let lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m");
|
||||
if (lookFor404.test(output)) {
|
||||
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
||||
e = new Error("Module not found");
|
||||
e.code = 404;
|
||||
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
|
||||
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
|
||||
e = new Error("Module not found");
|
||||
e.code = 404;
|
||||
} else {
|
||||
log.warn(log._("server.install.install-failed-long",{name:module}));
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(output);
|
||||
log.warn("------------------------------------------");
|
||||
e = new Error(log._("server.install.install-failed"));
|
||||
}
|
||||
}
|
||||
})
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
// In case of error, reset activePromise to be resolvable
|
||||
activePromise = Promise.resolve();
|
||||
@ -412,17 +441,29 @@ function uninstallModule(module) {
|
||||
log.info(log._("server.install.uninstalling",{name:module}));
|
||||
|
||||
var args = ['remove','--no-audit','--no-update-notifier','--no-fund','--save',module];
|
||||
log.trace(npmCommand + JSON.stringify(args));
|
||||
|
||||
exec.run(npmCommand,args,{
|
||||
cwd: installDir,
|
||||
},true).then(result => {
|
||||
let triggerPayload = {
|
||||
"module": module,
|
||||
"dir": installDir,
|
||||
}
|
||||
return hooks.trigger("preUninstall", triggerPayload).then(() => {
|
||||
// preUninstall passed
|
||||
// - run uninstall
|
||||
log.trace(npmCommand + JSON.stringify(args));
|
||||
return exec.run(npmCommand,args,{ cwd: installDir}, true)
|
||||
}).then(() => {
|
||||
log.info(log._("server.install.uninstalled",{name:module}));
|
||||
reportRemovedModules(list);
|
||||
library.removeExamplesDir(module);
|
||||
resolve(list);
|
||||
return hooks.trigger("postUninstall", triggerPayload).catch((err)=>{
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(err.toString());
|
||||
log.warn("------------------------------------------");
|
||||
}).finally(() => {
|
||||
resolve(list);
|
||||
})
|
||||
}).catch(result => {
|
||||
var output = result.stderr;
|
||||
let output = result.stderr || result;
|
||||
log.warn(log._("server.install.uninstall-failed-long",{name:module}));
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(output.toString());
|
||||
|
@ -34,6 +34,7 @@
|
||||
"install-failed-not-found": "$t(server.install.install-failed-long) module not found",
|
||||
"install-failed-name": "$t(server.install.install-failed-long) invalid module name: __name__",
|
||||
"install-failed-url": "$t(server.install.install-failed-long) invalid url: __url__",
|
||||
"post-install-error": "Error running 'postInstall' hook:",
|
||||
"upgrading": "Upgrading module: __name__ to version: __version__",
|
||||
"upgraded": "Upgraded module: __name__. Restart Node-RED to use the new version",
|
||||
"upgrade-failed-not-found": "$t(server.install.install-failed-long) version not found",
|
||||
|
@ -14,6 +14,7 @@ const os = require("os");
|
||||
const NR_TEST_UTILS = require("nr-test-utils");
|
||||
const externalModules = NR_TEST_UTILS.require("@node-red/registry/lib/externalModules");
|
||||
const exec = NR_TEST_UTILS.require("@node-red/util/lib/exec");
|
||||
const hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks");
|
||||
|
||||
let homeDir;
|
||||
|
||||
@ -40,6 +41,7 @@ describe("externalModules api", function() {
|
||||
await createUserDir()
|
||||
})
|
||||
afterEach(async function() {
|
||||
hooks.clear();
|
||||
await fs.remove(homeDir);
|
||||
})
|
||||
describe("checkFlowDependencies", function() {
|
||||
@ -102,6 +104,25 @@ describe("externalModules api", function() {
|
||||
fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
|
||||
})
|
||||
|
||||
|
||||
it("calls pre/postInstall hooks", async function() {
|
||||
externalModules.init({userDir: homeDir});
|
||||
externalModules.register("function", "libs");
|
||||
let receivedPreEvent,receivedPostEvent;
|
||||
hooks.add("preInstall", function(event) { receivedPreEvent = event; })
|
||||
hooks.add("postInstall", function(event) { receivedPostEvent = event; })
|
||||
|
||||
await externalModules.checkFlowDependencies([
|
||||
{type: "function", libs:[{module: "foo"}]}
|
||||
])
|
||||
exec.run.called.should.be.true();
|
||||
receivedPreEvent.should.have.property("module","foo")
|
||||
receivedPreEvent.should.have.property("version")
|
||||
receivedPreEvent.should.have.property("dir")
|
||||
receivedPreEvent.should.eql(receivedPostEvent)
|
||||
fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
|
||||
})
|
||||
|
||||
it("installs missing modules from inside subflow module", async function() {
|
||||
externalModules.init({userDir: homeDir});
|
||||
externalModules.register("function", "libs");
|
||||
@ -299,4 +320,4 @@ describe("externalModules api", function() {
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
@ -25,7 +25,7 @@ var NR_TEST_UTILS = require("nr-test-utils");
|
||||
var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer");
|
||||
var registry = NR_TEST_UTILS.require("@node-red/registry/lib/index");
|
||||
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry/lib/registry");
|
||||
const { events, exec, log } = NR_TEST_UTILS.require("@node-red/util");
|
||||
const { events, exec, log, hooks } = NR_TEST_UTILS.require("@node-red/util");
|
||||
|
||||
describe('nodes/registry/installer', function() {
|
||||
|
||||
@ -68,6 +68,7 @@ describe('nodes/registry/installer', function() {
|
||||
fs.statSync.restore();
|
||||
}
|
||||
exec.run.restore();
|
||||
hooks.clear();
|
||||
});
|
||||
|
||||
describe("installs module", function() {
|
||||
@ -251,6 +252,65 @@ describe('nodes/registry/installer', function() {
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it("triggers preInstall and postInstall hooks", function(done) {
|
||||
let receivedPreEvent,receivedPostEvent;
|
||||
hooks.add("preInstall", function(event) { receivedPreEvent = event; })
|
||||
hooks.add("postInstall", function(event) { receivedPostEvent = event; })
|
||||
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
|
||||
var res = {code: 0,stdout:"",stderr:""}
|
||||
var p = Promise.resolve(res);
|
||||
p.catch((err)=>{});
|
||||
execResponse = p;
|
||||
|
||||
var addModule = sinon.stub(registry,"addModule",function(md) {
|
||||
return Promise.resolve(nodeInfo);
|
||||
});
|
||||
|
||||
installer.installModule("this_wont_exist","1.2.3").then(function(info) {
|
||||
info.should.eql(nodeInfo);
|
||||
should.exist(receivedPreEvent)
|
||||
receivedPreEvent.should.have.property("module","this_wont_exist")
|
||||
receivedPreEvent.should.have.property("version","1.2.3")
|
||||
receivedPreEvent.should.have.property("dir")
|
||||
receivedPreEvent.should.have.property("url")
|
||||
receivedPreEvent.should.have.property("isExisting")
|
||||
receivedPreEvent.should.have.property("isUpgrade")
|
||||
receivedPreEvent.should.eql(receivedPostEvent)
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it("fails install if preInstall hook fails", function(done) {
|
||||
let receivedEvent;
|
||||
hooks.add("preInstall", function(event) { throw new Error("preInstall-error"); })
|
||||
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
|
||||
|
||||
installer.installModule("this_wont_exist","1.2.3").catch(function(err) {
|
||||
exec.run.called.should.be.false();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it("fails install if preInstall hook fails", function(done) {
|
||||
let receivedEvent;
|
||||
hooks.add("preInstall", function(event) { throw new Error("preInstall-error"); })
|
||||
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
|
||||
|
||||
installer.installModule("this_wont_exist","1.2.3").catch(function(err) {
|
||||
exec.run.called.should.be.false();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
it("rollsback install if postInstall hook fails", function(done) {
|
||||
hooks.add("postInstall", function(event) { throw new Error("fail"); })
|
||||
installer.installModule("this_wont_exist","1.2.3").catch(function(err) {
|
||||
exec.run.calledTwice.should.be.true();
|
||||
exec.run.firstCall.args[1].includes("install").should.be.true();
|
||||
exec.run.secondCall.args[1].includes("remove").should.be.true();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
describe("uninstalls module", function() {
|
||||
it("rejects invalid module names", function(done) {
|
||||
|
Loading…
Reference in New Issue
Block a user