Socket
Socket
Sign inDemoInstall

@node-red/registry

Package Overview
Dependencies
Maintainers
2
Versions
107
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@node-red/registry - npm Package Compare versions

Comparing version 1.2.9 to 1.3.0-beta.1

lib/externalModules.js

228

lib/index.js

@@ -1,2 +0,2 @@

/**
/*!
* Copyright JS Foundation and other contributors, http://js.foundation

@@ -31,13 +31,28 @@ *

var library = require("./library");
const externalModules = require("./externalModules")
var plugins = require("./plugins");
var settings;
/**
* Initialise the registry with a reference to a runtime object
* @param {Object} runtime - a runtime object
* @memberof @node-red/registry
*/
function init(runtime) {
settings = runtime.settings;
installer.init(runtime);
installer.init(runtime.settings);
// Loader requires the full runtime object because it initialises
// the util module it. The Util module is responsible for constructing the
// RED object passed to node modules when they are loaded.
loader.init(runtime);
registry.init(settings,loader,runtime.events);
plugins.init(runtime.settings);
registry.init(runtime.settings,loader);
library.init();
externalModules.init(runtime.settings);
}
/**
* Triggers the intial discovery and loading of all Node-RED node modules.
* found on the node path.
* @return {Promise} - resolves when the registry has finised discovering node modules.
* @memberof @node-red/registry
*/
function load() {

@@ -70,34 +85,233 @@ registry.load();

clear: registry.clear,
/**
* Register a node constructor function.
*
* @param {Object} nodeSet - the Node Set object the constructor is for
* @param {String} type - the node type
* @param {Function} constructor - the node constructor function
* @function
* @memberof @node-red/registry
*/
registerType: registry.registerNodeConstructor,
/**
* Get a node constructor function.
*
* @param {String} type - the node type
* @return {Function} the node constructor function
* @function
* @memberof @node-red/registry
*/
get: registry.getNodeConstructor,
registerSubflow: registry.registerSubflow,
/**
* Get a node's set information.
*
* @param {String} type - the node type or set identifier
* @return {Object} the node set information
* @function
* @memberof @node-red/registry
*/
getNodeInfo: registry.getNodeInfo,
/**
* Get a list of all nodes in the registry.
*
* @return {Object} the node list
* @function
* @memberof @node-red/registry
*/
getNodeList: registry.getNodeList,
/**
* Get a modules's information.
*
* @param {String} type - the module identifier
* @return {Object} the module information
* @function
* @memberof @node-red/registry
*/
getModuleInfo: registry.getModuleInfo,
/**
* Get a list of all moduless in the registry.
*
* @return {Object} the module list
* @function
* @memberof @node-red/registry
*/
getModuleList: registry.getModuleList,
/**
* Get the HTML configs for all nodes in the registry.
*
* @param {String} lang - the language to return, default `en-US`
* @return {String} the node configs
* @function
* @memberof @node-red/registry
*/
getNodeConfigs: registry.getAllNodeConfigs,
/**
* Get the HTML config for a single node set.
*
* @param {String} id - the node identifier
* @param {String} lang - the language to return, default `en-US`
* @return {String} the node config
* @function
* @memberof @node-red/registry
*/
getNodeConfig: registry.getNodeConfig,
/**
* Get the local path to a node's icon file.
*
* @param {String} module - the module that provides the icon
* @param {String} icon - the name of the icon
* @return {String} the local path to the icon
* @function
* @memberof @node-red/registry
*/
getNodeIconPath: registry.getNodeIconPath,
/**
* Get the full list of all icons available.
*
* @return {String} the icon list
* @function
* @memberof @node-red/registry
*/
getNodeIcons: registry.getNodeIcons,
/**
* Enables a node set, making it available for use.
*
* @param {String} type - the node type or set identifier
* @return {Promise} A promise that resolves when the node set has been enabled
* @throws if the identifier is not recognised or runtime settings are unavailable
* @function
* @memberof @node-red/registry
*/
enableNode: enableNodeSet,
/**
* Disables a node set, making it unavailable for use.
*
* @param {String} type - the node type or set identifier
* @return {Promise} A promise that resolves when the node set has been disabled
* @throws if the identifier is not recognised or runtime settings are unavailable
* @function
* @memberof @node-red/registry
*/
disableNode: registry.disableNodeSet,
/**
* Loads a new module into the registry.
*
* This will rescan the node module paths looking for this module.
*
* @param {String} module - the name of the module to add
* @return {Promise<Object>} A promise that resolves with the module information once it has been added
* @throws if the module has already been added or the runtime settings are unavailable
* @function
* @memberof @node-red/registry
*/
addModule: addModule,
/**
* Removes a module from the registry.
*
* @param {String} module - the name of the module to remove
* @return {Promise<Array>} A promise that resolves with the list of removed node sets
* @throws if the module is not found or the runtime settings are unavailable
* @function
* @memberof @node-red/registry
*/
removeModule: registry.removeModule,
/**
* Installs a new node module using npm and then add to the registry
*
* @param {String|Buffer} module - the name of the module to install, or a Buffer containing a module tar file
* @param {String} version - the version of the module to install, default: `latest`
* @param {String} url - (optional) a url to install the module from
* @return {Promise<Array>} A promise that resolves with the module information once it has been installed
* @function
* @memberof @node-red/registry
*/
installModule: installer.installModule,
/**
* Uninstalls a module using npm
*
* @param {String} module - the name of the module to uninstall
* @return {Promise<Array>} A promise that resolves when the module has been removed
* @function
* @memberof @node-red/registry
*/
uninstallModule: installer.uninstallModule,
/**
* Update to internal list of available modules based on what has been actually
* loaded.
*
* The `externalModules.autoInstall` (previously `autoInstallModules`)
* runtime option means the runtime may try to install
* missing modules after the initial load is complete. If that flag is not set
* this function is used to remove the modules from the registry's saved list.
* @function
* @memberof @node-red/registry
*/
cleanModuleList: registry.cleanModuleList,
paletteEditorEnabled: installer.paletteEditorEnabled,
/**
* Check if the regisrty is able to install/remove modules.
*
* This is based on whether it has found `npm` on the command-line.
* @return {Boolean} whether the installer is enabled
*
* @function
* @memberof @node-red/registry
*/
installerEnabled: installer.installerEnabled,
/**
* Get a list of all example flows provided by nodes in the registry.
* @return {Object} an object, indexed by module, listing all example flows
*
* @function
* @memberof @node-red/registry
*/
getNodeExampleFlows: library.getExampleFlows,
/**
* Gets the full path to a node example
* @param {String} module - the name of the module providing the example
* @param {String} path - the relative path of the example
* @return {String} the full path to the example
*
* @function
* @memberof @node-red/registry
*/
getNodeExampleFlowPath: library.getExampleFlowPath,
checkFlowDependencies: externalModules.checkFlowDependencies,
registerPlugin: plugins.registerPlugin,
getPlugin: plugins.getPlugin,
getPluginsByType: plugins.getPluginsByType,
getPluginList: plugins.getPluginList,
getPluginConfigs: plugins.getPluginConfigs,
exportPluginSettings: plugins.exportPluginSettings,
deprecated: require("./deprecated")
};

