Compare commits

..

64 Commits

Author SHA1 Message Date
Nick O'Leary
c294532152 Fix undo history of moves and post-deploy handling 2024-04-23 22:29:42 +02:00
Nick O'Leary
960af87fb0 Ensure subflow change state is cleared after deploy 2024-04-23 21:17:35 +02:00
Nick O'Leary
de7339ae97 Fix undo of subflow env property edits 2024-04-23 20:39:14 +02:00
Stephen McLaughlin
0995af62b6 Merge pull request #4664 from ZJvandeWeg/patch-3
docs: Add closing paragraph tag
2024-04-20 13:54:37 +01:00
Zeger-Jan van de Weg
c2e03a40b4 docs: Add closing paragraph tag
Minor change that only improves xpath parsing.
2024-04-20 14:20:59 +02:00
Nick O'Leary
29ed5b2792 Merge pull request #4655 from node-red/rel319
Bump for 3.1.9 release
2024-04-11 19:22:24 +01:00
Nick O'Leary
e39216e65a Bump for 3.1.9 release 2024-04-11 19:15:46 +01:00
Nick O'Leary
7ac7f9b4c8 Merge pull request #4654 from node-red/fix-subflow-recursion-check
Prevent subflow being added to itself
2024-04-11 19:12:43 +01:00
Stephen McLaughlin
4709eb9d49 Merge pull request #4652 from node-red/fix-windows-spawn
Fix use of spawn on windows with cmd files
2024-04-11 17:51:13 +01:00
Nick O'Leary
c13b8266dd Prevent subflow being added to itself 2024-04-11 17:05:10 +01:00
Nick O'Leary
bd58431603 Fix use of spawn on windows with cmd files 2024-04-11 14:40:29 +01:00
Nick O'Leary
9a3cb0b2b5 Merge pull request #4640 from node-red/fix-subflow-init-err
Guard refresh of unknown subflow
2024-04-02 20:06:47 +01:00
Nick O'Leary
6beae5a806 Merge pull request #4642 from node-red/4641-fix-subflow-module-debug-logging
Fix subflow module sending messages to debug sidebar
2024-04-02 20:06:31 +01:00
Nick O'Leary
a0636632a1 Fix subflow module sending messages to debug sidebar
Fixes #4641
2024-04-02 17:42:19 +01:00
Nick O'Leary
5dfa47ab6c Guard refresh of unknown subflow 2024-04-02 15:54:34 +01:00
Nick O'Leary
ade4679e8c Merge pull request #4636 from node-red/rel318
Bump for 3.1.8
2024-03-28 15:23:07 +00:00
Nick O'Leary
410b938442 Bump for 3.1.8 2024-03-28 15:02:02 +00:00
Nick O'Leary
19dcc3a683 Merge pull request #4632 from node-red/4625-sf-env-err-handling
Add validation and error handling on subflow instance properties
2024-03-28 11:10:28 +00:00
Nick O'Leary
20d067c1ea Merge pull request #4633 from node-red/4617-hide-library-context-options
Hide import/export context menu if disabled in theme
2024-03-28 11:10:14 +00:00
Nick O'Leary
9526566799 Hide import/export context menu if disabled in theme 2024-03-28 11:00:10 +00:00
Nick O'Leary
0b9dd82c91 Merge pull request #4631 from node-red/4626-subflow-change-notification
Show change indicator on subflow tabs
2024-03-27 19:10:39 +00:00
Nick O'Leary
19213434f9 Add validation to subflow instance env properties 2024-03-27 19:08:25 +00:00
Nick O'Leary
014691346a Handle malformed env var values and log errors 2024-03-27 18:23:12 +00:00
Nick O'Leary
6738b95c29 Merge pull request #4630 from node-red/bump-express
Bump dependencies
2024-03-27 18:11:54 +00:00
Nick O'Leary
6a8230ec1e Show change icon on subflow tabs
Fixes #4626
2024-03-27 18:10:04 +00:00
Nick O'Leary
5679d264b6 Bump dependencies 2024-03-27 18:00:06 +00:00
Nick O'Leary
37265cf4ef Merge pull request #4619 from node-red/4600-reset-workspace-index
Reset workspace index when clearing nodes
2024-03-21 17:38:39 +00:00
Nick O'Leary
8a63275989 Merge pull request #4613 from kazuhitoyokoi/master-fixglobalconfig
Remove typo in global config
2024-03-21 16:54:01 +00:00
Nick O'Leary
7fc64a84e8 Bump test helper 2024-03-21 15:16:49 +00:00
Nick O'Leary
02f7cdd5aa Ensure all httpRequest test servers are ready before tests run 2024-03-21 15:03:37 +00:00
Nick O'Leary
d7dcceef60 Add debug for http tests 2024-03-21 11:32:29 +00:00
Nick O'Leary
ae5e1570ae Reset workspace index when clearing nodes
Fixes #4600
2024-03-21 11:14:34 +00:00
Kazuhito Yokoi
3ca045394a Remove typo in global config 2024-03-16 18:51:13 +09:00
Nick O'Leary
179032cd4d Merge pull request #4608 from node-red/rel317
Bump for 3.1.7 release
2024-03-12 17:43:32 +00:00
Nick O'Leary
6a6f0d04d6 Bump for 3.1.7 release 2024-03-12 14:25:41 +00:00
Nick O'Leary
add4d9758c Merge pull request #4603 from kazuhitoyokoi/master-addjpn
Add Japanese translation for v3.1.6
2024-03-11 16:07:28 +00:00
Kazuhito Yokoi
a0d3ea62b2 Add Japanese translation for v3.1.6 2024-03-10 23:36:20 +09:00
Nick O'Leary
7447e88a50 Merge pull request #4593 from hardillb/hardillb-patch-1
Update jsonata version
2024-03-07 14:26:02 +00:00
Ben Hardill
a193b79d3d Bump jsonata to match utils 2024-03-05 10:31:03 +00:00
Ben Hardill
da380f7464 Update jsonata version
Pulls in fix for CVE-2024-27307
2024-03-05 10:22:49 +00:00
Nick O'Leary
269cf02c0b Merge pull request #4586 from node-red/rel316
Bump for 3.1.6 release
2024-03-01 11:47:57 +00:00
Nick O'Leary
fb50e2772a Bump for 3.1.6 release 2024-03-01 10:50:06 +00:00
Nick O'Leary
058c97138a Merge pull request #4582 from node-red/3795-allow-env-var-in-num-field-validation
Do not flag env var in num typedInput as error
2024-02-26 17:01:45 +00:00
Nick O'Leary
828ae29aed Merge pull request #4581 from node-red/4579-fix-undef-env-vars
Handle undefined env vars
2024-02-26 17:01:27 +00:00
Nick O'Leary
6a0f45140c Merge pull request #4568 from JaysonHurst/fips
fix: Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 …
2024-02-26 17:00:26 +00:00
Nick O'Leary
50a267528d Merge pull request #4580 from giscafer/remove-never-use-code
chore: remove never use import code
2024-02-26 16:58:20 +00:00
Nick O'Leary
220786be60 Do not flag env var in num typedInput as error 2024-02-26 16:55:01 +00:00
Nick O'Leary
fa78bb3d78 Handle undefined env vars
Fixes #4579
2024-02-26 16:17:09 +00:00
Nick O'Leary
9a32ebd0c0 Merge pull request #4578 from kazuhitoyokoi/master-fiximportdialog
Fix example flow name in import dialog
2024-02-26 16:09:10 +00:00
giscafer
4643f5e8cc chore: remove never use import code 2024-02-25 22:44:01 +08:00
Kazuhito Yokoi
7de0984d6d Update test case for example flow name 2024-02-25 17:38:46 +09:00
Kazuhito Yokoi
635334f096 Fix example flow name in import dialog 2024-02-25 17:04:42 +09:00
Nick O'Leary
f0d0990b5a Merge pull request #4575 from giscafer/master
fix: template node zh-CN translation
2024-02-22 13:03:24 +00:00
giscafer
43b3589451 fix: template node zh-CN translation 2024-02-22 13:02:06 +08:00
Nick O'Leary
016a19ba7c Merge pull request #4570 from node-red/fix-icon-scaling
Fix missing node icons in workspace
2024-02-20 10:37:33 +00:00
Nick O'Leary
aeb79bce2a Fix missing node icons in workspace 2024-02-19 16:07:22 +00:00
Jayson Hurst
0ab9b9a5fd Merge branch 'master' into fips 2024-02-16 17:53:30 -07:00
Jayson Hurst
56e58521bd Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 crypto hashes to work with the FIPS crypto policy. 2024-02-17 00:35:03 +00:00
Nick O'Leary
b10ef4c98c Merge pull request #4564 from node-red/rel315
Bump for 3.1.5 release
2024-02-08 15:37:48 +00:00
Nick O'Leary
3ff038fb98 Bump for 3.1.5 release 2024-02-08 15:32:53 +00:00
Nick O'Leary
adb498af24 Merge pull request #4562 from node-red/fix-require
Fix require of dns module
2024-02-07 15:24:10 +00:00
Nick O'Leary
fc67a2efc2 Merge pull request #4561 from node-red/4560-fix-global-env-cred
Ensure global creds object is initialised when adding first cred
2024-02-07 14:52:17 +00:00
Nick O'Leary
55771c7241 Fix require of dns module 2024-02-07 14:50:46 +00:00
Nick O'Leary
109fa5f04e Ensure global creds object is initialised when adding first cred 2024-02-07 10:02:22 +00:00
48 changed files with 413 additions and 225 deletions

View File

@@ -1,3 +1,49 @@
#### 3.1.9: Maintenance Release
- Prevent subflow being added to itself (#4654) @knolleary
- Fix use of spawn on windows with cmd files (#4652) @knolleary
- Guard refresh of unknown subflow (#4640) @knolleary
- Fix subflow module sending messages to debug sidebar (#4642) @knolleary
#### 3.1.8: Maintenance Release
- Add validation and error handling on subflow instance properties (#4632) @knolleary
- Hide import/export context menu if disabled in theme (#4633) @knolleary
- Show change indicator on subflow tabs (#4631) @knolleary
- Bump dependencies (#4630) @knolleary
- Reset workspace index when clearing nodes (#4619) @knolleary
- Remove typo in global config (#4613) @kazuhitoyokoi
#### 3.1.7: Maintenance Release
- Add Japanese translation for v3.1.6 (#4603) @kazuhitoyokoi
- Update jsonata version (#4593) @hardillb
#### 3.1.6: Maintenance Release
Editor
- Do not flag env var in num typedInput as error (#4582) @knolleary
- Fix example flow name in import dialog (#4578) @kazuhitoyokoi
- Fix missing node icons in workspace (#4570) @knolleary
Runtime
- Handle undefined env vars (#4581) @knolleary
- fix: Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 … (#4568) @JaysonHurst
- chore: remove never use import code (#4580) @giscafer
Nodes
- fix: template node zh-CN translation (#4575) @giscafer
#### 3.1.5: Maintenance Release
Runtime
- Fix require of dns module (#4562) @knolleary
- Ensure global creds object is initialised when adding first cred (#4561) @knolleary
#### 3.1.4: Maintenance Release #### 3.1.4: Maintenance Release
Editor Editor

View File

@@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "3.1.4", "version": "3.1.9",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org", "homepage": "https://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -41,7 +41,7 @@
"cors": "2.8.5", "cors": "2.8.5",
"cronosjs": "1.7.1", "cronosjs": "1.7.1",
"denque": "2.1.0", "denque": "2.1.0",
"express": "4.18.2", "express": "4.19.2",
"express-session": "1.17.3", "express-session": "1.17.3",
"form-data": "4.0.0", "form-data": "4.0.0",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
@@ -54,7 +54,7 @@
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.8.6", "jsonata": "1.8.7",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0", "media-typer": "1.1.0",
"memorystore": "1.6.7", "memorystore": "1.6.7",
@@ -64,7 +64,7 @@
"mqtt": "4.3.7", "mqtt": "4.3.7",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",
"mustache": "4.2.0", "mustache": "4.2.0",
"node-red-admin": "^3.1.2", "node-red-admin": "^3.1.3",
"node-watch": "0.7.4", "node-watch": "0.7.4",
"nopt": "5.0.0", "nopt": "5.0.0",
"oauth2orize": "1.11.1", "oauth2orize": "1.11.1",
@@ -74,7 +74,7 @@
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"raw-body": "2.5.2", "raw-body": "2.5.2",
"semver": "7.5.4", "semver": "7.5.4",
"tar": "6.1.13", "tar": "6.2.1",
"tough-cookie": "4.1.3", "tough-cookie": "4.1.3",
"uglify-js": "3.17.4", "uglify-js": "3.17.4",
"uuid": "9.0.0", "uuid": "9.0.0",
@@ -112,7 +112,7 @@
"mermaid": "^10.4.0", "mermaid": "^10.4.0",
"minami": "1.2.3", "minami": "1.2.3",
"mocha": "9.2.2", "mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.2", "node-red-node-test-helper": "^0.3.3",
"nodemon": "2.0.20", "nodemon": "2.0.20",
"proxy": "^1.0.2", "proxy": "^1.0.2",
"sass": "1.62.1", "sass": "1.62.1",

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var apiUtils = require("../util");
var runtimeAPI; var runtimeAPI;
var settings; var settings;
var theme = require("../editor/theme"); var theme = require("../editor/theme");

View File

@@ -18,7 +18,6 @@ var BearerStrategy = require('passport-http-bearer').Strategy;
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy; var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
var passport = require("passport"); var passport = require("passport");
var crypto = require("crypto");
var util = require("util"); var util = require("util");
var Tokens = require("./tokens"); var Tokens = require("./tokens");

View File

@@ -14,11 +14,9 @@
* limitations under the License. * limitations under the License.
**/ **/
var express = require("express");
var path = require('path'); var path = require('path');
var comms = require("./comms"); var comms = require("./comms");
var library = require("./library");
var info = require("./settings"); var info = require("./settings");
var auth = require("../auth"); var auth = require("../auth");

View File

@@ -15,8 +15,6 @@
**/ **/
var apiUtils = require("../util"); var apiUtils = require("../util");
var fs = require('fs');
var fspath = require('path');
var runtimeAPI; var runtimeAPI;

View File

@@ -13,9 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var fs = require('fs');
var path = require('path');
// var apiUtil = require('../util');
var i18n = require("@node-red/util").i18n; // TODO: separate module var i18n = require("@node-red/util").i18n; // TODO: separate module

View File

@@ -15,7 +15,6 @@
**/ **/
var apiUtils = require("../util"); var apiUtils = require("../util");
var express = require("express");
var runtimeAPI; var runtimeAPI;
var settings; var settings;

View File

@@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
var express = require("express");
var util = require("util"); var util = require("util");
var path = require("path"); var path = require("path");
var fs = require("fs"); var fs = require("fs");

View File

@@ -99,7 +99,7 @@ module.exports = {
// settings.instanceId is set asynchronously to the editor-api // settings.instanceId is set asynchronously to the editor-api
// being initiaised. So we defer calculating the cacheBuster hash // being initiaised. So we defer calculating the cacheBuster hash
// until the first load of the editor // until the first load of the editor
cacheBuster = crypto.createHash('md5').update(`${settings.version || 'version'}-${settings.instanceId || 'instanceId'}`).digest("hex").substring(0,12) cacheBuster = crypto.createHash('sha1').update(`${settings.version || 'version'}-${settings.instanceId || 'instanceId'}`).digest("hex").substring(0,12)
} }
let sessionMessages; let sessionMessages;

View File

@@ -24,11 +24,8 @@
* @namespace @node-red/editor-api * @namespace @node-red/editor-api
*/ */
var express = require("express");
var bodyParser = require("body-parser"); var bodyParser = require("body-parser");
var util = require('util');
var passport = require('passport'); var passport = require('passport');
var cors = require('cors');
var auth = require("./auth"); var auth = require("./auth");
var apiUtil = require("./util"); var apiUtil = require("./util");

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-api", "name": "@node-red/editor-api",
"version": "3.1.4", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,14 +16,14 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "3.1.4", "@node-red/util": "3.1.9",
"@node-red/editor-client": "3.1.4", "@node-red/editor-client": "3.1.9",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.20.2", "body-parser": "1.20.2",
"clone": "2.1.2", "clone": "2.1.2",
"cors": "2.8.5", "cors": "2.8.5",
"express-session": "1.17.3", "express-session": "1.17.3",
"express": "4.18.2", "express": "4.19.2",
"memorystore": "1.6.7", "memorystore": "1.6.7",
"mime": "3.0.0", "mime": "3.0.0",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",

View File

@@ -303,7 +303,8 @@
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません" "missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
}, },
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。", "conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。" "conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。",
"alreadyExists": "本ノードは既に存在"
}, },
"copyMessagePath": "パスをコピーしました", "copyMessagePath": "パスをコピーしました",
"copyMessageValue": "値をコピーしました", "copyMessageValue": "値をコピーしました",

View File

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

View File

@@ -706,11 +706,36 @@ RED.history = (function() {
} }
function markEventDirty (evt) {
// This isn't 100% thorough - just covers the main move/edit/delete cases
evt.dirty = true
if (evt.multi) {
for (let i = 0; i < evt.events.length-1; i++) {
markEventDirty(evt.events[i])
}
} else if (evt.t === 'move') {
for (let i=0;i<evt.nodes.length;i++) {
evt.nodes[i].moved = true
}
} else if (evt.t === 'edit') {
evt.changed = true
} else if (evt.t === 'delete') {
if (evt.nodes) {
for (let i=0;i<evt.nodes.length;i++) {
evt.nodes[i].changed = true
}
}
}
}
return { return {
//TODO: this function is a placeholder until there is a 'save' event that can be listened to
markAllDirty: function() { markAllDirty: function() {
for (var i=0;i<undoHistory.length;i++) { // A deploy has happened meaning any undo into the history will represent
// an undeployed change - regardless of what it was when the event was recorded.
// This goes back through the history any marks them all as being dirty events
// and also ensures individual node states are marked dirty
for (let i=0;i<undoHistory.length;i++) {
undoHistory[i].dirty = true; undoHistory[i].dirty = true;
markEventDirty(undoHistory[i])
} }
}, },
list: function() { list: function() {

View File

@@ -547,12 +547,16 @@ RED.nodes = (function() {
* @param {String} z tab id * @param {String} z tab id
*/ */
checkTabState: function (z) { checkTabState: function (z) {
const ws = workspaces[z] const ws = workspaces[z] || subflows[z]
if (ws) { if (ws) {
const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0 const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0
if (Boolean(ws.contentsChanged) !== contentsChanged) { if (Boolean(ws.contentsChanged) !== contentsChanged) {
ws.contentsChanged = contentsChanged ws.contentsChanged = contentsChanged
if (ws.type === 'tab') {
RED.events.emit("flows:change", ws); RED.events.emit("flows:change", ws);
} else {
RED.events.emit("subflows:change", ws);
}
} }
} }
} }
@@ -1025,7 +1029,22 @@ RED.nodes = (function() {
RED.nodes.registerType("subflow:"+sf.id, { RED.nodes.registerType("subflow:"+sf.id, {
defaults:{ defaults:{
name:{value:""}, name:{value:""},
env:{value:[]} env:{value:[], validate: function(value) {
const errors = []
if (value) {
value.forEach(env => {
const r = RED.utils.validateTypedProperty(env.value, env.type)
if (r !== true) {
errors.push(env.name+': '+r)
}
})
}
if (errors.length === 0) {
return true
} else {
return errors
}
}}
}, },
icon: function() { return sf.icon||"subflow.svg" }, icon: function() { return sf.icon||"subflow.svg" },
category: sf.category || "subflows", category: sf.category || "subflows",

View File

@@ -118,10 +118,16 @@ RED.contextMenu = (function () {
onselect: 'core:split-wire-with-link-nodes', onselect: 'core:split-wire-with-link-nodes',
disabled: !canEdit || !hasLinks disabled: !canEdit || !hasLinks
}, },
null, null
)
if (RED.settings.theme("menu.menu-item-import-library", true)) {
insertOptions.push(
{ onselect: 'core:show-import-dialog', label: RED._('common.label.import')}, { onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
{ onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') } { onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
) )
}
if (hasSelection && canEdit) { if (hasSelection && canEdit) {
const nodeOptions = [] const nodeOptions = []
if (!hasMultipleSelection && !isGroup) { if (!hasMultipleSelection && !isGroup) {
@@ -194,8 +200,14 @@ RED.contextMenu = (function () {
{ onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() }, { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
{ onselect: 'core:delete-selection', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete }, { onselect: 'core:delete-selection', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete },
{ onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete }, { onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }, )
{ onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }, if (RED.settings.theme("menu.menu-item-export-library", true)) {
menuItems.push(
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }
)
}
menuItems.push(
{ onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }
) )
} }

View File

@@ -612,7 +612,10 @@ RED.deploy = (function() {
} }
}); });
RED.nodes.eachSubflow(function (subflow) { RED.nodes.eachSubflow(function (subflow) {
if (subflow.changed) {
subflow.changed = false; subflow.changed = false;
RED.events.emit("subflows:change", subflow);
}
}); });
RED.nodes.eachWorkspace(function (ws) { RED.nodes.eachWorkspace(function (ws) {
if (ws.changed || ws.added) { if (ws.changed || ws.added) {

View File

@@ -1623,8 +1623,8 @@ RED.editor = (function() {
} }
if (!isSameObj(old_env, new_env)) { if (!isSameObj(old_env, new_env)) {
editing_node.env = new_env;
editState.changes.env = editing_node.env; editState.changes.env = editing_node.env;
editing_node.env = new_env;
editState.changed = true; editState.changed = true;
} }

View File

@@ -158,9 +158,11 @@ RED.sidebar.help = (function() {
function refreshSubflow(sf) { function refreshSubflow(sf) {
var item = treeList.treeList('get',"node-type:subflow:"+sf.id); var item = treeList.treeList('get',"node-type:subflow:"+sf.id);
if (item) {
item.subflowLabel = sf._def.label().toLowerCase(); item.subflowLabel = sf._def.label().toLowerCase();
item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()})); item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()}));
} }
}
function hideTOC() { function hideTOC() {
var tocButton = $('#red-ui-sidebar-help-show-toc') var tocButton = $('#red-ui-sidebar-help-show-toc')

View File

@@ -906,7 +906,10 @@ RED.utils = (function() {
* @returns true if valid, String if invalid * @returns true if valid, String if invalid
*/ */
function validateTypedProperty(propertyValue, propertyType, opt) { function validateTypedProperty(propertyValue, propertyType, opt) {
if (propertyValue && /^\${[^}]+}$/.test(propertyValue)) {
// Allow ${ENV_VAR} value
return true
}
let error let error
if (propertyType === 'json') { if (propertyType === 'json') {
try { try {

View File

@@ -646,6 +646,7 @@ RED.view = (function() {
} }
d3.event = event; d3.event = event;
var selected_tool = $(ui.draggable[0]).attr("data-palette-type"); var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
try {
var result = createNode(selected_tool); var result = createNode(selected_tool);
if (!result) { if (!result) {
return; return;
@@ -761,6 +762,13 @@ RED.view = (function() {
if (nn._def.autoedit) { if (nn._def.autoedit) {
RED.editor.edit(nn); RED.editor.edit(nn);
} }
} catch (error) {
if (error.code != "NODE_RED") {
RED.notify(RED._("notification.error",{message:error.toString()}),"error");
} else {
RED.notify(RED._("notification.error",{message:error.message}),"error");
}
}
} }
}); });
chart.on("focus", function() { chart.on("focus", function() {
@@ -2159,9 +2167,9 @@ RED.view = (function() {
if (n.ox !== n.n.x || n.oy !== n.n.y || addedToGroup) { if (n.ox !== n.n.x || n.oy !== n.n.y || addedToGroup) {
// This node has moved or added to a group // This node has moved or added to a group
if (rehomedNodes.has(n)) { if (rehomedNodes.has(n)) {
moveAndChangedGroupEvent.nodes.push({...n}) moveAndChangedGroupEvent.nodes.push({...n, moved: n.n.moved})
} else { } else {
moveEvent.nodes.push({...n}) moveEvent.nodes.push({...n, moved: n.n.moved})
} }
n.n.dirty = true; n.n.dirty = true;
n.n.moved = true; n.n.moved = true;
@@ -4156,7 +4164,7 @@ RED.view = (function() {
} }
var width = img.width * scaleFactor; var width = img.width * scaleFactor;
if (width > 20) { if (width > 20) {
scalefactor *= 20/width; scaleFactor *= 20/width;
width = 20; width = 20;
} }
var height = img.height * scaleFactor; var height = img.height * scaleFactor;
@@ -6063,14 +6071,19 @@ RED.view = (function() {
function createNode(type, x, y, z) { function createNode(type, x, y, z) {
const wasDirty = RED.nodes.dirty() const wasDirty = RED.nodes.dirty()
var m = /^subflow:(.+)$/.exec(type); var m = /^subflow:(.+)$/.exec(type);
var activeSubflow = z ? RED.nodes.subflow(z) : null; var activeSubflow = (z || RED.workspaces.active()) ? RED.nodes.subflow(z || RED.workspaces.active()) : null;
if (activeSubflow && m) { if (activeSubflow && m) {
var subflowId = m[1]; var subflowId = m[1];
let err
if (subflowId === activeSubflow.id) { if (subflowId === activeSubflow.id) {
throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") })) err = new Error(RED._("notification.errors.cannotAddSubflowToItself"))
} else if (RED.nodes.subflowContains(m[1], activeSubflow.id)) {
err = new Error(RED._("notification.errors.cannotAddCircularReference"))
} }
if (RED.nodes.subflowContains(m[1], activeSubflow.id)) { if (err) {
throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") })) err.code = 'NODE_RED'
throw err
} }
} }

View File

@@ -491,6 +491,11 @@ RED.workspaces = (function() {
createWorkspaceTabs(); createWorkspaceTabs();
RED.events.on("sidebar:resize",workspace_tabs.resize); RED.events.on("sidebar:resize",workspace_tabs.resize);
RED.events.on("workspace:clear", () => {
// Reset the index used to generate new flow names
workspaceIndex = 0
})
RED.actions.add("core:show-next-tab",function() { RED.actions.add("core:show-next-tab",function() {
var oldActive = activeWorkspace; var oldActive = activeWorkspace;
workspace_tabs.nextTab(); workspace_tabs.nextTab();
@@ -657,6 +662,9 @@ RED.workspaces = (function() {
RED.events.on("flows:change", (ws) => { RED.events.on("flows:change", (ws) => {
$("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added)); $("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
}) })
RED.events.on("subflows:change", (ws) => {
$("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
})
hideWorkspace(); hideWorkspace();
} }

View File

@@ -16,8 +16,20 @@
RED.validators = { RED.validators = {
number: function(blankAllowed,mopt){ number: function(blankAllowed,mopt){
return function(v, opt) { return function(v, opt) {
if ((blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v))) { if (blankAllowed && (v === '' || v === undefined)) {
return true; return true
}
if (v !== '') {
if (/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(v)) {
return true
}
if (/^\${[^}]+}$/.test(v)) {
// Allow ${ENV_VAR} value
return true
}
}
if (!isNaN(v)) {
return true
} }
if (opt && opt.label) { if (opt && opt.label) {
return RED._("validator.errors.invalid-num-prop", { return RED._("validator.errors.invalid-num-prop", {

View File

@@ -227,34 +227,42 @@
name: {value:""}, name: {value:""},
props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) { props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) {
if (!v || v.length === 0) { return true } if (!v || v.length === 0) { return true }
const errors = []
for (var i=0;i<v.length;i++) { for (var i=0;i<v.length;i++) {
if (/^\${[^}]+}$/.test(v[i].v)) {
// Allow ${ENV_VAR} value
continue
}
if (/msg|flow|global/.test(v[i].vt)) { if (/msg|flow|global/.test(v[i].vt)) {
if (!RED.utils.validatePropertyExpression(v[i].v)) { if (!RED.utils.validatePropertyExpression(v[i].v)) {
return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }); errors.push(RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }))
} }
} else if (v[i].vt === "jsonata") { } else if (v[i].vt === "jsonata") {
try{ jsonata(v[i].v); } try{ jsonata(v[i].v); }
catch(e){ catch(e){
return RED._("node-red:inject.errors.invalid-jsonata", { prop: 'msg.'+v[i].p, error: e.message }); errors.push(RED._("node-red:inject.errors.invalid-jsonata", { prop: 'msg.'+v[i].p, error: e.message }))
} }
} else if (v[i].vt === "json") { } else if (v[i].vt === "json") {
try{ JSON.parse(v[i].v); } try{ JSON.parse(v[i].v); }
catch(e){ catch(e){
return RED._("node-red:inject.errors.invalid-json", { prop: 'msg.'+v[i].p, error: e.message }); errors.push(RED._("node-red:inject.errors.invalid-json", { prop: 'msg.'+v[i].p, error: e.message }))
} }
} else if (v[i].vt === "num"){ } else if (v[i].vt === "num"){
if (!/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v[i].v)) { if (!/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v[i].v)) {
return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }); errors.push(RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }))
} }
} }
} }
if (errors.length > 0) {
return errors
}
return true; return true;
} }
}, },
repeat: { repeat: {
value:"", validate: function(v, opt) { value:"", validate: function(v, opt) {
if ((v === "") || if ((v === "") ||
(RED.validators.number(v) && (RED.validators.number()(v) &&
(v >= 0) && (v <= 2147483))) { (v >= 0) && (v <= 2147483))) {
return true; return true;
} }
@@ -263,7 +271,7 @@
}, },
crontab: {value:""}, crontab: {value:""},
once: {value:false}, once: {value:false},
onceDelay: {value:0.1}, onceDelay: {value:0.1, validate: RED.validators.number(true)},
topic: {value:""}, topic: {value:""},
payload: {value:"", validate: RED.validators.typedInput("payloadType", false) }, payload: {value:"", validate: RED.validators.typedInput("payloadType", false) },
payloadType: {value:"date"}, payloadType: {value:"date"},

View File

@@ -378,7 +378,7 @@
return { id: id, label: RED.nodes.workspace(id).label } //flow id + name return { id: id, label: RED.nodes.workspace(id).label } //flow id + name
} else { } else {
const instanceNode = RED.nodes.node(id) const instanceNode = RED.nodes.node(id)
const pathLabel = (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8)).name) const pathLabel = (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8))?.name || instanceNode.type)
return { id: id, label: pathLabel } return { id: id, label: pathLabel }
} }
}) })

View File

@@ -20,6 +20,7 @@ module.exports = function(RED) {
var exec = require('child_process').exec; var exec = require('child_process').exec;
var fs = require('fs'); var fs = require('fs');
var isUtf8 = require('is-utf8'); var isUtf8 = require('is-utf8');
const isWindows = process.platform === 'win32'
function ExecNode(n) { function ExecNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
@@ -85,9 +86,12 @@ module.exports = function(RED) {
} }
}); });
var cmd = arg.shift(); var cmd = arg.shift();
// Since 18.20.2/20.12.2, it is invalid to call spawn on Windows with a .bat/.cmd file
// without using shell: true.
const opts = isWindows ? { ...node.spawnOpt, shell: true } : node.spawnOpt
/* istanbul ignore else */ /* istanbul ignore else */
node.debug(cmd+" ["+arg+"]"); node.debug(cmd+" ["+arg+"]");
child = spawn(cmd,arg,node.spawnOpt); child = spawn(cmd,arg,opts);
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid}); node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
var unknownCommand = (child.pid === undefined); var unknownCommand = (child.pid === undefined);
if (node.timer !== 0) { if (node.timer !== 0) {

View File

@@ -103,7 +103,7 @@
<h4>Automatic mode</h4> <h4>Automatic mode</h4>
<p>Automatic mode uses the <code>parts</code> property of incoming messages to <p>Automatic mode uses the <code>parts</code> property of incoming messages to
determine how the sequence should be joined. This allows it to automatically determine how the sequence should be joined. This allows it to automatically
reverse the action of a <b>split</b> node. reverse the action of a <b>split</b> node.</p>
<h4>Manual mode</h4> <h4>Manual mode</h4>
<p>When configured to join in manual mode, the node is able to join sequences <p>When configured to join in manual mode, the node is able to join sequences

View File

@@ -1,3 +1,3 @@
<script type="text/html" data-help-name="global-config"> <script type="text/html" data-help-name="global-config">
<p>大域的なフローの設定を保持するノード大域的な環境変数の定義を含みます</p> <p>大域的なフローの設定を保持するノード大域的な環境変数の定義を含みます</p>
</script>p </script>

View File

@@ -23,7 +23,7 @@
<dt class="optional">template <span class="property-type">string</span></dt> <dt class="optional">template <span class="property-type">string</span></dt>
<dd><code>msg.payload</code>msg</dd> <dd><code>msg.payload</code>msg</dd>
</dl> </dl>
<h3>Outputs</h3> <h3>输出</h3>
<dl class="message-properties"> <dl class="message-properties">
<dt>msg <span class="property-type">object</span></dt> <dt>msg <span class="property-type">object</span></dt>
<dd>由来自传入msg的属性来填充已配置的模板后输出的带有属性的msg</dd> <dd>由来自传入msg的属性来填充已配置的模板后输出的带有属性的msg</dd>
@@ -32,7 +32,7 @@
<p>默认情况下使用<i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>格式如有需要也可以切换其他格式</p> <p>默认情况下使用<i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>格式如有需要也可以切换其他格式</p>
<p>例如: <p>例如:
<pre>Hello {{payload.name}}. Today is {{date}}</pre> <pre>Hello {{payload.name}}. Today is {{date}}</pre>
<p>receives a message containing: <p>接收一条消息其中包含:
<pre>{ <pre>{
date: "Monday", date: "Monday",
payload: { payload: {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/nodes", "name": "@node-red/nodes",
"version": "3.1.4", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -273,7 +273,7 @@ async function installModule(moduleDetails) {
let extraArgs = triggerPayload.args || []; let extraArgs = triggerPayload.args || [];
let args = ['install', ...extraArgs, installSpec] let args = ['install', ...extraArgs, installSpec]
log.trace(NPM_COMMAND + JSON.stringify(args)); log.trace(NPM_COMMAND + JSON.stringify(args));
return exec.run(NPM_COMMAND, args, { cwd: installDir },true) return exec.run(NPM_COMMAND, args, { cwd: installDir, shell: true },true)
} else { } else {
log.trace("skipping npm install"); log.trace("skipping npm install");
} }

View File

@@ -25,12 +25,15 @@ const registryUtil = require("./util");
const library = require("./library"); const library = require("./library");
const {exec,log,events,hooks} = require("@node-red/util"); const {exec,log,events,hooks} = require("@node-red/util");
const child_process = require('child_process'); const child_process = require('child_process');
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
let installerEnabled = false;
const isWindows = process.platform === 'win32'
const npmCommand = isWindows ? 'npm.cmd' : 'npm';
let installerEnabled = false;
let settings; let settings;
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/; const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/; const slashRe = isWindows ? /\\|[/]/ : /[/]/;
const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//; const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/; const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
@@ -225,7 +228,7 @@ async function installModule(module,version,url) {
let extraArgs = triggerPayload.args || []; let extraArgs = triggerPayload.args || [];
let args = ['install', ...extraArgs, installName] let args = ['install', ...extraArgs, installName]
log.trace(npmCommand + JSON.stringify(args)); log.trace(npmCommand + JSON.stringify(args));
return exec.run(npmCommand,args,{ cwd: installDir}, true) return exec.run(npmCommand,args,{ cwd: installDir, shell: true }, true)
} else { } else {
log.trace("skipping npm install"); log.trace("skipping npm install");
} }
@@ -260,7 +263,7 @@ async function installModule(module,version,url) {
log.warn("------------------------------------------"); log.warn("------------------------------------------");
e = new Error(log._("server.install.install-failed")+": "+err.toString()); e = new Error(log._("server.install.install-failed")+": "+err.toString());
if (err.hook === "postInstall") { if (err.hook === "postInstall") {
return exec.run(npmCommand,["remove",module],{ cwd: installDir}, false).finally(() => { return exec.run(npmCommand,["remove",module],{ cwd: installDir, shell: true }, false).finally(() => {
throw e; throw e;
}) })
} }
@@ -356,7 +359,7 @@ async function getModuleVersionFromNPM(module, version) {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
child_process.execFile(npmCommand,['info','--json',installName],function(err,stdout,stderr) { child_process.execFile(npmCommand,['info','--json',installName],{ shell: true },function(err,stdout,stderr) {
try { try {
if (!stdout) { if (!stdout) {
log.warn(log._("server.install.install-failed-not-found",{name:module})); log.warn(log._("server.install.install-failed-not-found",{name:module}));
@@ -511,7 +514,7 @@ function uninstallModule(module) {
let extraArgs = triggerPayload.args || []; let extraArgs = triggerPayload.args || [];
let args = ['remove', ...extraArgs, module] let args = ['remove', ...extraArgs, module]
log.trace(npmCommand + JSON.stringify(args)); log.trace(npmCommand + JSON.stringify(args));
return exec.run(npmCommand,args,{ cwd: installDir}, true) return exec.run(npmCommand,args,{ cwd: installDir, shell: true }, true)
} else { } else {
log.trace("skipping npm uninstall"); log.trace("skipping npm uninstall");
} }
@@ -578,7 +581,7 @@ async function checkPrereq() {
installerEnabled = false; installerEnabled = false;
} else { } else {
return new Promise(resolve => { return new Promise(resolve => {
child_process.execFile(npmCommand,['-v'],function(err,stdout) { child_process.execFile(npmCommand,['-v'],{ shell: true },function(err,stdout) {
if (err) { if (err) {
log.info(log._("server.palette-editor.npm-not-found")); log.info(log._("server.palette-editor.npm-not-found"));
installerEnabled = false; installerEnabled = false;

View File

@@ -36,7 +36,7 @@ async function getFlowsFromPath(path) {
promises.push(getFlowsFromPath(fullPath)); promises.push(getFlowsFromPath(fullPath));
} else if (/\.json$/.test(file)){ } else if (/\.json$/.test(file)){
validFiles.push(file); validFiles.push(file);
promises.push(Promise.resolve(file.split(".")[0])) promises.push(Promise.resolve(file.replace(/\.json$/, '')))
} }
}) })
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/registry", "name": "@node-red/registry",
"version": "3.1.4", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,11 +16,11 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "3.1.4", "@node-red/util": "3.1.9",
"clone": "2.1.2", "clone": "2.1.2",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"semver": "7.5.4", "semver": "7.5.4",
"tar": "6.1.13", "tar": "6.2.1",
"uglify-js": "3.17.4" "uglify-js": "3.17.4"
} }
} }

View File

@@ -485,7 +485,7 @@ class Flow {
} }
if (!key.startsWith("$parent.")) { if (!key.startsWith("$parent.")) {
if (this._env.hasOwnProperty(key)) { if (this._env.hasOwnProperty(key)) {
return (Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key] return (this._env[key] && Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
} }
} else { } else {
key = key.substring(8); key = key.substring(8);

View File

@@ -41,7 +41,7 @@ class Group {
} }
if (!key.startsWith("$parent.")) { if (!key.startsWith("$parent.")) {
if (this._env.hasOwnProperty(key)) { if (this._env.hasOwnProperty(key)) {
return (Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key] return (this._env[key] && Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
} }
} else { } else {
key = key.substring(8); key = key.substring(8);

View File

@@ -376,7 +376,7 @@ class Subflow extends Flow {
} }
if (!key.startsWith("$parent.")) { if (!key.startsWith("$parent.")) {
if (this._env.hasOwnProperty(key)) { if (this._env.hasOwnProperty(key)) {
return (Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key] return (this._env[key] && Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
} }
} else { } else {
key = key.substring(8); key = key.substring(8);

View File

@@ -106,15 +106,23 @@ async function evaluateEnvProperties(flow, env, credentials) {
result = { value: result, __clone__: true} result = { value: result, __clone__: true}
} }
evaluatedEnv[name] = result evaluatedEnv[name] = result
} else {
evaluatedEnv[name] = undefined
flow.error(`Error evaluating env property '${name}': ${err.toString()}`)
} }
resolve() resolve()
}); });
})) }))
} else { } else {
try {
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
if (typeof value === 'object') { if (typeof value === 'object') {
value = { value: value, __clone__: true} value = { value: value, __clone__: true}
} }
} catch (err) {
value = undefined
flow.error(`Error evaluating env property '${name}': ${err.toString()}`)
}
} }
evaluatedEnv[name] = value evaluatedEnv[name] = value
} }

View File

@@ -384,7 +384,8 @@ var api = module.exports = {
} }
} }
} else if (nodeType === "global-config") { } else if (nodeType === "global-config") {
const existingCredentialKeys = Object.keys(savedCredentials?.map || []) savedCredentials.map = savedCredentials.map || {}
const existingCredentialKeys = Object.keys(savedCredentials.map)
const newCredentialKeys = Object.keys(newCreds?.map || []) const newCredentialKeys = Object.keys(newCreds?.map || [])
existingCredentialKeys.forEach(key => { existingCredentialKeys.forEach(key => {
if (!newCreds.map?.[key]) { if (!newCreds.map?.[key]) {
@@ -396,7 +397,7 @@ var api = module.exports = {
}) })
newCredentialKeys.forEach(key => { newCredentialKeys.forEach(key => {
if (!/^has_/.test(key)) { if (!/^has_/.test(key)) {
if (!savedCredentials.map?.[key] || newCreds.map[key] !== '__PWRD__') { if (!savedCredentials.map[key] || newCreds.map[key] !== '__PWRD__') {
// This key either doesn't exist in current saved, or the // This key either doesn't exist in current saved, or the
// value has been changed // value has been changed
savedCredentials.map[key] = newCreds.map[key] savedCredentials.map[key] = newCreds.map[key]

View File

@@ -77,7 +77,7 @@ var storageModuleInterface = {
flows: flows, flows: flows,
credentials: creds credentials: creds
}; };
result.rev = crypto.createHash('md5').update(JSON.stringify(result.flows)).digest("hex"); result.rev = crypto.createHash('sha256').update(JSON.stringify(result.flows)).digest("hex");
return result; return result;
}) })
}); });
@@ -95,7 +95,7 @@ var storageModuleInterface = {
return credentialSavePromise.then(function() { return credentialSavePromise.then(function() {
return storageModule.saveFlows(flows, user).then(function() { return storageModule.saveFlows(flows, user).then(function() {
return crypto.createHash('md5').update(JSON.stringify(config.flows)).digest("hex"); return crypto.createHash('sha256').update(JSON.stringify(config.flows)).digest("hex");
}) })
}); });
}, },

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/runtime", "name": "@node-red/runtime",
"version": "3.1.4", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,11 +16,11 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/registry": "3.1.4", "@node-red/registry": "3.1.9",
"@node-red/util": "3.1.4", "@node-red/util": "3.1.9",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"clone": "2.1.2", "clone": "2.1.2",
"express": "4.18.2", "express": "4.19.2",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"json-stringify-safe": "5.0.1" "json-stringify-safe": "5.0.1"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/util", "name": "@node-red/util",
"version": "3.1.4", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -18,7 +18,7 @@
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"i18next": "21.10.0", "i18next": "21.10.0",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.8.6", "jsonata": "1.8.7",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"moment": "2.29.4", "moment": "2.29.4",
"moment-timezone": "0.5.43" "moment-timezone": "0.5.43"

View File

@@ -26,8 +26,8 @@ var server = null;
var apiEnabled = false; var apiEnabled = false;
const NODE_MAJOR_VERSION = process.versions.node.split('.')[0]; const NODE_MAJOR_VERSION = process.versions.node.split('.')[0];
if (NODE_MAJOR_VERSION > 14) { if (NODE_MAJOR_VERSION >= 16) {
const dns = require('node:dns'); const dns = require('dns');
dns.setDefaultResultOrder('ipv4first'); dns.setDefaultResultOrder('ipv4first');
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "3.1.4", "version": "3.1.9",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org", "homepage": "https://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -31,15 +31,15 @@
"flow" "flow"
], ],
"dependencies": { "dependencies": {
"@node-red/editor-api": "3.1.4", "@node-red/editor-api": "3.1.9",
"@node-red/runtime": "3.1.4", "@node-red/runtime": "3.1.9",
"@node-red/util": "3.1.4", "@node-red/util": "3.1.9",
"@node-red/nodes": "3.1.4", "@node-red/nodes": "3.1.9",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"express": "4.18.2", "express": "4.19.2",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"node-red-admin": "^3.1.2", "node-red-admin": "^3.1.3",
"nopt": "5.0.0", "nopt": "5.0.0",
"semver": "7.5.4" "semver": "7.5.4"
}, },

View File

@@ -60,6 +60,7 @@ describe('HTTP Request Node', function() {
function startServer(done) { function startServer(done) {
testPort += 1; testPort += 1;
testServer = stoppable(http.createServer(testApp)); testServer = stoppable(http.createServer(testApp));
const promises = []
testServer.listen(testPort,function(err) { testServer.listen(testPort,function(err) {
testSslPort += 1; testSslPort += 1;
console.log("ssl port", testSslPort); console.log("ssl port", testSslPort);
@@ -81,13 +82,17 @@ describe('HTTP Request Node', function() {
*/ */
}; };
testSslServer = stoppable(https.createServer(sslOptions,testApp)); testSslServer = stoppable(https.createServer(sslOptions,testApp));
console.log('> start testSslServer')
promises.push(new Promise((resolve, reject) => {
testSslServer.listen(testSslPort, function(err){ testSslServer.listen(testSslPort, function(err){
console.log(' done testSslServer')
if (err) { if (err) {
console.log(err); reject(err)
} else { } else {
console.log("started testSslServer"); resolve()
} }
}); });
}))
testSslClientPort += 1; testSslClientPort += 1;
var sslClientOptions = { var sslClientOptions = {
@@ -97,10 +102,17 @@ describe('HTTP Request Node', function() {
requestCert: true requestCert: true
}; };
testSslClientServer = stoppable(https.createServer(sslClientOptions, testApp)); testSslClientServer = stoppable(https.createServer(sslClientOptions, testApp));
console.log('> start testSslClientServer')
promises.push(new Promise((resolve, reject) => {
testSslClientServer.listen(testSslClientPort, function(err){ testSslClientServer.listen(testSslClientPort, function(err){
console.log("ssl-client", err) console.log(' done testSslClientServer')
if (err) {
reject(err)
} else {
resolve()
}
}); });
}))
testProxyPort += 1; testProxyPort += 1;
testProxyServer = stoppable(httpProxy(http.createServer())) testProxyServer = stoppable(httpProxy(http.createServer()))
@@ -109,7 +121,17 @@ describe('HTTP Request Node', function() {
res.setHeader("x-testproxy-header", "foobar") res.setHeader("x-testproxy-header", "foobar")
} }
}) })
testProxyServer.listen(testProxyPort) console.log('> testProxyServer')
promises.push(new Promise((resolve, reject) => {
testProxyServer.listen(testProxyPort, function(err) {
console.log(' done testProxyServer')
if (err) {
reject(err)
} else {
resolve()
}
})
}))
testProxyAuthPort += 1 testProxyAuthPort += 1
testProxyServerAuth = stoppable(httpProxy(http.createServer())) testProxyServerAuth = stoppable(httpProxy(http.createServer()))
@@ -131,9 +153,19 @@ describe('HTTP Request Node', function() {
res.setHeader("x-testproxy-header", "foobar") res.setHeader("x-testproxy-header", "foobar")
} }
}) })
testProxyServerAuth.listen(testProxyAuthPort) console.log('> testProxyServerAuth')
promises.push(new Promise((resolve, reject) => {
testProxyServerAuth.listen(testProxyAuthPort, function(err) {
console.log(' done testProxyServerAuth')
if (err) {
reject(err)
} else {
resolve()
}
})
}))
done(err); Promise.all(promises).then(() => { done() }).catch(done)
}); });
} }
@@ -429,7 +461,11 @@ describe('HTTP Request Node', function() {
if (err) { if (err) {
done(err); done(err);
} }
helper.startServer(done); console.log('> helper.startServer')
helper.startServer(function(err) {
console.log('> helper started')
done(err)
});
}); });
}); });

View File

@@ -33,16 +33,15 @@ describe("library api", function() {
should.not.exist(library.getExampleFlowPath('foo','bar')); should.not.exist(library.getExampleFlowPath('foo','bar'));
}); });
it('returns a valid example path', function(done) { it('returns valid example paths', function(done) {
library.init(); library.init();
library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() { library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() {
try { try {
var flows = library.getExampleFlows(); var flows = library.getExampleFlows();
flows.should.deepEqual({"test-module":{"f":["one"]}}); flows.should.deepEqual({"test-module":{"f":["1.2.3","one"]}});
var examplePath = library.getExampleFlowPath('test-module','one'); var examplePath = library.getExampleFlowPath('test-module','one');
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json')) examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'));
library.removeExamplesDir('test-module'); library.removeExamplesDir('test-module');
@@ -57,6 +56,5 @@ describe("library api", function() {
done(err); done(err);
} }
}); });
});
})
}); });