How to start an interactive terminal window on mac using child_process library of node? - node.js

I want to execute a bash command - dotnet dev-certs https --trust in an interactive terminal through child_process library.
Since it asks for the user password, it has to be interactive terminal.
I've already tried using AppleScript for it, but the user experience is subpar since it tends to leave half-closed terminal windows around.
Edit - Added the code snippet that I am using to create a child_process.
import * as cp from 'child_process'
cp.spawn('dotnet dev-certs https --trust', {})
I've tried many many combinations of cp.spawn and cp.exec.
e.g. cp.spawn('...', { shell: true, stdio: 'ignore', detached: true }) etc.
cp.spawn actually creates a process, but it's not interactive and immediately terminates.

You have to filter the incoming data from stdout with stdout.on('data', data => {}) to find the shell request for user input. After you found that specific line you can simply send data to the shell via the stdin.write('any input \n')
import * as cp from 'child_process'
const ls = cp.spawn('dotnet dev-certs https --trust', {})
ls.stdout.on('data', function (data) {
const currentData = data.toString();
//check for password input notification:
if(currentData === "input credentials: ")
{
//send the password via stdin. \n does the trick over here.
ls.stdin.write('Password\n');
}
});

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.

Node.js - exec shell commands with .bashrc initialized

I'm writing a small utility tool for development to sync files over ssh. Normally I use ssh-agent set up in .bashrc file to connect to my dev server easily. I'd like to use exec in the script, but calling ssh-agent, every time I make a request sounds a bit inoptimal.
Is there a way I could execute the agent code once, and then have it working for all subsequent ssh requests I make? E.g. to spawn a shell process like a terminal emulator, and then use that process to execute a command, rather than invoking a new shell with each command.
The reason I want to do this, is I don't want to store the password in a config file.
You can create one ssh process, and then execute other commands using same process. Here is an example how to use it for bash. I'm creating a new bash shell and execugte the command ls -la and exit you can execute other commands.
const cp = require("child_process")
class MyShell {
constructor(command) {
this._spawned = cp.spawn(command, {
stdio: ["pipe", "pipe", "inherit"],
})
}
execute(command, callback) {
this._spawned.stdin.write(command + "\n")
this._spawned.stdout.on("data", (chunk) => {
if (callback) {
callback(chunk.toString())
}
})
}
}
var myShell = new MyShell("bash")
myShell.execute("ls -la", (result) => {
console.log(result)
})
myShell.execute("exit")

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"
})

Cannot get output of child_process.spawn with interactive scripts

I cannot get any output in the following code:
var spawn = require('child_process').spawn,
script = 'ftp',
child = spawn(script);
child.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
child.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
child.on('close', function (code) {
console.log('child process exited with code ' + code);
});
It works for normal scripts such as 'ls', 'pwd' etc. But not for interactive programs such as 'ftp', 'telnet'. Any suggestions?
Edit:
Take another script for example:
#!/usr/bin/env python
name = raw_input("your name>")
print name
When spawn this script, I wish to fetch the prompt "your name>" with the data event, so that I can latter input something into stdin.
The problem is that I got nothing in the data event, and it seemed that none of these events are triggered.
ls, cat is controllable via input output and error stream.
ftp, telnet is controllable indirectly via tty.
The protocol is also base on input/output stream but it is more complicated. You can use available package to handle that protocol.
https://github.com/chjj/pty.js
var pty = require('pty.js');
var term = pty.spawn('ftp', [], options);
term.on('data', function(data) {
console.log(data);
});
term.write(ftpCmd + '\r');
The author of pty have some interesting examples, he forward pty to web via web socket, including terminal games:
https://github.com/chjj/tty.js
In interactive mode there is a command interpreter that reads user input from stdin, then accodingly prints output. So you have to write to stdin to do something. For example add following lines to your code with telnet command:
child.stdin.write('?\n');
child.stdin.write('quit\n');
Output:
stdout: Commands may be abbreviated. Commands are:
! cr mdir proxy send
$ delete mget sendport site
account debug mkdir put size
append dir mls pwd status
ascii disconnect mode quit struct
bell form modtime quote system
binary get mput recv sunique
bye glob newer reget tenex
case hash nmap rstatus trace
ccc help nlist rhelp type
cd idle ntrans rename user
cdup image open reset umask
chmod lcd passive restart verbose
clear ls private rmdir ?
close macdef prompt runique
cprotect mdelete protect safe
child process exited with code 0

How do I open a terminal application from node.js?

