# Commands
An instance of the command
class represents a single executable unit.
The seeli framework parses user input and executes the appropriate command.
Commands handle basic validation, formatting help messages, prompting for
user input. The vast majority of this functionality is handled through flags
# Setup
Creating a new command is as simple as creating a new instance of a seeli
Command
and passing it configuration options. Many of the options are used
by the default help
command to render documentation. At the most basic, a
command should have a name
, description
, and some additional usage
text.
'use strict'
const seeli = require('seeli')
const name = seeli.config('name')
module.exports = new seeli.Command({
name: 'simple'
, description: 'This is a simple example'
, usage: [
`${name} example [options]`
]
})
This a basic skeleton for a command command, but it doesn't have any defined behavior.
# Execution
To define what a command does, you pass a run
function that is responsible for
executing any and all command specific logic when the command is dispatched.
And javascript type can be returned from a run
function. When a String (opens new window)
is returned, it will automatically be passed to console.log
. When a return
value of type string
is received, a content
event is emitted with the return value.
const {Command} = require('seeli')
module.exports = new Command({
name: 'simple'
, description: 'This is a simple example'
, run: async function(command, data) {
return 'hello world'
}
})
$ cmd simple
> hello world
# Events
Commands are also event emitters (opens new window). When defining a command, any function property the starts with
on
will be attached as an event handler for the matching name, sans on
module.exports = new Command({
name: 'events'
, onCustom: function(...args) {
console.log(...args)
}
, run: async function() {
this.emit('custom', 1, 2, 3)
}
})
# Complex Content
To keep the concerns of presentation separate from logic and to ease
testing, it is a good idea to return structured data object from run
handlers. Formatting should be done by an event handler for the content
event.
'use strict'
const {Command} = require('seeli')
module.exports = new Command({
name: 'complex'
, description: 'renders complex objects'
, onContent: function(content) {
if (!content) return
this.ui.info(content.message)
this.ui.succeed('complete')
}
, run: async function(command, data) {
return {message: 'hello world'}
}
})
$ cmd complex
> ℹ hello world
> ✔ complete
TIP
compile and return structured output objects in your run
functions
and delegate text rendering to the and other conditional behaviors to
event handlers
This can also be useful in testing sutations or situations when using a command programmatically. Being able to inspect a structured value rather than capturing and parsing rendered output can make testing efforts significantly easier.
const assert = require('assert')
const {Command} = require('seeli')
const hello = new Command({
name: 'simple'
, run: async function(command, data) {
return {message: 'hello world'}
}
})
hello
.run()
.then((output) => {
assert.deepEqual(output, {message: 'hello world'})
})
# Flags
A flag represents a single value that you want to collect from user input.
When the command successfully executes, the collected values are passed to
the run
function of your command as a single object.
A simple example would be collecting name and age of an individual
const {Command} = require('seeli')
module.exports = new Command({
name: 'personalize'
, flags: {
name: {
type: String
, description: 'Your name'
, shorthand: 'n'
}
, age: {
type: Number
, description: 'Your age in years'
, shorthand: 'a'
}
}
, run: async function(command, data) {
console.log(data.name)
console.log(data.age)
}
})
# Options
name | required | type | description |
---|---|---|---|
type | true | string | The type of input that is expected. Boolean types to not expect input. The present of the flag implies true . Additionally, boolean flags allow for --no-<flag> to enforce false . If you want to accept multiple values, you specify type as an array with the first value being the type you which to accept. For example [String, Array] means you will accept multiple string values. |
description | false | string | a description of the flag in question. Used to generate help messages |
required | false | boolean | If set to true a RequiredFieldError will be emitted |
shorthand | false | string | An optional shorthand name that will be expanded out to the long hand flag. |
interactive | false | boolean | If set to false the flag will omitted from interactive prompts |
default | false | mixed | A value to return if the flag is omitted. |
mask | false | boolean | interactive mode only Sets the input type to masked input to hide values |
choices | false | array | Used only during an interactive command. Restricts the users options only to the options specified |
multi | false | boolean | interactive mode only If choices is specified, and multi is true, this user will be presented a multi checkbox UI allowing them to pick multiple values. The return value will be an array |
skip | false | boolean | interactive mode only - if set to true this flag will be omitted from the interactive command prompts |
event | false | boolean | if set to true the command will emit an event withe the same name as the flag with the value that was captured for that flag |
when | false | function | interactive mode only Receives the current user answers hash and should return true or false depending on whether or not this question should be asked. |
validate | false | function | receives user input and should return true if the value is valid, and an error message (String) otherwise. If false is returned, a default error message is provided. |
filter | false | function | interactive mode only Receives the user input and return the filtered value to be used inside the program. The value returned will be added to the Answers hash. |
# Types
Generally, all input from stdin
is represented as strings. Using the flag type
option you can specify
The data expected data type and the input value will be coerced appropriately.
In most cases, you may pass the native javascript type you want to use. There are special cases
for url
and path
# String
Coerce all values as text / strings
# Number
Coerce all values as numeric ( integer or decimal )
# Date
Converts javascript date string into full Date objects
# Boolean
Boolean is a special input type in that it doesn't require an input value. The presence of the flag indicates a true value.
The flag can be negated by prefixing the flag with no-
new Command({
flags: {
bool: {
type: Boolean
}
}
})
$ cmd --bool #true
$ cmd --no-bool #false
# URL
A valid URL string. If it can't be parsed or is not a value url, An error will be raised.
The flag type should be the node url
module
const url = require('url')
new Command({
flags: {
website: {
type: url
}
}
})
# Path
A valid file system path. If the path doesn't resolve, an error will be raised.
const path = require('path')
new Command({
flags: {
file: {
type: path
}
}
})
# Password
Collects values as strings, but does not display the input value when it is typed.
This is accomplished by setting flag property, mask
to true
. This only pertains to
interactive mode with the user is prompted for a password. Input flags cannot be masked
const path = require('path')
new Command({
flags: {
passowrd: {
type: String
, mask: true
}
}
})
# Array
Including Array
in combination with another type allows a particular flag to be repeated.
The resulting input value will always be an array
new Command({
flags: {
numbers: {
type: [Number, Array]
}
}
})
$ cmd --numbers=1 --numbers=2 --numbers=3
# {numbers: [1, 2, 3]}
# Nested Flags
Nested flags are a way to better control the shape of the input object that is
collected before the run
function is executed. Nested flags make use of a separator (:
)
to drill down into the final data
object
new Command({
flags: {
'deep:property': {
type: String
}
}
})
$ cmd --deep:property=hello
# {deep: {property: 'hello'}}
# Sub Commands
Commands can be nested allowing the construction of a more fluent
user experience. A command accepts an additional commands
array
property. Each command is stand alone and can define its own input flags
and execution behaviors
'use strict'
const seeli = require('seeli')
const name = seeli.colorize(seeli.config('name'))
const bar = new seeli.Command({
name: 'bar'
, description: 'A Bar Command'
, usage: [
`${name} foo bar --input=yes`
]
, flags: {
input: {
type: String
, description: 'A bar value'
}
}
, run: async () => {
return 'bar'
}
})
// main command
module.exports = new seeli.Command({
name: 'foo'
, description: 'A Foo command'
, usage: [
`${name} foo <action> [flags]`
, ...bar.usage
]
, commands: [bar]
, run: async function(cmd, data) {
const help = seeli.commands.get('help')
return help.run('foo')
}
})
When sub commands are registered the help output is augmented with additional information about all registered commands. This behavior is recurrsive in that a command can have 0 or more commands. Seeli itself is a command, which keeps the execution behavior consistent