Compare commits

..

100 Commits

Author SHA1 Message Date
Nick O'Leary
526b3fda91 Include rewired nodes when calculating Modified Flows stop list 2024-06-10 16:56:49 +01:00
Nick O'Leary
61b12f6bbe Merge pull request #3743 from bonanitech/fix-build-script
Ensure all CSS variables are in the output file
2024-05-30 13:43:25 +01:00
Nick O'Leary
d71b22412b Merge pull request #4734 from GogoVega/fix-sidebar-config
Fix the Sidebar Config is not refreshed after a deploy
2024-05-30 13:40:12 +01:00
Nick O'Leary
e408c6b376 Merge pull request #4729 from GogoVega/fix-4728
Fix checkboxes are not updated when calling `typedInput("value", "")`
2024-05-30 10:32:27 +01:00
GogoVega
bf30c24e8e Fix the Sidebar Config is not refreshed after a deploy 2024-05-29 20:44:49 +02:00
GogoVega
6c14ed0ef5 Revert and fix an undefined value returned instead of a false value 2024-05-28 22:08:42 +02:00
Nick O'Leary
6d41ecdae0 Merge pull request #4716 from corentin-sodebo-voile/master
Fix panning with middle mouse button on windows 10/11
2024-05-28 10:28:57 +01:00
Nick O'Leary
3f89bc2733 Merge pull request #4727 from kazuhitoyokoi/master-addjpn
Add Japanese translation for sidebar tooltip
2024-05-28 09:49:35 +01:00
Nick O'Leary
87a25df162 Merge pull request #4730 from GogoVega/translate-multiple-option-label
Translate the number of items selected in the options list
2024-05-28 09:49:03 +01:00
GogoVega
341f43610a Translate the number of items selected in the options list 2024-05-27 19:56:51 +02:00
GogoVega
67cdf3ef96 Fix checkboxes are not updated when calling typedInput("value", "") 2024-05-27 18:59:25 +02:00
Kazuhito Yokoi
cd98f448e9 Add Japanese translation for sidebar tooltip 2024-05-26 23:34:32 +09:00
corentin-sodebo-voile
fac79fd068 Fix panning with middle mouse button on windows 10/11
Without preventDefault, when you try to drag the canvas with middle mouse button on Windows (e.g. in Chrome), the cursor change to a "scroll cursor" and the canvas scrolls endlessly instead of being dragged accurately.
2024-05-23 15:30:13 +02:00
Nick O'Leary
da97c5d558 Merge pull request #4715 from GogoVega/fix-validateNodeEditorProperty
Fix a checkbox should return a Boolean value and not the string `on`
2024-05-23 13:43:55 +01:00
Nick O'Leary
ae7b9fe62e Merge pull request #4714 from GogoVega/fix-4712
Deleting a grouped node should update the group
2024-05-23 13:43:26 +01:00
GogoVega
e52c2911da Fix a checkbox must return a boolean value and not on 2024-05-23 10:57:51 +02:00
Nick O'Leary
07a29ff779 Merge pull request #4711 from GogoVega/change-config-node-cursor
Change the Config Node cursor to `pointer`
2024-05-22 17:02:05 +01:00
Nick O'Leary
1b4a8ebe83 Merge branch 'master' into fix-4712 2024-05-22 17:01:29 +01:00
GogoVega
abf2eacf18 Revert and fix without using RED.group.removeFromGroup 2024-05-21 19:48:21 +02:00
Gauthier Dandele
f808f4e2e8 Apply suggestions from code review
Co-authored-by: Nick O'Leary <nick.oleary@gmail.com>
2024-05-21 19:01:57 +02:00
Nick O'Leary
ca33d6b799 Merge pull request #4713 from GogoVega/add-missing-sidebar-tooltips
Add missing tooltips to Sidebar
2024-05-21 17:15:52 +01:00
Nick O'Leary
940740f15d Merge pull request #4710 from node-red/4704-node-edit-history
Allow nodes to return additional history entries in onEditSave
2024-05-21 17:10:23 +01:00
GogoVega
51208fcd0c Fix indexOf returns -1 2024-05-21 16:06:15 +02:00
GogoVega
707152d82f Deleting a grouped node should update the group 2024-05-21 15:07:26 +02:00
GogoVega
5538f6dd8a Add missing tooltips to Sidebar 2024-05-21 12:58:29 +02:00
GogoVega
d601e2caa4 Change the config node cursor to pointer 2024-05-21 11:37:01 +02:00
Nick O'Leary
46fdf56c79 Allow nodes to return additional history entries in onEditSave 2024-05-20 16:41:44 +01:00
Nick O'Leary
f55ee6e665 Merge pull request #4685 from node-red/4683-preserve-full-error-obj
Pass full error object in Function node and copy over cause property
2024-05-13 15:25:02 +01:00
Nick O'Leary
03648dc7e8 Update tests for changed function node low-level output 2024-05-09 17:25:47 +01:00
Nick O'Leary
66a667fe58 Pass full error object in Function node and copy over cause property
Fixes #4683
2024-05-09 16:48:51 +01:00
Nick O'Leary
1bb3a0eca5 Merge pull request #4534 from patlux/master
Replacing vm.createScript in favour of vm.Script
2024-05-09 15:19:45 +01:00
Nick O'Leary
08927dfb55 Merge pull request #4684 from node-red/4363-autoLogin-redirect-loop
Avoid login loops when autoLogin enabled but login fails
2024-05-08 15:48:21 +01:00
Nick O'Leary
b27483de9c Avoid login loops when autoLogin enabled but login fails
Fixes #4363
2024-05-08 15:09:51 +01:00
Nick O'Leary
211d420fb2 Merge pull request #4667 from node-red/fix-subflow-property-undo
Fix undo of subflow env property edits
2024-04-23 23:45:46 +02:00
Nick O'Leary
b8ca4665c1 Merge pull request #4660 from JoshuaCWebDeveloper/patch-1
Fix three error typos in monaco.js
2024-04-23 23:45:16 +02:00
Nick O'Leary
960af87fb0 Ensure subflow change state is cleared after deploy 2024-04-23 21:17:35 +02:00
Nick O'Leary
de7339ae97 Fix undo of subflow env property edits 2024-04-23 20:39:14 +02:00
Stephen McLaughlin
0995af62b6 Merge pull request #4664 from ZJvandeWeg/patch-3
docs: Add closing paragraph tag
2024-04-20 13:54:37 +01:00
Zeger-Jan van de Weg
c2e03a40b4 docs: Add closing paragraph tag
Minor change that only improves xpath parsing.
2024-04-20 14:20:59 +02:00
Joshua Carter
c855050bcf Fix three error typos in monaco.js 2024-04-15 08:09:26 -07:00
Nick O'Leary
29ed5b2792 Merge pull request #4655 from node-red/rel319
Bump for 3.1.9 release
2024-04-11 19:22:24 +01:00
Nick O'Leary
e39216e65a Bump for 3.1.9 release 2024-04-11 19:15:46 +01:00
Nick O'Leary
7ac7f9b4c8 Merge pull request #4654 from node-red/fix-subflow-recursion-check
Prevent subflow being added to itself
2024-04-11 19:12:43 +01:00
Stephen McLaughlin
4709eb9d49 Merge pull request #4652 from node-red/fix-windows-spawn
Fix use of spawn on windows with cmd files
2024-04-11 17:51:13 +01:00
Nick O'Leary
c13b8266dd Prevent subflow being added to itself 2024-04-11 17:05:10 +01:00
Nick O'Leary
bd58431603 Fix use of spawn on windows with cmd files 2024-04-11 14:40:29 +01:00
Nick O'Leary
9a3cb0b2b5 Merge pull request #4640 from node-red/fix-subflow-init-err
Guard refresh of unknown subflow
2024-04-02 20:06:47 +01:00
Nick O'Leary
6beae5a806 Merge pull request #4642 from node-red/4641-fix-subflow-module-debug-logging
Fix subflow module sending messages to debug sidebar
2024-04-02 20:06:31 +01:00
Nick O'Leary
a0636632a1 Fix subflow module sending messages to debug sidebar
Fixes #4641
2024-04-02 17:42:19 +01:00
Nick O'Leary
5dfa47ab6c Guard refresh of unknown subflow 2024-04-02 15:54:34 +01:00
Nick O'Leary
ade4679e8c Merge pull request #4636 from node-red/rel318
Bump for 3.1.8
2024-03-28 15:23:07 +00:00
Nick O'Leary
410b938442 Bump for 3.1.8 2024-03-28 15:02:02 +00:00
Nick O'Leary
19dcc3a683 Merge pull request #4632 from node-red/4625-sf-env-err-handling
Add validation and error handling on subflow instance properties
2024-03-28 11:10:28 +00:00
Nick O'Leary
20d067c1ea Merge pull request #4633 from node-red/4617-hide-library-context-options
Hide import/export context menu if disabled in theme
2024-03-28 11:10:14 +00:00
Nick O'Leary
9526566799 Hide import/export context menu if disabled in theme 2024-03-28 11:00:10 +00:00
Nick O'Leary
0b9dd82c91 Merge pull request #4631 from node-red/4626-subflow-change-notification
Show change indicator on subflow tabs
2024-03-27 19:10:39 +00:00
Nick O'Leary
19213434f9 Add validation to subflow instance env properties 2024-03-27 19:08:25 +00:00
Nick O'Leary
014691346a Handle malformed env var values and log errors 2024-03-27 18:23:12 +00:00
Nick O'Leary
6738b95c29 Merge pull request #4630 from node-red/bump-express
Bump dependencies
2024-03-27 18:11:54 +00:00
Nick O'Leary
6a8230ec1e Show change icon on subflow tabs
Fixes #4626
2024-03-27 18:10:04 +00:00
Nick O'Leary
5679d264b6 Bump dependencies 2024-03-27 18:00:06 +00:00
Nick O'Leary
37265cf4ef Merge pull request #4619 from node-red/4600-reset-workspace-index
Reset workspace index when clearing nodes
2024-03-21 17:38:39 +00:00
Nick O'Leary
8a63275989 Merge pull request #4613 from kazuhitoyokoi/master-fixglobalconfig
Remove typo in global config
2024-03-21 16:54:01 +00:00
Nick O'Leary
7fc64a84e8 Bump test helper 2024-03-21 15:16:49 +00:00
Nick O'Leary
02f7cdd5aa Ensure all httpRequest test servers are ready before tests run 2024-03-21 15:03:37 +00:00
Nick O'Leary
d7dcceef60 Add debug for http tests 2024-03-21 11:32:29 +00:00
Nick O'Leary
ae5e1570ae Reset workspace index when clearing nodes
Fixes #4600
2024-03-21 11:14:34 +00:00
Kazuhito Yokoi
3ca045394a Remove typo in global config 2024-03-16 18:51:13 +09:00
Nick O'Leary
179032cd4d Merge pull request #4608 from node-red/rel317
Bump for 3.1.7 release
2024-03-12 17:43:32 +00:00
Nick O'Leary
6a6f0d04d6 Bump for 3.1.7 release 2024-03-12 14:25:41 +00:00
Nick O'Leary
add4d9758c Merge pull request #4603 from kazuhitoyokoi/master-addjpn
Add Japanese translation for v3.1.6
2024-03-11 16:07:28 +00:00
Kazuhito Yokoi
a0d3ea62b2 Add Japanese translation for v3.1.6 2024-03-10 23:36:20 +09:00
Nick O'Leary
7447e88a50 Merge pull request #4593 from hardillb/hardillb-patch-1
Update jsonata version
2024-03-07 14:26:02 +00:00
Ben Hardill
a193b79d3d Bump jsonata to match utils 2024-03-05 10:31:03 +00:00
Ben Hardill
da380f7464 Update jsonata version
Pulls in fix for CVE-2024-27307
2024-03-05 10:22:49 +00:00
Nick O'Leary
269cf02c0b Merge pull request #4586 from node-red/rel316
Bump for 3.1.6 release
2024-03-01 11:47:57 +00:00
Nick O'Leary
fb50e2772a Bump for 3.1.6 release 2024-03-01 10:50:06 +00:00
Nick O'Leary
058c97138a Merge pull request #4582 from node-red/3795-allow-env-var-in-num-field-validation
Do not flag env var in num typedInput as error
2024-02-26 17:01:45 +00:00
Nick O'Leary
828ae29aed Merge pull request #4581 from node-red/4579-fix-undef-env-vars
Handle undefined env vars
2024-02-26 17:01:27 +00:00
Nick O'Leary
6a0f45140c Merge pull request #4568 from JaysonHurst/fips
fix: Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 …
2024-02-26 17:00:26 +00:00
Nick O'Leary
50a267528d Merge pull request #4580 from giscafer/remove-never-use-code
chore: remove never use import code
2024-02-26 16:58:20 +00:00
Nick O'Leary
220786be60 Do not flag env var in num typedInput as error 2024-02-26 16:55:01 +00:00
Nick O'Leary
fa78bb3d78 Handle undefined env vars
Fixes #4579
2024-02-26 16:17:09 +00:00
Nick O'Leary
9a32ebd0c0 Merge pull request #4578 from kazuhitoyokoi/master-fiximportdialog
Fix example flow name in import dialog
2024-02-26 16:09:10 +00:00
giscafer
4643f5e8cc chore: remove never use import code 2024-02-25 22:44:01 +08:00
Kazuhito Yokoi
7de0984d6d Update test case for example flow name 2024-02-25 17:38:46 +09:00
Kazuhito Yokoi
635334f096 Fix example flow name in import dialog 2024-02-25 17:04:42 +09:00
Nick O'Leary
f0d0990b5a Merge pull request #4575 from giscafer/master
fix: template node zh-CN translation
2024-02-22 13:03:24 +00:00
giscafer
43b3589451 fix: template node zh-CN translation 2024-02-22 13:02:06 +08:00
Nick O'Leary
016a19ba7c Merge pull request #4570 from node-red/fix-icon-scaling
Fix missing node icons in workspace
2024-02-20 10:37:33 +00:00
Nick O'Leary
aeb79bce2a Fix missing node icons in workspace 2024-02-19 16:07:22 +00:00
Jayson Hurst
0ab9b9a5fd Merge branch 'master' into fips 2024-02-16 17:53:30 -07:00
Jayson Hurst
56e58521bd Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 crypto hashes to work with the FIPS crypto policy. 2024-02-17 00:35:03 +00:00
Patrick Wozniak
28907082f1 fix usage of vm.Script() 2024-01-21 02:16:00 +01:00
Patrick Wozniak
f83174c40a fix use of vm.Script by adding new 2024-01-21 01:23:07 +01:00
Patrick Wozniak
ec062d008f replace vm.createScript in favor of vm.Script 2024-01-21 01:13:00 +01:00
Patrick Wozniak
a587655a5a adding pollyfill for vm.createScript
adds support for bun.sh
2024-01-21 01:00:02 +01:00
Mauricio Bonani
3e6f0acf79 Refactor code 2023-10-03 15:42:33 -04:00
Mauricio Bonani
7f93d943d7 Merge remote-tracking branch 'upstream/master' into fix-build-script 2023-10-03 09:04:40 -04:00
Mauricio Bonani
12543d2c2a Ensure all CSS variables are in the output file 2022-07-06 19:06:11 -04:00
68 changed files with 599 additions and 724 deletions

