Covenus-Commander
Modern, powerful command-line application framework for Node.js using typescript or ES6, inspired by commander, nestjs and angular
Description
Covenus-commander is a modern command-line application framework for Node.js which helps you simplify building command-line interfaces using modern JavaScript, is built with TypeScript, combining class-based modularity with the annotation syntax of decorators, to build a command-line interface application out of a set of loosely-coupled reusable components.
Covenus-commander is built with the awesome, popular, well-known library—Commander. And models it's application programming interface after Angular and Nest.
Installation
$ npm install covenus-commander --save
Setting up your Application
Covenus-commander is built with features from ES6. And the easiest way to get going with it is to use Babel or Typescript.
If using Typescript, your tsconfig.json
should have the emitDecoratorMetadata
and experimentalDecorators
properties set to true
"compilerOptions": "module": "commonjs" "target": "es6" "noImplicitAny": false "noLib": false "emitDecoratorMetadata": true "experimentalDecorators": true "exclude": "node_modules" "src/**/*.spec.ts"
Setting to Development Environment
While testing your application, you may want to see the stack trace details of runtime exceptions that occur within Coven-commander that it hides from you with the friendly An unhandled Coven runtime exception occured.
message. To do this simply include the CovenFactory.switchToDevelopmentMode()
before your call to CovenFactory.createCLI(...)
in your program class file
#!/usr/bin/env node const CovenFactory = CovenFactory; /*To see the call stack trace details of runtime exceptions that occuruncomment the switch to development mode call*/CovenFactory; const app = CovenFactory; app;
Now, lets start with the command-line application use-case scenarios documented on the popular and well-known library Commander page, and see how you would handle those same scenarios with Covenus-commander.
Option parsing
Options with covenus-commander are defined with the @CLIOption
decorator, passing in metadata arguments that serve to define the details and documentation for the options. The example below mimics the option parsing example on the Commander github page. Equally parsing the args and options from process.argv
to match.
; @{} @{} @{} @{}
Now that the options have been defined by their standalone classes annoted with the @CLIOption
decorator, the main command-line program class can now be declared and annoted with its own @CLIProgram
decorator. Passing in its own metadata arguments tying together the earlier options for the applications execution.
#!/usr/bin/env node ; @ { console; console; ifoptionspeppersconsole ifoptionspineappleconsole ifoptionsbbqSauceconsole ifoptionscheeseconsole } const app = CovenFactory;app;
Remember that Covenus-commander is built on the popular Commander, so its underlying framework benefits translate over. So the short flags of the individual options may be passed as a single arg, for example -abc
is equivalent to -a -b -c
. And multi-word options such as "--bbq-sauce" are camel-cased, becoming options.bbqSauce
when checking if the option flag was set etc.
Automated --help
The help information is auto-generated based on the information you've afforded covenus-commander already through the metadata annotations (e.g. @CLIOption
, @CLICommand
, @CLIArgument
) it provides by default, so the following help info is generated for free by the underlying framework once you pass the --help
flag on the command-line console:
$ ./examples/pizza --help
Usage: pizza [options]
An application for pizzas ordering
Options:
-h, --help output usage information
-V, --version output the version number
-p, --peppers Add peppers
-P, --pineapple Add pineapple
-b, --bbq Add bbq sauce
-c, --cheese <type> Add the specified type of cheese [marble]
-C, --no-cheese You do not want any cheese
Coercion
; @ /* The coercion method is invoked when the option's value is being evaluated, giving you a chance to validate or do your own custom evaluation before the resulting value is referenced in a command's 'execute' or program's 'run' method argValue is the value passed in on the command line for the option argDefault is the optional defualt value you set in the option metadata's 'defaultValue' property Always return a value for coercion, it is the return value that gets passed to a command's 'execute' or program's 'run' method */ : any argDefault; return argDefault; @ /* The coercion method is invoked when the option's value is being evaluated, giving you a chance to validate or do your own custom evaluation before the resulting value is referenced in a command's 'execute' or program's 'run' method argValue is the value passed in on the command line for the option argDefault is the optional defualt value you set in the option metadata's 'defaultValue' property Always return a value for coercion, it is the return value that gets passed to a command's 'execute' or program's 'run' method */ : any var val = ; if! return val; return 'Not a Number'; @ /* The coercion method is invoked when the option's value is being evaluated, giving you a chance to validate or do your own custom evaluation before the resulting value is referenced in a command's 'execute' or program's 'run' method argValue is the value passed in on the command line for the option argDefault is the optional defualt value you set in the option metadata's 'defaultValue' property Always return a value for coercion, it is the return value that gets passed to a command's 'execute' or program's 'run' method */ : any if return ; @ /* The coercion method is invoked when the option's value is being evaluated, giving you a chance to validate or do your own custom evaluation before the resulting value is referenced in a command's 'execute' or program's 'run' method argValue is the value passed in on the command line for the option argDefault is the optional defualt value you set in the option metadata's 'defaultValue' property Always return a value for coercion, it is the return value that gets passed to a command's 'execute' or program's 'run' method */ : any return argValue; @{} @ /* The coercion method is invoked when the option's value is being evaluated, giving you a chance to validate or do your own custom evaluation before the resulting value is referenced in a command's 'execute' or program's 'run' method argValue is the value passed in on the command line for the option argDefault is the optional defualt value you set in the option metadata's 'defaultValue' property Always return a value for coercion, it is the return value that gets passed to a command's 'execute' or program's 'run' method */ : any return argValue; @ /* The coercion method is invoked when the option's value is being evaluated, giving you a chance to validate or do your own custom evaluation before the resulting value is referenced in a command's 'execute' or program's 'run' method argValue is the value passed in on the command line for the option argDefault is the optional defualt value you set in the option metadata's 'defaultValue' property Always return a value for coercion, it is the return value that gets passed to a command's 'execute' or program's 'run' method */ : any return argDefault + 1;
#!/usr/bin/env node ; @ { console; console; console; console; console; optionsrange = optionsrange || ; console; console; console; console; console; } const app = CovenFactory;app;
Regular Expression
; @{} @{}
#!/usr/bin/env node ; @ { console; console; console; console; } const app = CovenFactory;app;
Variadic arguments
Commands can be defined by their own @CLICommand
decorator, that defines its own metadata argument to support the underlying Commander features. One of which is the last variadic argument support for a command. Here is an example:
; @ /* The execute method is invoked when the command verb is detected in the command line arguments parsed by the framework use @RequiredArg parameter decorator to access required arguments listed in 'requiredArgs' metadata property use @VariadicArg parameter decorator to access the last variadic argument set in the 'variadicLastArg' metadata property By default, if a required argument is not passed in from the command-line, the coven runtime will respond with a message demanding the argument be passed subsequently, so no need to test the 'dir' parameter if its set While, a variadic argument needs to be tested before use, if(otherDirs){} */ console; console; console; if otherDirs otherDirs;
The otherDirs
second parameter argument of the command class's execute
method represents the variadic argument, and an Array
is used for the value of a variadic argument. This is the parameter argument all the extra command-line arguments will be passed to.
#!/usr/bin/env node ; @ const app = CovenFactory;app;
Specify the argument syntax
The argument-based approach can also be defined by its own @CLIArgument
decorator, that defines its own metadata to support the underlying Commander argument syntax feature. Here is an example:
; @ /* The execute method is invoked when the arguments matching this class are detected in the command line arguments parsed by the framework use @RequiredArg parameter decorator to access required arguments listed in 'requiredArgs' metadata property use @OptionalArg parameter decorator to access optional arguments listed in 'optionalArgs' metadata property By default, if a required argument is not passed in from the command-line, the coven runtime will respond with a message demanding the argument be passed subsequently, so no need to test the 'cmdValue' parameter if its set While, an optional argument needs to be tested before use, if(envValue){} */ console; console; console; console; { output; output; output; output; }
The required input arguments are the ones indicated within the requiredArgs
metadata property of the @CLIArgument
, while the optional input arguments are the ones indicated within the optionalArgs
metadata property.
#!/usr/bin/env node ; @ const app = CovenFactory;app;
## Git-style sub-commands
// file: ./examples/pm ; @{} @{} @{}
#!/usr/bin/env node ; @ const app = CovenFactory;app;
When a command class defined with @CLICommand
is intended to be used as a sub-command, it must have its verbDescription
metadata property defined. this indicates to the covenus-commander framework that this command does not intend to have an execute
method invoked within it, but rather it should trigger the search for matching sub-command separate executables, much like git(1)
and other popular tools. So no execute
should be defined within the command class to handle its execution.
Covenus-commander will fallback to Commander, who will try to search the executables in the directory of the entry script (like ./examples/pm
) with the name program-command
, like pm-install
, pm-search
.
Specifying true
for the isDefault
property on the verbOption
metadata property will run the subcommand by default if no subcommand is specified as a command-line argument on the console.
If the command-line program is designed to be installed globally, make sure the executables have proper file-access modes, like 755
.
Custom help
You can display your own arbitrary extra help information
by implementing the onExtraHelpInfo
method in your command-line program class. So in addition to the default help text generated for your program's options(@CLIOption
), commands(@CLICommand
) or argument(@CLIArgument
) classes, you can include your own custom help text in the help output generated when
--help
is used on the console.
; @{} @{} @{}
#!/usr/bin/env node ; @ /* adding the 'onExtraHelpInfo' function to your cli program class allows you to output additional help info alongside the the generated help output when your app is called with '-h' or '--help' flag */ { output; output; output; output; output; output; output; } const app = CovenFactory;app;
Yields the following help output when node script-name.js -h
or node script-name.js --help
are run:
Usage: custom-help [options]
Options:
-h, --help output usage information
-V, --version output the version number
-f, --foo enable some foo
-b, --bar enable some bar
-B, --baz enable some baz
----- Custom Help Example -----
$ custom-help --help
$ custom-help -h
.displayHelp(method)
To output the application's help information without explicitly passing the --help
flag on the command-line. The implictly defined displayHelp()
method of your @CLIProgram
decorated program class can be invoked to output your program's help information. Also, there is an optional method onCustomizeHelpBeforeDisplay(helpText)
you can implement within your program class to allow post-processing of help text before it is displayed.
If you want your program to display it's help information by default (e.g. if no command or arguments are supplied on the command-line), you just include the showHelpByDefault: true
metadata property in your program class @CLIProgram
decorator's supplied metadata object:
; @{}
#!/usr/bin/env node ; @ { console; console; ifoptionsmessage /* the 'displayHelp' function is automatically injected into your @CliProgram class use it to trigger the output of the app's generated help output to the command line without exiting or using the '-h' or '--help' flag. */ this; else console; } /* the 'onCustomizeHelpBeforeDisplay' function if present in your @CliProgram class is automatically used by the 'displayHelp' to enable you customize the help output before it is sent to the command line Perhaps change the color of the text or do some manipulation of the text */ { var myCustomMessage = "\n My Custom Message \n\n\n"; return helpText; } const app = CovenFactory;app;
Examples
; @{} @{} @{} @{} @{} @ /* The execute method is invoked when the command verb is detected in the command line arguments parsed by the framework use @OptionalArg parameter decorator to access optional arguments listed in 'optionalArgs' metadata property use @CommandOptionArg parameter decorator to access the value of the options set in the 'options' metadata property of your command class, in this case its the 'setup_mode' defined above use @ProgramOptionArg parameter decorator to access the value of the options set in the 'options' metadata property of your program class, in this case its the 'config' set in the program class of this command */ env = env || 'all'; console; { output; output; output; output; } @ /* The execute method is invoked when the command verb is detected in the command line arguments parsed by the framework use @RequiredArg parameter decorator to access required arguments listed in 'requiredArgs' metadata property use @CommandOptionArg parameter decorator to access the value of the options set in the 'options' metadata property */ console; { output; output; output; output; output; } @ /* The execute method is invoked when the command verb is detected in the command line arguments parsed by the framework */ { console; }
#!/usr/bin/env node ; @ /* adding the 'onExtraHelpInfo' function to your cli program class allows you to output additional help info alongside the the generated help output when your app is called with '-h' or '--help' flag */ { output; output; } const app = CovenFactory;app;
Dependency Injection
Covenus-commander ships with its own dependency injection system to facilitate the building of self-contained and self-managed command-line applications. It adds the notion of component classes that are defined and then associated with your command-line program through its @CLIProgram
decorator. Then instances of these components can be automatically injected into the program's command(@CLICommand
), argument(@CLIArgument
) and option(@CLIOption
) class constructors by the covenus-commander's DI framework.
Custom Components
To add a component to your command-line program(@CLIProgram
), first define the component class with a component decorator of its own:
@ ...
Then associate the component's class with your program's decorator through its components
metadata property.
@ { //userService is dynamically injected }
And like Angular and Nest, you can also inject your components using other mechanisms.Such as:
By Value:
Sometimes what component you have to inject isn't class-based, simply just a javascript value or object type. In such cases you can use:
const val = {} @
Now the DI framework will associate the val
object with the string-based metatype 'UserService'
. This is also useful when you need test doubles(unit testing). E.g when your class-based metatype isn't ready, you can use a value based metatype instead then substitute it out once ready.
By Class:
Sometimes when using a class-based metatype for your component you may want to use a derived sub-class or a class with a different implentation of the original class type used as the component's metatype
@{} @
By Factory:
Sometimes when you want to use a component that depends on some other component or package or asyncronously retrieved value(Observable
or Promise
) itself during its instantiation, you will use the factory method to associate it with your program
@{} @
When using factory-based method, to use a fellow component as one of its dependencies you must include that component used in the inject
property of its metadata, so the DI framework knows to inject it into your factory callback.
By Custom Providers:
Sometimes you want to inject a custom value(or even class or factory based) into a component, command, argument, or option by yourself. In such cases, you'll use a custom provider.
@
Now to inject this component defined with this custom provider metatype key isLinuxEnvironment
, just use the @Inject
parameter decorator in the component, command, argument or option's class constructor.
; @ { console; }
Exception Traps:
Covenus-commander allows you to delegate your exception handling from being within the execute
or coercion
methods of your command or argument classes, and option classes respectively, to the trap
method of your dedicated exception trap classes. So, for example:
//user-not-found.trap; {} @ implements CLIExceptionTrap { ifexception instanceof UserNotFoundException output; }
Now, you only have to associate this exception trap class with your command, argument or option class(es) like so:
;;; @@ private userService; { thisuserService = userService; } /* The execute method is invoked when the command verb is detected in the command line arguments parsed by the framework use @ProgramOptionArg parameter decorator to access the value of the options set in the 'options' metadata property of your program class, in this case its the 'config' set in the program class of this command */ if!userId console; return; thisuserService;
So if userService.getUser(userId)
throws UserNotFoundException
, the UserTrap
exception trap defined earlier will trap that particular exception.
Exception Trap Scoping
The exception traps can be command, argument and option scoped. Meaning, that you can set exception traps on the classes of these covenus-commander elements when you use the @UseTraps
decorator on them.
Or if you'd like to trap excpetions globally, you can also set traps on your program(@CLIProgram
marked class). And this will trap any exceptions that gets thrown within the execute
or coercion
methods of your command or argument classes, and option classes respectively.
More Demos can be found in the tests directory.
License
MIT