node-red-node-defaults 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.txt ADDED
@@ -0,0 +1,7 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 William Shostak
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/index.js ADDED
@@ -0,0 +1,14 @@
1
+ module.exports = function(RED) {
2
+ const nodeDefaults = RED.settings.nodeDefaults || {}
3
+
4
+ // Expose nodeDefaults to the editor via HTTP.
5
+ // RED.settings with exportable:true uses a different client-side path;
6
+ // a plain GET endpoint is the reliable approach (see SFL pattern).
7
+ RED.httpAdmin.get('/node-defaults/config', (req, res) => {
8
+ res.json(nodeDefaults)
9
+ })
10
+
11
+ RED.plugins.registerPlugin('node-red-node-defaults', {
12
+ type: 'node-defaults'
13
+ })
14
+ }
package/package.json ADDED
@@ -0,0 +1,161 @@
1
+ {
2
+ "name": "node-red-node-defaults",
3
+ "version": "1.0.0",
4
+ "description": "Node-RED editor plugin that injects default values into node instances when dropped onto the canvas",
5
+ "author": "Will Shostak",
6
+ "keywords": [
7
+ "node-red",
8
+ "node-red-plugin",
9
+ "node-red-defaults",
10
+ "node-red-editor"
11
+ ],
12
+ "license": "ISC",
13
+ "private": false,
14
+ "scripts": {
15
+ "dev:prepare": "husky install",
16
+ "coverage": "jest --coverage",
17
+ "test": "jest",
18
+ "test:debug": "jest --detectOpenHandles"
19
+ },
20
+ "node-red": {
21
+ "plugins": {
22
+ "node-red-node-defaults": "plugin.html"
23
+ },
24
+ "nodes": {
25
+ "node-red-node-defaults": "index.js"
26
+ }
27
+ },
28
+ "jest": {
29
+ "verbose": true,
30
+ "testMatch": [
31
+ "**/test/*.js?(x)"
32
+ ],
33
+ "reporters": [
34
+ "default",
35
+ [
36
+ "jest-junit",
37
+ {
38
+ "outputDirectory": ".",
39
+ "outputName": "test-results.xml"
40
+ }
41
+ ]
42
+ ],
43
+ "coverageReporters": [
44
+ "cobertura",
45
+ "text"
46
+ ],
47
+ "transform": {
48
+ "^.+\\.jsx?$": "babel-jest"
49
+ }
50
+ },
51
+ "eslintConfig": {
52
+ "parserOptions": {
53
+ "sourceType": "script",
54
+ "ecmaVersion": 2020,
55
+ "ecmaFeatures": {
56
+ "globalReturn": true
57
+ }
58
+ },
59
+ "env": {
60
+ "es2021": true,
61
+ "node": true,
62
+ "browser": true,
63
+ "commonjs": true,
64
+ "jest": true
65
+ },
66
+ "extends": [
67
+ "airbnb-base",
68
+ "plugin:cypress/recommended",
69
+ "plugin:chai-friendly/recommended"
70
+ ],
71
+ "plugins": [
72
+ "node",
73
+ "security",
74
+ "prettier"
75
+ ],
76
+ "rules": {
77
+ "curly": [
78
+ "error",
79
+ "all"
80
+ ],
81
+ "semi": [
82
+ "error",
83
+ "never"
84
+ ],
85
+ "comma-dangle": [
86
+ "error",
87
+ "never"
88
+ ],
89
+ "arrow-parens": [
90
+ "error",
91
+ "as-needed"
92
+ ],
93
+ "sort-imports": [
94
+ "error",
95
+ {
96
+ "ignoreMemberSort": true,
97
+ "ignoreDeclarationSort": true
98
+ }
99
+ ],
100
+ "no-param-reassign": [
101
+ "error",
102
+ {
103
+ "props": false
104
+ }
105
+ ],
106
+ "no-plusplus": [
107
+ "error",
108
+ {
109
+ "allowForLoopAfterthoughts": true
110
+ }
111
+ ],
112
+ "no-bitwise": [
113
+ "error",
114
+ {
115
+ "allow": [
116
+ "~"
117
+ ]
118
+ }
119
+ ],
120
+ "jest/no-disabled-tests": "warn",
121
+ "jest/no-focused-tests": "error",
122
+ "jest/no-identical-title": "error",
123
+ "jest/prefer-to-have-length": "warn",
124
+ "jest/valid-expect": "error"
125
+ },
126
+ "globals": {
127
+ "logger": true,
128
+ "kantan": true,
129
+ "logToDB": true,
130
+ "configs": true,
131
+ "msg": true
132
+ }
133
+ },
134
+ "dependencies": {},
135
+ "optionalDependencies": {},
136
+ "devDependencies": {
137
+ "jest": "^29.0.0",
138
+ "concurrently": "^7.2.2",
139
+ "cross-env": "^7.0.3",
140
+ "eslint": "^8.57.1",
141
+ "eslint-config-airbnb-base": "^14.1.0",
142
+ "eslint-plugin-import": "^2.20.2",
143
+ "eslint-plugin-jest": "^28.11.0",
144
+ "eslint-plugin-node": "^11.1.0",
145
+ "eslint-plugin-prettier": "^3.3.0",
146
+ "eslint-plugin-security": "^1.4.0",
147
+ "husky": "8.0.3",
148
+ "jest-junit": "^16.0.0",
149
+ "node-red": "^4.0.0",
150
+ "node-red-node-test-helper": "^0.3.5",
151
+ "nyc": "^15.1.0",
152
+ "prettier": "^2.2.1",
153
+ "prettier-eslint": "^12.0.0",
154
+ "wait-on": "^6.0.1"
155
+ },
156
+ "browserslist": [
157
+ "> 1%",
158
+ "last 2 versions",
159
+ "not dead"
160
+ ]
161
+ }
package/plugin.html ADDED
@@ -0,0 +1,71 @@
1
+ <script type="text/javascript">
2
+ // Object-first module: injection logic in a named object so it mirrors
3
+ // node-defaults-injector.js (the testable CommonJS twin of this browser code).
4
+ // Injection rules:
5
+ // 1. field is null / undefined / empty string → inject
6
+ // 2. field matches the type's built-in default → inject (never changed)
7
+ // 3. field differs from built-in default → leave (user set it)
8
+ // typeDef.defaults[field].value is the stable built-in default — never mutated.
9
+ // Credential fields (typeDef.credentials) use rule 1 only.
10
+ const NodeDefaults = {
11
+ injectIntoNode(node, configFields, typeDef) {
12
+ Object.entries(configFields).forEach(([field, configValue]) => {
13
+ const { defaults = {}, credentials = {} } = typeDef
14
+ const inDefaults = field in defaults
15
+ const inCredentials = field in credentials
16
+
17
+ if (inCredentials) {
18
+ // Credentials live on node.credentials — inject only when empty.
19
+ node.credentials = node.credentials || {}
20
+ const currentValue = node.credentials[field]
21
+
22
+ if (currentValue == null || currentValue === '') {
23
+ node.credentials[field] = configValue
24
+ }
25
+ } else if (inDefaults) {
26
+ // Regular defaults — three-rule injection.
27
+ const builtinDefault = defaults[field].value
28
+ const currentValue = node[field]
29
+ const isUnchanged = currentValue === builtinDefault
30
+
31
+ if (currentValue == null || currentValue === '' || isUnchanged) {
32
+ node[field] = configValue
33
+ }
34
+ }
35
+ })
36
+ }
37
+ }
38
+
39
+ RED.plugins.registerPlugin('node-red-node-defaults', {
40
+ type: 'node-defaults',
41
+ onadd() {
42
+ const adminRoot = (RED.settings.httpAdminRoot || '').replace(/\/$/, '')
43
+ const url = `${adminRoot}/node-defaults/config`
44
+
45
+ // Safe default — {} means nodeDefaults[node.type] is cleanly falsy until
46
+ // the fetch resolves. No undefined in the model.
47
+ let nodeDefaults = {}
48
+
49
+ RED.events.on('nodes:add', node => {
50
+ if (node && nodeDefaults[node.type]) {
51
+ const typeDef = RED.nodes.getType(node.type)
52
+
53
+ if (typeDef) {
54
+ NodeDefaults.injectIntoNode(node, nodeDefaults[node.type], typeDef)
55
+ }
56
+ }
57
+ })
58
+
59
+ $.getJSON(url).done(data => {
60
+ if (!data || Object.keys(data).length === 0) {
61
+ return
62
+ }
63
+
64
+ nodeDefaults = data
65
+ })
66
+ .fail(err => {
67
+ console.error('[node-red-node-defaults] failed to fetch config', err)
68
+ })
69
+ }
70
+ })
71
+ </script>
package/readme.md ADDED
@@ -0,0 +1,102 @@
1
+ # node-red-node-defaults
2
+
3
+ A Node-RED editor plugin that automatically injects default values into node instances when they are dropped onto the canvas.
4
+
5
+ Defaults are defined once in `settings.js` and apply to every new node of the configured type — no flow-by-flow setup required.
6
+
7
+ ## How it works
8
+
9
+ The plugin has two parts:
10
+
11
+ | Part | File | Role |
12
+ |---|---|---|
13
+ | Server | `index.js` | Reads `nodeDefaults` from `settings.js`, exposes it via a lightweight HTTP endpoint |
14
+ | Editor | `index.html` | Fetches the config on load, listens for `nodes:add`, and injects defaults into each new node instance |
15
+
16
+ ### Injection rules (per field)
17
+
18
+ For each configured field the editor applies these rules in order:
19
+
20
+ 1. **Field is empty** (`null`, `undefined`, `''`) → inject
21
+ 2. **Field matches the node type's built-in default** → inject (user never changed it)
22
+ 3. **Field differs from the built-in default** → leave (user set it intentionally)
23
+
24
+ Credential fields (`userid`, `password`, etc.) use rule 1 only — inject if empty, leave if already set.
25
+
26
+ Existing saved nodes in a flow are never overwritten — injection only applies to nodes added to the canvas after the plugin loads.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ # In your Node-RED user directory (typically ~/.node-red)
32
+ npm install node-red-node-defaults
33
+ ```
34
+
35
+ Or during development, link it:
36
+
37
+ ```bash
38
+ # In this package directory
39
+ yarn link
40
+
41
+ # In your Node-RED user directory
42
+ yarn link node-red-node-defaults
43
+ ```
44
+
45
+ ## Configuration
46
+
47
+ Add a `nodeDefaults` object to your Node-RED `settings.js`. The top-level keys are node type names exactly as registered (e.g. `'e-mail'`, not the package name).
48
+
49
+ ```js
50
+ // settings.js
51
+ module.exports = {
52
+ // ... other settings ...
53
+
54
+ nodeDefaults: {
55
+ // Outbound SMTP (node-red-node-email)
56
+ 'e-mail': {
57
+ server: 'smtp.example.com',
58
+ port: '587',
59
+ secure: false, // false = STARTTLS on 587; true = TLS on 465
60
+ tls: false,
61
+ authtype: 'BASIC',
62
+ userid: 'noreply@example.com', // credential field
63
+ password: 'yourpassword' // credential field
64
+ },
65
+
66
+ // Inbound IMAP/POP3 (node-red-node-email)
67
+ 'e-mail in': {
68
+ protocol: 'IMAP',
69
+ server: 'imap.example.com',
70
+ port: '993',
71
+ useSSL: true,
72
+ autotls: 'never', // 'never' | 'required' | 'always'
73
+ box: 'INBOX',
74
+ disposition: 'Read', // 'None' | 'Read' | 'Delete'
75
+ criteria: 'UNSEEN',
76
+ repeat: '300'
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### Finding the correct field names
83
+
84
+ Field names must match what the node type declares in its `defaults` or `credentials` block. The easiest way to find them:
85
+
86
+ 1. Open the Node-RED editor
87
+ 2. Drop the node onto the canvas
88
+ 3. Open **Browser DevTools → Network → `/nodes`**
89
+ 4. Find the node's HTML file and look at `RED.nodes.registerType('type-name', { defaults: { ... }, credentials: { ... } })`
90
+
91
+ ## Security note
92
+
93
+ Credential values (e.g. `userid`, `password`) set in `settings.js` are returned in plaintext from the `/node-defaults/config` endpoint to any authenticated browser session that can reach the Node-RED editor. Ensure your Node-RED instance is not publicly accessible when using credential defaults.
94
+
95
+ ### Contact
96
+
97
+ - **Author:** William Shostak (https://github.com/wshostak)
98
+
99
+ ## License
100
+ This project is licensed under the **ISC License** — see the **[LICENSE](./LICENSE.txt)** file for more details.
101
+
102
+ Copyright (c) 2026 William Shostak