How can I handle an input prompt with node when executing a command via child_process? - node.js

For context, I'm on a Mac and I'm trying to script a 1Password CLI signin via their command-line tool. I'm trying to programmatically signing using a command that looks like:
op signin <signinaddress> <emailaddress> <secretkey> --output=raw
and I've tried with/without the --output=raw argument, but every time I simply get an error that looks like
[LOG] 2019/06/04 00:57:45 (ERROR) operation not supported on socket
child process exited with code 1
My initial hunch was that it had something to do with the command executions prompt displaying this special key character in the following image:
The relevant code is written in TypeScript and looks like this:
import { spawn } from 'child_process'
// ends up being `op signin <signinaddress> <emailaddress> <secretkey>`
const op = spawn(opExecutable, args);
let result: string | null = null
op.on('message', (message, sendHandle) => {
console.log('message', message, sendHandle)
});
op.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
if (data && typeof data.toString === 'function') {
result = data.toString()
}
});
op.on('close', (code, ...args) => {
console.log(`child process exited with code ${code}`, args);
});
Eventually, I'd like to run on all platforms and be able pass in stdin for the master password required to sign in, but I'm trying to figure out why my node app is crashing first :)

Apparently I was pretty close to a solution by using spawn, but I needed to specify configuration for stdio. Here's an example snippet of how I used spawn that worked for me:
const proc = spawn(
cmd, // the command you want to run, in my case `op`
args, // arguments you want to use with the above cmd `signin`, etc.
{
stdio: [
'inherit', // stdin: changed from the default `pipe`
'pipe', // stdout
'inherit' // stderr: changed from the default `pipe`
]
});

Related

Can't seem to figure out how to get the output of an process fired with spawn. stdout.on('data') not outputting for me

I am trying to setup some automation on a game server for the game Rust.
The game server itself is ran by running its executable file RustDedicated.exe with some arguments.
According to some googling and reading here on Stack Overflow I have made this script:
import config from "config";
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
const GAMESERVERPATH: string = config.get("Environment.RustDedicatedPath");
const EXECUTABLE: string = config.get("Environment.ExecutableFile");
const GAMESERVERARGS: Array<string> = [
"-batchmode",
"+server.port", `${config.get("Server.port")}`,
"+server.level", `"${config.get("Server.level")}"`,
"+server.seed", `${config.get("Server.seed")}`,
"+server.worldsize", `${config.get("Server.worldsize")}`,
"+server.maxplayers", `${config.get("Server.maxplayers")}`,
"+server.hostname", `"${config.get("Server.hostname")}"`,
"+server.description", `"${config.get("Server.description")}"`,
"+server.headerimage", `"${config.get("Server.headerimage")}"`,
"+rcon.port", `${config.get("Rcon.port")}`,
"+rcon.password", `"${config.get("Rcon.password")}"`,
"+rcon.web", `${config.get("Rcon.web")}`
];
const gameServerProc : ChildProcessWithoutNullStreams = spawn(
GAMESERVERPATH+EXECUTABLE,
GAMESERVERARGS,
{
cwd: GAMESERVERPATH,
shell: true,
}
);
gameServerProc.stdout.on("data", (data) => {
console.log(`stdout:${data.toString()}`);
});
gameServerProc.stderr.on("data", (data) => {
console.log(`stderr:${data.toString()}`);
});
gameServerProc.on("error", (err) => {
console.log(`error:${err.message}`);
});
What is happening is that i can see the output of the executable in the terminal window, and the server is firing without errors, but it seems that stdout is not firing the on('data') event.
I never see stdout:.
See the screenshot below where i have Code open, the output is on the bottom right.
Why is my script failing to get the on('data') firing when the executable outputs?
This issue had nothing to do with the code, but rather the game engine Unity. An extra argument -logFile - had to be present for the executable to output to stdout.

Nodejs - best way to know a service status [linux]

