Enquirer provides a flexible architecture for creating custom prompt types using base classes and extending the built-in functionality to meet specific requirements.
Foundation classes that provide core functionality for different prompt categories.
/**
* Base prompt class - foundation for all prompts
*/
class Prompt extends EventEmitter {
constructor(options);
run();
render();
submit();
cancel();
}
/**
* Base class for array-based prompts (select, multiselect, etc.)
*/
class ArrayPrompt extends Prompt {
constructor(options);
dispatch(char, key);
append(char);
delete();
space();
number(char);
a(); // Select all
i(); // Invert selection
}
/**
* Base class for string input prompts
*/
class StringPrompt extends Prompt {
constructor(options);
append(char);
delete();
deleteForward();
cutForward();
cutLeft();
}
/**
* Base class for boolean prompts
*/
class BooleanPrompt extends Prompt {
constructor(options);
dispatch(char, key);
}
/**
* Base class for numeric prompts
*/
class NumberPrompt extends Prompt {
constructor(options);
append(char);
delete();
multiply(n);
divide(n);
}
/**
* Base class for authentication prompts
*/
class AuthPrompt extends Prompt {
constructor(options);
submit();
}Extend base classes to create custom prompt functionality.
Basic Custom Prompt Example:
const { StringPrompt } = require('enquirer');
class EmailPrompt extends StringPrompt {
constructor(options) {
super(options);
this.type = 'email';
}
async submit() {
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(this.value)) {
this.state.error = 'Please enter a valid email address';
return this.render();
}
return super.submit();
}
format(value) {
return value.toLowerCase().trim();
}
}
// Register and use
const { Enquirer } = require('enquirer');
const enquirer = new Enquirer();
enquirer.register('email', EmailPrompt);
const response = await enquirer.prompt({
type: 'email',
name: 'userEmail',
message: 'Enter your email address'
});Advanced Custom Array Prompt:
const { ArrayPrompt } = require('enquirer');
class TagsPrompt extends ArrayPrompt {
constructor(options) {
super(options);
this.type = 'tags';
this.tags = [];
}
async keypress(char, key) {
// Handle comma to add tag
if (char === ',') {
return this.addTag();
}
// Handle backspace to remove tag
if (key.name === 'backspace' && this.input === '') {
return this.removeLastTag();
}
return super.keypress(char, key);
}
addTag() {
const tag = this.input.trim();
if (tag && !this.tags.includes(tag)) {
this.tags.push(tag);
this.input = '';
this.cursor = 0;
}
this.render();
}
removeLastTag() {
if (this.tags.length > 0) {
this.tags.pop();
this.render();
}
}
submit() {
// Add current input as final tag
if (this.input.trim()) {
this.addTag();
}
this.value = this.tags;
return super.submit();
}
render() {
const tags = this.tags.map(tag => `[${tag}]`).join(' ');
const prompt = `${this.message} ${tags} ${this.input}`;
this.clear();
this.write(prompt);
}
}Register custom prompts for use in your application.
/**
* Register custom prompt with Enquirer instance
* @param type - Prompt type name
* @param promptClass - Custom prompt class
*/
enquirer.register(type, promptClass);
/**
* Register multiple custom prompts
* @param prompts - Object mapping type names to prompt classes
*/
enquirer.register(prompts);Usage Examples:
const { Enquirer } = require('enquirer');
// Single registration
enquirer.register('email', EmailPrompt);
enquirer.register('tags', TagsPrompt);
// Multiple registration
enquirer.register({
'email': EmailPrompt,
'tags': TagsPrompt,
'color': ColorPickerPrompt,
'date': DatePrompt
});
// Use registered prompts
const responses = await enquirer.prompt([
{
type: 'email',
name: 'email',
message: 'Email address?'
},
{
type: 'tags',
name: 'skills',
message: 'Enter skills (comma-separated):'
}
]);Understanding the prompt lifecycle helps in creating robust custom prompts.
class CustomPrompt extends Prompt {
constructor(options) {
super(options);
// Initialize prompt state
}
initialize() {
// Called before prompt starts
// Setup initial state, validate options
}
render() {
// Called to display prompt to user
// Clear screen, write prompt, position cursor
}
keypress(char, key) {
// Handle user input
// Process keystrokes, update state
}
submit() {
// Handle submission
// Validate input, format result, emit events
}
cancel() {
// Handle cancellation (Ctrl+C)
// Cleanup, emit cancel event
}
}Custom prompts maintain state throughout their lifecycle.
class StatefulPrompt extends Prompt {
constructor(options) {
super(options);
// Initialize state
this.state = {
status: 'pending',
error: null,
index: 0,
value: '',
submitted: false,
cancelled: false
};
}
updateState(updates) {
Object.assign(this.state, updates);
this.render();
}
submit() {
if (this.validate()) {
this.state.status = 'submitted';
return super.submit();
}
this.state.error = 'Invalid input';
this.render();
}
}Custom prompts can emit and listen for events.
class EventfulPrompt extends Prompt {
constructor(options) {
super(options);
// Listen for internal events
this.on('keypress', this.handleKeypress.bind(this));
this.on('state', this.handleStateChange.bind(this));
}
handleKeypress(char, key) {
// Emit custom events
this.emit('input', { char, key, value: this.value });
}
handleStateChange(state) {
// React to state changes
if (state.error) {
this.emit('error', state.error);
}
}
submit() {
// Emit submission event
this.emit('submit', this.value);
return super.submit();
}
}
// Usage with event listeners
const prompt = new EventfulPrompt(options);
prompt.on('input', (data) => {
console.log('User input:', data);
});
prompt.on('error', (error) => {
console.log('Validation error:', error);
});
prompt.on('submit', (value) => {
console.log('Submitted value:', value);
});Helper methods for common custom prompt tasks.
/**
* Utility methods available in custom prompts
*/
class CustomPrompt extends Prompt {
// Screen management
clear(); // Clear the terminal
write(str); // Write string to stdout
moveCursor(x, y); // Move cursor position
// Input processing
append(char); // Add character to input
delete(); // Delete character (backspace)
deleteForward(); // Delete character forward
// Formatting
format(value); // Format the final value
result(value); // Transform result before returning
// Validation
validate(value); // Validate user input
// State helpers
isValid(); // Check if current state is valid
hasError(); // Check if there's an error
}Create plugins that extend Enquirer functionality.
function customPromptPlugin(enquirer) {
// Add custom prompts
enquirer.register({
'email': EmailPrompt,
'phone': PhonePrompt,
'url': UrlPrompt
});
// Add utility methods
enquirer.askEmail = async (message, options = {}) => {
return enquirer.prompt({
type: 'email',
name: 'email',
message,
...options
});
};
// Add default options
enquirer.options = {
...enquirer.options,
prefix: '🤖',
footer: 'Use Ctrl+C to cancel'
};
}
// Use plugin
const enquirer = new Enquirer();
enquirer.use(customPromptPlugin);
// Use plugin-added functionality
const email = await enquirer.askEmail('What is your email?');Test custom prompts with simulated input.
const { EventEmitter } = require('events');
class MockStream extends EventEmitter {
constructor() {
super();
this.output = '';
}
write(str) {
this.output += str;
}
clearLine() {}
cursorTo() {}
}
async function testCustomPrompt() {
const stdin = new MockStream();
const stdout = new MockStream();
const prompt = new CustomPrompt({
message: 'Test prompt',
stdin,
stdout
});
// Simulate user input
setTimeout(() => {
stdin.emit('keypress', 'h', { name: 'h' });
stdin.emit('keypress', 'i', { name: 'i' });
stdin.emit('keypress', '\r', { name: 'return' });
}, 10);
const result = await prompt.run();
console.log('Result:', result);
console.log('Output:', stdout.output);
}