How do I open a terminal application from node.js? - 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.

Related

Using script + screen through child_process

Solved:
I had to add \r\n at program.stdin.write(data) (something like this program.stdin.write(data+'\r\n')) and it worked.
It seems that if i don't put \r\n, it doesn't triggers, its like typing in a line without pressing enter so it will never be processed.
===========================================================================
I need to access screen through child_process, but it doesn't works properly.
First I tried to access using spawn.
const {spawn} = require('child_process');
const program = spawn('screen',['-x User/Aplication']);
program.stdout.on('data',data=>{
//Something
})
function writeTo(data){
program.stdin.write(data);
}
But i got the error "Must be connected to a terminal error". After some research i found a solution, use script+spawn to make a pseudo-console.
const {spawn} = require('child_process');
const program = spawn('script',['/dev/null']);//Pseudo-console
program.stdin.write('screen -x User/Aplication');//I access to screen through the pseudo-console, and it works.
program.stdout.on('data',data=>{
//Something
})
function writeTo(data){
program.stdin.write(data);
}
But... when I try to use writeTo, it doesn't works.
writeTo('Some command here')//Does nothing.
And somehow, when I pipe my console input, it works!
process.stdin.pipe(program.stdin);
Then I type something in my console and it proxies properly to connected screen.
Issue: It doesn't proxies properly when using program.stdin.write, but somehow, it works when i pipe my console process.stdin.pipe(program.stdin)
Observation 1: I made a short echo-program and it worked with both program.stdin.write and process.stdin.pipe(program.stdin)
echo.js
process.stdin.on('data',data=>{
console.log(`[Input]${data}`);
})
main.js
const {spawn} = require('child_process');
const program = spawn('node',['echo.js']);
program.stdout.pipe(process.stdout);
function writeTo(data){
program.stdin.write(data);
}
writeTo('Test');//Output: [Input]Test
process.stdin.pipe(program.stdin);//I type 'something'. Output: [Input]something
Observation 2: When using script+screen and piping my console, program.stdin.write only 'buffers' and process.stdin.pipe loads that buffer and sends it with what i typed.
program.stdin.write('He');//screen receives nothing
program.stdin.write('llo');//screen receives nothing
process.stdin.pipe(program.stdin);//I type ' world!'. screen receives 'Hello world!'
it may not be the whole problem, but the second argument to spawn should have each argument in a separate array element.
const program = spawn('screen',['-x', 'User/Aplication']);
I had to add \r\n at program.stdin.write(data) (something like this program.stdin.write(data+'\r\n')) and it worked.
It seems that if i don't put \r\n, it doesn't triggers as a new line and it doesn't sends it, its like typing all commands in a line without pressing enter so it will never be processed.

node.js: Trouble using a systemcall to write a file to the /tmp directory

As an exercise, I'm trying to use a systemcall from node.js to write a small text file to the /tmp directory. Here is my code:
#!/bin/node
var child_process = require("child_process");
var send = "Hello, world!";
child_process.exec('cat - > /tmp/test1', { input: send });
The file actually gets created; but, no content is placed in it. Things just hang. Can someone please tell me what I'm missing?
Also, I'd really like to know how to do this synchronously.
Thanks for any input.
... doug
hm unless i forgot to rtm too, this code will just never work. There is no such input option for cp.exec.
But there is a stdio option, will let us open the expected stdio on the child.
child_process.exec('cat - > /tmp/test1', { stdio: 'pipe' });
see https://nodejs.org/api/child_process.html#child_process_options_stdio
stdios are not string, they are streams, which we can end / write / pipe / close / push etc
see https://nodejs.org/api/stream.html
Note that stdin is a writable, stdout / stderr are readable.
To write the stdin of cat you ll now consume the cp.stdin object and call for its end() method.
child_process.exec('cat - > /tmp/test1', { stdio: 'pipe' }).stdin.end('hello world');
Note that end method is a write followed by a termination of the stream, which is required to tell cat to quit.
To ensure this is working well, we should refactor it, to not send stdin to a file, instead pipe child.stdout to the process.stdout.
var child_process = require('child_process');
var cp = child_process.exec('cat -', { stdio: 'pipe' });
cp.stdin.end('hello world');
cp.stdout.pipe(process.stderr);
Note that process is a global.
I finally got my original approach to work. The big stumbling block is to know that the synchronous methods are only available in version 0.12 (and later) of node.js. Here is the code that I finally got to work:
#!/usr/local/n/versions/node/0.12.14/bin/node
var child_process = require('child_process');
var send = "Hello, world!"
child_process.execSync('cat - > /tmp/test1', { input : send }).toString();
Thanks to all for the help.
... doug

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

Clear terminal window in Node.js readline shell

