Inquirer.js icon indicating copy to clipboard operation
Inquirer.js copied to clipboard

'process.stdin' in nodejs is not working properly

Open Anmours opened this issue 1 year ago • 6 comments

In my program, I need to listen to the information entered by the user in the console and make other responses. In the past year, everything has been working normally, but today I used '@ inquirer/prompts' to change some programs. After using this npm package, the original 'process.stdin.on' no longer works

How should I solve it? Please help me

I wrote a simple code to illustrate the problem:

const { input, select } = require('@inquirer/prompts');

async function main() {
  /* When I commented out the first two lines, the following code works properly */
  const text = await input({ message: `your name?\n` });
  console.log('inputText', text);
  /**
   * My code is omitted
   */

  /* When I used 'input', 'process.stdin.on' no longer works */
  process.stdin.on('data', async inputs => {
    let text = inputs.toString().trim();
    if (!text) return;
    switch (text) {
      case 'cls':
        console.clear();
        break;
      case 'bye':
        process.exit(0);
      default:
        console.log('stdinText:', text);
        /* I will do some other logic here, but I just streamlined it out */
        break;
    }
  });
}

main();

Anmours avatar Feb 05 '24 12:02 Anmours

It's unclear what would cause this - here's what the do when releasing the readline: https://github.com/SBoudrias/Inquirer.js/blob/master/packages/core/src/lib/screen-manager.mts#L125-L129

I think I'm missing a lot of information about what your program is supposed to do to be able to help. You want to listen to data constantly or only after the input prompt is answered?

And can you send a PR with a failing unit test? This would go a long way allowing us to debug this issue.

SBoudrias avatar Feb 05 '24 18:02 SBoudrias

I had the same problem and found this issue. After playing around a bit with the sample code, I got it partially working with the following code.

process.stdin.removeAllListeners();
process.stdin.on('data', async inputs => {

After entering the name, you have to press Enter once before you can make an normal entry.

In my project, I use raw mode to listen for input, with this mode enabled, I don't have to press Enter before receiving user input.

LukasLeppich avatar Mar 22 '24 07:03 LukasLeppich

@LukasLeppich could you provide a minimal code reproduction?

I'd love to look into this and make sure nothing leaks out of Inquirer - or raise an issue to Node is discarding a readline doesn't reset the stdin properly. But it's a bit hard to go ahead with so few details.

SBoudrias avatar Mar 22 '24 13:03 SBoudrias

@SBoudrias You can use the code @Anmours posted above.

If you execute the script, it will ask for your name and exit afterward.

If you remove the const text = await input({ message: 'your name?\n'}); line, you can type stuff and exit with bye.

If you add the process.stdin.removeAllListeners();line in front of the process.stdin.on('data', ... line and keep the const text = await input({ message: 'your name?\n' }); line, it asks for your name, than you have to press Enter once and after that, you can type stuff and exit with bye again.

The correct behavior would be to ask for your name, than repeat everything you type with stdinText: ..., clear you screen if you type cls and exit the script if you type bye.

LukasLeppich avatar Mar 22 '24 15:03 LukasLeppich

Thanks! I'm looking into this.

You can also call process.stdin.resume() before setting the event listener. And weirdly enough that's what the Node.js doc recommend: https://nodejs.org/api/process.html#signal-events

Also this interesting quote:

In "old" streams mode the stdin stream is paused by default, so one must call process.stdin.resume() to read from it. Note also that calling process.stdin.resume() itself would switch stream to "old" mode.

SBoudrias avatar Mar 27 '24 20:03 SBoudrias

Yeah, pretty much what happens. In both scenario, if you log process.stdin.isPaused() you'll see it's where the difference lie.

So once a readline instance is closed, it'll pause process.stdin. Calling on won't resume it; this must be done manually. (I'm not quite sure how I could change this behavior, calling resume in Inquirer will prevent scripts from exiting)

SBoudrias avatar Mar 27 '24 20:03 SBoudrias

@SBoudrias @LukasLeppich I solved this problem a long time ago, I just didn't give feedback. Compared to the code when I asked the question, I optimized it like this:

const { input, select } = require('@inquirer/prompts');
const readline = require('readline');

async function main() {
  /* When I commented out the first two lines, the following code works properly */
  const text = await input({ message: `your name?\n` });
  console.log('Hello,', text);
  

  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  
  rl.on('SIGINT', () => {
    console.log('Ctrl+C Proactively withdraw');
    process.exit(1);
  });
  rl.on('line',  async inputs => {
    let text = inputs.toString().trim();
    if (!text) return;
    switch (text) {
      case 'cls':
        console.clear();
        break;
      case 'bye':
        process.exit(0);
      default:
        console.log('stdinText:', text);
        /* I will do some other logic here, but I just streamlined it out */
        break;
    }
  });
  
}

main();

After using the 'Inquirer', I created a 'STDin' using the built-in 'Readline' in Node.js, which worked well and solved my problem.

'creatInterface' must be used after 'await input', otherwise the problem persists

Hope it is helpful to others.

Anmours avatar Jul 13 '24 23:07 Anmours

createInterface must be used after 'await input', otherwise the problem persists

Yeah node:readline module only works when a single instance is created at a time; all events are duplicated otherwise and there's conflict with what either does.

SBoudrias avatar Jul 14 '24 16:07 SBoudrias