From fb4f515c6e663ae887211bab433547b2a5ae1428 Mon Sep 17 00:00:00 2001 From: Gerrit Riessen Date: Fri, 13 Sep 2024 16:12:55 +0200 Subject: [PATCH 1/3] changes to add stdin support to the exec node --- .../nodes/core/function/90-exec.html | 12 +++++++ .../@node-red/nodes/core/function/90-exec.js | 32 +++++++++++++++++-- .../nodes/locales/en-US/messages.json | 8 +++-- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/90-exec.html b/packages/node_modules/@node-red/nodes/core/function/90-exec.html index 137807d2f..1042a7808 100644 --- a/packages/node_modules/@node-red/nodes/core/function/90-exec.html +++ b/packages/node_modules/@node-red/nodes/core/function/90-exec.html @@ -23,6 +23,11 @@ + +
@@ -57,6 +62,7 @@ defaults: { command: {value:""}, addpay: {value:"", validate: RED.validators.typedInput({ type: 'msg', allowBlank: true })}, + addpayTo: { value: "parameter" }, append: {value:""}, useSpawn: {value:"false"}, timer: {value:""}, @@ -84,10 +90,16 @@ if ($("#node-input-useSpawn").val() === null) { $("#node-input-useSpawn").val(this.useSpawn.toString()); } + + if ($("#node-input-addpayTo").val() === null) { + $("#node-input-addpayTo").val(this.addpayTo.toString()); + } + $("#node-input-addpay-cb").prop("checked", this.addpay === true || (this.addpay !== false && this.addpay !== "")) var addpayValue = (this.addpay === true)?"payload":((this.addpay === false || this.addpay === "")?"payload":this.addpay); $("#node-input-addpay-cb").on("change", function(evt) { $("#node-input-addpay").typedInput("disable",!$("#node-input-addpay-cb").prop("checked")); + $("#node-input-addpayTo").prop("disabled", !$("#node-input-addpay-cb").prop("checked")); }); $("#node-input-addpay").val(addpayValue); diff --git a/packages/node_modules/@node-red/nodes/core/function/90-exec.js b/packages/node_modules/@node-red/nodes/core/function/90-exec.js index 70aec8d2b..df418a6ee 100644 --- a/packages/node_modules/@node-red/nodes/core/function/90-exec.js +++ b/packages/node_modules/@node-red/nodes/core/function/90-exec.js @@ -20,6 +20,8 @@ module.exports = function(RED) { var exec = require('child_process').exec; var fs = require('fs'); var isUtf8 = require('is-utf8'); + var stream = require('stream') + const isWindows = process.platform === 'win32' function ExecNode(n) { @@ -30,6 +32,12 @@ module.exports = function(RED) { if (this.addpay === true) { this.addpay = "payload"; } + + this.addpayTo = n.addpayTo; + if ( this.addpayTo === undefined ) { + this.addpayTo = "parameter" + } + this.append = (n.append || "").trim(); this.useSpawn = (n.useSpawn == "true"); this.timer = Number(n.timer || 0)*1000; @@ -69,7 +77,7 @@ module.exports = function(RED) { // make the extra args into an array // then prepend with the msg.payload var arg = node.cmd; - if (node.addpay) { + if (node.addpay && node.addpayTo == "parameter") { var value = RED.util.getMessageProperty(msg, node.addpay); if (value !== undefined) { arg += " " + value; @@ -87,7 +95,7 @@ module.exports = function(RED) { }); var cmd = arg.shift(); // Since 18.20.2/20.12.2, it is invalid to call spawn on Windows with a .bat/.cmd file - // without using shell: true. + // without using shell: true. const opts = isWindows ? { ...node.spawnOpt, shell: true } : node.spawnOpt /* istanbul ignore else */ node.debug(cmd+" ["+arg+"]"); @@ -137,6 +145,16 @@ module.exports = function(RED) { node.error(code,RED.util.cloneMessage(msg)); } }); + + if (node.addpay && node.addpayTo == "stdin") { + var value = RED.util.getMessageProperty(msg, node.addpay); + if (value !== undefined) { + var stdinStream = new stream.Readable(); + stdinStream.push(value); // Add data to the internal queue for users of the stream to consume + stdinStream.push(null); // Signals the end of the stream (EOF) + stdinStream.pipe(child.stdin); + } + } } else { /* istanbul ignore else */ @@ -181,6 +199,16 @@ module.exports = function(RED) { child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer); } node.activeProcesses[child.pid] = child; + + if (node.addpay && node.addpayTo == "stdin") { + var value = RED.util.getMessageProperty(msg, node.addpay); + if (value !== undefined) { + var stdinStream = new stream.Readable(); + stdinStream.push(value); // Add data to the internal queue for users of the stream to consume + stdinStream.push(null); // Signals the end of the stream (EOF) + stdinStream.pipe(child.stdin); + } + } } } }); diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index bc89992e2..01fcf1779 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -233,14 +233,18 @@ "stdout": "stdout", "stderr": "stderr", "retcode": "return code", - "winHide": "Hide console" + "winHide": "Hide console", + "as": "as" + }, "placeholder": { "extraparams": "extra input parameters" }, "opt": { "exec": "when the command is complete - exec mode", - "spawn": "while the command is running - spawn mode" + "spawn": "while the command is running - spawn mode", + "parameter": "parameter", + "stdin": "stdin" }, "oldrc": "Use old style output (compatibility mode)" }, From ad230c99b73fefea14d6cc4e7e4371ce1bcdcc4b Mon Sep 17 00:00:00 2001 From: Gerrit Riessen Date: Mon, 24 Feb 2025 22:15:19 +0100 Subject: [PATCH 2/3] check for value type and convert anything that isn't a buffer or string --- .../node_modules/@node-red/nodes/core/function/90-exec.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/node_modules/@node-red/nodes/core/function/90-exec.js b/packages/node_modules/@node-red/nodes/core/function/90-exec.js index df418a6ee..64d235150 100644 --- a/packages/node_modules/@node-red/nodes/core/function/90-exec.js +++ b/packages/node_modules/@node-red/nodes/core/function/90-exec.js @@ -150,6 +150,9 @@ module.exports = function(RED) { var value = RED.util.getMessageProperty(msg, node.addpay); if (value !== undefined) { var stdinStream = new stream.Readable(); + if (!Buffer.isBuffer(value) && (typeof value) !== 'string') { + value = JSON.stringify(value) + } stdinStream.push(value); // Add data to the internal queue for users of the stream to consume stdinStream.push(null); // Signals the end of the stream (EOF) stdinStream.pipe(child.stdin); @@ -204,6 +207,9 @@ module.exports = function(RED) { var value = RED.util.getMessageProperty(msg, node.addpay); if (value !== undefined) { var stdinStream = new stream.Readable(); + if (!Buffer.isBuffer(value) && (typeof value) !== 'string') { + value = JSON.stringify(value) + } stdinStream.push(value); // Add data to the internal queue for users of the stream to consume stdinStream.push(null); // Signals the end of the stream (EOF) stdinStream.pipe(child.stdin); From bf6febf889637df77f905c2f49deefe8449afda3 Mon Sep 17 00:00:00 2001 From: Gerrit Riessen Date: Mon, 24 Feb 2025 22:15:56 +0100 Subject: [PATCH 3/3] added spec tests for the payload as stdin for the exec node --- test/nodes/core/function/90-exec_spec.js | 244 +++++++++++++++++++++++ 1 file changed, 244 insertions(+) diff --git a/test/nodes/core/function/90-exec_spec.js b/test/nodes/core/function/90-exec_spec.js index c397ea27d..bd31ee126 100644 --- a/test/nodes/core/function/90-exec_spec.js +++ b/test/nodes/core/function/90-exec_spec.js @@ -970,4 +970,248 @@ describe('exec node', function() { }); }); + + describe('calling exec with addpayTo', function() { + it('handle buffer payloads', function(done) { + var flow = [{ + id:"n1", + type:"exec", + wires:[["n2"],["n3"],["n4"]], + command: undefined, + addpay: "payload", + addpayTo: "stdin", + append:"", + useSpawn:"false", + oldrc:"false" + },{ + id:"n2", + type:"helper" + },{ + id:"n3", + type:"helper" + },{ + id:"n4", + type:"helper" + }]; + + var expected; + if (osType === "Windows_NT") { + flow[0].command = "cmd /C cat" + expected = "this payload goes to stdin\r\n"; + } else { + flow[0].command = "cat" + expected = "this payload goes to stdin\n"; + } + + helper.load(execNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + var n4 = helper.getNode("n4"); + var received = 0; + var messages = [null,null]; + + var completeTest = function() { + received = received + 1; + if (received < 2) { + return; + } + + try { + var msg = messages[0]; + msg.should.have.property("payload"); + Buffer.isBuffer(msg.payload).should.be.true(); + + msg.payload[0].should.be.eql(0x01) + msg.payload[1].should.be.eql(0x02) + msg.payload[2].should.be.eql(0x03) + msg.payload[3].should.be.eql(0x88) + + msg = messages[1]; + msg.should.have.property("payload"); + msg.payload.should.have.property("code",0); + + done(); + } + catch(err) { + done(err); + } + }; + n2.on("input", function(msg) { + messages[0] = msg; + completeTest(); + }); + n3.on("input", function(msg) { + // stderr wire should not receive msg + expect("should not be").to.eql("called") + }); + n4.on("input", function(msg) { + messages[1] = msg; + completeTest(); + }); + + n1.receive({payload:Buffer.from([0x01,0x02,0x03,0x88])}); + }) + }) + + it('handle string payloads', function(done) { + var flow = [{ + id:"n1", + type:"exec", + wires:[["n2"],["n3"],["n4"]], + command: undefined, + addpay: "payload", + addpayTo: "stdin", + append:"", + useSpawn:"false", + oldrc:"false" + },{ + id:"n2", + type:"helper" + },{ + id:"n3", + type:"helper" + },{ + id:"n4", + type:"helper" + }]; + + var expected; + if (osType === "Windows_NT") { + flow[0].command = "cmd /C cat" + expected = "this payload goes to stdin\r\n"; + } else { + flow[0].command = "cat" + expected = "this payload goes to stdin\n"; + } + + helper.load(execNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + var n4 = helper.getNode("n4"); + var received = 0; + var messages = [null,null]; + + var completeTest = function() { + received = received + 1; + if (received < 2) { + return; + } + + try { + var msg = messages[0]; + msg.should.have.property("payload"); + msg.payload.should.be.a.String(); + msg.payload.should.equal("this payload goes to stdin"); + + msg = messages[1]; + msg.should.have.property("payload"); + msg.payload.should.have.property("code",0); + + done(); + } + catch(err) { + done(err); + } + }; + n2.on("input", function(msg) { + messages[0] = msg; + completeTest(); + }); + n3.on("input", function(msg) { + // stderr wire should not receive msg + expect("should not be").to.eql("called") + }); + n4.on("input", function(msg) { + messages[1] = msg; + completeTest(); + }); + + n1.receive({payload:"this payload goes to stdin"}); + }) + + + }) + + it('handle array as type for payload', function(done) { + var flow = [{ + id:"n1", + type:"exec", + wires:[["n2"],["n3"],["n4"]], + command: undefined, + addpay: "payload", + addpayTo: "stdin", + append:"", + useSpawn:"false", + oldrc:"false" + },{ + id:"n2", + type:"helper" + },{ + id:"n3", + type:"helper" + },{ + id:"n4", + type:"helper" + }]; + + var expected; + if (osType === "Windows_NT") { + flow[0].command = "cmd /C cat" + expected = "this payload goes to stdin\r\n"; + } else { + flow[0].command = "cat" + expected = "this payload goes to stdin\n"; + } + + helper.load(execNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + var n4 = helper.getNode("n4"); + var received = 0; + var messages = [null,null]; + + var completeTest = function() { + received = received + 1; + if (received < 2) { + return; + } + + try { + var msg = messages[0]; + msg.should.have.property("payload"); + msg.payload.should.be.a.String(); + msg.payload.should.equal('[1,2,3,136]'); + + msg = messages[1]; + msg.should.have.property("payload"); + msg.payload.should.have.property("code",0); + + done(); + } + catch(err) { + done(err); + } + }; + n2.on("input", function(msg) { + messages[0] = msg; + completeTest(); + }); + n3.on("input", function(msg) { + // stderr wire should not receive msg + expect("should not be").to.eql("called") + }); + n4.on("input", function(msg) { + messages[1] = msg; + completeTest(); + }); + + n1.receive({payload:[0x01,0x02,0x03,0x88]}); + }) + + }) + + }) });