{"id":14155867,"url":"https://github.com/codeandcats/classy-commander","last_synced_at":"2025-04-15T06:28:21.711Z","repository":{"id":32992981,"uuid":"148303749","full_name":"codeandcats/classy-commander","owner":"codeandcats","description":"A TypeScript wrapper for Commander that lets you easily declare commands using classes \u0026 decorators and provides you strongly typed arguments.","archived":false,"fork":false,"pushed_at":"2024-04-06T03:05:02.000Z","size":1650,"stargazers_count":10,"open_issues_count":21,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-06T15:14:55.401Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codeandcats.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":"support/package.ts","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-09-11T11:00:56.000Z","updated_at":"2024-07-11T00:09:49.000Z","dependencies_parsed_at":"2024-07-12T01:42:58.000Z","dependency_job_id":"6260d950-9211-46fc-9663-10daae70e0d6","html_url":"https://github.com/codeandcats/classy-commander","commit_stats":null,"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeandcats%2Fclassy-commander","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeandcats%2Fclassy-commander/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeandcats%2Fclassy-commander/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeandcats%2Fclassy-commander/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codeandcats","download_url":"https://codeload.github.com/codeandcats/classy-commander/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249019932,"owners_count":21199454,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-17T08:05:03.409Z","updated_at":"2025-04-15T06:28:21.675Z","avatar_url":"https://github.com/codeandcats.png","language":"TypeScript","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/codeandcats/classy-commander/master/img/logo.png\" /\u003e\n  \u003cp align=\"center\"\u003e\n    A TypeScript wrapper for \u003ca href=\"https://github.com/tj/commander.js/\"\u003eCommander\u003c/a\u003e that lets you easily declare commands using classes \u0026 decorators and provides you with strongly typed arguments.\n  \u003cp\u003e\n\u003c/p\u003e\n\n[![npm version](https://badge.fury.io/js/classy-commander.svg)](https://badge.fury.io/js/classy-commander)\n[![Build Status](https://travis-ci.org/codeandcats/classy-commander.svg?branch=master)](https://travis-ci.org/codeandcats/classy-commander)\n[![Coverage Status](https://coveralls.io/repos/github/codeandcats/classy-commander/badge.svg?branch=master)](https://coveralls.io/github/codeandcats/classy-commander?branch=master)\n\n\n## Features\n- Write commands as modular classes that can be easily tested\n- Specify command usage via a class with decorators\n- Command values\n- Optional values\n- Options\n- Options with values\n- Automatic coercion\n- Version from package.json\n- Support for Inversion of Control containers like [Inversify](http://inversify.io/)\n\n\n\n## Install\n```sh\nnpm install classy-commander --save\n```\n\n\n## Usage\nFirst enable support for decorators in your `tsconfig.json` compiler options.\n\n```json\n{\n  \"compilerOptions\": {\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n  }\n}\n```\n\nLet's create a simple Calculator CLI app with a command that adds two numbers.\n\nOur entry-point looks like this.\n\n`./calc.ts`\n```typescript\nimport * as cli from 'classy-commander';\n\nimport './commands/add.ts';\n\ncli.execute();\n```\n\nOur add command looks like this.\n\n`./commands/add.ts`\n```typescript\nimport { Command, command, value } from 'classy-commander';\n\nexport class AddCommandParams {\n  @value()\n  value1: number = 0;\n\n  @value()\n  value2: number = 0;\n}\n\n@command('add', AddCommandParams, 'Adds two numbers')\nexport class AddCommand implements Command\u003cAddCommandParams\u003e {\n\n  execute(params: AddCommandParams) {\n    const { value1, value2 } = params;\n\n    const result = value1 + value2;\n\n    console.log(`${value1} + ${value2} = ${result}`);\n  }\n\n}\n```\n\nFor simplicity, we'll use [ts-node](https://github.com/TypeStrong/ts-node) to run our app.\n\nRunning `ts-node ./calc add 1 2` outputs:\n\n```\n1 + 2 = 3\n```\n\n\n\n## Using optional values\n\nBut what if we want to add 3 numbers?\n\nLets allow adding an _optional_ third number.\n\n```typescript\nimport { Command, command, value } from 'classy-commander';\n\nexport class AddCommandParams {\n  @value()\n  value1: number = 0;\n\n  @value()\n  value2: number = 0;\n\n  @value({ optional: true })\n  value3: number = 0;\n}\n\n@command('add', AddCommandParams, 'Adds two or three numbers')\nexport class AddCommand implements Command\u003cAddCommandParams\u003e {\n\n  execute(params: AddCommandParams) {\n    const { value1, value2, value3 } = params;\n\n    const result = value1 + value2 + value3;\n\n    if (value3) {\n      console.log(`${value1} + ${value2} + ${value3} = ${result}`);\n    } else {\n      console.log(`${value1} + ${value2} = ${result}`);\n    }\n  }\n\n}\n```\n\nRunning `ts-node ./calc add 1 2 3` now outputs:\n\n```\n1 + 2 + 3 = 6\n```\n\nAdding two numbers still works. `ts-node ./calc add 1 2` outputs:\n```\n1 + 2 = 3\n```\n\n\n\n## Variadic Arguments\n\nOkay, but what if we want to add 4 numbers, or 5? This could get messy.\n\nIt's time to turn our values into a variadic value.\n\n```typescript\nimport { Command, command, value } from 'classy-commander';\n\nexport class AddCommandParams {\n  @value({ variadic: { type: Number } })\n  values: number[] = [];\n}\n\n@command('add', AddCommandParams, 'Adds two or more numbers')\nexport class AddCommand implements Command\u003cAddCommandParams\u003e {\n\n  execute(params: AddCommandParams) {\n    const { values } = params;\n\n    const result = values.reduce((total, val) =\u003e total + val, 0);\n\n    console.log(`${values.join(' + ')} = ${result}`);\n  }\n\n}\n```\n\nRunning `ts-node ./calc add 1 2 3 4 5` now outputs:\n\n```\n1 + 2 + 3 + 4 + 5 = 15\n```\n\n\n\n## Using options\n\nLet's add an option to show thousand separators.\n\n```typescript\nimport { Command, command, option, value } from 'classy-commander';\n\nexport class AddCommandParams {\n  @value({ variadic: { type: Number } })\n  values: number[] = [];\n\n  @option({ shortName: 't' })\n  thousandSeparators: boolean = false;\n}\n\n@command('add', AddCommandParams, 'Adds two or more numbers')\nexport class AddCommand implements Command\u003cAddCommandParams\u003e {\n\n  execute(params: AddCommandParams) {\n    const { values, thousandSeparators } = params;\n\n    const result = values.reduce((total, val) =\u003e total + val, 0);\n\n    const format = (val: number) =\u003e val.toLocaleString(undefined, {\n      useGrouping: thousandSeparators\n    });\n\n    console.log(`${values.map((val) =\u003e format(val)).join(' + ')} = ${format(result)}`);\n  }\n\n}\n```\n\nRunning `ts-node ./calc add 500 1000 --thousandSeparators` or `ts-node ./calc add 500 1000 -t` will output:\n\n```\n500 + 1,000 = 1,500\n```\n\n\n\n## Using option values\n\nLets add an option with a value that lets us specify the number of decimal places to show.\n\n```typescript\nimport { Command, command, option, value } from 'classy-commander';\n\nexport class AddCommandParams {\n  @value({ variadic: { type: Number } })\n  values: number[] = [];\n\n  @option({ shortName: 't' })\n  thousandSeparators: boolean = false;\n\n  @option({ shortName: 'd', valueName: 'count' })\n  decimalPlaces: number = 0;\n}\n\n@command('add', AddCommandParams, 'Adds two or more numbers')\nexport class AddCommand implements Command\u003cAddCommandParams\u003e {\n\n  execute(params: AddCommandParams) {\n    const { values, thousandSeparators, decimalPlaces } = params;\n\n    const result = values.reduce((total, val) =\u003e total + val, 0);\n\n    const format = (val: number) =\u003e val.toLocaleString(undefined, {\n      useGrouping: thousandSeparators,\n      maximumFractionDigits: decimalPlaces\n    });\n\n    console.log(`${values.map((val) =\u003e format(val)).join(' + ')} = ${format(result)}`);\n  }\n\n}\n```\n\nRunning `ts-node ./calc add 1 2.2345 --decimalPlaces 2` will output:\n\n```\n1 + 2.23 = 3.23\n```\n\n\n\n## Getting usage\n\nRunning `ts-node ./calc.ts --help` outputs:\n\n```\n  Usage: calc [options] [command]\n\nOptions:\n\n  -h, --help                 output usage information\n\nCommands:\n\n  add [options] \u003cvalues...\u003e\n```\n\nRunning `ts-node ./calc.ts add --help` shows the usage for our `add` command:\n\n```\nUsage: add [options] \u003cvalues...\u003e\n\nOptions:\n\n  -t, --thousandSeparators\n  -d, --decimalPlaces \u003ccount\u003e   (default: 0)\n  -h, --help                   output usage information\n```\n\n\n\n## Dependency Injection\nTo 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](http://inversify.io/) library which has excellent support for TypeScript (though in principal we could use any JavaScript Dependency Injection library).\n\nLet's start by adding the calculator service.\n\n`./services/calculator.ts`\n```typescript\nimport { injectable } from 'inversify';\n\n@injectable()\nexport class Calculator {\n  add(...amounts: number[]) {\n    return amounts.reduce((total, amount) =\u003e total + amount, 0);\n  }\n}\n```\n\nNow lets update our add command to use the service.\n\n`./commands/add.ts`\n```typescript\nimport { injectable } from 'inversify';\nimport { Command, command, option, value } from 'classy-commander';\nimport { Calculator } from '../services/calculator';\n\nexport class AddCommandParams {\n  @value({ variadic: { type: Number } })\n  values: number[] = [];\n\n  @option({ shortName: 't' })\n  thousandSeparators: boolean = false;\n\n  @option({ shortName: 'd', valueName: 'count' })\n  decimalPlaces: number = 0;\n}\n\n@command('add', AddCommandParams, 'Adds two or more numbers')\n@injectable()\nexport class AddCommand implements Command\u003cAddCommandParams\u003e {\n  constructor(private calculator: Calculator) {\n  }\n\n  execute(params: AddCommandParams) {\n    const { values, thousandSeparators, decimalPlaces } = params;\n\n    const result = this.calculator.add(...values);\n\n    const format = (val: number) =\u003e val.toLocaleString(undefined, {\n      useGrouping: thousandSeparators,\n      maximumFractionDigits: decimalPlaces\n    });\n\n    console.log(`${values.map((val) =\u003e format(val)).join(' + ')} = ${format(result)}`);\n  }\n\n}\n```\n\nFinally, in our entrypoint, lets create our inversify container and pass it to classy-commander.\n\n`./calc.ts`\n```typescript\nimport { Container } from 'inversify';\nimport * as cli from 'classy-commander';\n\nimport './commands/add.ts';\nimport './services/calculator';\n\nconst container = new Container({ autoBindInjectable: true });\n\ncli\n  .ioc(container)\n  .execute();\n```\n\n\n\n## Specifying the version\nThere are two ways to specify the version of your CLI:\n\nUsing the version in your `package.json`.\n\n```typescript\nimport * as cli from 'classy-commander';\n\n...\n\ncli\n  .versionFromPackage(__dirname)\n  .execute();\n```\n\nOr manually.\n\n```typescript\nimport * as cli from 'classy-commander';\n\n...\n\ncli\n  .version('1.2.3')\n  .execute();\n```\n\n## Loading commands from a directory\nMaybe 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:\n\n```typescript\nimport * as cli from 'classy-commander';\n\nimport './commands/add.ts';\nimport './commands/subtract.ts';\nimport './commands/multiply.ts';\nimport './commands/divide.ts';\nimport './commands/square.ts';\nimport './commands/squareRoot.ts';\nimport './commands/cube.ts';\nimport './commands/cubeRoot.ts';\n\ncli.execute();\n```\n\nWe can tell classy-commander to dynamically load all commands from a directory thus reducing our imports.\n\n```typescript\nimport * as cli from 'classy-commander';\nimport * as path from 'path';\n\nasync function run() {\n  await cli.commandsFromDirectory(path.join(__dirname, '/commands'));\n  cli.execute();\n}\n\nrun().catch(console.error);\n```\n\n## Contributing\nGot an issue or a feature request? [Log it](https://github.com/codeandcats/classy-commander/issues).\n\n[Pull-requests](https://github.com/codeandcats/classy-commander/pulls) are also welcome. 😸\n","funding_links":[],"categories":["others"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodeandcats%2Fclassy-commander","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodeandcats%2Fclassy-commander","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodeandcats%2Fclassy-commander/lists"}