@inteli.city/node-red-contrib-exec-collection 1.0.3 → 1.0.5

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/thread.queue.js DELETED
@@ -1,586 +0,0 @@
1
- /** ////
2
- * Copyright JS Foundation and other contributors, http://js.foundation
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- **///
16
- module.exports = function(RED) {
17
- //* libraries
18
- "use strict";
19
- var mustache = require("mustache");
20
- var fs = require('fs');
21
- var tmp = require('tmp-promise');
22
- var fsPromises = require('fs').promises;
23
- var terminate = require("terminate");
24
- var yaml = require("js-yaml");
25
- var convertXML = require('xml-js');
26
- var exec = require('child_process').exec;
27
- var spawn = require('child_process').spawn;
28
- var Queue = require('queue');
29
- //* auxiliary functions
30
- //** function: extractTokens
31
- function extractTokens(tokens, set) {
32
- set = set || new Set();
33
- tokens.forEach(function(token) {
34
- if (token[0] !== 'text') {
35
- set.add(token[1]);
36
- if (token.length > 4) {
37
- extractTokens(token[4], set);
38
- }
39
- }
40
- });
41
- return set;
42
- }
43
-
44
- //** function: parseContext
45
- function parseContext(key) {
46
- var match = /^(flow|global)(\[(\w+)\])?\.(.+)/.exec(key);
47
- if (match) {
48
- var parts = {};
49
- parts.type = match[1];
50
- parts.store = (match[3] === '') ? "default" : match[3];
51
- parts.field = match[4];
52
- return parts;
53
- }
54
- return undefined;
55
- }
56
-
57
- //** function: parseEnv
58
- function parseEnv(key) {
59
- var match = /^env\.(.+)/.exec(key);
60
- if (match) {
61
- return match[1];
62
- }
63
- return undefined;
64
- }
65
-
66
-
67
- /**
68
- * Custom Mustache Context capable to collect message property and node
69
- * flow and global context
70
- */
71
-
72
- //** function: remove_by_value (prototype)
73
- Array.prototype.remove_by_value = function(val) {
74
- for (var i = 0; i < this.length; i++) {
75
- if (this[i] === val) {
76
- this.splice(i, 1);
77
- i--;
78
- }
79
- }
80
- return this;
81
- }
82
- //** function: NodeContext
83
- class NodeContext extends mustache.Context {
84
- constructor(msg, nodeContext, parent, escapeStrings, cachedContextTokens) {
85
- super(msg, parent);
86
- this.nodeContext = nodeContext;
87
- this.escapeStrings = escapeStrings;
88
- this.cachedContextTokens = cachedContextTokens;
89
- }
90
-
91
- lookup(name) {
92
- try {
93
- var value = super.lookup(name);
94
- if (value !== undefined) {
95
- if (typeof value === "object") {
96
- value = JSON.stringify(value);
97
- }
98
- if (this.escapeStrings && typeof value === "string") {
99
- value = value.replace(/\\/g, "\\\\")
100
- .replace(/\n/g, "\\n")
101
- .replace(/\t/g, "\\t")
102
- .replace(/\r/g, "\\r")
103
- .replace(/\f/g, "\\f")
104
- .replace(/[\b]/g, "\\b");
105
- }
106
- return value;
107
- }
108
-
109
- if (parseEnv(name)) {
110
- return this.cachedContextTokens[name];
111
- }
112
-
113
- var context = parseContext(name);
114
- if (context) {
115
- var target = this.nodeContext[context.type];
116
- if (target) {
117
- return this.cachedContextTokens[name];
118
- }
119
- }
120
- return '';
121
- } catch (err) {
122
- throw err;
123
- }
124
- }
125
-
126
- push(view) {
127
- return new NodeContext(view, this.nodeContext, this, undefined, this.cachedContextTokens);
128
- }
129
- }
130
-
131
- //* ExecQueueNode
132
- function ExecQueueNode(n) {
133
- //** setting values
134
- RED.nodes.createNode(this, n);
135
- this.name = n.name;
136
- this.field = n.field || "payload";
137
- // this.template = n.template;
138
- this.template = n.template === "" ? " " : n.template; // gambiarra (arrumar)
139
- this.syntax = n.syntax || "javascript";
140
- this.fieldType = n.fieldType || "msg";
141
- this.outputFormat = n.output || "str";
142
-
143
- this.cmd = (n.command || "").trim();
144
- this.append = (n.append || "").trim();
145
- this.useSpawn = n.useSpawn
146
- this.count = 0
147
- this.state = ""
148
- this.queue = n.queue
149
- this.executingCode = 0
150
- this.waitingForExecuting = 0
151
- this.processKilled = false
152
- this.statusTimerUp = new Date()
153
- this.statusTimerDown = new Date()
154
- // this.addpayCB = n.addpayCB
155
- this.cmdTemplate = n.cmdTemplate
156
- this.cmd = (n.command || "").trim();
157
- //this.parsedJSON = n.parsedJSON
158
- this.splitLine = n.splitLine
159
- this.cleanQueue = n.cleanQueue
160
-
161
- if (n.addpay === undefined) { n.addpay = true; }
162
- this.addpay = n.addpay;
163
- this.append = (n.append || "").trim();
164
- this.useSpawn = (n.useSpawn == "true");
165
- this.timer = Number(n.timer || 0) * 1000;
166
- this.activeProcesses = {};
167
- this.tempFiles = []
168
- this.oldrc = (n.oldrc || false).toString();
169
- //this.execOpt = {maxBuffer:50000000, windowsHide: (n.winHide === true)};
170
- //this.execOpt = {encoding:'binary', maxBuffer:50000000, windowsHide: (n.winHide === true)};
171
- this.execOpt = { maxBuffer: 50000000, windowsHide: (n.winHide === true), detached: true };
172
- this.spawnOpt = { windowsHide: (n.winHide === true), detached: true }
173
-
174
- // this.timer = Number(n.timer || 0)*1000;
175
- // this.activeProcesses = {};
176
- // this.oldrc = (n.oldrc || false).toString();
177
- // this.execOpt = {encoding:'binary', maxBuffer:50000000, windowsHide: (n.winHide === true)};
178
- // this.spawnOpt = {windowsHide: (n.winHide === true) }
179
- var node = this;
180
- //** node initialization setup
181
- if (process.platform === 'linux' && fs.existsSync('/bin/bash')) { node.execOpt.shell = '/bin/bash'; }
182
-
183
- var queue = Queue({ results: [], concurrency: node.queue, autostart: true })
184
- node.status({ fill: "blue", shape: "ring", text: `0 (0/${node.queue})` });
185
-
186
- setInterval(() => {
187
- if (node.executingCode !== 0 || node.waitingForExecuting !== 0) {
188
- node.status({ fill: "blue", shape: "ring", text: `${node.waitingForExecuting} (${node.executingCode}/${node.queue})` });
189
- }
190
- }, 1000)
191
-
192
- //** node.on('input')
193
- node.on("input", async function(msg, send, done) {
194
- try {
195
- msg.nodeName = node.name;
196
-
197
- if (msg._msgid === undefined) {
198
- output(msg, msg.message, send, done);
199
- delete msg.message;
200
- return;
201
- }
202
-
203
- const millisecondsUp = ((new Date()).getTime() - node.statusTimerUp.getTime());
204
- node.statusTimerUp = new Date();
205
-
206
- // resolving templates
207
- const resolvedTokens = await resolveTemplate(msg);
208
-
209
- // queue logic
210
- if (node.useSpawn === false) {
211
- msg.lastMessage = false;
212
- }
213
- if (node.executingCode < node.queue) {
214
- node.executingCode++;
215
- } else {
216
- node.waitingForExecuting++;
217
- }
218
- if (millisecondsUp > 100) {
219
- node.status({ fill: "blue", shape: "ring", text: `${node.waitingForExecuting} (${node.executingCode}/${node.queue})` });
220
- }
221
-
222
- await new Promise(resolve => {
223
- queue.push(async () => {
224
- try {
225
- const millisecondsDown = ((new Date()).getTime() - node.statusTimerDown.getTime());
226
- await executeCode(msg, send, done, node, resolvedTokens);
227
- node.statusTimerDown = new Date();
228
-
229
- if (node.waitingForExecuting > 0) {
230
- node.waitingForExecuting--;
231
- } else if (node.executingCode > 0) {
232
- node.executingCode--;
233
- if (node.executingCode === 0 && node.useSpawn === false) {
234
- msg.lastMessage = true;
235
- }
236
- }
237
- if (node.processKilled === false && (millisecondsDown > 100 || node.executingCode === 0)) {
238
- node.status({ fill: "blue", shape: "ring", text: `${node.waitingForExecuting} (${node.executingCode}/${node.queue})` });
239
- }
240
- resolve();
241
- } catch (error) {
242
- reject(error);
243
- }
244
- });
245
- });
246
-
247
- } catch (err) {
248
- done(err.message);
249
- }
250
- });
251
-
252
-
253
- //** node.on('close')
254
- node.on('close', async function() {
255
- //// KILL PROCESSES AND ERASE FILES
256
- queue.end()
257
- node.executingCode = 0
258
- node.waitingForExecuting = 0
259
- node.processKilled = true
260
- for (var pid in node.activeProcesses) {
261
- /* istanbul ignore else */
262
- if (node.activeProcesses.hasOwnProperty(pid)) {
263
- // process.kill(-pid, 9)
264
- terminate(pid)
265
- node.activeProcesses[pid] = null;
266
- node.warn(`Killing pid ${pid}`)
267
- }
268
- }
269
-
270
- for (let i = 0, len = node.tempFiles.length; i < len; i++) {
271
- await fsPromises.unlink(file);
272
- }
273
- node.activeProcesses = {};
274
- });
275
-
276
- //** executeCode
277
- async function executeCode(msg, send, done, node, resolvedTokens) {
278
-
279
- // Create a temporary file asynchronously
280
- const is_json = (node.outputFormat === "parsedJSON");
281
- let template = node.template || msg.template;
282
- const value = mustache.render(template, new NodeContext(msg, node.context(), null, is_json, resolvedTokens));
283
- const tmpObj = await tmp.file();
284
- await fsPromises.writeFile(tmpObj.path, value, 'utf8');
285
-
286
- let shellcode;
287
- let resolvedCommand
288
- if ( node.cmdTemplate ){
289
- resolvedCommand = await resolveCommand(node.cmd, msg);
290
- } else {
291
- resolvedCommand = node.cmd
292
- }
293
- if (process.platform === 'win32') { // For Windows
294
- shellcode = `${resolvedCommand} ${addPayload} ${tmpObj.path}`;
295
- } else { // For Linux and macOS
296
- shellcode = `
297
- export NODE_PATH="$NODE_PATH:$HOME/.node-red/node_modules:/usr/local/lib/node_modules"
298
- file="${tmpObj.path}"
299
- ${resolvedCommand}
300
- `;
301
- }
302
-
303
- try {
304
- if (!node.useSpawn) {
305
- await executeWithExec(shellcode, node, msg, send, done);
306
- } else {
307
- await executeWithSpawn(shellcode, node, msg, send, done);
308
- }
309
- } finally {
310
- await tmpObj.cleanup();
311
- }
312
- }
313
-
314
- async function executeWithExec(shellcode, node, msg, send, done) {
315
- return new Promise((resolve) => {
316
- const child = exec(shellcode, node.execOpt, (err, stdout, stderr) => {
317
- if (err) {
318
- const error = {
319
- type: 'error',
320
- code: err.code,
321
- killed: err.killed
322
- };
323
- msg.error_info = error;
324
- node.error(`error (${msg.nodeName})\n\n${stderr}`, msg);
325
- } else {
326
- if (stderr) {
327
- node.error(`warning (${msg.nodeName})\n\n${stderr}`, msg);
328
- }
329
-
330
- if (stdout) {
331
- stdout = stdout.trim();
332
- if (node.splitLine === false) {
333
- output(msg, stdout, send, done);
334
- } else {
335
- stdout = stdout.split('\n');
336
- for (let i = 0; i < stdout.length; i++) {
337
- node.emit("input", { "message": stdout[i] });
338
- }
339
- }
340
- }
341
- }
342
- delete node.activeProcesses[child.pid];
343
- resolve();
344
- });
345
- node.activeProcesses[child.pid] = child.pid;
346
- });
347
- }
348
-
349
- async function executeWithSpawn(shellcode, node, msg, send, done) {
350
- return new Promise((resolve, reject) => {
351
- const child = spawn('/bin/bash', ['-c', shellcode], node.spawnOpt);
352
- node.activeProcesses[child.pid] = child.pid;
353
-
354
- child.stdout.on('data', (data) => {
355
- data = data.toString();
356
- if (node.splitLine === false) {
357
- output(msg, data, send, done);
358
- } else {
359
- const lines = data.split('\n');
360
- for (let line of lines) {
361
- if (line) {
362
- node.emit("input", { "message": line });
363
- }
364
- }
365
- }
366
- });
367
-
368
- child.stderr.on('data', (data) => {
369
- node.error(`warning (${msg.nodeName})\n\n${data.toString()}`, msg);
370
- });
371
-
372
- child.on('close', (code) => {
373
- if (code !== 0) {
374
- const error = {
375
- type: 'error',
376
- code: code
377
- };
378
- msg.error_info = error;
379
- node.error(`error (${msg.nodeName}): The node hasn't finished its execution`, msg);
380
- reject(new Error(`Child process exited with code ${code}`));
381
- }
382
- delete node.activeProcesses[child.pid];
383
- resolve();
384
- });
385
- });
386
- }
387
-
388
- //** resolveTemplate
389
- async function resolveTemplate(msg) {
390
- var template = node.template;
391
- if (msg.hasOwnProperty("template")) {
392
- if (template == "" || template === null) {
393
- template = msg.template;
394
- }
395
- }
396
-
397
- var resolvedTokens = {};
398
- var tokens = extractTokens(mustache.parse(template));
399
-
400
- // Iterate over the extracted tokens to resolve their values.
401
- for (let name of tokens) {
402
- let env_name = parseEnv(name);
403
- if (env_name) {
404
- resolvedTokens[name] = RED.util.evaluateNodeProperty(env_name, 'env', node);
405
- continue;
406
- }
407
-
408
- // Check if the token refers to a flow or global context variable.
409
- let context = parseContext(name);
410
- if (context) {
411
- let type = context.type;
412
- let store = context.store;
413
- let field = context.field;
414
- let target = node.context()[type];
415
- if (target) {
416
- resolvedTokens[name] = await new Promise((resolve, reject) => {
417
- target.get(field, store, (err, val) => {
418
- if (err) reject(err);
419
- else resolve(val);
420
- });
421
- });
422
- }
423
- }
424
- }
425
-
426
- return resolvedTokens;
427
- }
428
-
429
- //** resolveCommand
430
- async function resolveCommand(command, msg) {
431
- return new Promise(async (resolve, reject) => {
432
- try {
433
- // Extract tokens from the command string
434
- var tokens = extractTokens(mustache.parse(command));
435
-
436
- var resolvedTokens = {};
437
-
438
- // Iterate over the extracted tokens to resolve their values.
439
- for (let name of tokens) {
440
- let env_name = parseEnv(name);
441
- if (env_name) {
442
- resolvedTokens[name] = RED.util.evaluateNodeProperty(env_name, 'env', node);
443
- continue;
444
- }
445
-
446
- // Check if the token refers to a flow or global context variable.
447
- let context = parseContext(name);
448
- if (context) {
449
- let type = context.type;
450
- let store = context.store;
451
- let field = context.field;
452
- let target = node.context()[type];
453
- if (target) {
454
- resolvedTokens[name] = await new Promise((innerResolve, innerReject) => {
455
- target.get(field, store, (err, val) => {
456
- if (err) innerReject(err);
457
- else innerResolve(val);
458
- });
459
- });
460
- }
461
- }
462
- }
463
-
464
- var parsedCommand = mustache.parse(command);
465
-
466
- // Extract the variable names from the parsed tokens.
467
- var variableNames = parsedCommand
468
- .filter(token => token[0] === '&')
469
- .map(token => token[1]);
470
-
471
- var resolvedValues = {};
472
-
473
- // Resolve the values for each variable.
474
- for (let variableName of variableNames) {
475
- let env_name = parseEnv(variableName);
476
- if (env_name) {
477
- resolvedValues[variableName] = RED.util.evaluateNodeProperty(env_name, 'env', node);
478
- continue;
479
- }
480
-
481
- // Check if the variable refers to a flow or global context variable.
482
- let context = parseContext(variableName);
483
- if (context) {
484
- let type = context.type;
485
- let store = context.store;
486
- let field = context.field;
487
- let target = node.context()[type];
488
- if (target) {
489
- resolvedValues[variableName] = await new Promise((innerResolve, innerReject) => {
490
- target.get(field, store, (err, val) => {
491
- if (err) innerReject(err);
492
- else innerResolve(val);
493
- });
494
- });
495
- }
496
- } else {
497
- // If the variable is not in the context or an env variable, try to get it from the msg object.
498
- resolvedValues[variableName] = msg[variableName];
499
- }
500
- }
501
-
502
- // Replace the variables in the original command with their resolved values.
503
- for (let variableName in resolvedValues) {
504
- command = command.replace(`{{{${variableName}}}}`, resolvedValues[variableName]);
505
- }
506
-
507
- console.log(command);
508
- resolve(command); // Use the resolve here
509
- } catch (error) {
510
- console.error("Error in resolveCommand:", error);
511
- reject(error); // Use the reject here
512
- }
513
- });
514
- }
515
-
516
- //** function: output
517
- function output(msg, value, send, done) {
518
- /* istanbul ignore else */
519
- let parseError = false
520
- //*** parse json
521
- if (node.outputFormat === "parsedJSON") {
522
- try {
523
- value = JSON.parse(value);
524
- if (typeof value === 'number') {
525
- parseError = true
526
- node.error('Error parsing JSON: \n\n' + error)
527
- }
528
- } catch (error) {
529
- parseError = true
530
- node.error('Error parsing JSON: \n\n' + error)
531
- }
532
- }
533
-
534
- //*** parse xml
535
- if (node.outputFormat === "parsedXML") {
536
- try {
537
- //value = JSON.parse(convertXML.xml2json(value, {compact: true, spaces: 4}))
538
- value = convertXML.xml2js(value, { compact: true, spaces: 4 })
539
- } catch (error) {
540
- parseError = true
541
- node.error('Error parsing XML: \n\n' + error)
542
- }
543
- }
544
-
545
- //*** parse yaml
546
- if (node.outputFormat === "parsedYAML") {
547
- try {
548
- value = yaml.load(value);
549
- if (typeof value === 'number') {
550
- parseError = true
551
- node.error('Error parsing YAML: \n\n' + error)
552
- }
553
- } catch (error) {
554
- parseError = true
555
- node.error('Error parsing YAML: \n\n' + error)
556
- }
557
- }
558
-
559
- //*** parse error
560
- if (parseError === false) {
561
- if (node.fieldType === 'msg') {
562
- RED.util.setMessageProperty(msg, node.field, value);
563
- send(msg);
564
- done();
565
- } else if ((node.fieldType === 'flow') ||
566
- (node.fieldType === 'global')) {
567
- var context = RED.util.parseContextStore(node.field);
568
- var target = node.context()[node.fieldType];
569
- target.set(context.key, value, context.store, function(err) {
570
- if (err) {
571
- done(err);
572
- } else {
573
- send(msg);
574
- done();
575
- }
576
- });
577
- }
578
- }
579
- }
580
-
581
- }
582
-
583
- //* end
584
- RED.nodes.registerType("thread.queue", ExecQueueNode);
585
- RED.library.register("templates");
586
- }