View File

@@ -12,11 +12,12 @@ permissions:
jobs:
build:
permissions:
checks: write # for coverallsapp/github-action to create new checks
contents: read # for actions/checkout to fetch code
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
node-version: [16, 18, 20]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
@@ -28,3 +29,8 @@ jobs:
- name: Run tests
run: |
npm run test
# - name: Publish to coveralls.io
# if: ${{ matrix.node-version == 16 }}
# uses: coverallsapp/github-action@v1.1.2
# with:
# github-token: ${{ github.token }}

View File

@@ -1,3 +1,42 @@
#### 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

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "4.0.0-dev",
"version": "3.1.9",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",
@@ -41,7 +41,7 @@
"cors": "2.8.5",
"cronosjs": "1.7.1",
"denque": "2.1.0",
"express": "4.18.2",
"express": "4.19.2",
"express-session": "1.17.3",
"form-data": "4.0.0",
"fs-extra": "11.1.1",
@@ -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,7 +74,7 @@
"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",
@@ -112,7 +112,7 @@
"mermaid": "^10.4.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",
@@ -122,6 +122,6 @@
"supertest": "6.3.3"
},
"engines": {
"node": ">=18"
"node": ">=14"
}
}

View File

@@ -33,9 +33,6 @@ module.exports = {
store: req.query['store'],
req: apiUtils.getRequestLogObject(req)
}
if (req.query['keysOnly'] !== undefined) {
opts.keysOnly = true
}
runtimeAPI.context.getValue(opts).then(function(result) {
res.json(result);
}).catch(function(err) {

View File

@@ -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");

View File

@@ -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

View File

@@ -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");

View File

@@ -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");

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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;

View File

@@ -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");

View File

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

View File

@@ -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",

View File

@@ -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",

View File

@@ -303,7 +303,8 @@
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
},
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。"
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。",
"alreadyExists": "本ノードは既に存在"
},
"copyMessagePath": "パスをコピーしました",
"copyMessageValue": "値をコピーしました",
@@ -718,6 +719,7 @@
"nodeHelp": "ノードヘルプ",
"showHelp": "ヘルプを表示",
"showInOutline": "アウトラインに表示",
"hideTopics": "トピックを非表示",
"showTopics": "トピックを表示",
"noHelp": "ヘルプのトピックが未選択",
"changeLog": "更新履歴"

