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 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 the resulting configuration object.
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 with the input message.
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` property can be used to define configuration values that are missing in the files.
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, a node error is throw. This error can be received with a `catch` node, for example.
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 is created from a subflow.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-config-files",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Read and merge configuration files (JSON).",
5
5
  "main": "install.js",
6
6
  "scripts": {
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).\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\n### Details\nThe node pproperty `defaultConfig` can be used to define configuration \nvalues that 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",
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": 660,
21
- "y": 300,
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.0",
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": 660,
60
- "y": 140,
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": 300,
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": 390,
102
- "y": 260,
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": 340,
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": 190,
141
- "y": 260,
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;\nlet errorMsg = null;\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 errorMsg = { payload: statusText };\n}\nconst statusMsg = { payload: { fill: \"blue\", shape: \"ring\", text: statusText } };\n\nmsg.configFiles = msg.payload;\n\nreturn [msg, statusMsg, errorMsg];\n",
174
- "outputs": 3,
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": 180,
184
+ "y": 160,
182
185
  "wires": [
183
186
  [
184
- "e2a872db2d9086e6"
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\nlet error = null;\n\n// ---------------------------------------------------------------------------------------\n\nasync function findFiles(dir, ext) {\n try {\n await fs.promises.access(dir); // throws if not accessible (e.g., doesn't exist)\n let filenames = await fs.promises.readdir(dir);\n filenames = filenames.filter(f => path.extname(f) === ext);\n\n const filepaths = filenames.map(f => path.join(dir, f));\n return filepaths;\n\n } catch (err) {\n error = `Directory not accessible: ${err.message}`;\n console.error('Directory not accessible:', err.message);\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------------------\n\nnode.log(`Searching files in directory '${dir}' with filename extension '${ext}' ...`);\n\nconst filepaths = await findFiles(dir, ext);\n\nnode.log(`Files found:`);\n\nfor (let fp of filepaths) {\n node.log(` ${fp}`);\n}\n\nmsg.payload = filepaths;\nconst errorMsg = error ? { payload: error } : null;\nreturn [msg, errorMsg];\n",
198
- "outputs": 2,
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.log(`Merged ALL configs:\\n ${JSON.stringify(msg.payload, null, 2)}`);\n return msg;\n}\n",
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": 420,
239
+ "y": 400,
259
240
  "wires": [
260
241
  []
261
242
  ]
262
243
  },
263
244
  {
264
- "id": "aa017e7a5d0c867e",
245
+ "id": "7f1816d008fa2b70",
265
246
  "type": "function",
266
247
  "z": "ec796389903c1d0f",
267
- "name": "throw error",
268
- "func": "node.error(msg?.payload, msg);\n",
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": 410,
276
- "y": 120,
256
+ "x": 650,
257
+ "y": 240,
277
258
  "wires": []
278
259
  },
279
260
  {
280
- "id": "7f1816d008fa2b70",
261
+ "id": "8639805af947d0c3",
281
262
  "type": "function",
282
263
  "z": "ec796389903c1d0f",
283
264
  "name": "log",
284
- "func": "node.log(`Reading file '${msg.payload}' ...`)\nreturn msg;",
285
- "outputs": 1,
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": 570,
292
- "y": 260,
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": "8639805af947d0c3",
308
+ "id": "efa7ea24e8224b84",
299
309
  "type": "function",
300
310
  "z": "ec796389903c1d0f",
301
- "name": "log",
302
- "func": "node.log(`Config read from '${msg.filename}':\\n${JSON.stringify(msg.payload, null, 2)}`)\nreturn msg;",
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": 340,
320
+ "y": 380,
311
321
  "wires": [
312
322
  []
313
323
  ]
314
324
  }
315
- ]
325
+ ]
316
326
  }