From 18610bb54094e56f6a35e250b7223f392a7271b6 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 22 May 2023 14:24:11 +0100 Subject: [PATCH] Ensure external modules are installed synchronously Fixes #4168 --- .../@node-red/registry/lib/externalModules.js | 111 +++++++++--------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/packages/node_modules/@node-red/registry/lib/externalModules.js b/packages/node_modules/@node-red/registry/lib/externalModules.js index b61392d9b..b76f748a4 100644 --- a/packages/node_modules/@node-red/registry/lib/externalModules.js +++ b/packages/node_modules/@node-red/registry/lib/externalModules.js @@ -242,63 +242,68 @@ async function ensureModuleDir() { } } +let installLock = Promise.resolve() async function installModule(moduleDetails) { - let installSpec = moduleDetails.module; - if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) { - const e = new Error("Install not allowed"); - e.code = "install_not_allowed"; - throw e; - } - if (moduleDetails.version) { - installSpec = installSpec+"@"+moduleDetails.version; - } - log.info(log._("server.install.installing",{name: moduleDetails.module,version: moduleDetails.version||"latest"})); - const installDir = getInstallDir(); - - await ensureModuleDir(); - - let triggerPayload = { - "module": moduleDetails.module, - "version": moduleDetails.version, - "dir": installDir, - "args": ["--production","--engine-strict"] - } - return hooks.trigger("preInstall", triggerPayload).then((result) => { - // preInstall passed - // - run install - if (result !== false) { - let extraArgs = triggerPayload.args || []; - let args = ['install', ...extraArgs, installSpec] - log.trace(NPM_COMMAND + JSON.stringify(args)); - return exec.run(NPM_COMMAND, args, { cwd: installDir },true) - } else { - log.trace("skipping npm install"); - } - }).then(() => { - return hooks.trigger("postInstall", triggerPayload) - }).then(() => { - log.info(log._("server.install.installed", { name: installSpec })); - const runtimeInstalledModules = settings.get("modules") || {}; - runtimeInstalledModules[moduleDetails.module] = moduleDetails; - settings.set("modules",runtimeInstalledModules) - }).catch(result => { - 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})); - e = new Error("Module not found"); - e.code = 404; - throw e; - } else { - log.error(log._("server.install.install-failed-long",{name:installSpec})); - log.error("------------------------------------------"); - log.error(output); - log.error("------------------------------------------"); - e = new Error(log._("server.install.install-failed")); - e.code = "unexpected_error"; + const result = installLock.then(async () => { + let installSpec = moduleDetails.module; + if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) { + const e = new Error("Install not allowed"); + e.code = "install_not_allowed"; throw e; } + if (moduleDetails.version) { + installSpec = installSpec+"@"+moduleDetails.version; + } + log.info(log._("server.install.installing",{name: moduleDetails.module,version: moduleDetails.version||"latest"})); + const installDir = getInstallDir(); + + await ensureModuleDir(); + + let triggerPayload = { + "module": moduleDetails.module, + "version": moduleDetails.version, + "dir": installDir, + "args": ["--production","--engine-strict"] + } + return hooks.trigger("preInstall", triggerPayload).then((result) => { + // preInstall passed + // - run install + if (result !== false) { + let extraArgs = triggerPayload.args || []; + let args = ['install', ...extraArgs, installSpec] + log.trace(NPM_COMMAND + JSON.stringify(args)); + return exec.run(NPM_COMMAND, args, { cwd: installDir },true) + } else { + log.trace("skipping npm install"); + } + }).then(() => { + return hooks.trigger("postInstall", triggerPayload) + }).then(() => { + log.info(log._("server.install.installed", { name: installSpec })); + const runtimeInstalledModules = settings.get("modules") || {}; + runtimeInstalledModules[moduleDetails.module] = moduleDetails; + settings.set("modules",runtimeInstalledModules) + }).catch(result => { + 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})); + e = new Error("Module not found"); + e.code = 404; + throw e; + } else { + log.error(log._("server.install.install-failed-long",{name:installSpec})); + log.error("------------------------------------------"); + log.error(output); + log.error("------------------------------------------"); + e = new Error(log._("server.install.install-failed")); + e.code = "unexpected_error"; + throw e; + } + }) }) + installLock = result.catch(() => {}) + return result } module.exports = {