@ralphwetzel/node-red-context-monitor 1.0.0 → 1.1.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/README.md +43 -2
- package/lib/monitor.js +367 -0
- package/monitor.html +53 -7
- package/monitor.js +102 -242
- package/package.json +19 -5
- package/resources/object_monitor.png +0 -0
- package/resources/object_msg.png +0 -0
- package/resources/object_prop.png +0 -0
- package/.github/workflows/npm_publish.yml +0 -33
package/README.md
CHANGED
|
@@ -11,7 +11,14 @@ This node allows to setup the reference to a context, then sends a message when
|
|
|
11
11
|
|
|
12
12
|
It sends a dedicated message on a separate port in case it detects that the value of the context was changed.
|
|
13
13
|
|
|
14
|
-
The message sent will carry the current value of the context as `msg.payload
|
|
14
|
+
The message sent will carry the current value of the context as `msg.payload`, the context key as `msg.topic`.
|
|
15
|
+
|
|
16
|
+
Monitoring details will be provided as `msg.monitoring`:
|
|
17
|
+
* The monitoring setup: `scope` & `key` always, `flow` (id) / `node` (id) if applicable.
|
|
18
|
+
* The id of the node that wrote to the context as `source`.
|
|
19
|
+
|
|
20
|
+
The message sent off the change port carries an additional property in `msg.monitoring`:
|
|
21
|
+
* The value overwritten as `previous`.
|
|
15
22
|
|
|
16
23
|
It is possible to monitor an infinite number of contexts with each instance of this node.
|
|
17
24
|
|
|
@@ -38,4 +45,38 @@ To monitor a `Node` scope context, set the scope to `Node`, then select flow & n
|
|
|
38
45
|
<img alt="node" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/node.png"
|
|
39
46
|
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
40
47
|
|
|
41
|
-
> Hint: This node doesn't create a context. It just tries to reference to those already existing. If you're referencing a non-existing context, no harm will happen.
|
|
48
|
+
> Hint: This node doesn't create a context. It just tries to reference to those already existing. If you're referencing a non-existing context, no harm will happen.
|
|
49
|
+
|
|
50
|
+
### Monitoring objects stored in context
|
|
51
|
+
You may of course define a setup that monitors objects stored in context.
|
|
52
|
+
|
|
53
|
+
If you create a reference to this object (stored in context) and write to its properties, this node issues its messages accordingly.
|
|
54
|
+
|
|
55
|
+
> Disclaimer: Monitoring changes to elements of an `Array` currently is not supported.
|
|
56
|
+
|
|
57
|
+
#### Example:
|
|
58
|
+
Monitoring context definition:
|
|
59
|
+
|
|
60
|
+
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/object_monitor.png"
|
|
61
|
+
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
62
|
+
|
|
63
|
+
Code in a `function` node:
|
|
64
|
+
|
|
65
|
+
``` javascript
|
|
66
|
+
// suppose, test_flow = { prop: "value" }
|
|
67
|
+
let obj = flow.get("test_flow");
|
|
68
|
+
obj.prop = "new";
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Message sent by the node:
|
|
72
|
+
|
|
73
|
+
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/object_msg.png"
|
|
74
|
+
style="min-width: 310px; width: 310px; align: center; border: 1px solid lightgray;"/>
|
|
75
|
+
|
|
76
|
+
#### Object property monitoring
|
|
77
|
+
You may define a setup that doesn't monitor the (whole) object, but only one of its properties:
|
|
78
|
+
|
|
79
|
+
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/object_prop.png"
|
|
80
|
+
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
81
|
+
|
|
82
|
+
Such a monitor will react _only_, when this property and - if it's an object - its child properties are written to.
|
package/lib/monitor.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/*
|
|
2
|
+
node-red-context-monitor by @ralphwetzel
|
|
3
|
+
https://github.com/ralphwetzel/node-red-context-monitor
|
|
4
|
+
License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
let RED;
|
|
8
|
+
let monitors;
|
|
9
|
+
|
|
10
|
+
let init = function(_RED, cache) {
|
|
11
|
+
RED = _RED;
|
|
12
|
+
monitors = cache;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// *****
|
|
16
|
+
// The function to wrap the NR managed context into our monitoring object
|
|
17
|
+
// This cant' be done w/ a simple new Proxy(context, handler) as Proxy ensures that immutable functions
|
|
18
|
+
// stay immutable - which doesn't support our intentions!
|
|
19
|
+
|
|
20
|
+
// node_id: The node_id as passed to context.get
|
|
21
|
+
// flow_id: The flow_id as passed to context.get
|
|
22
|
+
// ctx: the contet as managed by Node-RED
|
|
23
|
+
// root_id (optional): if global/flow context is re-wrapped, this is the (original) node_id
|
|
24
|
+
|
|
25
|
+
let create_wrapper = function(node_id, flow_id, ctx, root_id) {
|
|
26
|
+
|
|
27
|
+
if (!RED || !monitors) {
|
|
28
|
+
console.log("*** Error while loading node-red-context-monitor:");
|
|
29
|
+
console.log("> Wrapper system not initialized.");
|
|
30
|
+
return ctx;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let context = ctx;
|
|
34
|
+
|
|
35
|
+
var context_id = node_id;
|
|
36
|
+
if (flow_id) {
|
|
37
|
+
context_id = node_id + ":" + flow_id;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let obj = {};
|
|
41
|
+
let wrapper = new Proxy(obj, {
|
|
42
|
+
|
|
43
|
+
// *** Those 2 are valid only for function objects
|
|
44
|
+
|
|
45
|
+
// apply: function (target, thisArg, argumentsList) {
|
|
46
|
+
// return Reflect.apply(context, thisArg, argumentsList);
|
|
47
|
+
// },
|
|
48
|
+
// construct: function(target, argumentsList, newTarget) {
|
|
49
|
+
// return Reflect.construct(context, argumentsList, newTarget)
|
|
50
|
+
// },
|
|
51
|
+
|
|
52
|
+
// *** 'defineProperty' must reference the wrapper!
|
|
53
|
+
|
|
54
|
+
// defineProperty: function(target, propertyKey, attributes) {
|
|
55
|
+
// return Reflect.defineProperty(context, propertyKey, attributes);
|
|
56
|
+
// },
|
|
57
|
+
|
|
58
|
+
deleteProperty: function(target, propertyKey) {
|
|
59
|
+
return Reflect.deleteProperty(context, propertyKey);
|
|
60
|
+
},
|
|
61
|
+
get: function (target, propertyKey, receiver) {
|
|
62
|
+
if (["set", "get", "keys"].indexOf(propertyKey) > -1) {
|
|
63
|
+
return target[propertyKey];
|
|
64
|
+
} else if (propertyKey == "flow") {
|
|
65
|
+
// create a wrapper for the flow context
|
|
66
|
+
let flow_context = context.flow;
|
|
67
|
+
if (!flow_context) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
return create_wrapper(flow_id, undefined, flow_context, node_id);
|
|
71
|
+
} else if (propertyKey == "global") {
|
|
72
|
+
// create a wrapper for global context
|
|
73
|
+
let global_context = context.global;
|
|
74
|
+
if (!global_context) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
return create_wrapper('global', undefined, global_context, node_id);
|
|
78
|
+
}
|
|
79
|
+
return Reflect.get(context, propertyKey, receiver);
|
|
80
|
+
},
|
|
81
|
+
getOwnPropertyDescriptor: function (target, propertyKey) {
|
|
82
|
+
return Reflect.getOwnPropertyDescriptor(context, propertyKey);
|
|
83
|
+
},
|
|
84
|
+
getPrototypeOf: function (target){
|
|
85
|
+
Reflect.getPrototypeOf(context);
|
|
86
|
+
},
|
|
87
|
+
has: function (target, propertyKey){
|
|
88
|
+
return Reflect.has(context, propertyKey);
|
|
89
|
+
},
|
|
90
|
+
isExtensible: function (target) {
|
|
91
|
+
return Reflect.isExtensible(context);
|
|
92
|
+
},
|
|
93
|
+
ownKeys: function (target) {
|
|
94
|
+
return Reflect.ownKeys(context);
|
|
95
|
+
},
|
|
96
|
+
preventExtensions: function (target) {
|
|
97
|
+
return Reflect.preventExtensions(context);
|
|
98
|
+
},
|
|
99
|
+
set: function (target, propertyKey, value, receiver) {
|
|
100
|
+
return Reflect.set(context, propertyKey, value, receiver);
|
|
101
|
+
},
|
|
102
|
+
setPrototypeOf: function (target, prototype) {
|
|
103
|
+
return Reflect.setPrototypeOf(context, prototype)
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
Object.defineProperties(wrapper, {
|
|
108
|
+
get: {
|
|
109
|
+
value: function(key, storage, callback) {
|
|
110
|
+
|
|
111
|
+
let create_object_wrapper = function(object, getter_key) {
|
|
112
|
+
|
|
113
|
+
let handler = {
|
|
114
|
+
get: (target, property, receiver) => {
|
|
115
|
+
let getted = Reflect.get(target, property, receiver);
|
|
116
|
+
|
|
117
|
+
// if getted is an object, wrap this (again)
|
|
118
|
+
// to ensure monitoring in case of direct reference access
|
|
119
|
+
if (
|
|
120
|
+
typeof getted === 'object' &&
|
|
121
|
+
!Array.isArray(getted) &&
|
|
122
|
+
getted !== null
|
|
123
|
+
) {
|
|
124
|
+
let prop_chain = property;
|
|
125
|
+
if (getter_key?.length) {
|
|
126
|
+
prop_chain = getter_key + "." + prop_chain;
|
|
127
|
+
}
|
|
128
|
+
return create_object_wrapper(getted, prop_chain);
|
|
129
|
+
}
|
|
130
|
+
return getted;
|
|
131
|
+
},
|
|
132
|
+
set: function(target, propertyKey, value, receiver) {
|
|
133
|
+
// this is the monitoring function!
|
|
134
|
+
let previous_value = Reflect.get(target, propertyKey, receiver);
|
|
135
|
+
res = Reflect.set(target, propertyKey, value, receiver);
|
|
136
|
+
|
|
137
|
+
let prop_chain = propertyKey;
|
|
138
|
+
if (getter_key?.length) {
|
|
139
|
+
prop_chain = getter_key + "." + prop_chain;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
trigger(root_id ?? node_id, context_id, key, value, previous_value, prop_chain);
|
|
143
|
+
return res;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return new Proxy(object, handler);
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let create_array_wrapper = function(object, getter_key) {
|
|
152
|
+
|
|
153
|
+
let handler = {
|
|
154
|
+
get: (target, property, receiver) => {
|
|
155
|
+
let getted = Reflect.get(target, property, receiver);
|
|
156
|
+
|
|
157
|
+
if (
|
|
158
|
+
typeof getted === 'object' &&
|
|
159
|
+
Array.isArray(getted)
|
|
160
|
+
) {
|
|
161
|
+
let prop_chain = property;
|
|
162
|
+
if (getter_key?.length) {
|
|
163
|
+
prop_chain = getter_key + "." + prop_chain;
|
|
164
|
+
}
|
|
165
|
+
return create_object_wrapper(getted, prop_chain);
|
|
166
|
+
}
|
|
167
|
+
return getted;
|
|
168
|
+
},
|
|
169
|
+
set: function(target, propertyKey, value, receiver) {
|
|
170
|
+
// this is the monitoring function!
|
|
171
|
+
let previous_value = Reflect.get(target, propertyKey, receiver);
|
|
172
|
+
res = Reflect.set(target, propertyKey, value, receiver);
|
|
173
|
+
|
|
174
|
+
let prop_chain = propertyKey;
|
|
175
|
+
if (getter_key?.length) {
|
|
176
|
+
prop_chain = getter_key + "." + prop_chain;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
trigger(root_id ?? node_id, context_id, key, value, previous_value, prop_chain);
|
|
180
|
+
return res;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return new Proxy(object, handler);
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let apply_wrapper = function (getted) {
|
|
189
|
+
if (
|
|
190
|
+
typeof getted === 'object' &&
|
|
191
|
+
!Array.isArray(getted) &&
|
|
192
|
+
getted !== null
|
|
193
|
+
) {
|
|
194
|
+
return create_object_wrapper(getted);
|
|
195
|
+
}
|
|
196
|
+
return getted;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!callback && typeof storage === 'function') {
|
|
200
|
+
callback = storage;
|
|
201
|
+
storage = undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (callback) {
|
|
205
|
+
|
|
206
|
+
if (typeof callback !== 'function'){
|
|
207
|
+
throw new Error("Callback must be a function");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let callback_wrapper = function(err, value) {
|
|
211
|
+
let result = apply_wrapper(value);
|
|
212
|
+
callback(err, result);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
context.get(key, storage, callback_wrapper);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// be explicit: no (!!) callback here
|
|
220
|
+
let getted_value = context.get(key, storage);
|
|
221
|
+
return apply_wrapper(getted_value);
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
set: {
|
|
226
|
+
value: function(key, value, storage, callback) {
|
|
227
|
+
|
|
228
|
+
// this is the monitoring function!
|
|
229
|
+
let previous_value = context.get(key, storage);
|
|
230
|
+
|
|
231
|
+
if (!callback && typeof storage === 'function') {
|
|
232
|
+
callback = storage;
|
|
233
|
+
storage = undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (callback) {
|
|
237
|
+
|
|
238
|
+
if (typeof callback !== 'function'){
|
|
239
|
+
throw new Error("Callback must be a function");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let callback_wrapper = function(err, res) {
|
|
243
|
+
// intercept the callback & report success!
|
|
244
|
+
if (!err) {
|
|
245
|
+
trigger(root_id ?? node_id, context_id, key, value, previous_value);
|
|
246
|
+
}
|
|
247
|
+
return callback(err, res);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return context.set(key, value, storage, callback_wrapper);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let res = context.set(key, value, storage);
|
|
254
|
+
trigger(root_id ?? node_id, context_id, key, value, previous_value);
|
|
255
|
+
return res;
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
keys: {
|
|
259
|
+
value: function(storage, callback) {
|
|
260
|
+
return context.keys(storage, callback);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return wrapper;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// *****
|
|
269
|
+
// The function to trigger the context-monitoring nodes
|
|
270
|
+
|
|
271
|
+
// root_id: The node.id that wrote to the context
|
|
272
|
+
// context_id: The context (id) that was written to
|
|
273
|
+
// key: The key (in context) that ws written to
|
|
274
|
+
// new_value: The value written
|
|
275
|
+
// previous_value: The value over-written
|
|
276
|
+
// prop_chain: In case the context is a (nested) object, the sequence of property keys.
|
|
277
|
+
|
|
278
|
+
let trigger = function(root_id, context_id, key, new_value, previous_value, prop_chain) {
|
|
279
|
+
|
|
280
|
+
function trigger_receivers(monitoring_nodes, message) {
|
|
281
|
+
monitoring_nodes.forEach(node => {
|
|
282
|
+
let n = RED.nodes.getNode(node.id);
|
|
283
|
+
if (n) {
|
|
284
|
+
|
|
285
|
+
let msg = RED.util.cloneMessage(message);
|
|
286
|
+
|
|
287
|
+
msg.monitoring = {
|
|
288
|
+
"scope": node.data.scope,
|
|
289
|
+
"source": root_id
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
switch (node.data.scope) {
|
|
293
|
+
case "global":
|
|
294
|
+
msg.monitoring.key = node.data.key;
|
|
295
|
+
break;
|
|
296
|
+
case "flow":
|
|
297
|
+
msg.monitoring.flow = node.data.flow;
|
|
298
|
+
msg.monitoring.key = node.data.key;
|
|
299
|
+
break;
|
|
300
|
+
case "node":
|
|
301
|
+
msg.monitoring.flow = node.data.flow;
|
|
302
|
+
msg.monitoring.node = node.data.node;
|
|
303
|
+
msg.monitoring.key = node.data.key;
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
n.receive(msg);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let kc = key;
|
|
313
|
+
if (prop_chain?.length) {
|
|
314
|
+
kc += "." + prop_chain;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let keys = [];
|
|
318
|
+
try {
|
|
319
|
+
keys = RED.util.normalisePropertyExpression(kc);
|
|
320
|
+
} catch {}
|
|
321
|
+
|
|
322
|
+
for (let i=keys.length; i>0; i--) {
|
|
323
|
+
|
|
324
|
+
let cidk = context_id + ":" + keys.slice(0,i).join(".");
|
|
325
|
+
let mons = monitors[cidk] ?? [];
|
|
326
|
+
|
|
327
|
+
if (mons.length) {
|
|
328
|
+
let msg = {
|
|
329
|
+
payload: new_value,
|
|
330
|
+
previous: previous_value,
|
|
331
|
+
// do this once already here!
|
|
332
|
+
// ToDo: For non primitives: run a fast comparisor
|
|
333
|
+
changed: new_value != previous_value
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
msg.topic = kc;
|
|
337
|
+
|
|
338
|
+
trigger_receivers(mons, msg);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// End: The function ....
|
|
344
|
+
// *****
|
|
345
|
+
|
|
346
|
+
module.exports = {
|
|
347
|
+
init: init,
|
|
348
|
+
create_wrapper: create_wrapper
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (process.env.TESTING_CONTEXT_MONITOR) {
|
|
352
|
+
|
|
353
|
+
module.exports["trace"] = function(id) {
|
|
354
|
+
|
|
355
|
+
if (!RED || !monitors) {
|
|
356
|
+
console.log("*** Error while testing node-red-context-monitor:");
|
|
357
|
+
console.log("> Wrapper system not initialized.");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (id) {
|
|
362
|
+
return monitors[id];
|
|
363
|
+
}
|
|
364
|
+
return monitors;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
package/monitor.html
CHANGED
|
@@ -5,12 +5,47 @@
|
|
|
5
5
|
-->
|
|
6
6
|
|
|
7
7
|
<script type="text/javascript">
|
|
8
|
+
|
|
9
|
+
function validate_context_key(key) {
|
|
10
|
+
try {
|
|
11
|
+
var parts = RED.utils.normalisePropertyExpression(key);
|
|
12
|
+
|
|
13
|
+
// If there's a changable part (e.g. test[msg.payload])
|
|
14
|
+
// this will become an array type in parts.
|
|
15
|
+
// Those might be ok for other situtions, but not here!
|
|
16
|
+
for (i=0; i<parts.length; i++) {
|
|
17
|
+
let p = parts[i];
|
|
18
|
+
if (Array.isArray(p)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
RED.nodes.registerType('context-monitor',{
|
|
9
29
|
category: 'input',
|
|
10
30
|
color: '#b0b0b0',
|
|
11
31
|
defaults: {
|
|
12
32
|
name: {value:""},
|
|
13
|
-
monitoring: {
|
|
33
|
+
monitoring: {
|
|
34
|
+
value: [],
|
|
35
|
+
validate: function(scopes, opt) {
|
|
36
|
+
let msg;
|
|
37
|
+
if (!scopes || scopes.length === 0) { return true }
|
|
38
|
+
for (let i=0; i<scopes.length; i++) {
|
|
39
|
+
let scope = scopes[i];
|
|
40
|
+
if (!validate_context_key(scope.key)) {
|
|
41
|
+
return RED._("node-red:change.errors.invalid-prop", {
|
|
42
|
+
property: scope.key
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
14
49
|
},
|
|
15
50
|
inputs: 0,
|
|
16
51
|
outputs: 2,
|
|
@@ -41,7 +76,8 @@
|
|
|
41
76
|
function add_context(tpl) {
|
|
42
77
|
let c = {
|
|
43
78
|
"scope": tpl.scope ?? "global",
|
|
44
|
-
"flow"
|
|
79
|
+
// decode "this flow" marker
|
|
80
|
+
"flow": ("." === tpl.flow) ? node.z : tpl.flow,
|
|
45
81
|
"node": tpl.node,
|
|
46
82
|
"key": tpl.key ?? ""
|
|
47
83
|
}
|
|
@@ -157,8 +193,8 @@
|
|
|
157
193
|
});
|
|
158
194
|
|
|
159
195
|
let flow_opts = [];
|
|
160
|
-
let group_opts = [];
|
|
161
|
-
let node_opts
|
|
196
|
+
// let group_opts = [];
|
|
197
|
+
let node_opts;
|
|
162
198
|
|
|
163
199
|
RED.nodes.eachWorkspace( cb => {
|
|
164
200
|
flow_opts.push({
|
|
@@ -194,7 +230,8 @@
|
|
|
194
230
|
// if ($(`#context-scope-${index}`).typedInput("value") == "group") {
|
|
195
231
|
// $(`#context-scope-key-${index}`).prop("disabled", gol < 1);
|
|
196
232
|
// }
|
|
197
|
-
|
|
233
|
+
|
|
234
|
+
node_opts = [];
|
|
198
235
|
RED.nodes.eachNode( n => {
|
|
199
236
|
|
|
200
237
|
if (n.z == value) {
|
|
@@ -271,6 +308,11 @@
|
|
|
271
308
|
data.key = $(this).val();
|
|
272
309
|
node.dirty = true;
|
|
273
310
|
}
|
|
311
|
+
$(this).toggleClass("input-error", !validate_context_key($(this).val()));
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
$(`#context-scope-key-${index}`).on( "input", function () {
|
|
315
|
+
$(this).toggleClass("input-error", !validate_context_key($(this).val()));
|
|
274
316
|
});
|
|
275
317
|
|
|
276
318
|
// initialize the form
|
|
@@ -303,7 +345,7 @@
|
|
|
303
345
|
}
|
|
304
346
|
}
|
|
305
347
|
|
|
306
|
-
$(`#context-scope-key-${index}`).val(data.key);
|
|
348
|
+
$(`#context-scope-key-${index}`).val(data.key).toggleClass("input-error", !validate_context_key(data.key));
|
|
307
349
|
|
|
308
350
|
_initing = false;
|
|
309
351
|
},
|
|
@@ -379,8 +421,12 @@
|
|
|
379
421
|
// break;
|
|
380
422
|
case "node":
|
|
381
423
|
delete data.group;
|
|
382
|
-
|
|
424
|
+
}
|
|
383
425
|
|
|
426
|
+
if (data.flow == node.z) {
|
|
427
|
+
// set a special marker that 'this flow' shall be referenced
|
|
428
|
+
data.flow = ".";
|
|
429
|
+
}
|
|
384
430
|
})
|
|
385
431
|
|
|
386
432
|
node.monitoring = ctx;
|
package/monitor.js
CHANGED
|
@@ -5,270 +5,100 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const fs = require('fs-extra');
|
|
8
|
-
const os = require("os");
|
|
9
8
|
const path = require("path");
|
|
10
|
-
|
|
11
|
-
let error_header = "*** Error while loading node-red-context-monitor:";
|
|
9
|
+
const monitor = require('./lib/monitor.js');
|
|
12
10
|
|
|
13
11
|
// this used to be the cache of ctx triggered @ set
|
|
14
12
|
// ... that's why it's the set_cache!
|
|
15
13
|
let set_cache = {};
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
// *****
|
|
16
|
+
// Patch support: Scan the require database for the path to a to-be-required file
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
let require_cache = {
|
|
19
|
+
".": require.main
|
|
20
|
+
}
|
|
22
21
|
|
|
23
|
-
function
|
|
22
|
+
function scan_for_require_path(req_path) {
|
|
24
23
|
|
|
25
|
-
let
|
|
24
|
+
let found;
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return;
|
|
26
|
+
if (process.env.NODE_RED_HOME) {
|
|
27
|
+
found = path.join(process.env.NODE_RED_HOME, "..", req_path);
|
|
28
|
+
console.log("@f", found);
|
|
29
|
+
if (fs.existsSync(found)) {
|
|
30
|
+
return found;
|
|
33
31
|
}
|
|
34
|
-
} catch (err) {
|
|
35
|
-
console.log(err);
|
|
36
|
-
console.log(error_header);
|
|
37
|
-
if (err.code == 'ENOENT') {
|
|
38
|
-
console.log("require.main.path not found.");
|
|
39
|
-
} else {
|
|
40
|
-
console.log("Error while handling require.main.path.")
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
32
|
}
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
rm = path.normalize(rm);
|
|
47
|
-
let rms = []
|
|
48
|
-
let rmp;
|
|
49
|
-
do {
|
|
50
|
-
rmp = path.parse(rm);
|
|
51
|
-
if (rmp.base.length > 0) {
|
|
52
|
-
rms.unshift(rmp.base);
|
|
53
|
-
rm = rmp.dir;
|
|
54
|
-
}
|
|
55
|
-
} while (rmp.base.length > 0)
|
|
34
|
+
let runner = 0;
|
|
56
35
|
|
|
57
|
-
|
|
36
|
+
while (runner < Object.keys(require_cache).length) {
|
|
58
37
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// dev: [...]/node-red/packages/node_modules/node-red
|
|
62
|
-
// install: [...]/lib/node_modules/node-red
|
|
63
|
-
// pi: /lib/node_modules/node-red/
|
|
38
|
+
let key = Object.keys(require_cache)[runner];
|
|
39
|
+
let entry = require_cache[key];
|
|
64
40
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
rms.splice(-2);
|
|
41
|
+
if (entry.id?.includes(req_path)) {
|
|
42
|
+
found = entry.id;
|
|
43
|
+
break;
|
|
69
44
|
}
|
|
45
|
+
|
|
46
|
+
let cc = entry.children;
|
|
47
|
+
cc.forEach(c => {
|
|
48
|
+
if (!(c.id in require_cache)) {
|
|
49
|
+
require_cache[c.id] = c;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
runner += 1;
|
|
70
54
|
}
|
|
71
55
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
console.log("> utils.js:", p);
|
|
82
|
-
return null;
|
|
56
|
+
if (found) {
|
|
57
|
+
if (!fs.existsSync(found)) {
|
|
58
|
+
console.log("*** Error while loading node-red-context-monitor:")
|
|
59
|
+
console.log("Failed to calculate path to required file.");
|
|
60
|
+
console.log("Please raise an issue @ our GitHub repository, stating the following information:");
|
|
61
|
+
console.log("> scanned for:", req_path);
|
|
62
|
+
console.log("> found:", found);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
83
65
|
}
|
|
84
66
|
|
|
85
|
-
|
|
67
|
+
console.log(found);
|
|
68
|
+
return found;
|
|
86
69
|
}
|
|
87
70
|
|
|
88
71
|
// End: "Patch support ..."
|
|
89
72
|
// *****
|
|
90
73
|
|
|
91
74
|
// *****
|
|
92
|
-
// Make available the Context Manager
|
|
75
|
+
// Make available & patch the Context Manager
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
if (!context_manager_path) return;
|
|
96
|
-
const context_manager = require(context_manager_path);
|
|
77
|
+
let context_manager;
|
|
97
78
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
// stay immutable - which doesn't support our intentions!
|
|
79
|
+
// When running a test, NODE_RED_HOME is not defined.
|
|
80
|
+
if (process.env.NODE_RED_HOME && !process.env.TESTING_CONTEXT_MONITOR) {
|
|
101
81
|
|
|
102
|
-
let
|
|
82
|
+
let context_manager_path = scan_for_require_path("@node-red/runtime/lib/nodes/context");
|
|
83
|
+
if (context_manager_path) {
|
|
103
84
|
|
|
104
|
-
|
|
85
|
+
context_manager = require(context_manager_path);
|
|
105
86
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
let obj = {};
|
|
112
|
-
let wrapper = new Proxy(obj, {
|
|
113
|
-
|
|
114
|
-
// *** Those 2 are valid only for function objects
|
|
115
|
-
|
|
116
|
-
// apply: function (target, thisArg, argumentsList) {
|
|
117
|
-
// return Reflect.apply(context, thisArg, argumentsList);
|
|
118
|
-
// },
|
|
119
|
-
// construct: function(target, argumentsList, newTarget) {
|
|
120
|
-
// return Reflect.construct(context, argumentsList, newTarget)
|
|
121
|
-
// },
|
|
122
|
-
|
|
123
|
-
// *** 'defineProperty' must reference the wrapper!
|
|
124
|
-
|
|
125
|
-
// defineProperty: function(target, propertyKey, attributes) {
|
|
126
|
-
// return Reflect.defineProperty(context, propertyKey, attributes);
|
|
127
|
-
// },
|
|
128
|
-
|
|
129
|
-
deleteProperty: function(target, propertyKey) {
|
|
130
|
-
return Reflect.deleteProperty(context, propertyKey);
|
|
131
|
-
},
|
|
132
|
-
get: function (target, propertyKey, receiver) {
|
|
133
|
-
if (["set", "get", "keys"].indexOf(propertyKey) > -1) {
|
|
134
|
-
return target[propertyKey];
|
|
135
|
-
} else if (propertyKey == "flow") {
|
|
136
|
-
// create a wrapper for the flow context
|
|
137
|
-
let flow_context = context.flow;
|
|
138
|
-
if (!flow_context) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
return create_wrapper(flow_id, undefined, flow_context);
|
|
142
|
-
} else if (propertyKey == "global") {
|
|
143
|
-
// create a wrapper for global context
|
|
144
|
-
let global_context = context.global;
|
|
145
|
-
if (!global_context) {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
return create_wrapper('global', undefined, global_context);
|
|
149
|
-
}
|
|
150
|
-
return Reflect.get(context, propertyKey, receiver);
|
|
151
|
-
},
|
|
152
|
-
getOwnPropertyDescriptor: function (target, propertyKey) {
|
|
153
|
-
return Reflect.getOwnPropertyDescriptor(context, propertyKey);
|
|
154
|
-
},
|
|
155
|
-
getPrototypeOf: function (target){
|
|
156
|
-
Reflect.getPrototypeOf(context);
|
|
157
|
-
},
|
|
158
|
-
has: function (target, propertyKey){
|
|
159
|
-
return Reflect.has(context, propertyKey);
|
|
160
|
-
},
|
|
161
|
-
isExtensible: function (target) {
|
|
162
|
-
return Reflect.isExtensible(context);
|
|
163
|
-
},
|
|
164
|
-
ownKeys: function (target) {
|
|
165
|
-
return Reflect.ownKeys(context);
|
|
166
|
-
},
|
|
167
|
-
preventExtensions: function (target) {
|
|
168
|
-
return Reflect.preventExtensions(context);
|
|
169
|
-
},
|
|
170
|
-
set: function (target, propertyKey, value, receiver) {
|
|
171
|
-
return Reflect.set(context, propertyKey, value, receiver);
|
|
172
|
-
},
|
|
173
|
-
setPrototypeOf: function (target, prototype) {
|
|
174
|
-
return Reflect.setPrototypeOf(context, prototype)
|
|
87
|
+
// patching into getContext (exported as 'get')
|
|
88
|
+
const orig_context_get = context_manager.get;
|
|
89
|
+
context_manager.get = function(nodeId, flowId) {
|
|
90
|
+
let context = orig_context_get(nodeId, flowId);
|
|
91
|
+
return monitor.create_wrapper(nodeId, flowId, context);
|
|
175
92
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
set: {
|
|
185
|
-
value: function(key, value, storage, callback) {
|
|
186
|
-
// this is the monitoring function!
|
|
187
|
-
let previous_value = context.get(key, storage);
|
|
188
|
-
let res = context.set(key, value, storage, callback);
|
|
189
|
-
trigger(context_id + ":" + key, value, previous_value);
|
|
190
|
-
return res;
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
keys: {
|
|
194
|
-
value: function(storage, callback) {
|
|
195
|
-
return context.keys(storage, callback);
|
|
196
|
-
}
|
|
93
|
+
|
|
94
|
+
// patching into getFlowContext
|
|
95
|
+
const orig_get_flow_context = context_manager.getFlowContext;
|
|
96
|
+
context_manager.getFlowContext = function(flowId, parentFlowId) {
|
|
97
|
+
let flow_context = orig_get_flow_context(flowId, parentFlowId);
|
|
98
|
+
return monitor.create_wrapper(flowId, undefined, flow_context);
|
|
197
99
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return wrapper;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// *****
|
|
204
|
-
// The function to trigger the context-monitoring nodes
|
|
205
|
-
|
|
206
|
-
let trigger = function(context_key_id, new_value, previous_value) {
|
|
207
|
-
|
|
208
|
-
function trigger_receivers(cache, message) {
|
|
209
|
-
cache.forEach(node => {
|
|
210
|
-
let n = _RED.nodes.getNode(node.id);
|
|
211
|
-
if (n) {
|
|
212
|
-
|
|
213
|
-
let msg = _RED.util.cloneMessage(message);
|
|
214
|
-
|
|
215
|
-
msg.topic = node.data.key;
|
|
216
|
-
msg.monitoring = {
|
|
217
|
-
"scope": node.data.scope,
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
switch (node.data.scope) {
|
|
221
|
-
case "global":
|
|
222
|
-
msg.monitoring.key = node.data.key;
|
|
223
|
-
break;
|
|
224
|
-
case "flow":
|
|
225
|
-
msg.monitoring.flow = node.data.flow;
|
|
226
|
-
msg.monitoring.key = node.data.key;
|
|
227
|
-
break;
|
|
228
|
-
case "node":
|
|
229
|
-
msg.monitoring.flow = node.data.flow;
|
|
230
|
-
msg.monitoring.node = node.data.node;
|
|
231
|
-
msg.monitoring.key = node.data.key;
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
n.receive(msg);
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
let cache = set_cache[context_key_id] ?? [];
|
|
241
|
-
let msg = {
|
|
242
|
-
payload: new_value,
|
|
243
|
-
previous: previous_value
|
|
100
|
+
|
|
244
101
|
}
|
|
245
|
-
trigger_receivers(cache, msg);
|
|
246
|
-
|
|
247
|
-
// if (new_value !== previous_value) {
|
|
248
|
-
// let cache = change_cache[context_key_id] ?? [];
|
|
249
|
-
// let msg = {
|
|
250
|
-
// payload: new_value,
|
|
251
|
-
// previous: previous_value
|
|
252
|
-
// }
|
|
253
|
-
// trigger_receivers(cache, msg);
|
|
254
|
-
// }
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// End: The function ....
|
|
258
|
-
// *****
|
|
259
|
-
|
|
260
|
-
// patching into getContext (exported as 'get')
|
|
261
|
-
const orig_context_get = context_manager.get;
|
|
262
|
-
context_manager.get = function(nodeId, flowId) {
|
|
263
|
-
let context = orig_context_get(nodeId, flowId);
|
|
264
|
-
return create_wrapper(nodeId, flowId, context);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// patching into getFlowContext
|
|
268
|
-
const orig_get_flow_context = context_manager.getFlowContext;
|
|
269
|
-
context_manager.getFlowContext = function(flowId, parentFlowId) {
|
|
270
|
-
let flow_context = orig_get_flow_context(flowId, parentFlowId);
|
|
271
|
-
return create_wrapper(flowId, undefined, flow_context);
|
|
272
102
|
}
|
|
273
103
|
|
|
274
104
|
// End: "Make available ..."
|
|
@@ -279,31 +109,53 @@ context_manager.getFlowContext = function(flowId, parentFlowId) {
|
|
|
279
109
|
|
|
280
110
|
module.exports = function(RED) {
|
|
281
111
|
|
|
282
|
-
//
|
|
283
|
-
// necessary for trigger function to map node_id -> node object.
|
|
284
|
-
|
|
112
|
+
// Catch RED here & provide it to the monitor!
|
|
113
|
+
// It's necessary for trigger function to map node_id -> node object.
|
|
114
|
+
monitor.init(RED, set_cache);
|
|
285
115
|
|
|
286
116
|
function ContextMonitor(config) {
|
|
287
117
|
RED.nodes.createNode(this,config);
|
|
288
118
|
var node = this;
|
|
289
119
|
|
|
290
|
-
|
|
120
|
+
let scopes = config.monitoring ?? [];
|
|
121
|
+
node.data = scopes;
|
|
122
|
+
|
|
291
123
|
node.monitoring = [];
|
|
292
124
|
|
|
293
|
-
|
|
125
|
+
scopes.forEach( data => {
|
|
294
126
|
|
|
295
127
|
if (!data.key) return;
|
|
296
128
|
|
|
129
|
+
// support for complex keys
|
|
130
|
+
// test["mm"].value becomes test.mm.value
|
|
131
|
+
let key = data.key;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
let key_parts = RED.util.normalisePropertyExpression(key);
|
|
135
|
+
for (i=0; i<key_parts.length; i++) {
|
|
136
|
+
if (Array.isArray(key_parts[i])) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
key = key_parts.join('.');
|
|
141
|
+
} catch {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if ("." === data.flow) {
|
|
146
|
+
data.flow = node.z;
|
|
147
|
+
}
|
|
148
|
+
|
|
297
149
|
let ctx = "global";
|
|
298
150
|
switch (data.scope) {
|
|
299
151
|
case "global":
|
|
300
|
-
ctx = `global:${
|
|
152
|
+
ctx = `global:${key}`;
|
|
301
153
|
break;
|
|
302
154
|
case "flow":
|
|
303
|
-
ctx = `${data.flow}:${
|
|
155
|
+
ctx = `${data.flow}:${key}`;
|
|
304
156
|
break;
|
|
305
157
|
case "node":
|
|
306
|
-
ctx = `${data.node}:${data.flow}:${
|
|
158
|
+
ctx = `${data.node}:${data.flow}:${key}`;
|
|
307
159
|
break;
|
|
308
160
|
default:
|
|
309
161
|
return;
|
|
@@ -325,9 +177,11 @@ module.exports = function(RED) {
|
|
|
325
177
|
|
|
326
178
|
// unfold & check if changed
|
|
327
179
|
let prev = msg.previous;
|
|
180
|
+
let changed = msg.changed;
|
|
328
181
|
delete msg.previous;
|
|
182
|
+
delete msg.changed;
|
|
329
183
|
|
|
330
|
-
if (
|
|
184
|
+
if (changed) {
|
|
331
185
|
// if changed, clone & emit @ second output terminal
|
|
332
186
|
let m = RED.util.cloneMessage(msg);
|
|
333
187
|
delete m._msgid;
|
|
@@ -343,12 +197,18 @@ module.exports = function(RED) {
|
|
|
343
197
|
node.on("close",function() {
|
|
344
198
|
// remove this nodes ctx(s) from the trigger list
|
|
345
199
|
node.monitoring.forEach( ctx => {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
200
|
+
let sc = set_cache[ctx];
|
|
201
|
+
if (sc) {
|
|
202
|
+
set_cache[ctx] = sc.filter( n => {
|
|
203
|
+
return n.id !== node.id;
|
|
204
|
+
})
|
|
205
|
+
if (set_cache[ctx].length < 1) {
|
|
206
|
+
delete set_cache[ctx];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
349
209
|
})
|
|
350
210
|
});
|
|
351
211
|
}
|
|
352
212
|
|
|
353
213
|
RED.nodes.registerType("context-monitor",ContextMonitor);
|
|
354
|
-
}
|
|
214
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ralphwetzel/node-red-context-monitor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A Node-RED node to monitor a context.",
|
|
5
5
|
"main": "monitor.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "mocha \"test/**/*_spec.js\""
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -27,9 +27,23 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/ralphwetzel/node-red-context-monitor#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"fs-extra": "^11.1.1"
|
|
30
|
+
"fs-extra": "^11.1.1",
|
|
31
|
+
"node-red": "^3.1.0"
|
|
31
32
|
},
|
|
32
33
|
"engines": {
|
|
33
|
-
"node": ">=
|
|
34
|
-
}
|
|
34
|
+
"node": ">=14.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"mocha": "^10.2.0",
|
|
38
|
+
"node-red-node-test-helper": "^0.3.2"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"/examples",
|
|
42
|
+
"/lib",
|
|
43
|
+
"/resources",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"monitor.*",
|
|
46
|
+
"package.json",
|
|
47
|
+
"README.md"
|
|
48
|
+
]
|
|
35
49
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
-
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
|
|
3
|
-
|
|
4
|
-
name: "NPM Publish"
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
release:
|
|
8
|
-
types: [created]
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
build:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v3
|
|
15
|
-
- uses: actions/setup-node@v3
|
|
16
|
-
with:
|
|
17
|
-
node-version: 16
|
|
18
|
-
# - run: npm ci
|
|
19
|
-
# - run: npm test
|
|
20
|
-
|
|
21
|
-
publish-npm:
|
|
22
|
-
needs: build
|
|
23
|
-
runs-on: ubuntu-latest
|
|
24
|
-
steps:
|
|
25
|
-
- uses: actions/checkout@v3
|
|
26
|
-
- uses: actions/setup-node@v3
|
|
27
|
-
with:
|
|
28
|
-
node-version: 16
|
|
29
|
-
registry-url: https://registry.npmjs.org/
|
|
30
|
-
# - run: npm ci
|
|
31
|
-
- run: npm publish --access public
|
|
32
|
-
env:
|
|
33
|
-
NODE_AUTH_TOKEN: ${{secrets.npm_access_token}}
|