1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge branch 'master' into http-request-form-array

This commit is contained in:
Ben Hardill 2022-12-24 20:37:33 +00:00 committed by GitHub
commit 4c1d7ad2d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 396 additions and 88 deletions

View File

@ -5,6 +5,9 @@ on:
release: release:
types: [published] types: [published]
permissions:
contents: read
jobs: jobs:
generate: generate:
name: 'Update node-red-docker image' name: 'Update node-red-docker image'

View File

@ -6,8 +6,14 @@ on:
pull_request: pull_request:
branches: [ master, dev ] branches: [ master, dev ]
permissions:
contents: read
jobs: jobs:
build: build:
permissions:
checks: write # for coverallsapp/github-action to create new checks
contents: read # for actions/checkout to fetch code
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -1965,7 +1965,7 @@ RED.nodes = (function() {
} }
} }
} else { } else {
const keepNodesCurrentZ = reimport && n.z && RED.workspaces.contains(n.z) const keepNodesCurrentZ = reimport && n.z && (RED.workspaces.contains(n.z) || RED.nodes.subflow(n.z))
if (!keepNodesCurrentZ && n.z && !workspace_map[n.z] && !subflow_map[n.z]) { if (!keepNodesCurrentZ && n.z && !workspace_map[n.z] && !subflow_map[n.z]) {
n.z = activeWorkspace; n.z = activeWorkspace;
} }
@ -2067,7 +2067,7 @@ RED.nodes = (function() {
node.id = getID(); node.id = getID();
} else { } else {
node.id = n.id; node.id = n.id;
const keepNodesCurrentZ = reimport && node.z && RED.workspaces.contains(node.z) const keepNodesCurrentZ = reimport && node.z && (RED.workspaces.contains(node.z) || RED.nodes.subflow(node.z))
if (!keepNodesCurrentZ && (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z]))) { if (!keepNodesCurrentZ && (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z]))) {
if (createMissingWorkspace) { if (createMissingWorkspace) {
if (missingWorkspace === null) { if (missingWorkspace === null) {
@ -2740,6 +2740,7 @@ RED.nodes = (function() {
} }
}); });
const nodeGroupMap = {}
var replaceNodeIds = Object.keys(replaceNodes); var replaceNodeIds = Object.keys(replaceNodes);
if (replaceNodeIds.length > 0) { if (replaceNodeIds.length > 0) {
var reimportList = []; var reimportList = [];
@ -2750,6 +2751,12 @@ RED.nodes = (function() {
} else { } else {
allNodes.removeNode(n); allNodes.removeNode(n);
} }
if (n.g) {
// reimporting a node *without* including its group object
// will cause the g property to be cleared. Cache it
// here so we can restore it
nodeGroupMap[n.id] = n.g
}
reimportList.push(convertNode(n)); reimportList.push(convertNode(n));
RED.events.emit('nodes:remove',n); RED.events.emit('nodes:remove',n);
}); });
@ -2771,6 +2778,18 @@ RED.nodes = (function() {
var newNodeMap = {}; var newNodeMap = {};
result.nodes.forEach(function(n) { result.nodes.forEach(function(n) {
newNodeMap[n.id] = n; newNodeMap[n.id] = n;
if (nodeGroupMap[n.id]) {
// This node is in a group - need to substitute the
// node reference inside the group
n.g = nodeGroupMap[n.id]
const group = RED.nodes.group(n.g)
if (group) {
var index = group.nodes.findIndex(gn => gn.id === n.id)
if (index > -1) {
group.nodes[index] = n
}
}
}
}); });
RED.nodes.eachLink(function(l) { RED.nodes.eachLink(function(l) {
if (newNodeMap.hasOwnProperty(l.source.id)) { if (newNodeMap.hasOwnProperty(l.source.id)) {

View File

@ -146,7 +146,7 @@
{ value: "reset", source: ["delay","trigger","join","rbe"] }, { value: "reset", source: ["delay","trigger","join","rbe"] },
{ value: "responseCookies", source: ["http request"] }, { value: "responseCookies", source: ["http request"] },
{ value: "responseTopic", source: ["mqtt"] }, { value: "responseTopic", source: ["mqtt"] },
{ value: "responseURL", source: ["http request"] }, { value: "responseUrl", source: ["http request"] },
{ value: "restartTimeout", source: ["join"] }, { value: "restartTimeout", source: ["join"] },
{ value: "retain", source: ["mqtt"] }, { value: "retain", source: ["mqtt"] },
{ value: "schema", source: ["json"] }, { value: "schema", source: ["json"] },

View File

@ -238,6 +238,7 @@ RED.editor = (function() {
var valid = validateNodeProperty(node, defaults, property,value); var valid = validateNodeProperty(node, defaults, property,value);
if (((typeof valid) === "string") || !valid) { if (((typeof valid) === "string") || !valid) {
input.addClass("input-error"); input.addClass("input-error");
input.next(".red-ui-typedInput-container").addClass("input-error");
if ((typeof valid) === "string") { if ((typeof valid) === "string") {
var tooltip = input.data("tooltip"); var tooltip = input.data("tooltip");
if (tooltip) { if (tooltip) {
@ -250,6 +251,7 @@ RED.editor = (function() {
} }
} else { } else {
input.removeClass("input-error"); input.removeClass("input-error");
input.next(".red-ui-typedInput-container").removeClass("input-error");
var tooltip = input.data("tooltip"); var tooltip = input.data("tooltip");
if (tooltip) { if (tooltip) {
input.data("tooltip", null); input.data("tooltip", null);

0
packages/node_modules/@node-red/editor-client/src/js/ui/library.js vendored Executable file → Normal file
View File

0
packages/node_modules/@node-red/editor-client/src/js/ui/palette.js vendored Executable file → Normal file
View File

View File

3
packages/node_modules/@node-red/editor-client/src/js/ui/view.js vendored Executable file → Normal file
View File

@ -3369,6 +3369,9 @@ RED.view = (function() {
} }
if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) { if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) {
mouse_mode = RED.state.DEFAULT; mouse_mode = RED.state.DEFAULT;
// Avoid dbl click causing text selection.
d3.event.preventDefault()
document.getSelection().removeAllRanges()
if (d.type != "subflow") { if (d.type != "subflow") {
if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) { if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) {
RED.workspaces.show(d.type.substring(8)); RED.workspaces.show(d.type.substring(8));

View File

@ -4,7 +4,7 @@
<label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label> <label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label>
<select id="node-input-scope-select"> <select id="node-input-scope-select">
<option value="all" data-i18n="catch.scope.all"></option> <option value="all" data-i18n="catch.scope.all"></option>
<option value="target" data-i18n="catch.scope.selected"></options> <option value="target" data-i18n="catch.scope.selected"></option>
</select> </select>
</div> </div>
<div class="form-row node-input-uncaught-row"> <div class="form-row node-input-uncaught-row">

View File

@ -451,11 +451,13 @@
tabs.activateTab("func-tab-body"); tabs.activateTab("func-tab-body");
$( "#node-input-outputs" ).spinner({ $( "#node-input-outputs" ).spinner({
min:0, min: 0,
max: 500,
change: function(event, ui) { change: function(event, ui) {
var value = this.value; var value = parseInt(this.value);
if (!value.match(/^\d+$/)) { value = 1; } value = isNaN(value) ? 1 : value;
else if (value < this.min) { value = this.min; } value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
if (value !== this.value) { $(this).spinner("value", value); } if (value !== this.value) { $(this).spinner("value", value); }
} }
}); });

View File

@ -318,7 +318,7 @@ module.exports = function(RED) {
} }
var r = node.rules[currentRule]; var r = node.rules[currentRule];
if (r.t === "move") { if (r.t === "move") {
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) { if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1) && (r.p !== r.to)) {
applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt},(err,msg) => { applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt},(err,msg) => {
applyRule(msg,{t:"delete", p:r.p, pt:r.pt}, (err,msg) => { applyRule(msg,{t:"delete", p:r.p, pt:r.pt}, (err,msg) => {
completeApplyingRules(msg,currentRule,done); completeApplyingRules(msg,currentRule,done);

View File

@ -21,6 +21,7 @@
<option value="javascript">JavaScript</option> <option value="javascript">JavaScript</option>
<option value="css">CSS</option> <option value="css">CSS</option>
<option value="markdown">Markdown</option> <option value="markdown">Markdown</option>
<option value="php">PHP</option>
<option value="python">Python</option> <option value="python">Python</option>
<option value="sql">SQL</option> <option value="sql">SQL</option>
<option value="yaml">YAML</option> <option value="yaml">YAML</option>

View File

@ -421,7 +421,11 @@
<script type="text/javascript"> <script type="text/javascript">
(function() { (function() {
var typedInputNoneOpt = { value: 'none', label: '', hasValue: false }; var typedInputNoneOpt = {
value: 'none',
label: RED._("node-red:mqtt.label.none"),
hasValue: false
};
var makeTypedInputOpt = function(value){ var makeTypedInputOpt = function(value){
return { return {
value: value, value: value,
@ -436,7 +440,11 @@
makeTypedInputOpt("text/csv"), makeTypedInputOpt("text/csv"),
makeTypedInputOpt("text/html"), makeTypedInputOpt("text/html"),
makeTypedInputOpt("text/plain"), makeTypedInputOpt("text/plain"),
{value:"other", label:""} {
value: "other",
label: RED._("node-red:mqtt.label.other"),
icon: "red/images/typedInput/az.svg"
}
]; ];
function getDefaultContentType(value) { function getDefaultContentType(value) {
@ -499,17 +507,17 @@
cleansession: {value: true}, cleansession: {value: true},
birthTopic: {value:"", validate:validateMQTTPublishTopic}, birthTopic: {value:"", validate:validateMQTTPublishTopic},
birthQos: {value:"0"}, birthQos: {value:"0"},
birthRetain: {value:false}, birthRetain: {value:"false"},
birthPayload: {value:""}, birthPayload: {value:""},
birthMsg: { value: {}}, birthMsg: { value: {}},
closeTopic: {value:"", validate:validateMQTTPublishTopic}, closeTopic: {value:"", validate:validateMQTTPublishTopic},
closeQos: {value:"0"}, closeQos: {value:"0"},
closeRetain: {value:false}, closeRetain: {value:"false"},
closePayload: {value:""}, closePayload: {value:""},
closeMsg: { value: {}}, closeMsg: { value: {}},
willTopic: {value:"", validate:validateMQTTPublishTopic}, willTopic: {value:"", validate:validateMQTTPublishTopic},
willQos: {value:"0"}, willQos: {value:"0"},
willRetain: {value:false}, willRetain: {value:"false"},
willPayload: {value:""}, willPayload: {value:""},
willMsg: { value: {}}, willMsg: { value: {}},
userProps: { value: ""}, userProps: { value: ""},

View File

@ -366,6 +366,16 @@ module.exports = function(RED) {
} }
} }
function updateStatus(node, allNodes) {
let setStatus = setStatusDisconnected
if(node.connecting) {
setStatus = setStatusConnecting
} else if(node.connected) {
setStatus = setStatusConnected
}
setStatus(node, allNodes)
}
function setStatusDisconnected(node, allNodes) { function setStatusDisconnected(node, allNodes) {
if(allNodes) { if(allNodes) {
for (var id in node.users) { for (var id in node.users) {
@ -459,7 +469,6 @@ module.exports = function(RED) {
if(!opts || typeof opts !== "object") { if(!opts || typeof opts !== "object") {
return; //nothing to change, simply return return; //nothing to change, simply return
} }
const originalBrokerURL = node.brokerurl;
//apply property changes (only if the property exists in the opts object) //apply property changes (only if the property exists in the opts object)
setIfHasProperty(opts, node, "url", init); setIfHasProperty(opts, node, "url", init);
@ -468,7 +477,6 @@ module.exports = function(RED) {
setIfHasProperty(opts, node, "clientid", init); setIfHasProperty(opts, node, "clientid", init);
setIfHasProperty(opts, node, "autoConnect", init); setIfHasProperty(opts, node, "autoConnect", init);
setIfHasProperty(opts, node, "usetls", init); setIfHasProperty(opts, node, "usetls", init);
setIfHasProperty(opts, node, "usews", init);
setIfHasProperty(opts, node, "verifyservercert", init); setIfHasProperty(opts, node, "verifyservercert", init);
setIfHasProperty(opts, node, "compatmode", init); setIfHasProperty(opts, node, "compatmode", init);
setIfHasProperty(opts, node, "protocolVersion", init); setIfHasProperty(opts, node, "protocolVersion", init);
@ -571,9 +579,6 @@ module.exports = function(RED) {
if (typeof node.usetls === 'undefined') { if (typeof node.usetls === 'undefined') {
node.usetls = false; node.usetls = false;
} }
if (typeof node.usews === 'undefined') {
node.usews = false;
}
if (typeof node.verifyservercert === 'undefined') { if (typeof node.verifyservercert === 'undefined') {
node.verifyservercert = false; node.verifyservercert = false;
} }
@ -702,13 +707,17 @@ module.exports = function(RED) {
if (Object.keys(node.users).length === 1) { if (Object.keys(node.users).length === 1) {
if(node.autoConnect) { if(node.autoConnect) {
node.connect(); node.connect();
//update nodes status
setTimeout(function() {
updateStatus(node, true)
}, 1)
} }
} }
}; };
node.deregister = function(mqttNode,done) { node.deregister = function(mqttNode, done, autoDisconnect) {
delete node.users[mqttNode.id]; delete node.users[mqttNode.id];
if (!node.closing && node.connected && Object.keys(node.users).length === 0) { if (autoDisconnect && !node.closing && node.connected && Object.keys(node.users).length === 0) {
node.disconnect(); node.disconnect();
} }
done(); done();
@ -1000,7 +1009,7 @@ module.exports = function(RED) {
node.client.publish(msg.topic, msg.payload, options, function (err) { node.client.publish(msg.topic, msg.payload, options, function (err) {
if (done) { if (done) {
done(err) done(err)
} else { } else if(err) {
node.error(err, msg) node.error(err, msg)
} }
}) })
@ -1225,7 +1234,7 @@ module.exports = function(RED) {
} else { } else {
node.brokerConn.unsubscribe(node.topic,node.id, removed); node.brokerConn.unsubscribe(node.topic,node.id, removed);
} }
node.brokerConn.deregister(node, done); node.brokerConn.deregister(node, done, removed);
node.brokerConn = null; node.brokerConn = null;
} else { } else {
done(); done();
@ -1288,9 +1297,9 @@ module.exports = function(RED) {
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
} }
node.brokerConn.register(node); node.brokerConn.register(node);
node.on('close', function(done) { node.on('close', function(removed, done) {
if (node.brokerConn) { if (node.brokerConn) {
node.brokerConn.deregister(node,done); node.brokerConn.deregister(node, done, removed)
node.brokerConn = null; node.brokerConn = null;
} else { } else {
done(); done();

View File

@ -200,6 +200,15 @@ module.exports = function(RED) {
this.callback = function(req,res) { this.callback = function(req,res) {
var msgid = RED.util.generateId(); var msgid = RED.util.generateId();
res._msgid = msgid; res._msgid = msgid;
// Since Node 15, req.headers are lazily computed and the property
// marked as non-enumerable.
// That means it doesn't show up in the Debug sidebar.
// This redefines the property causing it to be evaluated *and*
// marked as enumerable again.
Object.defineProperty(req, 'headers', {
value: req.headers,
enumerable: true
})
if (node.method.match(/^(post|delete|put|options|patch)$/)) { if (node.method.match(/^(post|delete|put|options|patch)$/)) {
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.body}); node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.body});
} else if (node.method == "get") { } else if (node.method == "get") {

View File

@ -110,7 +110,12 @@ module.exports = function(RED) {
if (msg.payload[s].hasOwnProperty(p)) { if (msg.payload[s].hasOwnProperty(p)) {
/* istanbul ignore else */ /* istanbul ignore else */
if (typeof msg.payload[s][p] !== "object") { if (typeof msg.payload[s][p] !== "object") {
var q = "" + msg.payload[s][p]; // Fix to honour include null values flag
//if (typeof msg.payload[s][p] !== "object" || (node.include_null_values === true && msg.payload[s][p] === null)) {
var q = "";
if (msg.payload[s][p] !== undefined) {
q += msg.payload[s][p];
}
if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
q = q.replace(/"/g, '""'); q = q.replace(/"/g, '""');
ou += node.quo + q + node.quo + node.sep; ou += node.quo + q + node.quo + node.sep;
@ -130,9 +135,15 @@ module.exports = function(RED) {
ou += node.sep; ou += node.sep;
} }
else { else {
var p = RED.util.ensureString(RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']")); var tt = template[t];
if (template[t].indexOf('"') >=0 ) { tt = "'"+tt+"'"; }
else { tt = '"'+tt+'"'; }
var p = RED.util.getMessageProperty(msg,'payload["'+s+'"]['+tt+']');
/* istanbul ignore else */ /* istanbul ignore else */
if (p === "undefined") { p = ""; } if (p === undefined) { p = ""; }
// fix to honour include null values flag
//if (p === null && node.include_null_values !== true) { p = "";}
p = RED.util.ensureString(p);
if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
p = p.replace(/"/g, '""'); p = p.replace(/"/g, '""');
ou += node.quo + p + node.quo + node.sep; ou += node.quo + p + node.quo + node.sep;

0
packages/node_modules/@node-red/nodes/core/storage/10-file.html vendored Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

0
packages/node_modules/@node-red/nodes/locales/de/messages.json vendored Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -446,7 +446,9 @@
"staticTopic": "Subscribe to single topic", "staticTopic": "Subscribe to single topic",
"dynamicTopic": "Dynamic subscription", "dynamicTopic": "Dynamic subscription",
"auto-connect": "Connect automatically", "auto-connect": "Connect automatically",
"auto-mode-depreciated": "This option is depreciated. Please use the new auto-detect mode." "auto-mode-depreciated": "This option is depreciated. Please use the new auto-detect mode.",
"none": "none",
"other": "other"
}, },
"sections-label": { "sections-label": {
"birth-message": "Message sent on connection (birth message)", "birth-message": "Message sent on connection (birth message)",

View File

@ -446,7 +446,9 @@
"staticTopic": "1つのトピックを購読", "staticTopic": "1つのトピックを購読",
"dynamicTopic": "動的な購読", "dynamicTopic": "動的な購読",
"auto-connect": "自動接続", "auto-connect": "自動接続",
"auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。" "auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。",
"none": "なし",
"other": "その他"
}, },
"sections-label": { "sections-label": {
"birth-message": "接続時の送信メッセージ(Birthメッセージ)", "birth-message": "接続時の送信メッセージ(Birthメッセージ)",

0
packages/node_modules/@node-red/nodes/locales/ko/messages.json vendored Executable file → Normal file
View File

0
packages/node_modules/@node-red/nodes/locales/ru/messages.json vendored Executable file → Normal file
View File

View File

@ -43,37 +43,40 @@ function load(disableNodePathScan) {
return loadModuleFiles(modules); return loadModuleFiles(modules);
} }
function splitPath(p) {
return path.posix.normalize((p || '').replace(/\\/g, path.sep)).split(path.sep)
}
function loadModuleTypeFiles(module, type) { function loadModuleTypeFiles(module, type) {
const things = module[type]; const things = module[type];
var first = true; let first = true;
var promises = []; const promises = [];
for (var thingName in things) { for (let thingName in things) {
/* istanbul ignore else */ /* istanbul ignore else */
if (things.hasOwnProperty(thingName)) { if (things.hasOwnProperty(thingName)) {
if (module.name != "node-red" && first) { if (module.name != "node-red" && first) {
// Check the module directory exists // Check the module directory exists
first = false; first = false;
var fn = things[thingName].file; let moduleFn = module.path
var parts = fn.split("/"); const fn = things[thingName].file
var i = parts.length-1; const parts = splitPath(fn)
for (;i>=0;i--) { const nmi = parts.indexOf('node_modules')
if (parts[i] == "node_modules") { if(nmi > -1) {
break; moduleFn = parts.slice(0,nmi+2).join(path.sep);
} }
if (!moduleFn) {
// shortcut - skip calling statSync on empty string
break; // Module not found, don't attempt to load its nodes
} }
var moduleFn = parts.slice(0,i+2).join("/");
try { try {
var stat = fs.statSync(moduleFn); const stat = fs.statSync(moduleFn);
} catch(err) { } catch(err) {
// Module not found, don't attempt to load its nodes break; // Module not found, don't attempt to load its nodes
break;
} }
} }
try { try {
var promise; let promise;
if (type === "nodes") { if (type === "nodes") {
promise = loadNodeConfig(things[thingName]); promise = loadNodeConfig(things[thingName]);
} else if (type === "plugins") { } else if (type === "plugins") {
@ -82,8 +85,7 @@ function loadModuleTypeFiles(module, type) {
promises.push( promises.push(
promise.then( promise.then(
(function() { (function() {
var m = module.name; const n = thingName;
var n = thingName;
return function(nodeSet) { return function(nodeSet) {
things[n] = nodeSet; things[n] = nodeSet;
return nodeSet; return nodeSet;
@ -93,7 +95,6 @@ function loadModuleTypeFiles(module, type) {
); );
} catch(err) { } catch(err) {
console.log(err) console.log(err)
//
} }
} }
} }

View File

@ -156,6 +156,16 @@ function scanDirForNodesModules(dir,moduleName,package) {
} }
} }
} }
// if we have found a package.json, this IS a node_module, lets see if it is a node-red node
if (!isNodeRedModule && files.indexOf('package.json') > -1) {
package = getPackageDetails(dir) // get package details
if(package && package.isNodeRedModule) {
isNodeRedModule = true
files = ['package.json'] // shortcut the file scan
}
}
for (let i=0;i<files.length;i++) { for (let i=0;i<files.length;i++) {
let fn = files[i]; let fn = files[i];
if (!isNodeRedModule && /^@/.test(fn)) { if (!isNodeRedModule && /^@/.test(fn)) {

View File

@ -185,10 +185,17 @@ function loadNodeConfigs() {
function addModule(module) { function addModule(module) {
moduleNodes[module.name] = []; moduleNodes[module.name] = [];
moduleConfigs[module.name] = module; moduleConfigs[module.name] = module;
// console.log("registry.js.addModule",module.name,"user?",module.user,"usedBy",module.usedBy,"dependencies",module.dependencies) for (const setName in module.nodes) {
for (var setName in module.nodes) {
if (module.nodes.hasOwnProperty(setName)) { if (module.nodes.hasOwnProperty(setName)) {
var set = module.nodes[setName]; const set = module.nodes[setName];
if (!set.types) {
const err = new Error("Set has no types")
err.code = "set_has_no_types"
err.details = {
...set
}
set.err = err
}
moduleNodes[module.name].push(set.name); moduleNodes[module.name].push(set.name);
nodeList.push(set.id); nodeList.push(set.id);
if (!set.err) { if (!set.err) {

View File

@ -161,6 +161,8 @@ function start() {
for (i=0;i<nodeErrors.length;i+=1) { for (i=0;i<nodeErrors.length;i+=1) {
if (nodeErrors[i].err.code === "type_already_registered") { if (nodeErrors[i].err.code === "type_already_registered") {
log.warn("["+nodeErrors[i].id+"] "+log._("server.type-already-registered",{type:nodeErrors[i].err.details.type,module: nodeErrors[i].err.details.moduleA})); log.warn("["+nodeErrors[i].id+"] "+log._("server.type-already-registered",{type:nodeErrors[i].err.details.type,module: nodeErrors[i].err.details.moduleA}));
} else if (nodeErrors[i].err.code === "set_has_no_types") {
log.warn("["+nodeErrors[i].id+"] "+log._("server.set-has-no-types", nodeErrors[i].err.details));
} else { } else {
log.warn("["+nodeErrors[i].id+"] "+nodeErrors[i].err); log.warn("["+nodeErrors[i].id+"] "+nodeErrors[i].err);
} }

View File

@ -373,6 +373,11 @@ Node.prototype.send = function(msg) {
if (msg === null || typeof msg === "undefined") { if (msg === null || typeof msg === "undefined") {
return; return;
} else if (!util.isArray(msg)) { } else if (!util.isArray(msg)) {
// A single message has been passed in
if (typeof msg !== 'object') {
this.error(Log._("nodes.flow.non-message-returned", { type: typeof msg }));
return
}
if (this._wire) { if (this._wire) {
// A single message and a single wire on output 0 // A single message and a single wire on output 0
// TODO: pre-load flows.get calls - cannot do in constructor // TODO: pre-load flows.get calls - cannot do in constructor
@ -425,6 +430,9 @@ Node.prototype.send = function(msg) {
for (k = 0; k < msgs.length; k++) { for (k = 0; k < msgs.length; k++) {
var m = msgs[k]; var m = msgs[k];
if (m !== null && m !== undefined) { if (m !== null && m !== undefined) {
if (typeof m !== 'object') {
this.error(Log._("nodes.flow.non-message-returned", { type: typeof m }));
} else {
if (!m._msgid) { if (!m._msgid) {
hasMissingIds = true; hasMissingIds = true;
} }
@ -452,6 +460,7 @@ Node.prototype.send = function(msg) {
} }
} }
} }
}
/* istanbul ignore else */ /* istanbul ignore else */
if (!sentMessageId) { if (!sentMessageId) {
sentMessageId = redUtil.generateId(); sentMessageId = redUtil.generateId();

View File

@ -106,7 +106,7 @@ function runGitCommandWithSSHCommand(args,cwd,auth,emit) {
commandEnv.GIT_SSH = path.join(__dirname,"node-red-ssh.sh"); commandEnv.GIT_SSH = path.join(__dirname,"node-red-ssh.sh");
commandEnv.NODE_RED_KEY_FILE=auth.key_path; commandEnv.NODE_RED_KEY_FILE=auth.key_path;
// GIT_SSH_COMMAND - added in git 2.3.0 // GIT_SSH_COMMAND - added in git 2.3.0
commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null"; commandEnv.GIT_SSH_COMMAND = "ssh -i \"" + auth.key_path + "\" -F /dev/null";
// console.log('commandEnv:', commandEnv); // console.log('commandEnv:', commandEnv);
return runGitCommand(args,cwd,commandEnv,emit).then( result => { return runGitCommand(args,cwd,commandEnv,emit).then( result => {
rs.close(); rs.close();

0
packages/node_modules/@node-red/runtime/locales/de/runtime.json vendored Executable file → Normal file
View File

View File

@ -20,6 +20,7 @@
"errors-help": "Run with -v for details", "errors-help": "Run with -v for details",
"missing-modules": "Missing node modules:", "missing-modules": "Missing node modules:",
"node-version-mismatch": "Node module cannot be loaded on this version. Requires: __version__ ", "node-version-mismatch": "Node module cannot be loaded on this version. Requires: __version__ ",
"set-has-no-types": "Set does not have any types. name: '__name__', module: '__module__', file: '__file__'",
"type-already-registered": "'__type__' already registered by module __module__", "type-already-registered": "'__type__' already registered by module __module__",
"removing-modules": "Removing modules from config", "removing-modules": "Removing modules from config",
"added-types": "Added node types:", "added-types": "Added node types:",
@ -134,7 +135,8 @@
"flow": { "flow": {
"unknown-type": "Unknown type: __type__", "unknown-type": "Unknown type: __type__",
"missing-types": "missing types", "missing-types": "missing types",
"error-loop": "Message exceeded maximum number of catches" "error-loop": "Message exceeded maximum number of catches",
"non-message-returned": "Node tried to send a message of type __type__"
}, },
"index": { "index": {
"unrecognised-id": "Unrecognised id: __id__", "unrecognised-id": "Unrecognised id: __id__",

0
packages/node_modules/@node-red/runtime/locales/ko/runtime.json vendored Executable file → Normal file
View File

View File

@ -1717,6 +1717,24 @@ describe('change Node', function() {
changeNode1.receive({topic:{foo:{bar:1}}, payload:"String"}); changeNode1.receive({topic:{foo:{bar:1}}, payload:"String"});
}); });
}); });
it('moves the value of a message property object to itself', function(done) {
var flow = [{"id":"changeNode1","type":"change","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload","tot":"msg"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
msg.should.have.property('payload');
msg.payload.should.equal("bar");
done();
} catch(err) {
done(err);
}
});
changeNode1.receive({payload:"bar"});
});
});
it('moves the value of a message property object to a sub-property', function(done) { it('moves the value of a message property object to a sub-property', function(done) {
var flow = [{"id":"changeNode1","type":"change","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.foo","tot":"msg"}],"name":"changeNode","wires":[["helperNode1"]]}, var flow = [{"id":"changeNode1","type":"change","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.foo","tot":"msg"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}]; {id:"helperNode1", type:"helper", wires:[]}];

View File

@ -2322,7 +2322,7 @@ describe('HTTP Request Node', function() {
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on('input', function(msg) { n2.on('input', function(msg) {
try{ try{
msg.payload.should.startWith(`RequestError: Parse Error:`) msg.payload.should.match(/RequestError: Parse Error/)
done() done()
} catch (err) { } catch (err) {
done(err) done(err)

View File

@ -489,6 +489,39 @@ describe('MQTT Nodes', function () {
} }
testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks); testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks);
}); });
itConditional('should safely discard bad birth topic', function (done) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const baseTopic = nextTopic();
const brokerOptions = {
protocolVersion: 4,
birthTopic: baseTopic + "#", // a publish topic should never have a wildcard
birthPayload: "broker connected",
birthQos: 2,
}
const options = {};
const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null };
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
helperNode.on("input", function (msg) {
try {
msg.should.have.a.property("error").type("object");
msg.error.should.have.a.property("source").type("object");
msg.error.source.should.have.a.property("id", mqttIn.id);
done();
} catch (err) {
done(err)
}
});
return true; //handled
}
options.expectMsg = null;
try {
testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks);
done()
} catch(err) {
done(e)
}
});
itConditional('should publish close message', function (done) { itConditional('should publish close message', function (done) {
if (skipTests) { return this.skip() } if (skipTests) { return this.skip() }
this.timeout = 2000; this.timeout = 2000;
@ -646,12 +679,13 @@ function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hoo
const mqttBroker = helper.getNode(brokerOptions.id); const mqttBroker = helper.getNode(brokerOptions.id);
const mqttIn = helper.getNode(nodes.mqtt_in.id); const mqttIn = helper.getNode(nodes.mqtt_in.id);
const mqttOut = helper.getNode(nodes.mqtt_out.id); const mqttOut = helper.getNode(nodes.mqtt_out.id);
let afterLoadHandled = false; let afterLoadHandled = false, finished = false;
if (hooks.afterLoad) { if (hooks.afterLoad) {
afterLoadHandled = hooks.afterLoad(helperNode, mqttBroker, mqttIn, mqttOut) afterLoadHandled = hooks.afterLoad(helperNode, mqttBroker, mqttIn, mqttOut)
} }
if (!afterLoadHandled) { if (!afterLoadHandled) {
helperNode.on("input", function (msg) { helperNode.on("input", function (msg) {
finished = true
try { try {
compareMsgToExpected(msg, expectMsg); compareMsgToExpected(msg, expectMsg);
if (hooks.done) { hooks.done(); } if (hooks.done) { hooks.done(); }
@ -676,10 +710,12 @@ function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hoo
} }
}) })
.catch((e) => { .catch((e) => {
if(finished) { return }
if (hooks.done) { hooks.done(e); } if (hooks.done) { hooks.done(e); }
else { throw e; } else { throw e; }
}); });
} catch (err) { } catch (err) {
if(finished) { return }
if (hooks.done) { hooks.done(err); } if (hooks.done) { hooks.done(err); }
else { throw err; } else { throw err; }
} }

View File

@ -693,19 +693,20 @@ describe('CSV node', function() {
describe('json object to csv', function() { describe('json object to csv', function() {
it('should convert a simple object back to a csv', function(done) { it('should convert a simple object back to a csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e,f", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e,f,g,h,i,j,k", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
// console.log("GOT",msg)
try { try {
msg.should.have.property('payload', '4,foo,true,,0,"Hello\nWorld"\n'); msg.should.have.property('payload', '4,foo,true,,0,"Hello\nWorld",,,undefined,null,null\n');
done(); done();
} }
catch(e) { done(e); } catch(e) { done(e); }
}); });
var testJson = { e:0, d:1, b:"foo", c:true, a:4, f:"Hello\nWorld" }; var testJson = { e:0, d:1, b:"foo", c:true, a:4, f:"Hello\nWorld", h:undefined, i:"undefined",j:null,k:"null" };
n1.emit("input", {payload:testJson}); n1.emit("input", {payload:testJson});
}); });
}); });
@ -717,13 +718,14 @@ describe('CSV node', function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
// console.log("GOT",msg)
try { try {
msg.should.have.property('payload', '1,foo,"ba""r","di,ng"\n'); msg.should.have.property('payload', '1,foo,"ba""r","di,ng",,undefined,null\n');
done(); done();
} }
catch(e) { done(e); } catch(e) { done(e); }
}); });
var testJson = { d:1, b:"foo", c:"ba\"r", a:"di,ng" }; var testJson = { d:1, b:"foo", c:"ba\"r", a:"di,ng", e:undefined, f:"undefined", g:null,h:"null" };
n1.emit("input", {payload:testJson}); n1.emit("input", {payload:testJson});
}); });
}); });
@ -764,6 +766,33 @@ describe('CSV node', function() {
}); });
}); });
it('should handle a template with quotes in the property names', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"", hdrout:"all", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload', 'a"a,b\'b\nA1,B1\nA2,B2\n');
done();
}
catch(e) { done(e); }
});
var testJson = [
{
"a\"a": "A1",
"b'b": "B1"
},
{
"a\"a": "A2",
"b'b": "B2"
}
]
n1.emit("input", {payload:testJson});
});
});
it('should convert an array of objects to a multi-line csv', function(done) { it('should convert an array of objects to a multi-line csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,d,c,b", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,d,c,b", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];

View File

@ -15,11 +15,14 @@
**/ **/
var fs = require("fs-extra"); var fs = require("fs-extra");
var os = require("os");
var path = require("path"); var path = require("path");
var should = require("should"); var should = require("should");
var helper = require("node-red-node-test-helper"); var helper = require("node-red-node-test-helper");
var watchNode = require("nr-test-utils").require("@node-red/nodes/core/storage/23-watch.js"); var watchNode = require("nr-test-utils").require("@node-red/nodes/core/storage/23-watch.js");
var arch = os.arch();
var platform = os.platform();
describe('watch Node', function() { describe('watch Node', function() {
this.timeout(5000); this.timeout(5000);
@ -89,8 +92,11 @@ describe('watch Node', function() {
msg.should.have.property('payload', result.payload); msg.should.have.property('payload', result.payload);
msg.should.have.property('type', result.type); msg.should.have.property('type', result.type);
if('size' in result) { if('size' in result) {
if (!((arch === "arm64") && (platform === "darwin"))) {
// On OSX/ARM, two change events occur and first event do not reflect file size change. So ignore size field in the case.
msg.should.have.property('size', result.size); msg.should.have.property('size', result.size);
} }
}
count++; count++;
if(count === len) { if(count === len) {
n1.close(); n1.close();

View File

@ -329,17 +329,36 @@ describe("red/nodes/registry/localfilesystem",function() {
localfilesystem.init({nodesDir:[nodesDir2]}); localfilesystem.init({nodesDir:[nodesDir2]});
const nodeModule = localfilesystem.getModuleFiles(); const nodeModule = localfilesystem.getModuleFiles();
const loaded = Object.keys(nodeModule) const loaded = Object.keys(nodeModule)
loaded.should.have.a.property("length", 3)
loaded.indexOf('@test/testnode').should.greaterThan(-1, "Should load @test/testnode") loaded.indexOf('@test/testnode').should.greaterThan(-1, "Should load @test/testnode")
loaded.indexOf('lower-case').should.greaterThan(-1, "Should load lower-case")
loaded.indexOf('@lowercase/lower-case2').should.greaterThan(-1, "Should load @lowercase/lower-case2")
loaded.indexOf('testnode2').should.greaterThan(-1, "Should load testnode2") loaded.indexOf('testnode2').should.greaterThan(-1, "Should load testnode2")
loaded.indexOf('test-theme2').should.greaterThan(-1, "Should load test-theme2") loaded.indexOf('test-theme2').should.greaterThan(-1, "Should load test-theme2")
loaded.should.have.a.property("length", 5)
// scoped module with nodes in same dir as package.json
nodeModule['@test/testnode'].should.have.a.property('name','@test/testnode'); nodeModule['@test/testnode'].should.have.a.property('name','@test/testnode');
nodeModule['@test/testnode'].should.have.a.property('version','1.0.0'); nodeModule['@test/testnode'].should.have.a.property('version','1.0.0');
nodeModule['@test/testnode'].should.have.a.property('nodes'); nodeModule['@test/testnode'].should.have.a.property('nodes');
nodeModule['@test/testnode'].should.have.a.property('path'); nodeModule['@test/testnode'].should.have.a.property('path');
nodeModule['@test/testnode'].should.have.a.property('user', false); nodeModule['@test/testnode'].should.have.a.property('user', false);
// node-red module with nodes in sub dir
nodeModule['@lowercase/lower-case2'].should.have.a.property('name','@lowercase/lower-case2');
nodeModule['@lowercase/lower-case2'].should.have.a.property('version','2.0.0');
nodeModule['@lowercase/lower-case2'].should.have.a.property('nodes');
nodeModule['@lowercase/lower-case2'].nodes.should.have.a.property('lower-case');
nodeModule['@lowercase/lower-case2'].should.have.a.property('path');
nodeModule['@lowercase/lower-case2'].should.have.a.property('user', false);
// scoped module with nodes in sub dir
nodeModule['lower-case'].should.have.a.property('name', 'lower-case');
nodeModule['lower-case'].should.have.a.property('version','1.0.0');
nodeModule['lower-case'].should.have.a.property('nodes');
nodeModule['lower-case'].nodes.should.have.a.property('lower-case');
nodeModule['lower-case'].should.have.a.property('path');
nodeModule['lower-case'].should.have.a.property('user', false);
nodeModule['testnode2'].should.have.a.property('name','testnode2'); nodeModule['testnode2'].should.have.a.property('name','testnode2');
nodeModule['testnode2'].should.have.a.property('version','1.0.0'); nodeModule['testnode2'].should.have.a.property('version','1.0.0');
nodeModule['testnode2'].should.have.a.property('nodes'); nodeModule['testnode2'].should.have.a.property('nodes');

View File

@ -0,0 +1,26 @@
<script type="text/javascript">
RED.nodes.registerType('lower-case2',{
category: 'function',
color: '#a6bbcf',
defaults: {
name: {value:""}
},
inputs:1,
outputs:1,
icon: "file.png",
label: function() {
return this.name||"lower-case2";
}
});
</script>
<script type="text/html" data-template-name="lower-case2">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/html" data-help-name="lower-case2">
<p>A simple node that converts the message payloads into all lower-case2 characters</p>
</script>

View File

@ -0,0 +1,11 @@
module.exports = function(RED) {
function LowerCaseNode(config) {
RED.nodes.createNode(this,config);
var node = this;
node.on('input', function(msg) {
msg.payload = msg.payload.toLowerCase();
node.send(msg);
});
}
RED.nodes.registerType("lower-case2",LowerCaseNode);
}

View File

@ -0,0 +1,9 @@
{
"name" : "@lowercase/lower-case2",
"node-red" : {
"nodes": {
"lower-case": "lower-case2/lower-case.js"
}
},
"version": "2.0.0"
}

View File

@ -0,0 +1,26 @@
<script type="text/javascript">
RED.nodes.registerType('lower-case',{
category: 'function',
color: '#a6bbcf',
defaults: {
name: {value:""}
},
inputs:1,
outputs:1,
icon: "file.png",
label: function() {
return this.name||"lower-case";
}
});
</script>
<script type="text/html" data-template-name="lower-case">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/html" data-help-name="lower-case">
<p>A simple node that converts the message payloads into all lower-case characters</p>
</script>

View File

@ -0,0 +1,11 @@
module.exports = function(RED) {
function LowerCaseNode(config) {
RED.nodes.createNode(this,config);
var node = this;
node.on('input', function(msg) {
msg.payload = msg.payload.toLowerCase();
node.send(msg);
});
}
RED.nodes.registerType("lower-case",LowerCaseNode);
}

View File

@ -0,0 +1,9 @@
{
"name" : "lower-case",
"node-red" : {
"nodes": {
"lower-case": "lower-case/lower-case.js"
}
},
"version": "1.0.0"
}