Security News
JavaScript Leaders Demand Oracle Release the JavaScript Trademark
In an open letter, JavaScript community leaders urge Oracle to give up the JavaScript trademark, arguing that it has been effectively abandoned through nonuse.
@rushstack/ts-command-line
Advanced tools
The @rushstack/ts-command-line package provides a robust framework for building command-line interfaces (CLIs) with TypeScript. It offers features like argument parsing, tab completion, and rich command structures, making it easier to create complex and user-friendly command-line applications.
Defining commands
This feature allows developers to define custom commands by extending the CommandLineAction class. Each command can have its own parameters, help text, and execution logic.
{
import { CommandLineAction } from '@rushstack/ts-command-line';
class MyCommand extends CommandLineAction {
public constructor() {
super({
actionName: 'do-something',
summary: 'Does something interesting',
documentation: 'A longer description of the command.'
});
}
protected onDefineParameters(): void { /* Define parameters here */ }
protected onExecute(): Promise<void> {
// Your code here
console.log('Doing something...');
return Promise.resolve();
}
}
}
Parsing command line arguments
This feature involves creating a command line parser that can handle multiple commands, each with its own parameters. The parser takes care of interpreting the user's input and executing the corresponding command.
{
import { CommandLineParser } from '@rushstack/ts-command-line';
class MyCommandLine extends CommandLineParser {
public constructor() {
super({
toolFilename: 'my-tool',
toolDescription: 'This tool does amazing things.'
});
this.addAction(new MyCommand());
}
protected onDefineParameters(): void { /* Define global parameters here */ }
}
const myCommandLine = new MyCommandLine();
myCommandLine.execute();
}
Tab completion
This feature allows the CLI to support tab completion, improving user experience by allowing users to easily see available commands and options. Implementing this feature typically involves generating a script that users can source in their shell.
{
// Note: Actual implementation of tab completion requires setting up a shell script
// This example demonstrates registering a command that can generate such a script
import { CommandLineAction } from '@rushstack/ts-command-line';
class GenerateCompletionScriptCommand extends CommandLineAction {
public constructor() {
super({
actionName: 'generate-completion',
summary: 'Generates a script for tab completion.',
documentation: 'Generates a script to enable tab completion for the CLI.'
});
}
protected onExecute(): Promise<void> {
console.log('Generating tab completion script...');
return Promise.resolve();
}
}
}
Commander is a popular npm package for building command-line interfaces. It offers a simpler API compared to @rushstack/ts-command-line, making it a good choice for smaller projects or those who prefer a more straightforward approach. However, it might lack some of the advanced structuring and TypeScript integration provided by @rushstack/ts-command-line.
Yargs is another widely used library for building command-line tools. It focuses on easy argument parsing and includes built-in support for things like command chaining and advanced argument validation. While it offers robust features for argument handling, @rushstack/ts-command-line provides a more structured approach to defining commands and parameters, especially beneficial for complex CLI applications.
Oclif (Open CLI Framework) is a framework for building command-line interfaces developed by Heroku. It supports both TypeScript and JavaScript and offers features like plugin support, auto-documentation, and testing utilities. Oclif and @rushstack/ts-command-line share a similar philosophy in providing a structured approach to CLI development, but oclif's plugin ecosystem and Heroku's backing might make it more appealing for certain projects.
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.
no gotchas for developers: Many command-line libraries store their parsed data in a simple JavaScript hash object. This is convenient for small projects. But suppose a large project has many different source files that define and read parameters. If you try to read 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 --help
docs as someone else's job. ts-command-line requires each every parameter to have a documentation string, and will automatically generate the --help
docs 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:
widget
. This is the name of your Node.js bin script.--verbose
, --force
, and --max-count
.--max-count
integer parameter. (Flags don't have arguments, because their value is determined by whether the flag was provided or not.)push
token is called an action. It acts as sub-command with its own unique set of parameters.--verbose
flag is a global parameter because it precedes the action name. It affects all actions.--force
flag 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:
class PushAction extends CommandLineAction {
private _force: CommandLineFlagParameter;
public constructor() {
super({
actionName: 'push',
summary: 'Pushes a widget to the service',
documentation: 'Your long description goes here.'
});
}
protected onExecute(): Promise<void> { // abstract
return BusinessLogic.doTheWork(this._force.value);
}
protected onDefineParameters(): void { // abstract
this._force = this.defineFlagParameter({
parameterLongName: '--force',
parameterShortName: '-f',
description: 'Push and overwrite any existing state'
});
}
}
Then we might define the parser subclass like this:
class WidgetCommandLine extends CommandLineParser {
private _verbose: CommandLineFlagParameter;
public constructor() {
super({
toolFilename: 'widget',
toolDescription: 'The widget tool is really great.'
});
this.addAction(new PushAction());
}
protected onDefineParameters(): void { // abstract
this._verbose = this.defineFlagParameter({
parameterLongName: '--verbose',
parameterShortName: '-v',
description: 'Show extra logging detail'
});
}
protected onExecute(): Promise<void> { // override
BusinessLogic.configureLogger(this._verbose.value);
return super.onExecute();
}
}
To invoke the parser, the application entry point will do something like this:
const commandLine: WidgetCommandLine = new WidgetCommandLine();
commandLine.execute();
When we run widget --verbose push --force
, the PushAction.onExecute()
method will get invoked and then your business logic takes over.
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:
In this case, you can use the DynamicCommandLineAction
and DynamicCommandLineParser
classes which are not abstract (and not intended to be subclassed). Here's our above example rewritten for this model:
// Define the parser
const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({
toolFilename: 'widget',
toolDescription: 'The widget tool is really great.'
});
commandLineParser.defineFlagParameter({
parameterLongName: '--verbose',
parameterShortName: '-v',
description: 'Show extra logging detail'
});
// Define the action
const action: DynamicCommandLineAction = new DynamicCommandLineAction({
actionName: 'push',
summary: 'Pushes a widget to the service',
documentation: 'More detail about the "push" action'
});
commandLineParser.addAction(action);
action.defineFlagParameter({
parameterLongName: '--force',
parameterShortName: '-f',
description: 'Push and overwrite any existing state'
});
// Parse the command line
commandLineParser.execute(process.argv).then(() => {
console.log('The action is: ' + commandLineParser.selectedAction!.actionName);
console.log('The force flag is: ' + action.getFlagParameter('--force').value);
});
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:
FAQs
An object-oriented command-line parser for TypeScript
The npm package @rushstack/ts-command-line receives a total of 1,989,001 weekly downloads. As such, @rushstack/ts-command-line popularity was classified as popular.
We found that @rushstack/ts-command-line demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
In an open letter, JavaScript community leaders urge Oracle to give up the JavaScript trademark, arguing that it has been effectively abandoned through nonuse.
Security News
The initial version of the Socket Python SDK is now on PyPI, enabling developers to more easily interact with the Socket REST API in Python projects.
Security News
Floating dependency ranges in npm can introduce instability and security risks into your project by allowing unverified or incompatible versions to be installed automatically, leading to unpredictable behavior and potential conflicts.