@node-red/runtime 3.1.0-beta.2 → 3.1.0-beta.4

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/lib/flows/util.js CHANGED
@@ -13,23 +13,32 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  **/
16
- var clone = require("clone");
17
- var redUtil = require("@node-red/util").util;
18
- var Log = require("@node-red/util").log;
19
- var subflowInstanceRE = /^subflow:(.+)$/;
20
- var typeRegistry = require("@node-red/registry");
21
- const credentials = require("../nodes/credentials");
16
+ const clone = require("clone");
17
+ const redUtil = require("@node-red/util").util;
18
+ const Log = require("@node-red/util").log;
19
+ const typeRegistry = require("@node-red/registry");
20
+ const subflowInstanceRE = /^subflow:(.+)$/;
22
21
 
23
22
  let _runtime = null;
23
+ let envVarExcludes = {};
24
24
 
25
- var envVarExcludes = {};
25
+ function init(runtime) {
26
+ _runtime = runtime;
27
+ envVarExcludes = {};
28
+ if (runtime.settings.hasOwnProperty('envVarExcludes') && Array.isArray(runtime.settings.envVarExcludes)) {
29
+ runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true);
30
+ }
31
+ }
26
32
 
27
33
  function diffNodes(oldNode,newNode) {
28
34
  if (oldNode == null) {
29
35
  return true;
30
36
  }
31
- var oldKeys = Object.keys(oldNode).filter(function(p) { return p != "x" && p != "y" && p != "wires" });
32
- var newKeys = Object.keys(newNode).filter(function(p) { return p != "x" && p != "y" && p != "wires" });
37
+ const keyFilter = p => p != 'x' && p != 'y' && p != 'wires'
38
+ const groupKeyFilter = p => keyFilter(p) && p != 'nodes' && p != 'style' && p != 'w' && p != 'h'
39
+ var oldKeys = Object.keys(oldNode).filter(oldNode.type === 'group' ? groupKeyFilter : keyFilter);
40
+ var newKeys = Object.keys(newNode).filter(newNode.type === 'group' ? groupKeyFilter : keyFilter);
41
+
33
42
  if (oldKeys.length != newKeys.length) {
34
43
  return true;
35
44
  }
@@ -70,8 +79,64 @@ function mapEnvVarProperties(obj,prop,flow,config) {
70
79
  }
71
80
  }
72
81
 
82
+ async function evaluateEnvProperties(flow, env, credentials) {
83
+ const pendingEvaluations = []
84
+ const evaluatedEnv = {}
85
+ const envTypes = []
86
+ for (let i = 0; i < env.length; i++) {
87
+ let { name, value, type } = env[i]
88
+ if (type === "env") {
89
+ // Do env types last as they may include references to other env vars
90
+ // at this level which need to be resolved before they can be looked-up
91
+ envTypes.push(env[i])
92
+ } else if (type === "bool") {
93
+ value = (value === "true") || (value === true);
94
+ } else if (type === "cred") {
95
+ if (credentials.hasOwnProperty(name)) {
96
+ value = credentials[name];
97
+ }
98
+ } else if (type ==='jsonata') {
99
+ pendingEvaluations.push(new Promise((resolve, _) => {
100
+ redUtil.evaluateNodeProperty(value, 'jsonata', {_flow: flow}, null, (err, result) => {
101
+ if (!err) {
102
+ evaluatedEnv[name] = result
103
+ }
104
+ resolve()
105
+ });
106
+ }))
107
+ } else {
108
+ value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
109
+ }
110
+ evaluatedEnv[name] = value
111
+ }
112
+ if (pendingEvaluations.length > 0) {
113
+ await Promise.all(pendingEvaluations)
114
+ }
115
+ for (let i = 0; i < envTypes.length; i++) {
116
+ let { name, value, type } = envTypes[i]
117
+ // If an env-var wants to lookup itself, delegate straight to the parent
118
+ // https://github.com/node-red/node-red/issues/2099
119
+ if (value === name) {
120
+ value = `$parent.${name}`
121
+ }
122
+ if (evaluatedEnv.hasOwnProperty(value)) {
123
+ value = evaluatedEnv[value]
124
+ } else {
125
+ value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
126
+ }
127
+ evaluatedEnv[name] = value
128
+ }
129
+
130
+ return evaluatedEnv
131
+ }
73
132
 
