Detect externalModule dependencies inside subflow modules

Not sure this is 100% the right approach. If a subflow module has a dependency
it should be in the subflow's package.json and therefore installed next to the
subflow module in ~/.node-red/node_modules.

By treating it as a 'normal' external module, it will be dynamically installed
in ~/.node-red/externalModules. That then exposes the module to the user
who won't know why its there and may remove it.

It would be better to allow nodes inside a subflow module to require
from ~/.node-red/node_modules and not limit it to the externalModules
dir. The hard part is knowing when to do that.
This commit is contained in:
Nick O'Leary 2021-02-14 00:02:08 +00:00
parent 6336ab121e
commit d2c9ccbfdd
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
6 changed files with 24 additions and 7 deletions

View File

@ -6,6 +6,7 @@
const fs = require("fs-extra");
const registryUtil = require("./util");
const path = require("path");
const clone = require("clone");
const exec = require("@node-red/util").exec;
const log = require("@node-red/util").log;
@ -16,6 +17,7 @@ const EXTERNAL_MODULES_DIR = "externalModules";
const NPM_COMMAND = (process.platform === "win32") ? "npm.cmd" : "npm";
let registeredTypes = {};
let subflowTypes = {};
let settings;
let knownExternalModules = {};
@ -58,6 +60,10 @@ function register(type, dynamicModuleListProperty) {
registeredTypes[type] = dynamicModuleListProperty;
}
function registerSubflow(type, subflowConfig) {
subflowTypes[type] = subflowConfig;
}
function requireModule(module) {
if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
const e = new Error("Module not allowed");
@ -95,15 +101,21 @@ function isInstalled(moduleDetails) {
return moduleDetails.builtin || moduleDetails.known;
}
async function checkFlowDependencies(flowConfig) {
let nodes = clone(flowConfig);
await refreshExternalModules();
const checkedModules = {};
const promises = [];
const errors = [];
flowConfig.forEach(n => {
if (registeredTypes[n.type]) {
const checkedSubflows = {};
while (nodes.length > 0) {
let n = nodes.shift();
if (subflowTypes[n.type] && !checkedSubflows[n.type]) {
checkedSubflows[n.type] = true;
nodes = nodes.concat(subflowTypes[n.type].flow)
} else if (registeredTypes[n.type]) {
let nodeModules = n[registeredTypes[n.type]] || [];
if (!Array.isArray(nodeModules)) {
nodeModules = [nodeModules]
@ -135,8 +147,7 @@ async function checkFlowDependencies(flowConfig) {
}
})
}
})
}
return Promise.all(promises).then(refreshExternalModules).then(() => {
if (errors.length > 0) {
throw errors;
@ -205,6 +216,7 @@ async function installModule(moduleDetails) {
module.exports = {
init: init,
register: register,
registerSubflow: registerSubflow,
checkFlowDependencies: checkFlowDependencies,
require: requireModule
}

View File

@ -456,6 +456,9 @@ function registerSubflow(nodeSet, subflow) {
nodeSetInfo.config = result.config;
}
subflowModules[result.type] = result;
externalModules.registerSubflow(result.type,subflow);
events.emit("type-registered",result.type);
return result;
}

View File

@ -1,6 +1,6 @@
{
"name": "test-subflow-mod",
"version": "1.0.1",
"version": "1.0.2",
"description": "",
"keywords": [],
"license": "ISC",
@ -13,6 +13,7 @@
]
},
"dependencies": {
"node-red-node-random": "*"
"node-red-node-random": "*",
"cowsay2": "*"
}
}

View File

@ -189,6 +189,7 @@
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [ {"var":"cowsay2","module":"cowsay2"}],
"x": 240,
"y": 100,
"wires": [

Binary file not shown.