I have a simple readline shell written in Coffeescript:
rl = require 'readline'
cli = rl.createInterface process.stdin, process.stdout, null
cli.setPrompt "hello> "
cli.on 'line', (line) ->
console.log line
cli.prompt()
cli.prompt()
Running this displays a prompt:
$ coffee cli.coffee
hello>
I would like to be able to hit Ctrl-L to clear the screen. Is this possible?
I have also noticed that I cannot hit Ctrl-L in either the node or coffee REPLs either.
I am running on Ubuntu 11.04.
You can watch for the keypress yourself and clear the screen.
process.stdin.on 'keypress', (s, key) ->
if key.ctrl && key.name == 'l'
process.stdout.write '\u001B[2J\u001B[0;0f'
Clearing is done with ASCII control sequences like those written here:
http://ascii-table.com/ansi-escape-sequences-vt-100.php
The first code \u001B[2J instructs the terminal to clear itself, and the second one \u001B[0;0f forces the cursor back to position 0,0.
Note
The keypress event is no longer part of the standard Node API in Node >= 0.10.x but you can use the keypress module instead.
In the MAC terminal, to clear the console in NodeJS, you just hit COMMAND+K just like in Google Developer Tools Console so I'm guessing that on Windows it would be CTRL+K.
This is the only answer that will clear the screen AND scroll history.
function clear() {
// 1. Print empty lines until the screen is blank.
process.stdout.write('\033[2J');
// 2. Clear the scrollback.
process.stdout.write('\u001b[H\u001b[2J\u001b[3J');
}
// Try this example to see it in action!
(function loop() {
let i = -40; // Print 40 lines extra.
(function printLine() {
console.log('line ' + (i + 41));
if (++i < process.stdout.columns) {
setTimeout(printLine, 40);
}
else {
clear();
setTimeout(loop, 3000);
}
})()
})()
The first line ensures the visible lines are always cleared.
The second line ensures the scroll history is cleared.
Try also:
var rl = require('readline');
rl.cursorTo(process.stdout, 0, 0);
rl.clearScreenDown(process.stdout);
On response to #loganfsmyth comment on his answer (thanks for the edit!).
I have been looking here and there and, besides of the wonderfull keypress module, there is a core module that makes possible to create a cli with all standard terminal behavior (all things we give for granted today such as history, options to provide an auto-complete function and input events such as keypress are there).
The module is readline (documentation). The good news is that all the standar behaviour is already done for us so there is no need to attach event handlers (i.e. history, clearing the screen on Ctrl+L, man if you provided the auto complete function it'll be on Tabpress).
Just as an example
var readline = require('readline')
, cli = readline.createInterface({
input : process.stdin,
output : process.stdout
});
var myPrompt = ' > myPropmt '
cli.setPrompt(myPrompt, myPrompt.length);
// prompt length so you can use "color" in your prompt
cli.prompt();
// Display ' > myPrompt ' with all standard features (history also!)
cli.on('line', function(cmd){ // fired each time the input has a new line
cli.prompt();
})
cli.input.on('keypress', function(key){ // self explanatory
// arguments is a "key" object
// with really nice properties such as ctrl : false
process.stdout.write(JSON.stringify(arguments))
});
Really good discovery.
The node version I'm using is v0.10.29. I have been looking at the changelog and it was there since 2010 (commit 10d8ad).
You can clear screen using console.log() and escape sequences.
cli.on 'line', (line) ->
if line == 'cls'
console.log("\033[2J\033[0f")
else
console.log line
cli.prompt()
Vorpal.js makes things like this really easy.
For an interactive CLI with a clear command as well as a REPL within the context of your application, do this:
var vorpal = require('vorpal')();
var repl = require('vorpal-repl');
vorpal
.delimiter('hello>')
.use(repl)
.show();
vorpal
.command('clear', 'Clears the screen.')
.action(function (args, cb) {
var blank = '';
for (var i = 0; i < process.stdout.rows; ++i) {
blank += '\n';
}
vorpal.ui.rewrite(blank);
vorpal.ui.rewrite('');
cb();
});
If you are using readline.createInterface you could do something like:
function clearPrompt() {
rl.setPrompt("");
rl.prompt();
}
Note this won't clear the entire terminal, just the readline prompt. Still useful if you want the user to keep their place in the terminal

node.js using spawn for perl - stdout line by line

Spawn in nodeJS. I have just about managed to use this to run a bash command as follows. This seems to be pretty much non-blocing and I get action on the browser screen as the command trashes through data.
ls = spawn('find',['/'] );
response.writeHead(200, { "Content-Type": "text/plain" });
ls.stdout.on('data', function (data) {
console.log('stdout: ' + data);
response.write(data);
});
But I want to run a perl script with multiple arguments.
ls = spawn('blah.pl',['--argstring here', '--arg blah'] );
Perl script is just written to get arguments using getopts CPAN lib and it using CPAN expect lib to run though a pile of stuff - outputs to stdout and stderr if I have an error but I mostly care about stdout right now.
The thing is this is giving me no output. Seems to be completely blocking at least until the program finishes execution ... and it this case it doesn't at least for 10 mins.
Am I using spawn wrong?
I like the node module "carrier"
carrier = require "carrier"
childproc = require "child_process"
find = childproc.spawn "find"
find.stdout.setEncoding "utf8"
linereader = carrier.carry find.stdout
linereader.on "line", (line) -> console.log line

Resources