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

View File

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

View File

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

View File

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

Binary file not shown.