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

Merge branch 'dev' into mqtt5

This commit is contained in:
Steve-Mcl 2020-10-25 09:52:33 +00:00
commit a0d197d0c0
29 changed files with 132 additions and 42 deletions

View File

@ -1,3 +1,19 @@
### 1.2.0: Milestone Release
Editor
- Fix selection of link node not existing within active workspace #2722 (@HiroyasuNishiyama)
- Fix import of merged flow
- Fix width of upload button in Safari #2718 (@HiroyasuNishiyama)
- Update Chinese translations #2719 (@JiyeYu)
- Update Japanese translations needed for 1.2 #2710 (@kazuhitoyokoi)
- Fix unexpected line break of sidebar tab name popover #2716 (@HiroyasuNishiyama)
- i18n module refresh tooltip #2717 (@HiroyasuNishiyama)
- Add better error message if context file gets corrupted
- Update info text of function node #2714 (@HiroyasuNishiyama)
- Use markdown editor if editText called with md mode
- Prevent group actions when in non-default mouse mode
### 1.2.0-beta.1: Beta Release
Editor

View File

@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "1.2.0-beta.1",
"version": "1.2.0",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@ -26,7 +26,7 @@
}
],
"dependencies": {
"ajv": "6.12.5",
"ajv": "6.12.6",
"async-mutex": "0.2.4",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
@ -73,7 +73,7 @@
"request": "2.88.0",
"semver": "6.3.0",
"tar": "6.0.5",
"uglify-js": "3.11.0",
"uglify-js": "3.11.2",
"when": "3.7.8",
"ws": "6.2.1",
"xml2js": "0.4.23"
@ -111,7 +111,7 @@
"mosca": "^2.8.3",
"node-red-node-test-helper": "^0.2.5",
"node-sass": "^4.14.1",
"nodemon": "2.0.4",
"nodemon": "2.0.5",
"should": "13.2.3",
"sinon": "1.17.7",
"stoppable": "^1.1.0",

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "1.2.0-beta.1",
"version": "1.2.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/util": "1.2.0-beta.1",
"@node-red/editor-client": "1.2.0-beta.1",
"@node-red/util": "1.2.0",
"@node-red/editor-client": "1.2.0",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
"clone": "2.1.2",

View File

@ -545,6 +545,7 @@
"sortRecent": "recent",
"more": "+ __count__ more",
"upload": "Upload module tgz file",
"refresh": "Refresh module list",
"errors": {
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",

View File

@ -545,6 +545,7 @@
"sortRecent": "日付順",
"more": "+ さらに __count__ 個",
"upload": "モジュールのtgzファイルをアップロード",
"refresh": "モジュールリスト更新",
"errors": {
"catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>",
"installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",

View File

@ -22,7 +22,8 @@
"color": "颜色",
"position": "位置",
"enable": "启用",
"disable": "禁用"
"disable": "禁用",
"upload": "上传"
},
"type": {
"string": "字符串",
@ -197,6 +198,8 @@
"flow_plural": "__count__ 个流程",
"subflow": "__count__ 个子流程",
"subflow_plural": "__count__ 子流程",
"replacedNodes": "__count__ 个节点被置换",
"replacedNodes_plural": "__count__ 个节点被置换",
"pasteNodes": "在这里粘贴节点",
"selectFile": "选择要导入的文件",
"importNodes": "导入节点",
@ -212,6 +215,9 @@
"groupCopied_plural": "已复制 __count__ 个groups",
"groupStyleCopied": "已复制组风格",
"invalidFlow": "无效的流程: __message__",
"recoveredNodes": "复原的节点",
"recoveredNodesInfo": "导入节点时此流上的节点缺少有效的流ID。 它们已被添加到此流中,您可以复原或删除它们。",
"recoveredNodesNotification": "<p>导入的节点缺少有效的流ID</p><p>已将它们添加到名为 '__flowName__'的新流中。</p>",
"export": {
"selected": "已选择的节点",
"current": "现在的节点",
@ -226,13 +232,19 @@
},
"import": {
"import": "导入到",
"importSelected": "导入所选项",
"importCopy": "导入副本",
"viewNodes": "查看节点",
"newFlow": "新流程",
"replace": "置换",
"errors": {
"notArray": "输入的不是JSON数组",
"itemNotObject": "输入的流无效 - 项目 __index__ 不是节点对象",
"missingId": "输入的流无效-项 __index__ 缺少'id'属性",
"missingType": "输入的流程无效-项 __index__ 缺少'类型'属性"
}
},
"conflictNotification1": "您要导入的某些节点已经存在于工作空间中。",
"conflictNotification2": "选择要导入的节点,并确认要替换现有的节点还是导入它们的副本"
},
"copyMessagePath": "已复制路径",
"copyMessageValue": "已复制数值",
@ -533,6 +545,7 @@
"sortAZ": "a-z顺序",
"sortRecent": "日期顺序",
"more": "增加 __count__ 个",
"upload": "上传模块tgz文件",
"errors": {
"catalogLoadFailed": "无法加载节点目录。<br>查看浏览器控制台了解更多信息",
"installFailed": "无法安装: __module__<br>__message__<br>查看日志了解更多信息",
@ -709,6 +722,12 @@
"committerTip": "保留空白以使用系统默认值",
"userName": "用户名",
"email": "电子邮件",
"workflow": "工作流",
"workfowTip": "选择您偏好的工作流",
"workflowManual": "手动",
"workflowManualTip": "所有更改都必须在“历史记录”侧边栏中手动提交",
"workflowAuto": "自动",
"workflowAutoTip": "每次部署后都会自动提交更改",
"sshKeys": "SSH密钥",
"sshKeysTip": "允许您创建到远程git存储库的安全连接。",
"add": "添加密钥",

View File

@ -22,7 +22,8 @@
"color": "顏色",
"position": "位置",
"enable": "啟用",
"disable": "禁用"
"disable": "禁用",
"upload": "上傳"
},
"type": {
"string": "字符串",
@ -197,6 +198,8 @@
"flow_plural": "__count__ 多流程",
"subflow": "__count__ 子流程",
"subflow_plural": "__count__ 多子流程",
"replacedNodes": "__count__ 個節點被置換",
"replacedNodes_plural": "__count__ 個節點被置換",
"pasteNodes": "在這裡粘貼節點",
"selectFile": "匯入所選檔案",
"importNodes": "匯入節點",
@ -212,6 +215,9 @@
"groupCopied_plural": "已複製 __count__ 個groups",
"groupStyleCopied": "已複製組風格",
"invalidFlow": "無效的流程: __message__",
"recoveredNodes": "復原的節點",
"recoveredNodesInfo": "導入節點時此流上的節點缺少有效的流ID。它們已被添加到此流中您可以復原或刪除它們。",
"recoveredNodesNotification": "<p>導入的節點缺少有效的流ID</p><p>已將它們添加到名為 '__flowName__'的新流中。</p>",
"export": {
"selected": "已選擇的節點",
"current": "現在的節點",
@ -226,13 +232,19 @@
},
"import": {
"import": "匯入到",
"importSelected": "導入所選項",
"importCopy": "導入副本",
"viewNodes": "查看節點",
"newFlow": "新流程",
"replace": "置換",
"errors": {
"notArray": "輸入的不是JSON數組",
"itemNotObject": "輸入的流程無效-項目 __index__ 不是節點對象",
"missingId": "輸入的流程無效-項 __index__ 缺少“ id”屬性",
"missingType": "輸入的流程無效-項 __index__ 缺少“類型”屬性"
}
},
"conflictNotification1": "您要導入的某些節點已經存在於工作空間中。",
"conflictNotification2": "選擇要導入的節點,並確認要替換現有的節點還是導入它們的副本"
},
"copyMessagePath": "已複製路徑",
"copyMessageValue": "已複製數值",
@ -533,6 +545,7 @@
"sortAZ": "a-z順序",
"sortRecent": "日期順序",
"more": "增加 __count__ 個",
"upload": "上傳模塊tgz文件",
"errors": {
"catalogLoadFailed": "無法載入節點目錄。<br>查看瀏覽器控制臺瞭解更多資訊",
"installFailed": "無法安裝: __module__<br>__message__<br>查看日誌瞭解更多資訊",
@ -709,6 +722,12 @@
"committerTip": "保留空白以使用系統默認值",
"userName": "用戶名",
"email": "電子郵件",
"workflow": "工作流",
"workfowTip": "選擇您偏好的工作流",
"workflowManual": "手動",
"workflowManualTip": "所有更改都必須在“歷史記錄”側邊欄中手動提交",
"workflowAuto": "自動",
"workflowAutoTip": "每次部署後都會自動提交更改",
"sshKeys": "SSH密鑰",
"sshKeysTip": "允許您創建到遠程git存儲庫的安全連接。",
"add": "添加密鑰",

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/editor-client",
"version": "1.2.0-beta.1",
"version": "1.2.0",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@ -1842,6 +1842,8 @@ RED.nodes = (function() {
});
defaultWorkspace = null;
initialLoad = null;
workspaces = {};
RED.nodes.dirty(false);
RED.view.redraw(true, true);
RED.palette.refresh();

View File

@ -84,6 +84,7 @@ RED.popover = (function() {
var targetHeight = target.outerHeight();
var divHeight = div.height();
var divWidth = div.width();
var paddingRight = 10;
var viewportTop = $(window).scrollTop();
var viewportLeft = $(window).scrollLeft();
@ -105,7 +106,7 @@ RED.popover = (function() {
d = "right";
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
left = targetPos.left+targetWidth+deltaSizes[size].leftRight;
} else if (left+divWidth > viewportRight) {
} else if (left+divWidth+paddingRight > viewportRight) {
d = "left";
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
left = targetPos.left-deltaSizes[size].leftLeft-divWidth;

View File

@ -617,6 +617,7 @@ RED.tabs = (function() {
}
},
stop: function(event,ui) {
dragActive = false;
collapsedButtonsRow.children().css({position:"relative",left:"",transition:""});
$(".red-ui-tab-link-buttons").width('auto');
pinnedLink.css({zIndex:""});

View File

@ -1411,7 +1411,7 @@ RED.diff = (function() {
// Restore the original flow so subsequent merge resolutions can properly
// identify new-vs-old
RED.nodes.originalFlow(originalFlow);
imported[0].forEach(function(n) {
imported.nodes.forEach(function(n) {
if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
n.changed = true;
}

View File

@ -2788,7 +2788,13 @@ RED.editor = (function() {
editExpression: function(options) { showTypeEditor("_expression", options) },
editJSON: function(options) { showTypeEditor("_json", options) },
editMarkdown: function(options) { showTypeEditor("_markdown", options) },
editText: function(options) { showTypeEditor("_text", options) },
editText: function(options) {
if (options.mode == "markdown") {
showTypeEditor("_markdown", options)
} else {
showTypeEditor("_text", options)
}
},
editBuffer: function(options) { showTypeEditor("_buffer", options) },
buildEditForm: buildEditForm,
validateNode: validateNode,

View File

@ -796,7 +796,7 @@ RED.palette.editor = (function() {
loadedIndex = {};
initInstallTab();
})
RED.popover.tooltip(refreshButton,"NLS-TODO: Refresh module list");
RED.popover.tooltip(refreshButton,RED._("palette.editor.refresh"));
packageList = $('<ol>',{style:"position: absolute;top: 79px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({
addButton: false,

View File

@ -1892,7 +1892,8 @@ RED.view = (function() {
activeLinkNodes = {};
for (var i=0;i<movingSet.length();i++) {
var msn = movingSet.get(i);
if (msn.n.type === "link out" || msn.n.type === "link in") {
if ((msn.n.type === "link out" || msn.n.type === "link in") &&
(msn.n.z === activeWorkspace)) {
var linkNode = msn.n;
activeLinkNodes[linkNode.id] = linkNode;
var offFlowLinks = {};

View File

@ -253,6 +253,9 @@ button.red-ui-palette-editor-upload-button {
min-width: 0;
padding: 2px 8px;
}
form {
width: 0;
}
}
.red-ui-palette-editor-upload {
display: none;

View File

@ -23,6 +23,7 @@
to return nothing in order to halt a flow.</p>
<p>The <b>Setup</b> tab contains code that will be run whenever the node is started.
The <b>Close</b> tab contains code that will be run when the node is stopped.</p>
<p>If an promise object is returned from the setup code, input message processing starts after its completion.</p>
<h3>Details</h3>
<p>See the <a target="_blank" href="http://nodered.org/docs/writing-functions.html">online documentation</a>
for more information on writing functions.</p>
@ -34,6 +35,7 @@
<li>a single message object - passed to nodes connected to the first output</li>
<li>an array of message objects - passed to nodes connected to the corresponding outputs</li>
</ul>
<p>Note: The setup code is executed during the initialization of nodes. Therefore, if <code>node.send</code> is called in the setup tab, subsequent nodes may not be able to receive the message.</p>
<p>If any element of the array is itself an array of messages, multiple
messages are sent to the corresponding output.</p>
<p>If null is returned, either by itself or as an element of the array, no

View File

@ -20,6 +20,7 @@
<p><code>msg</code>オブジェクトは<code>msg.payload</code>プロパティにメッセージ本体を保持するのが慣例です。</p>
<p>通常、コードはメッセージオブジェクト(もしくは複数のメッセージオブジェクト)を返却します。後続フローの実行を停止したい場合は、オブジェクトを返却しなくてもかまいません。</p>
<p>Node-REDの開始時もしくはフローの設定をデプロイした際実行される初期化コードを<b>初期化処理</b>タブに、ノードの停止もしくは再デプロイ時に実行される終了処理コードを<b>終了処理</b>タブに指定できます。</p>
<p>初期化処理タブの返却値としてPromiseを返却すると、入力メッセージの処理を開始する前にその完了を待ちます。</p>
<h3>詳細</h3>
<p>コードの書き方の詳細については、<a target="_blank" href="http://nodered.org/docs/writing-functions.html">オンラインドキュメント</a>を参照してください。</p>
<h4>メッセージの送信</h4>
@ -29,6 +30,7 @@
<li>単一メッセージオブジェクト - 最初の出力に接続されたノードに渡されます</li>
<li>メッセージオブジェクトの配列 - 対応する出力に接続されたノードに渡されます</li>
</ul>
<p>注: 初期化処理の実行はードの初期化中に行われます。そのため、初期化処理タブにsendを記述した場合に後続ードでメッセージを受け取れないことがあります。</p>
<p>配列要素が配列の場合には、複数のメッセージを対応する出力に送出します。</p>
<p>返却方法が単一値か配列要素かにかかわらず、返却値がnullの場合メッセージの送出は行いません。</p>
<h4>ログ出力とエラー処理</h4>

View File

@ -19,6 +19,8 @@
<h3>输入</h3>
<dl class="message-properties">
<dt class="optional">delay <span class="property-type">数值</span></dt>
<dd>设置要应用于消息的延迟(以毫秒为单位)。仅当节点配置为允许消息去覆盖设置的的默认延迟间隔时,此选项才适用。</dd>
<dt class="optional">reset</dt>
<dd>如果收到带有此属性的消息,则将清除当前正在进行的任何超时或重复,且不会触发任何消息。</dd>
</dl>
@ -27,6 +29,7 @@
<p>该节点可用于在流中创建一个超时。 默认情况下,当它收到一条消息时,它将发送一条带有<code>1</code>的有效荷载的消息。然后它将等待250毫秒再发送第二条消息其有效荷载为<code>0</code>。这可以用于使连接到Raspberry Pi GPIO引脚的LED闪烁等例子上。</p>
<p>可以将发送的每个消息的有效荷载配置为各种值,包括不发送任何内容的选项。例如,将初始消息设置为<i>nothing</i>,然后选择将计时器与每个收到的消息一起扩展的选项,则该节点将充当看门狗计时器;仅在设置的间隔内未收到任何消息时才发送消息。</p>
<p>如果设置为<i>字符串</i>类型,则该节点支持<i>mustache</i>模板语法。</p>
<p>如果节点中启用了该选项,则可以通过<code> msg.delay </code>覆盖发送消息之间的延迟。该值必须以毫秒为单位。</p>
<p>如果节点收到具有<code>reset</code>属性或与节点中配置的匹配的<code>有效荷载</code>的消息,则将清除当前正在进行的任何超时或重复,并且不会触发任何消息。</p>
<p>可以将节点配置为以固定的时间间隔重新发送消息,直到被收到的消息重置为止。</p>
<p>(可选)可以将节点配置为将带有<code>msg.topic</code>的消息视为独立的流。</p>

View File

@ -321,6 +321,7 @@
"h": "小时"
},
"extend": " 如有新信息,延长延迟",
"override": "使用msg.delay覆盖延迟时间",
"second": " 发送第二条消息到单独的输出",
"label": {
"trigger": "触发",

View File

@ -19,6 +19,8 @@
<h3>输入</h3>
<dl class="message-properties">
<dt class="optional">delay <span class="property-type">数值</span></dt>
<dd>設置要應用於消息的延遲(以毫秒為單位)。僅當節點配置為允許消息去覆蓋設置的的默認延遲間隔時,此選項才適用。</dd>
<dt class="optional">reset</dt>
<dd>如果收到带有此属性的消息,则将清除当前正在进行的任何超时或重复,且不会触发任何消息。</dd>
</dl>
@ -27,6 +29,7 @@
<p>该节点可用于在流程中创建一个超时。 默认情况下,当它收到一条消息时,它将发送一条带有<code>1</code>的有效荷载的消息。然后它将等待250毫秒再发送第二条消息其有效荷载为<code>0</code>。这可以用于使连接到Raspberry Pi GPIO引脚的LED闪烁等例子上。</p>
<p>可以将发送的每个消息的有效荷载配置为各种值,包括不发送任何内容的选项。例如,将初始消息设置为<i>nothing</i>,然后选择将计时器与每个收到的消息一起扩展的选项,则该节点将充当看门狗计时器;仅在设置的间隔内未收到任何消息时才发送消息。</p>
<p>如果设置为<i>字符串</i>类型,则该节点支持<i>mustache</i>模板语法。</p>
<p>如果節點中啟用了該選項,則可以通過<code> msg.delay </code>覆蓋發送消息之間的延遲。該值必須以毫秒為單位。</p>
<p>如果节点收到具有<code>reset</code>属性或与节点中配置的匹配的<code>有效荷载</code>的消息,则将清除当前正在进行的任何超时或重复,并且不会触发任何消息。</p>
<p>可以将节点配置为以固定的时间间隔重新发送消息,直到被收到的消息重置为止。</p>
<p>(可选)可以将节点配置为将带有<code>msg.topic</code>的消息视为独立的流程。</p>

View File

@ -321,6 +321,7 @@
"h": "小時"
},
"extend": " 如有新資訊,延長延遲",
"override": "使用msg.delay覆蓋延遲時間",
"second": " 發送第二條消息到單獨的輸出",
"label": {
"trigger": "觸發",

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "1.2.0-beta.1",
"version": "1.2.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
@ -15,7 +15,7 @@
}
],
"dependencies": {
"ajv": "6.12.5",
"ajv": "6.12.6",
"body-parser": "1.19.0",
"cheerio": "0.22.0",
"content-type": "1.0.4",

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
"version": "1.2.0-beta.1",
"version": "1.2.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@ -16,10 +16,10 @@
}
],
"dependencies": {
"@node-red/util": "1.2.0-beta.1",
"@node-red/util": "1.2.0",
"semver": "6.3.0",
"tar": "6.0.5",
"uglify-js": "3.11.0",
"uglify-js": "3.11.2",
"when": "3.7.8"
}
}

View File

@ -137,16 +137,15 @@ function stringify(value) {
return { json: result, circular: hasCircular };
}
function writeFileAtomic(storagePath, content) {
async function writeFileAtomic(storagePath, content) {
// To protect against file corruption, write to a tmp file first and then
// rename to the destination file
let finalFile = storagePath + ".json";
let tmpFile = finalFile + "."+Date.now()+".tmp";
return fs.outputFile(tmpFile, content, "utf8").then(function() {
await fs.outputFile(tmpFile, content, "utf8");
return fs.rename(tmpFile,finalFile);
})
}
function LocalFileSystem(config){
this.config = config;
this.storageBaseDir = getBasePath(this.config);
@ -169,6 +168,7 @@ LocalFileSystem.prototype.open = function(){
if (this.cache) {
var scopes = [];
var promises = [];
var contextFiles = [];
return listFiles(self.storageBaseDir).then(function(files) {
files.forEach(function(file) {
var parts = file.split(path.sep);
@ -179,15 +179,22 @@ LocalFileSystem.prototype.open = function(){
} else {
scopes.push(parts[1].substring(0,parts[1].length-5)+":"+parts[0]);
}
promises.push(loadFile(path.join(self.storageBaseDir,file)));
let contextFile = path.join(self.storageBaseDir,file);
contextFiles.push(contextFile)
promises.push(loadFile(contextFile));
})
return Promise.all(promises);
}).then(function(res) {
scopes.forEach(function(scope,i) {
try {
var data = res[i]?JSON.parse(res[i]):{};
Object.keys(data).forEach(function(key) {
self.cache.set(scope,key,data[key]);
})
} catch(err) {
let error = new Error(log._("context.localfilesystem.invalid-json",{file: contextFiles[i]}))
throw error;
}
});
}).catch(function(err){
if(err.code == 'ENOENT') {

View File

@ -176,6 +176,7 @@
"error-invalid-default-module": "Default context store unknown: '__storage__'",
"unknown-store": "Unknown context store '__name__' specified. Using default store.",
"localfilesystem": {
"invalid-json": "Invalid JSON in context file '__file__'",
"error-circular": "Context __scope__ contains a circular reference that cannot be persisted",
"error-write": "Error writing context: __message__"
}

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "1.2.0-beta.1",
"version": "1.2.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/registry": "1.2.0-beta.1",
"@node-red/util": "1.2.0-beta.1",
"@node-red/registry": "1.2.0",
"@node-red/util": "1.2.0",
"async-mutex": "0.2.4",
"clone": "2.1.2",
"express": "4.17.1",

View File

@ -1,6 +1,6 @@
{
"name": "@node-red/util",
"version": "1.2.0-beta.1",
"version": "1.2.0",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "1.2.0-beta.1",
"version": "1.2.0",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@ -31,10 +31,10 @@
"flow"
],
"dependencies": {
"@node-red/editor-api": "1.2.0-beta.1",
"@node-red/runtime": "1.2.0-beta.1",
"@node-red/util": "1.2.0-beta.1",
"@node-red/nodes": "1.2.0-beta.1",
"@node-red/editor-api": "1.2.0",
"@node-red/runtime": "1.2.0",
"@node-red/util": "1.2.0",
"@node-red/nodes": "1.2.0",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"express": "4.17.1",