Security News
tea.xyz Spam Plagues npm and RubyGems Package Registries
Tea.xyz, a crypto project aimed at rewarding open source contributions, is once again facing backlash due to an influx of spam packages flooding public package registries.
classy-commander
Advanced tools
Readme
A TypeScript wrapper for Commander that lets you easily declare commands using classes & decorators and provides you with strongly typed arguments.
npm install classy-commander --save
First enable support for decorators in your tsconfig.json
compiler options.
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
}
}
Let's create a simple Calculator CLI app with a command that adds two numbers.
Our entry-point looks like this.
./calc.ts
import * as cli from 'classy-commander';
import './commands/add.ts';
cli.execute();
Our add command looks like this.
./commands/add.ts
import { Command, command, value } from 'classy-commander';
export class AddCommandParams {
@value()
value1: number = 0;
@value()
value2: number = 0;
}
@command('add', AddCommandParams, 'Adds two numbers')
export class AddCommand implements Command<AddCommandParams> {
execute(params: AddCommandParams) {
const { value1, value2 } = params;
const result = value1 + value2;
console.log(`${value1} + ${value2} = ${result}`);
}
}
For simplicity, we'll use ts-node to run our app.
Running ts-node ./calc add 1 2
outputs:
1 + 2 = 3
But what if we want to add 3 numbers?
Lets allow adding an optional third number.
import { Command, command, value } from 'classy-commander';
export class AddCommandParams {
@value()
value1: number = 0;
@value()
value2: number = 0;
@value({ optional: true })
value3: number = 0;
}
@command('add', AddCommandParams, 'Adds two or three numbers')
export class AddCommand implements Command<AddCommandParams> {
execute(params: AddCommandParams) {
const { value1, value2, value3 } = params;
const result = value1 + value2 + value3;
if (value3) {
console.log(`${value1} + ${value2} + ${value3} = ${result}`);
} else {
console.log(`${value1} + ${value2} = ${result}`);
}
}
}
Running ts-node ./calc add 1 2 3
now outputs:
1 + 2 + 3 = 6
Adding two numbers still works. ts-node ./calc add 1 2
outputs:
1 + 2 = 3
Okay, but what if we want to add 4 numbers, or 5? This could get messy.
It's time to turn our values into a variadic value.
import { Command, command, value } from 'classy-commander';
export class AddCommandParams {
@value({ variadic: { type: Number } })
values: number[] = [];
}
@command('add', AddCommandParams, 'Adds two or more numbers')
export class AddCommand implements Command<AddCommandParams> {
execute(params: AddCommandParams) {
const { values } = params;
const result = values.reduce((total, val) => total + val, 0);
console.log(`${values.join(' + ')} = ${result}`);
}
}
Running ts-node ./calc add 1 2 3 4 5
now outputs:
1 + 2 + 3 + 4 + 5 = 15
Let's add an option to show thousand separators.
import { Command, command, option, value } from 'classy-commander';
export class AddCommandParams {
@value({ variadic: { type: Number } })
values: number[] = [];
@option({ shortName: 't' })
thousandSeparators: boolean = false;
}
@command('add', AddCommandParams, 'Adds two or more numbers')
export class AddCommand implements Command<AddCommandParams> {
execute(params: AddCommandParams) {
const { values, thousandSeparators } = params;
const result = values.reduce((total, val) => total + val, 0);
const format = (val: number) => val.toLocaleString(undefined, {
useGrouping: thousandSeparators
});
console.log(`${values.map((val) => format(val)).join(' + ')} = ${format(result)}`);
}
}
Running ts-node ./calc add 500 1000 --thousandSeparators
or ts-node ./calc add 500 1000 -t
will output:
500 + 1,000 = 1,500
Lets add an option with a value that lets us specify the number of decimal places to show.
import { Command, command, option, value } from 'classy-commander';
export class AddCommandParams {
@value({ variadic: { type: Number } })
values: number[] = [];
@option({ shortName: 't' })
thousandSeparators: boolean = false;
@option({ shortName: 'd', valueName: 'count' })
decimalPlaces: number = 0;
}
@command('add', AddCommandParams, 'Adds two or more numbers')
export class AddCommand implements Command<AddCommandParams> {
execute(params: AddCommandParams) {
const { values, thousandSeparators, decimalPlaces } = params;
const result = values.reduce((total, val) => total + val, 0);
const format = (val: number) => val.toLocaleString(undefined, {
useGrouping: thousandSeparators,
maximumFractionDigits: decimalPlaces
});
console.log(`${values.map((val) => format(val)).join(' + ')} = ${format(result)}`);
}
}
Running ts-node ./calc add 1 2.2345 --decimalPlaces 2
will output:
1 + 2.23 = 3.23
Running ts-node ./calc.ts --help
outputs:
Usage: calc [options] [command]
Options:
-h, --help output usage information
Commands:
add [options] <values...>
Running ts-node ./calc.ts add --help
shows the usage for our add
command:
Usage: add [options] <values...>
Options:
-t, --thousandSeparators
-d, --decimalPlaces <count> (default: 0)
-h, --help output usage information
To keep our add command easy to test, lets move that heavy math into a calculator service, and have that service automatically injected into the command when it gets created. Let's use the awesome Inversify library which has excellent support for TypeScript (though in principal we could use any JavaScript Dependency Injection library).
Let's start by adding the calculator service.
./services/calculator.ts
import { injectable } from 'inversify';
@injectable()
export class Calculator {
add(...amounts: number[]) {
return amounts.reduce((total, amount) => total + amount, 0);
}
}
Now lets update our add command to use the service.
./commands/add.ts
import { injectable } from 'inversify';
import { Command, command, option, value } from 'classy-commander';
import { Calculator } from '../services/calculator';
export class AddCommandParams {
@value({ variadic: { type: Number } })
values: number[] = [];
@option({ shortName: 't' })
thousandSeparators: boolean = false;
@option({ shortName: 'd', valueName: 'count' })
decimalPlaces: number = 0;
}
@command('add', AddCommandParams, 'Adds two or more numbers')
@injectable()
export class AddCommand implements Command<AddCommandParams> {
constructor(private calculator: Calculator) {
}
execute(params: AddCommandParams) {
const { values, thousandSeparators, decimalPlaces } = params;
const result = this.calculator.add(...values);
const format = (val: number) => val.toLocaleString(undefined, {
useGrouping: thousandSeparators,
maximumFractionDigits: decimalPlaces
});
console.log(`${values.map((val) => format(val)).join(' + ')} = ${format(result)}`);
}
}
Finally, in our entrypoint, lets create our inversify container and pass it to classy-commander.
./calc.ts
import { Container } from 'inversify';
import * as cli from 'classy-commander';
import './commands/add.ts';
import './services/calculator';
const container = new Container({ autoBindInjectable: true });
cli
.ioc(container)
.execute();
There are two ways to specify the version of your CLI:
Using the version in your package.json
.
import * as cli from 'classy-commander';
...
cli
.versionFromPackage(__dirname)
.execute();
Or manually.
import * as cli from 'classy-commander';
...
cli
.version('1.2.3')
.execute();
Maybe we end up adding a bunch of commands to our CLI app and we don't want to manually import each command in our entry point like below:
import * as cli from 'classy-commander';
import './commands/add.ts';
import './commands/subtract.ts';
import './commands/multiply.ts';
import './commands/divide.ts';
import './commands/square.ts';
import './commands/squareRoot.ts';
import './commands/cube.ts';
import './commands/cubeRoot.ts';
cli.execute();
We can tell classy-commander to dynamically load all commands from a directory thus reducing our imports.
import * as cli from 'classy-commander';
import * as path from 'path';
async function run() {
await cli.commandsFromDirectory(path.join(__dirname, '/commands'));
cli.execute();
}
run().catch(console.error);
Got an issue or a feature request? Log it.
Pull-requests are also welcome. 😸
FAQs
A TypeScript wrapper for Commander that lets you easily declare commands using classes & decorators and provides strongly typed arguments.
The npm package classy-commander receives a total of 52 weekly downloads. As such, classy-commander popularity was classified as not popular.
We found that classy-commander demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer 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
Tea.xyz, a crypto project aimed at rewarding open source contributions, is once again facing backlash due to an influx of spam packages flooding public package registries.
Security News
As cyber threats become more autonomous, AI-powered defenses are crucial for businesses to stay ahead of attackers who can exploit software vulnerabilities at scale.
Security News
UnitedHealth Group disclosed that the ransomware attack on Change Healthcare compromised protected health information for millions in the U.S., with estimated costs to the company expected to reach $1 billion.