@microsoft/ts-command-line4.2.4 • Public • Published
This library helps you create professional command-line tools for Node.js. By "professional", we mean:
no gotchas for users: Seems obvious, but try typing "
npm install --save-dex" instead of "
npm install --save-dev" sometime. The command seems to execute successfully, but it doesn't save anything! The misspelled flag was silently ignored. This lack of rigor plagues many familiar NodeJS tools and can be confusing and frustrating. For a great user experience, a command line tool should always be strict about its syntax.
data['output-dir']when it wasn't defined, or if you misspell the key name, your tool will silently behave as if the parameter was omitted. And is
data['max-count']a string or a number? Hard to tell! We solve this by modeling each parameter kind as a real TypeScript class.
automatic documentation: Some command-line libraries treat the
--helpdocs as someone else's job. ts-command-line requires each every parameter to have a documentation string, and will automatically generate the
--helpdocs for you. If you like to write long paragraphs, no problem -- they will be word-wrapped correctly. [golf clap]
structure and extensibility: Instead of a simple function chain, ts-command-line provides a "scaffold" pattern that makes it easy to find and understand the command-line implementation for any tool project. The scaffold model is generally recommended, but there's also a "dynamic" model if you need it. See below for examples.
Internally, the implementation is based on argparse and the Python approach to command-lines. Compared to other libraries, ts-command-line doesn't provide zillions of custom syntaxes and bells and whistles. Instead it aims to be a simple, consistent, and professional solution for your command-line tool. Give it a try!
Suppose that we want to parse a command-line like this:
widget --verbose push --force --max-count 123
In this example, we can identify the following components:
- The tool name in this example is
widget. This is the name of your Node.js bin script.
- The parameters are
- The currently supported parameter kinds include: flag (i.e. boolean), integer, string, choice (i.e. enums), and string list.
- The value "123" is the argument for the
--max-countinteger parameter. (Flags don't have arguments, because their value is determined by whether the flag was provided or not.)
- Similar to Git's command-line, the
pushtoken is called an action. It acts as sub-command with its own unique set of parameters.
--verboseflag is a global parameter because it precedes the action name. It affects all actions.
--forceflag is an action parameter because it comes after the action name. It only applies to that action.
If your tool uses the scaffold model, you will create subclasses of two abstract base classes:
CommandLineParser for the overall command-line, and
CommandLineAction for each action.
Continuing our example from above, suppose we want to start with a couple simple flags like this:
widget --verbose push --force
We could define our subclass for the "
push" action like this:
Then we might define the parser subclass like this:
To invoke the parser, the application entry point will do something like this:
When we run
widget --verbose push --force, the
PushAction.onExecute() method will get invoked and then your business logic takes over.
Testing out the docs
If you invoke the tool as "
widget --help", the docs are automatically generated:
usage: widget [-h] [-v] <command> ... The widget tool is really great. Positional arguments: <command> push Pushes a widget to the service Optional arguments: -h, --help Show this help message and exit. -v, --verbose Show extra logging detail For detailed help about a specific command, use: widget <command> -h
For help about the
push action, the user can type "
widget push --help", which shows this output:
usage: widget push [-h] [-f] Your long description goes here. Optional arguments: -h, --help Show this help message and exit. -f, --force Push and overwrite any existing state
The action subclasses provide a simple, recognizable pattern that you can use across all your tooling projects. It's the generally recommended approach. However, there are some cases where we need to break out of the scaffold. For example:
- Actions or parameters may be discovered at runtime, e.g. from a config file
- The actions and their implementations may sometimes have very different structures
In this case, you can use the
DynamicCommandLineParser classes which are not abstract (and not intended to be subclassed). Here's our above example rewritten for this model:
// Define the parser;commandLineParser.defineFlagParameter;// Define the action;commandLineParser.addActionaction;action.defineFlagParameter;// Parse the command linecommandLineParser.executeprocess.argv.then;
You can also mix the two models. For example, we could augment the
WidgetCommandLine from the original model by adding
DynamicAction objects to it.
The API reference has complete documentation for the library.
Here are some real world GitHub projects that illustrate different use cases for ts-command-line: