mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Compare commits
	
		
			64 Commits
		
	
	
		
			3.1.4
			...
			undo-histo
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c294532152 | ||
|  | 960af87fb0 | ||
|  | de7339ae97 | ||
|  | 0995af62b6 | ||
|  | c2e03a40b4 | ||
|  | 29ed5b2792 | ||
|  | e39216e65a | ||
|  | 7ac7f9b4c8 | ||
|  | 4709eb9d49 | ||
|  | c13b8266dd | ||
|  | bd58431603 | ||
|  | 9a3cb0b2b5 | ||
|  | 6beae5a806 | ||
|  | a0636632a1 | ||
|  | 5dfa47ab6c | ||
|  | ade4679e8c | ||
|  | 410b938442 | ||
|  | 19dcc3a683 | ||
|  | 20d067c1ea | ||
|  | 9526566799 | ||
|  | 0b9dd82c91 | ||
|  | 19213434f9 | ||
|  | 014691346a | ||
|  | 6738b95c29 | ||
|  | 6a8230ec1e | ||
|  | 5679d264b6 | ||
|  | 37265cf4ef | ||
|  | 8a63275989 | ||
|  | 7fc64a84e8 | ||
|  | 02f7cdd5aa | ||
|  | d7dcceef60 | ||
|  | ae5e1570ae | ||
|  | 3ca045394a | ||
|  | 179032cd4d | ||
|  | 6a6f0d04d6 | ||
|  | add4d9758c | ||
|  | a0d3ea62b2 | ||
|  | 7447e88a50 | ||
|  | a193b79d3d | ||
|  | da380f7464 | ||
|  | 269cf02c0b | ||
|  | fb50e2772a | ||
|  | 058c97138a | ||
|  | 828ae29aed | ||
|  | 6a0f45140c | ||
|  | 50a267528d | ||
|  | 220786be60 | ||
|  | fa78bb3d78 | ||
|  | 9a32ebd0c0 | ||
|  | 4643f5e8cc | ||
|  | 7de0984d6d | ||
|  | 635334f096 | ||
|  | f0d0990b5a | ||
|  | 43b3589451 | ||
|  | 016a19ba7c | ||
|  | aeb79bce2a | ||
|  | 0ab9b9a5fd | ||
|  | 56e58521bd | ||
|  | b10ef4c98c | ||
|  | 3ff038fb98 | ||
|  | adb498af24 | ||
|  | fc67a2efc2 | ||
|  | 55771c7241 | ||
|  | 109fa5f04e | 
							
								
								
									
										46
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							| @@ -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", | ||||||
|   | |||||||
| @@ -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"); | ||||||
|   | |||||||
| @@ -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"); | ||||||
|   | |||||||
| @@ -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"); | ||||||
|   | |||||||
| @@ -15,8 +15,6 @@ | |||||||
|  **/ |  **/ | ||||||
|  |  | ||||||
| var apiUtils = require("../util"); | var apiUtils = require("../util"); | ||||||
| var fs = require('fs'); |  | ||||||
| var fspath = require('path'); |  | ||||||
|  |  | ||||||
| var runtimeAPI; | var runtimeAPI; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,7 +15,6 @@ | |||||||
|  **/ |  **/ | ||||||
|  |  | ||||||
| var apiUtils = require("../util"); | var apiUtils = require("../util"); | ||||||
| var express = require("express"); |  | ||||||
| var runtimeAPI; | var runtimeAPI; | ||||||
| var settings; | var settings; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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"); | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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"); | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -303,7 +303,8 @@ | |||||||
|                 "missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません" |                 "missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません" | ||||||
|             }, |             }, | ||||||
|             "conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。", |             "conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。", | ||||||
|             "conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。" |             "conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。", | ||||||
|  |             "alreadyExists": "本ノードは既に存在" | ||||||
|         }, |         }, | ||||||
|         "copyMessagePath": "パスをコピーしました", |         "copyMessagePath": "パスをコピーしました", | ||||||
|         "copyMessageValue": "値をコピーしました", |         "copyMessageValue": "値をコピーしました", | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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() { | ||||||
|   | |||||||
| @@ -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 | ||||||
|                         RED.events.emit("flows:change", ws); |                         if (ws.type === 'tab') { | ||||||
|  |                             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", | ||||||
|   | |||||||
| @@ -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 | ||||||
|                 { onselect: 'core:show-import-dialog', label: RED._('common.label.import')}, |  | ||||||
|                 { onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') } |  | ||||||
|             ) |             ) | ||||||
|  |             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-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") } | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -612,7 +612,10 @@ RED.deploy = (function() { | |||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|             RED.nodes.eachSubflow(function (subflow) { |             RED.nodes.eachSubflow(function (subflow) { | ||||||
|                 subflow.changed = false; |                 if (subflow.changed) { | ||||||
|  |                     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) { | ||||||
|   | |||||||
| @@ -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; | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -158,8 +158,10 @@ 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); | ||||||
|         item.subflowLabel = sf._def.label().toLowerCase(); |         if (item) { | ||||||
|         item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()})); |             item.subflowLabel = sf._def.label().toLowerCase(); | ||||||
|  |             item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()})); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function hideTOC() { |     function hideTOC() { | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -646,120 +646,128 @@ 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"); | ||||||
|                 var result = createNode(selected_tool); |  | ||||||
|                 if (!result) { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 var historyEvent = result.historyEvent; |  | ||||||
|                 var nn = RED.nodes.add(result.node); |  | ||||||
|  |  | ||||||
|                 var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); |  | ||||||
|                 if (showLabel !== undefined &&  (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { |  | ||||||
|                     nn.l = showLabel; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0)); |  | ||||||
|                 var helperWidth = ui.helper.width(); |  | ||||||
|                 var helperHeight = ui.helper.height(); |  | ||||||
|                 var mousePos = d3.touches(this)[0]||d3.mouse(this); |  | ||||||
|  |  | ||||||
|                 try { |                 try { | ||||||
|                     var isLink = (nn.type === "link in" || nn.type === "link out") |                     var result = createNode(selected_tool); | ||||||
|                     var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; |                     if (!result) { | ||||||
|  |                         return; | ||||||
|                     var label = RED.utils.getNodeLabel(nn, nn.type); |  | ||||||
|                     var labelParts = getLabelParts(label, "red-ui-flow-node-label"); |  | ||||||
|                     if (hideLabel) { |  | ||||||
|                         nn.w = node_height; |  | ||||||
|                         nn.h = Math.max(node_height,(nn.outputs || 0) * 15); |  | ||||||
|                     } else { |  | ||||||
|                         nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) ); |  | ||||||
|                         nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30); |  | ||||||
|                     } |                     } | ||||||
|                 } catch(err) { |                     var historyEvent = result.historyEvent; | ||||||
|                 } |                     var nn = RED.nodes.add(result.node); | ||||||
|  |  | ||||||
|                 mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); |                     var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); | ||||||
|                 mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); |                     if (showLabel !== undefined &&  (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { | ||||||
|                 mousePos[1] /= scaleFactor; |                         nn.l = showLabel; | ||||||
|                 mousePos[0] /= scaleFactor; |                     } | ||||||
|  |  | ||||||
|                 nn.x = mousePos[0]; |                     var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0)); | ||||||
|                 nn.y = mousePos[1]; |                     var helperWidth = ui.helper.width(); | ||||||
|  |                     var helperHeight = ui.helper.height(); | ||||||
|  |                     var mousePos = d3.touches(this)[0]||d3.mouse(this); | ||||||
|  |  | ||||||
|                 var minX = nn.w/2 -5; |                     try { | ||||||
|                 if (nn.x < minX) { |                         var isLink = (nn.type === "link in" || nn.type === "link out") | ||||||
|                     nn.x = minX; |                         var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; | ||||||
|                 } |  | ||||||
|                 var minY = nn.h/2 -5; |  | ||||||
|                 if (nn.y < minY) { |  | ||||||
|                     nn.y = minY; |  | ||||||
|                 } |  | ||||||
|                 var maxX = space_width -nn.w/2 +5; |  | ||||||
|                 if (nn.x > maxX) { |  | ||||||
|                     nn.x = maxX; |  | ||||||
|                 } |  | ||||||
|                 var maxY = space_height -nn.h +5; |  | ||||||
|                 if (nn.y > maxY) { |  | ||||||
|                     nn.y = maxY; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (snapGrid) { |                         var label = RED.utils.getNodeLabel(nn, nn.type); | ||||||
|                     var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn); |                         var labelParts = getLabelParts(label, "red-ui-flow-node-label"); | ||||||
|                     nn.x -= gridOffset.x; |                         if (hideLabel) { | ||||||
|                     nn.y -= gridOffset.y; |                             nn.w = node_height; | ||||||
|                 } |                             nn.h = Math.max(node_height,(nn.outputs || 0) * 15); | ||||||
|  |                         } else { | ||||||
|  |                             nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) ); | ||||||
|  |                             nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30); | ||||||
|  |                         } | ||||||
|  |                     } catch(err) { | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                 var linkToSplice = $(ui.helper).data("splice"); |                     mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); | ||||||
|                 if (linkToSplice) { |                     mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); | ||||||
|                     spliceLink(linkToSplice, nn, historyEvent) |                     mousePos[1] /= scaleFactor; | ||||||
|                 } |                     mousePos[0] /= scaleFactor; | ||||||
|  |  | ||||||
|  |                     nn.x = mousePos[0]; | ||||||
|  |                     nn.y = mousePos[1]; | ||||||
|  |  | ||||||
|  |                     var minX = nn.w/2 -5; | ||||||
|  |                     if (nn.x < minX) { | ||||||
|  |                         nn.x = minX; | ||||||
|  |                     } | ||||||
|  |                     var minY = nn.h/2 -5; | ||||||
|  |                     if (nn.y < minY) { | ||||||
|  |                         nn.y = minY; | ||||||
|  |                     } | ||||||
|  |                     var maxX = space_width -nn.w/2 +5; | ||||||
|  |                     if (nn.x > maxX) { | ||||||
|  |                         nn.x = maxX; | ||||||
|  |                     } | ||||||
|  |                     var maxY = space_height -nn.h +5; | ||||||
|  |                     if (nn.y > maxY) { | ||||||
|  |                         nn.y = maxY; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (snapGrid) { | ||||||
|  |                         var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn); | ||||||
|  |                         nn.x -= gridOffset.x; | ||||||
|  |                         nn.y -= gridOffset.y; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     var linkToSplice = $(ui.helper).data("splice"); | ||||||
|  |                     if (linkToSplice) { | ||||||
|  |                         spliceLink(linkToSplice, nn, historyEvent) | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     var group = $(ui.helper).data("group"); | ||||||
|  |                     if (group) { | ||||||
|  |                         var oldX = group.x;  | ||||||
|  |                         var oldY = group.y;  | ||||||
|  |                         RED.group.addToGroup(group, nn); | ||||||
|  |                         var moveEvent = null; | ||||||
|  |                         if ((group.x !== oldX) || | ||||||
|  |                             (group.y !== oldY)) { | ||||||
|  |                             moveEvent = { | ||||||
|  |                                 t: "move", | ||||||
|  |                                 nodes: [{n: group, | ||||||
|  |                                         ox: oldX, oy: oldY, | ||||||
|  |                                         dx: group.x -oldX, | ||||||
|  |                                         dy: group.y -oldY}], | ||||||
|  |                                 dirty: true | ||||||
|  |                             }; | ||||||
|  |                         } | ||||||
|  |                         historyEvent = { | ||||||
|  |                             t: 'multi', | ||||||
|  |                             events: [historyEvent], | ||||||
|  |  | ||||||
|                 var group = $(ui.helper).data("group"); |  | ||||||
|                 if (group) { |  | ||||||
|                     var oldX = group.x;  |  | ||||||
|                     var oldY = group.y;  |  | ||||||
|                     RED.group.addToGroup(group, nn); |  | ||||||
|                     var moveEvent = null; |  | ||||||
|                     if ((group.x !== oldX) || |  | ||||||
|                         (group.y !== oldY)) { |  | ||||||
|                         moveEvent = { |  | ||||||
|                             t: "move", |  | ||||||
|                             nodes: [{n: group, |  | ||||||
|                                      ox: oldX, oy: oldY, |  | ||||||
|                                      dx: group.x -oldX, |  | ||||||
|                                      dy: group.y -oldY}], |  | ||||||
|                             dirty: true |  | ||||||
|                         }; |                         }; | ||||||
|  |                         if (moveEvent) { | ||||||
|  |                             historyEvent.events.push(moveEvent) | ||||||
|  |                         } | ||||||
|  |                         historyEvent.events.push({ | ||||||
|  |                             t: "addToGroup", | ||||||
|  |                             group: group, | ||||||
|  |                             nodes: nn | ||||||
|  |                         }) | ||||||
|                     } |                     } | ||||||
|                     historyEvent = { |  | ||||||
|                         t: 'multi', |  | ||||||
|                         events: [historyEvent], |  | ||||||
|  |  | ||||||
|                     }; |                     RED.history.push(historyEvent); | ||||||
|                     if (moveEvent) { |                     RED.editor.validateNode(nn); | ||||||
|                         historyEvent.events.push(moveEvent) |                     RED.nodes.dirty(true); | ||||||
|  |                     // auto select dropped node - so info shows (if visible) | ||||||
|  |                     clearSelection(); | ||||||
|  |                     nn.selected = true; | ||||||
|  |                     movingSet.add(nn); | ||||||
|  |                     updateActiveNodes(); | ||||||
|  |                     updateSelection(); | ||||||
|  |                     redraw(); | ||||||
|  |  | ||||||
|  |                     if (nn._def.autoedit) { | ||||||
|  |                         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"); | ||||||
|                     } |                     } | ||||||
|                     historyEvent.events.push({ |  | ||||||
|                         t: "addToGroup", |  | ||||||
|                         group: group, |  | ||||||
|                         nodes: nn |  | ||||||
|                     }) |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 RED.history.push(historyEvent); |  | ||||||
|                 RED.editor.validateNode(nn); |  | ||||||
|                 RED.nodes.dirty(true); |  | ||||||
|                 // auto select dropped node - so info shows (if visible) |  | ||||||
|                 clearSelection(); |  | ||||||
|                 nn.selected = true; |  | ||||||
|                 movingSet.add(nn); |  | ||||||
|                 updateActiveNodes(); |  | ||||||
|                 updateSelection(); |  | ||||||
|                 redraw(); |  | ||||||
|  |  | ||||||
|                 if (nn._def.autoedit) { |  | ||||||
|                     RED.editor.edit(nn); |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| @@ -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 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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", { | ||||||
|   | |||||||
| @@ -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"}, | ||||||
|   | |||||||
| @@ -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 } | ||||||
|                         } |                         } | ||||||
|                     }) |                     }) | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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: { | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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"); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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$/, ''))) | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -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" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -106,14 +106,22 @@ 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 { | ||||||
|             value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); |             try { | ||||||
|             if (typeof value  === 'object') { |                 value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); | ||||||
|                 value = { value: value, __clone__: true} |                 if (typeof value  === 'object') { | ||||||
|  |                     value = { value: value, __clone__: true} | ||||||
|  |                 } | ||||||
|  |             } catch (err) { | ||||||
|  |                 value = undefined | ||||||
|  |                 flow.error(`Error evaluating env property '${name}': ${err.toString()}`) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         evaluatedEnv[name] = value |         evaluatedEnv[name] = value | ||||||
|   | |||||||
| @@ -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] | ||||||
|   | |||||||
| @@ -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"); | ||||||
|                 }) |                 }) | ||||||
|             }); |             }); | ||||||
|         }, |         }, | ||||||
|   | |||||||
| @@ -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" | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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" | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								packages/node_modules/node-red/lib/red.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								packages/node_modules/node-red/lib/red.js
									
									
									
									
										vendored
									
									
								
							| @@ -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'); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							| @@ -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" | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -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)); | ||||||
|             testSslServer.listen(testSslPort, function(err){ |             console.log('> start testSslServer') | ||||||
|                 if (err) { |             promises.push(new Promise((resolve, reject) => { | ||||||
|                     console.log(err); |                 testSslServer.listen(testSslPort, function(err){ | ||||||
|                 } else { |                     console.log(' done testSslServer') | ||||||
|                     console.log("started testSslServer"); |                     if (err) { | ||||||
|                 } |                         reject(err) | ||||||
|             }); |                     } else { | ||||||
|  |                         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)); | ||||||
|             testSslClientServer.listen(testSslClientPort, function(err){ |             console.log('> start testSslClientServer') | ||||||
|                 console.log("ssl-client", err) |             promises.push(new Promise((resolve, reject) => { | ||||||
|             }); |                 testSslClientServer.listen(testSslClientPort, function(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) | ||||||
|  |             }); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |     }); | ||||||
|     }) |  | ||||||
| }); | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user