user6689821 user6689821 - 4 months ago 27
Node.js Question

readline.write() does not arrive at stdout

I'm trying to write a test for a CLI that uses Node.js's readline module to print and capture info from the user, and I can't seem to capture anything from stdout. A simple version of the issue I'm facing follows.

app.js:

#!/usr/bin/env node

const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})

rl.write('hello\n')
process.exit()


runner.js:

const spawn = require('child_process').spawn

const rl = spawn(__dirname + '/app.js')

rl.stdout.on('data', chunk => {
console.log('stdout says', chunk.toString())
})


running
runner.js
, I would expect to see the output
stdout says hello
, but nothing prints.

However, if I run
app.js
directly,
hello
prints out to the console. Additionally, if I use other readline methods such as
question
, the handler will fire with the expected data.

Why doesn't this code work as expected? How can it be changed to work?

Answer

Relate to Readline output to file Node.js

To capture the output of rl.write(), one solution is: define "terminal" as true when creating readline interface instance.

Sample code:

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: true
});

Explanation: readline module in node.js only write data to the "output" stream when "terminal" is true. Otherwise, it just emit "line" event, and send data to the line event handler. According to the source code (https://github.com/nodejs/node/blob/master/lib/readline.js):

First, Check whether "terminal" is configured. If not, make it equal to output stream's isTTY property:

if (terminal === undefined && !(output === null || output === undefined)) {
  terminal = !!output.isTTY;
}
...
this.terminal = !!terminal;

Second, when rl.write() function is invoked, it will call _ttyWrite() or _normalWrite(), depending on whether "terminal" is true:

Interface.prototype.write = function(d, key) {
  ...
  this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d);
};

Finally, if _ttyWrite() is called, data will be sent to output stream. If _normalWrite() is called, output stream is ignored:

//Interface.prototype._ttyWrite will invoke Interface.prototype._insertString, which will call Interface.prototype._writeToOutput
Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) {
  ...
  if (this.output !== null && this.output !== undefined)
    this.output.write(stringToWrite);
};

Thus, when app.js is run in console directly, "hello" will be printed, as "terminal" is equal to process.stdout.isTTY, which is true. However, when executed in child process, "terminal" is false (if not configured), as process.stdout.isTTY is undefined now.