View File

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

View File

@@ -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",

View File

@@ -54,26 +54,25 @@
return icon;
}
function getMatch(value, searchValue) {
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
const len = idx > -1 ? searchValue.length : 0;
return {
index: idx,
found: idx > -1,
pre: value.substring(0,idx),
match: value.substring(idx,idx+len),
post: value.substring(idx+len),
var autoComplete = function(options) {
function getMatch(value, searchValue) {
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
const len = idx > -1 ? searchValue.length : 0;
return {
index: idx,
found: idx > -1,
pre: value.substring(0,idx),
match: value.substring(idx,idx+len),
post: value.substring(idx+len),
}
}
function generateSpans(match) {
const els = [];
if(match.pre) { els.push($('<span/>').text(match.pre)); }
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
if(match.post) { els.push($('<span/>').text(match.post)); }
return els;
}
}
function generateSpans(match) {
const els = [];
if(match.pre) { els.push($('<span/>').text(match.pre)); }
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
if(match.post) { els.push($('<span/>').text(match.post)); }
return els;
}
const msgAutoComplete = function(options) {
return function(val) {
var matches = [];
options.forEach(opt => {
@@ -103,197 +102,6 @@
}
}
function getEnvVars (obj, envVars = {}) {
contextKnownKeys.env = contextKnownKeys.env || {}
if (contextKnownKeys.env[obj.id]) {
return contextKnownKeys.env[obj.id]
}
let parent
if (obj.type === 'tab' || obj.type === 'subflow') {
RED.nodes.eachConfig(function (conf) {
if (conf.type === "global-config") {
parent = conf;
}
})
} else if (obj.g) {
parent = RED.nodes.group(obj.g)
} else if (obj.z) {
parent = RED.nodes.workspace(obj.z) || RED.nodes.subflow(obj.z)
}
if (parent) {
getEnvVars(parent, envVars)
}
if (obj.env) {
obj.env.forEach(env => {
envVars[env.name] = obj
})
}
contextKnownKeys.env[obj.id] = envVars
return envVars
}
const envAutoComplete = function (val) {
const editStack = RED.editor.getEditStack()
if (editStack.length === 0) {
done([])
return
}
const editingNode = editStack.pop()
if (!editingNode) {
return []
}
const envVarsMap = getEnvVars(editingNode)
const envVars = Object.keys(envVarsMap)
const matches = []
const i = val.lastIndexOf('${')
let searchKey = val
let isSubkey = false
if (i > -1) {
if (val.lastIndexOf('}') < i) {
searchKey = val.substring(i+2)
isSubkey = true
}
}
envVars.forEach(v => {
let valMatch = getMatch(v, searchKey);
if (valMatch.found) {
const optSrc = envVarsMap[v]
const element = $('<div>',{style: "display: flex"});
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
valEl.append(generateSpans(valMatch))
valEl.appendTo(element)
if (optSrc) {
const optEl = $('<div>').css({ "font-size": "0.8em" });
let label
if (optSrc.type === 'global-config') {
label = RED._('sidebar.context.global')
} else if (optSrc.type === 'group') {
label = RED.utils.getNodeLabel(optSrc) || (RED._('sidebar.info.group') + ': '+optSrc.id)
} else {
label = RED.utils.getNodeLabel(optSrc) || optSrc.id
}
optEl.append(generateSpans({ match: label }));
optEl.appendTo(element);
}
matches.push({
value: isSubkey ? val + v + '}' : v,
label: element,
i: valMatch.index
});
}
})
matches.sort(function(A,B){return A.i-B.i})
return matches
}
let contextKnownKeys = {}
let contextCache = {}
if (RED.events) {
RED.events.on("editor:close", function () {
contextCache = {}
contextKnownKeys = {}
});
}
const contextAutoComplete = function() {
const that = this
const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
contextKnownKeys[scope] = contextKnownKeys[scope] || {}
contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Set()
if (searchKey.length > 0) {
try {
RED.utils.normalisePropertyExpression(searchKey)
} catch (err) {
// Not a valid context key, so don't try looking up
done()
return
}
}
const url = `context/${scope}/${encodeURIComponent(searchKey)}?store=${store}&keysOnly`
if (contextCache[url]) {
// console.log('CACHED', url)
done()
} else {
// console.log('GET', url)
$.getJSON(url, function(data) {
// console.log(data)
contextCache[url] = true
const result = data[store] || {}
const keys = result.keys || []
const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
keys.forEach(key => {
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) {
contextKnownKeys[scope][store].add(keyPrefix + key)
} else {
contextKnownKeys[scope][store].add(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]")
}
})
done()
})
}
}
const getContextKeys = function(key, done) {
const keyParts = key.split('.')
const partialKey = keyParts.pop()
let scope = that.propertyType
if (scope === 'flow') {
// Get the flow id of the node we're editing
const editStack = RED.editor.getEditStack()
if (editStack.length === 0) {
done([])
return
}
const editingNode = editStack.pop()
if (editingNode.z) {
scope = `${scope}/${editingNode.z}`
} else {
done([])
return
}
}
const store = (contextStoreOptions.length === 1) ? contextStoreOptions[0].value : that.optionValue
const searchKey = keyParts.join('.')
getContextKeysFromRuntime(scope, store, searchKey, function() {
if (contextKnownKeys[scope][store].has(key) || key.endsWith(']')) {
getContextKeysFromRuntime(scope, store, key, function() {
done(contextKnownKeys[scope][store])
})
}
done(contextKnownKeys[scope][store])
})
}
return function(val, done) {
getContextKeys(val, function (keys) {
const matches = []
keys.forEach(v => {
let optVal = v
let valMatch = getMatch(optVal, val);
if (!valMatch.found && val.length > 0 && val.endsWith('.')) {
// Search key ends in '.' - but doesn't match. Check again
// with [" at the end instead so we match bracket notation
valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
}
if (valMatch.found) {
const element = $('<div>',{style: "display: flex"});
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
valEl.append(generateSpans(valMatch))
valEl.appendTo(element)
matches.push({
value: optVal,
label: element,
});
}
})
matches.sort(function(a, b) { return a.value.localeCompare(b.value) });
done(matches);
})
}
}
// This is a hand-generated list of completions for the core nodes (based on the node help html).
var msgCompletions = [
{ value: "payload" },
@@ -358,22 +166,20 @@
{ value: "_session", source: ["websocket out","tcp out"] },
]
var allOptions = {
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions)},
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
flow: {value:"flow",label:"flow.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport,
valueLabel: contextLabel,
autoComplete: contextAutoComplete
valueLabel: contextLabel
},
global: {value:"global",label:"global.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport,
valueLabel: contextLabel,
autoComplete: contextAutoComplete
valueLabel: contextLabel
},
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
@@ -445,8 +251,7 @@
env: {
value: "env",
label: "env variable",
icon: "red/images/typedInput/env.svg",
autoComplete: envAutoComplete
icon: "red/images/typedInput/env.svg"
},
node: {
value: "node",
@@ -622,7 +427,6 @@
}
var nlsd = false;
let contextStoreOptions;
$.widget( "nodered.typedInput", {
_create: function() {
@@ -634,7 +438,7 @@
}
}
var contextStores = RED.settings.context.stores;
contextStoreOptions = contextStores.map(function(store) {
var contextOptions = contextStores.map(function(store) {
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
}).sort(function(A,B) {
if (A.value === RED.settings.context.default) {
@@ -645,12 +449,12 @@
return A.value.localeCompare(B.value);
}
})
if (contextStoreOptions.length < 2) {
if (contextOptions.length < 2) {
allOptions.flow.options = [];
allOptions.global.options = [];
} else {
allOptions.flow.options = contextStoreOptions;
allOptions.global.options = contextStoreOptions;
allOptions.flow.options = contextOptions;
allOptions.global.options = contextOptions;
}
}
nlsd = true;
@@ -740,7 +544,7 @@
that.element.trigger('paste',evt);
});
this.input.on('keydown', function(evt) {
if (that.typeMap[that.propertyType].autoComplete || that.input.hasClass('red-ui-autoComplete')) {
if (that.typeMap[that.propertyType].autoComplete) {
return
}
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
@@ -930,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);
});
}
@@ -1026,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 }));
}
}
},
@@ -1163,9 +967,6 @@
// If previousType is !null, then this is a change of the type, rather than the initialisation
var previousType = this.typeMap[this.propertyType];
previousValue = this.input.val();
if (this.input.hasClass('red-ui-autoComplete')) {
this.input.autoComplete("destroy");
}
if (previousType && this.typeChanged) {
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
@@ -1212,9 +1013,7 @@
this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
}
if (previousType.autoComplete) {
if (this.input.hasClass('red-ui-autoComplete')) {
this.input.autoComplete("destroy");
}
this.input.autoComplete("destroy");
}
}
this.propertyType = type;
@@ -1342,16 +1141,6 @@
} else {
this.optionSelectTrigger.hide();
}
if (opt.autoComplete) {
let searchFunction = opt.autoComplete
if (searchFunction.length === 0) {
searchFunction = opt.autoComplete.call(this)
}
this.input.autoComplete({
search: searchFunction,
minLength: 0
})
}
}
this.optionMenu = this._createMenu(opt.options,opt,function(v){
if (!opt.multiple) {
@@ -1394,12 +1183,8 @@
this.valueLabelContainer.hide();
this.elementDiv.show();
if (opt.autoComplete) {
let searchFunction = opt.autoComplete
if (searchFunction.length === 0) {
searchFunction = opt.autoComplete.call(this)
}
this.input.autoComplete({
search: searchFunction,
search: opt.autoComplete,
minLength: 0
})
}

View File

@@ -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") }
)
}

View File

@@ -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);

