Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

inquirer

Package Overview
Dependencies
Maintainers
1
Versions
183
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

inquirer - npm Package Compare versions

Comparing version 0.5.1 to 0.6.0

lib/utils/events.js

10

lib/inquirer.js

@@ -33,9 +33,11 @@ /**

* Public CLI helper interface
* @param {Array} questions Questions settings array
* @param {Function} cb Callback being passed the user answers
* @return {null}
* @param {Array|Object|rx.Observable} questions - Questions settings array
* @param {Function} cb - Callback being passed the user answers
* @return {inquirer.ui.Prompt}
*/
inquirer.prompt = function( questions, allDone ) {
return new inquirer.ui.Prompt( questions, allDone );
var prompt = new inquirer.ui.Prompt();
prompt.run( questions, allDone );
return prompt;
};

32

lib/prompts/base.js

@@ -13,4 +13,4 @@ /**

var tty = require("../utils/tty");
var rx = require("rx");
/**

@@ -141,3 +141,33 @@ * Module exports

/**
* Run the provided validation method each time a submit event occur.
* @param {Rx.Observable} submit - submit event flow
* @return {Object} Object containing two observables: `success` and `error`
*/
Prompt.prototype.handleSubmitEvents = function( submit ) {
var opt = this.opt;
var validation = submit.flatMap(function( value ) {
return rx.Observable.create(function( observer ) {
utils.runAsync( opt.validate, function( isValid ) {
observer.onNext({ isValid: isValid, value: value });
observer.onCompleted();
}, value );
});
}).share();
var success = validation
.filter(function( state ) { return state.isValid === true; })
.take(1);
var error = validation
.filter(function( state ) { return state.isValid !== true; })
.takeUntil(success);
return {
success: success,
error: error
};
};
/**

@@ -144,0 +174,0 @@ * Filter a given input before sending back

@@ -10,2 +10,3 @@ /**

var utils = require("../utils/utils");
var observe = require("../utils/events");

@@ -61,8 +62,9 @@

// Move the selected marker on keypress
this.rl.on( "keypress", this.onKeypress.bind(this) );
var events = observe(this.rl);
this.lineObs = events.line.forEach( this.onSubmit.bind(this) );
events.normalizedUpKey.takeUntil( events.line ).forEach( this.onUpKey.bind(this) );
events.normalizedDownKey.takeUntil( events.line ).forEach( this.onDownKey.bind(this) );
events.numberKey.takeUntil( events.line ).forEach( this.onNumberKey.bind(this) );
events.spaceKey.takeUntil( events.line ).forEach( this.onSpaceKey.bind(this) );
// Once user confirm (enter key)
this.rl.on( "line", this.onSubmit.bind(this) );
// Init the prompt

@@ -134,4 +136,3 @@ this.render();

this.rl.removeAllListeners("keypress");
this.rl.removeAllListeners("line");
this.lineObs.dispose();
this.done( answer );

@@ -182,3 +183,48 @@ } else {

Prompt.prototype.handleKeypress = function(action) {
this.rl.output.unmute();
action();
// Rerender
this.clean().render();
this.rl.output.mute();
};
Prompt.prototype.onUpKey = function() {
this.handleKeypress(function() {
var len = this.opt.choices.realLength;
this.pointer = (this.pointer > 0) ? this.pointer - 1 : len - 1;
}.bind(this));
};
Prompt.prototype.onDownKey = function() {
this.handleKeypress(function() {
var len = this.opt.choices.realLength;
this.pointer = (this.pointer < len - 1) ? this.pointer + 1 : 0;
}.bind(this));
};
Prompt.prototype.onNumberKey = function( input ) {
this.handleKeypress(function() {
if ( input <= this.opt.choices.realLength ) {
this.pointer = input - 1;
this.toggleChoice( this.pointer );
}
}.bind(this));
};
Prompt.prototype.onSpaceKey = function( input ) {
this.handleKeypress(function() {
this.toggleChoice(this.pointer);
}.bind(this));
};
Prompt.prototype.toggleChoice = function( index ) {
var checked = this.opt.choices.getChoice(index).checked;
this.opt.choices.getChoice(index).checked = !checked;
};
/**

@@ -185,0 +231,0 @@ * Function for rendering checkbox choices

@@ -9,2 +9,3 @@ /**

var Base = require("./base");
var observe = require("../utils/events");

@@ -59,3 +60,3 @@

// Once user confirm (enter key)
this.rl.once( "line", this.onSubmit.bind(this) );
observe(this.rl).line.take(1).forEach( this.onEnd.bind(this) );

@@ -86,17 +87,15 @@ // Init

/**
* When user press "enter" key
* When user press `enter` key
*/
Prompt.prototype.onSubmit = function( input ) {
Prompt.prototype.onEnd = function( input ) {
this.status = "answered";
// Filter value to write final answer to screen
this.filter( input, function( output ) {
this.clean(1).render();
this.write( chalk.cyan(output ? "Yes" : "No") + "\n" );
var output = this.opt.filter( input );
this.done( input ); // send "input" because the master class will refilter
}.bind(this));
this.clean(1).render();
this.write( chalk.cyan(output ? "Yes" : "No") + "\n" );
this.done( input ); // send "input" because the master class will refilter
};

@@ -10,2 +10,3 @@ /**

var Separator = require("../objects/separator");
var observe = require("../utils/events");

@@ -31,3 +32,3 @@

this.validate();
this.validateChoices( this.opt.choices );

@@ -44,10 +45,3 @@ // Add the default `help` (/expand) option

// Setup the default string (capitalize the default key)
var defIndex = 0;
if ( _.isNumber(this.opt.default) && this.opt.choices.getChoice(this.opt.default) ) {
defIndex = this.opt.default;
}
var defStr = this.opt.choices.pluck("key");
this.rawDefault = defStr[ defIndex ];
defStr[ defIndex ] = String( defStr[defIndex] ).toUpperCase();
this.opt.default = defStr.join("");
this.opt.default = this.generateChoicesString( this.opt.choices, this.opt.default );

@@ -69,4 +63,5 @@ return this;

// Save user answer and update prompt to show selected option.
this.rl.on( "line", this.onSubmit.bind(this) );
this.rl.on( "keypress", this.onKeypress.bind(this) );
var events = observe(this.rl);
this.lineObs = events.line.forEach( this.onSubmit.bind(this) );
this.keypressObs = events.keypress.forEach( this.onKeypress.bind(this) );

@@ -159,4 +154,4 @@ // Init the prompt

this.rl.removeAllListeners("line");
this.rl.removeAllListeners("keypress");
this.lineObs.dispose();
this.keypressObs.dispose();
this.done( this.selected.value );

@@ -198,9 +193,10 @@ return;

* Validate the choices
* @param {Array} choices
*/
Prompt.prototype.validate = function() {
Prompt.prototype.validateChoices = function( choices ) {
var formatError;
var errors = [];
var keymap = {};
this.opt.choices.filter(Separator.exclude).map(function( choice ) {
choices.filter(Separator.exclude).map(function( choice ) {
if ( !choice.key || choice.key.length !== 1 ) {

@@ -228,3 +224,20 @@ formatError = true;

/**
* Generate a string out of the choices keys
* @param {Array} choices
* @param {Number} defaultIndex - the choice index to capitalize
* @return {String} The rendered choices key string
*/
Prompt.prototype.generateChoicesString = function( choices, defaultIndex ) {
var defIndex = 0;
if ( _.isNumber(defaultIndex) && this.opt.choices.getChoice(defaultIndex) ) {
defIndex = defaultIndex;
}
var defStr = this.opt.choices.pluck("key");
this.rawDefault = defStr[ defIndex ];
defStr[ defIndex ] = String( defStr[defIndex] ).toUpperCase();
return defStr.join("");
};
/**

@@ -231,0 +244,0 @@ * Function for rendering checkbox choices

@@ -9,3 +9,5 @@ /**

var Base = require("./base");
var observe = require("../utils/events");
/**

@@ -38,4 +40,8 @@ * Module exports

// Once user confirm (enter key)
this.rl.on( "line", this.onSubmit.bind(this) );
var submit = observe(this.rl).line.map( this.filterInput.bind(this) );
var validation = this.handleSubmitEvents( submit );
validation.success.forEach( this.onEnd.bind(this) );
validation.error.forEach( this.onError.bind(this) );
// Init

@@ -70,26 +76,25 @@ this.render();

Prompt.prototype.onSubmit = function( input ) {
var value = input;
if ( !value ) {
value = this.opt.default != null ? this.opt.default : "";
Prompt.prototype.filterInput = function( input ) {
if ( !input ) {
return this.opt.default != null ? this.opt.default : "";
}
this.validate( value, function( isValid ) {
if ( isValid === true ) {
this.filter( value, function( value ) {
this.status = "answered";
return input;
};
// Re-render prompt
this.clean(1).render();
Prompt.prototype.onEnd = function( state ) {
this.filter( state.value, function( filteredValue ) {
this.status = "answered";
// Render answer
this.write( chalk.cyan(value) + "\n" );
// Re-render prompt
this.clean(1).render();
this.rl.removeAllListeners("line");
this.done( value );
}.bind(this));
} else {
this.error( isValid ).clean().render();
}
// Render answer
this.write( chalk.cyan(filteredValue) + "\n" );
this.done( state.value );
}.bind(this));
};
Prompt.prototype.onError = function( state ) {
this.error( state.isValid ).clean().render();
};

@@ -10,2 +10,3 @@ /**

var utils = require("../utils/utils");
var observe = require("../utils/events");

@@ -65,8 +66,8 @@

// Move the selected marker on keypress
this.rl.on( "keypress", this.onKeypress.bind(this) );
var events = observe(this.rl);
events.normalizedUpKey.takeUntil( events.line ).forEach( this.onUpKey.bind(this) );
events.normalizedDownKey.takeUntil( events.line ).forEach( this.onDownKey.bind(this) );
events.numberKey.takeUntil( events.line ).forEach( this.onNumberKey.bind(this) );
events.line.take(1).forEach( this.onSubmit.bind(this) );
// Once user confirm (enter key)
this.rl.once( "line", this.onSubmit.bind(this) );
// Init the prompt

@@ -132,3 +133,2 @@ this.render();

this.rl.removeAllListeners("keypress");
this.done( choice.value );

@@ -142,19 +142,6 @@ };

Prompt.prototype.onKeypress = function( s, key ) {
// Only process up, down, j, k and 1-9 keys
var keyWhitelist = [ "up", "down", "j", "k" ];
if ( key && !_.contains(keyWhitelist, key.name) ) return;
if ( key && (key.name === "j" || key.name === "k") ) s = undefined;
if ( s && "123456789".indexOf(s) < 0 ) return;
Prompt.prototype.handleKeypress = function(action) {
this.rl.output.unmute();
var len = this.opt.choices.realLength;
if ( key && (key.name === "up" || key.name === "k") ) {
(this.selected > 0) ? this.selected-- : (this.selected = len - 1);
} else if ( key && (key.name === "down" || key.name === "j") ) {
(this.selected < len - 1) ? this.selected++ : (this.selected = 0);
} else if ( Number(s) <= len ) {
this.selected = Number(s) - 1;
}
action();

@@ -167,3 +154,25 @@ // Rerender

Prompt.prototype.onUpKey = function() {
this.handleKeypress(function() {
var len = this.opt.choices.realLength;
this.selected = (this.selected > 0) ? this.selected - 1 : len - 1;
}.bind(this));
};
Prompt.prototype.onDownKey = function() {
this.handleKeypress(function() {
var len = this.opt.choices.realLength;
this.selected = (this.selected < len - 1) ? this.selected + 1 : 0;
}.bind(this));
};
Prompt.prototype.onNumberKey = function( input ) {
this.handleKeypress(function() {
if ( input <= this.opt.choices.realLength ) {
this.selected = input - 1;
}
}.bind(this));
};
/**

@@ -170,0 +179,0 @@ * Function for rendering list choices

@@ -9,4 +9,4 @@ /**

var Base = require("./base");
var observe = require("../utils/events");
/**

@@ -38,6 +38,13 @@ * Module exports

var events = observe(this.rl);
// Once user confirm (enter key)
this.rl.on( "line", this.onSubmit.bind(this) );
this.rl.on( "keypress", this.onKeypress.bind(this) );
var submit = events.line.map( this.filterInput.bind(this) );
var validation = this.handleSubmitEvents( submit );
validation.success.forEach( this.onEnd.bind(this) );
validation.error.forEach( this.onError.bind(this) );
events.keypress.takeUntil( validation.success ).forEach( this.onKeypress.bind(this) );
// Init

@@ -96,4 +103,4 @@ this.render();

this.rl.removeAllListeners("line");
this.rl.removeAllListeners("keypress");
this.lineObs.dispose();
this.keypressObs.dispose();
this.done( value );

@@ -107,3 +114,39 @@ } else {

/**
* When user press `enter` key
*/
Prompt.prototype.filterInput = function( input ) {
if ( !input ) {
return this.opt.default != null ? this.opt.default : "";
}
return input;
};
Prompt.prototype.onEnd = function( state ) {
this.rl.output.unmute();
this.write("\n"); // manually output the line return as the readline was muted
this.status = "answered";
// Re-render prompt
this.clean(1).render();
// Mask answer
var mask = new Array( state.value.toString().length + 1 ).join("*");
// Render answer
this.write( chalk.cyan(mask) + "\n" );
this.done( state.value );
};
Prompt.prototype.onError = function( state ) {
this.rl.output.unmute();
this.write("\n"); // manually output the line return as the readline was muted
this.error( state.isValid ).clean().render();
this.rl.output.mute();
};
/**

@@ -110,0 +153,0 @@ * When user type

@@ -11,2 +11,3 @@ /**

var Separator = require("../objects/separator");
var observe = require("../utils/events");

@@ -39,2 +40,8 @@

_.extend(this.opt, {
validate: function( index ) {
return this.opt.choices.getChoice( index ) != null;
}.bind(this)
});
var def = this.opt.default;

@@ -62,6 +69,12 @@ if ( _.isNumber(def) && def >= 0 && def < this.opt.choices.realLength ) {

// Save user answer and update prompt to show selected option.
this.rl.on( "line", this.onSubmit.bind(this) );
this.rl.on( "keypress", this.onKeypress.bind(this) );
// Once user confirm (enter key)
var events = observe(this.rl);
var submit = events.line.map( this.filterInput.bind(this) );
var validation = this.handleSubmitEvents( submit );
validation.success.forEach( this.onEnd.bind(this) );
validation.error.forEach( this.onError.bind(this) );
events.keypress.takeUntil( validation.success ).forEach( this.onKeypress.bind(this) );
// Init the prompt

@@ -100,3 +113,2 @@ this.render();

/**

@@ -106,26 +118,24 @@ * When user press `enter` key

Prompt.prototype.onSubmit = function( input ) {
Prompt.prototype.filterInput = function( input ) {
if ( input == null || input === "" ) {
input = this.rawDefault;
return this.rawDefault;
} else {
input -= 1;
return input - 1;
}
};
var selectedChoice = this.opt.choices.getChoice(input);
Prompt.prototype.onEnd = function( state ) {
this.status = "answered";
this.selected = state.value;
// Input is valid
if ( selectedChoice != null ) {
this.status = "answered";
this.selected = input;
var selectedChoice = this.opt.choices.getChoice( this.selected );
// Re-render prompt
this.down().clean(2).render();
// Re-render prompt
this.down().clean(2).render();
this.rl.removeAllListeners("line");
this.rl.removeAllListeners("keypress");
this.done( selectedChoice.value );
return;
}
this.done( selectedChoice.value );
};
// Input is invalid
Prompt.prototype.onError = function() {
this.hasError = true;
this

@@ -138,3 +148,2 @@ .error("Please enter a valid index")

/**

@@ -144,3 +153,3 @@ * When user press a key

Prompt.prototype.onKeypress = function( s, key ) {
Prompt.prototype.onKeypress = function() {
var index = this.rl.line.length ? Number(this.rl.line) - 1 : 0;

@@ -154,3 +163,11 @@

this.cacheCursorPos().down().clean(1).render().write( this.rl.line ).restoreCursorPos();
this.cacheCursorPos();
if ( this.hasError ) {
this.down().clean(1);
} else {
this.clean();
}
this.render().write( this.rl.line ).restoreCursorPos();
};

@@ -157,0 +174,0 @@

@@ -6,3 +6,3 @@ /**

var _ = require("lodash");
var async = require("async");
var rx = require("rx");
var util = require("util");

@@ -26,5 +26,8 @@ var utils = require("../utils/utils");

function PromptUI( questions, allDone ) {
function PromptUI() {
Base.call(this);
}
util.inherits( PromptUI, Base );
PromptUI.prototype.run = function( questions, allDone ) {
// Keep global reference to the answers

@@ -35,12 +38,26 @@ this.answers = {};

// Make sure questions is an array.
if ( !_.isArray(questions) ) {
if ( _.isPlainObject(questions) ) {
questions = [questions];
}
// Create an observable, unless we received one as parameter.
// Note: As this is a public interface, we cannot do an instanceof check as we won't
// be using the exact same object in memory.
var obs = _.isArray( questions ) ? rx.Observable.fromArray( questions ) : questions;
// Start running the questions
async.mapSeries( questions, this.onEachPrompt.bind(this), this.onCompletion.bind(this) );
}
util.inherits( PromptUI, Base );
this.process = obs.concatMap( this.processQuestion.bind(this) );
this.process.forEach(
function() {},
function( err ) {
throw err;
},
this.onCompletion.bind(this)
);
return this.process;
};
/**

@@ -58,16 +75,31 @@ * Once all prompt are over

PromptUI.prototype.processQuestion = function( question ) {
return rx.Observable.defer(function() {
var obs = rx.Observable.create(function(obs) {
obs.onNext( question );
obs.onCompleted();
});
/**
* Each prompt rendering
* @param {Object} question Question object
* @param {Function} done Callback
*/
return obs
.concatMap( this.setDefaultType )
.concatMap( this.filterIfRunnable.bind(this) )
.concatMap( utils.fetchAsyncQuestionProperty.bind( null, question, "default", this.answers ) )
.concatMap( utils.fetchAsyncQuestionProperty.bind( null, question, "choices", this.answers ) )
.concatMap( this.fetchAnswer.bind(this) );
}.bind(this));
};
PromptUI.prototype.onEachPrompt = function( question, done ) {
// Callback to save answer and continu to next question
var after = function( answer ) {
this.answers[question.name] = answer;
done( null );
}.bind(this);
PromptUI.prototype.fetchAnswer = function( question ) {
var prompt = new inquirer.prompts[question.type]( question, this.rl, this.answers );
var answers = this.answers;
return utils.createObservableFromAsync(function() {
var done = this.async();
prompt.run(function( answer ) {
answers[question.name] = answer;
done({ name: question.name, answer: answer });
});
});
};
PromptUI.prototype.setDefaultType = function( question ) {
// Default type to input

@@ -77,21 +109,21 @@ if ( !inquirer.prompts[question.type] ) {

}
return rx.Observable.defer(function() {
return rx.Observable.return( question );
});
};
if ( _.isFunction(question.default) ) {
question.default = question.default( this.answers );
}
PromptUI.prototype.filterIfRunnable = function( question ) {
if ( !_.isFunction(question.when) ) return rx.Observable.return(question);
if ( _.isFunction(question.choices) ) {
question.choices = question.choices( this.answers );
}
var prompt = new inquirer.prompts[question.type]( question, this.rl, this.answers );
// Check if prompt should be runned (if `when` return true)
utils.runAsync( prompt.opt.when, function( continu ) {
if ( continu ) {
prompt.run( after );
} else {
done( null );
}
}, this.answers );
var answers = this.answers;
return rx.Observable.defer(function() {
return rx.Observable.create(function( obs ) {
utils.runAsync( question.when, function( shouldRun ) {
if ( shouldRun ) {
obs.onNext( question );
}
obs.onCompleted();
}, answers );
});
});
};

@@ -8,2 +8,3 @@ /**

var chalk = require("chalk");
var rx = require("rx");

@@ -27,9 +28,2 @@

utils.runAsync = function( func, cb ) {
var rest = [];
var len = 1;
while ( len++ < arguments.length ) {
rest.push( arguments[len] );
}
var async = false;

@@ -41,3 +35,3 @@ var isValid = func.apply({

}
}, rest );
}, Array.prototype.slice.call(arguments, 2) );

@@ -51,2 +45,43 @@ if ( !async ) {

/**
* Create an oversable returning the result of a function runned in sync or async mode.
* @param {Function} func Function to run
* @return {rx.Observable} Observable emitting when value is known
*/
utils.createObservableFromAsync = function( func ) {
return rx.Observable.defer(function() {
return rx.Observable.create(function( obs ) {
utils.runAsync( func, function( value ) {
obs.onNext( value );
obs.onCompleted();
});
});
});
};
/**
* Resolve a question property value if it is passed as a function.
* This method will overwrite the property on the question object with the received value.
* @param {Object} question - Question object
* @param {String} prop - Property to fetch name
* @param {Object} answers - Answers object
* @...rest {Mixed} rest - Arguments to pass to `func`
* @return {rx.Obsersable} - Observable emitting once value is known
*/
utils.fetchAsyncQuestionProperty = function( question, prop, answers ) {
if ( !_.isFunction(question[prop]) ) return rx.Observable.return(question);
return utils.createObservableFromAsync(function() {
var done = this.async();
utils.runAsync( question[prop], function( value ) {
question[prop] = value;
done( question );
}, answers );
});
};
/**
* Get the pointer char

@@ -53,0 +88,0 @@ * @return {String} the pointer char

{
"name": "inquirer",
"version": "0.5.1",
"version": "0.6.0",
"description": "A collection of common interactive command line user interfaces.",

@@ -22,3 +22,3 @@ "main": "lib/inquirer.js",

"dependencies": {
"async": "~0.8.0",
"chalk": "^0.5.0",
"cli-color": "~0.3.2",

@@ -28,4 +28,4 @@ "lodash": "~2.4.1",

"readline2": "~0.1.0",
"through": "~2.3.4",
"chalk": "~0.4.0"
"rx": "^2.2.27",
"through": "~2.3.4"
},

@@ -32,0 +32,0 @@ "devDependencies": {

@@ -50,3 +50,3 @@ Inquirer.js

- **questions** (Array) containing [Question Object](#question)
- **questions** (Array) containing [Question Object](#question) (using the [reactive interface](#reactive-interface), you can also pass a `Rx.Observable` instance)
- **callback** (Function) first parameter is the [Answers Object](#answers)

@@ -71,3 +71,3 @@

`validate`, `filter` and `when` functions can be asynchronously using `this.async()`. You just have to pass the value you'd normally return to the callback option.
`default`(if defined as a function), `validate`, `filter` and `when` functions can be asynchronously using `this.async()`. You just have to pass the value you'd normally return to the callback option.

@@ -223,2 +223,30 @@ ``` javascript

## Reactive interface
Internally, Inquirer uses the [JS reactive extension](https://github.com/Reactive-Extensions/RxJS) to handle events and async flows.
This mean you can take advantage of this feature to provide more advanced flows. For example, you can dynamically add questions to be asked:
```js
var prompts = Rx.Observable.create(function( obs ) {
obs.onNext({ /* question... */ });
setTimeout(function () {
obs.onNext({ /* question... */ });
obs.onCompleted();
});
});
inquirer.prompt(prompts);
```
And using the `process` property, you have access to more fine grained callbacks:
```js
inquirer.prompts(prompts).process.subscribe(
onEachAnswer,
onError,
onComplete
);
```
## Support (OS Terminals)

@@ -225,0 +253,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc