Running `prompt` inside `Prompt._run` crashes the process
I've been trying to create a prompt for asking for a list of certain objects.
For that reason I created a custom Prompt which itself runs prompt to ask questions and add the answers to an array.
For better understanding, I created a tiny example (it's written in TypeScript - if you wish, I could re-write it in js.):
import { InputQuestionOptions, prompt } from "inquirer";
import Prompt = require("inquirer/lib/prompts/base");
(
async () =>
{
try
{
prompt.registerPrompt(
"test",
class extends Prompt<InputQuestionOptions<any>>
{
/**
* @inheritdoc
*
* @param resolve
* A callback for resolving the prompt.
*
* @returns
* The prompt itself.
*/
protected override _run(resolve: (value: any) => void): void
{
(async () =>
{
let result: string[] = [];
let answers: {
/**
* A value indicating whether another email should be added.
*/
repeat: boolean;
/**
* The entered email.
*/
email: string;
};
do
{
answers = await prompt<typeof answers>(
[
{
name: "email",
message: "please enter an email"
},
{
name: "repeat",
type: "confirm",
message: "would you like to enter an additional email?"
}
]);
result.push(answers.email);
}
while (answers.repeat);
resolve(result);
})();
}
});
let result = await prompt(
[
{
type: "test",
name: "emails"
},
{
type: "input",
name: "food",
message: "I have one more question for you! What's your favourite food?"
}
]);
console.log("No worries, I'm still alive!");
console.log(result);
}
catch (exception)
{
console.log("Error thrown");
console.log(exception);
}
})();
Expected behavior
The code should either follow the normal path (echo "No worries, I'm still alive!" and log the result) or throw an exception.
Actual behavior
The process crashes (exit-code 0) after rendering the 2nd question without any error-message:
Debugger attached.
? please enter an email [email protected]
? please enter an email [email protected]
? would you like to enter an additional email? (Y/n)
? would you like to enter an additional email? Yes
? please enter an email [email protected]
? please enter an email [email protected]
? would you like to enter an additional email? (Y/n) n
? would you like to enter an additional email? No
? I have one more question for you! What's your favourite food? Waiting for the debugger to disconnect...
More details
What's quite funny is, that the custom prompt works if it appears last.
This here causes the described issue:
let result = await prompt(
[
{
type: "test",
name: "emails"
},
{
type: "input",
name: "food",
message: "I have one more question for you! What's your favourite food?"
}
]);
however, this works:
let result = await prompt(
[
{
type: "test",
name: "emails"
}
]);
and this here works, as well:
let result = await prompt(
[
{
type: "input",
name: "food",
message: "I have one more question for you! What's your favourite food?"
},
{
type: "test",
name: "emails"
}
]);
Any idea what might cause this issue?
Yeah, the problem is Node doesn't really allow running 2 readlines at the same time. So nesting your prompts make them conflict with each other.
I don't know of any fixes we can do to Inquirer to allow this.
Similar issue to https://github.com/SBoudrias/Inquirer.js/issues/941
I think I found a solution, though, I'm not quite sure it's a valid one...?
I added a this.rl.pause()-statement at the beginning and a this.rl.resume()-statement before the resolve(...) and it looks like this fixes the issue.
Do you think this is a valid solution?
import { createPromptModule, InputQuestionOptions, prompt } from "inquirer";
import Prompt = require("inquirer/lib/prompts/base");
import { createInterface } from "readline";
(
async () =>
{
try
{
prompt.registerPrompt(
"test",
class extends Prompt<InputQuestionOptions<any>>
{
/**
* @inheritdoc
*
* @param resolve
* A callback for resolving the prompt.
*
* @returns
* The prompt itself.
*/
protected override _run(resolve: (value: any) => void): this
{
(async () =>
{
+ this.rl.pause();
let result: string[] = [];
let answers: {
/**
* A value indicating whether another email should be added.
*/
repeat: boolean;
/**
* The entered email.
*/
email: string;
};
do
{
let promptResult = prompt<typeof answers>(
[
{
name: "email",
message: "please enter an email"
},
{
name: "repeat",
type: "confirm",
message: "would you like to enter an additional email?"
}
]);
answers = await promptResult;
result.push(answers.email);
}
while (answers.repeat);
+ this.rl.resume();
resolve(result);
})();
return this;
}
});
let result = await prompt(
[
{
type: "test",
name: "emails"
},
{
type: "input",
name: "food",
message: "I have one more question for you! What's your favourite food?"
}
]);
console.log("No worries, I'm still alive!");
console.log(result);
}
catch (exception)
{
console.log("Error thrown");
console.log(exception);
}
})();
Though - when using this approach, lines are doubled:
let result = await prompt(
[
{
type: "input",
name: "food",
message: "I have one more question for you! What's your favourite food?"
},
{
type: "test",
name: "emails"
},
{
type: "input",
name: "moreFood",
message: "I have one more question for you! What's your favourite food?"
},
{
type: "test",
name: "moreEmails"
},
{
type: "input",
name: "evenMoreFood",
message: "I have one more question for you! What's your favourite food?"
}
]);
? I have one more question for you! What's your favourite food? Lasagne
? please enter an email [email protected]
? would you like to enter an additional email? Yes
? please enter an email [email protected]
? would you like to enter an additional email? No
? I have one more question for you! What's your favourite food? Pizza
? I have one more question for you! What's your favourite food? Pizza
? please enter an email [email protected]
? would you like to enter an additional email? No
? I have one more question for you! What's your favourite food? Hamburger
? I have one more question for you! What's your favourite food? Hamburger
As you can see, the line containing "Pizza" and "Hamburger" are doubled - also, the defaults won't vanish on typing.
However - I think this is still quite an "okay"-ish workaround ^^
Though, I consider it a workaround, but this here works perfectly for me:
import { createInterface } from "readline";
import { ReadStream } from "tty";
import { createPromptModule, InputQuestionOptions, prompt } from "inquirer";
import Prompt = require("inquirer/lib/prompts/base");
(
async () =>
{
try
{
prompt.registerPrompt(
"test",
class extends Prompt<InputQuestionOptions<any>>
{
/**
* @inheritdoc
*
* @param resolve
* A callback for resolving the prompt.
*
* @returns
* The prompt itself.
*/
protected override _run(resolve: (value: any) => void): this
{
(async () =>
{
+ this.rl.pause();
let result: string[] = [];
let answers: {
/**
* A value indicating whether another email should be added.
*/
repeat: boolean;
/**
* The entered email.
*/
email: string;
};
do
{
let promptResult = prompt<typeof answers>(
[
{
name: "email",
message: "please enter an email"
},
{
name: "repeat",
type: "confirm",
message: "would you like to enter an additional email?"
}
]);
answers = await promptResult;
result.push(answers.email);
}
while (answers.repeat);
+ ((this.rl as any).input as ReadStream)?.setRawMode?.(true); // Null-conditional operators (`?.`) ensure, that members are accessed only if they exist.
+ this.rl.resume();
resolve(result);
})();
return this;
}
});
let result = await prompt(
[
{
type: "test",
name: "emails"
},
{
type: "input",
name: "food",
message: "I have one more question for you! What's your favourite food?"
}
]);
console.log("No worries, I'm still alive!");
console.log(result);
}
catch (exception)
{
console.log("Error thrown");
console.log(exception);
}
})();