View File

@@ -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;
}
@@ -2087,7 +2116,6 @@ RED.editor = (function() {
}
},
editBuffer: function(options) { showTypeEditor("_buffer", options) },
getEditStack: function () { return [...editStack] },
buildEditForm: buildEditForm,
validateNode: validateNode,
updateNodeProperties: updateNodeProperties,

View File

@@ -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)
}
}

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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 {

View File

@@ -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
}
}
@@ -6252,10 +6266,6 @@ RED.view = (function() {
}
})
}
if (selection.links) {
selectedLinks.clear();
selection.links.forEach(selectedLinks.add);
}
}
}
updateSelection();

View File

@@ -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();
}

View File

@@ -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", {

View File

@@ -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);

View File

@@ -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"},

View File

@@ -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 }
}
})

View File

@@ -5,7 +5,6 @@ module.exports = function(RED) {
const fs = require("fs-extra");
const path = require("path");
var debuglength = RED.settings.debugMaxLength || 1000;
var statuslength = RED.settings.debugStatusLength || 32;
var useColors = RED.settings.debugUseColors || false;
util.inspect.styles.boolean = "red";
const { hasOwnProperty } = Object.prototype;
@@ -165,7 +164,7 @@ module.exports = function(RED) {
}
}
if (st.length > statuslength) { st = st.substr(0,statuslength) + "..."; }
if (st.length > 32) { st = st.substr(0,32) + "..."; }
var newStatus = {fill:fill, shape:shape, text:st};
if (JSON.stringify(newStatus) !== node.oldState) { // only send if we have to

View File

@@ -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

View File

@@ -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);