I would like to be able to open Vim from node.js program running in the terminal, create some content, save and exit Vim, and then grab the contents of the file.
I'm trying to do something like this:
filename = '/tmp/tmpfile-' + process.pid
editor = process.env['EDITOR'] ? 'vi'
spawn editor, [filename], (err, stdout, stderr) ->
text = fs.readFileSync filename
console.log text
However, when this runs, it just hangs the terminal.
I've also tried it with exec and got the same result.
Update:
This is complicated by the fact that this process is launched from a command typed at a prompt with readline running. I completely extracted the relevant parts of my latest version out to a file. Here is it in its entirety:
{spawn} = require 'child_process'
fs = require 'fs'
tty = require 'tty'
rl = require 'readline'
cli = rl.createInterface process.stdin, process.stdout, null
cli.prompt()
filename = '/tmp/tmpfile-' + process.pid
proc = spawn 'vim', [filename]
#cli.pause()
process.stdin.resume()
indata = (c) ->
proc.stdin.write c
process.stdin.on 'data', indata
proc.stdout.on 'data', (c) ->
process.stdout.write c
proc.on 'exit', () ->
tty.setRawMode false
process.stdin.removeListener 'data', indata
# Grab content from the temporary file and display it
text = fs.readFile filename, (err, data) ->
throw err if err?
console.log data.toString()
# Try to resume readline prompt
cli.prompt()
The way it works as show above, is that it shows a prompt for a couple of seconds, and then launches in to Vim, but the TTY is messed up. I can edit, and save the file, and the contents are printed correctly. There is a bunch of junk printed to terminal on exit as well, and Readline functionality is broken afterward (no Up/Down arrow, no Tab completion).
If I uncomment the cli.pause() line, then the TTY is OK in Vim, but I'm stuck in insert mode, and the Esc key doesn't work. If I hit Ctrl-C it quits the child and parent process.
You can inherit stdio from the main process.
const child_process = require('child_process')
var editor = process.env.EDITOR || 'vi';
var child = child_process.spawn(editor, ['/tmp/somefile.txt'], {
stdio: 'inherit'
});
child.on('exit', function (e, code) {
console.log("finished");
});
More options here: http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
Update: My answer applied at the time it was created, but for modern versions of Node, look at this other answer.
First off, your usage of spawn isn't correct. Here are the docs. http://nodejs.org/docs/latest/api/child_processes.html#child_process.spawn
Your sample code makes it seem like you expect vim to automatically pop up and take over the terminal, but it won't. The important thing to remember is that even though you may spawn a process, it is up to you to make sure that the data from the process makes it through to your terminal for display.
In this case, you need to take data from stdin and send it to vim, and you need to take data output by vim and set it to your terminal, otherwise you won't see anything. You also need to set the tty into raw mode, otherwise node will intercept some of the key sequences, so vim will not behave properly.
Next, don't do readFileSync. If you come upon a case where you think you need to use a sync method, then chances are, you are doing something wrong.
Here's a quick example I put together. I can't vouch for it working in every single case, but it should cover most cases.
var tty = require('tty');
var child_process = require('child_process');
var fs = require('fs');
function spawnVim(file, cb) {
var vim = child_process.spawn( 'vim', [file])
function indata(c) {
vim.stdin.write(c);
}
function outdata(c) {
process.stdout.write(c);
}
process.stdin.resume();
process.stdin.on('data', indata);
vim.stdout.on('data', outdata);
tty.setRawMode(true);
vim.on('exit', function(code) {
tty.setRawMode(false);
process.stdin.pause();
process.stdin.removeListener('data', indata);
vim.stdout.removeListener('data', outdata);
cb(code);
});
}
var filename = '/tmp/somefile.txt';
spawnVim(filename, function(code) {
if (code == 0) {
fs.readFile(filename, function(err, data) {
if (!err) {
console.log(data.toString());
}
});
}
});
Update
I seeee. I don't think readline is as compatible with all of this as you would like unfortunately. The issue is that when you createInterface, node kind of assumes that it will have full control over that stream from that point forward. When we redirect that data to vim, readline is still there processing keypresses, but vim is also doing the same thing.
The only way around this that I see is to manually disable everything from the cli interface before you start vim.
Just before you spawn the process, we need to close the interface, and unfortunately manually remove the keypress listener because, at least at the moment, node does not remove it automatically.
process.stdin.removeAllListeners 'keypress'
cli.close()
tty.setRawMode true
Then in the process 'exit' callback, you will need to call createInterface again.
I tried to do something like this using Node's repl library - https://nodejs.org/api/repl.html - but nothing worked. I tried launching vscode and TextEdit, but on the Mac there didn't seem to be a way to wait for those programs to close. Using execSync with vim, nano, and micro all acted strangely or hung the terminal.
Finally I switched to using the readline library using the example given here https://nodejs.org/api/readline.html#readline_example_tiny_cli - and it worked using micro, e.g.
import { execSync } from 'child_process'
...
case 'edit':
const cmd = `micro foo.txt`
const result = execSync(cmd).toString()
console.log({ result })
break
It switches to micro in a Scratch buffer - hit ctrl-q when done, and it returns the buffer contents in result.

Resources