node-red-contrib-config-files 0.2.0 → 0.2.1
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/CHANGELOG.md +4 -0
- package/HELP.md +7 -6
- package/README.md +11 -7
- package/package.json +1 -1
- package/subflow.json +71 -61
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.1] - 2026-01-31
|
|
4
|
+
### Changed
|
|
5
|
+
- If directory `configDir` doesn't exist or is not accessible: Instead of throwing a node error, a node warning is created. This warning may appear in the “Debug Messages” sidebar.
|
|
6
|
+
|
|
3
7
|
## [0.2.0] - 2026-01-30
|
|
4
8
|
### Added
|
|
5
9
|
- If directory `configDir` doesn't exist or is not accessible, a node error is throw. This error can be received with a `catch` node, for example.
|
package/HELP.md
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
Read and merge configuration files (JSON).
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
### Inputs
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
Directory and filename extension used to search for configuration
|
|
6
6
|
files can be configured using the node properties and/or with the
|
|
7
7
|
input message.
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
: configDir (string) : Directory with config files. Absolute or relative path, default: `/config`. Input message `msg.configDir` overwrites the node property.
|
|
10
10
|
: configFileExt (string) : Filename extension for config files, default: `.json`. Input message `msg.configFileExt` overwrites the node property.
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
### Outputs
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
: payload (object) : Merged content from config files.
|
|
15
|
+
: configFiles [] : Paths to config files found, empty array if no files were found.
|
|
15
16
|
|
|
16
17
|
### Details
|
|
17
18
|
The node pproperty `defaultConfig` can be used to define configuration
|
|
18
|
-
values that are missing in the configuration files.
|
|
19
|
+
values that are used when they are missing in the configuration files.
|
|
19
20
|
|
|
20
21
|
The `configDir` is searched non-recursively for configuration files using
|
|
21
22
|
the filename extension `configFilePattern`.
|
package/README.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# node-red-contrib-config-files
|
|
2
|
-
[Node-Red](https://nodered.org/) node for reading and merging configuration files with JSON content. The output returns
|
|
2
|
+
[Node-Red](https://nodered.org/) node for reading and merging configuration files with JSON content. The output returns
|
|
3
|
+
the resulting configuration object.
|
|
3
4
|
|
|
4
|
-
Directory and filename extension used to search for configuration files can be configured using the node properties and/or
|
|
5
|
+
Directory and filename extension used to search for configuration files can be configured using the node properties and/or
|
|
6
|
+
with the input message.
|
|
5
7
|
|
|
6
|
-
The files are sorted by their file names and merged in this order. The `defaultConfig`
|
|
8
|
+
The files are sorted by their file names and merged in this order. The `defaultConfig` can be used to define configuration
|
|
9
|
+
values that are used when they are missing in the configuration files.
|
|
7
10
|
|
|
8
11
|
If no configuration files are found, the `defaultConfig` is returned.
|
|
9
12
|
|
|
@@ -11,10 +14,11 @@ The node status displays one of the following two messages:
|
|
|
11
14
|
- Found {count} config files in '{configDir}' with extension '{configFileExt}'
|
|
12
15
|
- No config files found in '{configDir}' with extension '{configFileExt}' -> using default config
|
|
13
16
|
|
|
14
|
-
If directory `configDir` doesn't exist or is not accessible
|
|
15
|
-
|
|
16
|
-
If no file was found with `configFileExt`, a node warning is created. This warning may appear in the “Debug Messages” sidebar.
|
|
17
|
+
If directory `configDir` doesn't exist or is not accessible or if no file was found with `configFileExt`, a node warning
|
|
18
|
+
is created. This warning may appear in the “Debug Messages” sidebar.
|
|
17
19
|
|
|
18
20
|
## Internals
|
|
19
|
-
- This custom node
|
|
21
|
+
- This custom node was created from a subflow.
|
|
20
22
|
- It uses `lodash.merge()` to merge the `defaultConfig` with the contents of the files found.
|
|
23
|
+
- Some debug information is written by the node using `node.debug()`. To view these messages, set the logging level
|
|
24
|
+
in `settings.js` to at least `debug`.
|
package/package.json
CHANGED
package/subflow.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "ec796389903c1d0f",
|
|
3
3
|
"type": "subflow",
|
|
4
4
|
"name": "config-files",
|
|
5
|
-
"info": "Read and merge configuration files (JSON)
|
|
5
|
+
"info": "Read and merge configuration files (JSON). \n\n### Inputs\n\nDirectory and filename extension used to search for configuration \nfiles can be configured using the node properties and/or with the \ninput message.\n\n: configDir (string) : Directory with config files. Absolute or relative path, default: `/config`. Input message `msg.configDir` overwrites the node property.\n: configFileExt (string) : Filename extension for config files, default: `.json`. Input message `msg.configFileExt` overwrites the node property.\n\n### Outputs\n\n: payload (object) : Merged content from config files.\n: configFiles [] : Paths to config files found, empty array if no files were found.\n\n### Details\nThe node pproperty `defaultConfig` can be used to define configuration \nvalues that are used when they are missing in the configuration files.\n\nThe `configDir` is searched non-recursively for configuration files using \nthe filename extension `configFilePattern`.\nThe files are sorted by their file names and merged in this order. \n\nThe content of the `defaultConfig` property and the configuration files \nmust by valid JSON.\n\n### References\n\n- [GitHub](https://github.com/schaeren/node-red-contrib-config-files) - source code",
|
|
6
6
|
"category": "",
|
|
7
7
|
"in": [
|
|
8
8
|
{
|
|
@@ -17,16 +17,19 @@
|
|
|
17
17
|
],
|
|
18
18
|
"out": [
|
|
19
19
|
{
|
|
20
|
-
"x":
|
|
21
|
-
"y":
|
|
20
|
+
"x": 740,
|
|
21
|
+
"y": 400,
|
|
22
22
|
"wires": [
|
|
23
23
|
{
|
|
24
24
|
"id": "86c429715975c781",
|
|
25
25
|
"port": 0
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "efa7ea24e8224b84",
|
|
29
|
+
"port": 0
|
|
26
30
|
}
|
|
27
31
|
]
|
|
28
|
-
}
|
|
29
|
-
],
|
|
32
|
+
} ],
|
|
30
33
|
"env": [
|
|
31
34
|
{
|
|
32
35
|
"name": "configDir",
|
|
@@ -48,7 +51,7 @@
|
|
|
48
51
|
"module": "node-red-contrib-config-files",
|
|
49
52
|
"type": "config-files",
|
|
50
53
|
"desc": "Read and merge configuration files (JSON).",
|
|
51
|
-
"version": "0.2.
|
|
54
|
+
"version": "0.2.1",
|
|
52
55
|
"author": "Peter Schären <peter.schaeren@gmail.com>",
|
|
53
56
|
"keywords": "node-red,config-files,config,json",
|
|
54
57
|
"license": "MIT"
|
|
@@ -56,8 +59,8 @@
|
|
|
56
59
|
"color": "#b0c2ba",
|
|
57
60
|
"icon": "node-red/parser-json.svg",
|
|
58
61
|
"status": {
|
|
59
|
-
"x":
|
|
60
|
-
"y":
|
|
62
|
+
"x": 740,
|
|
63
|
+
"y": 160,
|
|
61
64
|
"wires": [
|
|
62
65
|
{
|
|
63
66
|
"id": "73c98834cb90c714",
|
|
@@ -79,7 +82,7 @@
|
|
|
79
82
|
"encoding": "utf8",
|
|
80
83
|
"allProps": false,
|
|
81
84
|
"x": 200,
|
|
82
|
-
"y":
|
|
85
|
+
"y": 280,
|
|
83
86
|
"wires": [
|
|
84
87
|
[
|
|
85
88
|
"f3c260d6c082e422"
|
|
@@ -98,8 +101,8 @@
|
|
|
98
101
|
"stream": false,
|
|
99
102
|
"addname": "",
|
|
100
103
|
"property": "payload",
|
|
101
|
-
"x":
|
|
102
|
-
"y":
|
|
104
|
+
"x": 510,
|
|
105
|
+
"y": 240,
|
|
103
106
|
"wires": [
|
|
104
107
|
[
|
|
105
108
|
"7f1816d008fa2b70",
|
|
@@ -116,7 +119,7 @@
|
|
|
116
119
|
"action": "obj",
|
|
117
120
|
"pretty": false,
|
|
118
121
|
"x": 230,
|
|
119
|
-
"y":
|
|
122
|
+
"y": 320,
|
|
120
123
|
"wires": [
|
|
121
124
|
[
|
|
122
125
|
"86c429715975c781",
|
|
@@ -137,8 +140,8 @@
|
|
|
137
140
|
"msgKeyType": "elem",
|
|
138
141
|
"seqKey": "payload",
|
|
139
142
|
"seqKeyType": "msg",
|
|
140
|
-
"x":
|
|
141
|
-
"y":
|
|
143
|
+
"x": 370,
|
|
144
|
+
"y": 240,
|
|
142
145
|
"wires": [
|
|
143
146
|
[
|
|
144
147
|
"bebafe14f9cc467b"
|
|
@@ -170,23 +173,20 @@
|
|
|
170
173
|
"type": "function",
|
|
171
174
|
"z": "ec796389903c1d0f",
|
|
172
175
|
"name": "node status",
|
|
173
|
-
"func": "const count = Array.isArray(msg.payload) ? msg.payload.length : 0;\
|
|
174
|
-
"outputs":
|
|
176
|
+
"func": "const count = Array.isArray(msg.payload) ? msg.payload.length : 0;\n\nlet statusText = '';\nif (count > 0) {\n statusText = `Found ${count} config files in '${msg.configDir}' with extension '${msg.configFileExt}'`;\n}\nelse {\n statusText = `No config files found in '${msg.configDir}' with extension '${msg.configFileExt}' -> using default config`;\n}\nconst statusMsg = { payload: { fill: \"blue\", shape: \"ring\", text: statusText } };\n\nmsg.configFiles = msg.payload;\n\nreturn [msg, statusMsg];\n",
|
|
177
|
+
"outputs": 2,
|
|
175
178
|
"timeout": 0,
|
|
176
179
|
"noerr": 0,
|
|
177
180
|
"initialize": "",
|
|
178
181
|
"finalize": "",
|
|
179
182
|
"libs": [],
|
|
180
183
|
"x": 210,
|
|
181
|
-
"y":
|
|
184
|
+
"y": 160,
|
|
182
185
|
"wires": [
|
|
183
186
|
[
|
|
184
|
-
"
|
|
187
|
+
"6a7545e5271cd1fa"
|
|
185
188
|
],
|
|
186
|
-
[]
|
|
187
|
-
[
|
|
188
|
-
"cdec17dcc5a199ab"
|
|
189
|
-
]
|
|
189
|
+
[]
|
|
190
190
|
]
|
|
191
191
|
},
|
|
192
192
|
{
|
|
@@ -194,8 +194,8 @@
|
|
|
194
194
|
"type": "function",
|
|
195
195
|
"z": "ec796389903c1d0f",
|
|
196
196
|
"name": "find files",
|
|
197
|
-
"func": "const dir = msg.configDir;\nconst ext = msg.configFileExt\n\
|
|
198
|
-
"outputs":
|
|
197
|
+
"func": "const dir = msg.configDir;\nconst ext = msg.configFileExt\n\n// ---------------------------------------------------------------------------------------\n\nasync function findFiles(dir, ext) {\n try {\n // Check if directory exists and is accessible\n await fs.promises.access(dir);\n\n // Find files\n let filenames = await fs.promises.readdir(dir);\n filenames = filenames.filter(f => path.extname(f) === ext);\n\n // Prepend configDir to filenames\n const filepaths = filenames.map(f => path.join(dir, f));\n return filepaths ?? [];\n\n } catch (err) {\n node.warn(`Directory not found or not accessible: ${dir}`);\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------------------\n\nnode.debug(`Searching files in directory '${dir}' filename extension '${ext}' ...`);\n\nconst filepaths = await findFiles(dir, ext);\n\n// log\nif (filepaths.length > 0) {\n node.debug(`${filepaths.length} files found:`);\n for (let fp of filepaths) {\n node.debug(` ${fp}`);\n }\n}\nelse {\n node.warn(`No config files found in '${dir}' with extension '${ext}' -> using default config`);\n}\n\nmsg.payload = filepaths;\nreturn msg;\n",
|
|
198
|
+
"outputs": 1,
|
|
199
199
|
"timeout": 0,
|
|
200
200
|
"noerr": 0,
|
|
201
201
|
"initialize": "",
|
|
@@ -215,34 +215,15 @@
|
|
|
215
215
|
"wires": [
|
|
216
216
|
[
|
|
217
217
|
"73c98834cb90c714"
|
|
218
|
-
],
|
|
219
|
-
[
|
|
220
|
-
"aa017e7a5d0c867e"
|
|
221
218
|
]
|
|
222
219
|
]
|
|
223
220
|
},
|
|
224
|
-
{
|
|
225
|
-
"id": "cdec17dcc5a199ab",
|
|
226
|
-
"type": "function",
|
|
227
|
-
"z": "ec796389903c1d0f",
|
|
228
|
-
"name": "throw warning",
|
|
229
|
-
"func": "node.warn(msg?.payload);\n",
|
|
230
|
-
"outputs": 0,
|
|
231
|
-
"timeout": 0,
|
|
232
|
-
"noerr": 0,
|
|
233
|
-
"initialize": "",
|
|
234
|
-
"finalize": "",
|
|
235
|
-
"libs": [],
|
|
236
|
-
"x": 420,
|
|
237
|
-
"y": 160,
|
|
238
|
-
"wires": []
|
|
239
|
-
},
|
|
240
221
|
{
|
|
241
222
|
"id": "86c429715975c781",
|
|
242
223
|
"type": "function",
|
|
243
224
|
"z": "ec796389903c1d0f",
|
|
244
225
|
"name": "merge configs",
|
|
245
|
-
"func": "// const _t = global.get('libTracer');\n// _t.init(node, msg, flow.get('enableTracer')); \n\nlet config = flow.get('mergedConfig');\n\nconfig = lodash.merge(config, msg.payload);\n// _t.trace(node, msg, 'config', lodash.cloneDeep(config));\n\n\n// Last config file read?\nif (msg.parts.index == msg.parts.count - 1) {\n msg.topic = 'config';\n msg.payload = config;\n delete msg.parts;\n delete msg.filename;\n\n node.
|
|
226
|
+
"func": "// const _t = global.get('libTracer');\n// _t.init(node, msg, flow.get('enableTracer')); \n\nlet config = flow.get('mergedConfig');\n\nconfig = lodash.merge(config, msg.payload);\n// _t.trace(node, msg, 'config', lodash.cloneDeep(config));\n\n\n// Last config file read?\nif (msg.parts.index == msg.parts.count - 1) {\n msg.topic = 'config';\n msg.payload = config;\n delete msg.parts;\n delete msg.filename;\n\n node.debug(`Merged ALL configs:\\n ${JSON.stringify(msg.payload, null, 2)}`);\n return msg;\n}\n",
|
|
246
227
|
"outputs": 1,
|
|
247
228
|
"timeout": 0,
|
|
248
229
|
"noerr": 0,
|
|
@@ -255,51 +236,80 @@
|
|
|
255
236
|
}
|
|
256
237
|
],
|
|
257
238
|
"x": 220,
|
|
258
|
-
"y":
|
|
239
|
+
"y": 400,
|
|
259
240
|
"wires": [
|
|
260
241
|
[]
|
|
261
242
|
]
|
|
262
243
|
},
|
|
263
244
|
{
|
|
264
|
-
"id": "
|
|
245
|
+
"id": "7f1816d008fa2b70",
|
|
265
246
|
"type": "function",
|
|
266
247
|
"z": "ec796389903c1d0f",
|
|
267
|
-
"name": "
|
|
268
|
-
"func": "node.
|
|
248
|
+
"name": "log",
|
|
249
|
+
"func": "node.debug(`Reading file '${msg.payload}' ...`)\nreturn msg;",
|
|
269
250
|
"outputs": 0,
|
|
270
251
|
"timeout": 0,
|
|
271
252
|
"noerr": 0,
|
|
272
253
|
"initialize": "",
|
|
273
254
|
"finalize": "",
|
|
274
255
|
"libs": [],
|
|
275
|
-
"x":
|
|
276
|
-
"y":
|
|
256
|
+
"x": 650,
|
|
257
|
+
"y": 240,
|
|
277
258
|
"wires": []
|
|
278
259
|
},
|
|
279
260
|
{
|
|
280
|
-
"id": "
|
|
261
|
+
"id": "8639805af947d0c3",
|
|
281
262
|
"type": "function",
|
|
282
263
|
"z": "ec796389903c1d0f",
|
|
283
264
|
"name": "log",
|
|
284
|
-
"func": "node.
|
|
285
|
-
"outputs":
|
|
265
|
+
"func": "node.debug(`Config read from '${msg.filename}':\\n${JSON.stringify(msg.payload, null, 2)}`)\nreturn msg;",
|
|
266
|
+
"outputs": 0,
|
|
286
267
|
"timeout": 0,
|
|
287
268
|
"noerr": 0,
|
|
288
269
|
"initialize": "",
|
|
289
270
|
"finalize": "",
|
|
290
271
|
"libs": [],
|
|
291
|
-
"x":
|
|
292
|
-
"y":
|
|
272
|
+
"x": 650,
|
|
273
|
+
"y": 320,
|
|
274
|
+
"wires": []
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"id": "6a7545e5271cd1fa",
|
|
278
|
+
"type": "switch",
|
|
279
|
+
"z": "ec796389903c1d0f",
|
|
280
|
+
"name": "files found",
|
|
281
|
+
"property": "payload.length",
|
|
282
|
+
"propertyType": "msg",
|
|
283
|
+
"rules": [
|
|
284
|
+
{
|
|
285
|
+
"t": "gt",
|
|
286
|
+
"v": "0",
|
|
287
|
+
"vt": "str"
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"t": "else"
|
|
291
|
+
}
|
|
292
|
+
],
|
|
293
|
+
"checkall": "true",
|
|
294
|
+
"repair": false,
|
|
295
|
+
"outputs": 2,
|
|
296
|
+
"x": 210,
|
|
297
|
+
"y": 240,
|
|
293
298
|
"wires": [
|
|
294
|
-
[
|
|
299
|
+
[
|
|
300
|
+
"e2a872db2d9086e6"
|
|
301
|
+
],
|
|
302
|
+
[
|
|
303
|
+
"efa7ea24e8224b84"
|
|
304
|
+
]
|
|
295
305
|
]
|
|
296
306
|
},
|
|
297
307
|
{
|
|
298
|
-
"id": "
|
|
308
|
+
"id": "efa7ea24e8224b84",
|
|
299
309
|
"type": "function",
|
|
300
310
|
"z": "ec796389903c1d0f",
|
|
301
|
-
"name": "
|
|
302
|
-
"func": "
|
|
311
|
+
"name": "defaultConfig",
|
|
312
|
+
"func": "const defaultConfig = env.get('defaultConfig') ?? {};\n\nmsg.payload = defaultConfig;\nreturn msg;\n",
|
|
303
313
|
"outputs": 1,
|
|
304
314
|
"timeout": 0,
|
|
305
315
|
"noerr": 0,
|
|
@@ -307,10 +317,10 @@
|
|
|
307
317
|
"finalize": "",
|
|
308
318
|
"libs": [],
|
|
309
319
|
"x": 570,
|
|
310
|
-
"y":
|
|
320
|
+
"y": 380,
|
|
311
321
|
"wires": [
|
|
312
322
|
[]
|
|
313
323
|
]
|
|
314
324
|
}
|
|
315
|
-
|
|
325
|
+
]
|
|
316
326
|
}
|