364

lib/installer.js

@@ -18,19 +18,16 @@ /**

var path = require("path");
var os = require("os");
var fs = require("fs-extra");
var tar = require("tar");
const path = require("path");
const os = require("os");
const fs = require("fs-extra");
const tar = require("tar");
var registry = require("./registry");
var library = require("./library");
var log;
var exec;
const registry = require("./registry");
const registryUtil = require("./util");
const library = require("./library");
const {exec,log,events} = require("@node-red/util");
const child_process = require('child_process');
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
let installerEnabled = false;
var events;
var child_process = require('child_process');
var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
var paletteEditorEnabled = false;
var settings;
let settings;
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;

@@ -41,7 +38,32 @@ const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;

function init(runtime) {
events = runtime.events;
settings = runtime.settings;
log = runtime.log;
exec = runtime.exec;
// Default allow/deny lists
let installAllowList = ['*'];
let installDenyList = [];
let installAllAllowed = true;
let installVersionRestricted = false;
function init(_settings) {
settings = _settings;
// TODO: This is duplicated in localfilesystem.js
// Should it *all* be managed by util?
if (settings.externalModules && settings.externalModules.palette) {
if (settings.externalModules.palette.allowList || settings.externalModules.palette.denyList) {
installAllowList = settings.externalModules.palette.allowList;
installDenyList = settings.externalModules.palette.denyList;
}
}
installAllowList = registryUtil.parseModuleList(installAllowList);
installDenyList = registryUtil.parseModuleList(installDenyList);
installAllAllowed = installDenyList.length === 0;
if (!installAllAllowed) {
installAllowList.forEach(function(rule) {
installVersionRestricted = installVersionRestricted || (!!rule.version);
})
if (!installVersionRestricted) {
installDenyList.forEach(function(rule) {
installVersionRestricted = installVersionRestricted || (!!rule.version);
})
}
}
}

@@ -77,17 +99,3 @@

}
function checkExistingModule(module,version) {
var info = registry.getModuleInfo(module);
if (info) {
if (!version || info.version === version) {
var err = new Error("Module already loaded");
err.code = "module_already_loaded";
throw err;
}
return true;
}
return false;
}
function installModule(module,version,url) {
async function installModule(module,version,url) {
if (Buffer.isBuffer(module)) {

@@ -97,84 +105,117 @@ return installTarball(module)

module = module || "";
activePromise = activePromise.then(() => {
activePromise = activePromise.then(async function() {
//TODO: ensure module is 'safe'
return new Promise((resolve,reject) => {
var installName = module;
var isUpgrade = false;
try {
if (url) {
if (pkgurlRe.test(url) || localtgzRe.test(url)) {
// Git remote url or Tarball url - check the valid package url
installName = url;
} else {
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
const e = new Error("Invalid url");
e.code = "invalid_module_url";
reject(e);
return;
}
} else if (moduleRe.test(module)) {
// Simple module name - assume it can be npm installed
if (version) {
installName += "@"+version;
}
} else if (slashRe.test(module)) {
// A path - check if there's a valid package.json
installName = module;
let info = checkModulePath(module);
module = info.name;
} else {
log.warn(log._("server.install.install-failed-name",{name:module}));
const e = new Error("Invalid module name");
e.code = "invalid_module_name";
reject(e);
return;
}
isUpgrade = checkExistingModule(module,version);
} catch(err) {
return reject(err);
var installName = module;
let isRegistryPackage = true;
var isUpgrade = false;
var isExisting = false;
if (url) {
if (pkgurlRe.test(url) || localtgzRe.test(url)) {
// Git remote url or Tarball url - check the valid package url
installName = url;
isRegistryPackage = false;
} else {
log.warn(log._("server.install.install-failed-url",{name:module,url:url}));
const e = new Error("Invalid url");
e.code = "invalid_module_url";
throw e;
}
if (!isUpgrade) {
log.info(log._("server.install.installing",{name: module,version: version||"latest"}));
} else if (moduleRe.test(module)) {
// Simple module name - assume it can be npm installed
if (version) {
installName += "@"+version;
}
} else if (slashRe.test(module)) {
// A path - check if there's a valid package.json
installName = module;
let info = checkModulePath(module);
module = info.name;
isRegistryPackage = false;
} else {
log.warn(log._("server.install.install-failed-name",{name:module}));
const e = new Error("Invalid module name");
e.code = "invalid_module_name";
throw e;
}
if (!installAllAllowed) {
let installVersion = version;
if (installVersionRestricted && isRegistryPackage) {
installVersion = await getModuleVersionFromNPM(module, version);
}
if (!registryUtil.checkModuleAllowed(module,installVersion,installAllowList,installDenyList)) {
const e = new Error("Install not allowed");
e.code = "install_not_allowed";
throw e;
}
}
var info = registry.getModuleInfo(module);
if (info) {
if (!info.user) {
log.debug(`Installing existing module: ${module}`)
isExisting = true;
} else if (!version || info.version === version) {
var err = new Error("Module already loaded");
err.code = "module_already_loaded";
throw err;
}
isUpgrade = true;
} else {
isUpgrade = false;
}
if (!isUpgrade) {
log.info(log._("server.install.installing",{name: module,version: version||"latest"}));
} else {
log.info(log._("server.install.upgrading",{name: module,version: version||"latest"}));
}
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 => {
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
// in package.json and has been hidden from the editor.
// The user has requested to install this module. Having run
// the npm install above, it will now be listed in package.json.
// Update the registry to mark it as a user module so it will
// be available to the editor.
log.info(log._("server.install.installed",{name:module}));
return require("./registry").setUserInstalled(module,true).then(reportAddedModules);
} else if (!isUpgrade) {
log.info(log._("server.install.installed",{name:module}));
return require("./index").addModule(module).then(reportAddedModules);
} else {
log.info(log._("server.install.upgrading",{name: module,version: version||"latest"}));
log.info(log._("server.install.upgraded",{name:module, version:version}));
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
return require("./registry").setModulePendingUpdated(module,version);
}
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));
exec.run(npmCommand,args,{
cwd: installDir
}, true).then(result => {
if (!isUpgrade) {
log.info(log._("server.install.installed",{name:module}));
resolve(require("./index").addModule(module).then(reportAddedModules));
} else {
log.info(log._("server.install.upgraded",{name:module, version:version}));
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
resolve(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;
reject(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;
reject(e);
} else {
log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------");
log.warn(output);
log.warn("------------------------------------------");
reject(new Error(log._("server.install.install-failed")));
}
})
});
}).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 {
log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------");
log.warn(output);
log.warn("------------------------------------------");
throw new Error(log._("server.install.install-failed"));
}
})
}).catch(err => {

@@ -189,3 +230,2 @@ // In case of error, reset activePromise to be resolvable

function reportAddedModules(info) {
//comms.publish("node/added",info.nodes,false);
if (info.nodes.length > 0) {

@@ -229,3 +269,59 @@ log.info(log._("server.added-types"));

async function getModuleVersionFromNPM(module, version) {
let installName = module;
if (version) {
installName += "@" + version;
}
return new Promise((resolve, reject) => {
child_process.execFile(npmCommand,['info','--json',installName],function(err,stdout,stderr) {
try {
if (!stdout) {
log.warn(log._("server.install.install-failed-not-found",{name:module}));
e = new Error("Version not found");
e.code = 404;
reject(e);
return;
}
const response = JSON.parse(stdout);
if (response.error) {
if (response.error.code === "E404") {
log.warn(log._("server.install.install-failed-not-found",{name:module}));
e = new Error("Module not found");
e.code = 404;
reject(e);
} else {
log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------");
log.warn(response.error.summary);
log.warn("------------------------------------------");
reject(new Error(log._("server.install.install-failed")));
}
return;
} else {
resolve(response.version);
}
} catch(err) {
log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------");
if (stdout) {
log.warn(stdout);
}
if (stderr) {
log.warn(stderr);
}
log.warn(err);
log.warn("------------------------------------------");
reject(new Error(log._("server.install.install-failed")));
}
});
})
}
async function installTarball(tarball) {
if (settings.externalModules && settings.externalModules.palette && settings.externalModules.palette.allowUpload === false) {
throw new Error("Module upload disabled")
}
// Check this tarball contains a valid node-red module.

@@ -351,3 +447,28 @@ // Get its module name/version

function checkPrereq() {
async function checkPrereq() {
if (settings.editorTheme && settings.editorTheme.palette) {
if (settings.editorTheme.palette.hasOwnProperty("editable")) {
log.warn(log._("server.deprecatedOption",{old:"editorTheme.palette.editable", new:"externalModules.palette.allowInstall"}));
}
if (settings.editorTheme.palette.hasOwnProperty("upload")) {
log.warn(log._("server.deprecatedOption",{old:"editorTheme.palette.upload", new:"externalModules.palette.allowUpload"}));
}
}
try {
if (settings.editorTheme.palette.editable === false) {
log.info(log._("server.palette-editor.disabled"));
installerEnabled = false;
return
}
} catch(err) {}
try {
if (settings.externalModules.palette.allowInstall === false) {
log.info(log._("server.palette-editor.disabled"));
installerEnabled = false;
return
}
} catch(err) {}
if (settings.hasOwnProperty('editorTheme') &&

@@ -359,4 +480,3 @@ settings.editorTheme.hasOwnProperty('palette') &&

log.info(log._("server.palette-editor.disabled"));
paletteEditorEnabled = false;
return Promise.resolve();
installerEnabled = false;
} else {

@@ -367,9 +487,9 @@ return new Promise(resolve => {

log.info(log._("server.palette-editor.npm-not-found"));
paletteEditorEnabled = false;
installerEnabled = false;
} else {
if (parseInt(stdout.split(".")[0]) < 3) {
log.info(log._("server.palette-editor.npm-too-old"));
paletteEditorEnabled = false;
installerEnabled = false;
} else {
paletteEditorEnabled = true;
installerEnabled = true;
}

@@ -388,5 +508,5 @@ }

uninstallModule: uninstallModule,
paletteEditorEnabled: function() {
return paletteEditorEnabled
installerEnabled: function() {
return installerEnabled
}
}

@@ -17,3 +17,3 @@ /**

var fs = require('fs');
var fs = require('fs-extra');
var fspath = require('path');

@@ -26,36 +26,32 @@

function getFlowsFromPath(path) {
return new Promise(function(resolve,reject) {
var result = {};
fs.readdir(path,function(err,files) {
var promises = [];
var validFiles = [];
if (files) {
files.forEach(function(file) {
var fullPath = fspath.join(path,file);
var stats = fs.lstatSync(fullPath);
if (stats.isDirectory()) {
validFiles.push(file);
promises.push(getFlowsFromPath(fullPath));
} else if (/\.json$/.test(file)){
validFiles.push(file);
promises.push(Promise.resolve(file.split(".")[0]))
}
})
async function getFlowsFromPath(path) {
var result = {};
var validFiles = [];
return fs.readdir(path).then(files => {
var promises = [];
if (files) {
files.forEach(function(file) {
var fullPath = fspath.join(path,file);
var stats = fs.lstatSync(fullPath);
if (stats.isDirectory()) {
validFiles.push(file);
promises.push(getFlowsFromPath(fullPath));
} else if (/\.json$/.test(file)){
validFiles.push(file);
promises.push(Promise.resolve(file.split(".")[0]))
}
})
}
return Promise.all(promises)
}).then(results => {
results.forEach(function(r,i) {
if (typeof r === 'string') {
result.f = result.f||[];
result.f.push(r);
} else {
result.d = result.d||{};
result.d[validFiles[i]] = r;
}
var i=0;
Promise.all(promises).then(function(results) {
results.forEach(function(r) {
if (typeof r === 'string') {
result.f = result.f||[];
result.f.push(r);
} else {
result.d = result.d||{};
result.d[validFiles[i]] = r;
}
i++;
})
resolve(result);
})
});
})
return result;
})

@@ -62,0 +58,0 @@ }

@@ -17,3 +17,2 @@ /**

var when = require("when");
var fs = require("fs-extra");

@@ -27,11 +26,10 @@ var path = require("path");

var i18n = require("@node-red/util").i18n;
var log = require("@node-red/util").log;
var settings;
var runtime;
function init(_runtime) {
runtime = _runtime;
settings = runtime.settings;
localfilesystem.init(runtime);
registryUtil.init(runtime);
settings = _runtime.settings;
localfilesystem.init(settings);
registryUtil.init(_runtime);
}

@@ -43,60 +41,102 @@

// performance gains are minimal.
//return loadNodeFiles(registry.getModuleList());
runtime.log.info(runtime.log._("server.loading"));
//return loadModuleFiles(registry.getModuleList());
log.info(log._("server.loading"));
var nodeFiles = localfilesystem.getNodeFiles(disableNodePathScan);
return loadNodeFiles(nodeFiles);
var modules = localfilesystem.getNodeFiles(disableNodePathScan);
return loadModuleFiles(modules);
}
function loadNodeFiles(nodeFiles) {
function loadModuleTypeFiles(module, type) {
const things = module[type];
var first = true;
var promises = [];
var nodes = [];
for (var module in nodeFiles) {
for (var thingName in things) {
/* istanbul ignore else */
if (nodeFiles.hasOwnProperty(module)) {
if (nodeFiles[module].redVersion &&
!semver.satisfies(runtime.version().replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), nodeFiles[module].redVersion)) {
if (things.hasOwnProperty(thingName)) {
if (module.name != "node-red" && first) {
// Check the module directory exists
first = false;
var fn = things[thingName].file;
var parts = fn.split("/");
var i = parts.length-1;
for (;i>=0;i--) {
if (parts[i] == "node_modules") {
break;
}
}
var moduleFn = parts.slice(0,i+2).join("/");
try {
var stat = fs.statSync(moduleFn);
} catch(err) {
// Module not found, don't attempt to load its nodes
break;
}
}
try {
var promise;
if (type === "nodes") {
promise = loadNodeConfig(things[thingName]);
} else if (type === "plugins") {
promise = loadPluginConfig(things[thingName]);
}
promises.push(
promise.then(
(function() {
var m = module.name;
var n = thingName;
return function(nodeSet) {
things[n] = nodeSet;
return nodeSet;
}
})()
).catch(err => {console.log(err)})
);
} catch(err) {
console.log(err)
//
}
}
}
return promises;
}
function loadModuleFiles(modules) {
var pluginPromises = [];
var nodePromises = [];
for (var module in modules) {
/* istanbul ignore else */
if (modules.hasOwnProperty(module)) {
if (modules[module].redVersion &&
!semver.satisfies((settings.version||"0.0.0").replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), modules[module].redVersion)) {
//TODO: log it
runtime.log.warn("["+module+"] "+runtime.log._("server.node-version-mismatch",{version:nodeFiles[module].redVersion}));
nodeFiles[module].err = "version_mismatch";
log.warn("["+module+"] "+log._("server.node-version-mismatch",{version:modules[module].redVersion}));
modules[module].err = "version_mismatch";
continue;
}
if (module == "node-red" || !registry.getModuleInfo(module)) {
var first = true;
for (var node in nodeFiles[module].nodes) {
/* istanbul ignore else */
if (nodeFiles[module].nodes.hasOwnProperty(node)) {
if (module != "node-red" && first) {
// Check the module directory exists
first = false;
var fn = nodeFiles[module].nodes[node].file;
var parts = fn.split("/");
var i = parts.length-1;
for (;i>=0;i--) {
if (parts[i] == "node_modules") {
break;
}
}
var moduleFn = parts.slice(0,i+2).join("/");
if (modules[module].nodes) {
nodePromises = nodePromises.concat(loadModuleTypeFiles(modules[module], "nodes"));
}
if (modules[module].plugins) {
pluginPromises = pluginPromises.concat(loadModuleTypeFiles(modules[module], "plugins"));
}
}
}
}
var pluginList;
var nodeList;
try {
var stat = fs.statSync(moduleFn);
} catch(err) {
// Module not found, don't attempt to load its nodes
break;
}
}
try {
promises.push(loadNodeConfig(nodeFiles[module].nodes[node]).then((function() {
var m = module;
var n = node;
return function(nodeSet) {
nodeFiles[m].nodes[n] = nodeSet;
nodes.push(nodeSet);
}
})()));
} catch(err) {
//
}
return Promise.all(pluginPromises).then(function(results) {
pluginList = results.filter(r => !!r);
// Initial plugin load has happened. Ensure modules that provide
// plugins are in the registry now.
for (var module in modules) {
if (modules.hasOwnProperty(module)) {
if (modules[module].plugins && Object.keys(modules[module].plugins).length > 0) {
// Add the modules for plugins
if (!modules[module].err) {
registry.addModule(modules[module]);
}

@@ -106,15 +146,35 @@ }

}
}
return when.settle(promises).then(function(results) {
for (var module in nodeFiles) {
if (nodeFiles.hasOwnProperty(module)) {
if (!nodeFiles[module].err) {
registry.addModule(nodeFiles[module]);
return loadNodeSetList(pluginList);
}).then(function() {
return Promise.all(nodePromises);
}).then(function(results) {
nodeList = results.filter(r => !!r);
// Initial node load has happened. Ensure remaining modules are in the registry
for (var module in modules) {
if (modules.hasOwnProperty(module)) {
if (!modules[module].plugins || Object.keys(modules[module].plugins).length === 0) {
if (!modules[module].err) {
registry.addModule(modules[module]);
}
}
}
}
return loadNodeSetList(nodes);
return loadNodeSetList(nodeList);
});
}
async function loadPluginTemplate(plugin) {
return fs.readFile(plugin.template,'utf8').then(content => {
plugin.config = content;
return plugin;
}).catch(err => {
if (err.code === 'ENOENT') {
plugin.err = "Error: "+plugin.template+" does not exist";
} else {
plugin.err = err.toString();
}
return plugin;
});
}
async function loadNodeTemplate(node) {

@@ -167,9 +227,6 @@ return fs.readFile(node.template,'utf8').then(content => {

}).catch(err => {
node.types = [];
if (err.code === 'ENOENT') {
if (!node.types) {
node.types = [];
}
node.err = "Error: "+node.template+" does not exist";
} else {
// ENOENT means no html file. We can live with that. But any other error
// should be fatal
// node.err = "Error: "+node.template+" does not exist";
if (err.code !== 'ENOENT') {
node.types = [];

@@ -188,7 +245,8 @@ node.err = err.toString();

}
return fs.stat(path.join(path.dirname(node.file),"locales")).then(stat => {
const baseFile = node.file||node.template;
return fs.stat(path.join(path.dirname(baseFile),"locales")).then(stat => {
node.namespace = node.id;
return i18n.registerMessageCatalog(node.id,
path.join(path.dirname(node.file),"locales"),
path.basename(node.file,".js")+".json")
path.join(path.dirname(baseFile),"locales"),
path.basename(baseFile).replace(/\.[^.]+$/,".json"))
.then(() => node);

@@ -218,2 +276,3 @@ }).catch(err => {

var node = {
type: "node",
id: id,

@@ -242,2 +301,54 @@ module: module,

async function loadPluginConfig(fileInfo) {
var file = fileInfo.file;
var module = fileInfo.module;
var name = fileInfo.name;
var version = fileInfo.version;
var id = module + "/" + name;
var isEnabled = true;
// TODO: registry.getPluginInfo
// var info = registry.getPluginInfo(id);
// if (info) {
// if (info.hasOwnProperty("loaded")) {
// throw new Error(file+" already loaded");
// }
// isEnabled = info.enabled;
// }
if (!fs.existsSync(jsFile)) {
}
var plugin = {
type: "plugin",
id: id,
module: module,
name: name,
enabled: isEnabled,
loaded:false,
version: version,
local: fileInfo.local,
plugins: [],
config: "",
help: {}
};
var jsFile = file.replace(/\.[^.]+$/,".js");
var htmlFile = file.replace(/\.[^.]+$/,".html");
if (fs.existsSync(jsFile)) {
plugin.file = jsFile;
}
if (fs.existsSync(htmlFile)) {
plugin.template = htmlFile;
}
await loadNodeLocales(plugin)
if (plugin.template && !settings.disableEditor) {
return loadPluginTemplate(plugin);
}
return plugin
}
/**

@@ -252,4 +363,2 @@ * Loads the specified node into the runtime

function loadNodeSet(node) {
var nodeDir = path.dirname(node.file);
var nodeFn = path.basename(node.file);
if (!node.enabled) {

@@ -301,2 +410,46 @@ return Promise.resolve(node);

async function loadPlugin(plugin) {
if (!plugin.file) {
// No runtime component - nothing to load
return plugin;
}
try {
var r = require(plugin.file);
if (typeof r === "function") {
var red = registryUtil.createNodeApi(plugin);
var promise = r(red);
if (promise != null && typeof promise.then === "function") {
return promise.then(function() {
plugin.enabled = true;
plugin.loaded = true;
return plugin;
}).catch(function(err) {
plugin.err = err;
return plugin;
});
}
}
plugin.enabled = true;
plugin.loaded = true;
return plugin;
} catch(err) {
console.log(err);
plugin.err = err;
var stack = err.stack;
var message;
if (stack) {
var i = stack.indexOf(plugin.file);
if (i > -1) {
var excerpt = stack.substring(i+node.file.length+1,i+plugin.file.length+20);
var m = /^(\d+):(\d+)/.exec(excerpt);
if (m) {
plugin.err = err+" (line:"+m[1]+")";
}
}
}
return plugin;
}
}
function loadNodeSetList(nodes) {

@@ -306,3 +459,7 @@ var promises = [];

if (!node.err) {
promises.push(loadNodeSet(node));
if (node.type === "plugin") {
promises.push(loadPlugin(node).catch(err => {}));
} else {
promises.push(loadNodeSet(node).catch(err => {}));
}
} else {

@@ -313,3 +470,3 @@ promises.push(node);

return when.settle(promises).then(function() {
return Promise.all(promises).then(function() {
if (settings.available()) {

@@ -328,3 +485,4 @@ return registry.saveNodeList();

var nodes = [];
if (registry.getModuleInfo(module)) {
var existingInfo = registry.getModuleInfo(module);
if (existingInfo) {
// TODO: nls

@@ -336,4 +494,25 @@ var e = new Error("module_already_loaded");

try {
var moduleFiles = localfilesystem.getModuleFiles(module);
return loadNodeFiles(moduleFiles);
var moduleFiles = {};
var moduleStack = [module];
while(moduleStack.length > 0) {
var moduleToLoad = moduleStack.shift();
var files = localfilesystem.getModuleFiles(moduleToLoad);
if (files[moduleToLoad]) {
moduleFiles[moduleToLoad] = files[moduleToLoad];
if (moduleFiles[moduleToLoad].dependencies) {
log.debug(`Loading dependencies for ${module}`)
for (var i=0; i<moduleFiles[moduleToLoad].dependencies.length; i++) {
var dep = moduleFiles[moduleToLoad].dependencies[i]
if (!registry.getModuleInfo(dep)) {
log.debug(` - load ${dep}`)
moduleStack.push(dep);
} else {
log.debug(` - already loaded ${dep}`)
registry.addModuleDependency(dep,moduleToLoad)
}
}
}
}
}
return loadModuleFiles(moduleFiles).then(() => module)
} catch(err) {

@@ -340,0 +519,0 @@ return Promise.reject(err);

@@ -17,10 +17,12 @@ /**

var fs = require("fs");
var path = require("path");
const fs = require("fs");
const path = require("path");
const log = require("@node-red/util").log;
const i18n = require("@node-red/util").i18n;
const registryUtil = require("./util");
var events;
var log;
// Default allow/deny lists
let loadAllowList = ['*'];
let loadDenyList = [];
var log = require("@node-red/util").log;
var i18n = require("@node-red/util").i18n;

@@ -30,6 +32,16 @@ var settings;

var iconFileExtensions = [".png", ".gif", ".svg"];
var packageList = {};
function init(runtime) {
settings = runtime.settings;
events = runtime.events;
function init(_settings) {
settings = _settings;
// TODO: This is duplicated in installer.js
// Should it *all* be managed by util?
if (settings.externalModules && settings.externalModules.palette) {
if (settings.externalModules.palette.allowList || settings.externalModules.palette.denyList) {
loadAllowList = settings.externalModules.palette.allowList;
loadDenyList = settings.externalModules.palette.denyList;
}
}
loadAllowList = registryUtil.parseModuleList(loadAllowList);
loadDenyList = registryUtil.parseModuleList(loadDenyList);
}

@@ -80,3 +92,2 @@

* Synchronously walks the directory looking for node files.
* Emits 'node-icon-dir' events for an icon dirs found
* @param dir the directory to search

@@ -149,4 +160,8 @@ * @return an array of fully-qualified paths to .js files

if (pkg['node-red']) {
var moduleDir = path.join(dir,fn);
results.push({dir:moduleDir,package:pkg});
if (!registryUtil.checkModuleAllowed(pkg.name,pkg.version,loadAllowList,loadDenyList)) {
log.debug("! Module: "+pkg.name+" "+pkg.version+ " *ignored due to denyList*");
} else {
var moduleDir = path.join(dir,fn);
results.push({dir:moduleDir,package:pkg});
}
}

@@ -180,5 +195,13 @@ } catch(err) {

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];
});
}

@@ -205,30 +228,38 @@

var nodes = pkg['node-red'].nodes||{};
var results = [];
var iconDirs = [];
var iconList = [];
for (var n in nodes) {
/* istanbul ignore else */
if (nodes.hasOwnProperty(n)) {
var file = path.join(moduleDir,nodes[n]);
results.push({
file: file,
module: pkg.name,
name: n,
version: pkg.version
});
var iconDir = path.join(moduleDir,path.dirname(nodes[n]),"icons");
if (iconDirs.indexOf(iconDir) == -1) {
try {
fs.statSync(iconDir);
var icons = scanIconDir(iconDir);
iconList.push({path:iconDir,icons:icons});
iconDirs.push(iconDir);
} catch(err) {
function scanTypes(types) {
const files = [];
for (var n in types) {
/* istanbul ignore else */
if (types.hasOwnProperty(n)) {
var file = path.join(moduleDir,types[n]);
files.push({
file: file,
module: pkg.name,
name: n,
version: pkg.version
});
var iconDir = path.join(moduleDir,path.dirname(types[n]),"icons");
if (iconDirs.indexOf(iconDir) == -1) {
try {
fs.statSync(iconDir);
var icons = scanIconDir(iconDir);
iconList.push({path:iconDir,icons:icons});
iconDirs.push(iconDir);
} catch(err) {
}
}
}
}
return files;
}
var result = {files:results,icons:iconList};
var result = {
nodeFiles:scanTypes(pkg['node-red'].nodes||{}),
pluginFiles:scanTypes(pkg['node-red'].plugins||{}),
icons:iconList
};
var examplesDir = path.join(moduleDir,"examples");

@@ -238,3 +269,2 @@ try {

result.examples = {path:examplesDir};
// events.emit("node-examples-dir",{name:pkg.name,path:examplesDir});
} catch(err) {

@@ -285,16 +315,17 @@ }

var coreNodeEntry = {
name: "node-red",
version: settings.version,
nodes: {},
icons: iconList
}
var nodeList = {
"node-red": {
name: "node-red",
version: settings.version,
nodes: {},
icons: iconList
}
}
"node-red": coreNodeEntry
};
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};
}

@@ -308,3 +339,2 @@

// update a module they may not otherwise be able to touch
moduleFiles.sort(function(A,B) {

@@ -322,3 +352,3 @@ if (A.local && !B.local) {

if (!knownModules[mod.package.name]) {
knownModules[mod.package.name] = true;
knownModules[mod.package.name] = mod;
result = true;

@@ -328,27 +358,30 @@ } else {

}
log.debug("Module: "+mod.package.name+" "+mod.package.version+(result?"":" *ignored due to local copy*"));
log.debug(" "+mod.dir);
log.debug((result?"":"! ")+"Module: "+mod.package.name+" "+mod.package.version+" "+mod.dir+(result?"":" *ignored due to local copy*"));
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, nodeList);
} else {

@@ -361,4 +394,3 @@ // console.log("node path scan disabled");

function getModuleFiles(module) {
var nodeList = {};
// Update the package list
var moduleFiles = scanTreeForNodesModules(module);

@@ -370,4 +402,12 @@ if (moduleFiles.length === 0) {

}
// 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,seedObject) {
const nodeList = seedObject || {};
moduleFiles.forEach(function(moduleFile) {
var nodeModuleFiles = getModuleNodeFiles(moduleFile);

@@ -378,3 +418,6 @@ nodeList[moduleFile.package.name] = {

path: moduleFile.dir,
local: moduleFile.local||false,
user: moduleFile.user||false,
nodes: {},
plugins: {},
icons: nodeModuleFiles.icons,

@@ -386,6 +429,16 @@ examples: nodeModuleFiles.examples

}
nodeModuleFiles.files.forEach(function(node) {
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.nodeFiles.forEach(function(node) {
nodeList[moduleFile.package.name].nodes[node.name] = node;
nodeList[moduleFile.package.name].nodes[node.name].local = moduleFile.local || false;
});
nodeModuleFiles.pluginFiles.forEach(function(plugin) {
nodeList[moduleFile.package.name].plugins[plugin.name] = plugin;
nodeList[moduleFile.package.name].plugins[plugin.name].local = moduleFile.local || false;
});
});

@@ -418,2 +471,19 @@ return nodeList;

}
/**
* 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;
}

@@ -420,0 +490,0 @@ module.exports = {

@@ -22,4 +22,5 @@ /**

var library = require("./library");
var events;
const {events} = require("@node-red/util")
var subflows = require("./subflow");
var externalModules = require("./externalModules")
var settings;

@@ -32,14 +33,12 @@ var loader;

var nodeConstructors = {};
var nodeOptions = {};
var subflowModules = {};
var nodeTypeToId = {};
var moduleNodes = {};
function init(_settings,_loader, _events) {
function init(_settings,_loader) {
settings = _settings;
loader = _loader;
events = _events;
moduleNodes = {};
nodeTypeToId = {};
nodeConstructors = {};
nodeList = [];
nodeConfigCache = {};
clear();
}

@@ -61,3 +60,4 @@

enabled: n.enabled,
local: n.local||false
local: n.local||false,
user: n.user || false
};

@@ -70,2 +70,9 @@ if (n.hasOwnProperty("module")) {

}
if (n.hasOwnProperty("plugins")) {
r.plugins = n.plugins;
}
if (n.type === "plugin") {
r.editor = !!n.template;
r.runtime = !!n.file;
}
return r;

@@ -76,3 +83,3 @@ }

function getModule(id) {
function getModuleFromSetId(id) {
var parts = id.split("/");

@@ -82,3 +89,3 @@ return parts.slice(0,parts.length-1).join("/");

function getNode(id) {
function getNodeFromSetId(id) {
var parts = id.split("/");

@@ -101,2 +108,3 @@ return parts[parts.length-1];

local: moduleConfigs[module].local||false,
user: moduleConfigs[module].user||false,
nodes: {}

@@ -187,2 +195,3 @@ };

moduleConfigs[module.name] = module;
// console.log("registry.js.addModule",module.name,"user?",module.user,"usedBy",module.usedBy,"dependencies",module.dependencies)
for (var setName in module.nodes) {

@@ -226,7 +235,7 @@ if (module.nodes.hasOwnProperty(setName)) {

function removeNode(id) {
var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
var config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
if (!config) {
throw new Error("Unrecognised id: "+id);
}
delete moduleConfigs[getModule(id)].nodes[getNode(id)];
delete moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
var i = nodeList.indexOf(id);

@@ -239,3 +248,5 @@ if (i > -1) {

if (typeId === id) {
delete subflowModules[t];
delete nodeConstructors[t];
delete nodeOptions[t];
delete nodeTypeToId[t];

@@ -250,17 +261,43 @@ }

function removeModule(module) {
function removeModule(name,skipSave) {
if (!settings.available()) {
throw new Error("Settings unavailable");
}
var nodes = moduleNodes[module];
var infoList = [];
var module = moduleConfigs[name];
var nodes = moduleNodes[name];
if (!nodes) {
throw new Error("Unrecognised module: "+module);
throw new Error("Unrecognised module: "+name);
}
var infoList = [];
for (var i=0;i<nodes.length;i++) {
infoList.push(removeNode(module+"/"+nodes[i]));
if (module.usedBy && module.usedBy > 0) {
// We are removing a module that is used by other modules... so whilst
// this module should be removed from the editor palette, it needs to
// stay in the runtime... for now.
module.user = false;
for (var i=0;i<nodes.length;i++) {
infoList.push(filterNodeInfo(nodes[i]));
}
} else {
if (module.dependencies) {
module.dependencies.forEach(function(dep) {
// Check each dependency of this module to see if it is a non-user-installed
// module that we can expect to disappear once npm uninstall is run
if (!moduleConfigs[dep].user) {
moduleConfigs[dep].usedBy = moduleConfigs[dep].usedBy.filter(m => m !== name);
if (moduleConfigs[dep].usedBy.length === 0) {
// Remove the dependency
removeModule(dep,true);
}
}
});
}
for (var i=0;i<nodes.length;i++) {
infoList.push(removeNode(name+"/"+nodes[i]));
}
delete moduleNodes[name];
delete moduleConfigs[name];
}
delete moduleNodes[module];
delete moduleConfigs[module];
saveNodeList();
if (!skipSave) {
saveNodeList();
}
return infoList;

@@ -276,5 +313,5 @@ }

if (id) {
var module = moduleConfigs[getModule(id)];
var module = moduleConfigs[getModuleFromSetId(id)];
if (module) {
var config = module.nodes[getNode(id)];
var config = module.nodes[getNodeFromSetId(id)];
if (config) {

@@ -306,5 +343,5 @@ var info = filterNodeInfo(config);

if (id) {
var module = moduleConfigs[getModule(id)];
var module = moduleConfigs[getModuleFromSetId(id)];
if (module) {
return module.nodes[getNode(id)];
return module.nodes[getNodeFromSetId(id)];
}

@@ -320,2 +357,5 @@ }

if (moduleConfigs.hasOwnProperty(module)) {
if (!moduleConfigs[module].user && (moduleConfigs[module].usedBy && moduleConfigs[module].usedBy.length > 0)) {
continue;
}
var nodes = moduleConfigs[module].nodes;

@@ -341,13 +381,7 @@ for (var node in nodes) {

function getModuleList() {
//var list = [];
//for (var module in moduleNodes) {
// /* istanbul ignore else */
// if (moduleNodes.hasOwnProperty(module)) {
// list.push(registry.getModuleInfo(module));
// }
//}
//return list;
return moduleConfigs;
}
function getModule(id) {
return moduleConfigs[id];
}

@@ -361,5 +395,9 @@ function getModuleInfo(module) {

local: moduleConfigs[module].local,
user: moduleConfigs[module].user,
path: moduleConfigs[module].path,
nodes: []
};
if (moduleConfigs[module].dependencies) {
m.dependencies = moduleConfigs[module].dependencies;
}
if (moduleConfigs[module] && moduleConfigs[module].pending_version) {

@@ -391,3 +429,3 @@ m.pending_version = moduleConfigs[module].pending_version;

function registerNodeConstructor(nodeSet,type,constructor) {
function registerNodeConstructor(nodeSet,type,constructor,options) {
if (nodeConstructors.hasOwnProperty(type)) {

@@ -412,5 +450,35 @@ throw new Error(type+" already registered");

nodeConstructors[type] = constructor;
nodeOptions[type] = options;
if (options) {
if (options.dynamicModuleList) {
externalModules.register(type,options.dynamicModuleList);
}
}
events.emit("type-registered",type);
}
function registerSubflow(nodeSet, subflow) {
var nodeSetInfo = getFullNodeInfo(nodeSet);
const result = subflows.register(nodeSet,subflow);
if (subflowModules.hasOwnProperty(result.type)) {
throw new Error(result.type+" already registered");
}
if (nodeSetInfo) {
if (nodeSetInfo.types.indexOf(result.type) === -1) {
nodeSetInfo.types.push(result.type);
nodeTypeToId[result.type] = nodeSetInfo.id;
}
nodeSetInfo.config = result.config;
}
subflowModules[result.type] = result;
externalModules.registerSubflow(result.type,subflow);
events.emit("type-registered",result.type);
return result;
}
function getAllNodeConfigs(lang) {

@@ -422,3 +490,7 @@ if (!nodeConfigCache[lang]) {

var id = nodeList[i];
var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
var module = moduleConfigs[getModuleFromSetId(id)]
if (!module.user && (module.usedBy && module.usedBy.length > 0)) {
continue;
}
var config = module.nodes[getNodeFromSetId(id)];
if (config.enabled && !config.err) {

@@ -442,7 +514,7 @@ result += "\n<!-- --- [red-module:"+id+"] --- -->\n";

function getNodeConfig(id,lang) {
var config = moduleConfigs[getModule(id)];
var config = moduleConfigs[getModuleFromSetId(id)];
if (!config) {
return null;
}
config = config.nodes[getNode(id)];
config = config.nodes[getNodeFromSetId(id)];
if (config) {

@@ -468,7 +540,7 @@ var result = "<!-- --- [red-module:"+id+"] --- -->\n"+config.config;

} else {
config = moduleConfigs[getModule(id)].nodes[getNode(id)];
config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
}
if (!config || (config.enabled && !config.err)) {
return nodeConstructors[type];
return nodeConstructors[type] || subflowModules[type];
}

@@ -483,2 +555,4 @@ return null;

nodeConstructors = {};
nodeOptions = {};
subflowModules = {};
nodeTypeToId = {};

@@ -506,3 +580,3 @@ }

try {
config = moduleConfigs[getModule(id)].nodes[getNode(id)];
config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
delete config.err;

@@ -530,3 +604,3 @@ config.enabled = true;

try {
config = moduleConfigs[getModule(id)].nodes[getNode(id)];
config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
// TODO: persist setting

@@ -589,2 +663,13 @@ config.enabled = false;

function setUserInstalled(module,userInstalled) {
moduleConfigs[module].user = userInstalled;
return saveNodeList().then(function() {
return getModuleInfo(module);
});
}
function addModuleDependency(module,usedBy) {
moduleConfigs[module].usedBy = moduleConfigs[module].usedBy || [];
moduleConfigs[module].usedBy.push(usedBy);
}
var icon_paths = { };

@@ -643,2 +728,3 @@ var iconCache = {};

registerSubflow: registerSubflow,

@@ -651,2 +737,5 @@ addModule: addModule,

setModulePendingUpdated: setModulePendingUpdated,
setUserInstalled: setUserInstalled,
addModuleDependency:addModuleDependency,
removeModule: removeModule,

@@ -658,2 +747,3 @@

getModuleList: getModuleList,
getModule: getModule,
getModuleInfo: getModuleInfo,

@@ -674,3 +764,6 @@

cleanModuleList: cleanModuleList
cleanModuleList: cleanModuleList,
getModuleFromSetId: getModuleFromSetId,
getNodeFromSetId: getNodeFromSetId,
filterNodeInfo: filterNodeInfo
};

@@ -17,5 +17,6 @@ /**

var path = require("path");
var i18n = require("@node-red/util").i18n;
var registry;
const path = require("path");
const semver = require("semver");
const {events,i18n,log} = require("@node-red/util");
var runtime;

@@ -44,3 +45,3 @@

function requireModule(name) {
var moduleInfo = registry.getModuleInfo(name);
var moduleInfo = require("./index").getModuleInfo(name);
if (moduleInfo && moduleInfo.path) {

@@ -50,5 +51,4 @@ var relPath = path.relative(__dirname, moduleInfo.path);

} else {
var err = new Error(`Cannot find module '${name}'`);
err.code = "MODULE_NOT_FOUND";
throw err;
// Require it here to avoid the circular dependency
return require("./externalModules").require(name);
}

@@ -62,3 +62,3 @@ }

settings: {},
events: runtime.events,
events: events,
hooks: runtime.hooks,

@@ -70,3 +70,3 @@ util: runtime.util,

publish: function(topic,data,retain) {
runtime.events.emit("comms",{
events.emit("comms",{
topic: topic,

@@ -78,2 +78,13 @@ data: data,

},
plugins: {
registerPlugin: function(id,definition) {
return runtime.plugins.registerPlugin(node.id,id,definition);
},
get: function(id) {
return runtime.plugins.getPlugin(id);
},
getByType: function(type) {
return runtime.plugins.getPluginsByType(type);
}
},
library: {

@@ -88,7 +99,10 @@ register: function(type) {

}
copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials" ]);
copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials"]);
red.nodes.registerType = function(type,constructor,opts) {
runtime.nodes.registerType(node.id,type,constructor,opts);
}
copyObjectProperties(runtime.log,red.log,null,["init"]);
red.nodes.registerSubflow = function(subflowDef) {
runtime.nodes.registerSubflow(node.id,subflowDef)
}
copyObjectProperties(log,red.log,null,["init"]);
copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]);

@@ -115,8 +129,75 @@ if (runtime.adminApi) {

function checkAgainstList(module,version,list) {
for (let i=0;i<list.length;i++) {
let rule = list[i];
if (rule.module.test(module)) {
if (version && rule.version) {
if (semver.satisfies(version,rule.version)) {
return rule;
}
} else {
return rule;
}
}
}
}
function checkModuleAllowed(module,version,allowList,denyList) {
if (!allowList && !denyList) {
// Default to allow
return true;
}
if (allowList.length === 0 && denyList.length === 0) {
return true;
}
var allowedRule = checkAgainstList(module,version,allowList);
var deniedRule = checkAgainstList(module,version,denyList);
// console.log("A",allowedRule)
// console.log("D",deniedRule)
if (allowedRule && !deniedRule) {
return true;
}
if (!allowedRule && deniedRule) {
return false;
}
if (!allowedRule && !deniedRule) {
return true;
}
if (allowedRule.wildcardPos !== deniedRule.wildcardPos) {
return allowedRule.wildcardPos > deniedRule.wildcardPos
} else {
// First wildcard in same position.
// Go with the longer matching rule. This isn't going to be 100%
// right, but we are deep into edge cases at this point.
return allowedRule.module.toString().length > deniedRule.module.toString().length
}
return false;
}
function parseModuleList(list) {
list = list || ["*"];
return list.map(rule => {
let m = /^(.+?)(?:@(.*))?$/.exec(rule);
let wildcardPos = m[1].indexOf("*");
wildcardPos = wildcardPos===-1?Infinity:wildcardPos;
return {
module: new RegExp("^"+m[1].replace(/\*/g,".*")+"$"),
version: m[2],
wildcardPos: wildcardPos
}
})
}
module.exports = {
init: function(_runtime) {
runtime = _runtime;
registry = require("@node-red/registry/lib");
},
createNodeApi: createNodeApi
createNodeApi: createNodeApi,
parseModuleList: parseModuleList,
checkModuleAllowed: checkModuleAllowed
}
{
"name": "@node-red/registry",
"version": "1.2.9",
"version": "1.3.0-beta.1",
"license": "Apache-2.0",

@@ -19,8 +19,7 @@ "main": "./lib/index.js",

"dependencies": {
"@node-red/util": "1.2.9",
"@node-red/util": "1.3.0-beta.1",
"semver": "6.3.0",
"tar": "6.0.5",
"uglify-js": "3.12.4",
"when": "3.7.8"
"uglify-js": "3.12.4"
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc