From 9a660f3fe9ba932d8273461e87264f3c78552c1e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 28 Aug 2020 16:36:11 +0100 Subject: [PATCH] Support npm subflow modules --- .../editor-client/src/js/ui/editor.js | 31 +- .../editor-client/src/js/ui/subflow.js | 60 +++- .../@node-red/registry/lib/index.js | 2 + .../@node-red/registry/lib/loader.js | 11 +- .../@node-red/registry/lib/registry.js | 30 +- .../@node-red/registry/lib/subflow.js | 117 ++++++ .../@node-red/registry/lib/util.js | 3 + .../@node-red/runtime/lib/flows/Flow.js | 20 +- .../@node-red/runtime/lib/flows/Subflow.js | 7 +- .../@node-red/runtime/lib/flows/util.js | 340 ++++++++++-------- .../@node-red/runtime/lib/nodes/index.js | 20 ++ .../@node-red/registry/lib/loader_spec.js | 109 +++--- .../@node-red/registry/lib/subflow_spec.js | 3 + 13 files changed, 499 insertions(+), 254 deletions(-) create mode 100644 packages/node_modules/@node-red/registry/lib/subflow.js create mode 100644 test/unit/@node-red/registry/lib/subflow_spec.js diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 4488f86c0..b712d3e99 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -414,18 +414,20 @@ RED.editor = (function() { for (var cred in credDefinition) { if (credDefinition.hasOwnProperty(cred)) { var input = $("#" + prefix + '-' + cred); - var value = input.val(); - if (credDefinition[cred].type == 'password') { - node.credentials['has_' + cred] = (value !== ""); - if (value == '__PWRD__') { - continue; - } - changed = true; + if (input.length > 0) { + var value = input.val(); + if (credDefinition[cred].type == 'password') { + node.credentials['has_' + cred] = (value !== ""); + if (value == '__PWRD__') { + continue; + } + changed = true; - } - node.credentials[cred] = value; - if (value != node.credentials._[cred]) { - changed = true; + } + node.credentials[cred] = value; + if (value != node.credentials._[cred]) { + changed = true; + } } } } @@ -465,6 +467,7 @@ RED.editor = (function() { definition.oneditprepare.call(node); } catch(err) { console.log("oneditprepare",node.id,node.type,err.toString()); + console.log(err.stack); } } // Now invoke any change handlers added to the fields - passing true @@ -1192,7 +1195,7 @@ RED.editor = (function() { changed = true; } } catch(err) { - console.log("oneditsave",editing_node.id,editing_node.type,err.toString()); + console.warn("oneditsave",editing_node.id,editing_node.type,err.toString()); } for (d in editing_node._def.defaults) { @@ -1890,7 +1893,7 @@ RED.editor = (function() { try { configTypeDef.oneditsave.call(editing_config_node); } catch(err) { - console.log("oneditsave",editing_config_node.id,editing_config_node.type,err.toString()); + console.warn("oneditsave",editing_config_node.id,editing_config_node.type,err.toString()); } } @@ -2457,7 +2460,7 @@ RED.editor = (function() { changed = true; } } catch(err) { - console.log("oneditsave",editing_node.id,editing_node.type,err.toString()); + console.warn("oneditsave",editing_node.id,editing_node.type,err.toString()); } for (d in editing_node._def.defaults) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 3f4dc9d74..49434f6a9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -1725,22 +1725,54 @@ RED.subflow = (function() { parentEnv[env.name] = item; }) } - } - - if (node.env) { - for (var i = 0; i < node.env.length; i++) { - var env = node.env[i]; - if (parentEnv.hasOwnProperty(env.name)) { - parentEnv[env.name].type = env.type; - parentEnv[env.name].value = env.value; - } else { - // envList.push({ - // name: env.name, - // type: env.type, - // value: env.value, - // }); + if (node.env) { + for (var i = 0; i < node.env.length; i++) { + var env = node.env[i]; + if (parentEnv.hasOwnProperty(env.name)) { + parentEnv[env.name].type = env.type; + parentEnv[env.name].value = env.value; + } else { + // envList.push({ + // name: env.name, + // type: env.type, + // value: env.value, + // }); + } } } + } else if (/^sf-/.test(node.type)) { + var keys = Object.keys(node._def.defaults); + keys.forEach(function(name) { + if (name !== 'name') { + var prop = node._def.defaults[name]; + var nodeProp = node[name]; + var nodePropType; + var nodePropValue = nodeProp; + if (prop.ui && prop.ui.type === "cred") { + nodePropType = "cred"; + } else { + switch(typeof nodeProp) { + case "string": nodePropType = "str"; break; + case "number": nodePropType = "num"; break; + case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break; + default: + nodePropType = nodeProp.type; + nodePropValue = nodeProp.value; + } + } + var item = { + name: name, + type: nodePropType, + value: nodePropValue, + parent: { + type: prop.type, + value: prop.value + }, + ui: $.extend(true,{},prop.ui) + } + envList.push(item); + } + }) } return envList; } diff --git a/packages/node_modules/@node-red/registry/lib/index.js b/packages/node_modules/@node-red/registry/lib/index.js index 426dfcb61..03f979424 100644 --- a/packages/node_modules/@node-red/registry/lib/index.js +++ b/packages/node_modules/@node-red/registry/lib/index.js @@ -99,6 +99,8 @@ module.exports = { */ get: registry.getNodeConstructor, + registerSubflow: registry.registerSubflow, + /** * Get a node's set information. * diff --git a/packages/node_modules/@node-red/registry/lib/loader.js b/packages/node_modules/@node-red/registry/lib/loader.js index 18d202420..a2f737c30 100644 --- a/packages/node_modules/@node-red/registry/lib/loader.js +++ b/packages/node_modules/@node-red/registry/lib/loader.js @@ -158,13 +158,10 @@ async function loadNodeTemplate(node) { } return node }).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 = []; node.err = err.toString(); } diff --git a/packages/node_modules/@node-red/registry/lib/registry.js b/packages/node_modules/@node-red/registry/lib/registry.js index a91735071..159ec96da 100644 --- a/packages/node_modules/@node-red/registry/lib/registry.js +++ b/packages/node_modules/@node-red/registry/lib/registry.js @@ -20,6 +20,7 @@ var fs = require("fs"); var library = require("./library"); const {events} = require("@node-red/util") +var subflows = require("./subflow"); var settings; var loader; @@ -27,6 +28,8 @@ var nodeConfigCache = {}; var moduleConfigs = {}; var nodeList = []; var nodeConstructors = {}; +var subflowModules = {}; + var nodeTypeToId = {}; var moduleNodes = {}; @@ -36,6 +39,7 @@ function init(_settings,_loader) { moduleNodes = {}; nodeTypeToId = {}; nodeConstructors = {}; + subflowModules = {}; nodeList = []; nodeConfigCache = {}; } @@ -225,6 +229,7 @@ function removeNode(id) { config.types.forEach(function(t) { var typeId = nodeTypeToId[t]; if (typeId === id) { + delete subflowModules[t]; delete nodeConstructors[t]; delete nodeTypeToId[t]; } @@ -393,6 +398,27 @@ function registerNodeConstructor(nodeSet,type,constructor) { 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; + events.emit("type-registered",result.type); + return result; +} + function getAllNodeConfigs(lang) { if (!nodeConfigCache[lang]) { var result = ""; @@ -447,7 +473,7 @@ function getNodeConstructor(type) { } if (!config || (config.enabled && !config.err)) { - return nodeConstructors[type]; + return nodeConstructors[type] || subflowModules[type]; } return null; } @@ -457,6 +483,7 @@ function clear() { moduleConfigs = {}; nodeList = []; nodeConstructors = {}; + subflowModules = {}; nodeTypeToId = {}; } @@ -613,6 +640,7 @@ var registry = module.exports = { registerNodeConstructor: registerNodeConstructor, getNodeConstructor: getNodeConstructor, + registerSubflow: registerSubflow, addModule: addModule, diff --git a/packages/node_modules/@node-red/registry/lib/subflow.js b/packages/node_modules/@node-red/registry/lib/subflow.js new file mode 100644 index 000000000..a398d3607 --- /dev/null +++ b/packages/node_modules/@node-red/registry/lib/subflow.js @@ -0,0 +1,117 @@ +function getSubflowType(subflow) { + return "sf-"+subflow.id +} + +function generateSubflowConfig(subflow) { + + const subflowType = getSubflowType(subflow) + const label = subflow.name || subflowType; + const category = subflow.category || "function"; + const color = subflow.color || "#C0DEED"; + const inputCount = subflow.in?subflow.in.length:0; + const outputCount = subflow.out?subflow.out.length:0; + const icon = subflow.icon || "arrow-in.svg"; + + const defaults = { + name: {value: ""} + } + + const credentials = {} + + if (subflow.env) { + subflow.env.forEach(prop => { + var defaultValue; + + switch(prop.type) { + case "cred": defaultValue = ""; break; + case "str": defaultValue = prop.value||""; break; + case "bool": defaultValue = (typeof prop.value === 'boolean')?prop.value:prop.value === "true" ; break; + case "num": defaultValue = (typeof prop.value === 'number')?prop.value:Number(prop.value); break; + default: + defaultValue = { + type: prop.type, + value: prop.value||"" + } + } + + + + defaults[prop.name] = { + value: defaultValue, + ui: prop.ui + } + if (prop.type === 'cred') { + defaults[prop.name].ui.type = "cred"; + credentials[prop.name] = {type:"password"} + } + }) + } + const defaultString = JSON.stringify(defaults); + const credentialsString = JSON.stringify(credentials); + + return ` + +` +} + + +function register(id,subflow) { + return { + subflow: subflow, + type: getSubflowType(subflow), + config: generateSubflowConfig(subflow) + } +} + +module.exports = { + register: register +} \ No newline at end of file diff --git a/packages/node_modules/@node-red/registry/lib/util.js b/packages/node_modules/@node-red/registry/lib/util.js index 6e7609fd5..94690d6cb 100644 --- a/packages/node_modules/@node-red/registry/lib/util.js +++ b/packages/node_modules/@node-red/registry/lib/util.js @@ -83,6 +83,9 @@ function createNodeApi(node) { red.nodes.registerType = function(type,constructor,opts) { runtime.nodes.registerType(node.id,type,constructor,opts); } + 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"]); if (runtime.adminApi) { diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js index 307da6d62..dbccf3a3d 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js @@ -303,21 +303,13 @@ class Flow { if (node) { delete this.activeNodes[stopList[i]]; if (this.subflowInstanceNodes[stopList[i]]) { - try { - (function(subflow) { - promises.push(stopNode(node,false).then(() => subflow.stop())); - })(this.subflowInstanceNodes[stopList[i]]); - } catch(err) { - node.error(err); - } delete this.subflowInstanceNodes[stopList[i]]; - } else { - try { - var removed = removedMap[stopList[i]]; - promises.push(stopNode(node,removed).catch(()=>{})); - } catch(err) { - node.error(err); - } + } + try { + var removed = removedMap[stopList[i]]; + promises.push(stopNode(node,removed).catch(()=>{})); + } catch(err) { + node.error(err); } if (removedMap[stopList[i]]) { events.emit("node-status",{ diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js index 87facdc23..84477b3f7 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js @@ -208,7 +208,12 @@ class Subflow extends Flow { this.node = new Node(subflowInstanceConfig); this.node.on("input", function(msg) { this.send(msg);}); - this.node.on("close", function() { this.status({}); }) + // Called when the subflow instance node is being stopped + this.node.on("close", function(done) { + this.status({}); + // Stop the complete subflow + self.stop().finally(done) + }) this.node.status = status => this.parent.handleStatus(this.node,status); // Create a context instance // console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/util.js b/packages/node_modules/@node-red/runtime/lib/flows/util.js index d5a8cba9f..4c4cdebcb 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/util.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/util.js @@ -19,6 +19,7 @@ var Log = require("@node-red/util").log; var subflowInstanceRE = /^subflow:(.+)$/; var typeRegistry = require("@node-red/registry"); + var envVarExcludes = {}; function diffNodes(oldNode,newNode) { @@ -66,6 +67,194 @@ function mapEnvVarProperties(obj,prop,flow) { } } + +function createNode(flow,config) { + var newNode = null; + var type = config.type; + try { + var nodeTypeConstructor = typeRegistry.get(type); + if (typeof nodeTypeConstructor === "function") { + var conf = clone(config); + delete conf.credentials; + for (var p in conf) { + if (conf.hasOwnProperty(p)) { + mapEnvVarProperties(conf,p,flow); + } + } + try { + Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true }) + newNode = new nodeTypeConstructor(conf); + } catch (err) { + Log.log({ + level: Log.ERROR, + id:conf.id, + type: type, + msg: err + }); + } + } else if (nodeTypeConstructor) { + // console.log(nodeTypeConstructor) + var subflowConfig = parseConfig([nodeTypeConstructor.subflow].concat(nodeTypeConstructor.subflow.flow)); + var instanceConfig = clone(config); + instanceConfig.env = clone(nodeTypeConstructor.subflow.env); + + instanceConfig.env = nodeTypeConstructor.subflow.env.map(nodeProp => { + var nodePropType; + var nodePropValue = config[nodeProp.name]; + if (nodeProp.type === "cred") { + nodePropType = "cred"; + } else { + switch(typeof config[nodeProp.name]) { + case "string": nodePropType = "str"; break; + case "number": nodePropType = "num"; break; + case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break; + default: + nodePropType = config[nodeProp.name].type; + nodePropValue = config[nodeProp.name].value; + } + } + return { + name: nodeProp.name, + type: nodePropType, + value: nodePropValue + } + }) + + var subflow = require("./Subflow").create( + flow, + flow.global, + subflowConfig.subflows[nodeTypeConstructor.subflow.id], + instanceConfig + ); + subflow.start(); + return subflow.node; + + Log.error(Log._("nodes.flow.unknown-type", {type:type})); + } + } catch(err) { + Log.error(err); + } + return newNode; +} + +function parseConfig(config) { + var flow = {}; + flow.allNodes = {}; + flow.subflows = {}; + flow.configs = {}; + flow.flows = {}; + flow.groups = {}; + flow.missingTypes = []; + + config.forEach(function(n) { + flow.allNodes[n.id] = clone(n); + if (n.type === 'tab') { + flow.flows[n.id] = n; + flow.flows[n.id].subflows = {}; + flow.flows[n.id].configs = {}; + flow.flows[n.id].nodes = {}; + } + if (n.type === 'group') { + flow.groups[n.id] = n; + } + }); + + // TODO: why a separate forEach? this can be merged with above + config.forEach(function(n) { + if (n.type === 'subflow') { + flow.subflows[n.id] = n; + flow.subflows[n.id].configs = {}; + flow.subflows[n.id].nodes = {}; + flow.subflows[n.id].instances = []; + } + }); + var linkWires = {}; + var linkOutNodes = []; + config.forEach(function(n) { + if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { + var subflowDetails = subflowInstanceRE.exec(n.type); + + if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) { + if (flow.missingTypes.indexOf(n.type) === -1) { + flow.missingTypes.push(n.type); + } + } + var container = null; + if (flow.flows[n.z]) { + container = flow.flows[n.z]; + } else if (flow.subflows[n.z]) { + container = flow.subflows[n.z]; + } + if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) { + if (subflowDetails) { + var subflowType = subflowDetails[1] + n.subflow = subflowType; + flow.subflows[subflowType].instances.push(n) + } + if (container) { + container.nodes[n.id] = n; + } + } else { + if (container) { + container.configs[n.id] = n; + } else { + flow.configs[n.id] = n; + flow.configs[n.id]._users = []; + } + } + if (n.type === 'link in' && n.links) { + // Ensure wires are present in corresponding link out nodes + n.links.forEach(function(id) { + linkWires[id] = linkWires[id]||{}; + linkWires[id][n.id] = true; + }) + } else if (n.type === 'link out' && n.links) { + linkWires[n.id] = linkWires[n.id]||{}; + n.links.forEach(function(id) { + linkWires[n.id][id] = true; + }) + linkOutNodes.push(n); + } + } + }); + linkOutNodes.forEach(function(n) { + var links = linkWires[n.id]; + var targets = Object.keys(links); + n.wires = [targets]; + }); + + + var addedTabs = {}; + config.forEach(function(n) { + if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { + for (var prop in n) { + if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) { + // This property references a global config node + flow.configs[n[prop]]._users.push(n.id) + } + } + if (n.z && !flow.subflows[n.z]) { + + if (!flow.flows[n.z]) { + flow.flows[n.z] = {type:'tab',id:n.z}; + flow.flows[n.z].subflows = {}; + flow.flows[n.z].configs = {}; + flow.flows[n.z].nodes = {}; + addedTabs[n.z] = flow.flows[n.z]; + } + if (addedTabs[n.z]) { + if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) { + addedTabs[n.z].nodes[n.id] = n; + } else { + addedTabs[n.z].configs[n.id] = n; + } + } + } + } + }); + return flow; +} + module.exports = { init: function(runtime) { envVarExcludes = {}; @@ -79,123 +268,7 @@ module.exports = { diffNodes: diffNodes, mapEnvVarProperties: mapEnvVarProperties, - parseConfig: function(config) { - var flow = {}; - flow.allNodes = {}; - flow.subflows = {}; - flow.configs = {}; - flow.flows = {}; - flow.groups = {}; - flow.missingTypes = []; - - config.forEach(function(n) { - flow.allNodes[n.id] = clone(n); - if (n.type === 'tab') { - flow.flows[n.id] = n; - flow.flows[n.id].subflows = {}; - flow.flows[n.id].configs = {}; - flow.flows[n.id].nodes = {}; - } - if (n.type === 'group') { - flow.groups[n.id] = n; - } - }); - - // TODO: why a separate forEach? this can be merged with above - config.forEach(function(n) { - if (n.type === 'subflow') { - flow.subflows[n.id] = n; - flow.subflows[n.id].configs = {}; - flow.subflows[n.id].nodes = {}; - flow.subflows[n.id].instances = []; - } - }); - var linkWires = {}; - var linkOutNodes = []; - config.forEach(function(n) { - if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { - var subflowDetails = subflowInstanceRE.exec(n.type); - - if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) { - if (flow.missingTypes.indexOf(n.type) === -1) { - flow.missingTypes.push(n.type); - } - } - var container = null; - if (flow.flows[n.z]) { - container = flow.flows[n.z]; - } else if (flow.subflows[n.z]) { - container = flow.subflows[n.z]; - } - if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) { - if (subflowDetails) { - var subflowType = subflowDetails[1] - n.subflow = subflowType; - flow.subflows[subflowType].instances.push(n) - } - if (container) { - container.nodes[n.id] = n; - } - } else { - if (container) { - container.configs[n.id] = n; - } else { - flow.configs[n.id] = n; - flow.configs[n.id]._users = []; - } - } - if (n.type === 'link in' && n.links) { - // Ensure wires are present in corresponding link out nodes - n.links.forEach(function(id) { - linkWires[id] = linkWires[id]||{}; - linkWires[id][n.id] = true; - }) - } else if (n.type === 'link out' && n.links) { - linkWires[n.id] = linkWires[n.id]||{}; - n.links.forEach(function(id) { - linkWires[n.id][id] = true; - }) - linkOutNodes.push(n); - } - } - }); - linkOutNodes.forEach(function(n) { - var links = linkWires[n.id]; - var targets = Object.keys(links); - n.wires = [targets]; - }); - - - var addedTabs = {}; - config.forEach(function(n) { - if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { - for (var prop in n) { - if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) { - // This property references a global config node - flow.configs[n[prop]]._users.push(n.id) - } - } - if (n.z && !flow.subflows[n.z]) { - - if (!flow.flows[n.z]) { - flow.flows[n.z] = {type:'tab',id:n.z}; - flow.flows[n.z].subflows = {}; - flow.flows[n.z].configs = {}; - flow.flows[n.z].nodes = {}; - addedTabs[n.z] = flow.flows[n.z]; - } - if (addedTabs[n.z]) { - if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) { - addedTabs[n.z].nodes[n.id] = n; - } else { - addedTabs[n.z].configs[n.id] = n; - } - } - } - } - }); - return flow; - }, + parseConfig: parseConfig, diffConfigs: function(oldConfig, newConfig) { var id; @@ -475,36 +548,5 @@ module.exports = { * @param {object} config The node configuration object * @return {Node} The instance of the node */ - createNode: function(flow,config) { - var newNode = null; - var type = config.type; - try { - var nodeTypeConstructor = typeRegistry.get(type); - if (nodeTypeConstructor) { - var conf = clone(config); - delete conf.credentials; - for (var p in conf) { - if (conf.hasOwnProperty(p)) { - mapEnvVarProperties(conf,p,flow); - } - } - try { - Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true }) - newNode = new nodeTypeConstructor(conf); - } catch (err) { - Log.log({ - level: Log.ERROR, - id:conf.id, - type: type, - msg: err - }); - } - } else { - Log.error(Log._("nodes.flow.unknown-type", {type:type})); - } - } catch(err) { - Log.error(err); - } - return newNode; - } + createNode: createNode } diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js index 7c6b6085e..2bc7fd61e 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js @@ -112,6 +112,25 @@ function createNode(node,def) { } } +function registerSubflow(nodeSet, subflow) { + // TODO: extract credentials definition from subflow properties + var registeredType = registry.registerSubflow(nodeSet,subflow); + + if (subflow.env) { + var creds = {}; + var hasCreds = false; + subflow.env.forEach(e => { + if (e.type === "cred") { + creds[e.name] = {type: "password"}; + hasCreds = true; + } + }) + if (hasCreds) { + credentials.register(registeredType.type,creds); + } + } +} + function init(runtime) { settings = runtime.settings; log = runtime.log; @@ -196,6 +215,7 @@ module.exports = { // Node type registry registerType: registerType, + registerSubflow: registerSubflow, getType: registry.get, getNodeInfo: registry.getNodeInfo, diff --git a/test/unit/@node-red/registry/lib/loader_spec.js b/test/unit/@node-red/registry/lib/loader_spec.js index d62a8a9b4..274a20d44 100644 --- a/test/unit/@node-red/registry/lib/loader_spec.js +++ b/test/unit/@node-red/registry/lib/loader_spec.js @@ -414,60 +414,61 @@ describe("red/nodes/registry/loader",function() { }); }); - it("load core node files scanned by lfs - missing html file", function(done) { - stubs.push(sinon.stub(localfilesystem,"getNodeFiles", function(){ - var result = {}; - result["node-red"] = { - "name": "node-red", - "version": "1.2.3", - "nodes": { - "DuffNode": { - "file": path.join(resourcesDir,"DuffNode","DuffNode.js"), - "module": "node-red", - "name": "DuffNode" - } - } - }; - return result; - })); - - stubs.push(sinon.stub(registry,"saveNodeList", function(){ return })); - stubs.push(sinon.stub(registry,"addModule", function(){ return })); - // This module isn't already loaded - stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; })); - - stubs.push(sinon.stub(nodes,"registerType")); - loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); - loader.load().then(function(result) { - - registry.addModule.called.should.be.true(); - var module = registry.addModule.lastCall.args[0]; - module.should.have.property("name","node-red"); - module.should.have.property("version","1.2.3"); - module.should.have.property("nodes"); - module.nodes.should.have.property("DuffNode"); - module.nodes.DuffNode.should.have.property("id","node-red/DuffNode"); - module.nodes.DuffNode.should.have.property("module","node-red"); - module.nodes.DuffNode.should.have.property("name","DuffNode"); - module.nodes.DuffNode.should.have.property("file"); - module.nodes.DuffNode.should.have.property("template"); - module.nodes.DuffNode.should.have.property("enabled",true); - module.nodes.DuffNode.should.have.property("loaded",false); - module.nodes.DuffNode.should.have.property("types"); - module.nodes.DuffNode.types.should.have.a.length(0); - module.nodes.DuffNode.should.have.property("config",""); - module.nodes.DuffNode.should.have.property("help",{}); - module.nodes.DuffNode.should.have.property("namespace","node-red"); - module.nodes.DuffNode.should.have.property('err'); - module.nodes.DuffNode.err.should.endWith("DuffNode.html does not exist"); - - nodes.registerType.called.should.be.false(); - - done(); - }).catch(function(err) { - done(err); - }); - }); + // it("load core node files scanned by lfs - missing html file", function(done) { + // // This is now an okay situation + // stubs.push(sinon.stub(localfilesystem,"getNodeFiles", function(){ + // var result = {}; + // result["node-red"] = { + // "name": "node-red", + // "version": "1.2.3", + // "nodes": { + // "DuffNode": { + // "file": path.join(resourcesDir,"DuffNode","DuffNode.js"), + // "module": "node-red", + // "name": "DuffNode" + // } + // } + // }; + // return result; + // })); + // + // stubs.push(sinon.stub(registry,"saveNodeList", function(){ return })); + // stubs.push(sinon.stub(registry,"addModule", function(){ return })); + // // This module isn't already loaded + // stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; })); + // + // stubs.push(sinon.stub(nodes,"registerType")); + // loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); + // loader.load().then(function(result) { + // + // registry.addModule.called.should.be.true(); + // var module = registry.addModule.lastCall.args[0]; + // module.should.have.property("name","node-red"); + // module.should.have.property("version","1.2.3"); + // module.should.have.property("nodes"); + // module.nodes.should.have.property("DuffNode"); + // module.nodes.DuffNode.should.have.property("id","node-red/DuffNode"); + // module.nodes.DuffNode.should.have.property("module","node-red"); + // module.nodes.DuffNode.should.have.property("name","DuffNode"); + // module.nodes.DuffNode.should.have.property("file"); + // module.nodes.DuffNode.should.have.property("template"); + // module.nodes.DuffNode.should.have.property("enabled",true); + // module.nodes.DuffNode.should.have.property("loaded",false); + // module.nodes.DuffNode.should.have.property("types"); + // module.nodes.DuffNode.types.should.have.a.length(0); + // module.nodes.DuffNode.should.have.property("config",""); + // module.nodes.DuffNode.should.have.property("help",{}); + // module.nodes.DuffNode.should.have.property("namespace","node-red"); + // module.nodes.DuffNode.should.have.property('err'); + // module.nodes.DuffNode.err.should.endWith("DuffNode.html does not exist"); + // + // nodes.registerType.called.should.be.false(); + // + // done(); + // }).catch(function(err) { + // done(err); + // }); + // }); }); describe("#addModule",function() { diff --git a/test/unit/@node-red/registry/lib/subflow_spec.js b/test/unit/@node-red/registry/lib/subflow_spec.js new file mode 100644 index 000000000..2aa7db203 --- /dev/null +++ b/test/unit/@node-red/registry/lib/subflow_spec.js @@ -0,0 +1,3 @@ +describe("red/nodes/registry/subflow",function() { + it.skip("NEEDS TESTS"); +});