").css({
- "overflow-y": "scroll"
+ "overflow-y": "auto"
}).appendTo(stackContainer);
panels = RED.panels.create({
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js
index 07818ffd2..e38b0b67e 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js
@@ -108,7 +108,7 @@ RED.sidebar.info = (function() {
propertiesPanelContent = $("
").css({
"flex":"1 1 auto",
- "overflow-y":"scroll",
+ "overflow-y":"auto",
}).appendTo(propertiesPanel);
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js
index fc5b8e99e..989cb78ab 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js
@@ -269,8 +269,8 @@ RED.typeSearch = (function() {
moveCallback = opts.move;
RED.events.emit("type-search:open");
//shade.show();
- if ($("#red-ui-main-container").height() - opts.y - 150 < 0) {
- opts.y = opts.y - 235;
+ if ($("#red-ui-main-container").height() - opts.y - 195 < 0) {
+ opts.y = opts.y - 275;
}
dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
searchResultsDiv.slideDown(300);
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js
index 0f02077d8..ce433a3c6 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js
@@ -1015,7 +1015,7 @@ RED.view.tools = (function() {
const nodeDef = n._def || RED.nodes.getType(n.type)
if (nodeDef && nodeDef.defaults && nodeDef.defaults.name) {
const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef)
- const defaultNodeNameRE = new RegExp('^'+paletteLabel+' (\\d+)$')
+ const defaultNodeNameRE = new RegExp('^'+paletteLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')+' (\\d+)$')
if (!typeIndex.hasOwnProperty(n.type)) {
const existingNodes = RED.nodes.filterNodes({type: n.type})
let maxNameNumber = 0;
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
old mode 100755
new mode 100644
index dafcddda7..8fb54d379
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js
@@ -1072,12 +1072,15 @@ RED.view = (function() {
RED.view.redraw();
}
+ // `point` is the place in the workspace the mouse has clicked.
+ // This takes into account scrolling and scaling of the workspace.
var ox = point[0];
var oy = point[1];
+ // Need to map that to browser location to position the pop-up
const offset = $("#red-ui-workspace-chart").offset()
- var clientX = ox + offset.left - $("#red-ui-workspace-chart").scrollLeft()
- var clientY = oy + offset.top - $("#red-ui-workspace-chart").scrollTop()
+ var clientX = (ox * scaleFactor) + offset.left - $("#red-ui-workspace-chart").scrollLeft()
+ var clientY = (oy * scaleFactor) + offset.top - $("#red-ui-workspace-chart").scrollTop()
if (RED.settings.get("editor").view['view-snap-grid']) {
// eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red')
@@ -3367,6 +3370,9 @@ RED.view = (function() {
}
if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) {
mouse_mode = RED.state.DEFAULT;
+ // Avoid dbl click causing text selection.
+ d3.event.preventDefault()
+ document.getSelection().removeAllRanges()
if (d.type != "subflow") {
if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) {
RED.workspaces.show(d.type.substring(8));
@@ -4907,7 +4913,7 @@ RED.view = (function() {
if (d._def.button) {
var buttonEnabled = isButtonEnabled(d);
this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled);
- if (RED.runtime && Object.hasOwn(RED.runtime,'started')) {
+ if (RED.runtime && RED.runtime.started !== undefined) {
this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started);
}
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
index 58099877f..eb550c6f5 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss
@@ -30,7 +30,7 @@
bottom: 0px;
left:0px;
right: 0px;
- overflow-y: scroll;
+ overflow-y: auto;
}
.red-ui-debug-filter-box {
position:absolute;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss
index 1730b9e35..b6d12faf2 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss
@@ -368,7 +368,7 @@ button.red-ui-button-small
border:1px solid var(--red-ui-secondary-border-color);
border-radius:5px;
height: calc(100% - 21px);
- overflow-y: scroll;
+ overflow-y: auto;
background: var(--red-ui-secondary-background);
}
@@ -562,7 +562,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle {
.red-ui-icon-list {
width: 308px;
height: 200px;
- overflow-y: scroll;
+ overflow-y: auto;
line-height: 0px;
position: relative;
&.red-ui-icon-list-dark {
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss
index efae432b2..7d7544e2e 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss
@@ -32,7 +32,8 @@
color: var(--red-ui-primary-text-color);
border: 1px solid var(--red-ui-notification-border-default);
border-left-width: 16px;
- overflow: hidden;
+ overflow: scroll;
+ max-height: 80vh;
.ui-dialog-buttonset {
margin-top: 20px;
margin-bottom: 10px;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss
index ee43c7a87..a32fd53b4 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss
@@ -26,7 +26,7 @@
}
}
#red-ui-project-settings-tab-settings {
- overflow-y: scroll;
+ overflow-y: auto;
}
.red-ui-sidebar-vc-shade {
background: var(--red-ui-primary-background);
@@ -183,7 +183,7 @@
}
.red-ui-projects-dialog-project-list-inner-container {
flex-grow: 1 ;
- overflow-y: scroll;
+ overflow-y: auto;
position:relative;
.red-ui-editableList-border {
border: none;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss
index 3348e945a..db1bba3bb 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss
@@ -41,6 +41,7 @@
height: 50px;
background: var(--red-ui-secondary-background);
border: 2px solid var(--red-ui-primary-border-color);
+ color: var(--red-ui-primary-text-color);
text-align: center;
line-height:50px;
@@ -51,7 +52,7 @@
.red-ui-editor-radial-menu-opt-disabled {
border-color: var(--red-ui-tertiary-border-color);
- color: var(--red-ui-tertiary-border-color);
+ color: var(--red-ui-secondary-text-color-disabled);
}
.red-ui-editor-radial-menu-opt-active {
background: var(--red-ui-secondary-background-hover);
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss
index fc4c78afb..94450f337 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss
@@ -20,7 +20,7 @@
bottom: 0;
left: 0;
right: 0;
- overflow-y: scroll;
+ overflow-y: auto;
.red-ui-palette-category {
&:not(.expanded) button {
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss
index 36ab67e3f..5e0c7fa47 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss
@@ -67,7 +67,7 @@
left: 0;
bottom: 0;
padding: 8px 20px 20px;
- overflow-y: scroll;
+ overflow-y: auto;
}
.red-ui-settings-row {
padding: 5px 10px 2px;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss
index 24e156b1e..a5734570d 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss
@@ -29,7 +29,7 @@
#red-ui-workspace-chart {
overflow: auto;
position: absolute;
- bottom:25px;
+ bottom:26px;
top: 35px;
left:0px;
right:0px;
diff --git a/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts b/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts
index ae411f33c..fd2adcbd8 100644
--- a/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts
+++ b/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts
@@ -14,6 +14,9 @@ declare var msg: NodeMessage;
/** @type {string} the id of the incoming `msg` (alias of msg._msgid) */
declare const __msgid__:string;
+declare const util:typeof import('util')
+declare const promisify:typeof import('util').promisify
+
/**
* @typedef NodeStatus
* @type {object}
diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js
index 3d1af605c..2431a8bbd 100644
--- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js
+++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js
@@ -160,6 +160,7 @@
'$base64encode':{ args:[ ]},
'$boolean':{ args:[ 'arg' ]},
'$ceil':{ args:[ 'number' ]},
+ '$clone': { args:[ 'arg' ]},
'$contains':{ args:[ 'str', 'pattern' ]},
'$count':{ args:[ 'array' ]},
'$decodeUrl':{ args:[ 'str' ]},
diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.js b/packages/node_modules/@node-red/nodes/core/common/20-inject.js
index 734bce765..3f2992cd5 100644
--- a/packages/node_modules/@node-red/nodes/core/common/20-inject.js
+++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.js
@@ -95,45 +95,64 @@ module.exports = function(RED) {
}
this.on("input", function(msg, send, done) {
- var errors = [];
- var props = this.props;
+ const errors = [];
+ let props = this.props;
if (msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) {
props = msg.__user_inject_props__;
}
delete msg.__user_inject_props__;
- props.forEach(p => {
- var property = p.p;
- var value = p.v ? p.v : '';
- var valueType = p.vt ? p.vt : 'str';
+ props = [...props]
+ function evaluateProperty(doneEvaluating) {
+ if (props.length === 0) {
+ doneEvaluating()
+ return
+ }
+ const p = props.shift()
+ const property = p.p;
+ const value = p.v ? p.v : '';
+ const valueType = p.vt ? p.vt : 'str';
- if (!property) return;
-
- if (valueType === "jsonata") {
- if (p.v) {
- try {
- var exp = RED.util.prepareJSONataExpression(p.v, node);
- var val = RED.util.evaluateJSONataExpression(exp, msg);
- RED.util.setMessageProperty(msg, property, val, true);
+ if (property) {
+ if (valueType === "jsonata") {
+ if (p.v) {
+ try {
+ var exp = RED.util.prepareJSONataExpression(p.v, node);
+ var val = RED.util.evaluateJSONataExpression(exp, msg);
+ RED.util.setMessageProperty(msg, property, val, true);
+ }
+ catch (err) {
+ errors.push(err.message);
+ }
}
- catch (err) {
- errors.push(err.message);
+ evaluateProperty(doneEvaluating)
+ } else {
+ try {
+ RED.util.evaluateNodeProperty(value, valueType, node, msg, (err, newValue) => {
+ if (err) {
+ errors.push(err.toString())
+ } else {
+ RED.util.setMessageProperty(msg,property,newValue,true);
+ }
+ evaluateProperty(doneEvaluating)
+ })
+ } catch (err) {
+ errors.push(err.toString());
+ evaluateProperty(doneEvaluating)
}
}
- return;
+ } else {
+ evaluateProperty(doneEvaluating)
}
- try {
- RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true);
- } catch (err) {
- errors.push(err.toString());
- }
- });
-
- if (errors.length) {
- done(errors.join('; '));
- } else {
- send(msg);
- done();
}
+
+ evaluateProperty(() => {
+ if (errors.length) {
+ done(errors.join('; '));
+ } else {
+ send(msg);
+ done();
+ }
+ })
});
}
diff --git a/packages/node_modules/@node-red/nodes/core/common/24-complete.html b/packages/node_modules/@node-red/nodes/core/common/24-complete.html
index a6a7a2a45..b2d406bb5 100644
--- a/packages/node_modules/@node-red/nodes/core/common/24-complete.html
+++ b/packages/node_modules/@node-red/nodes/core/common/24-complete.html
@@ -1,6 +1,6 @@
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/80-template.html
index 07027e76e..52b6c2920 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/function/80-template.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/80-template.html
@@ -52,4 +52,7 @@
used to mark the templated sections. For example, to use
[[ ]]
instead, add the following line to the top of the template:
{{=[[ ]]=}}
+
Using environment variables
+
The template node can access environment variables using the syntax:
+
My favourite colour is {{env.COLOUR}}.
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
index 3ba344ae0..248bb3415 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
@@ -446,7 +446,9 @@
"staticTopic": "Subscribe to single topic",
"dynamicTopic": "Dynamic subscription",
"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": {
"birth-message": "Message sent on connection (birth message)",
@@ -554,7 +556,8 @@
},
"status": {
"requesting": "requesting"
- }
+ },
+ "insecureHTTPParser": "Disable strict HTTP parsing"
},
"websocket": {
"label": {
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html
index 1a9c17453..9b64003a5 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html
@@ -48,4 +48,7 @@
注: デフォルトでは、mustache形式は置換対象のHTML要素をエスケープします。これを抑止するには{{{三重}}}
括弧形式を使います。
もし、コンテンツの中で{{ }}
を出力する必要がある場合は、テンプレートで使われる記号文字を変えることもできます。例えば、[[ ]]
を代わりに用いるには、テンプレートの先頭に以下の行を追加します。
{{=[[ ]]=}}
+
環境変数の利用
+
templateノードでは、次の構文を用いると環境変数にアクセスできます:
+
私の好きな色は{{env.COLOUR}}です。
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json
index 2365f7f7a..263fbce97 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json
@@ -446,7 +446,9 @@
"staticTopic": "1つのトピックを購読",
"dynamicTopic": "動的な購読",
"auto-connect": "自動接続",
- "auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。"
+ "auto-mode-depreciated": "本オプションは非推奨になりました。新しい自動判定モードを使用してください。",
+ "none": "なし",
+ "other": "その他"
},
"sections-label": {
"birth-message": "接続時の送信メッセージ(Birthメッセージ)",
@@ -554,7 +556,8 @@
},
"status": {
"requesting": "要求中"
- }
+ },
+ "insecureHTTPParser": "厳密なHTTPパース処理を無効化"
},
"websocket": {
"label": {
diff --git a/packages/node_modules/@node-red/nodes/locales/ko/messages.json b/packages/node_modules/@node-red/nodes/locales/ko/messages.json
old mode 100755
new mode 100644
diff --git a/packages/node_modules/@node-red/nodes/locales/ru/messages.json b/packages/node_modules/@node-red/nodes/locales/ru/messages.json
old mode 100755
new mode 100644
diff --git a/packages/node_modules/@node-red/registry/lib/loader.js b/packages/node_modules/@node-red/registry/lib/loader.js
index 61f28ab86..d125156ab 100644
--- a/packages/node_modules/@node-red/registry/lib/loader.js
+++ b/packages/node_modules/@node-red/registry/lib/loader.js
@@ -43,37 +43,40 @@ function load(disableNodePathScan) {
return loadModuleFiles(modules);
}
+function splitPath(p) {
+ return path.posix.normalize((p || '').replace(/\\/g, path.sep)).split(path.sep)
+}
function loadModuleTypeFiles(module, type) {
const things = module[type];
- var first = true;
- var promises = [];
- for (var thingName in things) {
+ let first = true;
+ const promises = [];
+ for (let thingName in things) {
/* istanbul ignore else */
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;
- }
+ let moduleFn = module.path
+ const fn = things[thingName].file
+ const parts = splitPath(fn)
+ const nmi = parts.indexOf('node_modules')
+ if(nmi > -1) {
+ 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 {
- var stat = fs.statSync(moduleFn);
+ const stat = fs.statSync(moduleFn);
} catch(err) {
- // Module not found, don't attempt to load its nodes
- break;
+ break; // Module not found, don't attempt to load its nodes
}
}
try {
- var promise;
+ let promise;
if (type === "nodes") {
promise = loadNodeConfig(things[thingName]);
} else if (type === "plugins") {
@@ -82,8 +85,7 @@ function loadModuleTypeFiles(module, type) {
promises.push(
promise.then(
(function() {
- var m = module.name;
- var n = thingName;
+ const n = thingName;
return function(nodeSet) {
things[n] = nodeSet;
return nodeSet;
@@ -93,7 +95,6 @@ function loadModuleTypeFiles(module, type) {
);
} catch(err) {
console.log(err)
- //
}
}
}
@@ -125,38 +126,24 @@ function loadModuleFiles(modules) {
}
var pluginList;
var nodeList;
-
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]);
- }
- }
- }
- }
- return loadNodeSetList(pluginList);
- }).then(function() {
- return Promise.all(nodePromises);
+ 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]);
- }
+ if (!modules[module].err) {
+ registry.addModule(modules[module]);
}
}
}
+ }).then(function() {
+ return loadNodeSetList(pluginList);
+ }).then(function() {
return loadNodeSetList(nodeList);
- });
+ })
}
async function loadPluginTemplate(plugin) {
diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js
index da4006ecc..0c231552f 100644
--- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js
+++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js
@@ -106,8 +106,8 @@ function getLocalNodeFiles(dir, skipValidNodeRedModules) {
// when loading local files, if the path is a valid node-red module
// dont include it (will be picked up in scanTreeForNodesModules)
if(skipValidNodeRedModules && files.indexOf("package.json") >= 0) {
- const package = getPackageDetails(dir)
- if(package.isNodeRedModule) {
+ const packageDetails = getPackageDetails(dir)
+ if(packageDetails.isNodeRedModule) {
return {files: [], icons: []};
}
}
@@ -135,17 +135,17 @@ function getLocalNodeFiles(dir, skipValidNodeRedModules) {
return {files: result, icons: icons}
}
-function scanDirForNodesModules(dir,moduleName,package) {
+function scanDirForNodesModules(dir,moduleName,packageDetails) {
let results = [];
let scopeName;
let files
try {
let isNodeRedModule = false
- if(package) {
- dir = path.join(package.moduleDir,'..')
- files = [path.basename(package.moduleDir)]
- moduleName = (package.package ? package.package.name : null) || moduleName
- isNodeRedModule = package.isNodeRedModule
+ if(packageDetails) {
+ dir = path.join(packageDetails.moduleDir,'..')
+ files = [path.basename(packageDetails.moduleDir)]
+ moduleName = (packageDetails.package ? packageDetails.package.name : null) || moduleName
+ isNodeRedModule = packageDetails.isNodeRedModule
} else {
files = fs.readdirSync(dir);
if (moduleName) {
@@ -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) {
+ packageDetails = getPackageDetails(dir) // get package details
+ if(packageDetails && packageDetails.isNodeRedModule) {
+ isNodeRedModule = true
+ files = ['package.json'] // shortcut the file scan
+ }
+ }
+
for (let i=0;i
{
rs.close();
@@ -419,7 +421,10 @@ module.exports = {
});
},
initRepo: function(cwd) {
- return runGitCommand(["init"],cwd);
+ var args = ["init", "--initial-branch", "main"];
+ return runGitCommand(args, cwd).catch(function () {
+ return runGitCommand(["init"], cwd);
+ });
},
setUpstream: function(cwd,remoteBranch) {
var args = ["branch","--set-upstream-to",remoteBranch];
diff --git a/packages/node_modules/@node-red/runtime/locales/de/runtime.json b/packages/node_modules/@node-red/runtime/locales/de/runtime.json
old mode 100755
new mode 100644
index 6811ba86a..0249d638d
--- a/packages/node_modules/@node-red/runtime/locales/de/runtime.json
+++ b/packages/node_modules/@node-red/runtime/locales/de/runtime.json
@@ -1,6 +1,6 @@
{
"runtime": {
- "welcome": "Willkommen bei Node-RED!",
+ "welcome": "Willkommen bei Node-RED",
"version": "__component__ Version: __version__",
"unsupported_version": "Nicht unterstützte Version von __component__. Erforderlich: __requires__, jedoch gefunden: __version__",
"paths": {
@@ -8,7 +8,6 @@
"httpStatic": "HTTP-Statisch: __path__"
}
},
-
"server": {
"loading": "Paletten-Nodes werden geladen",
"palette-editor": {
@@ -34,17 +33,19 @@
"install-failed-not-found": "Das Modul $t(server.install.install-failed-long) wurde nicht gefunden",
"install-failed-name": "$t(server.install.install-failed-long). Ungültiger Modulname: __name__",
"install-failed-url": "$t(server.install.install-failed-long). Ungültige URL: __url__",
+ "post-install-error": "Fehler bei der Ausführung des 'postInstall'-Hooks:",
"upgrading": "Upgrade von Modul __name__ auf Version __version__ gestartet",
"upgraded": "Upgrade von Modul __name__ war erfolgreich. Neustart von Node-RED für die Verwendung der neuen Version erforderlich.",
"upgrade-failed-not-found": "Upgrade fehlgeschlagen. $t(server.install.install-failed-long). Version nicht gefunden.",
"uninstalling": "Das Modul __name__ wird deinstalliert",
"uninstall-failed": "Deinstallation fehlgeschlagen",
"uninstall-failed-long": "Die Deinstallation des Moduls __name__ ist fehlgeschlagen:",
- "uninstalled": "Das Modul __name__ ist deinstalliert"
+ "uninstalled": "Das Modul __name__ ist deinstalliert",
+ "old-ext-mod-dir-warning": "\n\n---------------------------------------------------------------------\nNode-RED 1.3 Verzeichnis externer Module erkannt:\n __oldDir__\nDieses Verzeichnis wird nicht mehr verwendet. Die externen Module werden\nin Ihrem Node-RED-Benutzerverzeichnis neu installiert:\n __newDir__\nLöschen Sie das alte externalModules-Verzeichnis, um diese Meldung abzustellen.\n---------------------------------------------------------------------\n"
},
"deprecatedOption": "Die Verwendung von __old__ ist abgekündigt. Stattdessen __new__ verwenden.",
"unable-to-listen": "Überwachen (listen) von __listenpath__ nicht möglich",
- "port-in-use": "FEHLER: Port wird verwendet",
+ "port-in-use": "Fehler: Port wird verwendet",
"uncaught-exception": "Nicht abgefangene Ausnahmebedingung:",
"admin-ui-disabled": "Administrator-Benutzeroberfläche deaktiv",
"now-running": "Server wird jetzt auf __listenpath__ ausgeführt",
@@ -55,11 +56,10 @@
"refresh-interval": "Erneuerung der https-Einstellungen erfolgt alle __interval__ Stunden",
"settings-refreshed": "https-Einstellungen wurden erneuert",
"refresh-failed": "Erneuerung der https-Einstellungen fehlgeschlagen: __message__",
- "nodejs-version": "httpsRefreshInterval erfordert Node.js 11 or later",
+ "nodejs-version": "httpsRefreshInterval erfordert Node.js 11 oder höher",
"function-required": "httpsRefreshInterval erfordert die https-Eigenschaft in Form einer Funktion"
}
},
-
"api": {
"flows": {
"error-save": "Fehler beim Speichern der Flows: __message__",
@@ -77,17 +77,16 @@
"error-enable": "Der Node konnte nicht aktiviert werden:"
}
},
-
"comms": {
"error": "Kommunikationskanal-Fehler: __message__",
"error-server": "Kommunikationsserver-Fehler: __message__",
"error-send": "Kommunikationsende-Fehler: __message__"
},
-
"settings": {
"user-not-available": "Einstellungen konnten nicht gespeichert werden: __message__",
"not-available": "Einstellungen nicht verfügbar",
- "property-read-only": "Die Eigenschaft '__prop__ 'ist schreibgeschützt"
+ "property-read-only": "Die Eigenschaft '__prop__' ist schreibgeschützt",
+ "readonly-mode": "Laufzeitumgebung im Nur-Lese-Modus. Änderungen werden nicht gespeichert."
},
"library": {
"unknownLibrary": "Unbekannte Bibliothek (Library): __library__",
@@ -98,10 +97,12 @@
},
"nodes": {
"credentials": {
- "error": "Fehler beim Laden der Berechtigungen: __message__",
- "error-saving": "Fehler beim Speichern der Berechtigungen: __message__",
- "not-registered": "Der Berechtigung-Typ '__type__' ist nicht registriert",
- "system-key-warning": "\n---------------------------------------------------------------------\nDie Datei mit den Flow-Berechtigungen wird mit einem vom System\ngenerierten Schlüssel verschlüsselt.\nWenn der vom System generierte Schlüssel aus irgendeinem Grund\nverloren geht, kann die Datei mit den Berechtigungen nicht\nwiederhergestellt werden. Sie muss dann gelöscht und die\nBerechtigungen müssen erneut eingestellt werden.\nEs sollte ein eigener Schlüssel mit Hilfe der Option\n'credentialSecret' in der Einstellungsdatei vorgegeben werden.\nNode-RED wird dann die Datei mit den Flow-Berechtigungen\nbei der nächsten Übernahme (deploy) einer Änderung erneut\nverschlüsseln.\n---------------------------------------------------------------------"
+ "error": "Fehler beim Laden der Credentials: __message__",
+ "error-saving": "Fehler beim Speichern der Credentials: __message__",
+ "not-registered": "Der Credentials-Typ '__type__' ist nicht registriert",
+ "system-key-warning": "\n---------------------------------------------------------------------\nDie Datei mit den Flow-Credentials wird mit einem vom System\ngenerierten Schlüssel verschlüsselt.\nWenn der vom System generierte Schlüssel aus irgendeinem Grund\nverloren geht, kann die Datei mit den Credentials nicht\nwiederhergestellt werden. Sie muss dann gelöscht und die\nCredentials müssen erneut eingestellt werden.\nEs sollte ein eigener Schlüssel mit Hilfe der Option\n'credentialSecret' in der Einstellungsdatei vorgegeben werden.\nNode-RED wird dann die Datei mit den Flow-Credentials\nbei der nächsten Übernahme (deploy) einer Änderung erneut\nverschlüsseln.\n---------------------------------------------------------------------",
+ "unencrypted": "Verwende unverschlüsselte Credentials",
+ "encryptedNotFound": "Verschlüsselte Credentials nicht gefunden"
},
"flows": {
"safe-mode": "Die Flows sind gestoppt im abgesicherten Modus. Übernahme (deploy) zum Starten.",
@@ -121,6 +122,7 @@
"stopped-flows": "Flows sind gestoppt",
"stopped": "gestoppt",
"stopping-error": "Fehler beim Stoppen des Nodes: __message__",
+ "updated-flows": "Flows aktualisiert",
"added-flow": "Flow wird hinzugefügt: __label__",
"updated-flow": "Aktualisierter Flow: __label__",
"removed-flow": "Entfernter Flow: __label__",
@@ -145,7 +147,6 @@
}
}
},
-
"storage": {
"index": {
"forbidden-flow-name": "Unzulässiger Flow-Name"
@@ -159,6 +160,7 @@
"restore": "Die '__type__'-Dateisicherung wird wiederhergestellt: __path__",
"restore-fail": "Die Wiederherstellung der '__type__'-Dateisicherung ist fehlgeschlagen: __message__",
"fsync-fail": "Die Übertragung der Datei __path__ auf das Laufwerk ist fehlgeschlagen: __message__",
+ "warn_name": "Name der Flows-Datei nicht festgelegt. Name wird unter Verwendung des Hostnamens generiert.",
"projects": {
"changing-project": "Aktives Projekt wird festgelegt: __project__",
"active-project": "Aktives Projekt: __project__",
@@ -174,7 +176,6 @@
}
}
},
-
"context": {
"log-store-init": "Kontextspeicher: __name__ [__info__]",
"error-loading-module": "Fehler beim Laden des Kontextspeichers: __message__",
@@ -189,5 +190,4 @@
"error-write": "Fehler beim Schreiben des Kontextes: __message__"
}
}
-
}
diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
index ecd010abb..46890c26c 100644
--- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
+++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
@@ -20,6 +20,7 @@
"errors-help": "Run with -v for details",
"missing-modules": "Missing node modules:",
"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__",
"removing-modules": "Removing modules from config",
"added-types": "Added node types:",
@@ -134,7 +135,8 @@
"flow": {
"unknown-type": "Unknown type: __type__",
"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": {
"unrecognised-id": "Unrecognised id: __id__",
diff --git a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json
index bb5b0badc..8e4bceebe 100644
--- a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json
+++ b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json
@@ -20,6 +20,7 @@
"errors-help": "詳細は -v を指定して実行してください",
"missing-modules": "不足しているノードモジュール:",
"node-version-mismatch": "ノードモジュールはこのバージョンではロードできません。必要なバージョン: __version__ ",
+ "set-has-no-types": "セットに型がありません。 名前: '__name__', モジュール: '__module__', ファイル: '__file__'",
"type-already-registered": "'__type__' はモジュール __module__ で登録済みです",
"removing-modules": "設定からモジュールを削除します",
"added-types": "追加したノード:",
@@ -134,7 +135,8 @@
"flow": {
"unknown-type": "不明なノード: __type__",
"missing-types": "欠落したノード",
- "error-loop": "メッセージの例外補足回数が最大値を超えました"
+ "error-loop": "メッセージの例外補足回数が最大値を超えました",
+ "non-message-returned": "ノードが __type__ 型のメッセージの送信を試みました"
},
"index": {
"unrecognised-id": "不明なID: __id__",
diff --git a/packages/node_modules/@node-red/runtime/locales/ko/runtime.json b/packages/node_modules/@node-red/runtime/locales/ko/runtime.json
old mode 100755
new mode 100644
diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js
index 5d8a05e44..d6e14f21b 100644
--- a/packages/node_modules/node-red/settings.js
+++ b/packages/node_modules/node-red/settings.js
@@ -181,7 +181,7 @@ module.exports = {
/** Some nodes, such as HTTP In, can be used to listen for incoming http requests.
* By default, these are served relative to '/'. The following property
- * can be used to specifiy a different root path. If set to false, this is
+ * can be used to specify a different root path. If set to false, this is
* disabled.
*/
//httpNodeRoot: '/red-nodes',
@@ -219,17 +219,17 @@ module.exports = {
/** When httpAdminRoot is used to move the UI to a different root path, the
* following property can be used to identify a directory of static content
* that should be served at http://localhost:1880/.
- * When httpStaticRoot is set differently to httpAdminRoot, there is no need
+ * When httpStaticRoot is set differently to httpAdminRoot, there is no need
* to move httpAdminRoot
*/
//httpStatic: '/home/nol/node-red-static/', //single static source
/* OR multiple static sources can be created using an array of objects... */
//httpStatic: [
- // {path: '/home/nol/pics/', root: "/img/"},
- // {path: '/home/nol/reports/', root: "/doc/"},
+ // {path: '/home/nol/pics/', root: "/img/"},
+ // {path: '/home/nol/reports/', root: "/doc/"},
//],
- /**
+ /**
* All static routes will be appended to httpStaticRoot
* e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/"
* then "/home/nol/docs" will be served at "/static/"
@@ -256,11 +256,11 @@ module.exports = {
*/
// lang: "de",
- /** Configure diagnostics options
+ /** Configure diagnostics options
* - enabled: When `enabled` is `true` (or unset), diagnostics data will
- * be available at http://localhost:1880/diagnostics
- * - ui: When `ui` is `true` (or unset), the action `show-system-info` will
- * be available to logged in users of node-red editor
+ * be available at http://localhost:1880/diagnostics
+ * - ui: When `ui` is `true` (or unset), the action `show-system-info` will
+ * be available to logged in users of node-red editor
*/
diagnostics: {
/** enable or disable diagnostics endpoint. Must be set to `false` to disable */
@@ -268,10 +268,10 @@ module.exports = {
/** enable or disable diagnostics display in the node-red editor. Must be set to `false` to disable */
ui: true,
},
- /** Configure runtimeState options
- * - enabled: When `enabled` is `true` flows runtime can be Started/Stoped
- * by POSTing to available at http://localhost:1880/flows/state
- * - ui: When `ui` is `true`, the action `core:start-flows` and
+ /** Configure runtimeState options
+ * - enabled: When `enabled` is `true` flows runtime can be Started/Stopped
+ * by POSTing to available at http://localhost:1880/flows/state
+ * - ui: When `ui` is `true`, the action `core:start-flows` and
* `core:stop-flows` will be available to logged in users of node-red editor
* Also, the deploy menu (when set to default) will show a stop or start button
*/
@@ -528,7 +528,7 @@ module.exports = {
*/
//tlsConfigDisableLocalFiles: true,
- /** The following property can be used to verify websocket connection attempts.
+ /** The following property can be used to verify WebSocket connection attempts.
* This allows, for example, the HTTP request headers to be checked to ensure
* they include valid authentication information.
*/
diff --git a/test/nodes/core/function/15-change_spec.js b/test/nodes/core/function/15-change_spec.js
index b8d7be03b..66f02d6d1 100644
--- a/test/nodes/core/function/15-change_spec.js
+++ b/test/nodes/core/function/15-change_spec.js
@@ -1717,6 +1717,24 @@ describe('change Node', function() {
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) {
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:[]}];
diff --git a/test/nodes/core/network/21-httprequest_spec.js b/test/nodes/core/network/21-httprequest_spec.js
index 688776289..14f3fd628 100644
--- a/test/nodes/core/network/21-httprequest_spec.js
+++ b/test/nodes/core/network/21-httprequest_spec.js
@@ -31,6 +31,8 @@ var multer = require("multer");
var RED = require("nr-test-utils").require("node-red/lib/red");
var fs = require('fs-extra');
var auth = require('basic-auth');
+const { version } = require("os");
+const net = require('net')
describe('HTTP Request Node', function() {
var testApp;
@@ -1527,7 +1529,7 @@ describe('HTTP Request Node', function() {
msg.payload.headers.should.have.property('Content-Type').which.startWith('application/json');
//msg.dynamicHeaderName should be present in headers with the value of msg.dynamicHeaderValue
msg.payload.headers.should.have.property('dyn-header-name').which.startWith('dyn-header-value');
- //static (custom) header set in Flow UI should be present
+ //static (custom) header set in Flow UI should be present
msg.payload.headers.should.have.property('static-header-name').which.startWith('static-header-value');
//msg.headers['location'] should be deleted because Flow UI "Location" header has a blank value
//ensures headers with matching characters but different case are eliminated
@@ -2265,4 +2267,105 @@ describe('HTTP Request Node', function() {
});
});
});
+
+ describe('should parse broken headers', function() {
+
+ const versions = process.versions.node.split('.')
+
+ if (( versions[0] == 14 && versions[1] >= 20 ) ||
+ ( versions[0] == 16 && versions[1] >= 16 ) ||
+ ( versions[0] == 18 && versions[1] >= 5 ) ||
+ ( versions[0] > 18)) {
+ // only test if on new enough NodeJS version
+
+ let port = testPort++
+
+ let server;
+
+ before(function() {
+ server = net.createServer(function (socket) {
+ socket.write("HTTP/1.0 200\nContent-Type: text/plain\n\nHelloWorld")
+ socket.end()
+ })
+
+ server.listen(port,'127.0.0.1', function(err) {
+ })
+ });
+
+ after(function() {
+ server.close()
+ });
+
+ it('should accept broken headers', function (done) {
+ var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:`http://localhost:${port}/`, insecureHTTPParser: true},
+ {id:"n2", type:"helper"}];
+ helper.load(httpRequestNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on('input', function(msg) {
+ try {
+ msg.payload.should.equal('HelloWorld')
+ done()
+ } catch (err) {
+ done(err)
+ }
+ })
+ n1.receive({payload: 'foo'})
+ });
+ });
+
+ it('should reject broken headers', function (done) {
+ var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:`http://localhost:${port}/`},
+ {id:"n2", type:"helper"}];
+ helper.load(httpRequestNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on('input', function(msg) {
+ try{
+ msg.payload.should.match(/RequestError: Parse Error/)
+ done()
+ } catch (err) {
+ done(err)
+ }
+ })
+ n1.receive({payload: 'foo'})
+
+ });
+ });
+ }
+ });
+
+ describe('multipart form posts', function() {
+ it('should send arrays as multiple entries', function (done) {
+ const flow = [
+ {
+ id: 'n1', type: 'http request', wires: [['n2']], method: 'POST', ret: 'obj', url: getTestURL('/file-upload'), headers: [
+ ]
+ },
+ { id: "n2", type: "helper" }
+ ];
+ helper.load(httpRequestNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ n2.on('input', function(msg){
+ try {
+ msg.payload.body.should.have.property('foo')
+ msg.payload.body.list.should.deepEqual(['a','b','c'])
+ done()
+ } catch (e) {
+ done(e)
+ }
+ });
+ n1.receive({
+ headers: {
+ 'content-type': 'multipart/form-data'
+ },
+ payload: {
+ foo: 'bar',
+ list: [ 'a', 'b', 'c' ]
+ }
+ });
+ })
+ });
+ })
});
diff --git a/test/nodes/core/network/21-mqtt_spec.js b/test/nodes/core/network/21-mqtt_spec.js
index 655cc9c73..f97442b50 100644
--- a/test/nodes/core/network/21-mqtt_spec.js
+++ b/test/nodes/core/network/21-mqtt_spec.js
@@ -27,7 +27,7 @@ describe('MQTT Nodes', function () {
} catch (error) { }
});
- it('should be loaded and have default values', function (done) {
+ it('should be loaded and have default values (MQTT V4)', function (done) {
this.timeout = 2000;
const { flow, nodes } = buildBasicMQTTSendRecvFlow({ id: "mqtt.broker", name: "mqtt_broker", autoConnect: false }, { id: "mqtt.in", topic: "in_topic" }, { id: "mqtt.out", topic: "out_topic" });
helper.load(mqttNodes, flow, function () {
@@ -61,6 +61,52 @@ describe('MQTT Nodes', function () {
mqttBroker.options.clientId.should.containEql('nodered_');
mqttBroker.options.should.have.property('keepalive').type("number");
mqttBroker.options.should.have.property('reconnectPeriod').type("number");
+ //as this is not a v5 connection, ensure v5 properties are not present
+ mqttBroker.options.should.not.have.property('protocolVersion', 5);
+ mqttBroker.options.should.not.have.property('properties');
+ done();
+ } catch (error) {
+ done(error)
+ }
+ });
+ });
+ it('should be loaded and have default values (MQTT V5)', function (done) {
+ this.timeout = 2000;
+ const { flow, nodes } = buildBasicMQTTSendRecvFlow({ id: "mqtt.broker", name: "mqtt_broker", autoConnect: false, cleansession: false, clientid: 'clientid', keepalive: 35, sessionExpiry: '6000', protocolVersion: '5', userProps: {"prop": "val"}}, { id: "mqtt.in", topic: "in_topic" }, { id: "mqtt.out", topic: "out_topic" });
+ helper.load(mqttNodes, flow, function () {
+ try {
+ const mqttIn = helper.getNode("mqtt.in");
+ const mqttOut = helper.getNode("mqtt.out");
+ const mqttBroker = helper.getNode("mqtt.broker");
+
+ should(mqttIn).be.type("object", "mqtt in node should be an object")
+ mqttIn.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node
+ mqttIn.should.have.property('datatype', 'utf8'); //default: 'utf8'
+ mqttIn.should.have.property('isDynamic', false); //default: false
+ mqttIn.should.have.property('inputs', 0); //default: 0
+ mqttIn.should.have.property('qos', 2); //default: 2
+ mqttIn.should.have.property('topic', "in_topic");
+ mqttIn.should.have.property('wires', [["helper.node"]]);
+
+ should(mqttOut).be.type("object", "mqtt out node should be an object")
+ mqttOut.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node
+ mqttOut.should.have.property('topic', "out_topic");
+
+ should(mqttBroker).be.type("object", "mqtt broker node should be an object")
+ mqttBroker.should.have.property('broker', BROKER_HOST);
+ mqttBroker.should.have.property('port', BROKER_PORT);
+ mqttBroker.should.have.property('brokerurl');
+ // mqttBroker.should.have.property('autoUnsubscribe', true);//default: true
+ mqttBroker.should.have.property('autoConnect', false);//Set "autoConnect:false" in brokerOptions
+ mqttBroker.should.have.property('options');
+ mqttBroker.options.should.have.property('clean', false);
+ mqttBroker.options.should.have.property('clientId', 'clientid');
+ mqttBroker.options.should.have.property('keepalive').type("number", 35);
+ mqttBroker.options.should.have.property('reconnectPeriod').type("number");
+ //as this IS a v5 connection, ensure v5 properties are not present
+ mqttBroker.options.should.have.property('protocolVersion', 5);
+ mqttBroker.options.should.have.property('properties');
+ mqttBroker.options.properties.should.have.property('sessionExpiryInterval');
done();
} catch (error) {
done(error)
@@ -173,7 +219,7 @@ describe('MQTT Nodes', function () {
topic: nextTopic(),
payload: '{prop:"value3", "num":3}', // send invalid JSON ...
}
- const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
+ const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null }
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
helperNode.on("input", function (msg) {
try {
@@ -299,7 +345,7 @@ describe('MQTT Nodes', function () {
topic: nextTopic(),
payload: '{prop:"value3", "num":3}', contentType: "application/json", // send invalid JSON ...
}
- const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
+ const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null }
hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
helperNode.on("input", function (msg) {
try {
@@ -385,7 +431,7 @@ describe('MQTT Nodes', function () {
if (skipTests) { return this.skip() }
this.timeout = 2000;
const options = {}
- const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }
+ const hooks = { beforeLoad: null, afterLoad: null, afterConnect: null }
hooks.beforeLoad = (flow) => { //add a status node pointed at MQTT Out node (to watch for connection status change)
flow.push({ "id": "status.node", "type": "status", "name": "status_node", "scope": ["mqtt.out"], "wires": [["helper.node"]] });//add status node to watch mqtt_out
}
@@ -416,20 +462,66 @@ describe('MQTT Nodes', function () {
this.timeout = 2000;
const baseTopic = nextTopic();
const brokerOptions = {
+ autoConnect: false,
protocolVersion: 4,
birthTopic: baseTopic + "/birth",
- birthPayload: "broker connected",
+ birthPayload: "broker birth",
birthQos: 2,
}
- const options = {};
- const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null };
- options.expectMsg = {
+ const expectMsg = {
topic: brokerOptions.birthTopic,
payload: brokerOptions.birthPayload,
qos: brokerOptions.birthQos
};
+ const options = { };
+ const hooks = { };
+ hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => {
+ helperNode.on("input", function (msg) {
+ try {
+ compareMsgToExpected(msg, expectMsg);
+ done();
+ } catch (error) {
+ done(error)
+ }
+ })
+ mqttIn.receive({ "action": "connect" }); //now request connect action
+ return true; //handled
+ }
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) {
if (skipTests) { return this.skip() }
this.timeout = 2000;
@@ -587,12 +679,13 @@ function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hoo
const mqttBroker = helper.getNode(brokerOptions.id);
const mqttIn = helper.getNode(nodes.mqtt_in.id);
const mqttOut = helper.getNode(nodes.mqtt_out.id);
- let afterLoadHandled = false;
+ let afterLoadHandled = false, finished = false;
if (hooks.afterLoad) {
afterLoadHandled = hooks.afterLoad(helperNode, mqttBroker, mqttIn, mqttOut)
}
if (!afterLoadHandled) {
helperNode.on("input", function (msg) {
+ finished = true
try {
compareMsgToExpected(msg, expectMsg);
if (hooks.done) { hooks.done(); }
@@ -617,10 +710,12 @@ function testSendRecv(brokerOptions, inNodeOptions, outNodeOptions, options, hoo
}
})
.catch((e) => {
+ if(finished) { return }
if (hooks.done) { hooks.done(e); }
else { throw e; }
});
} catch (err) {
+ if(finished) { return }
if (hooks.done) { hooks.done(err); }
else { throw err; }
}
@@ -666,6 +761,7 @@ function buildMQTTBrokerNode(id, name, brokerHost, brokerPort, options) {
node.cleansession = String(options.cleansession) == "false" ? false : true;
node.autoUnsubscribe = String(options.autoUnsubscribe) == "false" ? false : true;
node.autoConnect = String(options.autoConnect) == "false" ? false : true;
+ node.sessionExpiry = options.sessionExpiry ? options.sessionExpiry : undefined;
if (options.birthTopic) {
node.birthTopic = options.birthTopic;
@@ -760,8 +856,8 @@ function waitBrokerConnect(broker, timeLimit) {
let waitConnected = (broker, timeLimit) => {
const brokers = Array.isArray(broker) ? broker : [broker];
timeLimit = timeLimit || 1000;
- let timer, resolved = false;
return new Promise( (resolve, reject) => {
+ let timer, resolved = false;
timer = wait();
function wait() {
if (brokers.every(e => e.connected == true)) {
diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js
index 93d59a171..681711b3b 100644
--- a/test/nodes/core/parsers/70-CSV_spec.js
+++ b/test/nodes/core/parsers/70-CSV_spec.js
@@ -693,19 +693,20 @@ describe('CSV node', function() {
describe('json object to csv', function() {
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"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
+ // console.log("GOT",msg)
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();
}
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});
});
});
@@ -717,13 +718,14 @@ describe('CSV node', function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
+ // console.log("GOT",msg)
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();
}
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});
});
});
@@ -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) {
var flow = [ { id:"n1", type:"csv", temp:"a,d,c,b", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
diff --git a/test/nodes/core/storage/23-watch_spec.js b/test/nodes/core/storage/23-watch_spec.js
index a9942d5b1..dd7b94037 100644
--- a/test/nodes/core/storage/23-watch_spec.js
+++ b/test/nodes/core/storage/23-watch_spec.js
@@ -15,11 +15,14 @@
**/
var fs = require("fs-extra");
+var os = require("os");
var path = require("path");
var should = require("should");
var helper = require("node-red-node-test-helper");
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() {
this.timeout(5000);
@@ -89,7 +92,10 @@ describe('watch Node', function() {
msg.should.have.property('payload', result.payload);
msg.should.have.property('type', result.type);
if('size' in result) {
- msg.should.have.property('size', result.size);
+ 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);
+ }
}
count++;
if(count === len) {
diff --git a/test/unit/@node-red/editor-api/lib/index_spec.js b/test/unit/@node-red/editor-api/lib/index_spec.js
index 7308fdc2d..37bb82c13 100644
--- a/test/unit/@node-red/editor-api/lib/index_spec.js
+++ b/test/unit/@node-red/editor-api/lib/index_spec.js
@@ -61,7 +61,7 @@ describe("api/index", function() {
should.not.exist(api.httpAdmin);
done();
});
- describe('initalises admin api without adminAuth', function(done) {
+ describe('initalises admin api without adminAuth', function() {
before(function() {
beforeEach();
api.init({},{},{},{});
@@ -78,7 +78,7 @@ describe("api/index", function() {
})
});
- describe('initalises admin api without editor', function(done) {
+ describe('initalises admin api without editor', function() {
before(function() {
beforeEach();
api.init({ disableEditor: true },{},{},{});
@@ -95,7 +95,7 @@ describe("api/index", function() {
})
});
- describe('initialises api with admin middleware', function(done) {
+ describe('initialises api with admin middleware', function() {
it('ignores non-function values',function(done) {
api.init({ httpAdminRoot: true, httpAdminMiddleware: undefined },{},{},{});
const middlewareFound = api.httpAdmin._router.stack.filter((layer) => layer.name === 'testMiddleware')
@@ -112,7 +112,7 @@ describe("api/index", function() {
});
});
- describe('initialises api with authentication enabled', function(done) {
+ describe('initialises api with authentication enabled', function() {
it('enables an oauth/openID based authentication mechanism',function(done) {
const stub = sinon.stub(apiAuth, 'genericStrategy').callsFake(function(){});
@@ -135,7 +135,7 @@ describe("api/index", function() {
});
- describe('initialises api with custom cors config', function (done) {
+ describe('initialises api with custom cors config', function () {
const httpAdminCors = {
origin: "*",
methods: "GET,PUT,POST,DELETE"
@@ -156,7 +156,7 @@ describe("api/index", function() {
})
});
- describe('editor start', function (done) {
+ describe('editor start', function () {
it('cannot be started when editor is disabled', function (done) {
const stub = sinon.stub(apiEditor, 'start').callsFake(function () {
diff --git a/test/unit/@node-red/registry/lib/localfilesystem_spec.js b/test/unit/@node-red/registry/lib/localfilesystem_spec.js
index 3b1bb63f3..3ce74c158 100644
--- a/test/unit/@node-red/registry/lib/localfilesystem_spec.js
+++ b/test/unit/@node-red/registry/lib/localfilesystem_spec.js
@@ -329,17 +329,36 @@ describe("red/nodes/registry/localfilesystem",function() {
localfilesystem.init({nodesDir:[nodesDir2]});
const nodeModule = localfilesystem.getModuleFiles();
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('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('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('version','1.0.0');
nodeModule['@test/testnode'].should.have.a.property('nodes');
nodeModule['@test/testnode'].should.have.a.property('path');
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('version','1.0.0');
nodeModule['testnode2'].should.have.a.property('nodes');
diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.html b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.html
new file mode 100644
index 000000000..617f48491
--- /dev/null
+++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.html
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.js
new file mode 100644
index 000000000..73579ba04
--- /dev/null
+++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.js
@@ -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);
+}
\ No newline at end of file
diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/package.json
new file mode 100644
index 000000000..6b6ce9aa9
--- /dev/null
+++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/package.json
@@ -0,0 +1,9 @@
+{
+ "name" : "@lowercase/lower-case2",
+ "node-red" : {
+ "nodes": {
+ "lower-case": "lower-case2/lower-case.js"
+ }
+ },
+ "version": "2.0.0"
+}
\ No newline at end of file
diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.html b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.html
new file mode 100644
index 000000000..e57d51131
--- /dev/null
+++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.html
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.js
new file mode 100644
index 000000000..006b35eb6
--- /dev/null
+++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.js
@@ -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);
+}
diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/package.json
new file mode 100644
index 000000000..a632eaddd
--- /dev/null
+++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/package.json
@@ -0,0 +1,9 @@
+{
+ "name" : "lower-case",
+ "node-red" : {
+ "nodes": {
+ "lower-case": "lower-case/lower-case.js"
+ }
+ },
+ "version": "1.0.0"
+}
diff --git a/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js b/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js
index 07e499344..136e4826c 100644
--- a/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js
+++ b/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js
@@ -33,6 +33,11 @@ describe("runtime-api/diagnostics", function() {
flowFile: "flows.json",
mqttReconnectTime: 321,
serialReconnectTime: 432,
+ socketReconnectTime: 2222,
+ socketTimeout: 3333,
+ tcpMsgQueueSize: 4444,
+ inboundWebSocketTimeout: 5555,
+ runtimeState: {enabled: true, ui: false},
adminAuth: {},//should be sanitised to "SET"
httpAdminRoot: "/admin/root/",
httpAdminCors: {},//should be sanitised to "SET"
@@ -45,6 +50,7 @@ describe("runtime-api/diagnostics", function() {
uiHost: "something.secret.com",//should be sanitised to "SET"
uiPort: 1337,//should be sanitised to "SET"
userDir: "/var/super/secret/",//should be sanitised to "SET",
+ nodesDir: "/var/super/secret/",//should be sanitised to "SET",
contextStorage: {
default : { module: "memory" },
file: { module: "localfilesystem" },
@@ -73,8 +79,9 @@ describe("runtime-api/diagnostics", function() {
//result.runtime.xxxxx
const runtimeCount = Object.keys(result.runtime).length;
- runtimeCount.should.eql(4);//ensure no more than 4 keys are present in runtime
+ runtimeCount.should.eql(5);//ensure 5 keys are present in runtime
result.runtime.should.have.property('isStarted',true)
+ result.runtime.should.have.property('flows')
result.runtime.should.have.property('modules').type("object");
result.runtime.should.have.property('settings').type("object");
result.runtime.should.have.property('version','7.7.7');
@@ -87,7 +94,7 @@ describe("runtime-api/diagnostics", function() {
//result.runtime.settings.xxxxx
const settingsCount = Object.keys(result.runtime.settings).length;
- settingsCount.should.eql(21);//ensure no more than the 21 settings listed below are present in the settings object
+ settingsCount.should.eql(27);//ensure no more than the 21 settings listed below are present in the settings object
result.runtime.settings.should.have.property('available',true);
result.runtime.settings.should.have.property('apiMaxLength', "UNSET");//deliberately disabled to ensure UNSET is returned
result.runtime.settings.should.have.property('debugMaxLength', 1111);
@@ -96,6 +103,11 @@ describe("runtime-api/diagnostics", function() {
result.runtime.settings.should.have.property('flowFile', "flows.json");
result.runtime.settings.should.have.property('mqttReconnectTime', 321);
result.runtime.settings.should.have.property('serialReconnectTime', 432);
+ result.runtime.settings.should.have.property('socketReconnectTime', 2222);
+ result.runtime.settings.should.have.property('socketTimeout', 3333);
+ result.runtime.settings.should.have.property('tcpMsgQueueSize', 4444);
+ result.runtime.settings.should.have.property('inboundWebSocketTimeout', 5555);
+ result.runtime.settings.should.have.property('runtimeState', {enabled: true, ui: false});
result.runtime.settings.should.have.property("adminAuth", "SET"); //should be sanitised to "SET"
result.runtime.settings.should.have.property("httpAdminCors", "SET"); //should be sanitised to "SET"
result.runtime.settings.should.have.property('httpAdminRoot', "/admin/root/");
@@ -109,6 +121,7 @@ describe("runtime-api/diagnostics", function() {
result.runtime.settings.should.have.property("uiPort", "SET"); //should be sanitised to "SET"
result.runtime.settings.should.have.property("userDir", "SET"); //should be sanitised to "SET"
result.runtime.settings.should.have.property('contextStorage').type("object");
+ result.runtime.settings.should.have.property('nodesDir', "SET")
//result.runtime.settings.contextStorage.xxxxx
const contextCount = Object.keys(result.runtime.settings.contextStorage).length;