Do someone know the best way (or just a good one) to know a service status from a linux (centos, here) system ?
When i run this piece of code:
{ ... }
const { spawnSync } = require('child_process'),
ts3 = spawnSync('service teamspeak status | grep active'),
{ ... },
This throw me a ENOENT error. I got the same error from my windows system when I tried a simple dir command, I had to write a stupid cmd file named "dir.cmd" with the content "dir" in my system32 (or any dir in the path env variable) and replace
dir = spawnSync('dir'),
By
dir = spawnSync('dir.cmd'), //This file is now in a dir in the PATH env var
So, i think this is related to a no-auto-resolution of the files with a sh,cmd or something else extention
But this isn't working when I replace the "service" by a "service.sh" anyway (from the first piece of code)
So, maybe someone already did this before and can help me a bit ?
Thanks,
And have a nice day !
A couple of issues, first, when using spawn, you should pass the arguments withing an array. Second, you're trying to run two processes within one spawn.
Instead, you can break down two processes and use the stdout from the first process (service), as the stdin for the second one (grep). I believe this should do it:
const { spawn } = require('child_process');
const service = spawn('service', ['teamspeak', 'status']);
const grep = spawn('grep', ['active']);
service.stdout.on('data', (data) => {
grep.stdin.write(data);
});
service.stderr.on('data', (data) => {
console.error(`service stderr: ${data}`);
});
service.on('close', (code) => {
if (code !== 0) {
console.log(`ps process exited with code ${code}`);
}
grep.stdin.end();
});
grep.stdout.on('data', (data) => {
console.log(data.toString());
});
grep.stderr.on('data', (data) => {
console.error(`grep stderr: ${data}`);
});
grep.on('close', (code) => {
if (code !== 0) {
console.log(`grep process exited with code ${code}`);
}
});
Hope this helped.

Use child_process.execSync but keep output in console

I'd like to use the execSync method which was added in NodeJS 0.12 but still have the output in the console window from which i ran the Node script.
E.g. if I run a NodeJS script which has the following line I'd like to see the full output of the rsync command "live" inside the console:
require('child_process').execSync('rsync -avAXz --info=progress2 "/src" "/dest"');
I understand that execSync returns the ouput of the command and that I could print that to the console after execution but this way I don't have "live" output...
You can pass the parent´s stdio to the child process if that´s what you want:
require('child_process').execSync(
'rsync -avAXz --info=progress2 "/src" "/dest"',
{stdio: 'inherit'}
);
You can simply use .toString().
var result = require('child_process').execSync('rsync -avAXz --info=progress2 "/src" "/dest"').toString();
console.log(result);
Edit: Looking back on this, I've realised that it doesn't actually answer the specific question because it doesn't show the output to you 'live' — only once the command has finished running.
However, I'm leaving this answer here because I know quite a few people come across this question just looking for how to print the result of the command after execution.
Unless you redirect stdout and stderr as the accepted answer suggests, this is not possible with execSync or spawnSync. Without redirecting stdout and stderr those commands only return stdout and stderr when the command is completed.
To do this without redirecting stdout and stderr, you are going to need to use spawn to do this but it's pretty straight forward:
var spawn = require('child_process').spawn;
//kick off process of listing files
var child = spawn('ls', ['-l', '/']);
//spit stdout to screen
child.stdout.on('data', function (data) { process.stdout.write(data.toString()); });
//spit stderr to screen
child.stderr.on('data', function (data) { process.stdout.write(data.toString()); });
child.on('close', function (code) {
console.log("Finished with code " + code);
});
I used an ls command that recursively lists files so that you can test it quickly. Spawn takes as first argument the executable name you are trying to run and as it's second argument it takes an array of strings representing each parameter you want to pass to that executable.
However, if you are set on using execSync and can't redirect stdout or stderr for some reason, you can open up another terminal like xterm and pass it a command like so:
var execSync = require('child_process').execSync;
execSync("xterm -title RecursiveFileListing -e ls -latkR /");
This will allow you to see what your command is doing in the new terminal but still have the synchronous call.
Simply:
try {
const cmd = 'git rev-parse --is-inside-work-tree';
execSync(cmd).toString();
} catch (error) {
console.log(`Status Code: ${error.status} with '${error.message}'`;
}
Ref: https://stackoverflow.com/a/43077917/104085
// nodejs
var execSync = require('child_process').execSync;
// typescript
const { execSync } = require("child_process");
try {
const cmd = 'git rev-parse --is-inside-work-tree';
execSync(cmd).toString();
} catch (error) {
error.status; // 0 : successful exit, but here in exception it has to be greater than 0
error.message; // Holds the message you typically want.
error.stderr; // Holds the stderr output. Use `.toString()`.
error.stdout; // Holds the stdout output. Use `.toString()`.
}
When command runs successful:
Add {"encoding": "utf8"} in options.
execSync(`pwd`, {
encoding: "utf8"
})

NodeJS: Make it possible for user input when running child_process

I'm trying to create a Node script that will ask the user a few questions, save the input and using the answers run a mvn archetype:generate command to install their environment.
I got as far to where I get the Maven command running. But when Maven asks for user input for values such as groupId and `` I can enter the values, give an [enter] and that's where it stops.
It doesn't take input and process them. All it does is display it, as the CLI does, but doesn't accept them.
Here's a snippet of code with the values for user input pre-filled:
var spawn = require('child_process').spawn;
var answerCollection = {
"name": "nameOfMyArchetype", //answer of inquiry
"version": "1.2.3.4" //answer of inquiry
};
var cmd = "mvn";
var args = [
"archetype:generate",
"-DarchetypeArtifactId=" + answerCollection.name,
"-DarchetypeGroupId=com.backbase.expert.tools",
"-DarchetypeVersion=" + answerCollection.version
];
var runCmd = function(cmd, args, callback) {
var child = spawn(cmd, args);
child.stdin.pipe(process.stdin);
child.stdout.pipe(process.stdout);
child.stdout.on('end', function(res) {
console.log("stdout:end");
callback(res);
});
child.stderr.on('data', function(text) {
console.log("stderr:data");
console.log(data);
});
child.stderr.on('exit', function(data) {
console.log("stderr:exit");
console.log(data);
});
};
So far I've tried the above code with child_process and spawn = require('child_process').spawn('bash').
Question: Is there any other way to make sure I can trigger a script and if that returns with a prompt and asks for input I can type and enter and the script will continue?
From Facebook I got this tip to use cross-spawn, instead of child_process:
From Robert Haritonov:
Use cross-spawn: spawn('bower', bowerCommand, {stdio:'inherit'}).on('close', function () {});
This works perfectly well and provides exactly the behaviour I need.

How to respond to a command line promt with node.js

How would I respond to a command line prompt programmatically with node.js? For example, if I do process.stdin.write('sudo ls'); The command line will prompt for a password. Is there an event for 'prompt?'
Also, how do I know when something like process.stdin.write('npm install') is complete?
I'd like to use this to make file edits (needed to stage my app), deploy to my server, and reverse those file edits (needed for eventually deploying to production).
Any help would rock!
You'll want to use child_process.exec() to do this rather than writing the command to stdin.
var sys = require('sys'),
exec = require('child_process').exec;
// execute the 'sudo ls' command with a callback function
exec('sudo ls', function(error, stdout, stderr){
if (!error) {
// print the output
sys.puts(stdout);
} else {
// handle error
}
});
For the npm install one you might be better off with child_process.spawn() which will let you attach an event listener to run when the process exits. You could do the following:
var spawn = require('child_process').spawn;
// run 'npm' command with argument 'install'
// storing the process in variable npmInstall
var npmInstall = spawn('npm', ['install'], {
cwd: process.cwd(),
stdio: 'inherit'
});
// listen for the 'exit' event
// which fires when the process exits
npmInstall.on('exit', function(code, signal) {
if (code === 0) {
// process completed successfully
} else {
// handle error
}
});

Resources