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

seeli

Package Overview
Dependencies
Maintainers
2
Versions
89
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

seeli - npm Package Compare versions

Comparing version 7.0.0 to 8.0.0

.npmignore

270

lib/command.js

@@ -16,8 +16,12 @@ /*jshint laxcomma:true, smarttabs: true, node: true, esnext: true, unused: true */

* @requires clone
* @requires debug
* @requires strip-ansi
* @requires mout/array/append
* @requires mout/lang/toArray
* @requires mout/object/hasOwn
* @requires mout/lang/isObject
* @requires seeli/lib/domain
* @requires seeli/lib/usage
* @requires seeli/lib/exceptions
* @requires seeli/lib/conf
* @requires seeli/lib/lang/object
**/

@@ -34,2 +38,4 @@ const os = require( 'os' ) // native os

, strip = require( 'strip-ansi') // function helper to remove ansi color
, debug = require( 'debug')
, ora = require('ora')
, array_append = require( 'mout/array/append' ) //

@@ -39,17 +45,14 @@ , toArray = require( 'mout/lang/toArray' ) //

, isObject = require( 'mout/lang/isObject' )
, debug = require( 'debug')('seeli:command')
, domain = require( './domain')
, conf = require('./conf')
, usage = require('./usage')
, conf = require( './conf' )
, usage = require( './usage')
, object = require( './lang/object' )
, typeOf = require('./usage/type-of')
, exceptions = require( './exceptions')
, typeOf = require( './usage/type-of' )
, exceptions = require( './exceptions' )
, stop_flags = ['help', 'interactive', 'skip','color']
, ARGV = 'argv'
, on_exp = /^on[A-z]/
, noop = () => {}
, on_exp = /^on([A-z])/
, noop = () => { return Promise.resolve(null); }
;
/**

@@ -101,3 +104,3 @@ * Deep merge objects. except for path & url

function removeOn( name ){
return name.replace(on_exp, function(full, first ){
return name.replace(on_exp, function(full, first){
return first.toLowerCase();

@@ -113,2 +116,4 @@ });

, interactive: true
, name: 'command'
, ui: 'dots'
, flags: {

@@ -139,2 +144,3 @@ interactive: {

* @param {String|String[]} [options.usage=""] a string or array of string to describe command usage. If an array, items will be join with a new line character
* @param {String} [options.ui="dots"] ui progress indicator
* @param {Object} [options.flags] cli flags. top level keys will be used as the long hand flag

@@ -144,37 +150,48 @@ * @param {Function} [options.run] A function that will be used as the primary drive of the command. It should perform what ever action the command was intended to do

options:{
description:"diaplays a simple hello world command"
,usage:[
"Usage: cli hello --interactive",
"Usage: cli hello --name=john",
"Usage: cli hello --name=john --name=marry --name=paul -v screaming"
description:"diaplays a simple hello world command"
, usage:[
"Usage: cli hello --interactive"
, "Usage: cli hello --name=john"
, "Usage: cli hello --name=john --name=marry --name=paul -v screaming"
]
,flags:{
, flags:{
name:{
type:[ String, Array ]
,shorthand:'n'
,description:"The name of the person to say hello to"
, shorthand:'n'
, description:"The name of the person to say hello to"
}
,excited: {
, excited: {
type:Boolean
,shorthand: 'e'
,description:"Say hello in a very excited manner"
,default:false
, shorthand: 'e'
, description:"Say hello in a very excited manner"
, default:false
}
,volume:{
, volume:{
type:String
,choices:['normal', 'screaming']
,default:'normal'
,shorthand:'v'
, choices:['normal', 'screaming']
, default:'normal'
, shorthand:'v'
}
}
,run: function( cmd, data, cb ){
var out = [];
for( var x =0; x< data.name.length; x++ ){
out.push( "Hello, " + data.name[x] + "!" );
, run: async ( cmd, data ) => {
const out = [];
this.ui.start('processing names');
var names = Array.isArray( data.name ) ? data.name : [ data.name ];
for( var x = 0; x < names.length; x++ ){
this.ui.text = (`processing ${names[x]}`)
await new Promise((resolve) => {
setTimeout(() => {
let value = "Hello, " + names[x];
if( data.excited ){
value += '!';
}
out.push( data.volume === 'screaming' ? value.toUpperCase() : value );
resolve(true);
}, 1000 * x + 1);
});
}
out = out.join('\n');
out = data.value == 'screaming' ? out.toUpperCase() : out;
callback( out );
ui.succeed('names processed successfully');
return out
}

@@ -189,3 +206,2 @@ }

events.EventEmitter.call( this );
domain.add( this );
this._shcache = null;

@@ -197,3 +213,12 @@ this._optcache = null;

this.setOptions( defaults, ...options );
debug('strict mode: %s', this.options.strict);
this.ui = ora({
color: conf.get('color')
, spinner: this.options.ui
, text: 'loading'
, stream: process.stdout
});
this.debug = debug(`${conf.get('name')}:${this.options.name}`);
this.debug('strict mode: %s', this.options.strict);
}

@@ -207,3 +232,7 @@

get usage(){
var out = usage.from( this.options.flags );
const out = usage.from(
this.options.flags
, null
, this.options.interactive
);
return array_append(

@@ -213,3 +242,3 @@ toArray( this.options.usage ),

''
, !!out ? 'Options:' : ''
, !!out ? `Options:${os.EOL}` : ''
, out

@@ -334,4 +363,5 @@ ]

for( var opt in options ){
if( typeof( options[ opt ] ) == 'function' || on_exp.test(opt) ) {
if(on_exp.test(opt) && typeof( options[ opt ] ) == 'function' ) {
this.addListener( removeOn( opt ), options[ opt ]);
delete options[opt];
}

@@ -368,3 +398,3 @@ }

**/
interactive( cmd, done ){
async interactive( cmd ){
let that = this, flags;

@@ -374,47 +404,31 @@ flags = Object

.filter( function( flag ){
return stop_flags.indexOf( flag ) == -1;
return !stop_flags.includes( flag );
});
const answers = Object.create(null);
const series = (cb) => {
const next = () => {
if (!flags.length) return cb(null, answers);
const flag = flags.shift();
const current = that.options.flags[flag];
if (Array.isArray(current.type)) {
return ask(flag, cmd, current, (err, results) => {
if(err) return done(err);
answers[flag] = results;
next();
});
}
const arg = toQuestion(flag, cmd, current, answers);
inquirer
.prompt(arg)
.then((answer) => {
Object.assign(answers, answer);
next();
})
.catch(done);
};
next();
};
const args = [];
while (flags.length) {
const flag = flags.shift();
const current = that.options.flags[flag];
if (Array.isArray(current.type)) {
answers[flag] = await ask(flag, cmd, current);
continue;
}
const arg = toQuestion(flag, cmd, current, answers);
Object.assign(answers, await inquirer.prompt(arg));
}
series((err) => {
if (err) return done(err);
let args = [];
that.parsed = Object.assign( that.parsed, answers );
this.parsed = Object.assign(this.parsed, answers);
for( let answer in answers ){
args.push( `--${answer}=${answers[answer]}` );
object.set(that.parsed, answer, answers[answer]);
}
for( let answer in answers ){
args.push( `--${answer}=${answers[answer]}` );
object.set(this.parsed, answer, answers[answer]);
}
that.setOptions({
args: args
});
that.validate(cmd);
that.dispatch();
return that.options.run.call(that, cmd, that.argv, done);
this.setOptions({
args: args
});
this.validate(cmd);
this.dispatch();
return this.options.run.call(this, cmd, this.argv);
}

@@ -445,21 +459,14 @@

**/
run( cmd, callback ){
var done // callback function for commadn
, directive // the first non-flag directive passed to the command
;
async run( cmd ){
nopt.invalidHandler = this.invalidHandler.bind(this);
nopt.invalidHandler = this.invalidHandler.bind(this);
done = function( err, content ){
content = !!this.argv.color ? content : strip( content );
nopt.invalidHandler = null;
if( err ){
const directive = this.argv.argv.remain[1];
cmd = cmd ? cmd : directive || null;
if( this.argv.interactive ) {
if( this.options.interactive ){
this.dispatch();
const result = await this.interactive.call( this, cmd );
const content = !!this.argv.color ? result : strip( result );
/**
* dispatched when the command has failed in some way
* @name command.Command#error
* @event
* @param {Error} e
*/
this.emit('error', err );
} else{
/**
* dispatched when the command has sucessfully completed

@@ -471,15 +478,6 @@ * @name command.Command#content

this.emit('content', content);
}
callback && callback( err, content );
}.bind( this );
directive = this.argv.argv.remain[1];
cmd = cmd ? cmd : directive || null;
if( this.argv.interactive ) {
if( this.options.interactive ){
this.dispatch();
return this.interactive.call( this, cmd, done );
return content;
} else {
console.error('interactive mode - not availible\n'.yellow );
console.error( chalk.yellow('interactive mode - not availible\n') );
return null;
}

@@ -490,4 +488,7 @@ }

this.dispatch();
const result = this.options.run.call(this, cmd, this.argv, done );
return !!this.argv.color ? result : strip( result );
const result = await this.options.run.call(this, cmd, this.argv);
this.ui.stop();
const content = !!this.argv.color ? result : strip( result );
this.emit('content', content);
return content;
}

@@ -500,2 +501,3 @@

* @throws module:seeli/lib/exceptions/InvalidFieldException
* @throws module:seeli/lib/exceptions/RequiredException
**/

@@ -507,3 +509,3 @@ validate( cmd ){

.filter(( flag ) => {
return stop_flags.indexOf( flag ) == -1;
return !stop_flags.includes(flag);
})

@@ -516,4 +518,4 @@ .forEach(( flag ) => {

if( this._required.indexOf(flag) >= 0 && this.parsed[flag] === UNDEF ){
return this.emit('error', new exceptions.RequiredFieldException( flag ) );
if( this._required.includes(flag) && this.parsed[flag] === UNDEF ){
throw new exceptions.RequiredFieldException( flag );
}

@@ -524,7 +526,7 @@

if(isValid === false ){
return this.emit('error', new exceptions.InvalidFieldException(`${flag} failed validation.`) );
throw new exceptions.InvalidFieldException(`${flag} failed validation.`);
}
if( typeof isValid == 'string'){
return this.emit('error', new exceptions.InvalidFieldException( `${flag} - ${isValid}` ) );
throw new exceptions.InvalidFieldException( `${flag} - ${isValid}` );
}

@@ -535,2 +537,12 @@ });

/**
* Pass through function to inquirer for prompting input at the terminal
* @method module:seeli/lib/command#prompt
* @param {Object} options Inquirer prompt options
* @returns {Promise} Promise object representing the end user input from the question
**/
prompt(opts) {
return inquirer.prompt(opts);
}
/**
* Colorizes a text blob

@@ -550,24 +562,19 @@ * @method module:seeli/lib/command#colorize

function ask(name, cmd, opts, cb) {
async function ask(name, cmd, opts) {
const results = [];
const arg = toQuestion(name, cmd, opts);
const prompt = () => {
inquirer
.prompt( arg )
.then(( answer ) => {
if ( answer[name] === '' ) return cb(null, results);
results.push(answer[name]);
prompt();
})
.catch(cb);
};
prompt();
while (true) {
const answer = await inquirer.prompt( arg );
if ( answer[name] === '' ) break;
results.push(answer[name]);
}
return results;
}
function toQuestion(flag, cmd, opts, answers) {
const flag_display = flag.replace(':', ' ');
const arg = {
type: opts.type === Boolean ? 'confirm' : opts.mask ? 'password' : 'input'
, name: flag
, message: flag + ' : ' + opts.description || '(no description)'
, name: flag_display
, message: flag_display + ': ' + (opts.description || '(no description)')
, default: opts.default || null

@@ -587,3 +594,6 @@ };

}
return arg;
}

@@ -22,5 +22,5 @@ /*jshint laxcomma:true, smarttabs: true, node: true, unused: true */

description:"displays information about available commands"
,interactive:false
,alias:['hlep']
,run: function( cmd, data, done ){
, interactive:false
, alias:['hlep']
, run: async function(cmd){
var commands

@@ -36,9 +36,7 @@ , cls

if( cmd == "help" ){
done(null, "really?" );
return;
return 'really?';
}
if( !cmd ){
done(null, list() );
return;
return list();
}

@@ -61,6 +59,5 @@

} catch ( e ){
help = "no help found for " + cmd + " "+ e.message;
help = `no help found for ${cmd}`;
}
done( null, help );
return;
return help;
}

@@ -67,0 +64,0 @@ });

@@ -14,3 +14,2 @@ /*jshint laxcomma:true, smarttabs: true, unused: true, esnext: true, node: true */

* @requires seeli/lib/conf
* @requires seeli/lib/domain
**/

@@ -20,3 +19,2 @@ var nopt = require('nopt')

, chalk = require('chalk')
, clidomain = require('./domain')
, command = require( './command' ) // this is command

@@ -28,3 +26,2 @@ , commands = require('./commands')

, parsed
, clidomain
, colors

@@ -42,3 +39,5 @@ , help

clidomain.on('error', function(err){
process.on('unhandledRejection', onError);
function onError (err) {
console.error( chalk.red( util.format( '%s:', err.name) ), chalk( err.message ) );

@@ -51,3 +50,3 @@ if( parsed && parsed.traceback ){

return conf.get('exitOnError') ? process.exit( err.code || 1 ) : null;
});
}

@@ -94,8 +93,2 @@ // primary flags

/**
* Overrides a named command unconditionally
* @param {String} name The name of the command to set
* @param {module:seel/lib/command} command A commadn to over ride
**/
, cmd: cmd
/**
* starts the command line execution process

@@ -106,2 +99,16 @@ **/

, reset: commands.reset.bind( commands )
/**
* colorizes a given string using the chalk color found in local configuration
* @param {String} text A string to wrap in an ansi color
* @returns {String} The given string wrapped in the configued ansi color
* @example
* const seeli = require('seeli')
* seeli.set('color', 'blue')
* seeli.colorize('I am blue')
**/
, colorize: (txt) => {
const color = chalk[conf.get('color')]
if (!color) return txt
return color(txt)
}
};

@@ -127,12 +134,6 @@

function cmd( key, value ){
commands[ key ] = value;
}
function run( ){
parsed = nopt( opts, shorthands );
const cb = (err, content ) => {
if (err) return;
console.log( content || '' );
const cb = (content) => {
typeof content === 'string' && console.log( content );
if( conf.get('exitOnContent') ){

@@ -150,12 +151,9 @@ process.exit(0);

// allow for abbreviated commands
return clidomain.run(function(){
commands.help.run( command, cb);
});
return commands.help.run( command ).then(cb).catch(onError);
}
if(commands.hasOwnProperty( command ) ) {
return clidomain.run(function(){
commands[command].run(null, cb);
});
return commands[command].run(null).then(cb).catch(onError);
}
console.error('unknown command %s', command );

@@ -162,0 +160,0 @@ console.error('know commands: %s ', Object.keys( commands ).join(', ') );

@@ -19,4 +19,6 @@ /*jshint laxcomma: true, smarttabs: true, node: true, esnext: true*/

const os = require('os');
const cliui = require('cliui');
const util = require('util');
const chalk = require('chalk');
const width = require('string-width');
const typeOf = require('./type-of');

@@ -28,3 +30,4 @@ const conf = require('../conf');

function from( flags, plain ){
function from( flags, plain, interactive = true ){
const ui = cliui();
const return_value = [''];

@@ -34,16 +37,30 @@ const style = !!plain ? noop : chalk[conf.get('color')] || chalk.green;

for( const flag in flags ) {
const cols = [];
if (flag === 'interactive' && !interactive) continue
const config = flags[flag];
const type = typeOf( config.type );
return_value.push(util.format(
'%s--%s%s <%s> %s %s'
, config.shorthand ? `-${config.shorthand}, ` : ''
, flag
, type === 'boolean' ? `, --no-${flag}` : ''
, style(type)
, typeof config.default === 'undefined' ? '' : `[${chalk.bold( config.default )}]`
, config.description || ''
));
ui.div({
text: util.format(
'%s--%s%s'
, config.shorthand ? `-${config.shorthand}, ` : ''
, flag
, type === 'boolean' ? `, --no-${flag}` : ''
)
, padding: [0, 0, 0, 2]
, width: 40
}, {
text: `<${style(type)}>`
, align: 'left', width: 10
}, {
text: typeof config.default === 'undefined' ? '' : `[${chalk.bold( config.default )}]`
, align: 'left'
, width: 20
}, {
text: config.description || ''
, align: 'left'
, width: Math.max(80, width((config.description || '').trim()))
});
}
return_value.push('', `${style('<...>')}: input type | ${style('*')}: repeatable flags | ${style("[...]")}: default values` );
return return_value.join( os.EOL + ' ' );
ui.div({text: `${style('<...>')}: input type | ${style('*')}: repeatable flags | ${style("[...]")}: default values`, padding: [2, 0, 0, 2] });
return ui.toString();
}
{
"name": "seeli",
"version": "7.0.0",
"version": "8.0.0",
"description": "Object oriented, flexible CLI tools",

@@ -10,3 +10,3 @@ "main": "index.js",

"scripts": {
"test": "tap -Rtap -J --cov test/",
"test": "tap -Rtap -J --cov test",
"docs": "jsdoc -r lib"

@@ -47,3 +47,3 @@ },

"engines": {
"node": ">=6.0.0"
"node": ">=7.0.0"
},

@@ -57,2 +57,3 @@ "bugs": {

"chalk": "^2.1.0",
"cliui": "^4.0.0",
"clone": "^2.0.0",

@@ -63,2 +64,4 @@ "debug": "^3.1.0",

"nopt": "~4.0.0",
"ora": "^1.3.0",
"string-width": "^2.1.1",
"strip-ansi": "^4.0.0"

@@ -65,0 +68,0 @@ },

@@ -13,68 +13,89 @@ ![build image](https://travis-ci.org/esatterwhite/node-seeli.svg?branch=master)&nbsp;![package dependancies](https://david-dm.org/esatterwhite/node-seeli.png)

```js
var cli = require("seeli")
var Hello = new cli.Command({
const os = require('os')
const cli = require('seeli')
cli.set({
exitOnError: true
, color: 'green'
, name: 'example'
})
const Hello new cli.Command({
description:"displays a simple hello world command"
,usage:[
`${cli.bold("Usage:")} cli hello --interactive`,
`${cli.bold("Usage:")} cli hello --name=john`,
`${cli.bold("Usage:")} cli hello --name=john --name=marry --name=paul -v screaming`
, name: 'hello'
, ui: 'dots'
, usage:[
`${cli.bold("Usage:")} ${cli.get('name')} hello --interactive`
, `${cli.bold("Usage:")} ${cli.get('name')} hello --name=john`
, `${cli.bold("Usage:")} ${cli.get('name')} hello --name=john --name=marry --name=paul -v screaming`
]
,flags:{
, flags:{
name:{
type:[ String, Array ]
,shorthand:'n'
,description:"The name of the person to say hello to"
,required:true
, shorthand:'n'
, description:"The name of the person to say hello to"
, required:true
}
,excited: {
, 'nested:value' : {
type: Number
, shorthand: 'nv'
, description: 'A newsted Value'
, name: 'nested'
}
, excited: {
type:Boolean
,shorthand: 'e'
,description:"Say hello in a very excited manner"
,default:false
, shorthand: 'e'
, description:"Say hello in a very excited manner"
, default:false
}
,volume:{
, volume:{
type:String
,choices:['normal', 'screaming']
,description:"Will yell at each person"
,default:'normal'
,shorthand:'v'
, choices:['normal', 'screaming']
, description:"Will yell at each person"
, default:'normal'
, shorthand:'v'
}
}
, onContent: (content) => {
// command success
// content is the final output from run function
// non string content is not written to stdout automatically
// you could do it here
,password: {
type:String,
mask:true,
description:"unique password",
shorthand:'p',
required: false
}
console.log(content.join(os.EOL))
}
,run: function( cmd, data, cb ){
, run: async function( cmd, data ){
const out = [];
const names = Array.isArray( data.name ) ? data.name : [ data.name ]
for(const name of names){
var value = `Hello ${name}
if( data.excited ){
value += '!'
}
this.ui.start('processing names');
var names = Array.isArray( data.name ) ? data.name : [ data.name ];
for( var x = 0; x< names.length; x++ ){
this.ui.text = (`processing ${names[x]}`)
await new Promise((resolve) => {
setTimeout(() => {
let value = "Hello, " + names[x];
if( data.excited ){
value += '!';
}
out.push( value );
out.push( data.volume === 'screaming' ? value.toUpperCase() : value );
resolve(true);
}, 1000 * x + 1);
});
}
if (data.password) {
out.push('')
out.push('your password was set.')
}
out = out.join('\n');
out = data.volume == 'screaming' ? out.toUpperCase() : out;
cb( null, out );
this.ui.succeed('names processed successfully');
// anything returned from run
// is emitted from the `content` event
// strings will automatically be written to stdout
return out
}
});
cli.set('exitOnError', true)
cli.use('hello', Hello)
cli.set('color','green');
cli.run();

@@ -86,6 +107,6 @@ ```

```
node cli.js help world
node cli.js world --help
node cli.js world --interactive
node cli.js world --name=Mark --name=Sally --no-excited
node ./cli help world
node ./cli world --help
node ./cli world --interactive
node ./cli world --name=Mark --name=Sally --no-excited
```

@@ -103,7 +124,2 @@

## Seeli.exitOnError `<Boolean>`
If set to turn seeli will exit the process when it encouters an error. If false, it will leave error handling up to
the parent application
## Seeli.use( name `<string>`, cmd `<Command>` )

@@ -198,3 +214,4 @@

**flags** | `Object` | `{}` | key value pairs used to control the command where keys are the name of the flag and the values is a configuration object for the flag
**run** | `Function` | `no-op` | A function used as the body of the command. it will be passed a `name`, a `data` object containing the processed values from the command input and a `done` function to be called when the command is done.
**ui** | `String` | `dots` | The kind of [progress indicator](https://github.com/sindresorhus/cli-spinners/blob/master/spinners.jso) your command should use
**run** | `Function` | `no-op` | An async function used as the body of the command. It will be passed a `subcommand` name if one was passed, and a `data` object containing the processed values from the command input.

@@ -244,4 +261,9 @@ ### Flag Options

Your defined `run` function will be passed a `done` function to be called when your command has finished. This allows you to do complex async operations and I/O. The `done` callback accepts an error, if there is one, and the final output to be displayed for your command.
Your defined `run` function can be an async function, or a function that returns a `Promise`. This allows you to do complex async operations and I/O. If an error is thrown, it will be displayed.
Otherwise, the content returned from your `run` function will be output to stdout ( if it returned a `String`).
## Progress
Your command's `run` function has access to an instance of [ora](https://www.npmjs.com/package/ora) allowing you to display progress indicators and helpful messages while you perform other work.
## Events

@@ -253,28 +275,28 @@

var EventCommand = new cli.Command({
args:[ '--one', '--no-two']
, flags:{
one:{
type:Boolean
,event:true
}
,two:{
type:Boolean
,event:true
}
args:[ '--one', '--no-two']
, flags:{
one:{
type:Boolean
, event:true
}
, run: function( cmd, data, done ){
done( null, data.one && data.two )
, two:{
type:Boolean
, event:true
}
}
, run: async function( cmd, data ){
return data.one && data.two
}
});
EventCommand.on('one', function( value ){
assert.equal( true, value );
assert.equal( true, value );
});
EventCommand.on('two', function( value ){
assert.equal( false, value )
assert.equal( false, value )
});
EventCommand.on('content', function( value ){
assert.equal( false, value );
assert.equal( false, value );
});

@@ -285,15 +307,1 @@

## Errors
Errors are handled by Node's error [domains](http://nodejs.org/api/domain.html). Each command will run inside of its own domain and will emit an error event if and error is passed to the `done` callback from the `run` method. Seeli will suppress trace messages by default. You can use the `--traceback` flag on any command to surface the full stack trace. If the error object emitted has a `code` property that is a non zero value, seeli will forcefully exit the process with the error code.
```js
var cli = require("seeli")
var ErrCmd = new cli.Command({
run: function(){
var e = new Error("Invalid Command")
e.code = 10;
this.emit('error',e )
}
});
```
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