Merge pull request #5504 from node-red/sync-to-dev

Resync 4.1.6 to dev branch
pull/5389/merge
Nick O'Leary 2026-02-26 10:31:36 +00:00 committed by GitHub
commit afaede32dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 737 additions and 595 deletions

View File

@ -33,6 +33,17 @@ Nodes
- Add ability to use pfx or p12 file for TLS connection settings option (#4907) @dceejay
#### 4.1.6: Maintenance Release
- Allow palette.theme to be set via theme plugin and include icons (#5500) @knolleary
- Ensure config sidebar tooltip handles html content (#5501) @knolleary
- Allow node-red integrator access to available updates (#5499) @Steve-Mcl
- Add frontend pre and post debug message hooks (#5495) @Steve-Mcl
- Fix: allow middle-click panning over links and ports (#5496) @lklivingstone
- Support ctrl key to select configuration nodes (#5486) @kazuhitoyokoi
- Add § as shortcut meta-key (#5482) @gorenje
- Update dependencies (#5502) @knolleary
#### 4.1.5: Maintenance Release
- chore: bump tar to 7.5.7 (#5472) @bryopsida

576
package-lock.json generated
View File

@ -9,12 +9,13 @@
"version": "5.0.0-beta.2",
"license": "Apache-2.0",
"dependencies": {
"acorn": "8.15.0",
"acorn-walk": "8.3.4",
"ajv": "8.17.1",
"@node-rs/bcrypt": "^1.10.7",
"acorn": "8.16.0",
"acorn-walk": "8.3.5",
"ajv": "8.18.0",
"async-mutex": "0.5.0",
"basic-auth": "2.0.1",
"bcryptjs": "3.0.2",
"bcryptjs": "3.0.3",
"body-parser": "1.20.4",
"chalk": "^4.1.2",
"cheerio": "1.0.0-rc.10",
@ -45,7 +46,7 @@
"mime": "3.0.0",
"moment": "2.30.1",
"moment-timezone": "0.5.48",
"mqtt": "5.11.0",
"mqtt": "5.15.0",
"multer": "2.0.2",
"mustache": "4.2.0",
"node-red-admin": "^4.1.3",
@ -57,9 +58,9 @@
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "3.0.0",
"rfdc": "^1.3.1",
"semver": "7.7.1",
"tar": "7.5.7",
"rfdc": "1.4.1",
"semver": "7.7.4",
"tar": "7.5.9",
"tough-cookie": "5.1.2",
"uglify-js": "3.19.3",
"uuid": "9.0.1",
@ -91,11 +92,11 @@
"jquery-i18next": "1.2.1",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "4.3.0",
"mermaid": "11.9.0",
"mermaid": "11.12.3",
"minami": "1.2.3",
"mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.3",
"nodemon": "3.1.9",
"nodemon": "3.1.14",
"proxy": "^1.0.2",
"sass": "1.62.1",
"should": "13.2.3",
@ -107,7 +108,7 @@
"node": ">=18.5"
},
"optionalDependencies": {
"@node-rs/bcrypt": "1.10.7"
"@node-rs/bcrypt": "^1.10.7"
}
},
"node_modules/@antfu/install-pkg": {
@ -124,16 +125,6 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@antfu/utils": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz",
"integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@ -213,9 +204,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
"integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@ -302,60 +293,46 @@
"license": "MIT"
},
"node_modules/@chevrotain/cst-dts-gen": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
"integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz",
"integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/gast": "11.0.3",
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/gast": "11.1.1",
"@chevrotain/types": "11.1.1",
"lodash-es": "4.17.23"
}
},
"node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"dev": true,
"license": "MIT"
},
"node_modules/@chevrotain/gast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
"integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz",
"integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/types": "11.1.1",
"lodash-es": "4.17.23"
}
},
"node_modules/@chevrotain/gast/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"dev": true,
"license": "MIT"
},
"node_modules/@chevrotain/regexp-to-ast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
"integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz",
"integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@chevrotain/types": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
"integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz",
"integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@chevrotain/utils": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz",
"integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==",
"dev": true,
"license": "Apache-2.0"
},
@ -398,47 +375,17 @@
"license": "MIT"
},
"node_modules/@iconify/utils": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz",
"integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz",
"integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@antfu/install-pkg": "^1.0.0",
"@antfu/utils": "^8.1.0",
"@antfu/install-pkg": "^1.1.0",
"@iconify/types": "^2.0.0",
"debug": "^4.4.0",
"globals": "^15.14.0",
"kolorist": "^1.8.0",
"local-pkg": "^1.0.0",
"mlly": "^1.7.4"
"mlly": "^1.8.0"
}
},
"node_modules/@iconify/utils/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@iconify/utils/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@ -504,13 +451,13 @@
}
},
"node_modules/@mermaid-js/parser": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz",
"integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz",
"integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"langium": "3.3.1"
"langium": "^4.0.0"
}
},
"node_modules/@napi-rs/wasm-runtime": {
@ -860,6 +807,23 @@
"node": ">= 14"
}
},
"node_modules/@prantlf/jsonlint/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@sindresorhus/is": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
@ -1329,9 +1293,9 @@
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@ -1341,9 +1305,9 @@
}
},
"node_modules/acorn-walk": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
@ -1398,9 +1362,9 @@
"license": "MIT"
},
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@ -1839,9 +1803,9 @@
"dev": true
},
"node_modules/bcryptjs": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz",
"integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
"integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
"license": "BSD-3-Clause",
"bin": {
"bcrypt": "bin/bcrypt"
@ -2016,6 +1980,18 @@
"node": ">=8"
}
},
"node_modules/broker-factory": {
"version": "3.1.13",
"resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.13.tgz",
"integrity": "sha512-H2VALe31mEtO/SRcNp4cUU5BAm1biwhc/JaF77AigUuni/1YT0FLCJfbUxwIEs9y6Kssjk2fmXgf+Y9ALvmKlw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.6",
"fast-unique-numbers": "^9.0.26",
"tslib": "^2.8.1",
"worker-factory": "^7.0.48"
}
},
"node_modules/browser-stdout": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
@ -2267,18 +2243,18 @@
}
},
"node_modules/chevrotain": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz",
"integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
"@chevrotain/regexp-to-ast": "11.0.3",
"@chevrotain/types": "11.0.3",
"@chevrotain/utils": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/cst-dts-gen": "11.1.1",
"@chevrotain/gast": "11.1.1",
"@chevrotain/regexp-to-ast": "11.1.1",
"@chevrotain/types": "11.1.1",
"@chevrotain/utils": "11.1.1",
"lodash-es": "4.17.23"
}
},
"node_modules/chevrotain-allstar": {
@ -2294,13 +2270,6 @@
"chevrotain": "^11.0.0"
}
},
"node_modules/chevrotain/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"dev": true,
"license": "MIT"
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@ -2624,9 +2593,9 @@
}
},
"node_modules/confbox": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
"dev": true,
"license": "MIT"
},
@ -3184,9 +3153,9 @@
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
"integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
"dev": true,
"license": "ISC",
"engines": {
@ -3442,9 +3411,9 @@
}
},
"node_modules/dagre-d3-es": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz",
"integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==",
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz",
"integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -4125,13 +4094,6 @@
],
"license": "MIT"
},
"node_modules/exsolve": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
"dev": true,
"license": "MIT"
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -4170,16 +4132,16 @@
"license": "MIT"
},
"node_modules/fast-unique-numbers": {
"version": "8.0.13",
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-8.0.13.tgz",
"integrity": "sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==",
"version": "9.0.26",
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.26.tgz",
"integrity": "sha512-3Mtq8p1zQinjGyWfKeuBunbuFoixG72AUkk4VvzbX4ykCW9Q4FzRaNyIlfQhUjnKw2ARVP+/CKnoyr6wfHftig==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.8",
"tslib": "^2.6.2"
"@babel/runtime": "^7.28.6",
"tslib": "^2.8.1"
},
"engines": {
"node": ">=16.1.0"
"node": ">=18.2.0"
}
},
"node_modules/fast-uri": {
@ -4777,19 +4739,6 @@
"which": "bin/which"
}
},
"node_modules/globals": {
"version": "15.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/globule": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz",
@ -6399,9 +6348,9 @@
}
},
"node_modules/jsdoc-api/node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -6772,28 +6721,22 @@
"graceful-fs": "^4.1.9"
}
},
"node_modules/kolorist": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
"dev": true,
"license": "MIT"
},
"node_modules/langium": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz",
"integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz",
"integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"chevrotain": "~11.0.3",
"chevrotain-allstar": "~0.3.0",
"chevrotain": "~11.1.1",
"chevrotain-allstar": "~0.3.1",
"vscode-languageserver": "~9.0.1",
"vscode-languageserver-textdocument": "~1.0.11",
"vscode-uri": "~3.0.8"
"vscode-uri": "~3.1.0"
},
"engines": {
"node": ">=16.0.0"
"node": ">=20.10.0",
"npm": ">=10.2.3"
}
},
"node_modules/layout-base": {
@ -6952,24 +6895,6 @@
"node": ">=4"
}
},
"node_modules/local-pkg": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
"integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
"dev": true,
"license": "MIT",
"dependencies": {
"mlly": "^1.7.4",
"pkg-types": "^2.3.0",
"quansync": "^0.2.11"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -6987,16 +6912,16 @@
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.22",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz",
"integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==",
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
"dev": true,
"license": "MIT"
},
@ -7327,28 +7252,28 @@
}
},
"node_modules/mermaid": {
"version": "11.9.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.9.0.tgz",
"integrity": "sha512-YdPXn9slEwO0omQfQIsW6vS84weVQftIyyTGAZCwM//MGhPzL1+l6vO6bkf0wnP4tHigH1alZ5Ooy3HXI2gOag==",
"version": "11.12.3",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz",
"integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@braintree/sanitize-url": "^7.0.4",
"@iconify/utils": "^2.1.33",
"@mermaid-js/parser": "^0.6.2",
"@braintree/sanitize-url": "^7.1.1",
"@iconify/utils": "^3.0.1",
"@mermaid-js/parser": "^1.0.0",
"@types/d3": "^7.4.3",
"cytoscape": "^3.29.3",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0",
"d3": "^7.9.0",
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.11",
"dayjs": "^1.11.13",
"dagre-d3-es": "7.0.13",
"dayjs": "^1.11.18",
"dompurify": "^3.2.5",
"katex": "^0.16.22",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
"marked": "^16.0.0",
"lodash-es": "^4.17.23",
"marked": "^16.2.1",
"roughjs": "^4.6.6",
"stylis": "^4.3.6",
"ts-dedent": "^2.2.0",
@ -7532,25 +7457,6 @@
"ufo": "^1.6.1"
}
},
"node_modules/mlly/node_modules/confbox": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
"dev": true,
"license": "MIT"
},
"node_modules/mlly/node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.1.8",
"mlly": "^1.7.4",
"pathe": "^2.0.1"
}
},
"node_modules/mocha": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz",
@ -7666,9 +7572,9 @@
}
},
"node_modules/mocha/node_modules/glob/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@ -7749,28 +7655,27 @@
}
},
"node_modules/mqtt": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.11.0.tgz",
"integrity": "sha512-VDqfADTNvohwcY02NgxPb7OojIeDrNQ1q62r/DcM+bnIWY8LBi3nMTvdEaFEp6Bu4ejBIpHjJVthUEgnvGLemA==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.15.0.tgz",
"integrity": "sha512-KC+wAssYk83Qu5bT8YDzDYgUJxPhbLeVsDvpY2QvL28PnXYJzC2WkKruyMUgBAZaQ7h9lo9k2g4neRNUUxzgMw==",
"license": "MIT",
"dependencies": {
"@types/readable-stream": "^4.0.18",
"@types/ws": "^8.5.14",
"@types/readable-stream": "^4.0.21",
"@types/ws": "^8.18.1",
"commist": "^3.2.0",
"concat-stream": "^2.0.0",
"debug": "^4.4.0",
"debug": "^4.4.1",
"help-me": "^5.0.0",
"lru-cache": "^10.4.3",
"minimist": "^1.2.8",
"mqtt-packet": "^9.0.2",
"number-allocator": "^1.0.14",
"readable-stream": "^4.7.0",
"reinterval": "^1.1.0",
"rfdc": "^1.4.1",
"socks": "^2.8.3",
"socks": "^2.8.6",
"split2": "^4.2.0",
"worker-timers": "^7.1.8",
"ws": "^8.18.0"
"worker-timers": "^8.0.23",
"ws": "^8.18.3"
},
"bin": {
"mqtt": "build/bin/mqtt.js",
@ -8077,6 +7982,15 @@
"node": ">=6"
}
},
"node_modules/node-red-admin/node_modules/bcryptjs": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz",
"integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==",
"license": "BSD-3-Clause",
"bin": {
"bcrypt": "bin/bcrypt"
}
},
"node_modules/node-red-node-test-helper": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/node-red-node-test-helper/-/node-red-node-test-helper-0.3.5.tgz",
@ -8226,16 +8140,16 @@
}
},
"node_modules/nodemon": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
"integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==",
"version": "3.1.14",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
"integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^4",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.1.2",
"minimatch": "^10.2.1",
"pstree.remy": "^1.1.8",
"semver": "^7.5.3",
"simple-update-notifier": "^2.0.0",
@ -8254,6 +8168,29 @@
"url": "https://opencollective.com/nodemon"
}
},
"node_modules/nodemon/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/nodemon/node_modules/brace-expansion": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/nodemon/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@ -8283,16 +8220,19 @@
}
},
"node_modules/nodemon/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "10.2.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz",
"integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==",
"dev": true,
"license": "ISC",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^1.1.7"
"brace-expansion": "^5.0.2"
},
"engines": {
"node": "*"
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/nodemon/node_modules/ms": {
@ -9348,15 +9288,15 @@
}
},
"node_modules/pkg-types": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.2.2",
"exsolve": "^1.0.7",
"pathe": "^2.0.3"
"confbox": "^0.1.8",
"mlly": "^1.7.4",
"pathe": "^2.0.1"
}
},
"node_modules/points-on-curve": {
@ -9511,9 +9451,9 @@
}
},
"node_modules/qs": {
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
@ -9525,23 +9465,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/antfu"
},
{
"type": "individual",
"url": "https://github.com/sponsors/sxzz"
}
],
"license": "MIT"
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -9785,9 +9708,9 @@
}
},
"node_modules/readdir-glob/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true,
"license": "ISC",
"dependencies": {
@ -9883,12 +9806,6 @@
"node": ">=0.10.0"
}
},
"node_modules/reinterval": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
"integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==",
"license": "MIT"
},
"node_modules/release-zalgo": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
@ -10129,9 +10046,9 @@
"license": "BlueOak-1.0.0"
},
"node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@ -11012,9 +10929,9 @@
"dev": true
},
"node_modules/tar": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz",
"integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
@ -11357,9 +11274,9 @@
"license": "MIT"
},
"node_modules/ufo": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.2.tgz",
"integrity": "sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q==",
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
"dev": true,
"license": "MIT"
},
@ -11572,9 +11489,9 @@
"license": "MIT"
},
"node_modules/vscode-uri": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
"dev": true,
"license": "MIT"
},
@ -11685,38 +11602,51 @@
"node": ">=0.10.0"
}
},
"node_modules/worker-timers": {
"version": "7.1.8",
"resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.1.8.tgz",
"integrity": "sha512-R54psRKYVLuzff7c1OTFcq/4Hue5Vlz4bFtNEIarpSiCYhpifHU3aIQI29S84o1j87ePCYqbmEJPqwBTf+3sfw==",
"node_modules/worker-factory": {
"version": "7.0.48",
"resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.48.tgz",
"integrity": "sha512-CGmBy3tJvpBPjUvb0t4PrpKubUsfkI1Ohg0/GGFU2RvA9j/tiVYwKU8O7yu7gH06YtzbeJLzdUR29lmZKn5pag==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.5",
"tslib": "^2.6.2",
"worker-timers-broker": "^6.1.8",
"worker-timers-worker": "^7.0.71"
"@babel/runtime": "^7.28.6",
"fast-unique-numbers": "^9.0.26",
"tslib": "^2.8.1"
}
},
"node_modules/worker-timers": {
"version": "8.0.30",
"resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.30.tgz",
"integrity": "sha512-8P7YoMHWN0Tz7mg+9oEhuZdjBIn2z6gfjlJqFcHiDd9no/oLnMGCARCDkV1LR3ccQus62ZdtIp7t3aTKrMLHOg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.6",
"tslib": "^2.8.1",
"worker-timers-broker": "^8.0.15",
"worker-timers-worker": "^9.0.13"
}
},
"node_modules/worker-timers-broker": {
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-6.1.8.tgz",
"integrity": "sha512-FUCJu9jlK3A8WqLTKXM9E6kAmI/dR1vAJ8dHYLMisLNB/n3GuaFIjJ7pn16ZcD1zCOf7P6H62lWIEBi+yz/zQQ==",
"version": "8.0.15",
"resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.15.tgz",
"integrity": "sha512-Te+EiVUMzG5TtHdmaBZvBrZSFNauym6ImDaCAnzQUxvjnw+oGjMT2idmAOgDy30vOZMLejd0bcsc90Axu6XPWA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.5",
"fast-unique-numbers": "^8.0.13",
"tslib": "^2.6.2",
"worker-timers-worker": "^7.0.71"
"@babel/runtime": "^7.28.6",
"broker-factory": "^3.1.13",
"fast-unique-numbers": "^9.0.26",
"tslib": "^2.8.1",
"worker-timers-worker": "^9.0.13"
}
},
"node_modules/worker-timers-worker": {
"version": "7.0.71",
"resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-7.0.71.tgz",
"integrity": "sha512-ks/5YKwZsto1c2vmljroppOKCivB/ma97g9y77MAAz2TBBjPPgpoOiS1qYQKIgvGTr2QYPT3XhJWIB6Rj2MVPQ==",
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.13.tgz",
"integrity": "sha512-qjn18szGb1kjcmh2traAdki1eiIS5ikFo+L90nfMOvSRpuDw1hAcR1nzkP2+Hkdqz5thIRnfuWx7QSpsEUsA6Q==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.5",
"tslib": "^2.6.2"
"@babel/runtime": "^7.28.6",
"tslib": "^2.8.1",
"worker-factory": "^7.0.48"
}
},
"node_modules/workerpool": {
@ -11969,9 +11899,9 @@
}
},
"node_modules/zip-stream/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {

View File

@ -26,12 +26,12 @@
}
],
"dependencies": {
"acorn": "8.15.0",
"acorn-walk": "8.3.4",
"ajv": "8.17.1",
"acorn": "8.16.0",
"acorn-walk": "8.3.5",
"ajv": "8.18.0",
"async-mutex": "0.5.0",
"basic-auth": "2.0.1",
"bcryptjs": "3.0.2",
"bcryptjs": "3.0.3",
"body-parser": "1.20.4",
"chalk": "^4.1.2",
"cheerio": "1.0.0-rc.10",
@ -62,7 +62,7 @@
"mime": "3.0.0",
"moment": "2.30.1",
"moment-timezone": "0.5.48",
"mqtt": "5.11.0",
"mqtt": "5.15.0",
"multer": "2.0.2",
"mustache": "4.2.0",
"node-red-admin": "^4.1.3",
@ -74,9 +74,9 @@
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "3.0.0",
"rfdc": "^1.3.1",
"semver": "7.7.1",
"tar": "7.5.7",
"rfdc": "1.4.1",
"semver": "7.7.4",
"tar": "7.5.9",
"tough-cookie": "5.1.2",
"uglify-js": "3.19.3",
"uuid": "9.0.1",
@ -111,11 +111,11 @@
"jquery-i18next": "1.2.1",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "4.3.0",
"mermaid": "11.9.0",
"mermaid": "11.12.3",
"minami": "1.2.3",
"mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.3",
"nodemon": "3.1.9",
"nodemon": "3.1.14",
"proxy": "^1.0.2",
"sass": "1.62.1",
"should": "13.2.3",

View File

@ -53,10 +53,10 @@ module.exports = {
var opts = {
user: req.user
}
runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) {
runtimeAPI.settings.getRuntimeSettings(opts).then(async function(result) {
if (!settings.disableEditor) {
result.editorTheme = result.editorTheme||{};
var themeSettings = theme.settings();
const themeSettings = await theme.settings();
if (themeSettings) {
// result.editorTheme may already exist with the palette
// disabled. Need to merge that into the receive settings

View File

@ -42,7 +42,13 @@ var defaultContext = {
var settings;
var theme = null;
/**
* themeContext is an object passed to the mustache template to generate the editor index.html.
*/
var themeContext = clone(defaultContext);
/**
* themeSettings is an object passed to the editor client as the "editorTheme" property of the settings object
*/
var themeSettings = null;
var activeTheme = null;
@ -91,6 +97,119 @@ function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
return result
}
/**
* Check if a theme is enabled and load its settings.
* This is done lazily as it has to happen after the plugins have been loaded, but before the editor is served.
*/
async function loadThemePlugin () {
if (activeTheme && !activeThemeInitialised) {
const themePlugin = await runtimeAPI.plugins.getPlugin({
id:activeTheme
});
if (themePlugin) {
if (themePlugin.css) {
const cssFiles = serveFilesFromTheme(
themePlugin.css,
themeApp,
"/css/",
themePlugin.path
);
themeContext.page.css = cssFiles.concat(themeContext.page.css || [])
// Mutating `theme` is not ideal, but currently necessary as debug (packages/node_modules/@node-red/nodes/core/common/21-debug.js)
// accesses RED.settings.editorTheme.page._.css directly to apply theme to the debug pop-out window.
theme.page = theme.page || {_:{}}
theme.page._.css = cssFiles.concat(theme.page._.css || [])
}
if (themePlugin.scripts) {
const scriptFiles = serveFilesFromTheme(
themePlugin.scripts,
themeApp,
"/scripts/",
themePlugin.path
)
themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
theme.page = theme.page || {_:{}}
theme.page._.scripts = scriptFiles.concat(theme.page._.scripts || [])
}
// check and load page settings from theme
if (themePlugin.page) {
if (themePlugin.page.favicon && !theme.page.favicon) {
const result = serveFilesFromTheme(
[themePlugin.page.favicon],
themeApp,
"/",
themePlugin.path
)
if(result && result.length > 0) {
// update themeContext page favicon
themeContext.page.favicon = result[0]
}
}
if (themePlugin.page.tabicon && themePlugin.page.tabicon.icon && !theme.page.tabicon) {
const result = serveFilesFromTheme(
[themePlugin.page.tabicon.icon],
themeApp,
"/page/",
themePlugin.path
)
if(result && result.length > 0) {
// update themeContext page tabicon
themeContext.page.tabicon.icon = result[0]
themeContext.page.tabicon.colour = themeContext.page.tabicon.colour || themeContext.page.tabicon.colour
}
}
// if the plugin has a title AND the users settings.js does NOT
if (themePlugin.page.title && !theme.page.title) {
themeContext.page.title = themePlugin.page.title || themeContext.page.title
}
}
// check and load header settings from theme
if (themePlugin.header) {
if (themePlugin.header.image && !theme.header.image) {
const result = serveFilesFromTheme(
[themePlugin.header.image],
themeApp,
"/header/",
themePlugin.path
)
if(result && result.length > 0) {
// update themeContext header image
themeContext.header.image = result[0]
}
}
// if the plugin has a title AND the users settings.js does NOT have a title
if (themePlugin.header.title && !theme.header.title) {
themeContext.header.title = themePlugin.header.title || themeContext.header.title
}
// if the plugin has a header url AND the users settings.js does NOT
if (themePlugin.header.url && !theme.header.url) {
themeContext.header.url = themePlugin.header.url || themeContext.header.url
}
}
if (Array.isArray(themePlugin.palette?.theme)) {
themeSettings.palette = themeSettings.palette || {};
themeSettings.palette.theme = themePlugin.palette.theme;
// The theme is providing its own palette theme. It *might* include icons that need namespacing
// to the theme plugin module.
themePlugin.palette.theme.forEach(themeRule => {
if (themeRule.icon && themeRule.icon.indexOf("/") === -1) {
themeRule.icon = `${themePlugin.module}/${themeRule.icon}`;
}
})
}
// These settings are not exposed under `editorTheme`, so we don't have a merge strategy for them
// If they're defined in the theme plugin, they replace any settings.js values.
// But, this direct manipulation of `theme` is not ideal and relies on mutating a passed-in object
theme.codeEditor = theme.codeEditor || {}
theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options);
theme.mermaid = Object.assign({}, themePlugin.mermaid, theme.mermaid)
}
activeThemeInitialised = true;
}
}
module.exports = {
init: function(_settings, _runtimeAPI) {
settings = _settings;
@ -232,6 +351,7 @@ module.exports = {
res.json(themeContext);
})
// Copy the settings that need passing to the editor into themeSettings.
if (theme.hasOwnProperty("menu")) {
themeSettings.menu = theme.menu;
}
@ -263,104 +383,11 @@ module.exports = {
return themeApp;
},
context: async function() {
if (activeTheme && !activeThemeInitialised) {
const themePlugin = await runtimeAPI.plugins.getPlugin({
id:activeTheme
});
if (themePlugin) {
if (themePlugin.css) {
const cssFiles = serveFilesFromTheme(
themePlugin.css,
themeApp,
"/css/",
themePlugin.path
);
themeContext.page.css = cssFiles.concat(themeContext.page.css || [])
theme.page = theme.page || {_:{}}
theme.page._.css = cssFiles.concat(theme.page._.css || [])
}
if (themePlugin.scripts) {
const scriptFiles = serveFilesFromTheme(
themePlugin.scripts,
themeApp,
"/scripts/",
themePlugin.path
)
themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
theme.page = theme.page || {_:{}}
theme.page._.scripts = scriptFiles.concat(theme.page._.scripts || [])
}
// check and load page settings from theme
if (themePlugin.page) {
if (themePlugin.page.favicon && !theme.page.favicon) {
const result = serveFilesFromTheme(
[themePlugin.page.favicon],
themeApp,
"/",
themePlugin.path
)
if(result && result.length > 0) {
// update themeContext page favicon
themeContext.page.favicon = result[0]
theme.page = theme.page || {_:{}}
theme.page._.favicon = result[0]
}
}
if (themePlugin.page.tabicon && themePlugin.page.tabicon.icon && !theme.page.tabicon) {
const result = serveFilesFromTheme(
[themePlugin.page.tabicon.icon],
themeApp,
"/page/",
themePlugin.path
)
if(result && result.length > 0) {
// update themeContext page tabicon
themeContext.page.tabicon.icon = result[0]
themeContext.page.tabicon.colour = themeContext.page.tabicon.colour || themeContext.page.tabicon.colour
theme.page = theme.page || {_:{}}
theme.page._.tabicon = theme.page._.tabicon || {}
theme.page._.tabicon.icon = themeContext.page.tabicon.icon
theme.page._.tabicon.colour = themeContext.page.tabicon.colour
}
}
// if the plugin has a title AND the users settings.js does NOT
if (themePlugin.page.title && !theme.page.title) {
themeContext.page.title = themePlugin.page.title || themeContext.page.title
}
}
// check and load header settings from theme
if (themePlugin.header) {
if (themePlugin.header.image && !theme.header.image) {
const result = serveFilesFromTheme(
[themePlugin.header.image],
themeApp,
"/header/",
themePlugin.path
)
if(result && result.length > 0) {
// update themeContext header image
themeContext.header.image = result[0]
}
}
// if the plugin has a title AND the users settings.js does NOT have a title
if (themePlugin.header.title && !theme.header.title) {
themeContext.header.title = themePlugin.header.title || themeContext.header.title
}
// if the plugin has a header url AND the users settings.js does NOT
if (themePlugin.header.url && !theme.header.url) {
themeContext.header.url = themePlugin.header.url || themeContext.header.url
}
}
theme.codeEditor = theme.codeEditor || {}
theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options);
theme.mermaid = Object.assign({}, themePlugin.mermaid, theme.mermaid)
}
activeThemeInitialised = true;
}
await loadThemePlugin();
return themeContext;
},
settings: function() {
settings: async function() {
await loadThemePlugin();
return themeSettings;
},
serveFile: function(baseUrl,file) {

View File

@ -18,7 +18,7 @@
"dependencies": {
"@node-red/util": "5.0.0-beta.2",
"@node-red/editor-client": "5.0.0-beta.2",
"bcryptjs": "3.0.2",
"bcryptjs": "3.0.3",
"body-parser": "1.20.4",
"clone": "2.1.2",
"cors": "2.8.5",

View File

@ -1,135 +1,206 @@
RED.hooks = (function() {
RED.hooks = (function () {
// At the time of writing this PR, VALID_HOOKS were not enforced. There may be a good reason for this
// so the below flag has been added to permit this behaviour. If desired, this can be set to false to
// enforce that only known hooks can be added/triggered.
const knownHooksOnly = false
var VALID_HOOKS = [
const VALID_HOOKS = Object.freeze({
viewRemoveNode: true,
viewAddNode: true,
viewRemovePort: true,
viewAddPort: true,
viewRedrawNode: true,
debugPreProcessMessage: true,
debugPostProcessMessage: true
})
]
/**
* @typedef {keyof typeof VALID_HOOKS} HookId - A string literal type representing a hook identifier (sans label).
*
* @typedef {Object} HookItem - An item in the linked list of hooks for a given HookId
* @property {function} cb - The callback function to be called when the hook is triggered
* @property {HookItem|null} previousHook - The previous hook in the linked list
* @property {HookItem|null} nextHook - The next hook in the linked list
* @property {boolean} removed - Flag indicating if the hook has been removed
*
* @typedef {Record<HookId, HookItem|null>} Hooks - A mapping of HookIds to the head of their linked list of HookItems
*/
var hooks = { }
var labelledHooks = { }
/** @type {Hooks} - A mapping of HookIds to the head of their linked list of HookItems */
let hooks = {}
/** @type {Record<string, Record<HookId, HookItem>>} - A mapping of labels to their hooks */
let labelledHooks = {}
function add(hookId, callback) {
var parts = hookId.split(".");
var id = parts[0], label = parts[1];
const { label, id } = parseLabelledHook(hookId)
// if (VALID_HOOKS.indexOf(id) === -1) {
// throw new Error("Invalid hook '"+id+"'");
// }
if (label && labelledHooks[label] && labelledHooks[label][id]) {
throw new Error("Hook "+hookId+" already registered")
if (knownHooksOnly && !isKnownHook(id)) {
throw new Error("Invalid hook '" + id + "'")
}
if (label && labelledHooks[label] && labelledHooks[label][id]) {
throw new Error("Hook " + hookId + " already registered")
}
if (typeof callback !== "function") {
throw new Error("Invalid hook '" + hookId + "'. Callback must be a function")
}
var hookItem = {cb:callback, previousHook: null, nextHook: null }
var tailItem = hooks[id];
/** @type {HookItem} */
const hookItem = { cb: callback, previousHook: null, nextHook: null }
let tailItem = hooks[id]
if (tailItem === undefined) {
hooks[id] = hookItem;
hooks[id] = hookItem
} else {
while(tailItem.nextHook !== null) {
while (tailItem.nextHook !== null) {
tailItem = tailItem.nextHook
}
tailItem.nextHook = hookItem;
hookItem.previousHook = tailItem;
tailItem.nextHook = hookItem
hookItem.previousHook = tailItem
}
if (label) {
labelledHooks[label] = labelledHooks[label]||{};
labelledHooks[label][id] = hookItem;
labelledHooks[label] = labelledHooks[label] || {}
labelledHooks[label][id] = hookItem
}
}
function remove(hookId) {
var parts = hookId.split(".");
var id = parts[0], label = parts[1];
if ( !label) {
throw new Error("Cannot remove hook without label: "+hookId)
const { label, id } = parseLabelledHook(hookId)
if (!label) {
throw new Error("Cannot remove hook without label: " + hookId)
}
if (labelledHooks[label]) {
if (id === "*") {
// Remove all hooks for this label
var hookList = Object.keys(labelledHooks[label]);
for (var i=0;i<hookList.length;i++) {
removeHook(hookList[i],labelledHooks[label][hookList[i]])
const hookList = Object.keys(labelledHooks[label])
for (let i = 0; i < hookList.length; i++) {
removeHook(hookList[i], labelledHooks[label][hookList[i]])
}
delete labelledHooks[label];
delete labelledHooks[label]
} else if (labelledHooks[label][id]) {
removeHook(id,labelledHooks[label][id])
delete labelledHooks[label][id];
if (Object.keys(labelledHooks[label]).length === 0){
delete labelledHooks[label];
removeHook(id, labelledHooks[label][id])
delete labelledHooks[label][id]
if (Object.keys(labelledHooks[label]).length === 0) {
delete labelledHooks[label]
}
}
}
}
function removeHook(id,hookItem) {
var previousHook = hookItem.previousHook;
var nextHook = hookItem.nextHook;
/**
* Remove a hook from the linked list of hooks for a given id
* @param {HookId} id
* @param {HookItem} hookItem
* @private
*/
function removeHook(id, hookItem) {
let previousHook = hookItem.previousHook
let nextHook = hookItem.nextHook
if (previousHook) {
previousHook.nextHook = nextHook;
previousHook.nextHook = nextHook
} else {
hooks[id] = nextHook;
hooks[id] = nextHook
}
if (nextHook) {
nextHook.previousHook = previousHook;
nextHook.previousHook = previousHook
}
hookItem.removed = true;
hookItem.removed = true
if (!previousHook && !nextHook) {
delete hooks[id];
delete hooks[id]
}
}
function trigger(hookId, payload, done) {
var hookItem = hooks[hookId];
/**
* Trigger a hook, calling all registered callbacks in sequence.
* If any callback returns false, the flow is halted and no further hooks are called.
* @param {HookId} id The id of the hook to trigger (should not include a label - e.g. "viewAddNode", not "viewAddNode.myLabel")
* @param {*} payload The payload to be passed to each hook callback
* @param {function(?Error=):void} [done] Optional callback. If not provided, a Promise will be returned.
* @return {Promise|undefined} Returns a Promise if the done callback is not provided, otherwise undefined
*/
function trigger(id, payload, done) {
let hookItem = hooks[id]
if (!hookItem) {
if (done) {
done();
done()
return
} else {
return Promise.resolve()
}
return;
}
if (!done) {
return new Promise((resolve, reject) => {
invokeStack(hookItem, payload, function (err) {
if (err !== undefined && err !== false) {
if (!(err instanceof Error)) {
err = new Error(err)
}
err.hook = id
reject(err)
} else {
resolve(err)
}
})
})
} else {
invokeStack(hookItem, payload, done)
}
}
/**
* @private
*/
function invokeStack(hookItem, payload, done) {
function callNextHook(err) {
if (!hookItem || err) {
if (done) { done(err) }
return err;
done(err)
return
}
if (hookItem.removed) {
hookItem = hookItem.nextHook;
return callNextHook();
hookItem = hookItem.nextHook
callNextHook()
return
}
var callback = hookItem.cb;
const callback = hookItem.cb
if (callback.length === 1) {
try {
let result = callback(payload);
let result = callback(payload)
if (result === false) {
// Halting the flow
if (done) { done(false) }
return result;
done(false)
return
}
hookItem = hookItem.nextHook;
return callNextHook();
} catch(e) {
console.warn(e);
if (done) { done(e);}
return e;
if (result && typeof result.then === 'function') {
result.then(handleResolve, callNextHook)
return
}
hookItem = hookItem.nextHook
callNextHook()
} catch (e) {
done(e)
return
}
} else {
// There is a done callback
try {
callback(payload,function(result) {
if (result === undefined) {
hookItem = hookItem.nextHook;
callNextHook();
} else {
if (done) { done(result)}
}
})
} catch(e) {
console.warn(e);
if (done) { done(e) }
return e;
callback(payload, handleResolve)
} catch (e) {
done(e)
return
}
}
}
return callNextHook();
function handleResolve(result) {
if (result === undefined) {
hookItem = hookItem.nextHook
callNextHook()
} else {
done(result)
}
}
callNextHook()
}
function clear() {
@ -137,20 +208,48 @@ RED.hooks = (function() {
labelledHooks = {}
}
/**
* Check if a hook with the given id exists
* @param {string} hookId The hook identifier, which may include a label (e.g. "viewAddNode.myLabel")
* @returns {boolean}
*/
function has(hookId) {
var parts = hookId.split(".");
var id = parts[0], label = parts[1];
const { label, id } = parseLabelledHook(hookId)
if (label) {
return !!(labelledHooks[label] && labelledHooks[label][id])
}
return !!hooks[id]
}
return {
has: has,
clear: clear,
add: add,
remove: remove,
trigger: trigger
function isKnownHook(hookId) {
const { id } = parseLabelledHook(hookId)
return !!VALID_HOOKS[id]
}
})();
/**
* Split a hook identifier into its id and label components.
* @param {*} hookId A hook identifier, which may include a label (e.g. "viewAddNode.myLabel")
* @returns {{label: string, id: HookId}}
* @private
*/
function parseLabelledHook(hookId) {
if (typeof hookId !== "string") {
return { label: '', id: '' }
}
const parts = hookId.split(".")
const id = parts[0]
const label = parts[1]
return { label, id }
}
VALID_HOOKS['all'] = true // Special wildcard to allow hooks to indicate they should be triggered for all ids
return {
has,
clear,
add,
remove,
trigger,
isKnownHook
}
})()

View File

@ -43,6 +43,7 @@ RED.keyboard = (function() {
"-":189,
".":190,
"/":191,
"§":192, // <- top left key MacOS
"\\":220,
"'":222,
"?":191, // <- QWERTY specific

View File

@ -40,6 +40,12 @@ RED.palette.editor = (function() {
// Install tab - search input
let searchInput;
// Core and Package Updates
/** @type {Array<{package: string, current: string, available: string}>} */
const moduleUpdates = []
const updateStatusState = { version: null, moduleCount: 0 }
const SMALL_CATALOGUE_SIZE = 40
const typesInUse = {};
@ -1825,8 +1831,6 @@ RED.palette.editor = (function() {
const updateStatusWidget = $('<button type="button" class="red-ui-footer-button red-ui-update-status"></button>');
let updateStatusWidgetPopover;
const updateStatusState = { moduleCount: 0 }
let updateAvailable = [];
function addUpdateInfoToStatusBar() {
updateStatusWidgetPopover = RED.popover.create({
@ -1835,7 +1839,7 @@ RED.palette.editor = (function() {
interactive: true,
direction: "bottom",
content: function () {
const count = updateAvailable.length || 0;
const count = moduleUpdates.length || 0
const content = $('<div style="display: flex; flex-direction: column; gap: 5px;"></div>');
if (updateStatusState.version) {
$(`<a class='red-ui-button' href="https://github.com/node-red/node-red/releases/tag/${updateStatusState.version}" target="_blank">${RED._("telemetry.updateAvailableDesc", updateStatusState)}</a>`).appendTo(content)
@ -1845,7 +1849,7 @@ RED.palette.editor = (function() {
updateStatusWidgetPopover.close()
RED.actions.invoke("core:manage-palette", {
view: "nodes",
filter: '"' + updateAvailable.join('", "') + '"'
filter: '"' + moduleUpdates.map(u => u.package).join('", "') + '"'
});
}).appendTo(content)
}
@ -1867,7 +1871,7 @@ RED.palette.editor = (function() {
function refreshUpdateStatus() {
clearTimeout(pendingRefreshTimeout)
pendingRefreshTimeout = setTimeout(() => {
updateAvailable = [];
moduleUpdates.length = 0
for (const module of Object.keys(nodeEntries)) {
if (loadedIndex.hasOwnProperty(module)) {
const moduleInfo = nodeEntries[module].info;
@ -1875,35 +1879,51 @@ RED.palette.editor = (function() {
// Module updated
continue;
}
const current = moduleInfo.version
const latest = loadedIndex[module].version
if (updateAllowed &&
semVerCompare(loadedIndex[module].version, moduleInfo.version) > 0 &&
semVerCompare(latest, current) > 0 &&
RED.utils.checkModuleAllowed(module, null, updateAllowList, updateDenyList)
) {
updateAvailable.push(module);
moduleUpdates.push({ package: module, current, latest })
}
}
}
updateStatusState.moduleCount = updateAvailable.length;
updateStatusState.moduleCount = moduleUpdates.length
updateStatus();
}, 200)
}
function updateStatus() {
if (updateStatusState.moduleCount || updateStatusState.version) {
const updates = RED.palette.editor.getAvailableUpdates()
if (updates.count > 0) {
updateStatusWidget.empty();
let count = updateStatusState.moduleCount || 0;
if (updateStatusState.version) {
count ++
}
$(`<span><i class="fa fa-cube"></i> ${RED._("telemetry.updateAvailable", { count: count })}</span>`).appendTo(updateStatusWidget);
$(`<span><i class="fa fa-cube"></i> ${RED._("telemetry.updateAvailable", { count: updates.count })}</span>`).appendTo(updateStatusWidget);
RED.statusBar.show("red-ui-status-package-update");
} else {
RED.statusBar.hide("red-ui-status-package-update");
}
RED.events.emit("registry:updates-available", updates)
}
function getAvailableUpdates () {
const palette = [...moduleUpdates]
let core = null
let count = palette.length
if (updateStatusState.version) {
core = { current: RED.settings.version, latest: updateStatusState.version }
count ++
}
return {
count,
core,
palette
}
}
return {
init: init,
install: install
init,
install,
getAvailableUpdates
}
})();

View File

@ -210,7 +210,7 @@ RED.sidebar.config = (function() {
nodeDiv.addClass("red-ui-palette-node-config-invalid");
RED.popover.tooltip(nodeDivAnnotations, function () {
if (node.validationErrors && node.validationErrors.length > 0) {
return RED._("editor.errors.invalidProperties") + "<br> - " + node.validationErrors.join("<br> - ");
return $('<span>' + RED._("editor.errors.invalidProperties") + "<br> - " + node.validationErrors.join("<br> - ") + '</span>');
}
})
}
@ -218,7 +218,7 @@ RED.sidebar.config = (function() {
nodeDiv.on('click',function(e) {
e.stopPropagation();
RED.view.select(false);
if (e.metaKey) {
if (e.metaKey || e.ctrlKey) {
$(this).toggleClass("selected");
} else {
$(content).find(".red-ui-palette-node").removeClass("selected");

View File

@ -1110,20 +1110,36 @@ RED.utils = (function() {
return result;
}
/**
* Get the default icon for a given node based on its definition.
* @param {*} def
* @param {*} node
* @returns
*/
function getDefaultNodeIcon(def,node) {
def = def || {};
var icon_url;
if (node && node.type === "subflow") {
icon_url = "node-red/subflow.svg";
} else if (typeof def.icon === "function") {
try {
icon_url = def.icon.call(node);
} catch(err) {
console.log("Definition error: "+def.type+".icon",err);
icon_url = "arrow-in.svg";
}
} else {
icon_url = def.icon;
let themeRule = nodeIconCache[def.type]
if (themeRule === undefined) {
// If undefined, we've not checked the theme yet
nodeIconCache[def.type] = getThemeOverrideForNode(def, 'icon') || null;
themeRule = nodeIconCache[def.type];
}
if (themeRule) {
icon_url = themeRule.icon;
} else if (typeof def.icon === "function") {
try {
icon_url = def.icon.call(node);
} catch(err) {
console.log("Definition error: "+def.type+".icon",err);
icon_url = "arrow-in.svg";
}
} else {
icon_url = def.icon;
}
}
var iconPath = separateIconPath(icon_url);
@ -1249,48 +1265,60 @@ RED.utils = (function() {
return label
}
var nodeColorCache = {};
let nodeColorCache = {};
let nodeIconCache = {}
function clearNodeColorCache() {
nodeColorCache = {};
}
function getNodeColor(type, def) {
def = def || {};
var result = def.color;
var paletteTheme = RED.settings.theme('palette.theme') || [];
/**
* Checks if there is a theme override for the given node definition and property
* @param {*} def node definition
* @param {*} property either 'color' or 'icon'
* @returns the theme override value if there is a match, otherwise null
*/
function getThemeOverrideForNode(def, property) {
const paletteTheme = RED.settings.theme('palette.theme') || [];
if (paletteTheme.length > 0) {
if (!nodeColorCache.hasOwnProperty(type)) {
nodeColorCache[type] = def.color;
var l = paletteTheme.length;
for (var i = 0; i < l; i++ ){
var themeRule = paletteTheme[i];
if (themeRule.hasOwnProperty('category')) {
if (!themeRule.hasOwnProperty('_category')) {
themeRule._category = new RegExp(themeRule.category);
}
if (!themeRule._category.test(def.category)) {
continue;
}
for (let i = 0; i < paletteTheme.length; i++ ){
const themeRule = paletteTheme[i];
if (themeRule.hasOwnProperty('category')) {
if (!themeRule.hasOwnProperty('_category')) {
themeRule._category = new RegExp(themeRule.category);
}
if (themeRule.hasOwnProperty('type')) {
if (!themeRule.hasOwnProperty('_type')) {
themeRule._type = new RegExp(themeRule.type);
}
if (!themeRule._type.test(type)) {
continue;
}
if (!themeRule._category.test(def.category)) {
continue;
}
nodeColorCache[type] = themeRule.color || def.color;
break;
}
if (themeRule.hasOwnProperty('type')) {
if (!themeRule.hasOwnProperty('_type')) {
themeRule._type = new RegExp(themeRule.type);
}
if (!themeRule._type.test(def.type)) {
continue;
}
}
// We have found a rule that matches - now see if it provides the requested property
if (themeRule.hasOwnProperty(property)) {
return themeRule;
}
}
result = nodeColorCache[type];
}
if (result) {
return result;
} else {
return "#ddd";
return null;
}
function getNodeColor(type, def) {
def = def || {};
if (!nodeColorCache.hasOwnProperty(type)) {
const paletteTheme = RED.settings.theme('palette.theme') || [];
if (paletteTheme.length > 0) {
const themeRule = getThemeOverrideForNode(def, 'color');
nodeColorCache[type] = themeRule?.color || def.color;
} else {
nodeColorCache[type] = def.color;
}
}
return nodeColorCache[type] || "#ddd";
}
function addSpinnerOverlay(container,contain) {

View File

@ -4032,7 +4032,7 @@ RED.view = (function() {
clearSuggestedFlow();
RED.contextMenu.hide();
evt = evt || d3.event;
if (evt === 1) {
if (evt.button !== 0) {
return;
}
if (mouse_mode === RED.state.SELECTING_NODE) {
@ -4897,7 +4897,7 @@ RED.view = (function() {
d3.event.stopPropagation();
return;
}
if (d3.event.button === 2) {
if (d3.event.button !== 0) {
return
}
mousedown_link = d;

View File

@ -433,12 +433,28 @@ RED.debug = (function() {
if (o) { stack.push(o); }
if (!busy && (stack.length > 0)) {
busy = true;
processDebugMessage(stack.shift());
setTimeout(function() {
busy = false;
handleDebugMessage();
}, 15); // every 15mS = 66 times a second
if (stack.length > numMessages) { stack = stack.splice(-numMessages); }
const message = stack.shift()
// call any preDebugLog hooks, allowing them to modify the message or block it from being displayed
RED.hooks.trigger('debugPreProcessMessage', { message }).then(result => {
if (result === false) {
return false; // A hook returned false - halt processing of this message
}
return processDebugMessage(message);
}).then(processArtifacts => {
if (processArtifacts === false) {
return false; // A hook returned false - halt processing of this message
}
const { message, element, payload } = processArtifacts || {};
return RED.hooks.trigger('debugPostProcessMessage', { message, element, payload });
}).catch(err => {
console.error("Error in debug process message hooks", err);
}).finally(() => {
setTimeout(function() {
busy = false;
handleDebugMessage();
}, 15); // every 15mS = 66 times a second
if (stack.length > numMessages) { stack = stack.splice(-numMessages); }
})
}
} else {
debugPausedMessageCount++
@ -564,10 +580,13 @@ RED.debug = (function() {
sourceId: sourceNode && sourceNode.id,
rootPath: path,
nodeSelector: config.messageSourceClick,
enablePinning: true
enablePinning: true,
tools: o.tools // permit preDebugLog hooks to add extra tools to the <debugMessage> element
});
// Do this in a separate step so the element functions aren't stripped
debugMessage.appendTo(el);
// add the meta row tools container, even if there are no tools, so that the postProcessDebugMessage hook can add tools
const tools = $('<span class="red-ui-debug-msg-tools button-group"></span>').appendTo(metaRow)
// NOTE: relying on function error to have a "type" that all other msgs don't
if (o.hasOwnProperty("type") && (o.type === "function")) {
var errorLvlType = 'error';
@ -579,7 +598,6 @@ RED.debug = (function() {
msg.addClass('red-ui-debug-msg-level-' + errorLvl);
$('<span class="red-ui-debug-msg-topic">function : (' + errorLvlType + ')</span>').appendTo(metaRow);
} else {
var tools = $('<span class="red-ui-debug-msg-tools button-group"></span>').appendTo(metaRow);
var filterMessage = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-caret-down"></i></button>').appendTo(tools);
filterMessage.on("click", function(e) {
e.preventDefault();
@ -635,6 +653,14 @@ RED.debug = (function() {
if (atBottom) {
messageList.scrollTop(sbc.scrollHeight);
}
// return artifacts to permit postProcessDebugMessage hooks to modify the message element, access the
// processed payload or otherwise modify the message after it has been generated.
return {
message: o, // original debug message object, useful for any hook that might have tagged additional info onto it
element: msg, // the top-level element for this debug message
payload // the reconstructed debug message
}
}
function clearMessageList(clearFilter, filteredOnly) {

View File

@ -15,9 +15,9 @@
}
],
"dependencies": {
"acorn": "8.15.0",
"acorn-walk": "8.3.4",
"ajv": "8.17.1",
"acorn": "8.16.0",
"acorn-walk": "8.3.5",
"ajv": "8.18.0",
"body-parser": "1.20.4",
"cheerio": "1.0.0-rc.10",
"content-type": "1.0.5",
@ -35,7 +35,7 @@
"is-utf8": "0.2.1",
"js-yaml": "4.1.1",
"media-typer": "1.1.0",
"mqtt": "5.11.0",
"mqtt": "5.15.0",
"multer": "2.0.2",
"mustache": "4.2.0",
"node-watch": "0.7.4",

View File

@ -19,8 +19,8 @@
"@node-red/util": "5.0.0-beta.2",
"clone": "2.1.2",
"fs-extra": "11.3.0",
"semver": "7.7.1",
"tar": "7.5.7",
"semver": "7.7.4",
"tar": "7.5.9",
"uglify-js": "3.19.3"
}
}

View File

@ -25,7 +25,7 @@
"fs-extra": "11.3.0",
"got": "12.6.1",
"json-stringify-safe": "5.0.1",
"rfdc": "^1.3.1",
"semver": "7.7.1"
"rfdc": "1.4.1",
"semver": "7.7.4"
}
}

View File

@ -36,13 +36,13 @@
"@node-red/util": "5.0.0-beta.2",
"@node-red/nodes": "5.0.0-beta.2",
"basic-auth": "2.0.1",
"bcryptjs": "3.0.2",
"bcryptjs": "3.0.3",
"cors": "2.8.5",
"express": "4.22.1",
"fs-extra": "11.3.0",
"node-red-admin": "^4.1.3",
"nopt": "5.0.0",
"semver": "7.7.1"
"semver": "7.7.4"
},
"optionalDependencies": {
"@node-rs/bcrypt": "1.10.7"

View File

@ -53,7 +53,7 @@ describe("api/editor/theme", function () {
context.asset.should.have.a.property("main", "red/main.min.js");
context.asset.should.have.a.property("vendorMonaco", "vendor/monaco/monaco-bootstrap.js");
should.not.exist(theme.settings());
should.not.exist(await theme.settings());
});
it("uses non-minified js files when in dev mode", async function () {
@ -158,7 +158,7 @@ describe("api/editor/theme", function () {
context.should.have.a.property("login");
context.login.should.have.a.property("image", "theme/login/image");
var settings = theme.settings();
var settings = await theme.settings();
settings.should.have.a.property("deployButton");
settings.deployButton.should.have.a.property("type", "simple");
settings.deployButton.should.have.a.property("label", "Save");
@ -199,7 +199,7 @@ describe("api/editor/theme", function () {
});
it("test explicit userMenu set to true in theme setting", function () {
it("test explicit userMenu set to true in theme setting", async function () {
theme.init({
editorTheme: {
userMenu: true,
@ -208,7 +208,7 @@ describe("api/editor/theme", function () {
theme.app();
var settings = theme.settings();
var settings = await theme.settings();
settings.should.have.a.property("userMenu");
settings.userMenu.should.be.eql(true);