dthree / vorpal Goto Github PK
View Code? Open in Web Editor NEWNode's framework for interactive CLIs
Home Page: http://vorpal.js.org
License: MIT License
Node's framework for interactive CLIs
Home Page: http://vorpal.js.org
License: MIT License
I'd like the history to persist after exiting the shell, so if you re-enter the shell you can hit up arrow to see your previous command. Would you accept this as a PR with a toggle to turn it off? I'm thinking of using the temp dir to persist it (temporarily).
When passing arguments (or option values) surrounded with double quotes it should handle the input as literal strings and not split by any whitespace, nor should it parse it.
vorpal
.command('say <text>')
.option('-n, --name <nm>')
.action(function (args, cb) {
this.log((args.options.name ? args.options.name + ': ' : '') + args.text)
cb()
})
node~$ say "Hello World"
Hello World
node~$ say 'Innovation distinguishes between a leader and a follower.' --name 'Steve Jobs'
Steve Jobs: Innovation distinguishes between a leader and a follower.
Currently only single quotation marks are supported. I would also propose quotation marks to be optional (both individually and collectively)
Vorpal could expose rawParse(process.argv, { command: true|false}) to run a string through minimist or commander. This may be a special case.
I have an existing app, it's just using minimist for cli arguments in the usual form (app.js --arg items), and I'm working on the interactive CLI via vorpal. Trying to reduce redundancy when I can.
I am still working out how best to switch between command line (single commands) and interactive command line. But that's a different issue.
It's feel natural, like you're in a terminal. People are going to expect it to work this way (like the NodeJS shell).
Can't trace errors while running commands in non-interactive shell
Figured it out.
.exec
was intended to be programmatic, so it eats teh error and returns it in exec's callback.
.parse
calls.exec
, so it eats the error when invoked through .parse, but not otherwise.
#!/usr/bin/env node
var program = require('vorpal')();
var path = require('path');
var fs = require('fs');
var colors = require('colors');
var error = colors.bgRed.white.bold;
program.command('init [path]')
.description('Creates dummy config in current dir or [path]')
.action((args) => {
var path = args.path || '';
var configFile = path.join(process.cwd(), path, 'codecept.json');
this.log(path);
try {
fs.writeFileSync(configFile, '{}')
} catch (err) {
this.log(error(`File can't be created at ${configFile}`));
program.exit();
}
this.log(`Config created at ${configFile}`);
});
program.parse(process.argv);
Until the return key is pressed, all password characters are shown as plain text:
password: my-secret-password
After the return key was pressed, the characters are replaced with *
symbols:
password: ******************
As a sensitive user, I would like my password to be already hidden while typing. *
symbols should be shown from the very beginning.
Is there an option to achieve this?
Example code:
var vorpal = require('vorpal')();
vorpal
.command('connect')
.action(function (params, cb) {
this.prompt([{
type: 'password',
name: 'password',
message: 'password: '
}], cb);
});
vorpal.show();
vorpal.exec('connect');
It seems that Inquirer.js' input type renders rather strangely in Vorpal โ lacking spacing and the green question mark:
As opposed to the following in Inquirer.js:
Repro case:
const prompt = function(object, callback) {
object.prompt([
{
type: 'input',
name: 'name',
message: 'What is your name?',
default: 'John Doe',
},
{
type: 'list',
name: 'list',
message: 'What do cows drink?',
choices: ['milk', 'water', 'applejuice'],
default: 'water',
}
], function(result) {
callback();
});
};
Using Vorpal:
const cli = require('vorpal')();
cli
.command('test')
.action(function(args, callback) {
prompt(this, callback);
});
cli.show();
Using Inquirer.js:
const inquirer = require('vorpal/node_modules/inquirer');
prompt(inquirer, function() {
console.log(arguments);
});
Am unsure where this is going wrong, any ideas?
Guess I'll just drown you in feedback then.
Duplicate commands sit in the background until invoked, silently overriding each other. I also got a crash because of that (I think), which I unfortunately did not manage to replicate.
Especially with functions like vorpal-use, the user should at least be notified when a duplicate command is added. I didn't look how you implemented aliases, but the same should be true there.
And at the very least, you'll save some devs debugging time.
It seems as if the _parent attribute of a command is not passed on properly upon initialization? Is it enough to simply pass the exports-object to the newly created command?
console.log(this._parent) => [Function: Vorpal]
console.log(this._parent.toString()) => function Vorpal() {
if (!(this instanceof Vorpal)) {
return new Vorpal();
} // Program version
// Exposed through vorpal.version(str);
this._version = ''; // Registered vorpal.command commands and
// their options.
this.commands = []; // Queue of IP requests, executed async, in sync.
this._queue = []; // Current command being executed.
this._command = undefined; // Expose UI.
this.ui = ui; // Expose chalk as a convenience.
this.chalk = chalk; // Expose lodash as a convenience.
this.lodash = _; // Exposed through vorpal.delimiter(str).
this._delimiter = 'local@' + String(os.hostname()).split('.')[0] + '~$ ';
ui.setDelimiter(this._delimiter); // Placeholder for vantage server. If vantage
// is used, this will be over-written.
this.server = {
sessions: []
}; // Whether all stdout is being hooked through a function.
this._hooked = false; // Expose common utilities, like padding.
this.util = VorpalUtil; this.Session = Session; // Active vorpal server session.
this.session = new this.Session({
local: true,
user: 'local',
parent: this,
delimiter: this._delimiter
}); this._init();
return this;
}
Should intended behavior when initializing the command be something like:
var cmd = new Command(cmdName, this)
instead of:
var cmd = new Command(cmdName, exports);
This would allow the parent object attributes to be properly accessible to the Command-object. Ran into this kink while attempting to implement duplicate alias detection.
*At least with Promises
when not implementing a catch hook, typing in a command of a group gives you the help for it's subcommands.
if however you implement a catch hook, the catch hook is triggered instead.
I think it would still be useful to show the help for a command group, instead of triggering the catch.
Running gulp lint
yields many errors.
Would it be possible to support reading options starting with a minus?
In commander.js, t -i -2
would assign -2 to args.options.i
, whereas vorpal prints a friendly Missing required option. Showing Help:
.
Tested on the same code as the other issue. -i
or --index
makes no difference.
var Vorpal = require('./../../lib/vorpal');
var vorpal = new Vorpal();
vorpal.command('t')
.option("-i, --index <index>")
.action(function (args, cb) {
console.log(args.options.index);
cb()
});
vorpal.delimiter('foo:').show();
I'm running some async init code before I show vorpal and so ctrl-c twice no longer exits vorpal as I'm stuck in my callback. The idea here is that I only want to run vorpal if I can successfully connect to a database since all the vorpal commands are db related.
// basic setup
testConnection(function(err, result){
if(!err){
vorpal.show();
}
});
Is there an event or callback I can listen for to know when vorpal exists? Right now hitting ctrl-c appears to be stopping vorpal but Node is sitting in my callback waiting for something. I realize I might also have this setup wrong.
If I add the following:
process.on('SIGINT', () => process.exit(2));
Then I can get my program to quit if I hti ctrl-c three times. Otherwise I have to kill -9
it.
Thanks!
It should still be shown visibly on the command line UI, and you should be able to get at it via history. (This matches behavior of native command-line.)
There are a few types of arguments that can't be parsed in the REPL. For example, it's impossible to pass a JSON object.
I would like the argument parser to accommodate the following arguments:
command-name not-quoted
command-name '{"hello": "world"}`
command-name `{"hello": "little quotes 'hi' in here"}`
I'm working on code to do this. I don't think it's actually that complicated. Do you agree with the spec?
Adding a command with capital letters does not work.
Running it (either correctly written or in lowercase) yields Invalid Command. Showing Help:
.
The help does include this:
fooBar undefined
If forcing all-lowercase command names is intentional, a notification when an invalid command is added would be nice. If its unintentional: well, more stuff to fix =)
vorpal.command('fooBar')
.action(function(args, cb) {
this.log 'fooBar';
cb();
});
* Not my actual code, please ignore js errors produced by my blissful, coffeescript-induced ignorance.
Currently, my command keeps on running.
The output looks like this:
(^C again to quit)
function (str) {
if (str === undefined) {
return this._delimiter;
}
this._delimiter = String(str).trim() + ' ';
if (this.isLocal()) {
this.parent.ui.refresh();
} else {
this.parent._send('vantage-delimiter-downstream', 'downstream', {value: str, sessionId: this.id});
}
return this;
}
_
You can still type but nothing happens. Only way to recover is hitting CTRL+C again.
I have this pretty small vorpal app: https://github.com/fiatjaf/fieldbook-cli/tree/b3083a70b3ce12f137c7cb22cbe145eb9b9fce6c. The first command, auth
, triggers the creation of new commands (through vorpal.emit()
and vorpal.on()
), and it goes correctly, but then this bizarre error appears:
fiatjaf@spooner ~/c/fieldbook-cli>
./bin/executable.js auth --book 564a28s4dsc43200ce3215 --key key-1 --password 7734277-9dALvv70RL2342154v70ZQu
fieldbook~$
using book 564a28s4dsc43200ce3215.
Vorpal Prompt error: [Error: using book 564a28s4dsc43200ce3215.]
Which is strange, because my code is not calling .prompt
twice.
I put a dummy error (this EXIT()
function) on vorpal code to get the complete stack trace for the exact moment the error is triggered, and got the following:
fieldbook~$
using book 564a28s4dsc43200ce3215.
Vorpal Prompt error: [Error: using book 564a28s4dsc43200ce3215.]
/home/fiatjaf/comp/fieldbook-cli/node_modules/vorpal/lib/ui.js:191
EXIT()
^
ReferenceError: EXIT is not defined
at Object.ui.prompt (/home/fiatjaf/comp/fieldbook-cli/node_modules/vorpal/lib/ui.js:191:7)
at EventEmitter.vorpal.prompt (/home/fiatjaf/comp/fieldbook-cli/node_modules/vorpal/lib/vorpal.js:488:17)
at EventEmitter.session.prompt (/home/fiatjaf/comp/fieldbook-cli/node_modules/vorpal/lib/session.js:143:22)
at CommandInstance.prompt (/home/fiatjaf/comp/fieldbook-cli/node_modules/vorpal/lib/command-instance.js:76:25)
at CommandInstance.<anonymous> (/home/fiatjaf/comp/fieldbook-cli/index.js:114:14)
at EventEmitter.session.execCommandSet (/home/fiatjaf/comp/fieldbook-cli/node_modules/vorpal/lib/session.js:479:24)
at EventEmitter.vorpal._exec (/home/fiatjaf/comp/fieldbook-cli/node_modules/vorpal/lib/vorpal.js:826:18)
at EventEmitter.vorpal._execQueueItem (/home/fiatjaf/comp/fieldbook-cli/node_modules/vorpal/lib/vorpal.js:639:10)
at EventEmitter.vorpal._queueHandler (/home/fiatjaf/comp/fieldbook-cli/node_modules/vorpal/lib/vorpal.js:623:10)
at EventEmitter.vorpal.exec (/home/fiatjaf/comp/fieldbook-cli/node_modules/vorpal/lib/vorpal.js:598:10)
I don't understand it.
Options containing a hyphen, e.g. vorpal.command('t').option('a-b')
are not added to args.options when given in the cli.
May I ask why vorpal doesn't simply wrap commander for command-parsing? I love what vorpal does, but this seems like reinventing the wheel - unless there's an obvious factor I'm missing...
The following code outputs 0
for the input t -index 0
, but outputs undefined
for the input t -i 0
.
var Vorpal = require('./../../lib/vorpal');
var vorpal = new Vorpal();
vorpal.command('t')
.option("-i, --index <index>")
.action(function (args, cb) {
console.log(args.options.index);
cb()
});
vorpal.delimiter('foo:').show();
In source diving the arguments passed to command.option
, it appears that it accepts a 3rd and 4th parameter for defining a function and default value for the option, but it appears that these aren't actually being executed. In this test case, I was unable to get a default value or have the function execute. Am I doing something wrong, or is this just not supported yet?
#! /usr/bin/env node
let Vorpal = require('vorpal');
let cli = new Vorpal();
cli
.command('test')
.description('A test')
.option('--url <url>', 'Some url', function(val) { cli.log('VALUE!', value); }, 'http://some.url')
.action(function(args, done) {
this.log('URL:', args.options.url);
done();
});
cli.parse(process.argv);
$ ./cli test
URL: undefined
$ ./cli test --url other.url
URL: other.url
๐
Since you are using minimist
to parse the command options you have to allow your users to pass minimist
options as well.
For example I may want to pass {string: ['a', 'address']}
for my connect
command to indicate that the address
option should be treated as string always:
app$ connect --address 0x890765a99f0c8a98415756df2d821093ad0d273f
{ options: { address: 7.822966968792672e+47 } }
I may want to be strict about each command option, that's why probably an options
argument for the command
function would be good.
if I ask help for a subcommand:
My-cli$ help user add
Commands:
user add [options] Create a new admin user
if I change the command so it is a root level command:
My-cli$ help add-user
Usage: add-user [options]
Create a new admin user
Options:
--help output usage information
-u <username>, --username <username> the username for this user
-p <password>, --password <password> the password for this user
If I have a command that looks like:
command-name [strategy]
If strategy can only be "insert", "update", or "upsert", for example, how do I use Vorpal to validate it?
reverse hello | rev
Hit tab. No autocomplete.
Looks like it's possible to implement even with the API but would be nice to detect the pipe character and have it built in!
Do you want me to fix those? I'd need to know what you wanted written in those boxes, or just the gist of the info...
My journey trying to read the help for my own subcommands:
app~$ help
Commands:
help [command] Provides help for a given command.
exit [options] Exits instance of Vorpal.
Command Groups:
type * 2 sub-commands.
record * 3 sub-commands.
app~$ record
Command Groups:
record add * 3 sub-commands.
app~$ help record
Command Groups:
record add * 3 sub-commands.
app~$ help record add
Command Groups:
record add * 3 sub-commands.
app~$ record help add
Invalid Command. Showing Help:
Commands:
help [command] Provides help for a given command.
exit [options] Exits instance of Vorpal.
repl Enters REPL mode.
erase [options] erases db
Command Groups:
type * 2 sub-commands.
record * 3 sub-commands.
I still don't know how to do it, but perhaps vorpal shouldn't group the help for commands below a threshold? Maybe if the command group does not have more than 5 commands grouped than the help for them all will be shown on help
? Perhaps this number is configurable? Or, better, we should have to group commands explicitly (or maybe we could ungroup explicitly)?
using inquirer a prompt with validate function should show the validate output, but with vorpal it is somehow omitted.
this.prompt([
{
type: 'input',
name: 'test',
message: 'Please type something different than `test` to see an error pop up... or type `test` to continue',
validate: function (val) {
if (val != 'test') {
return "Please answer with the word test";
}
return true;
}
}
], function(answers) {
console.log(answers);
});
in Inquirer you can see this work perfectly in the examples/input.js
example. however the error gets lost somewhere in the process within vorpal I assume.
When I input past the width of the terminal, my input (in this case random numbers) goes onto the next lines.
deliminator$: 12313131313123131313131313213213111111111231323344
1123132334
112313233
11231323
1123132
112313
11231
1123
112
11
1
The following just display a red Error: true instead of a full error logging
import vorpal from 'vorpal'
const program = vorpal()
program.command('foo')
.action(function() {
return Promise.reject(new Error('Bouu'))
})
program
.delimiter(`jogabo:work$`)
.show()
In one of your examples, you demonstrated how to switch between multiple instances.
function switch(vorpal) {
vorpal.command('switch <instance>')
.action(function (args, cb) {
instances[args.instance].show();
});
return vorpal;
}
var instances = {
'a': new Vorpal().use(switch).delimiter('a:'),
'b': new Vorpal().use(switch).delimiter('b:'),
'c': new Vorpal().use(switch).delimiter('c:'),
}
Using version 1.4.0, it does work on first ''switch'', but switching again to the same instance will exit.
$ node switcher.js
a: switch a
a: switch a
$
I'm using Vorpal with the reduced test case below:
'use strict';
let cli = require('vorpal')();
cli
.command('test')
.description('reduced test case')
.option('--required <val>', 'Should be a required value')
.action(function(c) {
console.log(c.options.required);
});
cli.parse(process.argv);
node index test
This command's action should not execute using the given command, as no --required
value was specified. According to the current source in lib/util.js:199, missing required options shows a message in the console along with help. This does not happen.
An error message related to the correct command argument sequence does not reflect the documentation: "Put the required arguments first, and variadic in the back.". (See also gitter chat on Dec 29, 2015, 18:36 CET).
Thanks!
vorpal
.command('connect [username] [password]')
.description('Connect to server.')
.action(function (args, callback) {
setTimeout(function () {
// deleting options key from args
delete args.options;
var context = domain.create();
// error handling in domain
context.on('error', errorHandler);
// running the connect in domain
context.run(function() {
console.log("Arguments : ", args);
});
}, 0);
});
vorpal
.delimiter('hdb$')
.show();
Vorpal Prompt error: TypeError: Cannot read property 'setRawMode' of null
at ReadStream.setRawMode (tty.js:67:15)
at Interface._setRawMode (readline.js:177:23)
at new Interface (readline.js:137:10)
at Object.exports.createInterface (readline.js:39:10)
at Object.Interface.createInterface (/home/users/my-cli/node_modules/vorpal/node_modules/inquirer/node_modules/readline2/index.js:34:21)
at module.exports (/home/users/my-cli/node_modules/vorpal/node_modules/inquirer/lib/ui/baseUI.js:14:30)
at new module.exports (/home/users/my-cli/node_modules/vorpal/node_modules/inquirer/lib/ui/prompt.js:15:8)
at Object.promptModule as prompt
at Object.ui.prompt (/home/users/my-cli/node_modules/vorpal/lib/ui.js:171:25)
at EventEmitter.vorpal._prompt (/home/users/my-cli/node_modules/vorpal/lib/vorpal.js:528:15)
at EventEmitter. (/home/users/my-cli/node_modules/vorpal/lib/vorpal.js:542:12)
at callback (/home/users/my-cli/node_modules/vorpal/lib/vorpal.js:705:22)
at /home/users/my-cli/node_modules/vorpal/lib/vorpal.js:824:7
at EventEmitter._commandSetCallback (/home/users/my-cli/node_modules/vorpal/lib/session.js:455:5)
at EventEmitter.session.completeCommand (/home/users/my-cli/node_modules/vorpal/lib/session.js:519:12)
at onCompletion (/home/users/my-cli/node_modules/vorpal/lib/session.js:465:10)
Please let me know if I am doing something wrong in this.
In my tests I have to hit CTRL+C three times. If I move the process.exit() call into the keypress handler instead of _sigint, it works correctly on the second keypress.
[email protected] is about half the size, as they switched to "rx-lite" from the much larger rxjs package.
They have also "fixed" their lodash dependency, though that one is including the full librayr (now the bulk of the distribution size).
Hi, the issues just keep coming. I guess I'll have to take a look at the code soon and try to hack a pullrequest or two. I'm starting to feel bad for just posting these without trying to implement anything. Anyways, here we go:
vorpal.command('test <required> [optional]')
test foo
works as expected: { options: {}, required: 'foo' }
.
However:
vorpal.command('test [optional] <required>')
test foo
results in Missing required argument. Showing Help:
It would be great if vorpal was smart enough to recognize that foo should be the value for <required>
, not for [optional]
.
Looks like it affects promises only. The first command resolves, subsequent piped commands promise doesn't seem to be seen as resolved.
First. Thanks for this Project. It's awesome.
I was wondering if it's possible to run a Vorpal app outside it's own shell. I mean.. directly from the OS.
Thanks.
It looks like only a few methods from lodash are in use; the library is available in modular form:
https://www.npmjs.com/browse/keyword/lodash-modularized
The usual CTRL+Z does not seem to be working with vorpal in default config.
It does not like to be put in the background, restoring (via fg) exits.
The help text for "Missing required argument" always shows the first argument in the pipe, regardless of which argument failed validation.
Hi not sure if this even part of what Vorpal is all about, I did see the mode options, but what I wanted to know was
You can't kill it at all unless you manually pkill -9 node
in another terminal.
If I start typing my command:
command-one param | command-two param
Then I go back and try to edit command-one and change it to command-three, put my cursor here:
com_ | command-two param
Then I hit tab, it actually prints tab. No autocomplete. I think the code doesn't even think about the cursor position. Is it possible to react to the cursor pos?
Prevents me from canceling unit tests. Probably the CTRL+C behavior should only apply if vorpal is being shown().
Trying out ES6 arrow functions with Vorpal, but the session
normally available via this
does not work of course:
vorpal
.command('foo')
.description('Outputs "bar".')
.action((args, callback) => {
this.log('bar'); // ReferenceError: log is not defined
callback();
});
Though what we be the best way to use vorpal with arrow functions?
How about having context for repl mode , the way we can attach context
with account nodejs repl
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.