View File

@@ -233,9 +233,7 @@ module.exports = function(RED) {
// only replace if they match exactly
RED.util.setMessageProperty(msg,property,value);
} else {
// if target is boolean then just replace it
if (rule.tot === "bool") { current = value; }
else { current = current.replace(fromRE,value); }
current = current.replace(fromRE,value);
RED.util.setMessageProperty(msg,property,current);
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {

View File

@@ -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) {

View File

@@ -411,33 +411,23 @@ module.exports = function(RED) {
if (msg._session && msg._session.type == "tcp") {
var client = connectionPool[msg._session.id];
if (client) {
if (msg?.reset === true) {
client.destroy();
}
else {
if (Buffer.isBuffer(msg.payload)) {
client.write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
client.write(Buffer.from(msg.payload,'base64'));
} else {
client.write(Buffer.from(""+msg.payload));
}
if (Buffer.isBuffer(msg.payload)) {
client.write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
client.write(Buffer.from(msg.payload,'base64'));
} else {
client.write(Buffer.from(""+msg.payload));
}
}
}
else {
for (var i in connectionPool) {
if (msg?.reset === true) {
connectionPool[i].destroy();
}
else {
if (Buffer.isBuffer(msg.payload)) {
connectionPool[i].write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
connectionPool[i].write(Buffer.from(msg.payload,'base64'));
} else {
connectionPool[i].write(Buffer.from(""+msg.payload));
}
if (Buffer.isBuffer(msg.payload)) {
connectionPool[i].write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
connectionPool[i].write(Buffer.from(msg.payload,'base64'));
} else {
connectionPool[i].write(Buffer.from(""+msg.payload));
}
}
}
@@ -557,33 +547,13 @@ module.exports = function(RED) {
this.on("input", function(msg, nodeSend, nodeDone) {
var i = 0;
if (msg.payload !== undefined && (!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
msg.payload = msg.payload.toString();
}
var host = node.server || msg.host;
var port = node.port || msg.port;
if (node.out === "sit" && msg?.reset) {
if (msg.reset === true) { // kill all connections
for (var cl in clients) {
if (clients[cl].hasOwnProperty("client")) {
clients[cl].client.destroy();
delete clients[cl];
}
}
}
if (typeof(msg.reset) === "string" && msg.reset.includes(":")) { // just kill connection host:port
if (clients.hasOwnProperty(msg.reset) && clients[msg.reset].hasOwnProperty("client")) {
clients[msg.reset].client.destroy();
delete clients[msg.reset];
}
}
const cc = Object.keys(clients).length;
node.status({fill:"green",shape:cc===0?"ring":"dot",text:RED._("tcpin.status.connections",{count:cc})});
if ((host === undefined || port === undefined) && !msg.hasOwnProperty("payload")) { return; }
}
// Store client information independently
// the clients object will have:
// clients[id].client, clients[id].msg, clients[id].timeout
@@ -651,16 +621,13 @@ module.exports = function(RED) {
clients[connection_id].connecting = true;
clients[connection_id].client.connect(connOpts, function() {
//node.log(RED._("tcpin.errors.client-connected"));
// node.status({fill:"green",shape:"dot",text:"common.status.connected"});
node.status({fill:"green",shape:"dot",text:RED._("tcpin.status.connections",{count:Object.keys(clients).length})});
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].connected = true;
clients[connection_id].connecting = false;
let event;
while (event = dequeue(clients[connection_id].msgQueue)) {
if (event.msg.payload !== undefined) {
clients[connection_id].client.write(event.msg.payload);
}
clients[connection_id].client.write(event.msg.payload);
event.nodeDone();
}
if (node.out === "time" && node.splitc < 0) {
@@ -856,9 +823,7 @@ module.exports = function(RED) {
else if (!clients[connection_id].connecting && clients[connection_id].connected) {
if (clients[connection_id] && clients[connection_id].client) {
let event = dequeue(clients[connection_id].msgQueue)
if (event.msg.payload !== undefined ) {
clients[connection_id].client.write(event.msg.payload);
}
clients[connection_id].client.write(event.msg.payload);
event.nodeDone();
}
}

View File

@@ -30,8 +30,6 @@
before being sent.</p>
<p>If <code>msg._session</code> is not present the payload is
sent to <b>all</b> connected clients.</p>
<p>In Reply-to mode, setting <code>msg.reset = true</code> will reset the connection
specified by _session.id, or all connections if no _session.id is specified.</p>
<p><b>Note: </b>On some systems you may need root or administrator access
to access ports below 1024.</p>
</script>
@@ -42,8 +40,6 @@
returned characters into a fixed buffer, match a specified character before returning,
wait a fixed timeout from first reply and then return, sit and wait for data, or send then close the connection
immediately, without waiting for a reply.</p>
<p>If in sit and wait mode (remain connected) you can send <code>msg.reset = true</code> or <code>msg.reset = "host:port"</code> to force a break in
the connection and an automatic reconnection.</p>
<p>The response will be output in <code>msg.payload</code> as a buffer, so you may want to .toString() it.</p>
<p>If you leave tcp host or port blank they must be set by using the <code>msg.host</code> and <code>msg.port</code> properties in every message sent to the node.</p>
</script>

View File

@@ -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

View File

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

View File

@@ -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: {

View File

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

View File

@@ -264,7 +264,7 @@ async function installModule(moduleDetails) {
"module": moduleDetails.module,
"version": moduleDetails.version,
"dir": installDir,
"args": ["--omit=dev","--engine-strict"]
"args": ["--production","--engine-strict"]
}
return hooks.trigger("preInstall", triggerPayload).then((result) => {
// preInstall passed
@@ -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");
}

View File

@@ -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$/;
@@ -215,7 +218,7 @@ async function installModule(module,version,url) {
"dir": installDir,
"isExisting": isExisting,
"isUpgrade": isUpgrade,
"args": ['--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--omit=dev','--engine-strict']
"args": ['--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production','--engine-strict']
}
return hooks.trigger("preInstall", triggerPayload).then((result) => {
@@ -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;

View File

@@ -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$/, '')))
}
})
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
"version": "4.0.0-dev",
"version": "3.1.9",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,11 +16,11 @@
}
],
"dependencies": {
"@node-red/util": "4.0.0-dev",
"@node-red/util": "3.1.9",
"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"
}
}

View File

@@ -68,7 +68,6 @@ var api = module.exports = {
* @param {String} opts.store - the context store
* @param {String} opts.key - the context key
* @param {Object} opts.req - the request to log (optional)
* @param {Boolean} opts.keysOnly - whether to return keys only
* @return {Promise} - the node information
* @memberof @node-red/runtime_context
*/
@@ -103,15 +102,6 @@ var api = module.exports = {
if (key) {
store = store || availableStores.default;
ctx.get(key,store,function(err, v) {
if (opts.keysOnly) {
if (Array.isArray(v)) {
resolve({ [store]: { format: `array[${v.length}]`}})
} else if (typeof v === 'object') {
resolve({ [store]: { keys: Object.keys(v), format: 'Object' } })
} else {
resolve({ [store]: { keys: [] }})
}
}
var encoded = util.encodeObject({msg:v});
if (store !== availableStores.default) {
encoded.store = store;
@@ -128,58 +118,32 @@ var api = module.exports = {
stores = [store];
}
var result = {};
var c = stores.length;
var errorReported = false;
stores.forEach(function(store) {
if (opts.keysOnly) {
ctx.keys(store,function(err, keys) {
if (err) {
// TODO: proper error reporting
if (!errorReported) {
errorReported = true;
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}, opts.req);
var err = new Error();
err.code = "unexpected_error";
err.status = 400;
return reject(err);
}
return
exportContextStore(scope,ctx,store,result,function(err) {
if (err) {
// TODO: proper error reporting
if (!errorReported) {
errorReported = true;
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}, opts.req);
var err = new Error();
err.code = "unexpected_error";
err.status = 400;
return reject(err);
}
result[store] = { keys }
c--;
if (c === 0) {
if (!errorReported) {
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
resolve(result);
}
}
})
} else {
exportContextStore(scope,ctx,store,result,function(err) {
if (err) {
// TODO: proper error reporting
if (!errorReported) {
errorReported = true;
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}, opts.req);
var err = new Error();
err.code = "unexpected_error";
err.status = 400;
return reject(err);
}
return;
return;
}
c--;
if (c === 0) {
if (!errorReported) {
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
resolve(result);
}
c--;
if (c === 0) {
if (!errorReported) {
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
resolve(result);
}
}
});
}
}
});
})
}
} else {

View File

@@ -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;
});

View File

@@ -41,7 +41,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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -154,7 +154,7 @@ function start() {
log.info(log._("runtime.version",{component:"Node.js ",version:process.version}));
if (settings.UNSUPPORTED_VERSION) {
log.error("*****************************************************************");
log.error("* "+log._("runtime.unsupported_version",{component:"Node.js",version:process.version,requires: ">=18"})+" *");
log.error("* "+log._("runtime.unsupported_version",{component:"Node.js",version:process.version,requires: ">=8.9.0"})+" *");
log.error("*****************************************************************");
events.emit("runtime-event",{id:"runtime-unsupported-version",payload:{type:"error",text:"notification.errors.unsupportedVersion"},retain:true});
}

View File

@@ -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");
})
});
},

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/util",
"version": "4.0.0-dev",
"version": "3.1.9",
"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"

View File

@@ -33,7 +33,8 @@ if (NODE_MAJOR_VERSION >= 16) {
function checkVersion(userSettings) {
var semver = require('semver');
if (!semver.satisfies(process.version,">=18.0.0")) {
if (!semver.satisfies(process.version,">=14.0.0")) {
// TODO: in the future, make this a hard error.
// var e = new Error("Unsupported version of Node.js");
// e.code = "unsupported_version";
// throw e;

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "4.0.0-dev",
"version": "3.1.9",
"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": "4.0.0-dev",
"@node-red/runtime": "4.0.0-dev",
"@node-red/util": "4.0.0-dev",
"@node-red/nodes": "4.0.0-dev",
"@node-red/editor-api": "3.1.9",
"@node-red/runtime": "3.1.9",
"@node-red/util": "3.1.9",
"@node-red/nodes": "3.1.9",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"express": "4.18.2",
"express": "4.19.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"
},
@@ -47,6 +47,6 @@
"bcrypt": "5.1.0"
},
"engines": {
"node": ">=18"
"node": ">=14"
}
}

View File

@@ -26,13 +26,6 @@ if (process.argv[2] === 'admin') {
return;
}
var semver = require('semver');
if (!semver.satisfies(process.version, ">=18.0.0")) {
console.log("Unsupported version of Node.js:", process.version);
console.log("Node-RED requires Node.js v18 or later");
process.exit(1)
}
var http = require('http');
var https = require('https');
var util = require("util");
@@ -353,7 +346,7 @@ httpsPromise.then(function(startupHttps) {
} catch(err) {
if (err.code == "unsupported_version") {
console.log("Unsupported version of Node.js:",process.version);
console.log("Node-RED requires Node.js v18 or later");
console.log("Node-RED requires Node.js v8.9.0 or later");
} else {
console.log("Failed to start server:");
if (err.stack) {

View File

@@ -449,7 +449,6 @@ module.exports = {
* - ui (for use with Node-RED Dashboard)
* - debugUseColors
* - debugMaxLength
* - debugStatusLength
* - execMaxBufferSize
* - httpRequestTimeout
* - mqttReconnectTime
@@ -505,9 +504,6 @@ module.exports = {
/** The maximum length, in characters, of any message sent to the debug sidebar tab */
debugMaxLength: 1000,
/** The maximum length, in characters, of status messages under the debug node */
//debugStatusLength: 32,
/** Maximum buffer size for the exec node. Defaults to 10Mb */
//execMaxBufferSize: 10000000,

View File

@@ -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);
}

View File

@@ -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) {
@@ -1718,13 +1720,9 @@ describe('function node', function() {
describe("init function", function() {
it('should delay handling messages until init completes', function(done) {
const timeoutMS = 200;
// Since helper.load uses process.nextTick timers might occasionally finish
// a couple of milliseconds too early, so give some leeway to the check.
const timeoutCheckMargin = 5;
var flow = [{id:"n1",type:"function",wires:[["n2"]],initialize: `
return new Promise((resolve,reject) => {
setTimeout(resolve, ${timeoutMS});
setTimeout(resolve,200)
})`,
func:"return msg;"
},
@@ -1737,10 +1735,9 @@ describe('function node', function() {
msg.delta = Date.now() - msg.payload;
receivedMsgs.push(msg)
if (receivedMsgs.length === 5) {
let deltas = receivedMsgs.map(msg => msg.delta);
var errors = deltas.filter(delta => delta < (timeoutMS - timeoutCheckMargin))
var errors = receivedMsgs.filter(msg => msg.delta < 200)
if (errors.length > 0) {
done(new Error(`Message received before init completed - delta values ${JSON.stringify(deltas)} expected to be > ${timeoutMS - timeoutCheckMargin}`))
done(new Error(`Message received before init completed - was ${msg.delta} expected >300`))
} else {
done();
}

View File

@@ -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)
});
});
});

View File

@@ -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);
}
});
})
});
});