74
- function createNode(flow,config) {
133
+ /**
134
+ * Create a new instance of a node
135
+ * @param {Flow} flow The containing flow
136
+ * @param {object} config The node configuration object
137
+ * @return {Node} The instance of the node
138
+ */
139
+ async function createNode(flow,config) {
75
140
  var newNode = null;
76
141
  var type = config.type;
77
142
  try {
@@ -140,7 +205,7 @@ function createNode(flow,config) {
140
205
  // This allows nodes inside the subflow to get ahold of each other
141
206
  // such as a node accessing its config node
142
207
  flow.subflowInstanceNodes[config.id] = subflow
143
- subflow.start();
208
+ await subflow.start();
144
209
  return subflow.node;
145
210
  }
146
211
  } catch(err) {
@@ -150,373 +215,343 @@ function createNode(flow,config) {
150
215
  }
151
216
 
152
217
  function parseConfig(config) {
153
- var flow = {};
154
- flow.allNodes = {};
155
- flow.subflows = {};
156
- flow.configs = {};
157
- flow.flows = {};
158
- flow.groups = {};
159
- flow.missingTypes = [];
160
-
161
- config.forEach(function(n) {
162
- flow.allNodes[n.id] = clone(n);
163
- if (n.type === 'tab') {
164
- flow.flows[n.id] = n;
165
- flow.flows[n.id].subflows = {};
166
- flow.flows[n.id].configs = {};
167
- flow.flows[n.id].nodes = {};
168
- }
169
- if (n.type === 'group') {
170
- flow.groups[n.id] = n;
171
- }
172
- });
173
-
174
- // TODO: why a separate forEach? this can be merged with above
175
- config.forEach(function(n) {
176
- if (n.type === 'subflow') {
177
- flow.subflows[n.id] = n;
178
- flow.subflows[n.id].configs = {};
179
- flow.subflows[n.id].nodes = {};
180
- flow.subflows[n.id].instances = [];
181
- }
182
- });
183
- var linkWires = {};
184
- var linkOutNodes = [];
185
- config.forEach(function(n) {
186
- if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
187
- var subflowDetails = subflowInstanceRE.exec(n.type);
188
-
189
- if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) {
190
- if (flow.missingTypes.indexOf(n.type) === -1) {
191
- flow.missingTypes.push(n.type);
192
- }
193
- }
194
- var container = null;
195
- if (flow.flows[n.z]) {
196
- container = flow.flows[n.z];
197
- } else if (flow.subflows[n.z]) {
198
- container = flow.subflows[n.z];
199
- }
200
- if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
201
- if (subflowDetails) {
202
- var subflowType = subflowDetails[1]
203
- n.subflow = subflowType;
204
- flow.subflows[subflowType].instances.push(n)
205
- }
206
- if (container) {
207
- container.nodes[n.id] = n;
208
- }
209
- } else {
210
- if (container) {
211
- container.configs[n.id] = n;
212
- } else {
213
- flow.configs[n.id] = n;
214
- flow.configs[n.id]._users = [];
215
- }
216
- }
217
- if (n.type === 'link in' && n.links) {
218
- // Ensure wires are present in corresponding link out nodes
219
- n.links.forEach(function(id) {
220
- linkWires[id] = linkWires[id]||{};
221
- linkWires[id][n.id] = true;
222
- })
223
- } else if (n.type === 'link out' && n.links) {
224
- linkWires[n.id] = linkWires[n.id]||{};
225
- n.links.forEach(function(id) {
226
- linkWires[n.id][id] = true;
227
- })
228
- linkOutNodes.push(n);
229
- }
230
- }
231
- });
232
- linkOutNodes.forEach(function(n) {
233
- var links = linkWires[n.id];
234
- var targets = Object.keys(links);
235
- n.wires = [targets];
236
- });
237
-
238
-
239
- var addedTabs = {};
240
- config.forEach(function(n) {
241
- if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
242
- for (var prop in n) {
243
- if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) {
244
- // This property references a global config node
245
- flow.configs[n[prop]]._users.push(n.id)
246
- }
247
- }
248
- if (n.z && !flow.subflows[n.z]) {
249
-
250
- if (!flow.flows[n.z]) {
251
- flow.flows[n.z] = {type:'tab',id:n.z};
252
- flow.flows[n.z].subflows = {};
253
- flow.flows[n.z].configs = {};
254
- flow.flows[n.z].nodes = {};
255
- addedTabs[n.z] = flow.flows[n.z];
256
- }
257
- if (addedTabs[n.z]) {
258
- if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
259
- addedTabs[n.z].nodes[n.id] = n;
260
- } else {
261
- addedTabs[n.z].configs[n.id] = n;
262
- }
263
- }
264
- }
265
- }
266
- });
267
- return flow;
268
- }
218
+ var flow = {};
219
+ flow.allNodes = {};
220
+ flow.subflows = {};
221
+ flow.configs = {};
222
+ flow.flows = {};
223
+ flow.missingTypes = [];
224
+
225
+ config.forEach(function (n) {
226
+ flow.allNodes[n.id] = clone(n);
227
+ if (n.type === 'tab') {
228
+ flow.flows[n.id] = n;
229
+ flow.flows[n.id].subflows = {};
230
+ flow.flows[n.id].configs = {};
231
+ flow.flows[n.id].nodes = {};
232
+ flow.flows[n.id].groups = {};
233
+ } else if (n.type === 'subflow') {
234
+ flow.subflows[n.id] = n;
235
+ flow.subflows[n.id].configs = {};
236
+ flow.subflows[n.id].nodes = {};
237
+ flow.subflows[n.id].groups = {};
238
+ flow.subflows[n.id].instances = [];
239
+ }
240
+ });
269
241
 
270
- function getGlobalEnv(name) {
271
- const nodes = _runtime.nodes;
272
- if (!nodes) {
273
- return null;
274
- }
275
- const gconf = nodes.getGlobalConfig();
276
- const env = gconf ? gconf.env : null;
277
-
278
- if (env) {
279
- const cred = (gconf ? credentials.get(gconf.id) : null) || {
280
- map: {}
281
- };
282
- const map = cred.map;
283
-
284
- for (let i = 0; i < env.length; i++) {
285
- const item = env[i];
286
- if (item.name === name) {
287
- if (item.type === "cred") {
288
- return {
289
- name: name,
290
- value: map[name],
291
- type: "cred"
292
- };
242
+ var linkWires = {};
243
+ var linkOutNodes = [];
244
+ config.forEach(function (n) {
245
+ if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
246
+ var subflowDetails = subflowInstanceRE.exec(n.type);
247
+
248
+ if ((subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type))) {
249
+ if (flow.missingTypes.indexOf(n.type) === -1) {
250
+ flow.missingTypes.push(n.type);
293
251
  }
294
- return item;
295
252
  }
296
- }
297
- }
298
- return null;
299
- }
300
-
301
- module.exports = {
302
- init: function(runtime) {
303
- _runtime = runtime;
304
- envVarExcludes = {};
305
- if (runtime.settings.hasOwnProperty('envVarExcludes') && Array.isArray(runtime.settings.envVarExcludes)) {
306
- runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true);
307
- }
308
- },
309
- getEnvVar: function(k) {
310
- if (!envVarExcludes[k]) {
311
- const item = getGlobalEnv(k);
312
- if (item) {
313
- const val = redUtil.evaluateNodeProperty(item.value, item.type, null, null, null);
314
- return val;
253
+ var container = null;
254
+ if (flow.flows[n.z]) {
255
+ container = flow.flows[n.z];
256
+ } else if (flow.subflows[n.z]) {
257
+ container = flow.subflows[n.z];
315
258
  }
316
- return process.env[k];
317
- }
318
- return undefined;
319
- },
320
- diffNodes: diffNodes,
321
- mapEnvVarProperties: mapEnvVarProperties,
322
-
323
- parseConfig: parseConfig,
324
-
325
- diffConfigs: function(oldConfig, newConfig) {
326
- var id;
327
- var node;
328
- var nn;
329
- var wires;
330
- var j,k;
331
-
332
- if (!oldConfig) {
333
- oldConfig = {
334
- flows:{},
335
- allNodes:{}
259
+ if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
260
+ if (subflowDetails) {
261
+ var subflowType = subflowDetails[1]
262
+ n.subflow = subflowType;
263
+ if (flow.subflows[subflowType]) {
264
+ flow.subflows[subflowType].instances.push(n)
265
+ }
266
+ }
267
+ if (container) {
268
+ container.nodes[n.id] = n;
269
+ }
270
+ } else {
271
+ if (container) {
272
+ container.configs[n.id] = n;
273
+ } else {
274
+ flow.configs[n.id] = n;
275
+ flow.configs[n.id]._users = [];
276
+ }
336
277
  }
337
- }
338
- var changedSubflows = {};
339
-
340
- var added = {};
341
- var removed = {};
342
- var changed = {};
343
- var wiringChanged = {};
344
-
345
- var linkMap = {};
346
-
347
- var changedTabs = {};
348
-
349
- // Look for tabs that have been removed
350
- for (id in oldConfig.flows) {
351
- if (oldConfig.flows.hasOwnProperty(id) && (!newConfig.flows.hasOwnProperty(id))) {
352
- removed[id] = oldConfig.allNodes[id];
278
+ if (n.type === 'link in' && n.links) {
279
+ // Ensure wires are present in corresponding link out nodes
280
+ n.links.forEach(function (id) {
281
+ linkWires[id] = linkWires[id] || {};
282
+ linkWires[id][n.id] = true;
283
+ })
284
+ } else if (n.type === 'link out' && n.links) {
285
+ linkWires[n.id] = linkWires[n.id] || {};
286
+ n.links.forEach(function (id) {
287
+ linkWires[n.id][id] = true;
288
+ })
289
+ linkOutNodes.push(n);
290
+ }
291
+ } else if (n.type === 'group') {
292
+ const parentContainer = flow.flows[n.z] || flow.subflows[n.z]
293
+ if (parentContainer) {
294
+ parentContainer.groups[n.id] = n
353
295
  }
354
296
  }
355
-
356
- // Look for tabs that have been disabled
357
- for (id in oldConfig.flows) {
358
- if (oldConfig.flows.hasOwnProperty(id) && newConfig.flows.hasOwnProperty(id)) {
359
- var originalState = oldConfig.flows[id].disabled||false;
360
- var newState = newConfig.flows[id].disabled||false;
361
- if (originalState !== newState) {
362
- changedTabs[id] = true;
363
- if (originalState) {
364
- added[id] = oldConfig.allNodes[id];
297
+ });
298
+ linkOutNodes.forEach(function (n) {
299
+ var links = linkWires[n.id];
300
+ var targets = Object.keys(links);
301
+ n.wires = [targets];
302
+ });
303
+
304
+
305
+ var addedTabs = {};
306
+ config.forEach(function (n) {
307
+ if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
308
+ for (var prop in n) {
309
+ if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) {
310
+ // This property references a global config node
311
+ flow.configs[n[prop]]._users.push(n.id)
312
+ }
313
+ }
314
+ if (n.z && !flow.subflows[n.z]) {
315
+
316
+ if (!flow.flows[n.z]) {
317
+ flow.flows[n.z] = { type: 'tab', id: n.z };
318
+ flow.flows[n.z].subflows = {};
319
+ flow.flows[n.z].configs = {};
320
+ flow.flows[n.z].nodes = {};
321
+ addedTabs[n.z] = flow.flows[n.z];
322
+ }
323
+ if (addedTabs[n.z]) {
324
+ if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
325
+ addedTabs[n.z].nodes[n.id] = n;
365
326
  } else {
366
- removed[id] = oldConfig.allNodes[id];
327
+ addedTabs[n.z].configs[n.id] = n;
367
328
  }
368
329
  }
369
330
  }
370
331
  }
332
+ });
333
+ return flow;
334
+ }
335
+ function getEnvVar(k) {
336
+ if (!envVarExcludes[k]) {
337
+ return process.env[k];
338
+ }
339
+ return undefined;
340
+ }
341
+ function diffConfigs(oldConfig, newConfig) {
342
+ var id;
343
+ var node;
344
+ var nn;
345
+ var wires;
346
+ var j,k;
347
+
348
+ if (!oldConfig) {
349
+ oldConfig = {
350
+ flows:{},
351
+ allNodes:{}
352
+ }
353
+ }
354
+ var changedSubflows = {};
355
+
356
+ var added = {};
357
+ var removed = {};
358
+ var changed = {};
359
+ var flowChanged = {};
360
+ var wiringChanged = {};
361
+ var globalConfigChanged = false;
362
+ var linkMap = {};
363
+ var allNestedGroups = []
364
+
365
+ // Look for tabs that have been removed
366
+ for (id in oldConfig.flows) {
367
+ if (oldConfig.flows.hasOwnProperty(id) && (!newConfig.flows.hasOwnProperty(id))) {
368
+ removed[id] = oldConfig.allNodes[id];
369
+ }
370
+ }
371
371
 
372
- for (id in oldConfig.allNodes) {
373
- if (oldConfig.allNodes.hasOwnProperty(id)) {
374
- node = oldConfig.allNodes[id];
375
- if (node.type !== 'tab') {
376
- // build the map of what this node was previously wired to
377
- if (node.wires) {
378
- linkMap[node.id] = linkMap[node.id] || [];
379
- for (j=0;j<node.wires.length;j++) {
380
- wires = node.wires[j];
381
- for (k=0;k<wires.length;k++) {
382
- linkMap[node.id].push(wires[k]);
383
- nn = oldConfig.allNodes[wires[k]];
384
- if (nn) {
385
- linkMap[nn.id] = linkMap[nn.id] || [];
386
- linkMap[nn.id].push(node.id);
387
- }
372
+ // Look for tabs that have been disabled
373
+ for (id in oldConfig.flows) {
374
+ if (oldConfig.flows.hasOwnProperty(id) && newConfig.flows.hasOwnProperty(id)) {
375
+ var originalState = oldConfig.flows[id].disabled||false;
376
+ var newState = newConfig.flows[id].disabled||false;
377
+ if (originalState !== newState) {
378
+ if (originalState) {
379
+ added[id] = oldConfig.allNodes[id];
380
+ } else {
381
+ removed[id] = oldConfig.allNodes[id];
382
+ }
383
+ }
384
+ }
385
+ }
386
+
387
+ for (id in oldConfig.allNodes) {
388
+ if (oldConfig.allNodes.hasOwnProperty(id)) {
389
+ node = oldConfig.allNodes[id];
390
+ if (node.type !== 'tab') {
391
+ // build the map of what this node was previously wired to
392
+ if (node.wires) {
393
+ linkMap[node.id] = linkMap[node.id] || [];
394
+ for (j=0;j<node.wires.length;j++) {
395
+ wires = node.wires[j];
396
+ for (k=0;k<wires.length;k++) {
397
+ linkMap[node.id].push(wires[k]);
398
+ nn = oldConfig.allNodes[wires[k]];
399
+ if (nn) {
400
+ linkMap[nn.id] = linkMap[nn.id] || [];
401
+ linkMap[nn.id].push(node.id);
388
402
  }
389
403
  }
390
404
  }
391
- // This node has been removed or its flow disabled
392
- if (removed[node.z] || !newConfig.allNodes.hasOwnProperty(id)) {
393
- removed[id] = node;
394
- // Mark the container as changed
395
- if (!removed[node.z] && newConfig.allNodes[removed[id].z]) {
396
- changed[removed[id].z] = newConfig.allNodes[removed[id].z];
397
- if (changed[removed[id].z].type === "subflow") {
398
- changedSubflows[removed[id].z] = changed[removed[id].z];
399
- //delete removed[id];
400
- }
405
+ }
406
+ // This node has been removed or its flow disabled
407
+ if (removed[node.z] || !newConfig.allNodes.hasOwnProperty(id)) {
408
+ removed[id] = node;
409
+ // Mark the container as changed
410
+ if (!removed[node.z] && newConfig.allNodes[removed[id].z]) {
411
+ changed[removed[id].z] = newConfig.allNodes[removed[id].z];
412
+ if (changed[removed[id].z].type === "subflow") {
413
+ changedSubflows[removed[id].z] = changed[removed[id].z];
414
+ //delete removed[id];
401
415
  }
416
+ }
417
+ } else {
418
+ if (added[node.z]) {
419
+ added[id] = node;
402
420
  } else {
403
- if (added[node.z]) {
404
- added[id] = node;
405
- } else {
406
- var currentState = node.d;
407
- var newState = newConfig.allNodes[id].d;
408
- if (!currentState && newState) {
409
- removed[id] = node;
421
+ var currentState = node.d;
422
+ var newState = newConfig.allNodes[id].d;
423
+ if (!currentState && newState) {
424
+ removed[id] = node;
425
+ }
426
+ // This node has a material configuration change
427
+ if (diffNodes(node,newConfig.allNodes[id]) || newConfig.allNodes[id].credentials) {
428
+ changed[id] = newConfig.allNodes[id];
429
+ if (changed[id].type === "subflow") {
430
+ changedSubflows[id] = changed[id];
410
431
  }
411
- // This node has a material configuration change
412
- if (diffNodes(node,newConfig.allNodes[id]) || newConfig.allNodes[id].credentials) {
413
- changed[id] = newConfig.allNodes[id];
414
- if (changed[id].type === "subflow") {
415
- changedSubflows[id] = changed[id];
416
- }
417
- // Mark the container as changed
418
- if (newConfig.allNodes[changed[id].z]) {
419
- changed[changed[id].z] = newConfig.allNodes[changed[id].z];
420
- if (changed[changed[id].z].type === "subflow") {
421
- changedSubflows[changed[id].z] = changed[changed[id].z];
422
- delete changed[id];
423
- }
432
+ // Mark the container as changed
433
+ if (newConfig.allNodes[changed[id].z]) {
434
+ changed[changed[id].z] = newConfig.allNodes[changed[id].z];
435
+ if (changed[changed[id].z].type === "subflow") {
436
+ changedSubflows[changed[id].z] = changed[changed[id].z];
437
+ delete changed[id];
424
438
  }
425
439
  }
426
- // This node's wiring has changed
427
- if (!redUtil.compareObjects(node.wires,newConfig.allNodes[id].wires)) {
428
- wiringChanged[id] = newConfig.allNodes[id];
429
- // Mark the container as changed
430
- if (newConfig.allNodes[wiringChanged[id].z]) {
431
- changed[wiringChanged[id].z] = newConfig.allNodes[wiringChanged[id].z];
432
- if (changed[wiringChanged[id].z].type === "subflow") {
433
- changedSubflows[wiringChanged[id].z] = changed[wiringChanged[id].z];
434
- delete wiringChanged[id];
435
- }
440
+ if (newConfig.allNodes[id].type === 'global-config') {
441
+ globalConfigChanged = true
442
+ }
443
+ }
444
+ // This node's wiring has changed
445
+ if (!redUtil.compareObjects(node.wires,newConfig.allNodes[id].wires)) {
446
+ wiringChanged[id] = newConfig.allNodes[id];
447
+ // Mark the container as changed
448
+ if (newConfig.allNodes[wiringChanged[id].z]) {
449
+ changed[wiringChanged[id].z] = newConfig.allNodes[wiringChanged[id].z];
450
+ if (changed[wiringChanged[id].z].type === "subflow") {
451
+ changedSubflows[wiringChanged[id].z] = changed[wiringChanged[id].z];
452
+ delete wiringChanged[id];
436
453
  }
437
454
  }
438
455
  }
439
456
  }
440
457
  }
458
+ } else if (!removed[id]) {
459
+ if (JSON.stringify(node.env) !== JSON.stringify(newConfig.allNodes[id].env)) {
460
+ flowChanged[id] = newConfig.allNodes[id];
461
+ }
441
462
  }
442
463
  }
443
- // Look for added nodes
444
- for (id in newConfig.allNodes) {
445
- if (newConfig.allNodes.hasOwnProperty(id)) {
446
- node = newConfig.allNodes[id];
447
- // build the map of what this node is now wired to
448
- if (node.wires) {
449
- linkMap[node.id] = linkMap[node.id] || [];
450
- for (j=0;j<node.wires.length;j++) {
451
- wires = node.wires[j];
452
- for (k=0;k<wires.length;k++) {
453
- if (linkMap[node.id].indexOf(wires[k]) === -1) {
454
- linkMap[node.id].push(wires[k]);
464
+ }
465
+ // Look for added nodes
466
+ for (id in newConfig.allNodes) {
467
+ if (newConfig.allNodes.hasOwnProperty(id)) {
468
+ node = newConfig.allNodes[id];
469
+ if (node.type === 'group') {
470
+ if (node.g) {
471
+ allNestedGroups.push(node)
472
+ }
473
+ if (changed[node.id]) {
474
+ if (node.nodes) {
475
+ node.nodes.forEach(nid => {
476
+ if (!changed[nid]) {
477
+ changed[nid] = true
455
478
  }
456
- nn = newConfig.allNodes[wires[k]];
457
- if (nn) {
458
- linkMap[nn.id] = linkMap[nn.id] || [];
459
- if (linkMap[nn.id].indexOf(node.id) === -1) {
460
- linkMap[nn.id].push(node.id);
461
- }
479
+ })
480
+ }
481
+ }
482
+ }
483
+ // build the map of what this node is now wired to
484
+ if (node.wires) {
485
+ linkMap[node.id] = linkMap[node.id] || [];
486
+ for (j=0;j<node.wires.length;j++) {
487
+ wires = node.wires[j];
488
+ for (k=0;k<wires.length;k++) {
489
+ if (linkMap[node.id].indexOf(wires[k]) === -1) {
490
+ linkMap[node.id].push(wires[k]);
491
+ }
492
+ nn = newConfig.allNodes[wires[k]];
493
+ if (nn) {
494
+ linkMap[nn.id] = linkMap[nn.id] || [];
495
+ if (linkMap[nn.id].indexOf(node.id) === -1) {
496
+ linkMap[nn.id].push(node.id);
462
497
  }
463
498
  }
464
499
  }
465
500
  }
466
- // This node has been added
467
- if (!oldConfig.allNodes.hasOwnProperty(id)) {
468
- added[id] = node;
469
- // Mark the container as changed
470
- if (newConfig.allNodes[added[id].z]) {
471
- changed[added[id].z] = newConfig.allNodes[added[id].z];
472
- if (changed[added[id].z].type === "subflow") {
473
- changedSubflows[added[id].z] = changed[added[id].z];
474
- delete added[id];
475
- }
501
+ }
502
+ // This node has been added
503
+ if (!oldConfig.allNodes.hasOwnProperty(id)) {
504
+ added[id] = node;
505
+ // Mark the container as changed
506
+ if (newConfig.allNodes[added[id].z]) {
507
+ changed[added[id].z] = newConfig.allNodes[added[id].z];
508
+ if (changed[added[id].z].type === "subflow") {
509
+ changedSubflows[added[id].z] = changed[added[id].z];
510
+ delete added[id];
476
511
  }
477
512
  }
478
513
  }
479
514
  }
515
+ }
480
516
 
481
- var madeChange;
482
- // Loop through the nodes looking for references to changed config nodes
483
- // Repeat the loop if anything is marked as changed as it may need to be
484
- // propagated to parent nodes.
485
- // TODO: looping through all nodes every time is a bit inefficient - could be more targeted
486
- do {
487
- madeChange = false;
488
- for (id in newConfig.allNodes) {
489
- if (newConfig.allNodes.hasOwnProperty(id)) {
490
- node = newConfig.allNodes[id];
491
- for (var prop in node) {
492
- if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
493
- // This node has a property that references a changed/removed node
494
- // Assume it is a config node change and mark this node as
495
- // changed.
496
-
497
- var changeOrigin = changed[node[prop]];
498
- if (changeOrigin || removed[node[prop]]) {
499
- if (!changed[node.id]) {
500
- if (changeOrigin &&
501
- (prop === "g") &&
502
- (changeOrigin.type === "group")) {
503
- var oldNode = oldConfig.allNodes[node.id];
504
- // ignore change of group node
505
- // if group of this node not changed
506
- if (oldNode &&
507
- (node.g === oldNode.g)) {
508
- continue;
509
- }
517
+ var madeChange;
518
+ // Loop through the nodes looking for references to changed config nodes
519
+ // Repeat the loop if anything is marked as changed as it may need to be
520
+ // propagated to parent nodes.
521
+ // TODO: looping through all nodes every time is a bit inefficient - could be more targeted
522
+ do {
523
+ madeChange = false;
524
+ for (id in newConfig.allNodes) {
525
+ if (newConfig.allNodes.hasOwnProperty(id)) {
526
+ node = newConfig.allNodes[id];
527
+ for (var prop in node) {
528
+ if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
529
+ // This node has a property that references a changed/removed node
530
+ // Assume it is a config node change and mark this node as
531
+ // changed.
532
+
533
+ var changeOrigin = changed[node[prop]];
534
+ if (changeOrigin || removed[node[prop]]) {
535
+ if (!changed[node.id]) {
536
+ if (changeOrigin &&
537
+ (prop === "g") &&
538
+ (changeOrigin.type === "group")) {
539
+ var oldNode = oldConfig.allNodes[node.id];
540
+ // ignore change of group node
541
+ // if group of this node not changed
542
+ if (oldNode &&
543
+ (node.g === oldNode.g)) {
544
+ continue;
510
545
  }
511
- madeChange = true;
512
- changed[node.id] = node;
513
- // This node exists within subflow template
514
- // Mark the template as having changed
515
- if (newConfig.allNodes[node.z]) {
516
- changed[node.z] = newConfig.allNodes[node.z];
517
- if (changed[node.z].type === "subflow") {
518
- changedSubflows[node.z] = changed[node.z];
519
- }
546
+ }
547
+ madeChange = true;
548
+ changed[node.id] = node;
549
+ // This node exists within subflow template
550
+ // Mark the template as having changed
551
+ if (newConfig.allNodes[node.z]) {
552
+ changed[node.z] = newConfig.allNodes[node.z];
553
+ if (changed[node.z].type === "subflow") {
554
+ changedSubflows[node.z] = changed[node.z];
520
555
  }
521
556
  }
522
557
  }
@@ -524,94 +559,123 @@ module.exports = {
524
559
  }
525
560
  }
526
561
  }
527
- } while (madeChange===true)
562
+ }
563
+ } while (madeChange===true)
564
+
565
+ // Find any nodes that exist on a subflow template and remove from changed
566
+ // list as the parent subflow will now be marked as containing a change
567
+ for (id in newConfig.allNodes) {
568
+ if (newConfig.allNodes.hasOwnProperty(id)) {
569
+ node = newConfig.allNodes[id];
570
+ if (newConfig.allNodes[node.z] && newConfig.allNodes[node.z].type === "subflow") {
571
+ delete changed[node.id];
572
+ }
573
+ }
574
+ }
528
575
 
529
- // Find any nodes that exist on a subflow template and remove from changed
530
- // list as the parent subflow will now be marked as containing a change
531
- for (id in newConfig.allNodes) {
532
- if (newConfig.allNodes.hasOwnProperty(id)) {
533
- node = newConfig.allNodes[id];
534
- if (newConfig.allNodes[node.z] && newConfig.allNodes[node.z].type === "subflow") {
535
- delete changed[node.id];
536
- }
576
+ // Recursively mark all children of changed groups as changed
577
+ do {
578
+ madeChange = false
579
+ for (let i = 0; i < allNestedGroups.length; i++) {
580
+ const group = allNestedGroups[i]
581
+ if (!changed[group.id] && group.g && changed[group.g]) {
582
+ changed[group.id] = true
583
+ madeChange = true
584
+ }
585
+ if (changed[group.id] && group.nodes) {
586
+ group.nodes.forEach(nid => {
587
+ if (!changed[nid]) {
588
+ changed[nid] = true
589
+ madeChange = true
590
+ }
591
+ })
537
592
  }
538
593
  }
594
+ } while(madeChange)
539
595
 
540
- // Recursively mark all instances of changed subflows as changed
541
- var changedSubflowStack = Object.keys(changedSubflows);
542
- while (changedSubflowStack.length > 0) {
543
- var subflowId = changedSubflowStack.pop();
544
- for (id in newConfig.allNodes) {
545
- if (newConfig.allNodes.hasOwnProperty(id)) {
546
- node = newConfig.allNodes[id];
547
- if (node.type === 'subflow:'+subflowId) {
548
- if (!changed[node.id]) {
549
- changed[node.id] = node;
550
- if (!changed[changed[node.id].z] && newConfig.allNodes[changed[node.id].z]) {
551
- changed[changed[node.id].z] = newConfig.allNodes[changed[node.id].z];
552
- if (newConfig.allNodes[changed[node.id].z].type === "subflow") {
553
- // This subflow instance is inside a subflow. Add the
554
- // containing subflow to the stack to mark
555
- changedSubflowStack.push(changed[node.id].z);
556
- delete changed[node.id];
557
- }
596
+ // Recursively mark all instances of changed subflows as changed
597
+ var changedSubflowStack = Object.keys(changedSubflows);
598
+ while (changedSubflowStack.length > 0) {
599
+ var subflowId = changedSubflowStack.pop();
600
+ for (id in newConfig.allNodes) {
601
+ if (newConfig.allNodes.hasOwnProperty(id)) {
602
+ node = newConfig.allNodes[id];
603
+ if (node.type === 'subflow:'+subflowId) {
604
+ if (!changed[node.id]) {
605
+ changed[node.id] = node;
606
+ if (!changed[changed[node.id].z] && newConfig.allNodes[changed[node.id].z]) {
607
+ changed[changed[node.id].z] = newConfig.allNodes[changed[node.id].z];
608
+ if (newConfig.allNodes[changed[node.id].z].type === "subflow") {
609
+ // This subflow instance is inside a subflow. Add the
610
+ // containing subflow to the stack to mark
611
+ changedSubflowStack.push(changed[node.id].z);
612
+ delete changed[node.id];
558
613
  }
559
614
  }
560
615
  }
561
616
  }
562
617
  }
563
618
  }
619
+ }
564
620
 
565
- var diff = {
566
- added:Object.keys(added),
567
- changed:Object.keys(changed),
568
- removed:Object.keys(removed),
569
- rewired:Object.keys(wiringChanged),
570
- linked:[]
571
- }
572
621
 
573
- // Traverse the links of all modified nodes to mark the connected nodes
574
- var modifiedNodes = diff.added.concat(diff.changed).concat(diff.removed).concat(diff.rewired);
575
- var visited = {};
576
- while (modifiedNodes.length > 0) {
577
- node = modifiedNodes.pop();
578
- if (!visited[node]) {
579
- visited[node] = true;
580
- if (linkMap[node]) {
581
- if (!changed[node] && !added[node] && !removed[node] && !wiringChanged[node]) {
582
- diff.linked.push(node);
583
- }
584
- modifiedNodes = modifiedNodes.concat(linkMap[node]);
622
+
623
+ var diff = {
624
+ added:Object.keys(added),
625
+ changed:Object.keys(changed),
626
+ removed:Object.keys(removed),
627
+ rewired:Object.keys(wiringChanged),
628
+ linked:[],
629
+ flowChanged: Object.keys(flowChanged),
630
+ globalConfigChanged
631
+ }
632
+
633
+ // Traverse the links of all modified nodes to mark the connected nodes
634
+ var modifiedNodes = diff.added.concat(diff.changed).concat(diff.removed).concat(diff.rewired);
635
+ var visited = {};
636
+ while (modifiedNodes.length > 0) {
637
+ node = modifiedNodes.pop();
638
+ if (!visited[node]) {
639
+ visited[node] = true;
640
+ if (linkMap[node]) {
641
+ if (!changed[node] && !added[node] && !removed[node] && !wiringChanged[node]) {
642
+ diff.linked.push(node);
585
643
  }
644
+ modifiedNodes = modifiedNodes.concat(linkMap[node]);
586
645
  }
587
646
  }
588
- // console.log(diff);
589
- // for (id in newConfig.allNodes) {
590
- // console.log(
591
- // (added[id]?"a":(changed[id]?"c":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"l":" "),
592
- // newConfig.allNodes[id].type.padEnd(10),
593
- // id.padEnd(16),
594
- // (newConfig.allNodes[id].z||"").padEnd(16),
595
- // newConfig.allNodes[id].name||newConfig.allNodes[id].label||""
596
- // );
597
- // }
598
- // for (id in removed) {
599
- // console.log(
600
- // "- "+(diff.linked.indexOf(id)!==-1?"~":" "),
601
- // id,
602
- // oldConfig.allNodes[id].type,
603
- // oldConfig.allNodes[id].name||oldConfig.allNodes[id].label||""
604
- // );
605
- // }
606
-
607
- return diff;
608
- },
609
-
610
- /**
611
- * Create a new instance of a node
612
- * @param {Flow} flow The containing flow
613
- * @param {object} config The node configuration object
614
- * @return {Node} The instance of the node
615
- */
616
- createNode: createNode
647
+ }
648
+ // console.log(diff);
649
+ // for (id in newConfig.allNodes) {
650
+ // if (added[id] || changed[id] || wiringChanged[id] || diff.linked.indexOf(id)!==-1) {
651
+ // console.log(
652
+ // (added[id]?"a":(changed[id]?"c":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"l":" "),
653
+ // newConfig.allNodes[id].type.padEnd(10),
654
+ // id.padEnd(16),
655
+ // (newConfig.allNodes[id].z||"").padEnd(16),
656
+ // newConfig.allNodes[id].name||newConfig.allNodes[id].label||""
657
+ // );
658
+ // }
659
+ // }
660
+ // for (id in removed) {
661
+ // console.log(
662
+ // "- "+(diff.linked.indexOf(id)!==-1?"~":" "),
663
+ // id,
664
+ // oldConfig.allNodes[id].type,
665
+ // oldConfig.allNodes[id].name||oldConfig.allNodes[id].label||""
666
+ // );
667
+ // }
668
+
669
+ return diff;
670
+ }
671
+
672
+ module.exports = {
673
+ init,
674
+ createNode,
675
+ parseConfig,
676
+ diffConfigs,
677
+ diffNodes,
678
+ getEnvVar,
679
+ mapEnvVarProperties,
680
+ evaluateEnvProperties
617
681
  }