Handle subflow modules with their own npm dependencies

This commit is contained in:
Nick O'Leary
2020-11-25 19:07:30 +00:00
parent de15a1c36f
commit da96c85d32
15 changed files with 237 additions and 70 deletions

View File

@@ -28,6 +28,7 @@ let loadDenyList = [];
var settings;
var disableNodePathScan = false;
var iconFileExtensions = [".png", ".gif", ".svg"];
var packageList = {};
function init(_settings) {
settings = _settings;
@@ -187,9 +188,17 @@ function scanTreeForNodesModules(moduleName) {
var userDir;
if (settings.userDir) {
packageList = getPackageList();
userDir = path.join(settings.userDir,"node_modules");
results = scanDirForNodesModules(userDir,moduleName);
results.forEach(function(r) { r.local = true; });
results.forEach(function(r) {
// If it was found in <userDir>/node_modules then it is considered
// a local module.
// Also check to see if it is listed in the package.json file as a user-installed
// module. This distinguishes modules installed as a dependency
r.local = true;
r.user = !!packageList[r.package.name];
});
}
if (dir) {
@@ -288,20 +297,19 @@ function getNodeFiles(disableNodePathScan) {
}
}
var nodeList = {
"node-red": {
name: "node-red",
version: settings.version,
nodes: {},
icons: iconList
}
var nodeList = {};
var coreNodeEntry = {
name: "node-red",
version: settings.version,
nodes: {},
icons: iconList
}
nodeFiles.forEach(function(node) {
nodeList["node-red"].nodes[node.name] = node;
coreNodeEntry.nodes[node.name] = node;
});
if (settings.coreNodesDir) {
var examplesDir = path.join(settings.coreNodesDir,"examples");
nodeList["node-red"].examples = {path: examplesDir};
coreNodeEntry.examples = {path: examplesDir};
}
if (!disableNodePathScan) {
@@ -310,7 +318,6 @@ function getNodeFiles(disableNodePathScan) {
// Filter the module list to ignore global modules
// that have also been installed locally - allowing the user to
// update a module they may not otherwise be able to touch
moduleFiles.sort(function(A,B) {
if (A.local && !B.local) {
return -1
@@ -323,7 +330,7 @@ function getNodeFiles(disableNodePathScan) {
moduleFiles = moduleFiles.filter(function(mod) {
var result;
if (!knownModules[mod.package.name]) {
knownModules[mod.package.name] = true;
knownModules[mod.package.name] = mod;
result = true;
} else {
result = false;
@@ -332,48 +339,62 @@ function getNodeFiles(disableNodePathScan) {
return result;
});
moduleFiles.forEach(function(moduleFile) {
var nodeModuleFiles = getModuleNodeFiles(moduleFile);
nodeList[moduleFile.package.name] = {
name: moduleFile.package.name,
version: moduleFile.package.version,
path: moduleFile.dir,
local: moduleFile.local||false,
nodes: {},
icons: nodeModuleFiles.icons,
examples: nodeModuleFiles.examples
};
if (moduleFile.package['node-red'].version) {
nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version;
// Do a second pass to check we have all the declared node dependencies
// As this is only done as part of the initial palette load, `knownModules` will
// contain a list of everything discovered during this phase. This means
// we can check for missing dependencies here.
moduleFiles = moduleFiles.filter(function(mod) {
if (Array.isArray(mod.package["node-red"].dependencies)) {
const deps = mod.package["node-red"].dependencies;
const missingDeps = mod.package["node-red"].dependencies.filter(dep => {
if (knownModules[dep]) {
knownModules[dep].usedBy = knownModules[dep].usedBy || [];
knownModules[dep].usedBy.push(mod.package.name)
} else {
return true;
}
})
if (missingDeps.length > 0) {
log.error(`Module: ${mod.package.name} missing dependencies:`);
missingDeps.forEach(m => { log.error(` - ${m}`)});
return false;
}
}
nodeModuleFiles.files.forEach(function(node) {
node.local = moduleFile.local||false;
nodeList[moduleFile.package.name].nodes[node.name] = node;
});
nodeFiles = nodeFiles.concat(nodeModuleFiles.files);
return true;
});
nodeList = convertModuleFileListToObject(moduleFiles);
} else {
// console.log("node path scan disabled");
}
nodeList["node-red"] = coreNodeEntry;
return nodeList;
}
function getModuleFiles(module) {
var nodeList = {};
// Update the package list
var moduleFiles = scanTreeForNodesModules(module);
if (moduleFiles.length === 0) {
var err = new Error(log._("nodes.registry.localfilesystem.module-not-found", {module:module}));
err.code = 'MODULE_NOT_FOUND';
throw err;
}
// Unlike when doing the initial palette load, this call cannot verify the
// dependencies of the new module as it doesn't have visiblity of what
// is in the registry. That will have to be done be the caller in loader.js
return convertModuleFileListToObject(moduleFiles);
}
function convertModuleFileListToObject(moduleFiles) {
const nodeList = {};
moduleFiles.forEach(function(moduleFile) {
var nodeModuleFiles = getModuleNodeFiles(moduleFile);
nodeList[moduleFile.package.name] = {
name: moduleFile.package.name,
version: moduleFile.package.version,
path: moduleFile.dir,
local: moduleFile.local||false,
user: moduleFile.user||false,
nodes: {},
icons: nodeModuleFiles.icons,
examples: nodeModuleFiles.examples
@@ -381,7 +402,14 @@ function getModuleFiles(module) {
if (moduleFile.package['node-red'].version) {
nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version;
}
if (moduleFile.package['node-red'].dependencies) {
nodeList[moduleFile.package.name].dependencies = moduleFile.package['node-red'].dependencies;
}
if (moduleFile.usedBy) {
nodeList[moduleFile.package.name].usedBy = moduleFile.usedBy;
}
nodeModuleFiles.files.forEach(function(node) {
node.local = moduleFile.local||false;
nodeList[moduleFile.package.name].nodes[node.name] = node;
nodeList[moduleFile.package.name].nodes[node.name].local = moduleFile.local || false;
});
@@ -412,6 +440,23 @@ function scanIconDir(dir) {
})
return iconList;
}
/**
* Gets the list of modules installed in this runtime as reported by package.json
* Note: these may include non-Node-RED modules
*/
function getPackageList() {
var list = {};
if (settings.userDir) {
try {
var userPackage = path.join(settings.userDir,"package.json");
var pkg = JSON.parse(fs.readFileSync(userPackage,"utf-8"));
return pkg.dependencies;
} catch(err) {
log.error(err);
}
}
return list;
}
module.exports = {
init: init,