mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Compare commits
	
		
			137 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 80716cc45b | ||
|  | 300ec635d5 | ||
|  | eea7abb30b | ||
|  | 7512fdca49 | ||
|  | fa26111339 | ||
|  | 27eb992d01 | ||
|  | 9e227a0769 | ||
|  | 1da0379b51 | ||
|  | 30d992f889 | ||
|  | e3526cbe1c | ||
|  | aa2b2b0fb1 | ||
|  | 50134307fe | ||
|  | df0eb1a7e4 | ||
|  | b22f2e704d | ||
|  | c06ebb7e33 | ||
|  | 7e34a253b4 | ||
|  | 7322cd0a06 | ||
|  | 3fe4c12468 | ||
|  | 2a4fb7123d | ||
|  | 38a77d2b78 | ||
|  | dc239db256 | ||
|  | 4ba3c937a8 | ||
|  | 02893d3e78 | ||
|  | 5124bc6bf8 | ||
|  | 1048b16f3c | ||
|  | bbbbb1b1e0 | ||
|  | 14b452c996 | ||
|  | bb91a08939 | ||
|  | 526b3fda91 | ||
|  | d70b7ea924 | ||
|  | 1d342a778d | ||
|  | 476016cbcc | ||
|  | 61b12f6bbe | ||
|  | d71b22412b | ||
|  | e408c6b376 | ||
|  | bf30c24e8e | ||
|  | 6c14ed0ef5 | ||
|  | 6d41ecdae0 | ||
|  | 3f89bc2733 | ||
|  | 87a25df162 | ||
|  | 341f43610a | ||
|  | 67cdf3ef96 | ||
|  | cd98f448e9 | ||
|  | fac79fd068 | ||
|  | da97c5d558 | ||
|  | ae7b9fe62e | ||
|  | e52c2911da | ||
|  | 07a29ff779 | ||
|  | 1b4a8ebe83 | ||
|  | abf2eacf18 | ||
|  | f808f4e2e8 | ||
|  | ca33d6b799 | ||
|  | 940740f15d | ||
|  | 51208fcd0c | ||
|  | 707152d82f | ||
|  | 5538f6dd8a | ||
|  | d601e2caa4 | ||
|  | 46fdf56c79 | ||
|  | f55ee6e665 | ||
|  | 03648dc7e8 | ||
|  | 66a667fe58 | ||
|  | 1bb3a0eca5 | ||
|  | 08927dfb55 | ||
|  | b27483de9c | ||
|  | 211d420fb2 | ||
|  | b8ca4665c1 | ||
|  | 960af87fb0 | ||
|  | de7339ae97 | ||
|  | 0995af62b6 | ||
|  | c2e03a40b4 | ||
|  | c855050bcf | ||
|  | 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 | ||
|  | 28907082f1 | ||
|  | f83174c40a | ||
|  | ec062d008f | ||
|  | a587655a5a | ||
|  | 3e6f0acf79 | ||
|  | 7f93d943d7 | ||
|  | 12543d2c2a | 
							
								
								
									
										90
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,93 @@ | ||||
| #### 3.1.15: Maintenance Release | ||||
|  | ||||
|  - Bump express 3.1.x (#4988) @hardillb | ||||
|  | ||||
| #### 3.1.14: Maintenance Release | ||||
|  | ||||
|  - Update mermaid version | ||||
|  | ||||
| #### 3.1.13: Maintenance Release | ||||
|  | ||||
|  - Update cookie/DOMPurify dependencies | ||||
|  | ||||
| #### 3.1.12: Maintenance Release | ||||
|  | ||||
|  - Update express/body-parser dependencies | ||||
|  | ||||
| #### 3.1.11: Maintenance Release | ||||
|  | ||||
|  - Add/Update German Translations for delay node (#4762) @dceejay | ||||
|  - Update ws dependency | ||||
|  | ||||
| #### 3.1.10: Maintenance Release | ||||
|  | ||||
|  - Include rewired nodes when calculating Modified Flows stop list (#4754) @knolleary | ||||
|  - Fix clone of group env var properties (#4753) @knolleary | ||||
|  - Fix losing links when importing a copy of links into a subflow (#4750) @GogoVega | ||||
|  - Ensure all CSS variables are in the output file (#3743) @bonanitech | ||||
|  - Fix the Sidebar Config is not refreshed after a deploy (#4734) @GogoVega | ||||
|  - Fix checkboxes are not updated when calling `typedInput("value", "")` (#4729) @GogoVega | ||||
|  - Fix panning with middle mouse button on windows 10/11 (#4716) @corentin-sodebo-voile | ||||
|  - Add Japanese translation for sidebar tooltip (#4727) @kazuhitoyokoi | ||||
|  - Translate the number of items selected in the options list (#4730) @GogoVega | ||||
|  - Fix a checkbox should return a Boolean value and not the string `on` (#4715) @GogoVega | ||||
|  - Deleting a grouped node should update the group (#4714) @GogoVega | ||||
|  - Change the Config Node cursor to `pointer` (#4711) @GogoVega | ||||
|  - Add missing tooltips to Sidebar (#4713) @GogoVega | ||||
|  - Allow nodes to return additional history entries in onEditSave (#4710) @knolleary | ||||
|  - Pass full error object in Function node and copy over cause property (#4685) @knolleary | ||||
|  - Replacing vm.createScript in favour of vm.Script (#4534) @patlux | ||||
|  - Avoid login loops when autoLogin enabled but login fails (#4684) @knolleary | ||||
|  - Fix undo of subflow env property edits (#4667) @knolleary | ||||
|  - Fix three error typos in monaco.js (#4660) @JoshuaCWebDeveloper | ||||
|  - docs: Add closing paragraph tag (#4664) @ZJvandeWeg | ||||
|  | ||||
| #### 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 | ||||
|  | ||||
| Editor | ||||
|   | ||||
							
								
								
									
										26
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "node-red", | ||||
|     "version": "3.1.4", | ||||
|     "version": "3.1.15", | ||||
|     "description": "Low-code programming for event-driven applications", | ||||
|     "homepage": "https://nodered.org", | ||||
|     "license": "Apache-2.0", | ||||
| @@ -32,17 +32,17 @@ | ||||
|         "async-mutex": "0.4.0", | ||||
|         "basic-auth": "2.0.1", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "body-parser": "1.20.2", | ||||
|         "body-parser": "1.20.3", | ||||
|         "cheerio": "1.0.0-rc.10", | ||||
|         "clone": "2.1.2", | ||||
|         "content-type": "1.0.5", | ||||
|         "cookie": "0.5.0", | ||||
|         "cookie-parser": "1.4.6", | ||||
|         "cookie": "0.7.2", | ||||
|         "cookie-parser": "1.4.7", | ||||
|         "cors": "2.8.5", | ||||
|         "cronosjs": "1.7.1", | ||||
|         "denque": "2.1.0", | ||||
|         "express": "4.18.2", | ||||
|         "express-session": "1.17.3", | ||||
|         "express": "4.21.2", | ||||
|         "express-session": "1.18.1", | ||||
|         "form-data": "4.0.0", | ||||
|         "fs-extra": "11.1.1", | ||||
|         "got": "12.6.0", | ||||
| @@ -54,7 +54,7 @@ | ||||
|         "is-utf8": "0.2.1", | ||||
|         "js-yaml": "4.1.0", | ||||
|         "json-stringify-safe": "5.0.1", | ||||
|         "jsonata": "1.8.6", | ||||
|         "jsonata": "1.8.7", | ||||
|         "lodash.clonedeep": "^4.5.0", | ||||
|         "media-typer": "1.1.0", | ||||
|         "memorystore": "1.6.7", | ||||
| @@ -64,7 +64,7 @@ | ||||
|         "mqtt": "4.3.7", | ||||
|         "multer": "1.4.5-lts.1", | ||||
|         "mustache": "4.2.0", | ||||
|         "node-red-admin": "^3.1.2", | ||||
|         "node-red-admin": "^3.1.3", | ||||
|         "node-watch": "0.7.4", | ||||
|         "nopt": "5.0.0", | ||||
|         "oauth2orize": "1.11.1", | ||||
| @@ -74,18 +74,18 @@ | ||||
|         "passport-oauth2-client-password": "0.1.2", | ||||
|         "raw-body": "2.5.2", | ||||
|         "semver": "7.5.4", | ||||
|         "tar": "6.1.13", | ||||
|         "tar": "6.2.1", | ||||
|         "tough-cookie": "4.1.3", | ||||
|         "uglify-js": "3.17.4", | ||||
|         "uuid": "9.0.0", | ||||
|         "ws": "7.5.6", | ||||
|         "ws": "7.5.10", | ||||
|         "xml2js": "0.6.2" | ||||
|     }, | ||||
|     "optionalDependencies": { | ||||
|         "bcrypt": "5.1.1" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "dompurify": "2.4.1", | ||||
|         "dompurify": "2.5.7", | ||||
|         "grunt": "1.6.1", | ||||
|         "grunt-chmod": "~1.1.1", | ||||
|         "grunt-cli": "~1.4.3", | ||||
| @@ -109,10 +109,10 @@ | ||||
|         "jquery-i18next": "1.2.1", | ||||
|         "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", | ||||
|         "marked": "4.3.0", | ||||
|         "mermaid": "^10.4.0", | ||||
|         "mermaid": "11.3.0", | ||||
|         "minami": "1.2.3", | ||||
|         "mocha": "9.2.2", | ||||
|         "node-red-node-test-helper": "^0.3.2", | ||||
|         "node-red-node-test-helper": "^0.3.3", | ||||
|         "nodemon": "2.0.20", | ||||
|         "proxy": "^1.0.2", | ||||
|         "sass": "1.62.1", | ||||
|   | ||||
| @@ -13,7 +13,6 @@ | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
| var apiUtils = require("../util"); | ||||
| var runtimeAPI; | ||||
| var settings; | ||||
| var theme = require("../editor/theme"); | ||||
|   | ||||
| @@ -205,9 +205,10 @@ function genericStrategy(adminApp,strategy) { | ||||
|     passport.use(new strategy.strategy(options, verify)); | ||||
|  | ||||
|     adminApp.get('/auth/strategy', | ||||
|         passport.authenticate(strategy.name, {session:false, | ||||
|         passport.authenticate(strategy.name, { | ||||
|             session:false, | ||||
|             failureMessage: true, | ||||
|             failureRedirect: settings.httpAdminRoot | ||||
|             failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed' | ||||
|         }), | ||||
|         completeGenerateStrategyAuth, | ||||
|         handleStrategyError | ||||
| @@ -221,7 +222,7 @@ function genericStrategy(adminApp,strategy) { | ||||
|         passport.authenticate(strategy.name, { | ||||
|             session:false, | ||||
|             failureMessage: true, | ||||
|             failureRedirect: settings.httpAdminRoot | ||||
|             failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed' | ||||
|         }), | ||||
|         completeGenerateStrategyAuth, | ||||
|         handleStrategyError | ||||
|   | ||||
| @@ -18,7 +18,6 @@ var BearerStrategy = require('passport-http-bearer').Strategy; | ||||
| var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy; | ||||
|  | ||||
| var passport = require("passport"); | ||||
| var crypto = require("crypto"); | ||||
| var util = require("util"); | ||||
|  | ||||
| var Tokens = require("./tokens"); | ||||
|   | ||||
| @@ -14,11 +14,9 @@ | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
| var express = require("express"); | ||||
| var path = require('path'); | ||||
|  | ||||
| var comms = require("./comms"); | ||||
| var library = require("./library"); | ||||
| var info = require("./settings"); | ||||
|  | ||||
| var auth = require("../auth"); | ||||
|   | ||||
| @@ -15,8 +15,6 @@ | ||||
|  **/ | ||||
|  | ||||
| var apiUtils = require("../util"); | ||||
| var fs = require('fs'); | ||||
| var fspath = require('path'); | ||||
|  | ||||
| var runtimeAPI; | ||||
|  | ||||
|   | ||||
| @@ -13,9 +13,6 @@ | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * 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 | ||||
|  | ||||
|   | ||||
| @@ -15,7 +15,6 @@ | ||||
|  **/ | ||||
|  | ||||
| var apiUtils = require("../util"); | ||||
| var express = require("express"); | ||||
| var runtimeAPI; | ||||
| var settings; | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,6 @@ | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
| var express = require("express"); | ||||
| var util = require("util"); | ||||
| var path = require("path"); | ||||
| var fs = require("fs"); | ||||
|   | ||||
| @@ -99,7 +99,7 @@ module.exports = { | ||||
|             // settings.instanceId is set asynchronously to the editor-api | ||||
|             // being initiaised. So we defer calculating the cacheBuster hash | ||||
|             // 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; | ||||
|   | ||||
| @@ -24,11 +24,8 @@ | ||||
|   * @namespace @node-red/editor-api | ||||
|   */ | ||||
|  | ||||
| var express = require("express"); | ||||
| var bodyParser = require("body-parser"); | ||||
| var util = require('util'); | ||||
| var passport = require('passport'); | ||||
| var cors = require('cors'); | ||||
|  | ||||
| var auth = require("./auth"); | ||||
| var apiUtil = require("./util"); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/editor-api", | ||||
|     "version": "3.1.4", | ||||
|     "version": "3.1.15", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,14 +16,14 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/util": "3.1.4", | ||||
|         "@node-red/editor-client": "3.1.4", | ||||
|         "@node-red/util": "3.1.15", | ||||
|         "@node-red/editor-client": "3.1.15", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "body-parser": "1.20.2", | ||||
|         "body-parser": "1.20.3", | ||||
|         "clone": "2.1.2", | ||||
|         "cors": "2.8.5", | ||||
|         "express-session": "1.17.3", | ||||
|         "express": "4.18.2", | ||||
|         "express-session": "1.18.1", | ||||
|         "express": "4.21.2", | ||||
|         "memorystore": "1.6.7", | ||||
|         "mime": "3.0.0", | ||||
|         "multer": "1.4.5-lts.1", | ||||
| @@ -32,7 +32,7 @@ | ||||
|         "passport-http-bearer": "1.0.1", | ||||
|         "passport-oauth2-client-password": "0.1.2", | ||||
|         "passport": "0.6.0", | ||||
|         "ws": "7.5.6" | ||||
|         "ws": "7.5.10" | ||||
|     }, | ||||
|     "optionalDependencies": { | ||||
|         "bcrypt": "5.1.0" | ||||
|   | ||||
| @@ -719,6 +719,7 @@ | ||||
|             "nodeHelp": "Node Help", | ||||
|             "showHelp": "Show help", | ||||
|             "showInOutline": "Show in outline", | ||||
|             "hideTopics": "Hide topics", | ||||
|             "showTopics": "Show topics", | ||||
|             "noHelp": "No help topic selected", | ||||
|             "changeLog": "Change Log" | ||||
| @@ -914,6 +915,8 @@ | ||||
|         } | ||||
|     }, | ||||
|     "typedInput": { | ||||
|         "selected": "__count__ selected", | ||||
|         "selected_plural": "__count__ selected", | ||||
|         "type": { | ||||
|             "str": "string", | ||||
|             "num": "number", | ||||
|   | ||||
| @@ -719,6 +719,7 @@ | ||||
|       "nodeHelp": "Aide sur les noeuds", | ||||
|       "showHelp": "Afficher l'aide", | ||||
|       "showInOutline": "Afficher dans les grandes lignes", | ||||
|       "hideTopics": "Masquer les sujets", | ||||
|       "showTopics": "Afficher les sujets", | ||||
|       "noHelp": "Aucune rubrique d'aide sélectionnée", | ||||
|       "changeLog": "Journal des modifications" | ||||
| @@ -914,6 +915,8 @@ | ||||
|     } | ||||
|   }, | ||||
|   "typedInput": { | ||||
|     "selected": "__count__ sélectionnée", | ||||
|     "selected_plural": "__count__ sélectionnées", | ||||
|     "type": { | ||||
|       "str": "chaîne de caractères", | ||||
|       "num": "nombre", | ||||
|   | ||||
| @@ -303,7 +303,8 @@ | ||||
|                 "missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません" | ||||
|             }, | ||||
|             "conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。", | ||||
|             "conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。" | ||||
|             "conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。", | ||||
|             "alreadyExists": "本ノードは既に存在" | ||||
|         }, | ||||
|         "copyMessagePath": "パスをコピーしました", | ||||
|         "copyMessageValue": "値をコピーしました", | ||||
| @@ -718,6 +719,7 @@ | ||||
|             "nodeHelp": "ノードヘルプ", | ||||
|             "showHelp": "ヘルプを表示", | ||||
|             "showInOutline": "アウトラインに表示", | ||||
|             "hideTopics": "トピックを非表示", | ||||
|             "showTopics": "トピックを表示", | ||||
|             "noHelp": "ヘルプのトピックが未選択", | ||||
|             "changeLog": "更新履歴" | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/editor-client", | ||||
|     "version": "3.1.4", | ||||
|     "version": "3.1.15", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|   | ||||
| @@ -547,12 +547,16 @@ RED.nodes = (function() { | ||||
|              * @param {String} z tab id | ||||
|              */ | ||||
|             checkTabState: function (z) { | ||||
|                 const ws = workspaces[z] | ||||
|                 const ws = workspaces[z] || subflows[z] | ||||
|                 if (ws) { | ||||
|                     const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0 | ||||
|                     if (Boolean(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, { | ||||
|             defaults:{ | ||||
|                 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" }, | ||||
|             category: sf.category || "subflows", | ||||
| @@ -2360,6 +2379,13 @@ RED.nodes = (function() { | ||||
|             } else { | ||||
|                 delete n.g | ||||
|             } | ||||
|             // If importing into a subflow, ensure an outbound-link doesn't get added | ||||
|             if (activeSubflow && /^link /.test(n.type) && n.links) { | ||||
|                 n.links = n.links.filter(function(id) { | ||||
|                     const otherNode = node_map[id] || RED.nodes.node(id); | ||||
|                     return (otherNode && otherNode.z === activeWorkspace); | ||||
|                 }); | ||||
|             } | ||||
|             for (var d3 in n._def.defaults) { | ||||
|                 if (n._def.defaults.hasOwnProperty(d3)) { | ||||
|                     if (n._def.defaults[d3].type) { | ||||
| @@ -2383,14 +2409,6 @@ RED.nodes = (function() { | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             // If importing into a subflow, ensure an outbound-link doesn't | ||||
|             // get added | ||||
|             if (activeSubflow && /^link /.test(n.type) && n.links) { | ||||
|                 n.links = n.links.filter(function(id) { | ||||
|                     const otherNode = node_map[id] || RED.nodes.node(id); | ||||
|                     return (otherNode && otherNode.z === activeWorkspace) | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|         for (i=0;i<new_subflows.length;i++) { | ||||
|             n = new_subflows[i]; | ||||
|   | ||||
| @@ -734,12 +734,12 @@ | ||||
|             } | ||||
|             if (menu.opts.multiple) { | ||||
|                 var selected = {}; | ||||
|                  this.value().split(",").forEach(function(f) { | ||||
|                      selected[f] = true; | ||||
|                  }) | ||||
|                 this.value().split(",").forEach(function(f) { | ||||
|                     selected[f] = true; | ||||
|                 }); | ||||
|                 menu.find('input[type="checkbox"]').each(function() { | ||||
|                     $(this).prop("checked",selected[$(this).data('value')]) | ||||
|                 }) | ||||
|                     $(this).prop("checked", selected[$(this).data('value')] || false); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|  | ||||
| @@ -830,7 +830,7 @@ | ||||
|                         this.input.trigger('change',[this.propertyType,this.value()]); | ||||
|                     } | ||||
|                 } else { | ||||
|                     this.optionSelectLabel.text(o.length+" selected"); | ||||
|                     this.optionSelectLabel.text(RED._("typedInput.selected", { count: o.length })); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|   | ||||
| @@ -118,10 +118,16 @@ RED.contextMenu = (function () { | ||||
|                     onselect: 'core:split-wire-with-link-nodes', | ||||
|                     disabled: !canEdit || !hasLinks | ||||
|                 }, | ||||
|                 null, | ||||
|                 { onselect: 'core:show-import-dialog', label: RED._('common.label.import')}, | ||||
|                 { onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') } | ||||
|                 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-examples-import-dialog', label: RED._('menu.label.importExample') } | ||||
|                 ) | ||||
|             } | ||||
|  | ||||
|  | ||||
|             if (hasSelection && canEdit) { | ||||
|                 const nodeOptions = [] | ||||
|                 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:delete-selection', label: RED._('keyboard.deleteSelected'), 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) { | ||||
|                 subflow.changed = false; | ||||
|                 if (subflow.changed) { | ||||
|                     subflow.changed = false; | ||||
|                     RED.events.emit("subflows:change", subflow); | ||||
|                 } | ||||
|             }); | ||||
|             RED.nodes.eachWorkspace(function (ws) { | ||||
|                 if (ws.changed || ws.added) { | ||||
| @@ -628,6 +631,7 @@ RED.deploy = (function() { | ||||
|             // Once deployed, cannot undo back to a clean state | ||||
|             RED.history.markAllDirty(); | ||||
|             RED.view.redraw(); | ||||
|             RED.sidebar.config.refresh(); | ||||
|             RED.events.emit("deploy"); | ||||
|         }).fail(function (xhr, textStatus, err) { | ||||
|             RED.nodes.dirty(true); | ||||
|   | ||||
| @@ -248,6 +248,8 @@ RED.editor = (function() { | ||||
|             var value = input.val(); | ||||
|             if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") { | ||||
|                 value = input.text(); | ||||
|             } else if (input.attr("type") === "checkbox") { | ||||
|                 value = input.prop("checked"); | ||||
|             } | ||||
|             var valid = validateNodeProperty(node, defaults, property,value); | ||||
|             if (((typeof valid) === "string") || !valid) { | ||||
| @@ -741,9 +743,16 @@ RED.editor = (function() { | ||||
|             } | ||||
|  | ||||
|             try { | ||||
|                 var rc = editing_node._def.oneditsave.call(editing_node); | ||||
|                 const rc = editing_node._def.oneditsave.call(editing_node); | ||||
|                 if (rc === true) { | ||||
|                     editState.changed = true; | ||||
|                 } else if (typeof rc === 'object' && rc !== null ) { | ||||
|                     if (rc.changed === true) { | ||||
|                         editState.changed = true | ||||
|                     } | ||||
|                     if (Array.isArray(rc.history) && rc.history.length > 0) { | ||||
|                         editState.history = rc.history | ||||
|                     } | ||||
|                 } | ||||
|             } catch(err) { | ||||
|                 console.warn("oneditsave",editing_node.id,editing_node.type,err.toString()); | ||||
| @@ -914,6 +923,17 @@ RED.editor = (function() { | ||||
|                             dirty: startDirty | ||||
|                         } | ||||
|  | ||||
|                         if (editing_node.g) { | ||||
|                             const group = RED.nodes.group(editing_node.g); | ||||
|                             // Don't use RED.group.removeFromGroup as that emits | ||||
|                             // a change event on the node - but we're deleting it | ||||
|                             const index = group?.nodes.indexOf(editing_node) ?? -1; | ||||
|                             if (index > -1) { | ||||
|                                 group.nodes.splice(index, 1); | ||||
|                                 RED.group.markDirty(group); | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         RED.nodes.dirty(true); | ||||
|                         RED.view.redraw(true); | ||||
|                         RED.history.push(historyEvent); | ||||
| @@ -1015,7 +1035,7 @@ RED.editor = (function() { | ||||
|                                     } | ||||
|                                 }); | ||||
|                             } | ||||
|                             var historyEvent = { | ||||
|                             let historyEvent = { | ||||
|                                 t:'edit', | ||||
|                                 node:editing_node, | ||||
|                                 changes:editState.changes, | ||||
| @@ -1031,6 +1051,15 @@ RED.editor = (function() { | ||||
|                                     instances:subflowInstances | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
|                             if (editState.history) { | ||||
|                                 historyEvent = { | ||||
|                                     t: 'multi', | ||||
|                                     events: [ historyEvent, ...editState.history ], | ||||
|                                     dirty: wasDirty | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
|                             RED.history.push(historyEvent); | ||||
|                         } | ||||
|                         editing_node.dirty = true; | ||||
| @@ -1623,8 +1652,8 @@ RED.editor = (function() { | ||||
|                         } | ||||
|  | ||||
|                         if (!isSameObj(old_env, new_env)) { | ||||
|                             editing_node.env = new_env; | ||||
|                             editState.changes.env = editing_node.env; | ||||
|                             editing_node.env = new_env; | ||||
|                             editState.changed = true; | ||||
|                         } | ||||
|  | ||||
|   | ||||
| @@ -514,7 +514,7 @@ RED.editor.codeEditor.monaco = (function() { | ||||
|                 _monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions); | ||||
|                 if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); } | ||||
|             } catch (error) { | ||||
|                 console.warn("monaco - Error setting up json options", err) | ||||
|                 console.warn("monaco - Error setting up json options", error) | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -526,7 +526,7 @@ RED.editor.codeEditor.monaco = (function() { | ||||
|                 if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); } | ||||
|                 if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); } | ||||
|             } catch (error) { | ||||
|                 console.warn("monaco - Error setting up html options", err) | ||||
|                 console.warn("monaco - Error setting up html options", error) | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -546,7 +546,7 @@ RED.editor.codeEditor.monaco = (function() { | ||||
|                 if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); } | ||||
|                 if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); } | ||||
|             } catch (error) { | ||||
|                 console.warn("monaco - Error setting up CSS/SCSS/LESS options", err) | ||||
|                 console.warn("monaco - Error setting up CSS/SCSS/LESS options", error) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -382,9 +382,11 @@ RED.sidebar.config = (function() { | ||||
|                 refreshConfigNodeList(); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes")); | ||||
|         RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes")); | ||||
|  | ||||
|         RED.popover.tooltip($('#red-ui-sidebar-config-collapse-all'), RED._("palette.actions.collapse-all")); | ||||
|         RED.popover.tooltip($('#red-ui-sidebar-config-expand-all'), RED._("palette.actions.expand-all")); | ||||
|     } | ||||
|  | ||||
|     function flashConfigNode(el) { | ||||
|   | ||||
| @@ -36,7 +36,13 @@ RED.sidebar.help = (function() { | ||||
|         toolbar = $("<div>", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(content); | ||||
|         $('<span class="button-group"><a id="red-ui-sidebar-help-show-toc" class="red-ui-button red-ui-button-small selected" href="#"><i class="fa fa-list-ul"></i></a></span>').appendTo(toolbar) | ||||
|         var showTOCButton = toolbar.find('#red-ui-sidebar-help-show-toc') | ||||
|         RED.popover.tooltip(showTOCButton,RED._("sidebar.help.showTopics")); | ||||
|         RED.popover.tooltip(showTOCButton, function () { | ||||
|             if ($(showTOCButton).hasClass('selected')) { | ||||
|                 return RED._("sidebar.help.hideTopics"); | ||||
|             } else { | ||||
|                 return RED._("sidebar.help.showTopics"); | ||||
|             } | ||||
|         }); | ||||
|         showTOCButton.on("click",function(e) { | ||||
|             e.preventDefault(); | ||||
|             if ($(this).hasClass('selected')) { | ||||
| @@ -158,8 +164,10 @@ RED.sidebar.help = (function() { | ||||
|  | ||||
|     function refreshSubflow(sf) { | ||||
|         var item = treeList.treeList('get',"node-type:subflow:"+sf.id); | ||||
|         item.subflowLabel = sf._def.label().toLowerCase(); | ||||
|         item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()})); | ||||
|         if (item) { | ||||
|             item.subflowLabel = sf._def.label().toLowerCase(); | ||||
|             item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()})); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function hideTOC() { | ||||
|   | ||||
| @@ -906,7 +906,10 @@ RED.utils = (function() { | ||||
|      * @returns true if valid, String if invalid | ||||
|      */ | ||||
|     function validateTypedProperty(propertyValue, propertyType, opt) { | ||||
|  | ||||
|         if (propertyValue && /^\${[^}]+}$/.test(propertyValue)) { | ||||
|             // Allow ${ENV_VAR} value | ||||
|             return true | ||||
|         } | ||||
|         let error | ||||
|         if (propertyType === 'json') { | ||||
|             try { | ||||
|   | ||||
| @@ -646,120 +646,128 @@ RED.view = (function() { | ||||
|                 } | ||||
|                 d3.event = event; | ||||
|                 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 { | ||||
|                     var isLink = (nn.type === "link in" || nn.type === "link out") | ||||
|                     var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; | ||||
|  | ||||
|                     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); | ||||
|                     var result = createNode(selected_tool); | ||||
|                     if (!result) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } catch(err) { | ||||
|                 } | ||||
|                     var historyEvent = result.historyEvent; | ||||
|                     var nn = RED.nodes.add(result.node); | ||||
|  | ||||
|                 mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); | ||||
|                 mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); | ||||
|                 mousePos[1] /= scaleFactor; | ||||
|                 mousePos[0] /= scaleFactor; | ||||
|                     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; | ||||
|                     } | ||||
|  | ||||
|                 nn.x = mousePos[0]; | ||||
|                 nn.y = mousePos[1]; | ||||
|                     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); | ||||
|  | ||||
|                 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; | ||||
|                 } | ||||
|                     try { | ||||
|                         var isLink = (nn.type === "link in" || nn.type === "link out") | ||||
|                         var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; | ||||
|  | ||||
|                 if (snapGrid) { | ||||
|                     var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn); | ||||
|                     nn.x -= gridOffset.x; | ||||
|                     nn.y -= gridOffset.y; | ||||
|                 } | ||||
|                         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 linkToSplice = $(ui.helper).data("splice"); | ||||
|                 if (linkToSplice) { | ||||
|                     spliceLink(linkToSplice, nn, historyEvent) | ||||
|                 } | ||||
|                     mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); | ||||
|                     mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); | ||||
|                     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], | ||||
|  | ||||
|                     }; | ||||
|                     if (moveEvent) { | ||||
|                         historyEvent.events.push(moveEvent) | ||||
|                     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); | ||||
|                     } | ||||
|                 } 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); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
| @@ -1182,6 +1190,7 @@ RED.view = (function() { | ||||
|  | ||||
|         if (d3.event.button === 1) { | ||||
|             // Middle Click pan | ||||
|             d3.event.preventDefault(); | ||||
|             mouse_mode = RED.state.PANNING; | ||||
|             mouse_position = [d3.event.pageX,d3.event.pageY] | ||||
|             scroll_position = [chart.scrollLeft(),chart.scrollTop()]; | ||||
| @@ -4156,7 +4165,7 @@ RED.view = (function() { | ||||
|                     } | ||||
|                     var width = img.width * scaleFactor; | ||||
|                     if (width > 20) { | ||||
|                         scalefactor *= 20/width; | ||||
|                         scaleFactor *= 20/width; | ||||
|                         width = 20; | ||||
|                     } | ||||
|                     var height = img.height * scaleFactor; | ||||
| @@ -6063,14 +6072,19 @@ RED.view = (function() { | ||||
|      function createNode(type, x, y, z) { | ||||
|         const wasDirty = RED.nodes.dirty() | ||||
|         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) { | ||||
|             var subflowId = m[1]; | ||||
|             let err | ||||
|             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)) { | ||||
|                 throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") })) | ||||
|             if (err) { | ||||
|                 err.code = 'NODE_RED' | ||||
|                 throw err | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -491,6 +491,11 @@ RED.workspaces = (function() { | ||||
|         createWorkspaceTabs(); | ||||
|         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() { | ||||
|             var oldActive = activeWorkspace; | ||||
|             workspace_tabs.nextTab(); | ||||
| @@ -657,6 +662,9 @@ RED.workspaces = (function() { | ||||
|         RED.events.on("flows:change", (ws) => { | ||||
|             $("#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(); | ||||
|     } | ||||
|   | ||||
| @@ -16,8 +16,20 @@ | ||||
| RED.validators = { | ||||
|     number: function(blankAllowed,mopt){ | ||||
|         return function(v, opt) { | ||||
|             if ((blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v))) { | ||||
|                 return true; | ||||
|             if (blankAllowed && (v === '' || v === undefined)) { | ||||
|                 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) { | ||||
|                 return RED._("validator.errors.invalid-num-prop", { | ||||
|   | ||||
| @@ -37,7 +37,6 @@ ul.red-ui-sidebar-node-config-list { | ||||
|     } | ||||
|     .red-ui-palette-node { | ||||
|         // overflow: hidden; | ||||
|         cursor: default; | ||||
|         &.selected { | ||||
|             border-color: transparent; | ||||
|             box-shadow: 0 0 0 2px var(--red-ui-node-selected-color); | ||||
|   | ||||
| @@ -227,34 +227,42 @@ | ||||
|             name: {value:""}, | ||||
|             props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) { | ||||
|                     if (!v || v.length === 0) { return true } | ||||
|                     const errors = [] | ||||
|                     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 (!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") { | ||||
|                             try{ jsonata(v[i].v); } | ||||
|                             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") { | ||||
|                             try{ JSON.parse(v[i].v); } | ||||
|                             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"){ | ||||
|                             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; | ||||
|                 } | ||||
|             }, | ||||
|             repeat: { | ||||
|                 value:"", validate: function(v, opt) { | ||||
|                     if ((v === "") || | ||||
|                         (RED.validators.number(v) && | ||||
|                         (RED.validators.number()(v) && | ||||
|                          (v >= 0) && (v <= 2147483))) { | ||||
|                         return true; | ||||
|                     } | ||||
| @@ -263,7 +271,7 @@ | ||||
|             }, | ||||
|             crontab: {value:""}, | ||||
|             once: {value:false}, | ||||
|             onceDelay: {value:0.1}, | ||||
|             onceDelay: {value:0.1, validate: RED.validators.number(true)}, | ||||
|             topic: {value:""}, | ||||
|             payload: {value:"", validate: RED.validators.typedInput("payloadType", false) }, | ||||
|             payloadType: {value:"date"}, | ||||
|   | ||||
| @@ -378,7 +378,7 @@ | ||||
|                             return { id: id, label: RED.nodes.workspace(id).label } //flow id + name | ||||
|                         } else { | ||||
|                             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 } | ||||
|                         } | ||||
|                     }) | ||||
|   | ||||
| @@ -194,27 +194,46 @@ | ||||
|                 nodeMap[node.links[i]].new = true; | ||||
|             } | ||||
|         } | ||||
|         var n; | ||||
|         for (var id in nodeMap) { | ||||
|  | ||||
|         let editHistories = [] | ||||
|         let n; | ||||
|         for (let id in nodeMap) { | ||||
|             if (nodeMap.hasOwnProperty(id)) { | ||||
|                 n = RED.nodes.node(id); | ||||
|                 if (n) { | ||||
|                     editHistories.push({ | ||||
|                         t:'edit', | ||||
|                         node: n, | ||||
|                         changes: { | ||||
|                             links: [...n.links] | ||||
|                         }, | ||||
|                         changed: n.changed | ||||
|                     }) | ||||
|                     if (nodeMap[id].old && !nodeMap[id].new) { | ||||
|                         // Removed id | ||||
|                         i = n.links.indexOf(node.id); | ||||
|                         if (i > -1) { | ||||
|                             n.links.splice(i,1); | ||||
|                             n.changed = true | ||||
|                             n.dirty = true | ||||
|                         } | ||||
|                     } else if (!nodeMap[id].old && nodeMap[id].new) { | ||||
|                         // Added id | ||||
|                         i = n.links.indexOf(id); | ||||
|                         if (i === -1) { | ||||
|                             n.links.push(node.id); | ||||
|                             n.changed = true | ||||
|                             n.dirty = true | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (editHistories.length > 0) { | ||||
|             return { | ||||
|                 history: editHistories | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function onAdd() { | ||||
| @@ -254,13 +273,14 @@ | ||||
|             onEditPrepare(this,"link out"); | ||||
|         }, | ||||
|         oneditsave: function() { | ||||
|             onEditSave(this); | ||||
|             const result = onEditSave(this); | ||||
|             // In case the name has changed, ensure any link call nodes on this | ||||
|             // tab are redrawn with the updated name | ||||
|             var localCallNodes = RED.nodes.filterNodes({z:RED.workspaces.active(), type:"link call"}); | ||||
|             localCallNodes.forEach(function(node) { | ||||
|                 node.dirty = true; | ||||
|             }); | ||||
|             return result | ||||
|         }, | ||||
|         onadd: onAdd, | ||||
|         oneditresize: resizeNodeList | ||||
| @@ -329,7 +349,7 @@ | ||||
|             onEditPrepare(this,"link in"); | ||||
|         }, | ||||
|         oneditsave: function() { | ||||
|             onEditSave(this); | ||||
|             return onEditSave(this); | ||||
|         }, | ||||
|         oneditresize: resizeNodeList | ||||
|     }); | ||||
| @@ -373,7 +393,7 @@ | ||||
|  | ||||
|         }, | ||||
|         oneditsave: function() { | ||||
|             onEditSave(this); | ||||
|             return onEditSave(this); | ||||
|         }, | ||||
|         onadd: onAdd, | ||||
|         oneditresize: resizeNodeList | ||||
|   | ||||
| @@ -374,7 +374,7 @@ module.exports = function(RED) { | ||||
|                         iniOpt.breakOnSigint = true; | ||||
|                     } | ||||
|                 } | ||||
|                 node.script = vm.createScript(functionText, createVMOpt(node, "")); | ||||
|                 node.script = new vm.Script(functionText, createVMOpt(node, "")); | ||||
|                 if (node.fin && (node.fin !== "")) { | ||||
|                     var finText = `(function () { | ||||
|                         var node = { | ||||
| @@ -438,10 +438,9 @@ module.exports = function(RED) { | ||||
|  | ||||
|                             //store the error in msg to be used in flows | ||||
|                             msg.error = err; | ||||
|  | ||||
|                             var line = 0; | ||||
|                             var errorMessage; | ||||
|                             if (stack.length > 0) { | ||||
|                                 let line = 0; | ||||
|                                 let errorMessage; | ||||
|                                 while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) { | ||||
|                                     line++; | ||||
|                                 } | ||||
| @@ -455,11 +454,13 @@ module.exports = function(RED) { | ||||
|                                         errorMessage += " (line "+lineno+", col "+cha+")"; | ||||
|                                     } | ||||
|                                 } | ||||
|                                 if (errorMessage) { | ||||
|                                     err.message = errorMessage | ||||
|                                 } | ||||
|                             } | ||||
|                             if (!errorMessage) { | ||||
|                                 errorMessage = err.toString(); | ||||
|                             } | ||||
|                             done(errorMessage); | ||||
|                             // Pass the whole error object so any additional properties | ||||
|                             // (such as cause) are preserved | ||||
|                             done(err); | ||||
|                         } | ||||
|                         else if (typeof err === "string") { | ||||
|                             done(err); | ||||
|   | ||||
| @@ -20,6 +20,7 @@ module.exports = function(RED) { | ||||
|     var exec = require('child_process').exec; | ||||
|     var fs = require('fs'); | ||||
|     var isUtf8 = require('is-utf8'); | ||||
|     const isWindows = process.platform === 'win32' | ||||
|  | ||||
|     function ExecNode(n) { | ||||
|         RED.nodes.createNode(this,n); | ||||
| @@ -85,9 +86,12 @@ module.exports = function(RED) { | ||||
|                         } | ||||
|                     }); | ||||
|                     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  */ | ||||
|                     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}); | ||||
|                     var unknownCommand = (child.pid === undefined); | ||||
|                     if (node.timer !== 0) { | ||||
|   | ||||
| @@ -20,12 +20,26 @@ | ||||
|         <dt class="optional">delay <span class="property-type">number</span></dt> | ||||
|         <dd>Legt die Verzögerung in Millisekunden fest, die auf die Nachricht angewendet werden soll. | ||||
|             Zur Nutzung dieser Option muss <i>Verzög. mit msg.delay überschreibbar</i> aktiviert sein.</dd> | ||||
|         <dt class="optional">rate <span class="property-type">number</span></dt> | ||||
|         <dd>Setzt die Verzögerung in Millisekunden zwischen den Nachrichten. Diese Node überschreibt die | ||||
|             bestehende Verzögerung die in der Node konfiguration, wenn die empfangende Nachricht <code>msg.rate</code> | ||||
|             in Millisekunden enthält. Dies trifft nur zu, wenn in der Node konfiguriert ist, das empfangene | ||||
|             Nachrichten den konfigurierten Wert überschreiben können.</dd> | ||||
|         <dt class="optional">reset</dt> | ||||
|         <dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist, | ||||
|             werden alle im Node gepufferten Nachrichten gelöscht.</dd> | ||||
|         <dt class="optional">flush</dt> | ||||
|         <dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist, | ||||
|             werden alle im Node gepufferten Nachrichten sofort gesendet.</dd> | ||||
|         <dt class="optional">flush</dt> | ||||
|         <dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen numerischen Wert gesetzt ist, | ||||
|             wird diese Anzahl an Nachrichten sofort gesendet. Wenn ein anderer Typ gesetzt ist (z.B. Boolean), | ||||
|             werden alle in der Node gepufferten Nachrichten gesendet.</dd> | ||||
|         <dt class="optional">toFront</dt> | ||||
|         <dd>Wenn diese Eigenschaft im Ratenbegrenzungsmodus für die empfangene Nachricht auf den booleschen Wert | ||||
|             <code>true</code> gesetzt ist, Anschließend wird die Nachricht an den Anfang der Warteschlange verschoben | ||||
|             und als nächstes freigegeben. Dies kann in Kombination mit <code>msg.flush=1</code> verwendet werden, um sofort erneut zu senden. | ||||
|         </dd> | ||||
|     </dl> | ||||
|     <h3>Details</h3> | ||||
|     <p>Wenn Verzögerung als Nachrichtenaktion eingestellt ist, kann die Verzögerungszeit ein fixer Wert, | ||||
|   | ||||
| @@ -103,7 +103,7 @@ | ||||
|     <h4>Automatic mode</h4> | ||||
|     <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 | ||||
|        reverse the action of a <b>split</b> node. | ||||
|        reverse the action of a <b>split</b> node.</p> | ||||
|  | ||||
|     <h4>Manual mode</h4> | ||||
|     <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"> | ||||
|     <p>大域的なフローの設定を保持するノード。大域的な環境変数の定義を含みます。</p> | ||||
| </script>p | ||||
| </script> | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
|         <dt class="optional">template <span class="property-type">string</span></dt> | ||||
|         <dd>由<code>msg.payload</code>填充的模板。如果未在编辑面板中配置,则可以将设为msg的属性。</dd> | ||||
|     </dl> | ||||
|     <h3>Outputs</h3> | ||||
|     <h3>输出</h3> | ||||
|     <dl class="message-properties"> | ||||
|         <dt>msg <span class="property-type">object</span></dt> | ||||
|         <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>例如: | ||||
|     <pre>Hello {{payload.name}}. Today is {{date}}</pre> | ||||
|     <p>receives a message containing: | ||||
|     <p>接收一条消息,其中包含: | ||||
|     <pre>{ | ||||
|   date: "Monday", | ||||
|   payload: { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/nodes", | ||||
|     "version": "3.1.4", | ||||
|     "version": "3.1.15", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
| @@ -18,11 +18,11 @@ | ||||
|         "acorn": "8.8.2", | ||||
|         "acorn-walk": "8.2.0", | ||||
|         "ajv": "8.12.0", | ||||
|         "body-parser": "1.20.2", | ||||
|         "body-parser": "1.20.3", | ||||
|         "cheerio": "1.0.0-rc.10", | ||||
|         "content-type": "1.0.5", | ||||
|         "cookie-parser": "1.4.6", | ||||
|         "cookie": "0.5.0", | ||||
|         "cookie-parser": "1.4.7", | ||||
|         "cookie": "0.7.2", | ||||
|         "cors": "2.8.5", | ||||
|         "cronosjs": "1.7.1", | ||||
|         "denque": "2.1.0", | ||||
| @@ -43,7 +43,7 @@ | ||||
|         "raw-body": "2.5.2", | ||||
|         "tough-cookie": "4.1.3", | ||||
|         "uuid": "9.0.0", | ||||
|         "ws": "7.5.6", | ||||
|         "ws": "7.5.10", | ||||
|         "xml2js": "0.6.2", | ||||
|         "iconv-lite": "0.6.3" | ||||
|     } | ||||
|   | ||||
| @@ -273,7 +273,7 @@ async function installModule(moduleDetails) { | ||||
|                 let extraArgs = triggerPayload.args || []; | ||||
|                 let args = ['install', ...extraArgs, installSpec] | ||||
|                 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 { | ||||
|                 log.trace("skipping npm install"); | ||||
|             } | ||||
|   | ||||
| @@ -25,12 +25,15 @@ const registryUtil = require("./util"); | ||||
| const library = require("./library"); | ||||
| const {exec,log,events,hooks} = require("@node-red/util"); | ||||
| 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; | ||||
|  | ||||
| const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/; | ||||
| const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/; | ||||
| const slashRe = isWindows ? /\\|[/]/ : /[/]/; | ||||
| const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//; | ||||
| const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/; | ||||
|  | ||||
| @@ -225,7 +228,7 @@ async function installModule(module,version,url) { | ||||
|                 let extraArgs = triggerPayload.args || []; | ||||
|                 let args = ['install', ...extraArgs, installName] | ||||
|                 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 { | ||||
|                 log.trace("skipping npm install"); | ||||
|             } | ||||
| @@ -260,7 +263,7 @@ async function installModule(module,version,url) { | ||||
|                 log.warn("------------------------------------------"); | ||||
|                 e = new Error(log._("server.install.install-failed")+": "+err.toString()); | ||||
|                 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; | ||||
|                     }) | ||||
|                 } | ||||
| @@ -356,7 +359,7 @@ async function getModuleVersionFromNPM(module, version) { | ||||
|     } | ||||
|  | ||||
|     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 { | ||||
|                 if (!stdout) { | ||||
|                     log.warn(log._("server.install.install-failed-not-found",{name:module})); | ||||
| @@ -511,7 +514,7 @@ function uninstallModule(module) { | ||||
|                     let extraArgs = triggerPayload.args || []; | ||||
|                     let args = ['remove', ...extraArgs, module] | ||||
|                     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 { | ||||
|                     log.trace("skipping npm uninstall"); | ||||
|                 } | ||||
| @@ -578,7 +581,7 @@ async function checkPrereq() { | ||||
|         installerEnabled = false; | ||||
|     } else { | ||||
|         return new Promise(resolve => { | ||||
|             child_process.execFile(npmCommand,['-v'],function(err,stdout) { | ||||
|             child_process.execFile(npmCommand,['-v'],{ shell: true },function(err,stdout) { | ||||
|                 if (err) { | ||||
|                     log.info(log._("server.palette-editor.npm-not-found")); | ||||
|                     installerEnabled = false; | ||||
|   | ||||
| @@ -36,7 +36,7 @@ async function getFlowsFromPath(path) { | ||||
|                     promises.push(getFlowsFromPath(fullPath)); | ||||
|                 } else if (/\.json$/.test(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", | ||||
|     "version": "3.1.4", | ||||
|     "version": "3.1.15", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,11 +16,11 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/util": "3.1.4", | ||||
|         "@node-red/util": "3.1.15", | ||||
|         "clone": "2.1.2", | ||||
|         "fs-extra": "11.1.1", | ||||
|         "semver": "7.5.4", | ||||
|         "tar": "6.1.13", | ||||
|         "tar": "6.2.1", | ||||
|         "uglify-js": "3.17.4" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -485,7 +485,7 @@ class Flow { | ||||
|         } | ||||
|         if (!key.startsWith("$parent.")) { | ||||
|             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 { | ||||
|                 key = key.substring(8); | ||||
| @@ -678,6 +678,9 @@ class Flow { | ||||
|                 if (logMessage.hasOwnProperty('stack')) { | ||||
|                     errorMessage.error.stack = logMessage.stack; | ||||
|                 } | ||||
|                 if (logMessage.hasOwnProperty('cause')) { | ||||
|                     errorMessage.error.cause = logMessage.cause; | ||||
|                 } | ||||
|                 targetCatchNode.receive(errorMessage); | ||||
|                 handled = true; | ||||
|             }); | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| const flowUtil = require("./util"); | ||||
| const credentials = require("../nodes/credentials"); | ||||
| const clone = require("clone"); | ||||
|  | ||||
| /** | ||||
|  * This class represents a group within the runtime. | ||||
| @@ -41,7 +42,7 @@ class Group { | ||||
|         } | ||||
|         if (!key.startsWith("$parent.")) { | ||||
|             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 { | ||||
|             key = key.substring(8); | ||||
|   | ||||
| @@ -376,7 +376,7 @@ class Subflow extends Flow { | ||||
|         } | ||||
|         if (!key.startsWith("$parent.")) { | ||||
|             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 { | ||||
|             key = key.substring(8); | ||||
|   | ||||
| @@ -462,9 +462,8 @@ function stop(type,diff,muteLog,isDeploy) { | ||||
|     if (type === 'nodes') { | ||||
|         stopList = diff.changed.concat(diff.removed); | ||||
|     } else if (type === 'flows') { | ||||
|         stopList = diff.changed.concat(diff.removed).concat(diff.linked); | ||||
|         stopList = diff.changed.concat(diff.removed).concat(diff.linked).concat(diff.rewired); | ||||
|     } | ||||
|  | ||||
|     events.emit("flows:stopping",{config: activeConfig, type: type, diff: diff}) | ||||
|  | ||||
|     // Stop the global flow object last | ||||
|   | ||||
| @@ -106,14 +106,22 @@ async function evaluateEnvProperties(flow, env, credentials) { | ||||
|                             result = { value: result, __clone__: true} | ||||
|                         } | ||||
|                         evaluatedEnv[name] = result | ||||
|                     } else { | ||||
|                         evaluatedEnv[name] = undefined | ||||
|                         flow.error(`Error evaluating env property '${name}': ${err.toString()}`) | ||||
|                     } | ||||
|                     resolve() | ||||
|                 }); | ||||
|             })) | ||||
|         } else { | ||||
|             value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); | ||||
|             if (typeof value  === 'object') { | ||||
|                 value = { value: value, __clone__: true} | ||||
|             try { | ||||
|                 value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); | ||||
|                 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 | ||||
|   | ||||
| @@ -384,7 +384,8 @@ var api = module.exports = { | ||||
|                     } | ||||
|                 } | ||||
|             } 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 || []) | ||||
|                 existingCredentialKeys.forEach(key => { | ||||
|                     if (!newCreds.map?.[key]) { | ||||
| @@ -396,7 +397,7 @@ var api = module.exports = { | ||||
|                 }) | ||||
|                 newCredentialKeys.forEach(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 | ||||
|                             // value has been changed | ||||
|                             savedCredentials.map[key] = newCreds.map[key] | ||||
|   | ||||
| @@ -77,7 +77,7 @@ var storageModuleInterface = { | ||||
|                         flows: flows, | ||||
|                         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; | ||||
|                 }) | ||||
|             }); | ||||
| @@ -95,7 +95,7 @@ var storageModuleInterface = { | ||||
|  | ||||
|             return credentialSavePromise.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", | ||||
|     "version": "3.1.4", | ||||
|     "version": "3.1.15", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,11 +16,11 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/registry": "3.1.4", | ||||
|         "@node-red/util": "3.1.4", | ||||
|         "@node-red/registry": "3.1.15", | ||||
|         "@node-red/util": "3.1.15", | ||||
|         "async-mutex": "0.4.0", | ||||
|         "clone": "2.1.2", | ||||
|         "express": "4.18.2", | ||||
|         "express": "4.21.2", | ||||
|         "fs-extra": "11.1.1", | ||||
|         "json-stringify-safe": "5.0.1" | ||||
|     } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/util", | ||||
|     "version": "3.1.4", | ||||
|     "version": "3.1.15", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
| @@ -18,7 +18,7 @@ | ||||
|         "fs-extra": "11.1.1", | ||||
|         "i18next": "21.10.0", | ||||
|         "json-stringify-safe": "5.0.1", | ||||
|         "jsonata": "1.8.6", | ||||
|         "jsonata": "1.8.7", | ||||
|         "lodash.clonedeep": "^4.5.0", | ||||
|         "moment": "2.29.4", | ||||
|         "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; | ||||
|  | ||||
| const NODE_MAJOR_VERSION = process.versions.node.split('.')[0]; | ||||
| if (NODE_MAJOR_VERSION > 14) { | ||||
|     const dns = require('node:dns'); | ||||
| if (NODE_MAJOR_VERSION >= 16) { | ||||
|     const dns = require('dns'); | ||||
|     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", | ||||
|     "version": "3.1.4", | ||||
|     "version": "3.1.15", | ||||
|     "description": "Low-code programming for event-driven applications", | ||||
|     "homepage": "https://nodered.org", | ||||
|     "license": "Apache-2.0", | ||||
| @@ -31,15 +31,15 @@ | ||||
|         "flow" | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/editor-api": "3.1.4", | ||||
|         "@node-red/runtime": "3.1.4", | ||||
|         "@node-red/util": "3.1.4", | ||||
|         "@node-red/nodes": "3.1.4", | ||||
|         "@node-red/editor-api": "3.1.15", | ||||
|         "@node-red/runtime": "3.1.15", | ||||
|         "@node-red/util": "3.1.15", | ||||
|         "@node-red/nodes": "3.1.15", | ||||
|         "basic-auth": "2.0.1", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "express": "4.18.2", | ||||
|         "express": "4.21.2", | ||||
|         "fs-extra": "11.1.1", | ||||
|         "node-red-admin": "^3.1.2", | ||||
|         "node-red-admin": "^3.1.3", | ||||
|         "nopt": "5.0.0", | ||||
|         "semver": "7.5.4" | ||||
|     }, | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
| // 4. Edit your settings file to set the theme: | ||||
| //       editorTheme: { | ||||
| //           page: { | ||||
| //               css: "/path/to/file/generated/by/this/script" | ||||
| //               css: '/path/to/file/generated/by/this/script' | ||||
| //           } | ||||
| //       } | ||||
| // | ||||
| @@ -22,110 +22,69 @@ | ||||
|  | ||||
|  | ||||
|  | ||||
| const os = require("os"); | ||||
| const nopt = require("nopt"); | ||||
| const path = require("path"); | ||||
| const fs = require("fs-extra"); | ||||
| const sass = require("sass"); | ||||
| const os = require('os'); | ||||
| const nopt = require('nopt'); | ||||
| const path = require('path'); | ||||
| const fs = require('fs-extra'); | ||||
| const sass = require('sass'); | ||||
|  | ||||
| const knownOpts = { | ||||
|     "help": Boolean, | ||||
|     "long": Boolean, | ||||
|     "in": [path], | ||||
|     "out": [path] | ||||
|     'help': Boolean, | ||||
|     'long': Boolean, | ||||
|     'in': [path], | ||||
|     'out': [path] | ||||
| }; | ||||
| const shortHands = { | ||||
|     "?":["--help"] | ||||
|     '?':['--help'] | ||||
| }; | ||||
| nopt.invalidHandler = function(k,v,t) {} | ||||
|  | ||||
| const parsedArgs = nopt(knownOpts,shortHands,process.argv,2) | ||||
|  | ||||
| if (parsedArgs.help) { | ||||
|     console.log("Usage: build-custom-theme [-?] [--in FILE] [--out FILE]"); | ||||
|     console.log(""); | ||||
|     console.log("Options:"); | ||||
|     console.log("  --in  FILE  Custom colors sass file"); | ||||
|     console.log("  --out FILE  Where you write the result"); | ||||
|     console.log("  --long      Do not compress the output"); | ||||
|     console.log("  -?, --help  Show this help"); | ||||
|     console.log(""); | ||||
|     process.exit(); | ||||
|     showUsageAndExit(0) | ||||
| } | ||||
|  | ||||
|  | ||||
| const ruleRegex = /(\$.*?) *: *(\S[\S\s]*?);/g; | ||||
| var match; | ||||
|  | ||||
| const customColors = {}; | ||||
|  | ||||
| if (parsedArgs.in && fs.existsSync(parsedArgs.in)) { | ||||
|     let customColorsFile = fs.readFileSync(parsedArgs.in,"utf-8"); | ||||
|     while((match = ruleRegex.exec(customColorsFile)) !== null) { | ||||
|         customColors[match[1]] = match[2]; | ||||
|     } | ||||
| if (!parsedArgs.in) { | ||||
|     console.warn('Missing argument: in') | ||||
|     showUsageAndExit(1) | ||||
| } | ||||
|  | ||||
| // Load base colours | ||||
| let colorsFile = fs.readFileSync(path.join(__dirname,"../packages/node_modules/@node-red/editor-client/src/sass/colors.scss"),"utf-8") | ||||
| let updatedColors = []; | ||||
|  | ||||
| while((match = ruleRegex.exec(colorsFile)) !== null) { | ||||
|     updatedColors.push(match[1]+": "+(customColors[match[1]]||match[2])+";") | ||||
| } | ||||
|  | ||||
|  | ||||
| (async function() { | ||||
|     const tmpDir = os.tmpdir(); | ||||
|     const workingDir = await fs.mkdtemp(`${tmpDir}${path.sep}`); | ||||
|     await fs.copy(path.join(__dirname,"../packages/node_modules/@node-red/editor-client/src/sass/"),workingDir) | ||||
|     await fs.writeFile(path.join(workingDir,"colors.scss"),updatedColors.join("\n")) | ||||
|  | ||||
|     const result = sass.renderSync({ | ||||
|         outputStyle: "expanded", | ||||
|         file: path.join(workingDir,"style-custom-theme.scss"), | ||||
|     }); | ||||
|     await fs.copy(path.join(__dirname, '../packages/node_modules/@node-red/editor-client/src/sass/'), workingDir); | ||||
|     await fs.copyFile(parsedArgs.in, path.join(workingDir,'colors.scss')); | ||||
|  | ||||
|     const css = result.css.toString() | ||||
|     const lines = css.split("\n"); | ||||
|     const colorCSS = [] | ||||
|     const nonColorCSS = []; | ||||
|     const output = sass.compile( | ||||
|         path.join(workingDir, 'style-custom-theme.scss'), | ||||
|         {style: parsedArgs.long === true ? 'expanded' : 'compressed'} | ||||
|     ); | ||||
|  | ||||
|     let inKeyFrameBlock = false; | ||||
|  | ||||
|     lines.forEach(l => { | ||||
|         if (inKeyFrameBlock) { | ||||
|             nonColorCSS.push(l); | ||||
|             if (/^}/.test(l)) { | ||||
|                 inKeyFrameBlock = false; | ||||
|             } | ||||
|         } else if (/^@keyframes/.test(l)) { | ||||
|             nonColorCSS.push(l); | ||||
|             inKeyFrameBlock = true; | ||||
|         } else if (!/^  /.test(l)) { | ||||
|             colorCSS.push(l); | ||||
|             nonColorCSS.push(l); | ||||
|         } else if (/color|border|background|fill|stroke|outline|box-shadow/.test(l)) { | ||||
|             colorCSS.push(l); | ||||
|         } else { | ||||
|             nonColorCSS.push(l); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     const nrPkg = require("../package.json"); | ||||
|     const nrPkg = require('../package.json'); | ||||
|     const now = new Date().toISOString(); | ||||
|     const header = `/*\n* Theme generated with Node-RED ${nrPkg.version} on ${now}\n*/`; | ||||
|  | ||||
|     const header = `/* | ||||
|     * Theme generated with Node-RED ${nrPkg.version} on ${now} | ||||
|     */`; | ||||
|  | ||||
|     var output = sass.renderSync({outputStyle: parsedArgs.long?"expanded":"compressed",data:colorCSS.join("\n")}); | ||||
|     if (parsedArgs.out) { | ||||
|  | ||||
|         await fs.writeFile(parsedArgs.out,header+"\n"+output.css); | ||||
|         await fs.writeFile(parsedArgs.out, header+'\n'+output.css); | ||||
|     } else { | ||||
|         console.log(header); | ||||
|         console.log(output.css.toString()); | ||||
|     } | ||||
|  | ||||
|     await fs.remove(workingDir); | ||||
| })() | ||||
|  | ||||
| function showUsageAndExit (exitCode) { | ||||
|     console.log(''); | ||||
|     console.log('Usage: build-custom-theme [-?] [--in FILE] [--out FILE]'); | ||||
|     console.log(''); | ||||
|     console.log('Options:'); | ||||
|     console.log('  --in  FILE  Custom colors sass file'); | ||||
|     console.log('  --out FILE  Where you write the result'); | ||||
|     console.log('  --long      Do not compress the output'); | ||||
|     console.log('  -?, --help  Show this help'); | ||||
|     console.log(''); | ||||
|     process.exit(exitCode); | ||||
| } | ||||
| @@ -4,7 +4,7 @@ const path = require("path"); | ||||
| const fs = require("fs-extra"); | ||||
| const should = require("should"); | ||||
|  | ||||
| const LATEST = "3"; | ||||
| const LATEST = "4"; | ||||
|  | ||||
| function generateScript() { | ||||
|     return new Promise((resolve, reject) => { | ||||
|   | ||||
| @@ -390,7 +390,8 @@ describe('function node', function() { | ||||
|                     msg.should.have.property('level', helper.log().ERROR); | ||||
|                     msg.should.have.property('id', 'n1'); | ||||
|                     msg.should.have.property('type', 'function'); | ||||
|                     msg.should.have.property('msg', 'ReferenceError: retunr is not defined (line 2, col 1)'); | ||||
|                     msg.should.have.property('msg') | ||||
|                     msg.msg.message.should.equal('ReferenceError: retunr is not defined (line 2, col 1)'); | ||||
|                     done(); | ||||
|                 } catch(err) { | ||||
|                     done(err); | ||||
| @@ -659,7 +660,8 @@ describe('function node', function() { | ||||
|                 msg.should.have.property('level', helper.log().ERROR); | ||||
|                 msg.should.have.property('id', name); | ||||
|                 msg.should.have.property('type', 'function'); | ||||
|                 msg.should.have.property('msg', 'Error: Callback must be a function'); | ||||
|                 msg.should.have.property('msg') | ||||
|                 msg.msg.message.should.equal('Callback must be a function'); | ||||
|                 done(); | ||||
|             } | ||||
|             catch (e) { | ||||
|   | ||||
| @@ -60,6 +60,7 @@ describe('HTTP Request Node', function() { | ||||
|     function startServer(done) { | ||||
|         testPort += 1; | ||||
|         testServer = stoppable(http.createServer(testApp)); | ||||
|         const promises = [] | ||||
|         testServer.listen(testPort,function(err) { | ||||
|             testSslPort += 1; | ||||
|             console.log("ssl port", testSslPort); | ||||
| @@ -81,13 +82,17 @@ describe('HTTP Request Node', function() { | ||||
|                 */ | ||||
|             }; | ||||
|             testSslServer = stoppable(https.createServer(sslOptions,testApp)); | ||||
|             testSslServer.listen(testSslPort, function(err){ | ||||
|                 if (err) { | ||||
|                     console.log(err); | ||||
|                 } else { | ||||
|                     console.log("started testSslServer"); | ||||
|                 } | ||||
|             }); | ||||
|             console.log('> start testSslServer') | ||||
|             promises.push(new Promise((resolve, reject) => { | ||||
|                 testSslServer.listen(testSslPort, function(err){ | ||||
|                     console.log(' done testSslServer') | ||||
|                     if (err) { | ||||
|                         reject(err) | ||||
|                     } else { | ||||
|                         resolve() | ||||
|                     } | ||||
|                 }); | ||||
|             })) | ||||
|  | ||||
|             testSslClientPort += 1; | ||||
|             var sslClientOptions = { | ||||
| @@ -97,10 +102,17 @@ describe('HTTP Request Node', function() { | ||||
|                 requestCert: true | ||||
|             }; | ||||
|             testSslClientServer = stoppable(https.createServer(sslClientOptions, testApp)); | ||||
|             testSslClientServer.listen(testSslClientPort, function(err){ | ||||
|                 console.log("ssl-client", err) | ||||
|             }); | ||||
|  | ||||
|             console.log('> start testSslClientServer') | ||||
|             promises.push(new Promise((resolve, reject) => { | ||||
|                 testSslClientServer.listen(testSslClientPort, function(err){ | ||||
|                     console.log(' done testSslClientServer') | ||||
|                     if (err) { | ||||
|                         reject(err) | ||||
|                     } else { | ||||
|                         resolve() | ||||
|                     } | ||||
|                 }); | ||||
|             })) | ||||
|             testProxyPort += 1; | ||||
|             testProxyServer = stoppable(httpProxy(http.createServer())) | ||||
|  | ||||
| @@ -109,7 +121,17 @@ describe('HTTP Request Node', function() { | ||||
|                     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 | ||||
|             testProxyServerAuth = stoppable(httpProxy(http.createServer())) | ||||
| @@ -131,9 +153,19 @@ describe('HTTP Request Node', function() { | ||||
|                     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) { | ||||
|                 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')); | ||||
|     }); | ||||
|  | ||||
|     it('returns a valid example path', function(done) { | ||||
|     it('returns valid example paths', function(done) { | ||||
|         library.init(); | ||||
|         library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() { | ||||
|             try { | ||||
|                 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'); | ||||
|                 examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json')) | ||||
|  | ||||
|                 examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json')); | ||||
|  | ||||
|                 library.removeExamplesDir('test-module'); | ||||
|  | ||||
| @@ -57,6 +56,5 @@ describe("library api", function() { | ||||
|                 done(err); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     }) | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -16,6 +16,31 @@ describe('Group', function () { | ||||
|             group.getSetting("NR_GROUP_NAME").should.equal("g1") | ||||
|             group.getSetting("NR_GROUP_ID").should.equal("group1") | ||||
|         }) | ||||
|         it("returns cloned env var property", async function () { | ||||
|             const group = new Group({ | ||||
|                 getSetting: v => v+v | ||||
|             }, { | ||||
|                 name: "g1", | ||||
|                 id: "group1", | ||||
|                 env: [ | ||||
|                     { | ||||
|                         name: 'jsonEnvVar', | ||||
|                         type: 'json', | ||||
|                         value: '{"a":1}' | ||||
|                     } | ||||
|                 ] | ||||
|             }) | ||||
|             await group.start() | ||||
|             const result = group.getSetting('jsonEnvVar') | ||||
|             result.should.have.property('a', 1) | ||||
|             result.a = 2 | ||||
|             result.b = 'hello' | ||||
|  | ||||
|             const result2 = group.getSetting('jsonEnvVar') | ||||
|             result2.should.have.property('a', 1) | ||||
|             result2.should.not.have.property('b') | ||||
|  | ||||
|         }) | ||||
|         it("delegates to parent if not found", async function () { | ||||
|             const group = new Group({ | ||||
|                 getSetting: v => v+v | ||||
|   | ||||
		Reference in New Issue
	
	Block a user